@alpacakit/channels 0.1.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +772 -0
- package/dist/binding-compiler.d.ts +29 -0
- package/dist/binding-compiler.d.ts.map +1 -0
- package/dist/binding-compiler.js +40 -0
- package/dist/cli-coercion.d.ts +5 -0
- package/dist/cli-coercion.d.ts.map +1 -0
- package/dist/cli-coercion.js +51 -0
- package/dist/cli-compiler.d.ts +46 -0
- package/dist/cli-compiler.d.ts.map +1 -0
- package/dist/cli-compiler.js +213 -0
- package/dist/cli-executor.d.ts +25 -0
- package/dist/cli-executor.d.ts.map +1 -0
- package/dist/cli-executor.js +44 -0
- package/dist/cli-help-projection.d.ts +13 -0
- package/dist/cli-help-projection.d.ts.map +1 -0
- package/dist/cli-help-projection.js +159 -0
- package/dist/cli-model.d.ts +240 -0
- package/dist/cli-model.d.ts.map +1 -0
- package/dist/cli-model.js +89 -0
- package/dist/cli-presentation.d.ts +23 -0
- package/dist/cli-presentation.d.ts.map +1 -0
- package/dist/cli-presentation.js +265 -0
- package/dist/cli-schema.d.ts +4 -0
- package/dist/cli-schema.d.ts.map +1 -0
- package/dist/cli-schema.js +7 -0
- package/dist/cli-tree.d.ts +4 -0
- package/dist/cli-tree.d.ts.map +1 -0
- package/dist/cli-tree.js +421 -0
- package/dist/cli-values.d.ts +24 -0
- package/dist/cli-values.d.ts.map +1 -0
- package/dist/cli-values.js +40 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +5 -0
- package/dist/codecs.d.ts +25 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/codecs.js +74 -0
- package/dist/contract.d.ts +12 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +28 -0
- package/dist/factory.d.ts +16 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +188 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +3 -0
- package/dist/introspection.d.ts +40 -0
- package/dist/introspection.d.ts.map +1 -0
- package/dist/introspection.js +56 -0
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +84 -0
- package/dist/model.d.ts +166 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +22 -0
- package/dist/runtime.d.ts +47 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +140 -0
- package/dist/schema.d.ts +14 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +70 -0
- package/dist/tree-internal.d.ts +14 -0
- package/dist/tree-internal.d.ts.map +1 -0
- package/dist/tree-internal.js +20 -0
- package/dist/values.d.ts +32 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +30 -0
- package/package.json +47 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ErasedEntryDefinition, ErasedParameterDefinition } from "./model.js";
|
|
2
|
+
export declare const PARAMETER_BINDING_ISSUE_KIND: {
|
|
3
|
+
readonly duplicate: "duplicate";
|
|
4
|
+
readonly nonexistent: "nonexistent";
|
|
5
|
+
readonly requiredUnreachable: "required_unreachable";
|
|
6
|
+
};
|
|
7
|
+
export type ParameterBindingRow = {
|
|
8
|
+
readonly parameterName: string;
|
|
9
|
+
};
|
|
10
|
+
export type CompiledParameterBinding<Row extends ParameterBindingRow = ParameterBindingRow> = {
|
|
11
|
+
readonly row: Row;
|
|
12
|
+
readonly parameter: ErasedParameterDefinition;
|
|
13
|
+
};
|
|
14
|
+
export type ParameterBindingIssue = {
|
|
15
|
+
readonly kind: typeof PARAMETER_BINDING_ISSUE_KIND.duplicate;
|
|
16
|
+
readonly parameterName: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: typeof PARAMETER_BINDING_ISSUE_KIND.nonexistent;
|
|
19
|
+
readonly parameterName: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: typeof PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable;
|
|
22
|
+
readonly parameterName: string;
|
|
23
|
+
};
|
|
24
|
+
export type ParameterBindingCompilation<Row extends ParameterBindingRow> = {
|
|
25
|
+
readonly bindings: readonly CompiledParameterBinding<Row>[];
|
|
26
|
+
readonly issues: readonly ParameterBindingIssue[];
|
|
27
|
+
};
|
|
28
|
+
export declare function compileParameterBindings<Row extends ParameterBindingRow>(entry: ErasedEntryDefinition, rows: readonly Row[]): ParameterBindingCompilation<Row>;
|
|
29
|
+
//# sourceMappingURL=binding-compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-compiler.d.ts","sourceRoot":"","sources":["../src/binding-compiler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,4BAA4B;;;;CAI/B,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAClC,GAAG,SAAS,mBAAmB,GAAG,mBAAmB,IACnD;IACF,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,4BAA4B,CAAC,SAAS,CAAC;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,4BAA4B,CAAC,WAAW,CAAC;IAC/D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,4BAA4B,CAAC,mBAAmB,CAAC;IACvE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEN,MAAM,MAAM,2BAA2B,CAAC,GAAG,SAAS,mBAAmB,IAAI;IACzE,QAAQ,CAAC,QAAQ,EAAE,SAAS,wBAAwB,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACnD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,GAAG,SAAS,mBAAmB,EACtE,KAAK,EAAE,qBAAqB,EAC5B,IAAI,EAAE,SAAS,GAAG,EAAE,GACnB,2BAA2B,CAAC,GAAG,CAAC,CAkClC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { findParameter } from "./internal.js";
|
|
2
|
+
import { PARAMETER_REQUIREMENT_KIND } from "./values.js";
|
|
3
|
+
export const PARAMETER_BINDING_ISSUE_KIND = {
|
|
4
|
+
duplicate: "duplicate",
|
|
5
|
+
nonexistent: "nonexistent",
|
|
6
|
+
requiredUnreachable: "required_unreachable",
|
|
7
|
+
};
|
|
8
|
+
export function compileParameterBindings(entry, rows) {
|
|
9
|
+
const bindings = [];
|
|
10
|
+
const issues = [];
|
|
11
|
+
const names = new Set();
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
if (names.has(row.parameterName)) {
|
|
14
|
+
issues.push({
|
|
15
|
+
kind: PARAMETER_BINDING_ISSUE_KIND.duplicate,
|
|
16
|
+
parameterName: row.parameterName,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
names.add(row.parameterName);
|
|
20
|
+
const parameter = findParameter(entry.parameters, row.parameterName);
|
|
21
|
+
if (parameter === null) {
|
|
22
|
+
issues.push({
|
|
23
|
+
kind: PARAMETER_BINDING_ISSUE_KIND.nonexistent,
|
|
24
|
+
parameterName: row.parameterName,
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
bindings.push({ row, parameter });
|
|
29
|
+
}
|
|
30
|
+
for (const parameter of entry.parameters) {
|
|
31
|
+
if (parameter.requirement.kind === PARAMETER_REQUIREMENT_KIND.required &&
|
|
32
|
+
!names.has(parameter.name)) {
|
|
33
|
+
issues.push({
|
|
34
|
+
kind: PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable,
|
|
35
|
+
parameterName: parameter.name,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { bindings, issues };
|
|
40
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JsonValue } from "@alpacakit/core";
|
|
2
|
+
import { type CliValueCoercion } from "./cli-model.js";
|
|
3
|
+
import type { ValueShape } from "./model.js";
|
|
4
|
+
export declare function coerceCliArgvValue(shape: ValueShape, rawValue: unknown, coercion: CliValueCoercion | null): JsonValue | undefined;
|
|
5
|
+
//# sourceMappingURL=cli-coercion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-coercion.d.ts","sourceRoot":"","sources":["../src/cli-coercion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAChC,SAAS,GAAG,SAAS,CAWvB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CLI_VALUE_COERCION } from "./cli-model.js";
|
|
2
|
+
import { VALUE_SHAPE_KIND } from "./values.js";
|
|
3
|
+
export function coerceCliArgvValue(shape, rawValue, coercion) {
|
|
4
|
+
if (coercion !== null) {
|
|
5
|
+
return coerceExplicitCliValue(rawValue, coercion);
|
|
6
|
+
}
|
|
7
|
+
if (shape.kind === VALUE_SHAPE_KIND.array) {
|
|
8
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
9
|
+
return values.map((value) => coerceCliArgvScalarValue(shape.element, value));
|
|
10
|
+
}
|
|
11
|
+
return coerceCliArgvScalarValue(shape, rawValue);
|
|
12
|
+
}
|
|
13
|
+
function coerceCliArgvScalarValue(shape, value) {
|
|
14
|
+
if (shape.kind === VALUE_SHAPE_KIND.boolean) {
|
|
15
|
+
if (value === "true") {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (value === "false") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (shape.kind === VALUE_SHAPE_KIND.positiveInteger &&
|
|
23
|
+
typeof value === "string" &&
|
|
24
|
+
/^[+-]?\d+$/.test(value)) {
|
|
25
|
+
return Number(value);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function coerceExplicitCliValue(rawValue, coercion) {
|
|
30
|
+
if (Array.isArray(rawValue)) {
|
|
31
|
+
return rawValue.map((value) => coerceExplicitCliScalar(value, coercion));
|
|
32
|
+
}
|
|
33
|
+
return coerceExplicitCliScalar(rawValue, coercion);
|
|
34
|
+
}
|
|
35
|
+
function coerceExplicitCliScalar(value, coercion) {
|
|
36
|
+
if (typeof value !== "string") {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(value);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (coercion === CLI_VALUE_COERCION.jsonOrString) {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Invalid JSON value: ${errorMessage(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function errorMessage(error) {
|
|
50
|
+
return error instanceof Error ? error.message : String(error);
|
|
51
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CliEntryBinding, type CliLongOptionFlag, type CliOptionFlag, type CliOptionValueMode, type CliValueCoercion } from "./cli-model.js";
|
|
2
|
+
import { CLI_OPTION_KIND, CLI_POSITIONAL_PLAN_KIND } from "./cli-values.js";
|
|
3
|
+
import type { EntryTreeEndpoint, ErasedParameterDefinition } from "./model.js";
|
|
4
|
+
export declare const CLI_OPTION_ACTION_KIND: {
|
|
5
|
+
readonly setBoolean: "set_boolean";
|
|
6
|
+
readonly readValue: "read_value";
|
|
7
|
+
};
|
|
8
|
+
type CompiledCliOptionBase = {
|
|
9
|
+
readonly parameter: ErasedParameterDefinition;
|
|
10
|
+
readonly flags: readonly CliOptionFlag[];
|
|
11
|
+
readonly coercion: CliValueCoercion | null;
|
|
12
|
+
};
|
|
13
|
+
export type CompiledCliOption = (CompiledCliOptionBase & {
|
|
14
|
+
readonly kind: typeof CLI_OPTION_KIND.booleanFlag;
|
|
15
|
+
readonly negativeFlags: readonly CliLongOptionFlag[];
|
|
16
|
+
}) | (CompiledCliOptionBase & {
|
|
17
|
+
readonly kind: typeof CLI_OPTION_KIND.value;
|
|
18
|
+
readonly valueMode: CliOptionValueMode;
|
|
19
|
+
readonly valueName: string;
|
|
20
|
+
});
|
|
21
|
+
export type CompiledCliPositional = {
|
|
22
|
+
readonly kind: typeof CLI_POSITIONAL_PLAN_KIND.single | typeof CLI_POSITIONAL_PLAN_KIND.rest;
|
|
23
|
+
readonly parameter: ErasedParameterDefinition;
|
|
24
|
+
readonly valueName: string;
|
|
25
|
+
readonly coercion: CliValueCoercion | null;
|
|
26
|
+
};
|
|
27
|
+
export type CliOptionAction = {
|
|
28
|
+
readonly kind: typeof CLI_OPTION_ACTION_KIND.setBoolean;
|
|
29
|
+
readonly parameterName: string;
|
|
30
|
+
readonly value: boolean;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: typeof CLI_OPTION_ACTION_KIND.readValue;
|
|
33
|
+
readonly option: Extract<CompiledCliOption, {
|
|
34
|
+
readonly kind: typeof CLI_OPTION_KIND.value;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
export type CompiledCliEndpoint = {
|
|
38
|
+
readonly endpoint: EntryTreeEndpoint;
|
|
39
|
+
readonly help: CliEntryBinding["help"];
|
|
40
|
+
readonly options: readonly CompiledCliOption[];
|
|
41
|
+
readonly positionals: readonly CompiledCliPositional[];
|
|
42
|
+
readonly optionByToken: ReadonlyMap<string, CliOptionAction>;
|
|
43
|
+
};
|
|
44
|
+
export declare function compileCliBinding(endpoint: EntryTreeEndpoint, binding: CliEntryBinding, location: string, issues: string[]): CompiledCliEndpoint;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=cli-compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-compiler.d.ts","sourceRoot":"","sources":["../src/cli-compiler.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,eAAe,EAEf,wBAAwB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/E,eAAO,MAAM,sBAAsB;;;CAGzB,CAAC;AAUX,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB,CAAC,qBAAqB,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,eAAe,CAAC,WAAW,CAAC;IAClD,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACtD,CAAC,GACF,CAAC,qBAAqB,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,eAAe,CAAC,KAAK,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC,CAAC;AAEP,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,IAAI,EACT,OAAO,wBAAwB,CAAC,MAAM,GACtC,OAAO,wBAAwB,CAAC,IAAI,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,sBAAsB,CAAC,UAAU,CAAC;IACxD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,sBAAsB,CAAC,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CACtB,iBAAiB,EACjB;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,eAAe,CAAC,KAAK,CAAA;KAAE,CAChD,CAAC;CACH,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,QAAQ,CAAC,WAAW,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACvD,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC9D,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,GACf,mBAAmB,CAgDrB"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { compileParameterBindings, PARAMETER_BINDING_ISSUE_KIND, } from "./binding-compiler.js";
|
|
2
|
+
import { CLI_ARG_KIND, } from "./cli-model.js";
|
|
3
|
+
import { CLI_BOOLEAN_MODE, CLI_OPTION_KIND, CLI_OPTION_VALUE_MODE, CLI_POSITIONAL_PLAN_KIND, } from "./cli-values.js";
|
|
4
|
+
import { PARAMETER_REQUIREMENT_KIND, VALUE_SHAPE_KIND } from "./values.js";
|
|
5
|
+
export const CLI_OPTION_ACTION_KIND = {
|
|
6
|
+
setBoolean: "set_boolean",
|
|
7
|
+
readValue: "read_value",
|
|
8
|
+
};
|
|
9
|
+
const CLI_COMPILER_SYNTAX = {
|
|
10
|
+
assignmentSeparator: "=",
|
|
11
|
+
invalidLongOptionPrefix: "---",
|
|
12
|
+
longOptionPrefix: "--",
|
|
13
|
+
negatedLongOptionPrefix: "--no-",
|
|
14
|
+
optionPrefix: "-",
|
|
15
|
+
};
|
|
16
|
+
export function compileCliBinding(endpoint, binding, location, issues) {
|
|
17
|
+
const compiledParameters = compileParameterBindings(endpoint.entry, binding.parameters);
|
|
18
|
+
issues.push(...compiledParameters.issues.map((issue) => formatCliParameterBindingIssue(location, issue)));
|
|
19
|
+
const options = [];
|
|
20
|
+
const positionals = [];
|
|
21
|
+
for (const compiled of compiledParameters.bindings) {
|
|
22
|
+
if (compiled.row.kind === CLI_ARG_KIND.option) {
|
|
23
|
+
options.push(compileCliOption({ row: compiled.row, parameter: compiled.parameter }, location, issues));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
positionals.push(compileCliPositional({ row: compiled.row, parameter: compiled.parameter }, location, issues));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const [index, positional] of positionals.entries()) {
|
|
30
|
+
if (positional.kind === CLI_POSITIONAL_PLAN_KIND.rest &&
|
|
31
|
+
index !== positionals.length - 1) {
|
|
32
|
+
issues.push(`${location}: variadic positional ${positional.parameter.name} must be last`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
endpoint,
|
|
37
|
+
help: binding.help,
|
|
38
|
+
options,
|
|
39
|
+
positionals,
|
|
40
|
+
optionByToken: createCliOptionActionIndex(options, location, issues),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function compileCliOption(compiled, location, issues) {
|
|
44
|
+
const { row, parameter } = compiled;
|
|
45
|
+
const flags = row.flags;
|
|
46
|
+
if (flags.length === 0) {
|
|
47
|
+
issues.push(`${location}: option ${row.parameterName} must declare a flag`);
|
|
48
|
+
}
|
|
49
|
+
for (const flag of flags) {
|
|
50
|
+
if (!isReachableCliOptionFlag(flag)) {
|
|
51
|
+
issues.push(`${location}: option ${row.parameterName} declares unreachable CLI flag ${JSON.stringify(flag)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (row.valueName.length === 0) {
|
|
55
|
+
issues.push(`${location}: option ${row.parameterName} valueName must not be empty`);
|
|
56
|
+
}
|
|
57
|
+
switch (parameter.codec.shape.kind) {
|
|
58
|
+
case VALUE_SHAPE_KIND.boolean:
|
|
59
|
+
return compileCliBooleanOption(compiled, flags, location, issues);
|
|
60
|
+
case VALUE_SHAPE_KIND.array:
|
|
61
|
+
rejectBooleanChoices(row, location, issues);
|
|
62
|
+
return {
|
|
63
|
+
kind: CLI_OPTION_KIND.value,
|
|
64
|
+
parameter,
|
|
65
|
+
flags,
|
|
66
|
+
coercion: row.coercion,
|
|
67
|
+
valueName: row.valueName,
|
|
68
|
+
valueMode: row.repeatable
|
|
69
|
+
? CLI_OPTION_VALUE_MODE.commaOrRepeat
|
|
70
|
+
: CLI_OPTION_VALUE_MODE.repeat,
|
|
71
|
+
};
|
|
72
|
+
case VALUE_SHAPE_KIND.positiveInteger:
|
|
73
|
+
case VALUE_SHAPE_KIND.string:
|
|
74
|
+
case VALUE_SHAPE_KIND.stringEnum:
|
|
75
|
+
rejectBooleanChoices(row, location, issues);
|
|
76
|
+
if (row.repeatable) {
|
|
77
|
+
issues.push(`${location}: repeatMode contradicts ${row.parameterName} codec shape`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
kind: CLI_OPTION_KIND.value,
|
|
81
|
+
parameter,
|
|
82
|
+
flags,
|
|
83
|
+
coercion: row.coercion,
|
|
84
|
+
valueName: row.valueName,
|
|
85
|
+
valueMode: CLI_OPTION_VALUE_MODE.replace,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function isReachableCliOptionFlag(flag) {
|
|
90
|
+
return (flag.startsWith(CLI_COMPILER_SYNTAX.optionPrefix) &&
|
|
91
|
+
flag !== CLI_COMPILER_SYNTAX.optionPrefix &&
|
|
92
|
+
flag !== CLI_COMPILER_SYNTAX.longOptionPrefix &&
|
|
93
|
+
!flag.startsWith(CLI_COMPILER_SYNTAX.invalidLongOptionPrefix) &&
|
|
94
|
+
!flag.includes(CLI_COMPILER_SYNTAX.assignmentSeparator) &&
|
|
95
|
+
!/\s/.test(flag));
|
|
96
|
+
}
|
|
97
|
+
function compileCliBooleanOption(compiled, flags, location, issues) {
|
|
98
|
+
const { row, parameter } = compiled;
|
|
99
|
+
if (row.repeatable) {
|
|
100
|
+
issues.push(`${location}: repeatMode contradicts ${row.parameterName} codec shape`);
|
|
101
|
+
}
|
|
102
|
+
const booleanMode = row.booleanMode ??
|
|
103
|
+
(parameter.requirement.kind ===
|
|
104
|
+
PARAMETER_REQUIREMENT_KIND.optionalWithDefault
|
|
105
|
+
? CLI_BOOLEAN_MODE.flag
|
|
106
|
+
: CLI_BOOLEAN_MODE.value);
|
|
107
|
+
if (booleanMode === CLI_BOOLEAN_MODE.value) {
|
|
108
|
+
if (row.negated) {
|
|
109
|
+
issues.push(`${location}: negated option ${row.parameterName} requires flag booleanMode`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
kind: CLI_OPTION_KIND.value,
|
|
113
|
+
parameter,
|
|
114
|
+
flags,
|
|
115
|
+
coercion: row.coercion,
|
|
116
|
+
valueName: row.valueName,
|
|
117
|
+
valueMode: CLI_OPTION_VALUE_MODE.replace,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (row.valueNameAuthored) {
|
|
121
|
+
issues.push(`${location}: valueName contradicts flag booleanMode for ${row.parameterName}`);
|
|
122
|
+
}
|
|
123
|
+
if (row.coercion !== null) {
|
|
124
|
+
issues.push(`${location}: coercion contradicts flag booleanMode for ${row.parameterName}`);
|
|
125
|
+
}
|
|
126
|
+
const negativeFlags = row.negated
|
|
127
|
+
? flags.filter(isCliLongOptionFlag).map(negateCliLongOptionFlag)
|
|
128
|
+
: [];
|
|
129
|
+
if (row.negated && negativeFlags.length === 0) {
|
|
130
|
+
issues.push(`${location}: negated option ${row.parameterName} requires a long flag`);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
kind: CLI_OPTION_KIND.booleanFlag,
|
|
134
|
+
parameter,
|
|
135
|
+
flags,
|
|
136
|
+
coercion: null,
|
|
137
|
+
negativeFlags,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function isCliLongOptionFlag(flag) {
|
|
141
|
+
return flag.startsWith(CLI_COMPILER_SYNTAX.longOptionPrefix);
|
|
142
|
+
}
|
|
143
|
+
function negateCliLongOptionFlag(flag) {
|
|
144
|
+
return `${CLI_COMPILER_SYNTAX.negatedLongOptionPrefix}${flag.slice(CLI_COMPILER_SYNTAX.longOptionPrefix.length)}`;
|
|
145
|
+
}
|
|
146
|
+
function rejectBooleanChoices(row, location, issues) {
|
|
147
|
+
if (row.booleanMode !== null) {
|
|
148
|
+
issues.push(`${location}: booleanMode contradicts ${row.parameterName} codec shape`);
|
|
149
|
+
}
|
|
150
|
+
if (row.negated) {
|
|
151
|
+
issues.push(`${location}: negated contradicts ${row.parameterName} codec shape`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function compileCliPositional(compiled, location, issues) {
|
|
155
|
+
const { row, parameter } = compiled;
|
|
156
|
+
if (row.variadic && parameter.codec.shape.kind !== VALUE_SHAPE_KIND.array) {
|
|
157
|
+
issues.push(`${location}: variadic positional ${row.parameterName} requires an array codec`);
|
|
158
|
+
}
|
|
159
|
+
if (row.valueName.length === 0) {
|
|
160
|
+
issues.push(`${location}: positional ${row.parameterName} valueName must not be empty`);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
kind: row.variadic
|
|
164
|
+
? CLI_POSITIONAL_PLAN_KIND.rest
|
|
165
|
+
: CLI_POSITIONAL_PLAN_KIND.single,
|
|
166
|
+
parameter,
|
|
167
|
+
valueName: row.valueName,
|
|
168
|
+
coercion: row.coercion,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function createCliOptionActionIndex(options, location, issues) {
|
|
172
|
+
const actions = new Map();
|
|
173
|
+
for (const option of options) {
|
|
174
|
+
if (option.kind === CLI_OPTION_KIND.booleanFlag) {
|
|
175
|
+
for (const token of option.flags) {
|
|
176
|
+
registerCliOptionAction(actions, token, {
|
|
177
|
+
kind: CLI_OPTION_ACTION_KIND.setBoolean,
|
|
178
|
+
parameterName: option.parameter.name,
|
|
179
|
+
value: true,
|
|
180
|
+
}, location, issues);
|
|
181
|
+
}
|
|
182
|
+
for (const token of option.negativeFlags) {
|
|
183
|
+
registerCliOptionAction(actions, token, {
|
|
184
|
+
kind: CLI_OPTION_ACTION_KIND.setBoolean,
|
|
185
|
+
parameterName: option.parameter.name,
|
|
186
|
+
value: false,
|
|
187
|
+
}, location, issues);
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
for (const token of option.flags) {
|
|
192
|
+
registerCliOptionAction(actions, token, { kind: CLI_OPTION_ACTION_KIND.readValue, option }, location, issues);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return actions;
|
|
196
|
+
}
|
|
197
|
+
function registerCliOptionAction(actions, token, action, location, issues) {
|
|
198
|
+
if (actions.has(token)) {
|
|
199
|
+
issues.push(`${location}: duplicate CLI option flag ${token}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
actions.set(token, action);
|
|
203
|
+
}
|
|
204
|
+
function formatCliParameterBindingIssue(location, issue) {
|
|
205
|
+
switch (issue.kind) {
|
|
206
|
+
case PARAMETER_BINDING_ISSUE_KIND.duplicate:
|
|
207
|
+
return `${location}: duplicate binding for parameter ${issue.parameterName}`;
|
|
208
|
+
case PARAMETER_BINDING_ISSUE_KIND.nonexistent:
|
|
209
|
+
return `${location}: binding names nonexistent parameter ${issue.parameterName}`;
|
|
210
|
+
case PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable:
|
|
211
|
+
return `${location}: required parameter ${issue.parameterName} is unreachable from CLI`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CLI_ROUTER_RESULT_KIND, CliResolvedEndpointResult, CliRouter, CliRouterResult } from "./cli-model.js";
|
|
2
|
+
import type { EntryTreeEndpoint } from "./model.js";
|
|
3
|
+
type ResolvedCliResult = Extract<CliRouterResult, {
|
|
4
|
+
readonly kind: typeof CLI_ROUTER_RESULT_KIND.resolved;
|
|
5
|
+
}>;
|
|
6
|
+
export type CliEndpointHandlerBinding<Endpoint extends EntryTreeEndpoint = EntryTreeEndpoint, Result = unknown> = {
|
|
7
|
+
readonly endpoint: Endpoint;
|
|
8
|
+
readonly handler: (input: CliResolvedEndpointResult<Endpoint>) => Result | Promise<Result>;
|
|
9
|
+
};
|
|
10
|
+
export type CliEndpointHandlerRegistration<Result = unknown> = {
|
|
11
|
+
readonly endpoint: EntryTreeEndpoint;
|
|
12
|
+
readonly handler: (...args: never[]) => Result | Promise<Result>;
|
|
13
|
+
};
|
|
14
|
+
export declare class CliExecutorDefinitionError extends Error {
|
|
15
|
+
readonly issues: readonly string[];
|
|
16
|
+
constructor(issues: readonly string[]);
|
|
17
|
+
}
|
|
18
|
+
export declare function handleCliEndpoint<const Endpoint extends EntryTreeEndpoint, Result>(endpoint: Endpoint, handler: (input: CliResolvedEndpointResult<Endpoint>) => Result | Promise<Result>): CliEndpointHandlerBinding<Endpoint, Result>;
|
|
19
|
+
type CliHandlerResult<Bindings extends readonly CliEndpointHandlerRegistration[]> = Awaited<ReturnType<Bindings[number]["handler"]>>;
|
|
20
|
+
export type CliExecutor<Bindings extends readonly CliEndpointHandlerRegistration[]> = {
|
|
21
|
+
execute(result: ResolvedCliResult): Promise<CliHandlerResult<Bindings>>;
|
|
22
|
+
};
|
|
23
|
+
export declare function createCliExecutor<const Bindings extends readonly CliEndpointHandlerRegistration[]>(router: CliRouter, bindings: Bindings): CliExecutor<Bindings>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=cli-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-executor.d.ts","sourceRoot":"","sources":["../src/cli-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EACzB,SAAS,EACT,eAAe,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,KAAK,iBAAiB,GAAG,OAAO,CAC9B,eAAe,EACf;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,sBAAsB,CAAC,QAAQ,CAAA;CAAE,CAC1D,CAAC;AAEF,MAAM,MAAM,yBAAyB,CACnC,QAAQ,SAAS,iBAAiB,GAAG,iBAAiB,EACtD,MAAM,GAAG,OAAO,IACd;IACF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,yBAAyB,CAAC,QAAQ,CAAC,KACvC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,8BAA8B,CAAC,MAAM,GAAG,OAAO,IAAI;IAC7D,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE,CAAC;AAEF,qBAAa,0BAA2B,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE;gBAAzB,MAAM,EAAE,SAAS,MAAM,EAAE;CAI/C;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,QAAQ,SAAS,iBAAiB,EACxC,MAAM,EAEN,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,CACP,KAAK,EAAE,yBAAyB,CAAC,QAAQ,CAAC,KACvC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC5B,yBAAyB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAE7C;AAED,KAAK,gBAAgB,CACnB,QAAQ,SAAS,SAAS,8BAA8B,EAAE,IACxD,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAErD,MAAM,MAAM,WAAW,CACrB,QAAQ,SAAS,SAAS,8BAA8B,EAAE,IACxD;IACF,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;CACzE,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,QAAQ,SAAS,SAAS,8BAA8B,EAAE,EAChE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAsC9D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class CliExecutorDefinitionError extends Error {
|
|
2
|
+
issues;
|
|
3
|
+
constructor(issues) {
|
|
4
|
+
super(`Invalid CLI executor definition:\n${issues.join("\n")}`);
|
|
5
|
+
this.issues = issues;
|
|
6
|
+
this.name = CliExecutorDefinitionError.name;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function handleCliEndpoint(endpoint, handler) {
|
|
10
|
+
return { endpoint, handler };
|
|
11
|
+
}
|
|
12
|
+
export function createCliExecutor(router, bindings) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
const routerEndpoints = new Set(router.endpoints);
|
|
15
|
+
const handlers = new Map();
|
|
16
|
+
for (const binding of bindings) {
|
|
17
|
+
if (!routerEndpoints.has(binding.endpoint)) {
|
|
18
|
+
issues.push(`handler endpoint ${binding.endpoint.entry.name} is not in the router`);
|
|
19
|
+
}
|
|
20
|
+
if (handlers.has(binding.endpoint)) {
|
|
21
|
+
issues.push(`duplicate handler for endpoint ${binding.endpoint.entry.name}`);
|
|
22
|
+
}
|
|
23
|
+
handlers.set(binding.endpoint, binding);
|
|
24
|
+
}
|
|
25
|
+
for (const endpoint of router.endpoints) {
|
|
26
|
+
if (!handlers.has(endpoint)) {
|
|
27
|
+
issues.push(`missing handler for endpoint ${endpoint.entry.name}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (issues.length > 0) {
|
|
31
|
+
throw new CliExecutorDefinitionError(issues);
|
|
32
|
+
}
|
|
33
|
+
const execute = async (result) => {
|
|
34
|
+
const binding = handlers.get(result.endpoint);
|
|
35
|
+
if (!binding) {
|
|
36
|
+
throw new CliExecutorDefinitionError([
|
|
37
|
+
`missing handler for endpoint ${result.endpoint.entry.name}`,
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
const handler = binding.handler;
|
|
41
|
+
return await handler(result);
|
|
42
|
+
};
|
|
43
|
+
return { execute };
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CompiledCliEndpoint } from "./cli-compiler.js";
|
|
2
|
+
import type { CliHelpExample, CliHelpSurface, CliSegment } from "./cli-model.js";
|
|
3
|
+
import type { EntryTreeNode } from "./model.js";
|
|
4
|
+
export declare function createCliHelpSurface(input: {
|
|
5
|
+
readonly node: EntryTreeNode;
|
|
6
|
+
readonly segment: CliSegment;
|
|
7
|
+
readonly endpoint: CompiledCliEndpoint | null;
|
|
8
|
+
readonly childHelp: readonly CliHelpSurface[];
|
|
9
|
+
readonly root: boolean;
|
|
10
|
+
}): CliHelpSurface;
|
|
11
|
+
export declare function validateCliCommandHelp(endpoint: CompiledCliEndpoint, location: string, issues: string[]): void;
|
|
12
|
+
export declare function validateCliHelpExamples(examples: readonly CliHelpExample[], location: string, issues: string[]): void;
|
|
13
|
+
//# sourceMappingURL=cli-help-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-help-projection.d.ts","sourceRoot":"","sources":["../src/cli-help-projection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAGpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,cAAc,EAKd,cAAc,EAGd,UAAU,EACX,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA4ChD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,GAAG,cAAc,CAoBjB;AAkCD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CA2BN;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CAaN"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { CLI_HELP_USAGE_KIND, CLI_OPTION_KIND, CLI_POSITIONAL_PLAN_KIND, } from "./cli-values.js";
|
|
2
|
+
import { describeParameterRequirement } from "./introspection.js";
|
|
3
|
+
import { PARAMETER_REQUIREMENT_KIND } from "./values.js";
|
|
4
|
+
const CLI_HELP_USAGE_SYNTAX = {
|
|
5
|
+
commandArgument: "<command>",
|
|
6
|
+
commandPartSeparator: " ",
|
|
7
|
+
emptyPart: "",
|
|
8
|
+
optionArgument: "[options]",
|
|
9
|
+
optionalClose: "]",
|
|
10
|
+
optionalOpen: "[",
|
|
11
|
+
requiredClose: ">",
|
|
12
|
+
requiredOpen: "<",
|
|
13
|
+
variadicSuffix: "...",
|
|
14
|
+
};
|
|
15
|
+
const CLI_HELP_VALIDATION_ISSUE = {
|
|
16
|
+
duplicateSectionParameter: (location, parameterName) => `${location}: help option parameter ${parameterName} appears more than once in option sections`,
|
|
17
|
+
emptyExampleCommand: "CLI help example command must not be empty",
|
|
18
|
+
emptyExampleTitle: "CLI help example title must not be empty",
|
|
19
|
+
nonOptionSectionParameter: (location, heading, parameterName) => `${location}: help option section ${JSON.stringify(heading)} names non-option parameter ${parameterName}`,
|
|
20
|
+
};
|
|
21
|
+
const GROUP_HELP_PROJECTION = {
|
|
22
|
+
examples: [],
|
|
23
|
+
options: [],
|
|
24
|
+
optionSections: [],
|
|
25
|
+
positionals: [],
|
|
26
|
+
usage: { kind: CLI_HELP_USAGE_KIND.derived },
|
|
27
|
+
};
|
|
28
|
+
export function createCliHelpSurface(input) {
|
|
29
|
+
const endpoint = projectCliEndpointHelp(input.endpoint);
|
|
30
|
+
const hasChildren = input.childHelp.length > 0;
|
|
31
|
+
return {
|
|
32
|
+
name: input.node.name,
|
|
33
|
+
token: input.root ? null : input.segment.token,
|
|
34
|
+
aliases: input.root ? [] : input.segment.aliases,
|
|
35
|
+
description: input.node.description,
|
|
36
|
+
remarks: input.node.documentation.remarks,
|
|
37
|
+
relatedDocs: input.node.documentation.relatedDocs,
|
|
38
|
+
optionGroups: projectCliHelpOptionGroups(endpoint.options, endpoint.optionSections),
|
|
39
|
+
positionals: endpoint.positionals,
|
|
40
|
+
usages: resolveCliUsages(endpoint, hasChildren),
|
|
41
|
+
examples: [...input.segment.help.examples, ...endpoint.examples],
|
|
42
|
+
subcommandHint: input.segment.help.subcommandHint,
|
|
43
|
+
children: input.childHelp,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function projectCliHelpOptionGroups(options, sections) {
|
|
47
|
+
const optionByParameterName = new Map(options.map((option) => [option.parameterName, option]));
|
|
48
|
+
const sectionedParameterNames = new Set(sections.flatMap((section) => section.parameterNames));
|
|
49
|
+
const groups = sections.map((section) => ({
|
|
50
|
+
heading: section.heading,
|
|
51
|
+
description: section.description,
|
|
52
|
+
// Section references are validated before the completed router is exposed.
|
|
53
|
+
options: section.parameterNames.map((parameterName) => optionByParameterName.get(parameterName)),
|
|
54
|
+
}));
|
|
55
|
+
const unsectionedOptions = options.filter((option) => !sectionedParameterNames.has(option.parameterName));
|
|
56
|
+
if (unsectionedOptions.length > 0) {
|
|
57
|
+
groups.push({
|
|
58
|
+
heading: null,
|
|
59
|
+
description: null,
|
|
60
|
+
options: unsectionedOptions,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return groups;
|
|
64
|
+
}
|
|
65
|
+
export function validateCliCommandHelp(endpoint, location, issues) {
|
|
66
|
+
const { help, options } = endpoint;
|
|
67
|
+
validateCliHelpExamples(help.examples, location, issues);
|
|
68
|
+
const optionNames = new Set(options.map((option) => option.parameter.name));
|
|
69
|
+
const sectionedNames = new Set();
|
|
70
|
+
for (const section of help.optionSections) {
|
|
71
|
+
for (const parameterName of section.parameterNames) {
|
|
72
|
+
if (!optionNames.has(parameterName)) {
|
|
73
|
+
issues.push(CLI_HELP_VALIDATION_ISSUE.nonOptionSectionParameter(location, section.heading, parameterName));
|
|
74
|
+
}
|
|
75
|
+
if (sectionedNames.has(parameterName)) {
|
|
76
|
+
issues.push(CLI_HELP_VALIDATION_ISSUE.duplicateSectionParameter(location, parameterName));
|
|
77
|
+
}
|
|
78
|
+
sectionedNames.add(parameterName);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function validateCliHelpExamples(examples, location, issues) {
|
|
83
|
+
for (const example of examples) {
|
|
84
|
+
if (example.title.length === 0) {
|
|
85
|
+
issues.push(`${location}: ${CLI_HELP_VALIDATION_ISSUE.emptyExampleTitle}`);
|
|
86
|
+
}
|
|
87
|
+
if (example.command.length === 0) {
|
|
88
|
+
issues.push(`${location}: ${CLI_HELP_VALIDATION_ISSUE.emptyExampleCommand}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function projectCliEndpointHelp(endpoint) {
|
|
93
|
+
if (endpoint === null) {
|
|
94
|
+
return GROUP_HELP_PROJECTION;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
examples: endpoint.help.examples,
|
|
98
|
+
options: endpoint.options.map(cliHelpOptionFor),
|
|
99
|
+
optionSections: endpoint.help.optionSections,
|
|
100
|
+
positionals: endpoint.positionals.map(cliHelpPositionalFor),
|
|
101
|
+
usage: endpoint.help.usage,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function resolveCliUsages(endpoint, hasChildren) {
|
|
105
|
+
switch (endpoint.usage.kind) {
|
|
106
|
+
case CLI_HELP_USAGE_KIND.authored:
|
|
107
|
+
return endpoint.usage.values;
|
|
108
|
+
case CLI_HELP_USAGE_KIND.derived:
|
|
109
|
+
return [deriveCliUsage(endpoint, hasChildren)];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function deriveCliUsage(endpoint, hasChildren) {
|
|
113
|
+
const parts = [];
|
|
114
|
+
if (endpoint.options.length > 0) {
|
|
115
|
+
parts.push(CLI_HELP_USAGE_SYNTAX.optionArgument);
|
|
116
|
+
}
|
|
117
|
+
parts.push(...endpoint.positionals.map(formatCliUsagePositional));
|
|
118
|
+
if (hasChildren) {
|
|
119
|
+
parts.push(CLI_HELP_USAGE_SYNTAX.commandArgument);
|
|
120
|
+
}
|
|
121
|
+
return parts.join(CLI_HELP_USAGE_SYNTAX.commandPartSeparator);
|
|
122
|
+
}
|
|
123
|
+
function formatCliUsagePositional(positional) {
|
|
124
|
+
const name = `${positional.valueName}${positional.variadic
|
|
125
|
+
? CLI_HELP_USAGE_SYNTAX.variadicSuffix
|
|
126
|
+
: CLI_HELP_USAGE_SYNTAX.emptyPart}`;
|
|
127
|
+
return positional.requirement.kind === PARAMETER_REQUIREMENT_KIND.required
|
|
128
|
+
? `${CLI_HELP_USAGE_SYNTAX.requiredOpen}${name}${CLI_HELP_USAGE_SYNTAX.requiredClose}`
|
|
129
|
+
: `${CLI_HELP_USAGE_SYNTAX.optionalOpen}${name}${CLI_HELP_USAGE_SYNTAX.optionalClose}`;
|
|
130
|
+
}
|
|
131
|
+
function cliHelpOptionFor(option) {
|
|
132
|
+
const base = {
|
|
133
|
+
parameterName: option.parameter.name,
|
|
134
|
+
flags: option.flags,
|
|
135
|
+
description: option.parameter.description,
|
|
136
|
+
requirement: describeParameterRequirement(option.parameter.requirement),
|
|
137
|
+
};
|
|
138
|
+
return option.kind === CLI_OPTION_KIND.booleanFlag
|
|
139
|
+
? {
|
|
140
|
+
...base,
|
|
141
|
+
kind: CLI_OPTION_KIND.booleanFlag,
|
|
142
|
+
negativeFlags: option.negativeFlags,
|
|
143
|
+
}
|
|
144
|
+
: {
|
|
145
|
+
...base,
|
|
146
|
+
kind: CLI_OPTION_KIND.value,
|
|
147
|
+
valueName: option.valueName,
|
|
148
|
+
valueMode: option.valueMode,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function cliHelpPositionalFor(positional) {
|
|
152
|
+
return {
|
|
153
|
+
parameterName: positional.parameter.name,
|
|
154
|
+
valueName: positional.valueName,
|
|
155
|
+
description: positional.parameter.description,
|
|
156
|
+
requirement: describeParameterRequirement(positional.parameter.requirement),
|
|
157
|
+
variadic: positional.kind === CLI_POSITIONAL_PLAN_KIND.rest,
|
|
158
|
+
};
|
|
159
|
+
}
|