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})