Commit 96d40f9
Changed files (4)
src/cli.ts
@@ -15,74 +15,97 @@
// limitations under the License.
import fs from 'fs-extra'
+import minimist from 'minimist'
import { createRequire } from 'node:module'
-import { tmpdir } from 'node:os'
import { basename, dirname, extname, join, resolve } from 'node:path'
import url from 'node:url'
import { updateArgv } from './goods.js'
-import { $, argv, chalk, fetch, ProcessOutput } from './index.js'
+import { $, chalk, fetch, ProcessOutput } from './index.js'
import { startRepl } from './repl.js'
import { randomId } from './util.js'
import { installDeps, parseDeps } from './deps.js'
+function printUsage() {
+ // language=txt
+ console.log(`
+ ${chalk.bold('zx ' + getVersion())}
+ A tool for writing better scripts
+
+ ${chalk.bold('Usage')}
+ zx [options] <script>
+
+ ${chalk.bold('Options')}
+ --quiet don't echo commands
+ --shell=<path> custom shell binary
+ --prefix=<command> prefix all commands
+ --interactive, -i start repl
+ --eval=<js>, -e evaluate script
+ --experimental enable new api proposals
+ --install parse and load script dependencies from the registry
+ --version, -v print current zx version
+ --help, -h print help
+`)
+}
+
+const argv = minimist(process.argv.slice(2), {
+ string: ['shell', 'prefix', 'eval'],
+ boolean: [
+ 'version',
+ 'help',
+ 'quiet',
+ 'install',
+ 'interactive',
+ 'experimental',
+ ],
+ alias: { e: 'eval', i: 'interactive', v: 'version', h: 'help' },
+ stopEarly: true,
+})
+
await (async function main() {
const globals = './globals.js'
await import(globals)
- if (argv.quiet) {
- $.verbose = false
+ if (argv.quiet) $.verbose = false
+ if (argv.shell) $.shell = argv.shell
+ if (argv.prefix) $.prefix = argv.prefix
+ if (argv.experimental) {
+ Object.assign(global, await import('./experimental.js'))
}
- if (typeof argv.shell === 'string') {
- $.shell = argv.shell
+ if (argv.version) {
+ console.log(getVersion())
+ return
}
- if (typeof argv.prefix === 'string') {
- $.prefix = argv.prefix
+ if (argv.help) {
+ printUsage()
+ return
}
- if (argv.experimental) {
- Object.assign(global, await import('./experimental.js'))
+ if (argv.interactive) {
+ startRepl()
+ return
}
- if (process.argv.length == 3) {
- if (['--version', '-v', '-V'].includes(process.argv[2])) {
- console.log(getVersion())
- return
- }
- if (['--help', '-h'].includes(process.argv[2])) {
- printUsage()
- return
- }
- if (['--interactive', '-i'].includes(process.argv[2])) {
- startRepl()
- return
- }
+ if (argv.eval) {
+ await runScript(argv.eval)
+ return
}
- if (argv.eval || argv.e) {
- const script = (argv.eval || argv.e).toString()
- await runScript(script)
+ const firstArg = argv._[0]
+ updateArgv(argv._.slice(firstArg === undefined ? 0 : 1))
+ if (!firstArg || firstArg === '-') {
+ const success = await scriptFromStdin()
+ if (!success) startRepl()
return
}
- let firstArg = process.argv.slice(2).find((a) => !a.startsWith('--'))
- if (typeof firstArg === 'undefined' || firstArg === '-') {
- let ok = await scriptFromStdin()
- if (!ok) {
- startRepl()
- }
- } else if (
- firstArg.startsWith('http://') ||
- firstArg.startsWith('https://')
- ) {
+ if (/^https?:/.test(firstArg)) {
await scriptFromHttp(firstArg)
+ return
+ }
+ let filepath
+ if (firstArg.startsWith('/')) {
+ filepath = firstArg
+ } else if (firstArg.startsWith('file:///')) {
+ filepath = url.fileURLToPath(firstArg)
} else {
- let filepath
- if (firstArg.startsWith('/')) {
- filepath = firstArg
- } else if (firstArg.startsWith('file:///')) {
- filepath = url.fileURLToPath(firstArg)
- } else {
- filepath = resolve(firstArg)
- }
- updateArgv({ sliceAt: 3 })
- await importPath(filepath)
+ filepath = resolve(firstArg)
}
- return
+ await importPath(filepath)
})().catch((err) => {
if (err instanceof ProcessOutput) {
console.error('Error:', err.message)
@@ -93,9 +116,8 @@ await (async function main() {
})
async function runScript(script: string) {
- let filepath = join(tmpdir(), randomId() + '.mjs')
- await fs.mkdtemp(filepath)
- await writeAndImport(script, filepath, join(process.cwd(), 'stdin.mjs'))
+ const filepath = join(process.cwd(), `zx-${randomId()}.mjs`)
+ await writeAndImport(script, filepath)
}
async function scriptFromStdin() {
@@ -115,20 +137,17 @@ async function scriptFromStdin() {
}
async function scriptFromHttp(remote: string) {
- let res = await fetch(remote)
+ const res = await fetch(remote)
if (!res.ok) {
console.error(`Error: Can't get ${remote}`)
process.exit(1)
}
- let script = await res.text()
- let filename = new URL(remote).pathname
- let filepath = join(tmpdir(), basename(filename))
- await fs.mkdtemp(filepath)
- await writeAndImport(
- script,
- filepath,
- join(process.cwd(), basename(filename))
- )
+ const script = await res.text()
+ const pathname = new URL(remote).pathname
+ const name = basename(pathname)
+ const ext = extname(pathname) || '.mjs'
+ const filepath = join(process.cwd(), `${name}-${randomId()}${ext}`)
+ await writeAndImport(script, filepath)
}
async function writeAndImport(
@@ -136,16 +155,12 @@ async function writeAndImport(
filepath: string,
origin = filepath
) {
- const contents = script.toString()
- await fs.writeFile(filepath, contents)
-
- if (argv.install) {
- await installDeps(parseDeps(contents), dirname(filepath))
+ await fs.writeFile(filepath, script.toString())
+ try {
+ await importPath(filepath, origin)
+ } finally {
+ await fs.rm(filepath)
}
-
- let wait = importPath(filepath, origin)
- await fs.rm(filepath)
- await wait
}
async function importPath(filepath: string, origin = filepath) {
@@ -169,6 +184,11 @@ async function importPath(filepath: string, origin = filepath) {
origin
)
}
+ if (argv.install) {
+ const deps = parseDeps(await fs.readFile(filepath))
+ console.log('Installing dependencies...', deps)
+ await installDeps(deps, dirname(filepath))
+ }
let __filename = resolve(origin)
let __dirname = dirname(__filename)
let require = createRequire(origin)
@@ -243,24 +263,3 @@ function transformMarkdown(buf: Buffer) {
function getVersion(): string {
return createRequire(import.meta.url)('../package.json').version
}
-
-function printUsage() {
- console.log(`
- ${chalk.bold('zx ' + getVersion())}
- A tool for writing better scripts
-
- ${chalk.bold('Usage')}
- zx [options] <script>
-
- ${chalk.bold('Options')}
- --quiet don't echo commands
- --shell=<path> custom shell binary
- --prefix=<command> prefix all commands
- --interactive, -i start repl
- --eval=<js>, -e evaluate script
- --experimental enable new api proposals
- --install parse and load script dependencies from the registry
- --version, -v print current zx version
- --help, -h print help
-`)
-}
src/deps.ts
@@ -15,37 +15,32 @@
import { $ } from './core.js'
export async function installDeps(
- dependencies: Record<string, any> = {},
+ dependencies: Record<string, string>,
prefix?: string
) {
- const pkgs = Object.entries(dependencies).map(
+ const packages = Object.entries(dependencies).map(
([name, version]) => `${name}@${version}`
)
-
const flags = prefix ? `--prefix=${prefix}` : ''
-
- if (pkgs.length === 0) {
+ if (packages.length === 0) {
return
}
-
- await $`npm install --no-save --no-audit --no-fund ${flags} ${pkgs}`
+ await $`npm install --no-save --no-audit --no-fund ${flags} ${packages}`
}
const builtinsRe =
/^(_http_agent|_http_client|_http_common|_http_incoming|_http_outgoing|_http_server|_stream_duplex|_stream_passthrough|_stream_readable|_stream_transform|_stream_wrap|_stream_writable|_tls_common|_tls_wrap|assert|async_hooks|buffer|child_process|cluster|console|constants|crypto|dgram|diagnostics_channel|dns|domain|events|fs|http|http2|https|inspector|module|net|os|path|perf_hooks|process|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|trace_events|tty|url|util|v8|vm|wasi|worker_threads|zlib)$/
-export function parseDeps(content: string): Record<string, any> {
+export function parseDeps(content: Buffer): Record<string, string> {
const re =
/(?:\sfrom\s+|[\s(:\[](?:import|require)\s*\()["']((?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*)[/a-z0-9-._~]*["'](?:\s*;?\s*(?:\/\*|\/\/)\s*([a-z0-9-._~^*]+))?/g
- const deps: Record<string, any> = {}
+ const deps: Record<string, string> = {}
let m
-
do {
- m = re.exec(content)
+ m = re.exec(content.toString())
if (m && !builtinsRe.test(m[1])) {
deps[m[1]] = m[2] || 'latest'
}
} while (m)
-
return deps
}
src/goods.ts
@@ -27,8 +27,8 @@ export { default as path } from 'node:path'
export { default as os } from 'node:os'
export let argv = minimist(process.argv.slice(2))
-export function updateArgv(params: { sliceAt: number }) {
- argv = minimist(process.argv.slice(params.sliceAt))
+export function updateArgv(args: string[]) {
+ argv = minimist(args)
;(global as any).argv = argv
}
package.json
@@ -69,7 +69,7 @@
"c8": "^7.12.0",
"madge": "^5.0.1",
"prettier": "^2.7.1",
- "tsd": "^0.24.0",
+ "tsd": "^0.24.1",
"typescript": "^4.8.3",
"uvu": "^0.5.6"
},