main
1// Copyright 2024 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 { describe, test } from 'node:test'
17import {
18 $,
19 within,
20 path,
21 glob,
22 tempdir,
23 fs,
24 version,
25} from '../../build/index.js'
26
27const __dirname = new URL('.', import.meta.url).pathname
28const root = path.resolve(__dirname, '../..')
29const sync = async (from, to, entries) => {
30 for (const entry of entries)
31 await fs.copy(path.resolve(from, entry), path.join(to, entry))
32}
33
34describe('npm artifact', () => {
35 describe('contents', () => {
36 test('zx full', async () =>
37 within(async () => {
38 const tmp = tempdir()
39 $.cwd = tmp
40 $.quiet = true
41
42 // link
43 await $`ln -s ${path.resolve(root, 'node_modules')} ${path.resolve(tmp, 'node_modules')}`
44 await sync(root, tmp, [
45 'scripts',
46 'build',
47 'man',
48 'package.json',
49 'package-main.json',
50 'README.md',
51 'LICENSE',
52 ])
53
54 // pack / unpack
55 await $`mv package-main.json package.json`
56 const pack = await $`npm pack`
57 await $`tar xf ${pack}`
58 await $`rm ${pack}`.nothrow()
59
60 // run
61 const { stderr } =
62 await $`node -e 'import {$} from "./package/build/core.js"; $.verbose = true; await $\`echo hello\`'`
63 assert.match(stderr, /hello/)
64
65 // contents
66 const pkgJson = await fs.readJson(
67 path.resolve(tmp, 'package/package.json')
68 )
69 const files = await glob('**/*', {
70 cwd: path.resolve(tmp, 'package'),
71 absolute: false,
72 onlyFiles: true,
73 })
74
75 assert.equal(pkgJson.name, 'zx')
76 assert.equal(pkgJson.description, 'A tool for writing better scripts')
77 assert.equal(pkgJson.devDependencies, undefined)
78 assert.equal(pkgJson.prettier, undefined)
79 assert.equal(pkgJson.scripts, undefined)
80 assert.equal(pkgJson.volta, undefined)
81
82 assert.deepEqual(
83 files.sort(),
84 [
85 'LICENSE',
86 'README.md',
87 'package.json',
88 'man/zx.1',
89 'build/cli.cjs',
90 'build/cli.d.ts',
91 'build/cli.js',
92 'build/core.cjs',
93 'build/core.d.ts',
94 'build/core.js',
95 'build/deno.js',
96 'build/deps.cjs',
97 'build/deps.d.ts',
98 'build/error.d.ts',
99 'build/esblib.cjs',
100 'build/globals.cjs',
101 'build/globals.d.ts',
102 'build/globals.js',
103 'build/goods.d.ts',
104 'build/index.cjs',
105 'build/index.d.ts',
106 'build/index.js',
107 'build/internals.cjs',
108 'build/internals.d.ts',
109 'build/log.d.ts',
110 'build/md.d.ts',
111 'build/util.cjs',
112 'build/util.d.ts',
113 'build/versions.d.ts',
114 'build/vendor-core.cjs',
115 'build/vendor-core.d.ts',
116 'build/vendor-extra.cjs',
117 'build/vendor-extra.d.ts',
118 'build/vendor.cjs',
119 'build/vendor.d.ts',
120 'build/3rd-party-licenses',
121 ].sort()
122 )
123 }))
124
125 test('zx@lite', async () =>
126 within(async () => {
127 const tmp = tempdir()
128 $.cwd = tmp
129 $.quiet = true
130
131 // link
132 await $`ln -s ${path.resolve(root, 'node_modules')} ${path.resolve(tmp, 'node_modules')}`
133 await sync(root, tmp, [
134 'build',
135 'package.json',
136 'package-lite.json',
137 'README.md',
138 'LICENSE',
139 'scripts',
140 ])
141
142 // pack / unpack
143 await $`mv package-lite.json package.json`
144 const pack = await $`npm pack`
145 await $`tar xf ${pack}`
146 await $`rm ${pack}`.nothrow()
147
148 // run
149 const { stderr } =
150 await $`node -e 'import {$} from "./package/build/core.js"; $.verbose = true; await $\`echo hello\`'`
151 assert.match(stderr, /hello/)
152
153 // contents
154 const pkgJson = await fs.readJson(
155 path.resolve(tmp, 'package/package.json')
156 )
157 const files = await glob('**/*', {
158 cwd: path.resolve(tmp, 'package'),
159 absolute: false,
160 onlyFiles: true,
161 })
162
163 assert.equal(pkgJson.name, 'zx')
164 assert.equal(pkgJson.devDependencies, undefined)
165 assert.equal(pkgJson.bin, undefined)
166 assert.deepEqual(
167 files.sort(),
168 [
169 'LICENSE',
170 'README.md',
171 'build/3rd-party-licenses',
172 'build/core.cjs',
173 'build/core.d.ts',
174 'build/core.js',
175 'build/deno.js',
176 'build/error.d.ts',
177 'build/esblib.cjs',
178 'build/internals.cjs',
179 'build/internals.d.ts',
180 'build/log.d.ts',
181 'build/util.cjs',
182 'build/util.d.ts',
183 'build/vendor-core.cjs',
184 'build/vendor-core.d.ts',
185 'package.json',
186 ].sort()
187 )
188 }))
189 })
190
191 describe('compatibility', () => {
192 test('js', async () => {
193 const out = await within(async () => {
194 $.cwd = path.resolve(root, 'test/fixtures/js-project')
195 await $`npm i --no-package-lock`
196 return $`node node_modules/zx/build/cli.js --verbose script.js`
197 })
198 assert.match(out.stderr, /js-script/)
199 })
200
201 test('tsc', async () => {
202 const out = await within(async () => {
203 $.cwd = path.resolve(root, 'test/fixtures/ts-project')
204 await $`npm i --no-package-lock`
205 try {
206 await $`npx tsc`
207 } catch (err) {
208 throw new Error(err.stdout)
209 }
210 return $`node build/script.js`
211 })
212 assert.match(out.stderr, /ts-script/)
213 })
214
215 test('tsc (isolated)', async () => {
216 const tmp = tempdir()
217 const t$ = $({ cwd: tmp, quiet: true })
218 const zxdir = path.resolve(tmp, 'node_modules/zx')
219 const pkgJson = {
220 name: 'zx-test',
221 dependencies: {
222 typescript: '^5',
223 '@types/node': '*',
224 '@types/fs-extra': '*',
225 },
226 }
227
228 await fs.outputJSON(path.resolve(tmp, 'package.json'), pkgJson)
229 await t$`npm i`
230 await sync(root, zxdir, ['package.json', 'build']) // `file:<path>` dep mounts `node_modules` too, so we use cloning here
231
232 const tsconfig = {
233 compilerOptions: {
234 module: 'commonjs',
235 target: 'esnext',
236 outDir: 'bundle',
237 rootDir: 'src',
238 declaration: true,
239 declarationMap: false,
240 esModuleInterop: true,
241 },
242 include: ['src'],
243 }
244 const indexTs = `import {$} from 'zx'
245(async () => {
246 await $({verbose: true})\`echo hello\`
247})()
248`
249 await fs.outputJSON(path.resolve(tmp, 'tsconfig.json'), tsconfig)
250 await fs.outputFile(path.resolve(tmp, 'src/index.ts'), indexTs)
251
252 await t$`tsc`
253 const out = await t$`node bundle/index.js`.text()
254 assert.strictEqual(out, '$ echo hello\nhello\n')
255 })
256
257 test('esbuild (iife)', async () => {
258 const tmp = tempdir()
259 const t$ = $({ cwd: tmp, quiet: true })
260 const zxdir = path.resolve(tmp, 'node_modules/zx')
261 const pkgJson = {
262 name: 'zx-test',
263 dependencies: {
264 esbuild: '^0.25.8',
265 },
266 }
267
268 await sync(root, zxdir, ['package.json', 'build'])
269 await fs.outputJSON(path.resolve(tmp, 'package.json'), pkgJson)
270
271 const verJs = `import {version, $} from 'zx'
272(async () => {
273 await $({verbose: true})\`echo \${version}\`
274})()
275`
276 await fs.outputFile(path.resolve(tmp, 'src/ver.js'), verJs)
277 await fs.mkdir(path.resolve(tmp, 'bundle'))
278
279 await t$`npx esbuild src/ver.js --bundle --format=iife --platform=node > bundle/ver.js`
280 const out = await t$`node bundle/ver.js`.text()
281 assert.strictEqual(out, `$ echo ${version}\n${version}\n`)
282 })
283 })
284})