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