@ykdz/template 0.0.38 → 0.0.40

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 (220) hide show
  1. package/dist/application.d.ts +1 -52
  2. package/dist/application.js +0 -81
  3. package/dist/cli.js +14 -28
  4. package/dist/input-schemas.d.ts +30 -0
  5. package/dist/input-schemas.js +75 -0
  6. package/dist/main.d.ts +3 -14
  7. package/dist/main.js +496 -378
  8. package/dist/result-schemas.d.ts +222 -0
  9. package/dist/result-schemas.js +74 -0
  10. package/dist/standard-schema.d.ts +3 -0
  11. package/dist/standard-schema.js +29 -0
  12. package/node_modules/@standard-schema/spec/LICENSE +21 -0
  13. package/node_modules/@standard-schema/spec/README.md +198 -0
  14. package/node_modules/@standard-schema/spec/dist/index.cjs +18 -0
  15. package/node_modules/@standard-schema/spec/dist/index.d.cts +119 -0
  16. package/node_modules/@standard-schema/spec/dist/index.d.ts +119 -0
  17. package/node_modules/@standard-schema/spec/dist/index.js +0 -0
  18. package/node_modules/@standard-schema/spec/package.json +52 -0
  19. package/node_modules/@types/node/LICENSE +21 -0
  20. package/node_modules/@types/node/README.md +15 -0
  21. package/node_modules/@types/node/assert/strict.d.ts +111 -0
  22. package/node_modules/@types/node/assert.d.ts +1115 -0
  23. package/node_modules/@types/node/async_hooks.d.ts +619 -0
  24. package/node_modules/@types/node/buffer.buffer.d.ts +477 -0
  25. package/node_modules/@types/node/buffer.d.ts +1941 -0
  26. package/node_modules/@types/node/child_process.d.ts +1480 -0
  27. package/node_modules/@types/node/cluster.d.ts +578 -0
  28. package/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  29. package/node_modules/@types/node/console.d.ts +453 -0
  30. package/node_modules/@types/node/constants.d.ts +21 -0
  31. package/node_modules/@types/node/crypto.d.ts +5484 -0
  32. package/node_modules/@types/node/dgram.d.ts +715 -0
  33. package/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  34. package/node_modules/@types/node/dns/promises.d.ts +503 -0
  35. package/node_modules/@types/node/dns.d.ts +923 -0
  36. package/node_modules/@types/node/domain.d.ts +166 -0
  37. package/node_modules/@types/node/events.d.ts +967 -0
  38. package/node_modules/@types/node/fs/promises.d.ts +1354 -0
  39. package/node_modules/@types/node/fs.d.ts +4777 -0
  40. package/node_modules/@types/node/globals.d.ts +170 -0
  41. package/node_modules/@types/node/globals.typedarray.d.ts +41 -0
  42. package/node_modules/@types/node/http.d.ts +2246 -0
  43. package/node_modules/@types/node/http2.d.ts +2882 -0
  44. package/node_modules/@types/node/https.d.ts +585 -0
  45. package/node_modules/@types/node/index.d.ts +101 -0
  46. package/node_modules/@types/node/inspector.d.ts +323 -0
  47. package/node_modules/@types/node/inspector.generated.d.ts +4406 -0
  48. package/node_modules/@types/node/module.d.ts +844 -0
  49. package/node_modules/@types/node/net.d.ts +1197 -0
  50. package/node_modules/@types/node/os.d.ts +507 -0
  51. package/node_modules/@types/node/package.json +140 -0
  52. package/node_modules/@types/node/path.d.ts +200 -0
  53. package/node_modules/@types/node/perf_hooks.d.ts +1054 -0
  54. package/node_modules/@types/node/process.d.ts +2101 -0
  55. package/node_modules/@types/node/punycode.d.ts +117 -0
  56. package/node_modules/@types/node/querystring.d.ts +152 -0
  57. package/node_modules/@types/node/readline/promises.d.ts +161 -0
  58. package/node_modules/@types/node/readline.d.ts +600 -0
  59. package/node_modules/@types/node/repl.d.ts +438 -0
  60. package/node_modules/@types/node/sea.d.ts +162 -0
  61. package/node_modules/@types/node/sqlite.d.ts +1182 -0
  62. package/node_modules/@types/node/stream/consumers.d.ts +45 -0
  63. package/node_modules/@types/node/stream/promises.d.ts +90 -0
  64. package/node_modules/@types/node/stream/web.d.ts +596 -0
  65. package/node_modules/@types/node/stream.d.ts +1707 -0
  66. package/node_modules/@types/node/string_decoder.d.ts +67 -0
  67. package/node_modules/@types/node/test.d.ts +2656 -0
  68. package/node_modules/@types/node/timers/promises.d.ts +108 -0
  69. package/node_modules/@types/node/timers.d.ts +285 -0
  70. package/node_modules/@types/node/tls.d.ts +1314 -0
  71. package/node_modules/@types/node/trace_events.d.ts +197 -0
  72. package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +473 -0
  73. package/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  74. package/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  75. package/node_modules/@types/node/ts5.6/index.d.ts +103 -0
  76. package/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  77. package/node_modules/@types/node/ts5.7/index.d.ts +103 -0
  78. package/node_modules/@types/node/tty.d.ts +209 -0
  79. package/node_modules/@types/node/url.d.ts +1103 -0
  80. package/node_modules/@types/node/util.d.ts +2376 -0
  81. package/node_modules/@types/node/v8.d.ts +1010 -0
  82. package/node_modules/@types/node/vm.d.ts +1160 -0
  83. package/node_modules/@types/node/wasi.d.ts +202 -0
  84. package/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
  85. package/node_modules/@types/node/web-globals/crypto.d.ts +32 -0
  86. package/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  87. package/node_modules/@types/node/web-globals/events.d.ts +97 -0
  88. package/node_modules/@types/node/web-globals/fetch.d.ts +62 -0
  89. package/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  90. package/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  91. package/node_modules/@types/node/web-globals/streams.d.ts +22 -0
  92. package/node_modules/@types/node/worker_threads.d.ts +894 -0
  93. package/node_modules/@types/node/zlib.d.ts +768 -0
  94. package/node_modules/@valibot/to-json-schema/LICENSE.md +9 -0
  95. package/node_modules/@valibot/to-json-schema/README.md +475 -0
  96. package/node_modules/@valibot/to-json-schema/dist/index.cjs +800 -0
  97. package/node_modules/@valibot/to-json-schema/dist/index.d.cts +991 -0
  98. package/node_modules/@valibot/to-json-schema/dist/index.d.mts +991 -0
  99. package/node_modules/@valibot/to-json-schema/dist/index.min.cjs +1 -0
  100. package/node_modules/@valibot/to-json-schema/dist/index.min.mjs +1 -0
  101. package/node_modules/@valibot/to-json-schema/dist/index.mjs +795 -0
  102. package/node_modules/@valibot/to-json-schema/package.json +75 -0
  103. package/node_modules/@ykdz/cli-contract/LICENSE +21 -0
  104. package/node_modules/@ykdz/cli-contract/dist/atomic-variant-compiler.d.ts +17 -0
  105. package/node_modules/@ykdz/cli-contract/dist/atomic-variant-compiler.js +55 -0
  106. package/node_modules/@ykdz/cli-contract/dist/cli-contract.d.ts +1164 -0
  107. package/node_modules/@ykdz/cli-contract/dist/cli-contract.js +1408 -0
  108. package/node_modules/@ykdz/cli-contract/dist/cli-execution.d.ts +54 -0
  109. package/node_modules/@ykdz/cli-contract/dist/cli-execution.js +708 -0
  110. package/node_modules/@ykdz/cli-contract/dist/cli-invocation.d.ts +174 -0
  111. package/node_modules/@ykdz/cli-contract/dist/cli-invocation.js +524 -0
  112. package/node_modules/@ykdz/cli-contract/dist/cli-write-error.d.ts +10 -0
  113. package/node_modules/@ykdz/cli-contract/dist/cli-write-error.js +14 -0
  114. package/node_modules/@ykdz/cli-contract/dist/contract-definition-error.d.ts +260 -0
  115. package/node_modules/@ykdz/cli-contract/dist/contract-definition-error.js +130 -0
  116. package/node_modules/@ykdz/cli-contract/dist/contract-execution-error.d.ts +63 -0
  117. package/node_modules/@ykdz/cli-contract/dist/contract-execution-error.js +17 -0
  118. package/node_modules/@ykdz/cli-contract/dist/contract-schema-compiler.d.ts +6 -0
  119. package/node_modules/@ykdz/cli-contract/dist/contract-schema-compiler.js +532 -0
  120. package/node_modules/@ykdz/cli-contract/dist/contract-schema.d.ts +14 -0
  121. package/node_modules/@ykdz/cli-contract/dist/contract-schema.js +1 -0
  122. package/node_modules/@ykdz/cli-contract/dist/description.d.ts +2 -0
  123. package/node_modules/@ykdz/cli-contract/dist/description.js +13 -0
  124. package/node_modules/@ykdz/cli-contract/dist/index.d.ts +8 -0
  125. package/node_modules/@ykdz/cli-contract/dist/index.js +7 -0
  126. package/node_modules/@ykdz/cli-contract/dist/json-value.d.ts +4 -0
  127. package/node_modules/@ykdz/cli-contract/dist/json-value.js +64 -0
  128. package/node_modules/@ykdz/cli-contract/dist/node.d.ts +21 -0
  129. package/node_modules/@ykdz/cli-contract/dist/node.js +109 -0
  130. package/node_modules/@ykdz/cli-contract/dist/outcome-fact.d.ts +162 -0
  131. package/node_modules/@ykdz/cli-contract/dist/outcome-fact.js +113 -0
  132. package/node_modules/@ykdz/cli-contract/dist/outcome-wire.d.ts +53 -0
  133. package/node_modules/@ykdz/cli-contract/dist/outcome-wire.js +189 -0
  134. package/node_modules/@ykdz/cli-contract/dist/stream-variant-compiler.d.ts +16 -0
  135. package/node_modules/@ykdz/cli-contract/dist/stream-variant-compiler.js +45 -0
  136. package/node_modules/@ykdz/cli-contract/package.json +69 -0
  137. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +20 -0
  138. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +5 -0
  139. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +19 -0
  140. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +1 -1
  141. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -1
  142. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +2 -1
  143. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +9 -5
  144. package/node_modules/@ykdz/template-builtin-presets/package.json +2 -1
  145. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/AGENTS.md.template +3 -3
  146. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +128 -59
  147. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +80 -8
  148. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +7 -3
  149. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +9 -19
  150. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +144 -60
  151. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/standard-schema.ts +37 -0
  152. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/e2e/journeys/lookup.journey.ts +71 -0
  153. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +118 -59
  154. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +2 -1
  155. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +15 -0
  156. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +1 -1
  157. package/node_modules/undici-types/LICENSE +21 -0
  158. package/node_modules/undici-types/README.md +6 -0
  159. package/node_modules/undici-types/agent.d.ts +32 -0
  160. package/node_modules/undici-types/api.d.ts +43 -0
  161. package/node_modules/undici-types/balanced-pool.d.ts +30 -0
  162. package/node_modules/undici-types/cache-interceptor.d.ts +179 -0
  163. package/node_modules/undici-types/cache.d.ts +36 -0
  164. package/node_modules/undici-types/client-stats.d.ts +15 -0
  165. package/node_modules/undici-types/client.d.ts +123 -0
  166. package/node_modules/undici-types/connector.d.ts +36 -0
  167. package/node_modules/undici-types/content-type.d.ts +21 -0
  168. package/node_modules/undici-types/cookies.d.ts +30 -0
  169. package/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  170. package/node_modules/undici-types/dispatcher.d.ts +279 -0
  171. package/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  172. package/node_modules/undici-types/errors.d.ts +177 -0
  173. package/node_modules/undici-types/eventsource.d.ts +66 -0
  174. package/node_modules/undici-types/fetch.d.ts +211 -0
  175. package/node_modules/undici-types/formdata.d.ts +108 -0
  176. package/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  177. package/node_modules/undici-types/global-origin.d.ts +7 -0
  178. package/node_modules/undici-types/h2c-client.d.ts +73 -0
  179. package/node_modules/undici-types/handlers.d.ts +15 -0
  180. package/node_modules/undici-types/header.d.ts +160 -0
  181. package/node_modules/undici-types/index.d.ts +91 -0
  182. package/node_modules/undici-types/interceptors.d.ts +80 -0
  183. package/node_modules/undici-types/mock-agent.d.ts +68 -0
  184. package/node_modules/undici-types/mock-call-history.d.ts +111 -0
  185. package/node_modules/undici-types/mock-client.d.ts +27 -0
  186. package/node_modules/undici-types/mock-errors.d.ts +12 -0
  187. package/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  188. package/node_modules/undici-types/mock-pool.d.ts +27 -0
  189. package/node_modules/undici-types/package.json +55 -0
  190. package/node_modules/undici-types/patch.d.ts +29 -0
  191. package/node_modules/undici-types/pool-stats.d.ts +19 -0
  192. package/node_modules/undici-types/pool.d.ts +41 -0
  193. package/node_modules/undici-types/proxy-agent.d.ts +29 -0
  194. package/node_modules/undici-types/readable.d.ts +68 -0
  195. package/node_modules/undici-types/retry-agent.d.ts +8 -0
  196. package/node_modules/undici-types/retry-handler.d.ts +125 -0
  197. package/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  198. package/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  199. package/node_modules/undici-types/socks5-proxy-agent.d.ts +25 -0
  200. package/node_modules/undici-types/util.d.ts +18 -0
  201. package/node_modules/undici-types/utility.d.ts +7 -0
  202. package/node_modules/undici-types/webidl.d.ts +347 -0
  203. package/node_modules/undici-types/websocket.d.ts +188 -0
  204. package/package.json +5 -3
  205. package/dist/command-result.d.ts +0 -9
  206. package/dist/command-result.js +0 -231
  207. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/e2e/journeys/greet.journey.ts +0 -46
  208. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/greet.test.ts +0 -15
  209. package/node_modules/commander/LICENSE +0 -22
  210. package/node_modules/commander/Readme.md +0 -1172
  211. package/node_modules/commander/index.js +0 -21
  212. package/node_modules/commander/lib/argument.js +0 -147
  213. package/node_modules/commander/lib/command.js +0 -2790
  214. package/node_modules/commander/lib/error.js +0 -36
  215. package/node_modules/commander/lib/help.js +0 -731
  216. package/node_modules/commander/lib/option.js +0 -377
  217. package/node_modules/commander/lib/suggestSimilar.js +0 -99
  218. package/node_modules/commander/package-support.json +0 -19
  219. package/node_modules/commander/package.json +0 -64
  220. package/node_modules/commander/typings/index.d.ts +0 -1113
@@ -584,15 +584,19 @@ function addManifestBlockers(options: {
584
584
  "imports",
585
585
  "provenance",
586
586
  ].filter((field) => Object.hasOwn(manifest, field));
587
+ const runtimeDependencies = manifest.dependencies;
587
588
  const manifestContractValid =
588
589
  manifest.type === "module" &&
589
590
  exactStringSet(manifest.files, publicationFiles) &&
590
591
  expectedPublicNodeRangeFromRoot !== undefined &&
591
592
  isObject(manifest.engines) &&
592
593
  manifest.engines.node === expectedPublicNodeRangeFromRoot &&
593
- validRecordOfStrings(manifest.dependencies) &&
594
- typeof manifest.dependencies.commander === "string" &&
595
- manifest.dependencies.commander.trim().length > 0 &&
594
+ validRecordOfStrings(runtimeDependencies) &&
595
+ ["@ykdz/cli-contract", "@valibot/to-json-schema", "valibot"].every(
596
+ (name) =>
597
+ typeof runtimeDependencies[name] === "string" &&
598
+ runtimeDependencies[name].trim().length > 0,
599
+ ) &&
596
600
  isObject(scripts) &&
597
601
  typeof scripts.build === "string" &&
598
602
  scripts.build.length > 0 &&
@@ -1,27 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
 
4
+ import { nodeCliOutput } from "@ykdz/cli-contract/node";
5
+
4
6
  import { cliCommandIdentity } from "./cli-command-identity.ts";
5
- import { runCli, type CliRuntime } from "./main.ts";
7
+ import { runCli } from "./main.ts";
6
8
 
7
9
  const require = createRequire(import.meta.url);
8
- const packageManifest = require("../package.json") as unknown;
9
-
10
- const runtime: CliRuntime = {
10
+ process.exitCode = await runCli({
11
11
  argv: process.argv,
12
- streams: {
13
- stdin: process.stdin,
14
- stdout: process.stdout,
15
- stderr: process.stderr,
12
+ write: nodeCliOutput({ stdout: process.stdout, stderr: process.stderr }),
13
+ // 身份读取和校验也使用 runCli 的意外故障出口。
14
+ get identity() {
15
+ return cliCommandIdentity(require("../package.json"));
16
16
  },
17
- cwd: process.cwd(),
18
- env: process.env,
19
- tty: {
20
- stdin: Boolean(process.stdin.isTTY),
21
- stdout: Boolean(process.stdout.isTTY),
22
- stderr: Boolean(process.stderr.isTTY),
23
- },
24
- identity: cliCommandIdentity(packageManifest),
25
- };
26
-
27
- process.exitCode = await runCli(runtime);
17
+ });
@@ -1,78 +1,162 @@
1
- import { Command, CommanderError } from "commander";
1
+ import {
2
+ CliWriteError,
3
+ defineCli,
4
+ executeCli,
5
+ helpCapability,
6
+ outputCapability,
7
+ parseCliInvocation,
8
+ text,
9
+ versionCapability,
10
+ type WriteCliOutput,
11
+ } from "@ykdz/cli-contract";
12
+ import { isNodeBrokenPipe } from "@ykdz/cli-contract/node";
13
+ import * as v from "valibot";
2
14
 
3
15
  import type { CliCommandIdentity } from "./cli-command-identity.ts";
4
-
5
- export type Greeting = {
6
- readonly message: string;
7
- };
16
+ import { standardSchema } from "./standard-schema.ts";
8
17
 
9
18
  export type CliRuntime = {
10
19
  readonly argv: readonly string[];
11
- readonly streams: {
12
- readonly stdin: object;
13
- readonly stdout: { write(chunk: string): unknown };
14
- readonly stderr: { write(chunk: string): unknown };
15
- };
16
- readonly cwd: string;
17
- readonly env: Readonly<Record<string, string | undefined>>;
18
- readonly tty: {
19
- readonly stdin: boolean;
20
- readonly stdout: boolean;
21
- readonly stderr: boolean;
22
- };
20
+ readonly write: WriteCliOutput;
23
21
  readonly identity: CliCommandIdentity;
24
22
  };
25
23
 
26
- export function greet(name: string): Greeting {
27
- const normalizedName = name.trim();
28
- if (normalizedName.length === 0) {
29
- throw new Error("Name must not be empty");
30
- }
31
- return { message: `Hello, ${normalizedName}` };
32
- }
24
+ const records = [
25
+ { name: "ada", title: "Ada Lovelace" },
26
+ { name: "grace", title: "Grace Hopper" },
27
+ ];
33
28
 
34
- function errorMessage(error: unknown): string {
35
- return error instanceof Error ? error.message : String(error);
36
- }
37
-
38
- export function createCliCommand(runtime: CliRuntime): Command {
39
- const command = new Command()
40
- .name(runtime.identity.commandName)
41
- .description("A TypeScript command-line tool.")
42
- .version(runtime.identity.version)
43
- .configureOutput({
44
- writeOut: (text) => runtime.streams.stdout.write(text),
45
- writeErr: (text) => runtime.streams.stderr.write(text),
46
- })
47
- .showHelpAfterError()
48
- .exitOverride();
49
- const greetCommand = command
50
- .command("greet")
51
- .description("Greet a person.")
52
- .argument("<name>", "name to greet")
53
- .action((name: string) => {
54
- try {
55
- runtime.streams.stdout.write(`${greet(name).message}\n`);
56
- } catch (error) {
57
- greetCommand.error(`error: ${errorMessage(error)}`, {
58
- exitCode: 1,
59
- code: "cli.greet.invalid-name",
60
- });
61
- }
62
- });
63
- return command;
29
+ function createCliContract(identity: CliCommandIdentity) {
30
+ const define = defineCli<{ getManifest(): Record<string, unknown> }>();
31
+ return define({
32
+ root: "cli",
33
+ help: helpCapability({
34
+ shortAlias: "-h",
35
+ headings: {
36
+ usage: "用法",
37
+ commands: "命令",
38
+ arguments: "参数",
39
+ options: "选项",
40
+ },
41
+ wording: {
42
+ commandPlaceholder: "命令",
43
+ choices: "候选:{choices}",
44
+ default: "默认:{value}",
45
+ },
46
+ }),
47
+ version: versionCapability({
48
+ value: text.line(`${identity.commandName} ${identity.version}`),
49
+ shortAlias: "-V",
50
+ }),
51
+ output: outputCapability({ defaultFormat: "structured", text: true }),
52
+ usageFailureExitCode: 2,
53
+ commands: {
54
+ cli: {
55
+ kind: "rootGroup",
56
+ name: identity.commandName,
57
+ description: "查询示例记录。",
58
+ },
59
+ ...define.command("lookup")({
60
+ kind: "command",
61
+ parent: "cli",
62
+ name: "lookup",
63
+ description: "按名称查询内存记录。",
64
+ fields: {
65
+ name: { kind: "positional", description: "小写字母组成的记录名称" },
66
+ },
67
+ input: standardSchema(
68
+ v.object({
69
+ name: v.pipe(
70
+ v.string(),
71
+ v.regex(/^[a-z]+$/, "名称只能包含小写字母。"),
72
+ ),
73
+ }),
74
+ ),
75
+ success: {
76
+ kind: "data",
77
+ variants: {
78
+ found: {
79
+ description: "找到记录",
80
+ exitCode: 0,
81
+ schema: standardSchema(
82
+ v.object({ name: v.string(), title: v.string() }),
83
+ ),
84
+ text: ({ name, title }) =>
85
+ text.line(`找到记录:${title}(${name})`),
86
+ },
87
+ },
88
+ },
89
+ failures: {
90
+ notFound: {
91
+ description: "记录不存在",
92
+ exitCode: 1,
93
+ schema: standardSchema(v.object({ name: v.string() })),
94
+ text: ({ name }) => text.line(`未找到记录:${name}。`),
95
+ },
96
+ },
97
+ handler({ input, outcome }) {
98
+ const record = records.find(({ name }) => name === input.name);
99
+ return record === undefined
100
+ ? outcome.failure.notFound({ name: input.name })
101
+ : outcome.data.found(record);
102
+ },
103
+ }),
104
+ ...define.command("schema")({
105
+ kind: "command",
106
+ parent: "cli",
107
+ name: "schema",
108
+ description: "导出完整 CLI 契约及输入/输出 Schema。",
109
+ input: standardSchema(v.object({})),
110
+ fields: {},
111
+ success: {
112
+ kind: "data",
113
+ variants: {
114
+ exported: {
115
+ description: "完整 CLI 契约",
116
+ exitCode: 0,
117
+ schema: standardSchema(
118
+ v.object({ manifest: v.record(v.string(), v.unknown()) }),
119
+ ),
120
+ text: ({ manifest }) =>
121
+ text.lines([
122
+ "CLI 契约",
123
+ ...JSON.stringify(manifest, null, 2).split("\n"),
124
+ ]),
125
+ },
126
+ },
127
+ },
128
+ failures: {},
129
+ handler: ({ dependencies, outcome }) =>
130
+ outcome.data.exported({ manifest: dependencies.getManifest() }),
131
+ }),
132
+ },
133
+ });
64
134
  }
65
135
 
66
136
  export async function runCli(runtime: CliRuntime): Promise<number> {
67
137
  try {
68
- await createCliCommand(runtime).parseAsync([...runtime.argv], {
69
- from: "node",
138
+ const contract = createCliContract(runtime.identity);
139
+ const result = await executeCli(contract, {
140
+ invocation: parseCliInvocation(contract, runtime.argv.slice(2)),
141
+ dependencies: { getManifest: () => ({ ...contract.manifest }) },
142
+ write: runtime.write,
70
143
  });
71
- return 0;
144
+ return result.exitCode;
72
145
  } catch (error) {
73
- if (error instanceof CommanderError) {
74
- return error.exitCode;
146
+ if (
147
+ error instanceof CliWriteError &&
148
+ error.destination === "stdout" &&
149
+ isNodeBrokenPipe(error)
150
+ )
151
+ return 0;
152
+ try {
153
+ await runtime.write({
154
+ destination: "stderr",
155
+ chunk: "命令执行失败;请检查运行环境或报告此问题。\n",
156
+ });
157
+ } catch {
158
+ // 诊断通道故障不能改变原定退出类别。
75
159
  }
76
- throw error;
160
+ return 70;
77
161
  }
78
162
  }
@@ -0,0 +1,37 @@
1
+ import { toStandardJsonSchema } from "@valibot/to-json-schema";
2
+ import type * as v from "valibot";
3
+
4
+ /** 上游执行器要求失败结果不带 value;保留 Valibot 的验证及官方 JSON Schema 投影。 */
5
+ export function standardSchema<
6
+ T extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>,
7
+ >(schema: T, ignoreActions: string[] = []) {
8
+ const converted = toStandardJsonSchema(schema);
9
+ const adapted: typeof converted = {
10
+ "~standard": {
11
+ ...converted["~standard"],
12
+ jsonSchema: {
13
+ input: (options) =>
14
+ converted["~standard"].jsonSchema.input({
15
+ ...options,
16
+ libraryOptions: { ...options.libraryOptions, ignoreActions },
17
+ }),
18
+ output: (options) =>
19
+ converted["~standard"].jsonSchema.output({
20
+ ...options,
21
+ libraryOptions: { ...options.libraryOptions, ignoreActions },
22
+ }),
23
+ },
24
+ validate(value) {
25
+ const result = converted["~standard"].validate(value);
26
+ if (result instanceof Promise)
27
+ return result.then((resolved) =>
28
+ resolved.issues === undefined
29
+ ? resolved
30
+ : { issues: resolved.issues },
31
+ );
32
+ return result.issues === undefined ? result : { issues: result.issues };
33
+ },
34
+ },
35
+ };
36
+ return adapted;
37
+ }
@@ -0,0 +1,71 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { cliCommandIdentity } from "../../../src/cli-command-identity.ts";
6
+ import type { CliJourney } from "../journey.ts";
7
+
8
+ const journey: CliJourney = {
9
+ name: "lookup",
10
+ modes: ["source", "distribution", "packed"],
11
+ async setup() {},
12
+ commands() {
13
+ return [
14
+ { name: "查询记录", args: ["lookup", "ada"] },
15
+ { name: "文本查询", args: ["lookup", "ada", "--output-format", "text"] },
16
+ { name: "未找到记录", args: ["lookup", "missing"] },
17
+ {
18
+ name: "文本失败",
19
+ args: ["lookup", "missing", "--output-format", "text"],
20
+ },
21
+ { name: "非法输入", args: ["lookup", " ", "--output-format", "text"] },
22
+ { name: "完整契约", args: ["schema"] },
23
+ { name: "帮助", args: ["--help"] },
24
+ { name: "版本", args: ["--version"] },
25
+ ];
26
+ },
27
+ async assertions({ context, results }) {
28
+ const identity = cliCommandIdentity(
29
+ JSON.parse(
30
+ await readFile(path.join(context.packageRoot, "package.json"), "utf8"),
31
+ ),
32
+ );
33
+ assert.equal(results[0]?.exitCode, 0);
34
+ assert.equal(results[0]?.stderr, "");
35
+ assert.deepEqual(JSON.parse(results[0]?.stdout ?? ""), {
36
+ schemaVersion: "1",
37
+ command: "lookup",
38
+ kind: "data",
39
+ variant: "found",
40
+ data: { name: "ada", title: "Ada Lovelace" },
41
+ });
42
+ assert.equal(results[1]?.exitCode, 0);
43
+ assert.equal(results[1]?.stderr, "");
44
+ assert.match(results[1]?.stdout ?? "", /找到记录:Ada Lovelace/u);
45
+ for (const result of results.slice(2, 4)) {
46
+ assert.equal(result.exitCode, 1);
47
+ assert.equal(result.stdout, "");
48
+ }
49
+ assert.equal(JSON.parse(results[2]?.stderr ?? "").variant, "notFound");
50
+ assert.match(results[3]?.stderr ?? "", /未找到记录:missing/u);
51
+ assert.equal(results[4]?.exitCode, 2);
52
+ assert.equal(results[4]?.stdout, "");
53
+ assert.equal(JSON.parse(results[4]?.stderr ?? "").kind, "usageFailure");
54
+ for (const result of results.slice(5)) {
55
+ assert.equal(result.exitCode, 0);
56
+ assert.equal(result.stderr, "");
57
+ }
58
+ const schema = JSON.parse(results[5]?.stdout ?? "");
59
+ assert.equal(schema.command, "schema");
60
+ for (const fact of ["lookup", "schema", "found", "notFound"])
61
+ assert.ok(JSON.stringify(schema.data.manifest).includes(fact));
62
+ assert.match(results[6]?.stdout ?? "", /lookup/u);
63
+ assert.ok(results[6]?.stdout.includes(identity.commandName));
64
+ assert.equal(
65
+ results[7]?.stdout,
66
+ `${identity.commandName} ${identity.version}\n`,
67
+ );
68
+ },
69
+ };
70
+
71
+ export default journey;
@@ -5,79 +5,138 @@ import { describe, expect, it } from "vitest";
5
5
  import { cliCommandIdentity } from "../../src/cli-command-identity.ts";
6
6
  import { runCli, type CliRuntime } from "../../src/main.ts";
7
7
 
8
- const require = createRequire(import.meta.url);
9
- const packageManifest = require("../../package.json") as unknown;
10
- const identity = cliCommandIdentity(packageManifest);
8
+ const identity = cliCommandIdentity(
9
+ createRequire(import.meta.url)("../../package.json"),
10
+ );
11
11
 
12
- function testRuntime(args: readonly string[]): {
13
- readonly runtime: CliRuntime;
14
- readonly stdout: () => string;
15
- readonly stderr: () => string;
16
- } {
12
+ function testRuntime(args: readonly string[]) {
17
13
  let stdout = "";
18
14
  let stderr = "";
19
- return {
20
- runtime: {
21
- argv: ["node", identity.commandName, ...args],
22
- streams: {
23
- stdin: {},
24
- stdout: { write: (chunk) => (stdout += chunk) },
25
- stderr: { write: (chunk) => (stderr += chunk) },
26
- },
27
- cwd: "/workspace",
28
- env: { MODE: "test" },
29
- tty: { stdin: false, stdout: false, stderr: false },
30
- identity,
15
+ const runtime: CliRuntime = {
16
+ argv: ["node", identity.commandName, ...args],
17
+ identity,
18
+ write: ({ destination, chunk }) => {
19
+ if (destination === "stdout") stdout += chunk;
20
+ else stderr += chunk;
31
21
  },
32
- stdout: () => stdout,
33
- stderr: () => stderr,
34
22
  };
23
+ return { runtime, stdout: () => stdout, stderr: () => stderr };
35
24
  }
36
25
 
37
- describe("CLI command control", () => {
38
- it("parses greet and writes business output to the injected stream", async () => {
39
- const output = testRuntime(["greet", " Ada Lovelace "]);
40
-
41
- await expect(runCli(output.runtime)).resolves.toBe(0);
42
- expect(output.stdout()).toBe("Hello, Ada Lovelace\n");
26
+ describe("CLI 契约", () => {
27
+ it("查询成功默认返回结构化数据,也支持中文文本", async () => {
28
+ const output = testRuntime(["lookup", "ada"]);
29
+ expect(await runCli(output.runtime)).toBe(0);
30
+ expect(JSON.parse(output.stdout())).toEqual({
31
+ schemaVersion: "1",
32
+ command: "lookup",
33
+ kind: "data",
34
+ variant: "found",
35
+ data: { name: "ada", title: "Ada Lovelace" },
36
+ });
43
37
  expect(output.stderr()).toBe("");
38
+ const human = testRuntime(["lookup", "ada", "--output-format", "text"]);
39
+ expect(await runCli(human.runtime)).toBe(0);
40
+ expect(human.stdout()).toContain("找到记录");
41
+ expect(human.stdout()).toContain("Ada Lovelace");
44
42
  });
45
43
 
46
- it("renders the version derived from the package manifest", async () => {
47
- const output = testRuntime(["--version"]);
48
-
49
- await expect(runCli(output.runtime)).resolves.toBe(0);
50
- expect(output.stdout()).toBe(`${identity.version}\n`);
51
- expect(output.stderr()).toBe("");
52
- });
53
-
54
- it("renders Commander help without exiting the process", async () => {
55
- const output = testRuntime(["--help"]);
56
-
57
- await expect(runCli(output.runtime)).resolves.toBe(0);
58
- expect(output.stdout()).toContain(
59
- `Usage: ${identity.commandName} [options] [command]`,
60
- );
61
- expect(output.stdout()).toContain("greet <name>");
62
- expect(output.stderr()).toBe("");
44
+ it("未找到记录是已声明失败,非法名称是用法错误", async () => {
45
+ const missing = testRuntime(["lookup", "missing"]);
46
+ expect(await runCli(missing.runtime)).toBe(1);
47
+ expect(missing.stdout()).toBe("");
48
+ expect(JSON.parse(missing.stderr())).toMatchObject({
49
+ kind: "failure",
50
+ variant: "notFound",
51
+ data: { name: "missing" },
52
+ });
53
+ for (const args of [["lookup"], ["lookup", " "]]) {
54
+ const invalid = testRuntime([...args, "--output-format", "text"]);
55
+ expect(await runCli(invalid.runtime)).toBe(2);
56
+ expect(invalid.stdout()).toBe("");
57
+ expect(JSON.parse(invalid.stderr())).toMatchObject({
58
+ kind: "usageFailure",
59
+ });
60
+ }
63
61
  });
64
62
 
65
- it("reports missing arguments as Commander usage errors", async () => {
66
- const output = testRuntime(["greet"]);
67
-
68
- await expect(runCli(output.runtime)).resolves.toBe(1);
69
- expect(output.stdout()).toBe("");
70
- expect(output.stderr()).toContain(
71
- "error: missing required argument 'name'",
63
+ it("帮助和版本使用 manifest 身份,schema 导出完整契约", async () => {
64
+ const version = testRuntime(["--version"]);
65
+ expect(await runCli(version.runtime)).toBe(0);
66
+ expect(version.stdout()).toBe(
67
+ `${identity.commandName} ${identity.version}\n`,
72
68
  );
73
- expect(output.stderr()).toContain(`Usage: ${identity.commandName} greet`);
69
+ const help = testRuntime(["--help"]);
70
+ expect(await runCli(help.runtime)).toBe(0);
71
+ expect(help.stdout()).toContain(identity.commandName);
72
+ expect(help.stdout()).toContain("lookup");
73
+ expect(help.stdout()).toContain("schema");
74
+ const schema = testRuntime(["schema"]);
75
+ expect(await runCli(schema.runtime)).toBe(0);
76
+ const humanSchema = testRuntime(["schema", "--output-format", "text"]);
77
+ expect(await runCli(humanSchema.runtime)).toBe(0);
78
+ expect(humanSchema.stdout()).toContain("CLI 契约");
79
+ expect(humanSchema.stdout()).toContain('"commands"');
80
+ const manifest = JSON.parse(schema.stdout()).data.manifest;
81
+ expect(manifest).toMatchObject({
82
+ commands: {
83
+ lookup: {
84
+ input: {
85
+ inputSchema: {
86
+ properties: { name: { pattern: "^[a-z]+$" } },
87
+ required: ["name"],
88
+ },
89
+ },
90
+ success: { variants: { found: { exitCode: 0 } } },
91
+ failures: { notFound: { exitCode: 1 } },
92
+ },
93
+ schema: { success: { variants: { exported: { exitCode: 0 } } } },
94
+ },
95
+ controls: {
96
+ output: {
97
+ defaultFormat: "structured",
98
+ formats: ["structured", "text"],
99
+ },
100
+ },
101
+ usageFailure: { exitCode: 2 },
102
+ });
74
103
  });
75
104
 
76
- it("adapts business validation into a testable command error", async () => {
77
- const output = testRuntime(["greet", " "]);
78
-
79
- await expect(runCli(output.runtime)).resolves.toBe(1);
80
- expect(output.stdout()).toBe("");
81
- expect(output.stderr()).toContain("error: Name must not be empty");
105
+ it("等待输出完成,仅 stdout EPIPE 安静成功", async () => {
106
+ const output = testRuntime(["lookup", "ada"]);
107
+ expect(
108
+ await runCli({
109
+ ...output.runtime,
110
+ write: async () => {
111
+ throw Object.assign(new Error("pipe"), { code: "EPIPE" });
112
+ },
113
+ }),
114
+ ).toBe(0);
115
+ expect(
116
+ await runCli({
117
+ ...output.runtime,
118
+ write: async () => {
119
+ throw new Error("disk");
120
+ },
121
+ }),
122
+ ).toBe(70);
123
+ const failure = testRuntime(["lookup", "missing"]);
124
+ expect(
125
+ await runCli({
126
+ ...failure.runtime,
127
+ write: async () => {
128
+ throw Object.assign(new Error("pipe"), { code: "EPIPE" });
129
+ },
130
+ }),
131
+ ).toBe(70);
132
+ const argv = ["node", identity.commandName];
133
+ Object.defineProperty(argv, "slice", {
134
+ value() {
135
+ throw new Error("内部细节");
136
+ },
137
+ });
138
+ expect(await runCli({ ...output.runtime, argv })).toBe(70);
139
+ expect(output.stderr()).toContain("命令执行失败");
140
+ expect(output.stderr()).not.toContain("内部细节");
82
141
  });
83
142
  });
@@ -21,6 +21,8 @@ overrides:
21
21
  "vue>typescript": "-"
22
22
 
23
23
  catalog:
24
+ "@ykdz/cli-contract": 0.2.1
25
+ "@valibot/to-json-schema": ^1.8.0
24
26
  "@devcontainers/cli": 0.89.0
25
27
  "@hono/node-server": ^2.0.12
26
28
  "@playwright/test": ^1.62.1
@@ -31,7 +33,6 @@ catalog:
31
33
  "@vikejs/hono": ^0.2.1
32
34
  "@vue/devtools-api": ^8.1.5
33
35
  "@vue/tsconfig": ^0.9.1
34
- commander: ^15.0.0
35
36
  drizzle-kit: 1.0.0-rc.5-ab785fc
36
37
  drizzle-orm: 1.0.0-rc.5-ab785fc
37
38
  execa: ^10.0.1
@@ -1,3 +1,4 @@
1
+ import * as v from "valibot";
1
2
  /**
2
3
  * The durable, preset-agnostic topology used by local template follow-up
3
4
  * operations. Preset provenance deliberately belongs in Generation Record.
@@ -40,6 +41,20 @@ export type BlueprintValidationResult = {
40
41
  readonly ok: false;
41
42
  readonly issues: readonly BlueprintValidationIssue[];
42
43
  };
44
+ declare function packageDefinitionMessage(issue: v.BaseIssue<unknown>): string;
45
+ export declare const projectBlueprintSchema: v.StrictObjectSchema<{
46
+ readonly schemaVersion: v.LiteralSchema<3, (issue: v.LiteralIssue) => string>;
47
+ readonly packages: v.ArraySchema<v.StrictObjectSchema<{
48
+ readonly name: v.StringSchema<"Package name must be a string">;
49
+ readonly path: v.StringSchema<"Package Path must be a string">;
50
+ readonly role: v.PicklistSchema<readonly ["cli-tool", "runtime-service", "shared-library", "native-package"], "Package Role must be cli-tool, runtime-service, shared-library, or native-package">;
51
+ readonly packageDefinitionId: v.StringSchema<"Package Definition ID must use the package-<sha256> format">;
52
+ }, typeof packageDefinitionMessage>, "Package Definitions must be an array">;
53
+ readonly packageLinkIntents: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
54
+ readonly consumerPackagePath: v.StringSchema<"Package Link Intent consumer Package Path must be a string">;
55
+ readonly providerPackagePath: v.StringSchema<"Package Link Intent provider Package Path must be a string">;
56
+ }, (issue: v.StrictObjectIssue) => "Package Link Intent must be an object" | "Unknown Blueprint v3 field">, "Package Link Intents must be an array">, undefined>;
57
+ }, (issue: v.StrictObjectIssue) => "Local Template Metadata must be an object" | "Unknown Blueprint v3 field">;
43
58
  /** Canonical npm package-name predicate shared by Blueprint and input preparation. */
44
59
  export declare function isValidNewNpmPackageName(value: string): boolean;
45
60
  export type NewPackagePathValidation = {
@@ -42,7 +42,7 @@ const packageLinkIntentSchema = v.strictObject({
42
42
  }, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
43
43
  ? unknownBlueprintField
44
44
  : "Package Link Intent must be an object");
45
- const projectBlueprintSchema = v.strictObject({
45
+ export const projectBlueprintSchema = v.strictObject({
46
46
  schemaVersion: v.literal(3, (issue) => `Unsupported Local Template Metadata schema version ${String(issue.input)}; expected 3`),
47
47
  packages: v.array(packageDefinitionSchema, "Package Definitions must be an array"),
48
48
  packageLinkIntents: v.optional(v.array(packageLinkIntentSchema, "Package Link Intents must be an array")),