feat: support shorthand help CLI flag
MODIFY
internal/cli-flags/Parser.test.ts
internal/cli-flags/Parser.test.ts
@@ -307,6 +307,42 @@ test(
},
);
+test(
+ "allow special-cased -h shorthand for help",
+ async (t) => {
+ await testParser(
+ t,
+ {
+ options: {
+ noProcessExit: true,
+ },
+ defineFlags: () => {
+ return {};
+ },
+ args: ["-h"],
+ },
+ );
+ },
+);
+
+test(
+ "do not allow invalid shorthand commands",
+ async (t) => {
+ await testParser(
+ t,
+ {
+ options: {
+ noProcessExit: true,
+ },
+ defineFlags: () => {
+ return {};
+ },
+ args: ["-help"],
+ },
+ );
+ },
+);
+
test(
"command required with no command but with --help flag",
async (t) => {

MODIFY
internal/cli-flags/Parser.ts
internal/cli-flags/Parser.ts
@@ -216,11 +216,11 @@ export default class Parser<T> {
this.setFlag(camelName, String(rawArgs.shift()));
}
- this.flagToArgIndex.set(camelName, this.args.length);
-
if (arg[0] === "-" && arg[1] !== "-") {
this.shorthandFlags.add(camelName);
}
+
+ this.flagToArgIndex.set(camelName, this.args.length);
} else {
// Not a flag and hasn't been consumed already by a previous arg so it must be a file
this.args.push(arg);
@@ -375,11 +375,16 @@ export default class Parser<T> {
consumer.markUsedProperty(key);
}
- for (const shorthandName of this.shorthandFlags) {
- consumer.get(shorthandName).unexpected(
- descriptions.FLAGS.UNSUPPORTED_SHORTHANDS,
+ for (const [key] of this.flags) {
+ if (this.shorthandFlags.has(key)) {
+ const def = this.declaredFlags.get(key)?.definition;
+ if (def && def.metadata?.alternateName !== key) {
+ consumer.get(key).unexpected(
+ descriptions.FLAGS.UNSUPPORTED_SHORTHAND(key),
);
}
+ }
+ }
for (const incorrectName of this.incorrectCaseFlags) {
consumer.get(incorrectName).unexpected(
@@ -530,6 +535,7 @@ export default class Parser<T> {
"help",
{
description: markup`Show this help screen`,
+ alternateName: "h",
},
).asBoolean(false);
@@ -550,9 +556,7 @@ export default class Parser<T> {
}
}
- if (!shouldShowHelp) {
this.checkBadFlags(consumer, definedCommand);
- }
this.currentCommand = undefined;
@@ -624,6 +628,10 @@ export default class Parser<T> {
argCol += ` <${inputName}>`;
}
+ if (metadata.alternateName) {
+ argCol += `, -${metadata.alternateName}`;
+ }
+
// Set arg col length if we'll be longer
if (argColumnLength < argCol.length) {
argColumnLength = argCol.length;
@@ -654,7 +662,10 @@ export default class Parser<T> {
optionOutput.push({
argName,
- arg: concatMarkup(highlightShell({input: argCol}), markup` `),
+ arg: concatMarkup(
+ highlightShell({input: argCol, isShorthand: !!metadata.alternateName}),
+ markup` `,
+ ),
description: descCol,
});
}

MODIFY
internal/consume/Consumer.ts
internal/consume/Consumer.ts
@@ -614,8 +614,12 @@ export default class Consumer {
public get(key: string, metadata?: ConsumePropertyMetadata): Consumer {
const value = this.asOriginalUnknownObject();
- this.markUsedProperty(key);
- return this.fork(key, value[key], metadata);
+ const valueKey =
+ metadata?.alternateName && this.has(metadata.alternateName)
+ ? metadata.alternateName
+ : key;
+ this.markUsedProperty(valueKey);
+ return this.fork(key, value[valueKey], metadata);
}
public getIndex(index: number): Consumer {

MODIFY
internal/consume/types.ts
internal/consume/types.ts
@@ -37,6 +37,7 @@ export type ConsumeContext = {
export type ConsumePropertyMetadata = {
description?: StaticMarkup;
inputName?: string;
+ alternateName?: string;
getDiagnosticLocation?: (
target: ConsumeSourceLocationRequestTarget,
) => DiagnosticLocation;

MODIFY
internal/diagnostics/descriptions/flags.ts
internal/diagnostics/descriptions/flags.ts
@@ -4,7 +4,9 @@ import {toKebabCase} from "@internal/string-utils";
import {DiagnosticAdvice} from "../types";
export const flags = createDiagnosticsCategory({
- UNSUPPORTED_SHORTHANDS: {message: markup`Shorthand flags are not supported`},
+ UNSUPPORTED_SHORTHAND: (flag: string) => ({
+ message: markup`The <emphasis>-${flag}</emphasis> flag is not a valid shorthand flag`,
+ }),
INCORRECT_CASED_FLAG: (flag: string) => ({
message: markup`Incorrect cased flag name`,
advice: [

MODIFY
internal/markup-syntax-highlight/highlightShell.ts
internal/markup-syntax-highlight/highlightShell.ts
@@ -11,12 +11,13 @@ import {markupTag} from "@internal/markup/escape";
export default function highlightShell(
opts: {
input: string;
+ isShorthand?: boolean;
},
): HighlightCodeResult {
// TODO properly handle strings with spaces
const segments: Array<string> = opts.input.split(" ");
- let firstCommandSegment = true;
+ let segmentCount = 0;
const parts = segments.map((segment, i) => {
if (i === 0) {
@@ -43,8 +44,9 @@ export default function highlightShell(
//return markupToken("operator", segment);
}
- if (firstCommandSegment) {
- firstCommandSegment = false;
+ segmentCount++;
+
+ if (segmentCount === 1 || (opts.isShorthand && segmentCount === 2)) {
return markupToken("function", segment);
}

feat: support shorthand help CLI flag (#1071)