@pulse-compute/cli 0.0.0 → 1.0.0-beta.1

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 (190) hide show
  1. package/API.md +539 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +70 -1
  4. package/bin/pulse.js +15 -0
  5. package/bin/pulsewasm-extract.js +22 -0
  6. package/cli-spec.json +575 -0
  7. package/completions/_pulse +106 -0
  8. package/completions/pulse.bash +31 -0
  9. package/completions/pulse.fish +76 -0
  10. package/docs/README.md +77 -0
  11. package/docs/architecture/current-contracts.md +381 -0
  12. package/docs/architecture/overview.md +93 -0
  13. package/docs/architecture/vision.md +515 -0
  14. package/docs/concepts/bodies.md +186 -0
  15. package/docs/concepts/compilation-and-lowering.md +496 -0
  16. package/docs/concepts/contracts-and-providers.md +158 -0
  17. package/docs/concepts/effects-and-continuations.md +243 -0
  18. package/docs/concepts/entities-and-adapters.md +71 -0
  19. package/docs/concepts/package-owned-lowering.md +175 -0
  20. package/docs/concepts/targets-and-hosts.md +62 -0
  21. package/docs/contributing/README.md +38 -0
  22. package/docs/contributing/adding-core-provider.md +315 -0
  23. package/docs/contributing/adding-first-party-lowerer.md +327 -0
  24. package/docs/contributing/entities-lowering.md +91 -0
  25. package/docs/contributing/package-lowerer-contract.md +383 -0
  26. package/docs/contributing/pulse-aware-packages.md +149 -0
  27. package/docs/examples.md +112 -0
  28. package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
  29. package/docs/getting-started.md +128 -0
  30. package/docs/guides/compatibility-imports.md +60 -0
  31. package/docs/guides/deploying-fastly.md +188 -0
  32. package/docs/guides/deploying-node.md +143 -0
  33. package/docs/guides/events.md +233 -0
  34. package/docs/guides/fastly-capabilities.md +153 -0
  35. package/docs/guides/fetching-and-composition.md +101 -0
  36. package/docs/guides/grip.md +94 -0
  37. package/docs/guides/json-schemas.md +233 -0
  38. package/docs/guides/migrating-from-express.md +192 -0
  39. package/docs/guides/project-lifecycle.md +143 -0
  40. package/docs/guides/routing.md +184 -0
  41. package/docs/guides/troubleshooting.md +47 -0
  42. package/docs/maintainers/README.md +60 -0
  43. package/docs/maintainers/codex-maintainer.md +71 -0
  44. package/docs/maintainers/documentation-deployment.md +147 -0
  45. package/docs/maintainers/documentation-system.md +58 -0
  46. package/docs/maintainers/documentation-versioning.md +65 -0
  47. package/docs/maintainers/maintainer-charter.md +84 -0
  48. package/docs/maintainers/maintenance-policy.json +778 -0
  49. package/docs/maintainers/maintenance-policy.md +124 -0
  50. package/docs/maintainers/npm-publishing.md +154 -0
  51. package/docs/maintainers/plugin-readiness.json +19 -0
  52. package/docs/maintainers/plugin-readiness.md +31 -0
  53. package/docs/maintainers/public-site.md +158 -0
  54. package/docs/maintainers/release-acceptance.md +250 -0
  55. package/docs/maintainers/release-manifest.md +88 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +93 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +288 -0
  60. package/docs/packages/README.md +59 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +71 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +113 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +217 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +256 -0
  71. package/docs/preview-scope.md +142 -0
  72. package/docs/reference/README.md +33 -0
  73. package/docs/reference/cli-spec.json +575 -0
  74. package/docs/reference/cli.md +457 -0
  75. package/docs/reference/compatibility-matrix.md +169 -0
  76. package/docs/reference/diagnostics.md +1872 -0
  77. package/docs/reference/documentation-versions.json +14 -0
  78. package/docs/reference/environment.md +347 -0
  79. package/docs/reference/handler-authoring.md +264 -0
  80. package/docs/reference/project-config.md +554 -0
  81. package/docs/reference/project-config.schema.json +918 -0
  82. package/docs/reference/release-manifest.json +427 -0
  83. package/docs/reference/shell-completion.md +23 -0
  84. package/documentation-site.json +661 -0
  85. package/documentation-versions.json +14 -0
  86. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  87. package/examples/01-hello-json/.pulse/config.ts +16 -0
  88. package/examples/01-hello-json/README.md +119 -0
  89. package/examples/01-hello-json/package.json +20 -0
  90. package/examples/01-hello-json/src/index.ts +9 -0
  91. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  92. package/examples/01-hello-json/tsconfig.json +14 -0
  93. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  94. package/examples/02-request-schema/.pulse/config.ts +17 -0
  95. package/examples/02-request-schema/README.md +145 -0
  96. package/examples/02-request-schema/package.json +20 -0
  97. package/examples/02-request-schema/src/index.ts +18 -0
  98. package/examples/02-request-schema/src/schemas.ts +20 -0
  99. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  100. package/examples/02-request-schema/tsconfig.json +14 -0
  101. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  102. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  103. package/examples/03-fetch-composition/README.md +210 -0
  104. package/examples/03-fetch-composition/package.json +20 -0
  105. package/examples/03-fetch-composition/src/index.ts +60 -0
  106. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  107. package/examples/03-fetch-composition/tsconfig.json +14 -0
  108. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  109. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  110. package/examples/05-fastly-capabilities/README.md +257 -0
  111. package/examples/05-fastly-capabilities/package.json +22 -0
  112. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  113. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  114. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  115. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  116. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  117. package/examples/07-opaque-proxy/README.md +150 -0
  118. package/examples/07-opaque-proxy/package.json +21 -0
  119. package/examples/07-opaque-proxy/src/index.ts +9 -0
  120. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  121. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  122. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  123. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  124. package/examples/09-router-lowering/README.md +287 -0
  125. package/examples/09-router-lowering/package.json +22 -0
  126. package/examples/09-router-lowering/src/index.ts +65 -0
  127. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  128. package/examples/09-router-lowering/tsconfig.json +14 -0
  129. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  130. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  131. package/examples/10-entities-tools/README.md +165 -0
  132. package/examples/10-entities-tools/package.json +21 -0
  133. package/examples/10-entities-tools/src/handlers.ts +15 -0
  134. package/examples/10-entities-tools/src/index.ts +31 -0
  135. package/examples/10-entities-tools/src/schemas.ts +18 -0
  136. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  137. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  138. package/examples/10-entities-tools/tsconfig.json +14 -0
  139. package/examples/11-events/.pulse/.gitignore +4 -0
  140. package/examples/11-events/.pulse/config.ts +24 -0
  141. package/examples/11-events/README.md +194 -0
  142. package/examples/11-events/package.json +20 -0
  143. package/examples/11-events/src/index.ts +27 -0
  144. package/examples/11-events/src/schemas.ts +19 -0
  145. package/examples/11-events/tests/pulse.harness.ts +34 -0
  146. package/examples/11-events/tsconfig.json +15 -0
  147. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  148. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  149. package/examples/12-mcp-proxy/README.md +149 -0
  150. package/examples/12-mcp-proxy/package.json +20 -0
  151. package/examples/12-mcp-proxy/src/index.ts +17 -0
  152. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  153. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  154. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  155. package/examples/13-jwt-es256/README.md +195 -0
  156. package/examples/13-jwt-es256/package.json +21 -0
  157. package/examples/13-jwt-es256/src/index.ts +21 -0
  158. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  159. package/examples/13-jwt-es256/tsconfig.json +14 -0
  160. package/examples/README.md +36 -0
  161. package/package.json +66 -6
  162. package/project-config.schema.json +918 -0
  163. package/release-manifest.json +427 -0
  164. package/src/command-spec.js +279 -0
  165. package/src/completion.js +113 -0
  166. package/src/diagnostics.js +350 -0
  167. package/src/documentation.js +45 -0
  168. package/src/index.d.ts +133 -0
  169. package/src/index.js +15 -0
  170. package/src/internal/command-executor.d.ts +29 -0
  171. package/src/internal/command-executor.js +143 -0
  172. package/src/internal/command-plan.d.ts +9 -0
  173. package/src/internal/command-plan.js +73 -0
  174. package/src/internal/command-reporter.d.ts +15 -0
  175. package/src/internal/command-reporter.js +133 -0
  176. package/src/internal/command-request.d.ts +35 -0
  177. package/src/internal/command-request.js +154 -0
  178. package/src/internal/node-http.js +54 -0
  179. package/src/internal/project-context.d.ts +66 -0
  180. package/src/internal/project-context.js +175 -0
  181. package/src/project-config-reference.js +4 -0
  182. package/src/project-config-schema.d.ts +23 -0
  183. package/src/project-config-schema.js +271 -0
  184. package/src/project-config.js +724 -0
  185. package/src/project-execution.js +2930 -0
  186. package/src/provider-drivers.js +3 -0
  187. package/src/target-support.js +3 -0
  188. package/src/typescript-module-loader.js +269 -0
  189. package/src/workflow.js +78 -0
  190. package/src/workspace.js +82 -0
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ const COMMAND_SPEC_VERSION = 'pulse.cli-command-spec.v5';
4
+ const COMMANDS = Object.freeze(['init', 'dev', 'test', 'inspect', 'compile', 'build', 'doctor']);
5
+ const PUBLIC_COMMAND_ORDER = Object.freeze(['init', 'doctor', 'inspect', 'test', 'dev', 'compile', 'build']);
6
+
7
+ function freezeOption(value) {
8
+ return Object.freeze({
9
+ ...value,
10
+ flags: Object.freeze(value.flags.map((entry) => Object.freeze(typeof entry === 'string' ? { name: entry } : { ...entry }))),
11
+ commands: value.commands === 'all' ? 'all' : Object.freeze([...value.commands]),
12
+ valueNameByCommand: value.valueNameByCommand ? Object.freeze({ ...value.valueNameByCommand }) : undefined,
13
+ descriptionByCommand: value.descriptionByCommand ? Object.freeze({ ...value.descriptionByCommand }) : undefined
14
+ });
15
+ }
16
+
17
+ const OPTION_SPECS = Object.freeze([
18
+ freezeOption({ id: 'help', key: 'help', flags: ['--help', '-h'], kind: 'boolean', commands: 'all', visibility: 'public', description: 'Print public CLI help and exit.' }),
19
+ freezeOption({ id: 'json', key: 'json', flags: ['--json'], kind: 'boolean', commands: 'all', visibility: 'public', description: 'Emit machine-readable JSON. pulse dev emits one JSON event per line.' }),
20
+ freezeOption({ id: 'dry-run', key: 'dryRun', flags: ['--dry-run', '--plan'], kind: 'boolean', commands: 'all', visibility: 'public', description: 'Resolve and report the command plan without executing it.' }),
21
+ freezeOption({ id: 'force', key: 'force', flags: ['--force'], kind: 'boolean', commands: ['init'], visibility: 'public', description: 'Allow generated files to replace entries in a non-empty target directory.' }),
22
+ freezeOption({ id: 'strict', key: 'strict', flags: ['--strict'], kind: 'boolean', commands: ['doctor'], visibility: 'public', description: 'Treat doctor warnings as failed checks.' }),
23
+ freezeOption({ id: 'once', key: 'once', flags: ['--once'], kind: 'boolean', commands: ['dev'], visibility: 'public', description: 'Close the development server after the first completed request.' }),
24
+ freezeOption({ id: 'watch', key: 'watch', flags: [{ name: '--watch', value: true }, { name: '--no-watch', value: false }], kind: 'boolean-value', commands: ['dev'], visibility: 'public', description: 'Enable or disable entry and schema dependency watching. Watching is enabled by default.' }),
25
+ freezeOption({ id: 'clean', key: 'clean', flags: [{ name: '--clean', value: true }, { name: '--no-clean', value: false }], kind: 'boolean-value', commands: ['compile', 'build'], visibility: 'public', description: 'Remove or preserve the selected output directory before writing artifacts. Cleaning is enabled by default.' }),
26
+ freezeOption({ id: 'experimental-native-size', key: 'experimentalNativeSize', flags: ['--experimental-native-size'], kind: 'boolean', commands: ['compile', 'build'], visibility: 'public', description: 'Experimentally optimize Native Wasm for size. JavaScript build targets reject this flag.' }),
27
+
28
+ freezeOption({ id: 'profile', key: 'profile', flags: ['--profile'], kind: 'value', valueName: '<profile>', commands: ['doctor', 'inspect', 'test', 'dev', 'compile', 'build'], visibility: 'public', description: 'Select a project profile. Precedence: --profile, PULSE_PROFILE, pulse.defaultProfile.' }),
29
+ freezeOption({ id: 'out', key: 'outDir', flags: ['--out'], kind: 'value', valueName: '<dir>', commands: ['compile', 'build'], visibility: 'public', description: 'Override the project-relative artifact output directory.' }),
30
+ freezeOption({ id: 'host', key: 'host', flags: ['--host'], kind: 'value', valueName: '<host>', commands: ['dev'], visibility: 'public', description: 'Override the development listen host.' }),
31
+ freezeOption({ id: 'port', key: 'port', flags: ['--port'], kind: 'value', valueName: '<port>', parse: 'number', commands: ['dev'], visibility: 'public', description: 'Override the development listen port. Use 0 to request an ephemeral port.' }),
32
+ freezeOption({ id: 'artifact', key: 'artifact', flags: ['--artifact'], kind: 'value', valueName: '<file.json>', commands: ['inspect'], visibility: 'public', description: 'Inspect an existing JSON build or compiler artifact instead of a project.' }),
33
+ freezeOption({ id: 'case', key: 'caseName', flags: ['--case'], kind: 'value', valueName: '<name>', commands: ['test'], visibility: 'public', description: 'Run one named project test case.' }),
34
+ freezeOption({ id: 'name', key: 'name', flags: ['--name'], kind: 'value', valueName: '<package-name>', commands: ['init'], visibility: 'public', description: 'Set the generated npm package name.' })
35
+ ]);
36
+
37
+ function freezeCommand(value) {
38
+ return Object.freeze({
39
+ ...value,
40
+ usage: Object.freeze([...value.usage]),
41
+ positionals: Object.freeze([...(value.positionals || [])].map((entry) => Object.freeze({ ...entry }))),
42
+ optionIds: Object.freeze([...value.optionIds]),
43
+ examples: Object.freeze([...(value.examples || [])]),
44
+ diagnostics: Object.freeze([...(value.diagnostics || [])]),
45
+ outputs: Object.freeze([...(value.outputs || [])]),
46
+ sideEffects: Object.freeze([...(value.sideEffects || [])])
47
+ });
48
+ }
49
+
50
+ const COMMAND_SPECS = Object.freeze({
51
+ init: freezeCommand({
52
+ name: 'init',
53
+ summary: 'Create a conventional .pulse workspace with an async Pulse application and dedicated test harness.',
54
+ usage: ['pulse init [directory] [--name <package-name>] [--force]'],
55
+ positionals: [{ name: 'directory', key: 'target', description: 'Target directory. Defaults to the current directory.' }],
56
+ optionIds: ['name', 'force'],
57
+ examples: ['pulse init ./my-pulse-app', 'pulse init ./edge-app --name @example/edge-app'],
58
+ diagnostics: ['PULSE_INIT_NOT_EMPTY', 'PULSE_INIT_FILE_EXISTS', 'PULSE_PROVIDER_UNSUPPORTED'],
59
+ outputs: ['Human output lists generated files and next steps.', 'With --json, emits one initialization result object.'],
60
+ sideEffects: ['Creates .pulse/config.ts, an async Pulse application, a dedicated test harness, package scripts, and documentation.', 'Does not install dependencies or run npm, pnpm, or any network operation.'],
61
+ exit: '0 on success; 2 for invalid usage, provider selection, or unsafe overwrite conditions.'
62
+ }),
63
+ doctor: freezeCommand({
64
+ name: 'doctor',
65
+ summary: 'Audit project shape, compilation, schemas, provider bindings, output safety, and required tools.',
66
+ usage: ['pulse doctor [directory] [--profile <name>] [--strict]'],
67
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
68
+ optionIds: ['profile', 'strict'],
69
+ examples: ['pulse doctor ./my-pulse-app', 'pulse doctor ./edge-app --strict --json'],
70
+ diagnostics: ['PULSE_CONFIG_NOT_FOUND', 'PULSE_PROJECT_COMPILE_FAILED', 'PULSE_NODE_VERSION_UNSUPPORTED', 'PULSE_CANONICAL_NATIVE_COMPILE_FAILED'],
71
+ outputs: ['Human output prints every check.', 'With --json, emits one completed audit object including failed and warning checks.'],
72
+ sideEffects: ['Reads and compiles the project.', 'Does not write the build output.'],
73
+ exit: '0 when the audit passes; 1 when a completed audit contains failed checks; 2–5 only when the audit command itself cannot be completed.'
74
+ }),
75
+ inspect: freezeCommand({
76
+ name: 'inspect',
77
+ summary: 'Report canonical compiler, schema, effect, continuation, capability, provider, and build-mode details.',
78
+ usage: [
79
+ 'pulse inspect [directory] [--profile <name>]',
80
+ 'pulse inspect --artifact <file.json>'
81
+ ],
82
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
83
+ optionIds: ['profile', 'artifact'],
84
+ examples: ['pulse inspect ./my-pulse-app --json', 'pulse inspect --artifact ./dist/pulse-build.json'],
85
+ diagnostics: ['PULSE_CONFIG_NOT_FOUND', 'PULSE_PROJECT_COMPILE_FAILED', 'PULSE_CANONICAL_COMPILE_FAILED'],
86
+ outputs: ['Human output prints a compact project/compiler summary.', 'With --json, emits one inspection or artifact object.'],
87
+ sideEffects: ['Compiles and validates the project in memory.', 'Does not write the build output.'],
88
+ exit: '0 on success; 2–5 according to the emitted stable diagnostic.'
89
+ }),
90
+ test: freezeCommand({
91
+ name: 'test',
92
+ summary: 'Run configured cases through the selected provider local-conformance runtime.',
93
+ usage: ['pulse test [directory] [--profile <name>] [--case <name>]'],
94
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
95
+ optionIds: ['profile', 'case'],
96
+ examples: ['pulse test ./my-pulse-app', 'pulse test ./my-pulse-app --case smoke --json'],
97
+ diagnostics: ['PULSE_TEST_PROVIDER_REQUIRED', 'PULSE_PROVIDER_CAPABILITY_MISSING', 'PULSE_SCHEMA_DECODE'],
98
+ outputs: ['Human output uses TAP-like case lines and a summary.', 'With --json, emits one test-run object; expected stable errors remain inside their case results.'],
99
+ sideEffects: ['Compiles the project and executes configured deterministic or live fixtures.', 'Does not write the build output.'],
100
+ exit: '0 when all selected cases pass; 1 when the completed test run has failed cases; 2–5 when setup, compilation, runtime, or toolchain execution fails before a normal result.'
101
+ }),
102
+ dev: freezeCommand({
103
+ name: 'dev',
104
+ summary: 'Start the foreground local server with the selected provider conformance runtime.',
105
+ usage: ['pulse dev [directory] [--profile <name>] [--host <host>] [--port <port>] [--watch|--no-watch] [--once]'],
106
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
107
+ optionIds: ['profile', 'host', 'port', 'watch', 'once'],
108
+ examples: ['pulse dev ./my-pulse-app', 'pulse dev ./my-pulse-app --port 0 --once --json'],
109
+ diagnostics: ['PULSE_DEV_PROVIDER_UNSUPPORTED', 'PULSE_REQUEST_BODY_TOO_LARGE', 'PULSE_FETCH_NETWORK'],
110
+ outputs: ['Human output reports the ready URL and reload or error messages.', 'With --json, stdout is newline-delimited JSON events: compiled, ready, reloaded, request, compile-error, and request-error. It is not one enclosing JSON document.'],
111
+ sideEffects: ['Binds a foreground HTTP listener.', 'Watches the entry and schema dependency graph by default.', 'Configuration-file changes require restarting the command.'],
112
+ exit: '0 after a normal server close; 2–5 when setup, compilation, provider, runtime, or toolchain initialization fails.'
113
+ }),
114
+ compile: freezeCommand({
115
+ name: 'compile',
116
+ summary: 'Compile the canonical project into provider-neutral Pulse-owned WebAssembly.',
117
+ usage: ['pulse compile [directory] [--profile <name>] [--out <dir>] [--clean|--no-clean] [--experimental-native-size]'],
118
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
119
+ optionIds: ['profile', 'out', 'clean', 'experimental-native-size'],
120
+ examples: ['pulse compile ./my-pulse-app', 'pulse compile ./edge-app --out ./dist --json', 'pulse compile ./edge-app --experimental-native-size'],
121
+ diagnostics: ['PULSE_BUILD_OUT_UNSAFE', 'PULSE_CANONICAL_NATIVE_PLAN_FAILED', 'PULSE_CANONICAL_NATIVE_COMPILE_FAILED'],
122
+ outputs: ['Writes pulse-compile.json, the canonical program, native plan, generated AssemblyScript, compact Wasm, WAT, and native manifest.', 'The output is provider-neutral and does not package a deployment provider runtime.', 'Experimental size builds record the exact non-default Native compiler optimization settings.', 'With --json, emits one compile result object containing exact artifact paths and native metadata.'],
123
+ sideEffects: ['Cleans the output directory by default.', 'Invokes the lockfile-pinned AssemblyScript compiler.', 'Refuses absolute, parent-traversal, and symbolic-link traversal outside the project root.'],
124
+ exit: '0 on success; 2–5 according to the emitted stable diagnostic.'
125
+ }),
126
+ build: freezeCommand({
127
+ name: 'build',
128
+ summary: 'Compile the canonical project and realize the deployment provider selected by the active project profile.',
129
+ usage: ['pulse build [directory] [--profile <name>] [--out <dir>] [--clean|--no-clean] [--experimental-native-size]'],
130
+ positionals: [{ name: 'directory', key: 'directory', description: 'Project discovery start. Defaults to the current directory.' }],
131
+ optionIds: ['profile', 'out', 'clean', 'experimental-native-size'],
132
+ examples: ['pulse build ./my-pulse-app', 'pulse build ./edge-app --out ./dist --json', 'pulse build ./edge-app --experimental-native-size'],
133
+ diagnostics: ['PULSE_BUILD_OUT_UNSAFE', 'PULSE_BUILD_PROVIDER_REQUIRED', 'PULSE_EXPERIMENTAL_NATIVE_SIZE_UNSUPPORTED', 'PULSE_CANONICAL_NATIVE_COMPILE_FAILED'],
134
+ outputs: ['Writes pulse-build.json, the provider-neutral native module, and the configured provider realization.', 'Fastly builds write generated AssemblyScript and direct-host-ABI native Wasm at bin/main.wasm; no JavaScript runtime image is packaged.', 'Experimental size builds record the exact non-default Native compiler optimization settings in portable, provider, and build metadata.', 'With --json, emits one build result object containing exact portable and provider artifact paths.'],
135
+ sideEffects: ['Cleans the output directory by default.', 'Invokes the native compiler and configured provider realization.', 'Refuses absolute, parent-traversal, and symbolic-link traversal outside the project root.'],
136
+ exit: '0 on success; 2–5 according to the emitted stable diagnostic.'
137
+ })
138
+ });
139
+
140
+ const META_INVOCATIONS = Object.freeze([
141
+ Object.freeze({ syntax: 'pulse --help | pulse -h | pulse help', description: 'Print public help.' }),
142
+ Object.freeze({ syntax: 'pulse --version | pulse -v | pulse version', description: 'Print the installed CLI package version.' }),
143
+ Object.freeze({ syntax: 'pulse completion <bash|zsh|fish>', description: 'Print a shell completion script generated from the public command specification.' })
144
+ ]);
145
+
146
+ const OPTION_BY_FLAG = new Map();
147
+ for (const option of OPTION_SPECS) {
148
+ for (const flag of option.flags) OPTION_BY_FLAG.set(flag.name, Object.freeze({ option, flag }));
149
+ }
150
+
151
+ function optionForToken(token) {
152
+ const exact = OPTION_BY_FLAG.get(token);
153
+ if (exact) return exact;
154
+ if (!String(token).startsWith('--') || !String(token).includes('=')) return undefined;
155
+ const name = String(token).slice(0, String(token).indexOf('='));
156
+ const found = OPTION_BY_FLAG.get(name);
157
+ if (!found || found.option.kind !== 'value') return undefined;
158
+ return found;
159
+ }
160
+
161
+ function optionAllowed(option, command) {
162
+ return option.commands === 'all' || option.commands.includes(command);
163
+ }
164
+
165
+ function publicOptionsForCommand(command) {
166
+ const commandSpec = COMMAND_SPECS[command];
167
+ if (!commandSpec) return Object.freeze([]);
168
+ const ids = new Set(['help', 'json', 'dry-run', ...commandSpec.optionIds]);
169
+ return Object.freeze(OPTION_SPECS.filter((option) => option.visibility === 'public' && ids.has(option.id) && optionAllowed(option, command)));
170
+ }
171
+
172
+ function optionDisplay(option, command) {
173
+ const valueName = command && option.valueNameByCommand && option.valueNameByCommand[command]
174
+ ? option.valueNameByCommand[command]
175
+ : option.valueName;
176
+ return option.flags.map((flag) => `${flag.name}${option.kind === 'value' ? ` ${valueName}` : ''}`).join(', ');
177
+ }
178
+
179
+ function optionDescription(option, command) {
180
+ return command && option.descriptionByCommand && option.descriptionByCommand[command]
181
+ ? option.descriptionByCommand[command]
182
+ : option.description;
183
+ }
184
+
185
+ function publicCommandSpecDocument(options = {}) {
186
+ const version = String(options.version || 'unknown');
187
+ const completionShells = Object.freeze([...(options.completionShells || [])]);
188
+ const globalIds = new Set(['help', 'json', 'dry-run']);
189
+ function serializeOption(option, command) {
190
+ return Object.freeze({
191
+ id: option.id,
192
+ key: option.key,
193
+ flags: Object.freeze(option.flags.map((flag) => Object.freeze({ ...flag }))),
194
+ kind: option.kind,
195
+ valueName: command && option.valueNameByCommand && option.valueNameByCommand[command]
196
+ ? option.valueNameByCommand[command]
197
+ : option.valueName,
198
+ description: optionDescription(option, command),
199
+ unsupportedCode: option.unsupportedCode
200
+ });
201
+ }
202
+ return Object.freeze({
203
+ schemaVersion: COMMAND_SPEC_VERSION,
204
+ cliVersion: version,
205
+ completionShells,
206
+ metaInvocations: Object.freeze(META_INVOCATIONS.map((entry) => Object.freeze({ ...entry }))),
207
+ globalOptions: Object.freeze(OPTION_SPECS
208
+ .filter((entry) => entry.visibility === 'public' && globalIds.has(entry.id))
209
+ .map((entry) => serializeOption(entry))),
210
+ commands: Object.freeze(PUBLIC_COMMAND_ORDER.map((name) => {
211
+ const command = COMMAND_SPECS[name];
212
+ return Object.freeze({
213
+ name,
214
+ summary: command.summary,
215
+ usage: command.usage,
216
+ positionals: command.positionals,
217
+ options: Object.freeze(publicOptionsForCommand(name)
218
+ .filter((entry) => !globalIds.has(entry.id))
219
+ .map((entry) => serializeOption(entry, name))),
220
+ examples: command.examples,
221
+ diagnostics: command.diagnostics,
222
+ outputs: command.outputs,
223
+ sideEffects: command.sideEffects,
224
+ exit: command.exit
225
+ });
226
+ }))
227
+ });
228
+ }
229
+
230
+ function renderUsage(options = {}) {
231
+ const version = String(options.version || 'unknown');
232
+ const configFiles = options.configFiles || [];
233
+ const lines = [
234
+ `Pulse CLI ${version}`,
235
+ '',
236
+ 'Meta commands:'
237
+ ];
238
+ for (const entry of META_INVOCATIONS) lines.push(` ${entry.syntax}`, ` ${entry.description}`);
239
+ lines.push('', 'Usage:');
240
+ for (const command of PUBLIC_COMMAND_ORDER) {
241
+ for (const signature of COMMAND_SPECS[command].usage) lines.push(` ${signature}`);
242
+ }
243
+ lines.push('', 'Global options:');
244
+ for (const option of OPTION_SPECS.filter((entry) => entry.visibility === 'public' && ['help', 'json', 'dry-run'].includes(entry.id))) {
245
+ lines.push(` ${optionDisplay(option).padEnd(26)} ${optionDescription(option)}`);
246
+ }
247
+ lines.push('', 'Command options:');
248
+ for (const command of PUBLIC_COMMAND_ORDER) {
249
+ const commandOptions = publicOptionsForCommand(command).filter((entry) => !['help', 'json', 'dry-run'].includes(entry.id));
250
+ lines.push(` ${command}:`);
251
+ for (const option of commandOptions) lines.push(` ${optionDisplay(option, command).padEnd(30)} ${optionDescription(option, command)}`);
252
+ }
253
+ lines.push(
254
+ '',
255
+ 'Project config search:',
256
+ ` ${configFiles.join(', ')}`,
257
+ '',
258
+ 'Use --dry-run or --plan to resolve a command without executing it.',
259
+ 'Project discovery starts from [directory] or the current directory and searches upward for .pulse/config.ts.',
260
+ 'Provider selection belongs in the active profile; pulse compile is provider-neutral and pulse build realizes the selected provider.'
261
+ );
262
+ return `${lines.join('\n')}\n`;
263
+ }
264
+
265
+ module.exports = Object.freeze({
266
+ COMMAND_SPEC_VERSION,
267
+ COMMANDS,
268
+ PUBLIC_COMMAND_ORDER,
269
+ COMMAND_SPECS,
270
+ OPTION_SPECS,
271
+ META_INVOCATIONS,
272
+ optionForToken,
273
+ optionAllowed,
274
+ publicOptionsForCommand,
275
+ optionDisplay,
276
+ optionDescription,
277
+ publicCommandSpecDocument,
278
+ renderUsage
279
+ });
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ PUBLIC_COMMAND_ORDER,
5
+ COMMAND_SPECS,
6
+ publicOptionsForCommand
7
+ } = require('./command-spec.js');
8
+
9
+ const COMPLETION_SHELLS = Object.freeze(['bash', 'zsh', 'fish']);
10
+ const META_WORDS = Object.freeze(['completion', 'help', 'version', '--help', '-h', '--version', '-v']);
11
+
12
+ function optionNames(command) {
13
+ return Object.freeze(publicOptionsForCommand(command).flatMap((option) => option.flags.map((flag) => flag.name)));
14
+ }
15
+
16
+ function valueChoices() {
17
+ return Object.freeze([]);
18
+ }
19
+
20
+ function bashCompletion() {
21
+ const commands = [...PUBLIC_COMMAND_ORDER, ...META_WORDS].join(' ');
22
+ const commandCases = PUBLIC_COMMAND_ORDER.map((command) => ` ${command}) words=${JSON.stringify(optionNames(command).join(' '))} ;;`).join('\n');
23
+ return `# Generated from @pulse-compute/cli/src/command-spec.js.\n# Install for the current shell with: source <(pulse completion bash)\n_pulse_completion() {\n local cur command words\n COMPREPLY=()\n cur="\${COMP_WORDS[COMP_CWORD]}"\n command="\${COMP_WORDS[1]}"\n\n if (( COMP_CWORD == 1 )); then\n COMPREPLY=( $(compgen -W ${JSON.stringify(commands)} -- "$cur") )\n return 0\n fi\n\n if [[ "$command" == "completion" ]]; then\n COMPREPLY=( $(compgen -W ${JSON.stringify(COMPLETION_SHELLS.join(' '))} -- "$cur") )\n return 0\n fi\n\n case "$command" in\n${commandCases}\n *) words=${JSON.stringify(commands)} ;;\n esac\n COMPREPLY=( $(compgen -W "$words" -- "$cur") )\n}\ncomplete -F _pulse_completion pulse\n`;
24
+ }
25
+
26
+ function zshEscape(value) {
27
+ return String(value).replace(/'/g, "'\\''");
28
+ }
29
+
30
+ function zshValueCompletion(option, command) {
31
+ if (option.kind !== 'value') return '';
32
+ if (['project', 'out'].includes(option.id)) return ':directory:_directories';
33
+ if (['config', 'entry', 'artifact'].includes(option.id)) return ':file:_files';
34
+ return ':value:';
35
+ }
36
+
37
+ function zshCompletion() {
38
+ const commands = PUBLIC_COMMAND_ORDER.map((name) => ` '${zshEscape(name)}:${zshEscape(COMMAND_SPECS[name].summary)}'`).join('\n');
39
+ const commandCases = PUBLIC_COMMAND_ORDER.map((command) => {
40
+ const options = publicOptionsForCommand(command).flatMap((option) => {
41
+ const description = zshEscape(option.descriptionByCommand?.[command] || option.description);
42
+ const value = zshValueCompletion(option, command);
43
+ return option.flags.map((flag) => ` '${zshEscape(flag.name)}[${description}]${value}'`);
44
+ }).join(' \\\n');
45
+ return ` ${command})\n _arguments -s ${options || "'*:argument:_files'"}\n ;;`;
46
+ }).join('\n');
47
+ return `#compdef pulse\n# Generated from @pulse-compute/cli/src/command-spec.js.\nlocal -a commands\ncommands=(\n${commands}\n 'completion:Print a generated shell completion script'\n 'help:Print public help'\n 'version:Print the installed CLI version'\n '--help:Print public help'\n '-h:Print public help'\n '--version:Print the installed CLI version'\n '-v:Print the installed CLI version'\n)\n\nif (( CURRENT == 2 )); then\n _describe 'pulse command' commands\n return\nfi\n\ncase $words[2] in\n completion)\n _values 'shell' ${COMPLETION_SHELLS.join(' ')}\n ;;\n${commandCases}\n *)\n _describe 'pulse command' commands\n ;;\nesac\n`;
48
+ }
49
+
50
+ function fishDescription(value) {
51
+ return String(value).replace(/'/g, "\\'");
52
+ }
53
+
54
+ function fishCompletion() {
55
+ const lines = [
56
+ '# Generated from @pulse-compute/cli/src/command-spec.js.',
57
+ '# Install for the current shell with: pulse completion fish | source',
58
+ 'complete -c pulse -f'
59
+ ];
60
+ for (const command of PUBLIC_COMMAND_ORDER) {
61
+ lines.push(`complete -c pulse -n '__fish_use_subcommand' -a '${command}' -d '${fishDescription(COMMAND_SPECS[command].summary)}'`);
62
+ }
63
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -a 'completion' -d 'Print a generated shell completion script'");
64
+ lines.push("complete -c pulse -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'");
65
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -a 'help version'");
66
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -l help -d 'Print public help'");
67
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -s h -d 'Print public help'");
68
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -l version -d 'Print the installed CLI version'");
69
+ lines.push("complete -c pulse -n '__fish_use_subcommand' -s v -d 'Print the installed CLI version'");
70
+ for (const command of PUBLIC_COMMAND_ORDER) {
71
+ for (const option of publicOptionsForCommand(command)) {
72
+ for (const flag of option.flags) {
73
+ const parts = [`complete -c pulse -n '__fish_seen_subcommand_from ${command}'`];
74
+ if (flag.name.startsWith('--')) parts.push(`-l ${flag.name.slice(2)}`);
75
+ else if (flag.name.startsWith('-')) parts.push(`-s ${flag.name.slice(1)}`);
76
+ if (option.kind === 'value') {
77
+ parts.push('-r');
78
+ const choices = valueChoices(option.id, command);
79
+ if (choices.length) parts.push(`-a '${choices.join(' ')}'`);
80
+ }
81
+ parts.push(`-d '${fishDescription(option.descriptionByCommand?.[command] || option.description)}'`);
82
+ lines.push(parts.join(' '));
83
+ }
84
+ }
85
+ }
86
+ return `${lines.join('\n')}\n`;
87
+ }
88
+
89
+ function renderCompletion(shell) {
90
+ const normalized = String(shell || '').toLowerCase();
91
+ if (normalized === 'bash') return bashCompletion();
92
+ if (normalized === 'zsh') return zshCompletion();
93
+ if (normalized === 'fish') return fishCompletion();
94
+ const error = new Error(`Unsupported completion shell ${shell}. Choose bash, zsh, or fish.`);
95
+ error.code = 'PULSE_ARGUMENT_UNEXPECTED';
96
+ throw error;
97
+ }
98
+
99
+ function completionFile(shell) {
100
+ if (shell === 'bash') return 'pulse.bash';
101
+ if (shell === 'zsh') return '_pulse';
102
+ if (shell === 'fish') return 'pulse.fish';
103
+ throw new Error(`Unsupported completion shell ${shell}`);
104
+ }
105
+
106
+ module.exports = Object.freeze({
107
+ COMPLETION_SHELLS,
108
+ META_WORDS,
109
+ optionNames,
110
+ valueChoices,
111
+ renderCompletion,
112
+ completionFile
113
+ });