From 3614aff961f21ee6c3e02e0ac842b0382e09c1bd Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Sat, 22 Feb 2025 10:17:25 +0300 Subject: [PATCH 1/2] feat: introduce `--ext-override` option to process non-std (non js/ts like) file extensions resolves #1104 --- man/zx.1 | 2 ++ src/cli.ts | 5 +++-- test/cli.test.js | 6 ++++++ test/export.test.js | 1 + test/fixtures/non-std-ext.zx | 19 +++++++++++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100755 test/fixtures/non-std-ext.zx diff --git a/man/zx.1 b/man/zx.1 index 8279582e1d..8b233cd44e 100644 --- a/man/zx.1 +++ b/man/zx.1 @@ -25,6 +25,8 @@ prefer locally installed packages bins evaluate script .SS --ext=<.mjs> default extension +.SS --ext-override +override script extensions .SS --install, -i install dependencies .SS --registry= diff --git a/src/cli.ts b/src/cli.ts index 1aa3a1cfe4..2bfe1c3b0c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -65,6 +65,7 @@ export function printUsage() { --cwd= set current directory --eval=, -e evaluate script --ext=<.mjs> default extension + --ext-override override script extensions --install, -i install dependencies --registry= npm registry, defaults to https://registry.npmjs.org/ --version, -v print current zx version @@ -80,7 +81,7 @@ export function printUsage() { // prettier-ignore export const argv: minimist.ParsedArgs = parseArgv(process.argv.slice(2), { string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry', 'env'], - boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local'], + boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local', 'ext-override'], alias: { e: 'eval', i: 'install', v: 'version', h: 'help', l: 'prefer-local', 'env-file': 'env' }, stopEarly: true, parseBoolean: true, @@ -180,7 +181,7 @@ async function readScript() { } const { ext, base, dir } = path.parse(tempPath || scriptPath) - if (ext === '') { + if (ext === '' || argv.extOverride) { tempPath = getFilepath(dir, base) } if (ext === '.md') { diff --git a/test/cli.test.js b/test/cli.test.js index 8a095ec7e2..64eaa7b679 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -223,6 +223,12 @@ describe('cli', () => { ) }) + test('scripts with non standard extension (override)', async () => { + const o = + await $`node build/cli.js --ext-override test/fixtures/non-std-ext.zx` + assert.ok(o.stdout.trim().endsWith('zx/test/fixtures/non-std-ext.zx.mjs')) + }) + test22('scripts from stdin with explicit extension', async () => { const out = await $`node --experimental-strip-types build/cli.js --ext='.ts' <<< 'const foo: string = "bar"; console.log(foo)'` diff --git a/test/export.test.js b/test/export.test.js index 6e07584e51..9bbe26981b 100644 --- a/test/export.test.js +++ b/test/export.test.js @@ -64,6 +64,7 @@ describe('cli', () => { assert.equal(typeof cli.argv, 'object', 'cli.argv') assert.equal(typeof cli.argv._, 'object', 'cli.argv._') assert.equal(typeof cli.argv.experimental, 'boolean', 'cli.argv.experimental') + assert.equal(typeof cli.argv.extOverride, 'boolean', 'cli.argv.extOverride') assert.equal(typeof cli.argv.h, 'boolean', 'cli.argv.h') assert.equal(typeof cli.argv.help, 'boolean', 'cli.argv.help') assert.equal(typeof cli.argv.i, 'boolean', 'cli.argv.i') diff --git a/test/fixtures/non-std-ext.zx b/test/fixtures/non-std-ext.zx new file mode 100755 index 0000000000..37d93bfc59 --- /dev/null +++ b/test/fixtures/non-std-ext.zx @@ -0,0 +1,19 @@ +#!/usr/bin/env zx + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +console.log(chalk.yellowBright`If file has non-std ext and 'ext-override' option specified, zx assumes it's ESM.`) +await $`pwd` +console.log('__filename =', __filename) From e4b8e48eaa45382ea75bd51ce1f964365f956989 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Sat, 22 Feb 2025 16:04:50 +0300 Subject: [PATCH 2/2] refactor(cli): use `ext` option to override non-js-like script extensions --- docs/cli.md | 15 ++++++++++++--- man/zx.1 | 4 +--- src/cli.ts | 8 ++++---- test/cli.test.js | 9 +++++++-- test/export.test.js | 1 - 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 729693fa03..65260c6412 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -13,7 +13,16 @@ assumes that it is an [ESM](https://nodejs.org/api/modules.html#modules_module_createrequire_filename) module unless the `--ext` option is specified. +## Non-standard extension +`zx` internally loads scripts via `import` API, so you can use any extension supported by the runtime (nodejs, deno, bun) or apply a [custom loader](https://nodejs.org/api/cli.html#--experimental-loadermodule). +However, if the script has a non-js-like extension (`/^\.[mc]?[jt]sx?$/`) and the `--ext` is specified, it will be used. +```bash +zx script.zx # Unknown file extension "\.zx" +zx --ext=mjs script.zx # OK +``` + +## Markdown ```bash zx docs/markdown.md ``` @@ -89,10 +98,10 @@ Enable verbose mode. ## `--shell` -Specify a custom shell binary. +Specify a custom shell binary path. By default, zx refers to `bash`. ```bash -zx --shell=/bin/bash script.mjs +zx --shell=/bin/another/sh script.mjs ``` ## `--prefer-local, -l` @@ -131,7 +140,7 @@ When `cwd` option is specified, it will be used as base path: ## `--ext` -Override the default (temp) script extension. Default is `.mjs`. +Overrides the default script extension (`.mjs`). ## `--version, -v` diff --git a/man/zx.1 b/man/zx.1 index 8b233cd44e..09f9ee513e 100644 --- a/man/zx.1 +++ b/man/zx.1 @@ -24,9 +24,7 @@ prefer locally installed packages bins .SS --eval=, -e evaluate script .SS --ext=<.mjs> -default extension -.SS --ext-override -override script extensions +script extension .SS --install, -i install dependencies .SS --registry= diff --git a/src/cli.ts b/src/cli.ts index 2bfe1c3b0c..e0cf848c0c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -35,6 +35,7 @@ import { randomId, bufToString } from './util.js' import { createRequire, type minimist } from './vendor.js' const EXT = '.mjs' +const EXT_RE = /^\.[mc]?[jt]sx?$/ isMain() && main().catch((err) => { @@ -64,8 +65,7 @@ export function printUsage() { --prefer-local, -l prefer locally installed packages bins --cwd= set current directory --eval=, -e evaluate script - --ext=<.mjs> default extension - --ext-override override script extensions + --ext=<.mjs> script extension --install, -i install dependencies --registry= npm registry, defaults to https://registry.npmjs.org/ --version, -v print current zx version @@ -81,7 +81,7 @@ export function printUsage() { // prettier-ignore export const argv: minimist.ParsedArgs = parseArgv(process.argv.slice(2), { string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry', 'env'], - boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local', 'ext-override'], + boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local'], alias: { e: 'eval', i: 'install', v: 'version', h: 'help', l: 'prefer-local', 'env-file': 'env' }, stopEarly: true, parseBoolean: true, @@ -181,7 +181,7 @@ async function readScript() { } const { ext, base, dir } = path.parse(tempPath || scriptPath) - if (ext === '' || argv.extOverride) { + if (ext === '' || (argv.ext && !EXT_RE.test(ext))) { tempPath = getFilepath(dir, base) } if (ext === '.md') { diff --git a/test/cli.test.js b/test/cli.test.js index 64eaa7b679..3698130c7f 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -223,10 +223,15 @@ describe('cli', () => { ) }) - test('scripts with non standard extension (override)', async () => { + test('scripts with non standard extension', async () => { const o = - await $`node build/cli.js --ext-override test/fixtures/non-std-ext.zx` + await $`node build/cli.js --ext='.mjs' test/fixtures/non-std-ext.zx` assert.ok(o.stdout.trim().endsWith('zx/test/fixtures/non-std-ext.zx.mjs')) + + await assert.rejects( + $`node build/cli.js test/fixtures/non-std-ext.zx`, + /Unknown file extension "\.zx"/ + ) }) test22('scripts from stdin with explicit extension', async () => { diff --git a/test/export.test.js b/test/export.test.js index 9bbe26981b..6e07584e51 100644 --- a/test/export.test.js +++ b/test/export.test.js @@ -64,7 +64,6 @@ describe('cli', () => { assert.equal(typeof cli.argv, 'object', 'cli.argv') assert.equal(typeof cli.argv._, 'object', 'cli.argv._') assert.equal(typeof cli.argv.experimental, 'boolean', 'cli.argv.experimental') - assert.equal(typeof cli.argv.extOverride, 'boolean', 'cli.argv.extOverride') assert.equal(typeof cli.argv.h, 'boolean', 'cli.argv.h') assert.equal(typeof cli.argv.help, 'boolean', 'cli.argv.help') assert.equal(typeof cli.argv.i, 'boolean', 'cli.argv.i')