mirror of
https://github.com/pnpm/pnpm.git
synced 2026-02-15 09:33:45 -05:00
feat: allow unknown options if they are prefixed with "config."
PR #2755 close #2685 close #2710
This commit is contained in:
5
.changeset/giant-lions-march.md
Normal file
5
.changeset/giant-lions-march.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/parse-cli-args": minor
|
||||
---
|
||||
|
||||
Allow unknown options that are prefixed with "config."
|
||||
@@ -126,17 +126,32 @@ export default async function parseCliArgs (
|
||||
}
|
||||
|
||||
const knownOptions = new Set(Object.keys(types))
|
||||
const unknownOptions = getUnknownOptions(Object.keys(options), knownOptions)
|
||||
return {
|
||||
argv,
|
||||
cmd,
|
||||
options,
|
||||
params,
|
||||
unknownOptions,
|
||||
workspaceDir,
|
||||
...normalizeOptions(options, knownOptions),
|
||||
}
|
||||
}
|
||||
|
||||
const CUSTOM_OPTION_PREFIX = 'config.'
|
||||
|
||||
function normalizeOptions (options: Record<string, unknown>, knownOptions: Set<string>) {
|
||||
const standardOptionNames = []
|
||||
const normalizedOptions = {}
|
||||
for (const [optionName, optionValue] of Object.entries(options)) {
|
||||
if (optionName.startsWith(CUSTOM_OPTION_PREFIX)) {
|
||||
normalizedOptions[optionName.substring(CUSTOM_OPTION_PREFIX.length)] = optionValue
|
||||
continue
|
||||
}
|
||||
normalizedOptions[optionName] = optionValue
|
||||
standardOptionNames.push(optionName)
|
||||
}
|
||||
const unknownOptions = getUnknownOptions(standardOptionNames, knownOptions)
|
||||
return { options: normalizedOptions, unknownOptions }
|
||||
}
|
||||
|
||||
function getUnknownOptions (usedOptions: string[], knownOptions: Set<string>) {
|
||||
const unknownOptions = new Map<string, string[]>()
|
||||
const closestMatches = getClosestOptionMatches.bind(null, Array.from(knownOptions))
|
||||
|
||||
@@ -113,6 +113,27 @@ test('detect unknown options', async (t) => {
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('allow any option that starts with "config."', async (t) => {
|
||||
const { options, unknownOptions } = await parseCliArgs({
|
||||
...DEFAULT_OPTS,
|
||||
getTypesByCommandName: (commandName: string) => {
|
||||
if (commandName === 'install') {
|
||||
return {
|
||||
bar: Boolean,
|
||||
recursive: Boolean,
|
||||
registry: String,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
universalOptionsTypes: { filter: [String, Array] },
|
||||
}, ['install', '--config.save-dev', '--registry=https://example.com', '--config.qar', '--filter=packages'])
|
||||
t.deepEqual(Array.from(unknownOptions.entries()), [])
|
||||
t.equal(options.qar, true)
|
||||
t.equal(options['save-dev'], true)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('do not incorrectly change "install" command to "add"', async (t) => {
|
||||
const { cmd } = await parseCliArgs({
|
||||
...DEFAULT_OPTS,
|
||||
|
||||
@@ -12,5 +12,6 @@ export function formatUnknownOptionsError (unknownOptions: Map<string, string[]>
|
||||
if (!didYouMeanOptions?.length) {
|
||||
return output
|
||||
}
|
||||
return `${output}\nDid you mean '${didYouMeanOptions.join("', or '")}'?`
|
||||
return `${output}
|
||||
Did you mean '${didYouMeanOptions.join("', or '")}'? Use "--config.unknown=value" to force an unknown option.`
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ test('formatUnknownOptionsError()', async (t) => {
|
||||
t.equal(
|
||||
formatUnknownOptionsError(new Map([['foo', ['foa', 'fob']]])),
|
||||
`${ERROR} ${chalk.red("Unknown option: 'foo'")}
|
||||
Did you mean 'foa', or 'fob'?`
|
||||
Did you mean 'foa', or 'fob'? Use "--config.unknown=value" to force an unknown option.`
|
||||
)
|
||||
t.equal(
|
||||
formatUnknownOptionsError(new Map([['foo', []], ['bar', []]])),
|
||||
|
||||
Reference in New Issue
Block a user