@alpacakit/channels 0.1.0-beta.36 → 0.1.0-beta.39

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.
Files changed (99) hide show
  1. package/README.md +204 -723
  2. package/dist/binding-compiler.d.ts +12 -2
  3. package/dist/binding-compiler.d.ts.map +1 -1
  4. package/dist/binding-compiler.js +25 -0
  5. package/dist/cli-coercion.d.ts +1 -1
  6. package/dist/cli-coercion.d.ts.map +1 -1
  7. package/dist/cli-coercion.js +14 -29
  8. package/dist/cli-compiler.d.ts +4 -17
  9. package/dist/cli-compiler.d.ts.map +1 -1
  10. package/dist/cli-compiler.js +78 -39
  11. package/dist/cli-help-projection.d.ts.map +1 -1
  12. package/dist/cli-help-projection.js +41 -36
  13. package/dist/cli-model.d.ts +50 -65
  14. package/dist/cli-model.d.ts.map +1 -1
  15. package/dist/cli-model.js +26 -34
  16. package/dist/cli-tree.d.ts +6 -3
  17. package/dist/cli-tree.d.ts.map +1 -1
  18. package/dist/cli-tree.js +161 -387
  19. package/dist/cli.d.ts +1 -5
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +1 -5
  22. package/dist/codecs.d.ts +20 -18
  23. package/dist/codecs.d.ts.map +1 -1
  24. package/dist/codecs.js +21 -31
  25. package/dist/contract.d.ts.map +1 -1
  26. package/dist/contract.js +8 -3
  27. package/dist/definition-error.d.ts +5 -0
  28. package/dist/definition-error.d.ts.map +1 -0
  29. package/dist/definition-error.js +8 -0
  30. package/dist/factory.d.ts +25 -11
  31. package/dist/factory.d.ts.map +1 -1
  32. package/dist/factory.js +28 -18
  33. package/dist/http-compiler.d.ts +15 -0
  34. package/dist/http-compiler.d.ts.map +1 -0
  35. package/dist/http-compiler.js +37 -0
  36. package/dist/http-model.d.ts +42 -0
  37. package/dist/http-model.d.ts.map +1 -0
  38. package/dist/http-model.js +20 -0
  39. package/dist/http-tree.d.ts +7 -0
  40. package/dist/http-tree.d.ts.map +1 -0
  41. package/dist/http-tree.js +181 -0
  42. package/dist/http.d.ts +3 -0
  43. package/dist/http.d.ts.map +1 -0
  44. package/dist/http.js +2 -0
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/input-compiler.d.ts +13 -0
  49. package/dist/input-compiler.d.ts.map +1 -0
  50. package/dist/input-compiler.js +122 -0
  51. package/dist/introspection.d.ts +1 -1
  52. package/dist/introspection.d.ts.map +1 -1
  53. package/dist/introspection.js +30 -16
  54. package/dist/mcp.d.ts +25 -24
  55. package/dist/mcp.d.ts.map +1 -1
  56. package/dist/mcp.js +87 -65
  57. package/dist/model.d.ts +44 -42
  58. package/dist/model.d.ts.map +1 -1
  59. package/dist/model.js +44 -9
  60. package/dist/owned.d.ts +12 -0
  61. package/dist/owned.d.ts.map +1 -0
  62. package/dist/owned.js +61 -0
  63. package/dist/registration.d.ts +41 -0
  64. package/dist/registration.d.ts.map +1 -0
  65. package/dist/registration.js +14 -0
  66. package/dist/runtime.d.ts +2 -35
  67. package/dist/runtime.d.ts.map +1 -1
  68. package/dist/runtime.js +2 -127
  69. package/dist/schema.d.ts +11 -8
  70. package/dist/schema.d.ts.map +1 -1
  71. package/dist/schema.js +110 -47
  72. package/dist/string-coercion.d.ts +4 -0
  73. package/dist/string-coercion.d.ts.map +1 -0
  74. package/dist/string-coercion.js +24 -0
  75. package/dist/tree-internal.d.ts +4 -2
  76. package/dist/tree-internal.d.ts.map +1 -1
  77. package/dist/tree-internal.js +5 -2
  78. package/dist/values.d.ts +1 -6
  79. package/dist/values.d.ts.map +1 -1
  80. package/dist/values.js +1 -6
  81. package/examples/README.md +28 -0
  82. package/examples/book-search.ts +151 -0
  83. package/examples/greeting.ts +193 -0
  84. package/package.json +9 -4
  85. package/dist/cli-application.d.ts +0 -40
  86. package/dist/cli-application.d.ts.map +0 -1
  87. package/dist/cli-application.js +0 -90
  88. package/dist/cli-executor.d.ts +0 -25
  89. package/dist/cli-executor.d.ts.map +0 -1
  90. package/dist/cli-executor.js +0 -44
  91. package/dist/cli-presentation.d.ts +0 -32
  92. package/dist/cli-presentation.d.ts.map +0 -1
  93. package/dist/cli-presentation.js +0 -293
  94. package/dist/cli-result-envelope.d.ts +0 -104
  95. package/dist/cli-result-envelope.d.ts.map +0 -1
  96. package/dist/cli-result-envelope.js +0 -137
  97. package/dist/cli-schema.d.ts +0 -4
  98. package/dist/cli-schema.d.ts.map +0 -1
  99. package/dist/cli-schema.js +0 -7
package/dist/cli-tree.js CHANGED
@@ -1,423 +1,197 @@
1
1
  import { coerceCliArgvValue } from "./cli-coercion.js";
2
- import { CLI_OPTION_ACTION_KIND, compileCliBinding, } from "./cli-compiler.js";
2
+ import { compileCliBinding, projectCliParameters, } from "./cli-compiler.js";
3
3
  import { createCliHelpSurface, validateCliCommandHelp, validateCliHelpExamples, } from "./cli-help-projection.js";
4
- import { CLI_ROUTER_RESULT_KIND, } from "./cli-model.js";
5
- import { isCliEntryBinding, isCliSegment } from "./cli-schema.js";
6
- import { CLI_OPTION_VALUE_MODE, CLI_POSITIONAL_PLAN_KIND, } from "./cli-values.js";
7
- import { decodeEntryInput } from "./runtime.js";
8
- import { EntryTreeDefinitionError, findChannelBinding, formatTreePath, validateEntryTreeStructure, visitEntryTree, } from "./schema.js";
9
- import { CHANNEL, ENTRY_DECODE_RESULT_KIND } from "./values.js";
10
- const CLI_ARGUMENT_SYNTAX = {
11
- inlineValueSeparator: "=",
12
- optionPrefix: "-",
13
- positionalSeparator: "--",
14
- repeatedValueSeparator: ",",
15
- };
16
- const CLI_ARGUMENT_TOKEN_KIND = {
17
- option: "option",
18
- positionalSeparator: "positional_separator",
19
- value: "value",
20
- };
21
- const CLI_PARSE_RESULT_KIND = {
22
- parsed: "parsed",
23
- raw: "raw",
24
- };
25
- const CLI_TREE_VALIDATION_ISSUE = {
26
- subcommandHintWithoutChildren: "CLI subcommand hint requires at least one child command",
27
- };
28
- const EMPTY_CLI_GROUP_HELP = {
4
+ import { CLI_ARG_KIND, } from "./cli-model.js";
5
+ import { compileInputPlan } from "./input-compiler.js";
6
+ import { ownedArray, ownedRecord } from "./owned.js";
7
+ import { packRegistrationCollection, } from "./registration.js";
8
+ import { EntryTreeDefinitionError, findChannelBinding, formatTreePath, isEntryTreeDefinition, validateEntryTreeStructure, } from "./schema.js";
9
+ import { isChannelPayload, PAYLOAD_KIND } from "./tree-internal.js";
10
+ import { CHANNEL } from "./values.js";
11
+ const EMPTY_CLI_GROUP_HELP = ownedRecord({
29
12
  subcommandHint: null,
30
- examples: [],
31
- };
32
- export function createCliRouter(tree) {
33
- const root = compileCliTree(tree);
34
- const endpointLocations = listCliEndpointLocations(root);
35
- return {
36
- help: root.help,
37
- endpoints: endpointLocations.map((location) => location.endpoint),
38
- endpointLocations,
39
- resolve: (argv) => resolveCli(root, argv),
40
- };
41
- }
42
- function listCliEndpointLocations(root) {
43
- const locations = [];
44
- const visited = new Set();
45
- const pending = [root];
46
- while (pending.length > 0) {
47
- const node = pending.pop();
48
- if (!node || visited.has(node)) {
49
- continue;
50
- }
51
- visited.add(node);
52
- if (node.endpoint !== null) {
53
- locations.push({
54
- endpoint: node.endpoint.endpoint,
55
- path: node.path.slice(1),
56
- binding: node.endpoint.binding,
57
- });
58
- }
59
- pending.push(...node.childByToken.values());
60
- }
61
- return locations;
62
- }
63
- function compileCliTree(tree) {
64
- const issues = [...validateEntryTreeStructure(tree)];
65
- const nodeRecords = new Map();
66
- const nodeQueue = [{ node: tree.root, parentPath: [], root: true }];
67
- for (const current of nodeQueue) {
68
- const segment = cliSegmentFor(current.node, current.parentPath, current.root, issues);
69
- const path = current.root
70
- ? [current.node.name]
71
- : [...current.parentPath, segment.token];
72
- nodeRecords.set(current.node, {
73
- path,
74
- segment,
75
- endpoint: cliEndpointFor(current.node, path, issues),
76
- });
77
- nodeQueue.push(...current.node.children.map((node) => ({
78
- node,
79
- parentPath: path,
80
- root: false,
81
- })));
82
- }
83
- const publicNodes = new Map();
84
- const pending = [];
85
- visitEntryTree(tree, {
86
- visitNode(node) {
87
- pending.push(node);
88
- },
89
- visitEndpoint() { },
90
- });
91
- for (let index = pending.length - 1; index >= 0; index -= 1) {
92
- const node = pending[index];
93
- if (!node) {
94
- continue;
95
- }
96
- const record = nodeRecords.get(node);
97
- if (!record) {
98
- continue;
99
- }
100
- const children = node.children.flatMap((child) => {
101
- const compiled = publicNodes.get(child);
102
- return compiled ? [compiled] : [];
103
- });
104
- if (record.segment.help.subcommandHint !== null && children.length === 0) {
105
- issues.push(`${formatTreePath(record.path)}: ${CLI_TREE_VALIDATION_ISSUE.subcommandHintWithoutChildren}`);
106
- }
107
- if (record.endpoint === null &&
108
- children.length === 0 &&
109
- node !== tree.root) {
110
- continue;
111
- }
112
- validateCliChildren(record.path, children, issues);
113
- if (record.endpoint !== null &&
114
- record.endpoint.positionals.length > 0 &&
115
- children.length > 0) {
116
- issues.push(`${formatTreePath(record.path)}: executable CLI node cannot have both positionals and children`);
117
- }
118
- const childByToken = new Map();
119
- for (const child of children) {
120
- childByToken.set(child.segment.token, child);
121
- }
122
- const compiledNode = {
123
- path: record.path,
124
- segment: record.segment,
125
- endpoint: record.endpoint,
126
- childByToken,
127
- help: createCliHelpSurface({
128
- node,
129
- segment: record.segment,
130
- endpoint: record.endpoint,
131
- childHelp: children.map((child) => child.help),
132
- root: node === tree.root,
133
- }),
134
- };
135
- publicNodes.set(node, compiledNode);
136
- }
13
+ examples: ownedArray([]),
14
+ });
15
+ export function collectCliCommands(source) {
16
+ const structuralIssues = validateEntryTreeStructure(source);
17
+ if (structuralIssues.length > 0) {
18
+ throw new EntryTreeDefinitionError(structuralIssues);
19
+ }
20
+ const issues = [];
21
+ const root = isEntryTreeDefinition(source)
22
+ ? compileTreeRoot(source.root, issues)
23
+ : compileVirtualRoot(source, issues);
137
24
  if (issues.length > 0) {
138
25
  throw new EntryTreeDefinitionError(issues);
139
26
  }
140
- const root = publicNodes.get(tree.root);
141
- if (!root) {
142
- throw new EntryTreeDefinitionError([
143
- "entry tree has no public CLI endpoint",
144
- ]);
145
- }
146
- return root;
147
- }
148
- function cliSegmentFor(node, path, root, issues) {
149
- const override = node.segments.find((row) => row.channel === CHANNEL.cli);
150
- let segment;
151
- if (!override) {
152
- segment = { token: node.name, help: EMPTY_CLI_GROUP_HELP };
153
- }
154
- else if (!isCliSegment(override.segment)) {
155
- issues.push(`${formatTreePath(path)}: invalid CLI segment override`);
156
- segment = { token: node.name, help: EMPTY_CLI_GROUP_HELP };
157
- }
158
- else {
159
- segment = override.segment;
160
- }
161
- validateCliHelpExamples(segment.help.examples, formatTreePath(path), issues);
162
- if (!root) {
163
- validateCliCommandToken(segment.token, path, issues);
164
- }
165
- return segment;
27
+ return ownedRecord({ root });
28
+ }
29
+ function compileTreeRoot(root, issues) {
30
+ const segment = cliSegmentFor(root, [], issues);
31
+ return compileNode(root, segment, [], true, issues);
32
+ }
33
+ function compileVirtualRoot(node, issues) {
34
+ const segment = cliSegmentFor(node, [node.name], issues);
35
+ const child = compileNode(node, segment, [segment.token], false, issues);
36
+ const help = emptyVirtualRootHelp(child.help);
37
+ return ownedRecord({
38
+ token: "",
39
+ path: ownedArray([]),
40
+ description: "",
41
+ documentation: ownedRecord({
42
+ remarks: ownedArray([]),
43
+ relatedDocs: ownedArray([]),
44
+ }),
45
+ help,
46
+ commands: packRegistrationCollection([]),
47
+ children: ownedArray([child]),
48
+ });
166
49
  }
167
- function validateCliCommandToken(token, path, issues) {
168
- if (token.length === 0) {
169
- issues.push(`${formatTreePath(path)}: CLI segment token must not be empty`);
170
- return;
171
- }
172
- if (classifyCliArgumentToken(token) !== CLI_ARGUMENT_TOKEN_KIND.value) {
173
- issues.push(`${formatTreePath(path)}: CLI segment token ${JSON.stringify(token)} is unreachable`);
174
- }
50
+ function compileNode(node, segment, path, root, issues) {
51
+ const token = segment.token;
52
+ validateCliToken(token, path, issues);
53
+ const compiledEndpoint = cliEndpointFor(node, path, issues);
54
+ const children = node.children.map((child) => {
55
+ const childSegment = cliSegmentFor(child, [...path, child.name], issues);
56
+ return compileNode(child, childSegment, [...path, childSegment.token], false, issues);
57
+ });
58
+ validateChildTokens(path, children, issues);
59
+ if (segment.help.subcommandHint !== null && children.length === 0) {
60
+ issues.push(`${formatTreePath(path)}: CLI subcommand hint requires children`);
61
+ }
62
+ const help = createCliHelpSurface({
63
+ node,
64
+ segment,
65
+ endpoint: compiledEndpoint,
66
+ childHelp: children.map((child) => child.help),
67
+ root,
68
+ });
69
+ const registrations = compiledEndpoint === null
70
+ ? []
71
+ : [createCliRegistration(compiledEndpoint, path)];
72
+ return ownedRecord({
73
+ token,
74
+ path: ownedArray(path),
75
+ description: node.description,
76
+ documentation: node.documentation,
77
+ help,
78
+ commands: packRegistrationCollection(registrations),
79
+ children: ownedArray(children),
80
+ });
175
81
  }
176
82
  function cliEndpointFor(node, path, issues) {
177
- const endpoints = node.endpoints.flatMap((endpoint) => findChannelBinding(endpoint, CHANNEL.cli) ? [endpoint] : []);
83
+ const endpoints = [
84
+ ...new Set(node.endpoints.filter((endpoint) => endpoint.channels.some((binding) => binding.channel === CHANNEL.cli))),
85
+ ];
86
+ const location = formatTreePath(path);
178
87
  if (endpoints.length > 1) {
179
- issues.push(`${formatTreePath(path)}: more than one public CLI endpoint`);
88
+ issues.push(`${location}: different CLI endpoints occupy the same path`);
180
89
  }
181
90
  const endpoint = endpoints[0];
182
- if (!endpoint) {
91
+ if (endpoint === undefined) {
183
92
  return null;
184
93
  }
185
94
  const channelBinding = findChannelBinding(endpoint, CHANNEL.cli);
186
- if (!channelBinding || !isCliEntryBinding(channelBinding.binding)) {
187
- issues.push(`${formatTreePath(path)}: invalid CLI endpoint binding`);
95
+ if (channelBinding === null || !isCliChannelBinding(channelBinding)) {
96
+ issues.push(`${location}: invalid CLI endpoint binding`);
188
97
  return null;
189
98
  }
190
- const location = `${formatTreePath(path)}#${endpoint.entry.name}`;
191
- const compiled = compileCliBinding(endpoint, channelBinding.binding, location, issues);
99
+ const compiled = compileCliBinding(endpoint, channelBinding.binding, `${location}#${endpoint.entry.name}`, issues);
192
100
  validateCliCommandHelp(compiled, location, issues);
193
101
  return compiled;
194
102
  }
195
- function validateCliChildren(path, children, issues) {
196
- const tokens = new Map();
197
- for (const child of children) {
198
- const token = child.segment.token;
199
- const owner = tokens.get(token);
200
- if (owner !== undefined) {
201
- issues.push(`${formatTreePath(path)}: CLI sibling token collision ${JSON.stringify(token)} between ${owner} and ${child.help.name}`);
202
- }
203
- else {
204
- tokens.set(token, child.help.name);
205
- }
206
- }
207
- }
208
- function resolveCli(root, argv) {
209
- let node = root;
210
- let index = 0;
211
- while (index < argv.length) {
212
- const token = argv[index];
213
- if (token === undefined) {
214
- break;
215
- }
216
- if (classifyCliArgumentToken(token) !== CLI_ARGUMENT_TOKEN_KIND.value) {
217
- break;
218
- }
219
- const child = node.childByToken.get(token);
220
- if (!child) {
221
- break;
103
+ function createCliRegistration(compiled, path) {
104
+ const input = compileInputPlan({
105
+ entry: compiled.endpoint.entry,
106
+ bindings: compiled.parameterBindings,
107
+ });
108
+ const rows = new Map(compiled.binding.parameters.map((row) => [row.parameterName, row]));
109
+ const parseOptions = (raw) => input.parseCoerced(raw, (parameter, value) => {
110
+ const row = rows.get(parameter.name);
111
+ if (row === undefined) {
112
+ throw new Error(`Missing compiled CLI binding for ${parameter.name}`);
222
113
  }
223
- node = child;
224
- index += 1;
225
- }
226
- const unmatchedToken = argv[index];
227
- if (node.endpoint !== null &&
228
- node.childByToken.size > 0 &&
229
- unmatchedToken !== undefined &&
230
- classifyCliArgumentToken(unmatchedToken) === CLI_ARGUMENT_TOKEN_KIND.value) {
231
- return {
232
- kind: CLI_ROUTER_RESULT_KIND.unknownCommand,
233
- path: node.path,
234
- token: unmatchedToken,
235
- };
236
- }
237
- if (node.endpoint === null) {
238
- return unmatchedToken === undefined
239
- ? { kind: CLI_ROUTER_RESULT_KIND.incompleteCommand, path: node.path }
240
- : {
241
- kind: CLI_ROUTER_RESULT_KIND.unknownCommand,
242
- path: node.path,
243
- token: unmatchedToken,
244
- };
245
- }
246
- const compiledEndpoint = node.endpoint;
247
- const rawResult = parseCliArguments(compiledEndpoint, argv.slice(index));
248
- switch (rawResult.kind) {
249
- case CLI_ROUTER_RESULT_KIND.invalidInput:
250
- case CLI_ROUTER_RESULT_KIND.invalidParameter:
251
- return { ...rawResult, path: node.path };
252
- case CLI_PARSE_RESULT_KIND.raw:
253
- break;
254
- }
255
- const endpoint = compiledEndpoint.endpoint;
256
- const decoded = decodeEntryInput({
257
- entry: endpoint.entry,
258
- parameterNames: endpoint.entry.parameters.map((parameter) => parameter.name),
259
- raw: rawResult.raw,
260
- coerce: (parameter, rawValue) => coerceCliArgvValue(parameter.codec.shape, rawValue, cliCoercionFor(compiledEndpoint, parameter.name)),
114
+ return coerceCliValue(parameter, row, value);
115
+ });
116
+ const channelBinding = findChannelBinding(compiled.endpoint, CHANNEL.cli);
117
+ if (channelBinding === null) {
118
+ throw new Error("Compiled CLI endpoint lost its binding");
119
+ }
120
+ return ownedRecord({
121
+ endpoint: compiled.endpoint,
122
+ entry: compiled.endpoint.entry,
123
+ binding: channelBinding,
124
+ path: ownedArray(path),
125
+ parameters: projectCliParameters(compiled),
126
+ input: ownedRecord({
127
+ schema: input.schema,
128
+ parse: input.parse,
129
+ project: input.project,
130
+ parseOptions,
131
+ }),
132
+ handler: compiled.endpoint.handler,
261
133
  });
262
- switch (decoded.kind) {
263
- case ENTRY_DECODE_RESULT_KIND.resolved:
264
- return {
265
- kind: CLI_ROUTER_RESULT_KIND.resolved,
266
- path: node.path,
267
- endpoint,
268
- projection: decoded.projection,
269
- };
270
- case ENTRY_DECODE_RESULT_KIND.invalidInput:
271
- return {
272
- kind: CLI_ROUTER_RESULT_KIND.invalidInput,
273
- path: node.path,
274
- issues: decoded.issues,
275
- };
276
- case ENTRY_DECODE_RESULT_KIND.invalidParameter:
277
- return {
278
- kind: CLI_ROUTER_RESULT_KIND.invalidParameter,
279
- path: node.path,
280
- parameterName: decoded.parameterName,
281
- issues: decoded.issues,
282
- };
283
- case ENTRY_DECODE_RESULT_KIND.internalError:
284
- return {
285
- kind: CLI_ROUTER_RESULT_KIND.internalError,
286
- path: node.path,
287
- error: decoded.error,
288
- };
289
- }
290
134
  }
291
- function cliCoercionFor(endpoint, parameterName) {
292
- return ([...endpoint.options, ...endpoint.positionals].find((binding) => binding.parameter.name === parameterName)?.coercion ?? null);
135
+ function coerceCliValue(parameter, row, raw) {
136
+ const expanded = row.kind === CLI_ARG_KIND.option && row.repeatable && row.coercion === null
137
+ ? expandCommaSeparated(raw)
138
+ : raw;
139
+ return coerceCliArgvValue(parameter.name, parameter.codec.shape, expanded, row.coercion);
293
140
  }
294
- function parseCliArguments(endpoint, argv) {
295
- const raw = new Map();
296
- const positionalValues = [];
297
- let positionalOnly = false;
298
- for (let index = 0; index < argv.length; index += 1) {
299
- const token = argv[index];
300
- if (token === undefined) {
301
- continue;
302
- }
303
- if (!positionalOnly) {
304
- switch (classifyCliArgumentToken(token)) {
305
- case CLI_ARGUMENT_TOKEN_KIND.positionalSeparator:
306
- positionalOnly = true;
307
- continue;
308
- case CLI_ARGUMENT_TOKEN_KIND.option: {
309
- const parsed = parseCliOption({ endpoint, argv, index, raw });
310
- switch (parsed.kind) {
311
- case CLI_ROUTER_RESULT_KIND.invalidInput:
312
- case CLI_ROUTER_RESULT_KIND.invalidParameter:
313
- return parsed;
314
- case CLI_PARSE_RESULT_KIND.parsed:
315
- if (parsed.consumedNext) {
316
- index += 1;
317
- }
318
- }
319
- continue;
320
- }
321
- case CLI_ARGUMENT_TOKEN_KIND.value:
322
- break;
323
- }
324
- }
325
- positionalValues.push(token);
326
- }
327
- return bindCliPositionals(endpoint, positionalValues, raw);
141
+ function expandCommaSeparated(raw) {
142
+ const values = Array.isArray(raw) ? raw : [raw];
143
+ return values.flatMap((value) => typeof value === "string" ? value.split(",") : [value]);
328
144
  }
329
- function parseCliOption(input) {
330
- const token = input.argv[input.index] ?? "";
331
- const [flag, inlineValue] = splitOptionToken(token);
332
- const action = input.endpoint.optionByToken.get(flag);
333
- if (!action) {
334
- return invalidCliInput(`Unknown option ${flag}`);
335
- }
336
- if (action.kind === CLI_OPTION_ACTION_KIND.setBoolean) {
337
- return setCliBooleanValue(input.raw, action, flag, inlineValue);
145
+ function cliSegmentFor(node, path, issues) {
146
+ const override = node.segments.find((row) => row.channel === CHANNEL.cli);
147
+ if (override === undefined) {
148
+ return ownedRecord({ token: node.name, help: EMPTY_CLI_GROUP_HELP });
338
149
  }
339
- const option = action.option;
340
- const value = inlineValue ?? input.argv[input.index + 1];
341
- if (value === undefined) {
342
- return invalidCliParameter(option.parameter.name, `${flag} requires a value`);
150
+ if (!isCliSegment(override.segment)) {
151
+ issues.push(`${formatTreePath(path)}: invalid CLI segment override`);
152
+ return ownedRecord({ token: node.name, help: EMPTY_CLI_GROUP_HELP });
343
153
  }
344
- storeOptionValue(input.raw, option, value);
345
- return {
346
- kind: CLI_PARSE_RESULT_KIND.parsed,
347
- consumedNext: inlineValue === undefined,
348
- };
154
+ validateCliHelpExamples(override.segment.help.examples, formatTreePath(path), issues);
155
+ return override.segment;
349
156
  }
350
- function setCliBooleanValue(raw, action, flag, inlineValue) {
351
- if (inlineValue !== undefined) {
352
- return invalidCliParameter(action.parameterName, `${flag} does not take a value`);
157
+ function validateCliToken(token, path, issues) {
158
+ if (token.length === 0 || /\s/u.test(token)) {
159
+ issues.push(`${formatTreePath(path)}: CLI token must be non-empty and contain no whitespace`);
353
160
  }
354
- raw.set(action.parameterName, action.value);
355
- return { kind: CLI_PARSE_RESULT_KIND.parsed, consumedNext: false };
356
161
  }
357
- function bindCliPositionals(endpoint, positionalValues, raw) {
358
- let positionalIndex = 0;
359
- for (const positional of endpoint.positionals) {
360
- if (positional.kind === CLI_POSITIONAL_PLAN_KIND.rest) {
361
- const values = positionalValues.slice(positionalIndex);
362
- if (values.length > 0) {
363
- raw.set(positional.parameter.name, values);
364
- }
365
- positionalIndex = positionalValues.length;
162
+ function validateChildTokens(path, children, issues) {
163
+ const owners = new Map();
164
+ for (const child of children) {
165
+ const owner = owners.get(child.token);
166
+ if (owner !== undefined) {
167
+ issues.push(`${formatTreePath(path)}: CLI child token ${JSON.stringify(child.token)} collides between ${owner} and ${child.description}`);
366
168
  }
367
169
  else {
368
- const value = positionalValues[positionalIndex];
369
- if (value !== undefined) {
370
- raw.set(positional.parameter.name, value);
371
- positionalIndex += 1;
372
- }
373
- }
374
- }
375
- if (positionalIndex < positionalValues.length) {
376
- return invalidCliInput(`Unexpected argument ${positionalValues[positionalIndex]}`);
377
- }
378
- return { kind: CLI_PARSE_RESULT_KIND.raw, raw: Object.fromEntries(raw) };
379
- }
380
- function storeOptionValue(raw, option, value) {
381
- switch (option.valueMode) {
382
- case CLI_OPTION_VALUE_MODE.replace:
383
- raw.set(option.parameter.name, value);
384
- return;
385
- case CLI_OPTION_VALUE_MODE.repeat:
386
- appendOptionValues(raw, option.parameter.name, [value]);
387
- return;
388
- case CLI_OPTION_VALUE_MODE.commaOrRepeat:
389
- appendOptionValues(raw, option.parameter.name, value.split(CLI_ARGUMENT_SYNTAX.repeatedValueSeparator));
390
- return;
391
- }
392
- }
393
- function appendOptionValues(raw, parameterName, values) {
394
- const current = raw.get(parameterName);
395
- raw.set(parameterName, Array.isArray(current) ? [...current, ...values] : values);
396
- }
397
- function splitOptionToken(token) {
398
- const equalsIndex = token.indexOf(CLI_ARGUMENT_SYNTAX.inlineValueSeparator);
399
- return equalsIndex < 0
400
- ? [token, undefined]
401
- : [token.slice(0, equalsIndex), token.slice(equalsIndex + 1)];
170
+ owners.set(child.token, child.description);
171
+ }
172
+ }
173
+ }
174
+ function emptyVirtualRootHelp(child) {
175
+ return ownedRecord({
176
+ name: "",
177
+ token: null,
178
+ description: "",
179
+ optionGroups: ownedArray([]),
180
+ positionals: ownedArray([]),
181
+ children: ownedArray([child]),
182
+ remarks: ownedArray([]),
183
+ relatedDocs: ownedArray([]),
184
+ usages: ownedArray(["<command>"]),
185
+ examples: ownedArray([]),
186
+ subcommandHint: null,
187
+ });
402
188
  }
403
- function classifyCliArgumentToken(token) {
404
- if (token === CLI_ARGUMENT_SYNTAX.positionalSeparator) {
405
- return CLI_ARGUMENT_TOKEN_KIND.positionalSeparator;
406
- }
407
- return token.startsWith(CLI_ARGUMENT_SYNTAX.optionPrefix)
408
- ? CLI_ARGUMENT_TOKEN_KIND.option
409
- : CLI_ARGUMENT_TOKEN_KIND.value;
189
+ function isCliEntryBinding(value) {
190
+ return isChannelPayload(PAYLOAD_KIND.cliBinding, value);
410
191
  }
411
- function invalidCliInput(issue) {
412
- return {
413
- kind: CLI_ROUTER_RESULT_KIND.invalidInput,
414
- issues: [issue],
415
- };
192
+ function isCliChannelBinding(binding) {
193
+ return binding.channel === CHANNEL.cli && isCliEntryBinding(binding.binding);
416
194
  }
417
- function invalidCliParameter(parameterName, issue) {
418
- return {
419
- kind: CLI_ROUTER_RESULT_KIND.invalidParameter,
420
- parameterName,
421
- issues: [issue],
422
- };
195
+ function isCliSegment(value) {
196
+ return isChannelPayload(PAYLOAD_KIND.cliSegment, value);
423
197
  }
package/dist/cli.d.ts CHANGED
@@ -1,8 +1,4 @@
1
- export * from "./cli-application.js";
2
- export * from "./cli-executor.js";
3
1
  export * from "./cli-model.js";
4
- export * from "./cli-presentation.js";
5
- export * from "./cli-result-envelope.js";
6
- export { createCliRouter } from "./cli-tree.js";
2
+ export { collectCliCommands } from "./cli-tree.js";
7
3
  export { CLI_BOOLEAN_MODE, CLI_HELP_USAGE_KIND, CLI_OPTION_KIND, CLI_OPTION_VALUE_MODE, } from "./cli-values.js";
8
4
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
package/dist/cli.js CHANGED
@@ -1,7 +1,3 @@
1
- export * from "./cli-application.js";
2
- export * from "./cli-executor.js";
3
1
  export * from "./cli-model.js";
4
- export * from "./cli-presentation.js";
5
- export * from "./cli-result-envelope.js";
6
- export { createCliRouter } from "./cli-tree.js";
2
+ export { collectCliCommands } from "./cli-tree.js";
7
3
  export { CLI_BOOLEAN_MODE, CLI_HELP_USAGE_KIND, CLI_OPTION_KIND, CLI_OPTION_VALUE_MODE, } from "./cli-values.js";
package/dist/codecs.d.ts CHANGED
@@ -1,25 +1,27 @@
1
+ import type { JsonValue } from "@alpacakit/core";
1
2
  import { z } from "zod";
2
- import type { ValueCodec, ValueForShape, ValueShape } from "./model.js";
3
- declare function defineValueCodec<const Shape extends ValueShape>(input: {
4
- readonly schema: z.ZodType<ValueForShape<Shape>>;
5
- readonly shape: Shape;
6
- }): ValueCodec<ValueForShape<Shape>>;
7
- declare function stringCodec(): ValueCodec<string>;
8
- declare function booleanCodec(): ValueCodec<boolean>;
3
+ import type { ValueCodec, ValueShapeFor } from "./model.js";
4
+ type JsonInput<Raw> = Raw extends JsonValue ? Raw : JsonValue;
5
+ declare function defineValueCodec<Raw, Value extends JsonValue>(input: {
6
+ readonly schema: z.ZodType<Value, Raw>;
7
+ readonly shape: ValueShapeFor<JsonInput<Raw>>;
8
+ }): ValueCodec<Raw, Value>;
9
+ declare function stringCodec(): ValueCodec<string, string>;
10
+ declare function booleanCodec(): ValueCodec<boolean, boolean>;
9
11
  declare function positiveIntegerCodec(input?: {
10
12
  readonly max: number | null;
11
- }): ValueCodec<number>;
12
- declare function stringEnumCodec<const Values extends readonly [string, ...string[]]>(values: Values): ValueCodec<Values[number]>;
13
- declare function stringEnumListCodec<const Values extends readonly [string, ...string[]]>(values: Values): ValueCodec<readonly Values[number][]>;
14
- declare function stringListCodec(): ValueCodec<readonly string[]>;
13
+ }): ValueCodec<number, number>;
14
+ declare function stringEnumCodec<const Values extends readonly [string, ...string[]]>(values: Values): ValueCodec<Values[number], Values[number]>;
15
+ declare function stringEnumListCodec<const Values extends readonly [string, ...string[]]>(values: Values): ValueCodec<readonly Values[number][], readonly Values[number][]>;
16
+ declare function stringListCodec(): ValueCodec<readonly string[], readonly string[]>;
15
17
  export declare const codecs: {
16
- define: typeof defineValueCodec;
17
- string: typeof stringCodec;
18
- stringList: typeof stringListCodec;
19
- boolean: typeof booleanCodec;
20
- positiveInteger: typeof positiveIntegerCodec;
21
- stringEnum: typeof stringEnumCodec;
22
- stringEnumList: typeof stringEnumListCodec;
18
+ readonly define: typeof defineValueCodec;
19
+ readonly string: typeof stringCodec;
20
+ readonly stringList: typeof stringListCodec;
21
+ readonly boolean: typeof booleanCodec;
22
+ readonly positiveInteger: typeof positiveIntegerCodec;
23
+ readonly stringEnum: typeof stringEnumCodec;
24
+ readonly stringEnumList: typeof stringEnumListCodec;
23
25
  };
24
26
  export {};
25
27
  //# sourceMappingURL=codecs.d.ts.map