Skip to content

Commit

Permalink
feat(cli): provide --prefer-local option (#1015)
Browse files Browse the repository at this point in the history
* feat(cli): provide `--prefer-local` option

* test: extend argv test

* refactor(util): simplify `snakeToCamel`
  • Loading branch information
antongolub authored Dec 21, 2024
1 parent a69ddd4 commit 8860b44
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "zx/index",
"path": "build/*.{js,cjs}",
"limit": "803 kB",
"limit": "804 kB",
"brotli": false,
"gzip": false
},
Expand Down
4 changes: 3 additions & 1 deletion man/zx.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 9 additions & 12 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
isStringLiteral,
noop,
once,
parseBool,
parseDuration,
preferLocalBin,
proxyOverride,
Expand Down Expand Up @@ -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
Expand Down
33 changes: 29 additions & 4 deletions src/goods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))
Expand Down
17 changes: 10 additions & 7 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
50 changes: 42 additions & 8 deletions test/goods.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
})

Expand Down Expand Up @@ -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,
}
)
})
})
6 changes: 4 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ import {
quote,
quotePowerShell,
within,
argv,
os,
argv,
parseArgv,
updateArgv,
globby,
glob,
Expand Down Expand Up @@ -82,8 +83,9 @@ describe('index', () => {
assert(useBash)

// goods
assert(argv)
assert(os)
assert(argv)
assert(parseArgv)
assert(updateArgv)
assert(globby)
assert(glob)
Expand Down
1 change: 1 addition & 0 deletions test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

0 comments on commit 8860b44

Please sign in to comment.