Skip to content

Commit 553dfe1

Browse files
aarondillfisker
andauthored
refactor(cli): use util.parseArgs to parse arguments (#283)
* feat: use util.parseArgs * refactor: destrucure argument in parseCLIArguments This moved argument destructuring to the parseCliArguments function, renames values to options, renames positionals to patterns, and moves the try catch to the run function * refactor: move default pattern to parseCliArgument * fix: typo isQuiet->shouldBeQuiet * fix: show help on illegal option value * test: added tests for illegal argument usage * feat: add support for --no-check and --no-quiet * Revert "feat: add support for --no-check and --no-quiet" This reverts commit 10a6067. * test: add tests which throw for --no-* arguments * test: update tests * test: update snapshots * style: linting * refactor: use `Object.hasOwn` --------- Co-authored-by: fisker Cheung <[email protected]>
1 parent 0acfb7d commit 553dfe1

File tree

7 files changed

+258
-56
lines changed

7 files changed

+258
-56
lines changed

cli.js

+51-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
22
import { globSync } from 'tinyglobby'
33
import fs from 'node:fs'
4+
import { parseArgs } from 'node:util'
45
import getStdin from 'get-stdin'
56
import sortPackageJson from './index.js'
67
import Reporter from './reporter.js'
@@ -30,6 +31,32 @@ If file/glob is omitted, './package.json' file will be processed.
3031
)
3132
}
3233

34+
function parseCliArguments() {
35+
const { values: options, positionals: patterns } = parseArgs({
36+
options: {
37+
check: { type: 'boolean', short: 'c', default: false },
38+
quiet: { type: 'boolean', short: 'q', default: false },
39+
stdin: { type: 'boolean', default: false },
40+
ignore: {
41+
type: 'string',
42+
short: 'i',
43+
multiple: true,
44+
default: ['node_modules/**'],
45+
},
46+
version: { type: 'boolean', short: 'v', default: false },
47+
help: { type: 'boolean', short: 'h', default: false },
48+
},
49+
allowPositionals: true,
50+
strict: true,
51+
})
52+
53+
if (patterns.length === 0) {
54+
patterns[0] = 'package.json'
55+
}
56+
57+
return { options, patterns }
58+
}
59+
3360
function sortPackageJsonFile(file, reporter, isCheck) {
3461
const original = fs.readFileSync(file, 'utf8')
3562
const sorted = sortPackageJson(original)
@@ -46,6 +73,7 @@ function sortPackageJsonFile(file, reporter, isCheck) {
4673

4774
function sortPackageJsonFiles(patterns, { ignore, ...options }) {
4875
const files = globSync(patterns, { ignore })
76+
4977
const reporter = new Reporter(files, options)
5078
const { isCheck } = options
5179

@@ -64,61 +92,38 @@ async function sortPackageJsonFromStdin() {
6492
}
6593

6694
function run() {
67-
const cliArguments = process.argv
68-
.slice(2)
69-
.map((arg) => arg.split('='))
70-
.flat()
71-
72-
if (
73-
cliArguments.some((argument) => argument === '--help' || argument === '-h')
74-
) {
95+
let options, patterns
96+
try {
97+
;({ options, patterns } = parseCliArguments())
98+
} catch (error) {
99+
process.exitCode = 2
100+
console.error(error.message)
101+
if (
102+
error.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION' ||
103+
error.code === 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE'
104+
) {
105+
console.error(`Try 'sort-package-json --help' for more information.`)
106+
}
107+
return
108+
}
109+
110+
if (options.help) {
75111
return showHelpInformation()
76112
}
77113

78-
if (
79-
cliArguments.some(
80-
(argument) => argument === '--version' || argument === '-v',
81-
)
82-
) {
114+
if (options.version) {
83115
return showVersion()
84116
}
85117

86-
if (cliArguments.some((argument) => argument === '--stdin')) {
118+
if (options.stdin) {
87119
return sortPackageJsonFromStdin()
88120
}
89121

90-
const patterns = []
91-
const ignore = []
92-
let isCheck = false
93-
let shouldBeQuiet = false
94-
95-
let lastArg
96-
for (const argument of cliArguments) {
97-
if (lastArg === '--ignore' || lastArg === '-i') {
98-
ignore.push(argument)
99-
lastArg = undefined
100-
continue
101-
}
102-
if (argument === '--check' || argument === '-c') {
103-
isCheck = true
104-
} else if (argument === '--quiet' || argument === '-q') {
105-
shouldBeQuiet = true
106-
} else if (argument === '--ignore' || argument === '-i') {
107-
lastArg = argument
108-
} else {
109-
patterns.push(argument)
110-
}
111-
}
112-
113-
if (!patterns.length) {
114-
patterns[0] = 'package.json'
115-
}
116-
117-
if (!ignore.length) {
118-
ignore[0] = 'node_modules'
119-
}
120-
121-
sortPackageJsonFiles(patterns, { ignore, isCheck, shouldBeQuiet })
122+
sortPackageJsonFiles(patterns, {
123+
ignore: options.ignore,
124+
isCheck: options.check,
125+
shouldBeQuiet: options.quiet,
126+
})
122127
}
123128

124129
run()

eslint.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default [
1313
languageOptions: {
1414
globals: { ...globals.builtin, ...globals.node },
1515
},
16+
settings: { node: { version: '20' } },
1617
},
1718
{ ignores: ['index.cjs'] },
1819
]

index.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import gitHooks from 'git-hooks-list'
55
import isPlainObject from 'is-plain-obj'
66
import semver from 'semver'
77

8-
const hasOwn =
9-
// eslint-disable-next-line n/no-unsupported-features/es-builtins, n/no-unsupported-features/es-syntax -- will enable later
10-
Object.hasOwn ||
11-
// TODO: Remove this when we drop supported for Node.js v14
12-
((object, property) => Object.prototype.hasOwnProperty.call(object, property))
138
const pipe =
149
(fns) =>
1510
(x, ...args) =>
@@ -51,7 +46,7 @@ const sortDirectories = sortObjectBy([
5146
const overProperty =
5247
(property, over) =>
5348
(object, ...args) =>
54-
hasOwn(object, property)
49+
Object.hasOwn(object, property)
5550
? { ...object, [property]: over(object[property], ...args) }
5651
: object
5752
const sortGitHooks = sortObjectBy(gitHooks)
@@ -218,8 +213,8 @@ const defaultNpmScripts = new Set([
218213

219214
const hasDevDependency = (dependency, packageJson) => {
220215
return (
221-
hasOwn(packageJson, 'devDependencies') &&
222-
hasOwn(packageJson.devDependencies, dependency)
216+
Object.hasOwn(packageJson, 'devDependencies') &&
217+
Object.hasOwn(packageJson.devDependencies, dependency)
223218
)
224219
}
225220

reporter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Reporter {
6060
return
6161
}
6262

63-
const { isCheck, isQuiet } = this.#options
63+
const { isCheck, shouldBeQuiet } = this.#options
6464

6565
if (isCheck && changedFilesCount) {
6666
process.exitCode = 1
@@ -70,7 +70,7 @@ class Reporter {
7070
process.exitCode = 2
7171
}
7272

73-
if (isQuiet) {
73+
if (shouldBeQuiet) {
7474
return
7575
}
7676

tests/cli.js

+47
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@ test('run `cli --help` with `--version`', macro.testCLI, {
6363
message: 'Should prioritize help over version.',
6464
})
6565

66+
test('run `cli --help=value`', macro.testCLI, {
67+
args: ['--help=value'],
68+
message: 'Should report illegal argument and suggest help.',
69+
})
70+
71+
test('run `cli --version=true`', macro.testCLI, {
72+
args: ['--version=true'],
73+
message: 'Should report illegal argument and suggest help.',
74+
})
75+
76+
test('run `cli --unknown-option`', macro.testCLI, {
77+
args: ['--unknown-option'],
78+
message: 'Should report unknown option and suggest help.',
79+
})
80+
81+
test('run `cli -u` with unknown option', macro.testCLI, {
82+
args: ['-u'],
83+
message: 'Should report unknown option and suggest help.',
84+
})
85+
86+
test('run `cli --no-version`', macro.testCLI, {
87+
args: ['--no-version'],
88+
message: 'A snapshot to show how `--no-*` works, not care about result.',
89+
})
90+
6691
test('run `cli` with no patterns', macro.testCLI, {
6792
fixtures: [
6893
{
@@ -87,6 +112,11 @@ test('run `cli --quiet` with no patterns', macro.testCLI, {
87112
message: 'Should format package.json without message.',
88113
})
89114

115+
test('run `cli --quiet=value`', macro.testCLI, {
116+
args: ['--quiet=value'],
117+
message: 'Should report illegal argument and suggest help.',
118+
})
119+
90120
test('run `cli -q` with no patterns', macro.testCLI, {
91121
fixtures: [
92122
{
@@ -111,6 +141,11 @@ test('run `cli --check` with no patterns', macro.testCLI, {
111141
message: 'Should not sort package.json',
112142
})
113143

144+
test('run `cli --check=value`', macro.testCLI, {
145+
args: ['--check=value'],
146+
message: 'Should report illegal argument and suggest help.',
147+
})
148+
114149
test('run `cli --check --quiet` with no patterns', macro.testCLI, {
115150
fixtures: [
116151
{
@@ -147,6 +182,18 @@ test('run `cli -c -q` with no patterns', macro.testCLI, {
147182
message: 'Should support `-q` alias',
148183
})
149184

185+
test('run `cli -cq` with no patterns', macro.testCLI, {
186+
fixtures: [
187+
{
188+
file: 'package.json',
189+
content: badJson,
190+
expect: badJson,
191+
},
192+
],
193+
args: ['-cq'],
194+
message: 'Should support option aggregation',
195+
})
196+
150197
test('run `cli` on 1 bad file', macro.testCLI, {
151198
fixtures: [
152199
{

0 commit comments

Comments
 (0)