main
  1// Copyright 2021 Google LLC
  2//
  3// Licensed under the Apache License, Version 2.0 (the "License");
  4// you may not use this file except in compliance with the License.
  5// You may obtain a copy of the License at
  6//
  7//     https://www.apache.org/licenses/LICENSE-2.0
  8//
  9// Unless required by applicable law or agreed to in writing, software
 10// distributed under the License is distributed on an "AS IS" BASIS,
 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12// See the License for the specific language governing permissions and
 13// limitations under the License.
 14
 15import assert from 'node:assert'
 16import { test, describe } from 'node:test'
 17import { $, tmpfile, tmpdir, fs, path } from '../build/index.js'
 18import { installDeps, parseDeps } from '../build/deps.cjs'
 19
 20const __dirname = new URL('.', import.meta.url).pathname
 21const root = path.resolve(__dirname, '..')
 22const cli = path.resolve(root, 'build/cli.js')
 23
 24describe('deps', () => {
 25  describe('installDeps()', () => {
 26    const pkgjson = tmpfile(
 27      'package.json',
 28      '{"name": "temp", "version": "0.0.0"}'
 29    )
 30    const cwd = path.dirname(pkgjson)
 31    const t$ = $({ cwd })
 32    const load = (dep) =>
 33      fs.readJsonSync(path.join(cwd, 'node_modules', dep, 'package.json'))
 34
 35    test('loader works via JS API', async () => {
 36      await installDeps(
 37        {
 38          cpy: '9.0.1',
 39          'lodash-es': '4.17.21',
 40        },
 41        cwd
 42      )
 43      assert(load('cpy').name === 'cpy')
 44      assert(load('lodash-es').name === 'lodash-es')
 45    })
 46
 47    test('loader works via JS API with custom npm registry URL', async () => {
 48      await installDeps(
 49        {
 50          '@jsr/std__internal': '1.0.5',
 51        },
 52        cwd,
 53        'https://npm.jsr.io'
 54      )
 55
 56      assert(load('@jsr/std__internal').name === '@jsr/std__internal')
 57    })
 58
 59    test('loader works via CLI', async () => {
 60      const out =
 61        await t$`node ${cli} --install <<< 'import _ from "lodash" /* @4.17.15 */; console.log(_.VERSION)'`
 62      assert.match(out.stdout, /4.17.15/)
 63    })
 64
 65    test('loader works via CLI with custom npm registry URL', async () => {
 66      const code =
 67        'import { diff } from "@jsr/std__internal";console.log(diff instanceof Function)'
 68      const file = tmpfile('index.mjs', code)
 69
 70      let out = await t$`node ${cli} --i --registry=https://npm.jsr.io ${file}`
 71      fs.remove(file)
 72      assert.match(out.stdout, /true/)
 73
 74      out = await t$`node ${cli}  -i --registry=https://npm.jsr.io <<< ${code}`
 75      assert.match(out.stdout, /true/)
 76    })
 77
 78    test('throws on invalid installer type', async () => {
 79      await assert.rejects(
 80        () =>
 81          installDeps({ foo: 'latest' }, cwd, 'https://npm.jsr.io', 'invalid'),
 82        {
 83          message: /Unsupported installer type: invalid. Supported types: npm/,
 84        }
 85      )
 86    })
 87
 88    test('does nothing on empty deps', async () => {
 89      const cwd = tmpdir()
 90      await installDeps({}, cwd)
 91      assert(!fs.existsSync(path.join(cwd, 'node_modules')))
 92    })
 93  })
 94
 95  describe('parseDeps()', () => {
 96    test('import or require', async () => {
 97      ;[
 98        [`import "foo"`, { foo: 'latest' }],
 99        [`import "foo"`, { foo: 'latest' }],
100        [`import * as bar from "foo"`, { foo: 'latest' }],
101        [`import('foo')`, { foo: 'latest' }],
102        [`require('foo')`, { foo: 'latest' }],
103        [`require('foo/bar')`, { foo: 'latest' }],
104        [`require('foo/bar.js')`, { foo: 'latest' }],
105        [`require('foo-bar')`, { 'foo-bar': 'latest' }],
106        [`require('foo_bar')`, { foo_bar: 'latest' }],
107        [`require('@foo/bar')`, { '@foo/bar': 'latest' }],
108        [`require('@foo/bar/baz')`, { '@foo/bar': 'latest' }],
109        [`require('foo.js')`, { 'foo.js': 'latest' }],
110
111        // ignores local deps
112        [`import '.'`, {}],
113        [`require('.')`, {}],
114        [`require('..')`, {}],
115        [`require('../foo.js')`, {}],
116        [`require('./foo.js')`, {}],
117
118        // ignores invalid pkg names
119        [`require('_foo')`, {}],
120        [`require('@')`, {}],
121        [`require('@/_foo')`, {}],
122        [`require('@foo')`, {}],
123      ].forEach(([input, result]) => {
124        assert.deepEqual(parseDeps(input), result)
125      })
126    })
127
128    test('import with org and filename', async () => {
129      assert.deepEqual(parseDeps(`import "@foo/bar/file"`), {
130        '@foo/bar': 'latest',
131      })
132    })
133
134    test('import with version', async () => {
135      assert.deepEqual(parseDeps(`import "foo" // @2.x`), { foo: '2.x' })
136      assert.deepEqual(parseDeps(`import "foo" // @^7`), { foo: '^7' })
137      assert.deepEqual(parseDeps(`import "foo" /* @1.2.x */`), { foo: '1.2.x' })
138    })
139
140    test('multiline', () => {
141      const contents = `
142  require('a') // @1.0.0
143  const b =require('b') /* @2.0.0 */
144  const c = {
145    c:require('c') /* @3.0.0 */,
146    d: await import('d') /* @4.0.0 */,
147    ...require('e') /* @5.0.0 */
148  }
149  const f = [...require('f') /* @6.0.0 */]
150  ;require('g'); // @7.0.0
151  const h = 1 *require('h') // @8.0.0
152  {require('i') /* @9.0.0 */}
153  import 'j' // @10.0.0
154
155  import fs from 'fs'
156  import path from 'path'
157  import foo from "foo"
158  // import aaa from 'a'
159  /* import bbb from 'b' */
160  import bar from "bar" /* @1.0.0 */
161  import baz from "baz" //    @^2.0
162  import qux from "@qux/pkg/entry" //    @^3.0
163  import {api as alias} from "qux/entry/index.js" // @^4.0.0-beta.0
164
165  const cpy = await import('cpy')
166  const { pick } = require("lodash") //  @4.17.15
167  `
168
169      assert.deepEqual(parseDeps(contents), {
170        a: '1.0.0',
171        b: '2.0.0',
172        c: '3.0.0',
173        d: '4.0.0',
174        e: '5.0.0',
175        f: '6.0.0',
176        g: '7.0.0',
177        h: '8.0.0',
178        i: '9.0.0',
179        j: '10.0.0',
180        foo: 'latest',
181        bar: '1.0.0',
182        baz: '^2.0',
183        '@qux/pkg': '^3.0',
184        qux: '^4.0.0-beta.0',
185        cpy: 'latest',
186        lodash: '4.17.15',
187      })
188    })
189  })
190})