Commit 8860b44
Changed files (9)
man/zx.1
@@ -19,13 +19,15 @@ set the shell to use
prefix all commands
.SS --postfix=<command>
postfix all commands
+.SS --prefer-local, -l
+prefer locally installed packages bins
.SS --eval=<js>, -e
evaluate script
.SS --ext=<.mjs>
default extension
.SS --install, -i
install dependencies
-.SS --registry<URL>
+.SS --registry=<URL>
npm registry, defaults to https://registry.npmjs.org/
.SS --repl
start repl
src/cli.ts
@@ -21,10 +21,10 @@ import {
updateArgv,
fetch,
chalk,
- minimist,
fs,
path,
VERSION,
+ parseArgv,
} from './index.js'
import { installDeps, parseDeps } from './deps.js'
import { randomId } from './util.js'
@@ -57,6 +57,7 @@ export function printUsage() {
--shell=<path> custom shell binary
--prefix=<command> prefix all commands
--postfix=<command> postfix all commands
+ --prefer-local, -l prefer locally installed packages bins
--cwd=<path> set current directory
--eval=<js>, -e evaluate script
--ext=<.mjs> default extension
@@ -71,19 +72,14 @@ export function printUsage() {
`)
}
-export const argv: minimist.ParsedArgs = minimist(process.argv.slice(2), {
+// prettier-ignore
+export const argv = parseArgv(process.argv.slice(2), {
string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext', 'registry'],
- boolean: [
- 'version',
- 'help',
- 'quiet',
- 'verbose',
- 'install',
- 'repl',
- 'experimental',
- ],
- alias: { e: 'eval', i: 'install', v: 'version', h: 'help' },
+ boolean: ['version', 'help', 'quiet', 'verbose', 'install', 'repl', 'experimental', 'prefer-local'],
+ alias: { e: 'eval', i: 'install', v: 'version', h: 'help', l: 'prefer-local' },
stopEarly: true,
+ parseBoolean: true,
+ camelCase: true,
})
export async function main() {
@@ -95,6 +91,7 @@ export async function main() {
if (argv.shell) $.shell = argv.shell
if (argv.prefix) $.prefix = argv.prefix
if (argv.postfix) $.postfix = argv.postfix
+ if (argv.preferLocal) $.preferLocal = argv.preferLocal
if (argv.version) {
console.log(VERSION)
return
src/core.ts
@@ -50,6 +50,7 @@ import {
isStringLiteral,
noop,
once,
+ parseBool,
parseDuration,
preferLocalBin,
proxyOverride,
@@ -929,7 +930,7 @@ export function resolveDefaults(
return Object.entries(env).reduce<Options>((m, [k, v]) => {
if (v && k.startsWith(prefix)) {
const _k = snakeToCamel(k.slice(prefix.length))
- const _v = { true: true, false: false }[v.toLowerCase()] ?? v
+ const _v = parseBool(v)
if (allowed.has(_k)) (m as any)[_k] = _v
}
return m
src/goods.ts
@@ -15,7 +15,14 @@
import assert from 'node:assert'
import { createInterface } from 'node:readline'
import { $, within, ProcessOutput } from './core.js'
-import { type Duration, isStringLiteral, parseDuration } from './util.js'
+import {
+ type Duration,
+ identity,
+ isStringLiteral,
+ parseBool,
+ parseDuration,
+ snakeToCamel,
+} from './util.js'
import {
chalk,
minimist,
@@ -27,12 +34,30 @@ import {
export { default as path } from 'node:path'
export * as os from 'node:os'
-export const argv: minimist.ParsedArgs = minimist(process.argv.slice(2))
-export function updateArgv(args: string[]) {
+type ArgvOpts = minimist.Opts & { camelCase?: boolean; parseBoolean?: boolean }
+
+export const parseArgv = (
+ args: string[] = process.argv.slice(2),
+ opts: ArgvOpts = {}
+): minimist.ParsedArgs =>
+ Object.entries(minimist(args, opts)).reduce<minimist.ParsedArgs>(
+ (m, [k, v]) => {
+ const kTrans = opts.camelCase ? snakeToCamel : identity
+ const vTrans = opts.parseBoolean ? parseBool : identity
+ const [_k, _v] = k === '--' || k === '_' ? [k, v] : [kTrans(k), vTrans(v)]
+ m[_k] = _v
+ return m
+ },
+ {} as minimist.ParsedArgs
+ )
+
+export function updateArgv(args?: string[], opts?: ArgvOpts) {
for (const k in argv) delete argv[k]
- Object.assign(argv, minimist(args))
+ Object.assign(argv, parseArgv(args, opts))
}
+export const argv: minimist.ParsedArgs = parseArgv()
+
export function sleep(duration: Duration): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, parseDuration(duration))
src/util.ts
@@ -39,6 +39,10 @@ export function tempfile(name?: string, data?: string | Buffer): string {
export function noop() {}
+export function identity<T>(v: T): T {
+ return v
+}
+
export function randomId() {
return Math.random().toString(36).slice(2)
}
@@ -282,17 +286,16 @@ export const proxyOverride = <T extends object>(
},
}) as T
-// https://stackoverflow.com/a/7888303
export const camelToSnake = (str: string) =>
str
.split(/(?=[A-Z])/)
.map((s) => s.toUpperCase())
.join('_')
-// https://stackoverflow.com/a/61375162
export const snakeToCamel = (str: string) =>
- str
- .toLowerCase()
- .replace(/([-_][a-z])/g, (group) =>
- group.toUpperCase().replace('-', '').replace('_', '')
- )
+ str.toLowerCase().replace(/([a-z])[_-]+([a-z])/g, (_, p1, p2) => {
+ return p1 + p2.toUpperCase()
+ })
+
+export const parseBool = (v: string): boolean | string =>
+ ({ true: true, false: false })[v] ?? v
test/goods.test.js
@@ -32,15 +32,8 @@ describe('goods', () => {
assert.match((await p).stdout, /Answer is foo/)
})
- test('globby available', async () => {
+ test('globby() works', async () => {
assert.equal(globby, glob)
- assert.equal(typeof globby, 'function')
- assert.equal(typeof globby.globbySync, 'function')
- assert.equal(typeof globby.globbyStream, 'function')
- assert.equal(typeof globby.generateGlobTasks, 'function')
- assert.equal(typeof globby.isDynamicPattern, 'function')
- assert.equal(typeof globby.isGitIgnored, 'function')
- assert.equal(typeof globby.isGitIgnoredSync, 'function')
assert.deepEqual(await globby('*.md'), ['README.md'])
})
@@ -178,4 +171,45 @@ describe('goods', () => {
assert(out.exitCode !== 0)
})
})
+
+ test('parseArgv() works', () => {
+ assert.deepEqual(
+ parseArgv(
+ // prettier-ignore
+ [
+ '--foo-bar', 'baz',
+ '-a', '5',
+ '-a', '42',
+ '--aaa', 'AAA',
+ '--force',
+ './some.file',
+ '--b1', 'true',
+ '--b2', 'false',
+ '--b3',
+ '--b4', 'false',
+ '--b5', 'true',
+ '--b6', 'str'
+ ],
+ {
+ boolean: ['force', 'b3', 'b4', 'b5', 'b6'],
+ camelCase: true,
+ parseBoolean: true,
+ alias: { a: 'aaa' },
+ }
+ ),
+ {
+ a: [5, 42, 'AAA'],
+ aaa: [5, 42, 'AAA'],
+ fooBar: 'baz',
+ force: true,
+ _: ['./some.file', 'str'],
+ b1: true,
+ b2: false,
+ b3: true,
+ b4: false,
+ b5: true,
+ b6: true,
+ }
+ )
+ })
})
test/index.test.js
@@ -39,8 +39,9 @@ import {
quote,
quotePowerShell,
within,
- argv,
os,
+ argv,
+ parseArgv,
updateArgv,
globby,
glob,
@@ -82,8 +83,9 @@ describe('index', () => {
assert(useBash)
// goods
- assert(argv)
assert(os)
+ assert(argv)
+ assert(parseArgv)
assert(updateArgv)
assert(globby)
assert(glob)
test/util.test.js
@@ -145,5 +145,6 @@ describe('util', () => {
assert.equal(snakeToCamel('NOTHROW'), 'nothrow')
assert.equal(snakeToCamel('PREFER_LOCAL'), 'preferLocal')
assert.equal(snakeToCamel('SOME_MORE_BIG_STR'), 'someMoreBigStr')
+ assert.equal(snakeToCamel('kebab-input-str'), 'kebabInputStr')
})
})
.size-limit.json
@@ -9,7 +9,7 @@
{
"name": "zx/index",
"path": "build/*.{js,cjs}",
- "limit": "803 kB",
+ "limit": "804 kB",
"brotli": false,
"gzip": false
},