Skip to content

Commit 9eb0c77

Browse files
author
Spencer
authored
[dev/run] expose unexpected flags as more than just names (#54080)
1 parent 0576327 commit 9eb0c77

File tree

9 files changed

+211
-21
lines changed

9 files changed

+211
-21
lines changed

packages/kbn-dev-utils/src/run/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ $ node scripts/my_task
117117

118118
- *`flags.allowUnexpected: boolean`*
119119

120-
By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`.
120+
By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`. Unexpected flags will be collected from argv into `flags.unexpected`. To parse these flags and guess at their types, you can additionally pass `flags.guessTypesForUnexpectedFlags` but that's not recommended.
121121

122122

123123
- ***`createFailError(reason: string, options: { exitCode: number, showHelp: boolean }): FailError`***
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { getFlags } from './flags';
21+
22+
it('gets flags correctly', () => {
23+
expect(
24+
getFlags(['-a', '--abc=bcd', '--foo=bar', '--no-bar', '--foo=baz', '--box', 'yes', '-zxy'], {
25+
flags: {
26+
boolean: ['x'],
27+
string: ['abc'],
28+
alias: {
29+
x: 'extra',
30+
},
31+
allowUnexpected: true,
32+
},
33+
})
34+
).toMatchInlineSnapshot(`
35+
Object {
36+
"_": Array [],
37+
"abc": "bcd",
38+
"debug": false,
39+
"extra": true,
40+
"help": false,
41+
"quiet": false,
42+
"silent": false,
43+
"unexpected": Array [
44+
"-a",
45+
"--foo=bar",
46+
"--foo=baz",
47+
"--no-bar",
48+
"--box",
49+
"yes",
50+
"-z",
51+
"-y",
52+
],
53+
"v": false,
54+
"verbose": false,
55+
"x": true,
56+
}
57+
`);
58+
});
59+
60+
it('guesses types for unexpected flags', () => {
61+
expect(
62+
getFlags(['-abc', '--abc=bcd', '--no-foo', '--bar'], {
63+
flags: {
64+
allowUnexpected: true,
65+
guessTypesForUnexpectedFlags: true,
66+
},
67+
})
68+
).toMatchInlineSnapshot(`
69+
Object {
70+
"_": Array [],
71+
"a": true,
72+
"abc": "bcd",
73+
"b": true,
74+
"bar": true,
75+
"c": true,
76+
"debug": false,
77+
"foo": false,
78+
"help": false,
79+
"quiet": false,
80+
"silent": false,
81+
"unexpected": Array [
82+
"-a",
83+
"-b",
84+
"-c",
85+
"-abc",
86+
"--abc=bcd",
87+
"--no-foo",
88+
"--bar",
89+
],
90+
"v": false,
91+
"verbose": false,
92+
}
93+
`);
94+
});

packages/kbn-dev-utils/src/run/flags.ts

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export interface Flags {
3737
}
3838

3939
export function getFlags(argv: string[], options: Options): Flags {
40-
const unexpected: string[] = [];
40+
const unexpectedNames = new Set<string>();
4141
const flagOpts = options.flags || {};
4242

4343
const { verbose, quiet, silent, debug, help, _, ...others } = getopts(argv, {
@@ -49,15 +49,64 @@ export function getFlags(argv: string[], options: Options): Flags {
4949
},
5050
default: flagOpts.default,
5151
unknown: (name: string) => {
52-
unexpected.push(name);
52+
unexpectedNames.add(name);
53+
return flagOpts.guessTypesForUnexpectedFlags;
54+
},
55+
} as any);
56+
57+
const unexpected: string[] = [];
58+
for (const unexpectedName of unexpectedNames) {
59+
const matchingArgv: string[] = [];
60+
61+
iterArgv: for (const [i, v] of argv.entries()) {
62+
for (const prefix of ['--', '-']) {
63+
if (v.startsWith(prefix)) {
64+
// -/--name=value
65+
if (v.startsWith(`${prefix}${unexpectedName}=`)) {
66+
matchingArgv.push(v);
67+
continue iterArgv;
68+
}
69+
70+
// -/--name (value possibly follows)
71+
if (v === `${prefix}${unexpectedName}`) {
72+
matchingArgv.push(v);
5373

54-
if (options.flags && options.flags.allowUnexpected) {
55-
return true;
74+
// value follows -/--name
75+
if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) {
76+
matchingArgv.push(argv[i + 1]);
77+
}
78+
79+
continue iterArgv;
80+
}
81+
}
5682
}
5783

58-
return false;
59-
},
60-
} as any);
84+
// special case for `--no-{flag}` disabling of boolean flags
85+
if (v === `--no-${unexpectedName}`) {
86+
matchingArgv.push(v);
87+
continue iterArgv;
88+
}
89+
90+
// special case for shortcut flags formatted as `-abc` where `a`, `b`,
91+
// and `c` will be three separate unexpected flags
92+
if (
93+
unexpectedName.length === 1 &&
94+
v[0] === '-' &&
95+
v[1] !== '-' &&
96+
!v.includes('=') &&
97+
v.includes(unexpectedName)
98+
) {
99+
matchingArgv.push(`-${unexpectedName}`);
100+
continue iterArgv;
101+
}
102+
}
103+
104+
if (matchingArgv.length) {
105+
unexpected.push(...matchingArgv);
106+
} else {
107+
throw new Error(`unable to find unexpected flag named "${unexpectedName}"`);
108+
}
109+
}
61110

62111
return {
63112
verbose,
@@ -75,7 +124,7 @@ export function getHelp(options: Options) {
75124
const usage = options.usage || `node ${relative(process.cwd(), process.argv[1])}`;
76125

77126
const optionHelp = (
78-
dedent((options.flags && options.flags.help) || '') +
127+
dedent(options?.flags?.help || '') +
79128
'\n' +
80129
dedent`
81130
--verbose, -v Log verbosely

packages/kbn-dev-utils/src/run/run.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface Options {
3636
description?: string;
3737
flags?: {
3838
allowUnexpected?: boolean;
39+
guessTypesForUnexpectedFlags?: boolean;
3940
help?: string;
4041
alias?: { [key: string]: string | string[] };
4142
boolean?: string[];
@@ -46,7 +47,6 @@ export interface Options {
4647

4748
export async function run(fn: RunFn, options: Options = {}) {
4849
const flags = getFlags(process.argv.slice(2), options);
49-
const allowUnexpected = options.flags ? options.flags.allowUnexpected : false;
5050

5151
if (flags.help) {
5252
process.stderr.write(getHelp(options));
@@ -97,7 +97,7 @@ export async function run(fn: RunFn, options: Options = {}) {
9797
const cleanupTasks: CleanupTask[] = [unhookExit];
9898

9999
try {
100-
if (!allowUnexpected && flags.unexpected.length) {
100+
if (!options.flags?.allowUnexpected && flags.unexpected.length) {
101101
throw createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`);
102102
}
103103

packages/kbn-dev-utils/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"compilerOptions": {
44
"outDir": "target",
55
"target": "ES2019",
6-
"declaration": true
6+
"declaration": true,
7+
"declarationMap": true
78
},
89
"include": [
910
"src/**/*"

packages/kbn-pm/dist/index.js

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37054,8 +37054,8 @@ const tooling_log_1 = __webpack_require__(415);
3705437054
const fail_1 = __webpack_require__(425);
3705537055
const flags_1 = __webpack_require__(426);
3705637056
async function run(fn, options = {}) {
37057+
var _a;
3705737058
const flags = flags_1.getFlags(process.argv.slice(2), options);
37058-
const allowUnexpected = options.flags ? options.flags.allowUnexpected : false;
3705937059
if (flags.help) {
3706037060
process.stderr.write(flags_1.getHelp(options));
3706137061
process.exit(1);
@@ -37098,7 +37098,7 @@ async function run(fn, options = {}) {
3709837098
const unhookExit = exit_hook_1.default(doCleanup);
3709937099
const cleanupTasks = [unhookExit];
3710037100
try {
37101-
if (!allowUnexpected && flags.unexpected.length) {
37101+
if (!((_a = options.flags) === null || _a === void 0 ? void 0 : _a.allowUnexpected) && flags.unexpected.length) {
3710237102
throw fail_1.createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`);
3710337103
}
3710437104
try {
@@ -37218,7 +37218,7 @@ const path_1 = __webpack_require__(16);
3721837218
const dedent_1 = tslib_1.__importDefault(__webpack_require__(14));
3721937219
const getopts_1 = tslib_1.__importDefault(__webpack_require__(427));
3722037220
function getFlags(argv, options) {
37221-
const unexpected = [];
37221+
const unexpectedNames = new Set();
3722237222
const flagOpts = options.flags || {};
3722337223
const { verbose, quiet, silent, debug, help, _, ...others } = getopts_1.default(argv, {
3722437224
string: flagOpts.string,
@@ -37229,13 +37229,55 @@ function getFlags(argv, options) {
3722937229
},
3723037230
default: flagOpts.default,
3723137231
unknown: (name) => {
37232-
unexpected.push(name);
37233-
if (options.flags && options.flags.allowUnexpected) {
37234-
return true;
37235-
}
37236-
return false;
37232+
unexpectedNames.add(name);
37233+
return flagOpts.guessTypesForUnexpectedFlags;
3723737234
},
3723837235
});
37236+
const unexpected = [];
37237+
for (const unexpectedName of unexpectedNames) {
37238+
const matchingArgv = [];
37239+
iterArgv: for (const [i, v] of argv.entries()) {
37240+
for (const prefix of ['--', '-']) {
37241+
if (v.startsWith(prefix)) {
37242+
// -/--name=value
37243+
if (v.startsWith(`${prefix}${unexpectedName}=`)) {
37244+
matchingArgv.push(v);
37245+
continue iterArgv;
37246+
}
37247+
// -/--name (value possibly follows)
37248+
if (v === `${prefix}${unexpectedName}`) {
37249+
matchingArgv.push(v);
37250+
// value follows -/--name
37251+
if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) {
37252+
matchingArgv.push(argv[i + 1]);
37253+
}
37254+
continue iterArgv;
37255+
}
37256+
}
37257+
}
37258+
// special case for `--no-{flag}` disabling of boolean flags
37259+
if (v === `--no-${unexpectedName}`) {
37260+
matchingArgv.push(v);
37261+
continue iterArgv;
37262+
}
37263+
// special case for shortcut flags formatted as `-abc` where `a`, `b`,
37264+
// and `c` will be three separate unexpected flags
37265+
if (unexpectedName.length === 1 &&
37266+
v[0] === '-' &&
37267+
v[1] !== '-' &&
37268+
!v.includes('=') &&
37269+
v.includes(unexpectedName)) {
37270+
matchingArgv.push(`-${unexpectedName}`);
37271+
continue iterArgv;
37272+
}
37273+
}
37274+
if (matchingArgv.length) {
37275+
unexpected.push(...matchingArgv);
37276+
}
37277+
else {
37278+
throw new Error(`unable to find unexpected flag named "${unexpectedName}"`);
37279+
}
37280+
}
3723937281
return {
3724037282
verbose,
3724137283
quiet,
@@ -37249,8 +37291,9 @@ function getFlags(argv, options) {
3724937291
}
3725037292
exports.getFlags = getFlags;
3725137293
function getHelp(options) {
37294+
var _a, _b;
3725237295
const usage = options.usage || `node ${path_1.relative(process.cwd(), process.argv[1])}`;
37253-
const optionHelp = (dedent_1.default((options.flags && options.flags.help) || '') +
37296+
const optionHelp = (dedent_1.default(((_b = (_a = options) === null || _a === void 0 ? void 0 : _a.flags) === null || _b === void 0 ? void 0 : _b.help) || '') +
3725437297
'\n' +
3725537298
dedent_1.default `
3725637299
--verbose, -v Log verbosely

src/dev/run_i18n_check.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ run(
133133
{
134134
flags: {
135135
allowUnexpected: true,
136+
guessTypesForUnexpectedFlags: true,
136137
},
137138
}
138139
);

src/dev/run_i18n_extract.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ run(
9393
{
9494
flags: {
9595
allowUnexpected: true,
96+
guessTypesForUnexpectedFlags: true,
9697
},
9798
}
9899
);

src/dev/run_i18n_integrate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ run(
121121
{
122122
flags: {
123123
allowUnexpected: true,
124+
guessTypesForUnexpectedFlags: true,
124125
},
125126
}
126127
);

0 commit comments

Comments
 (0)