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

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 (191) hide show
  1. package/API.md +572 -0
  2. package/CHANGELOG.md +71 -0
  3. package/README.md +71 -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 +470 -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 +300 -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 +70 -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 +202 -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 +100 -0
  48. package/docs/maintainers/maintenance-policy.json +809 -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 +293 -0
  55. package/docs/maintainers/release-manifest.md +89 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +105 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +334 -0
  60. package/docs/packages/README.md +60 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +78 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +122 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +243 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +265 -0
  71. package/docs/packages/s3.md +101 -0
  72. package/docs/preview-scope.md +142 -0
  73. package/docs/reference/README.md +33 -0
  74. package/docs/reference/cli-spec.json +575 -0
  75. package/docs/reference/cli.md +457 -0
  76. package/docs/reference/compatibility-matrix.md +169 -0
  77. package/docs/reference/diagnostics.md +1872 -0
  78. package/docs/reference/documentation-versions.json +22 -0
  79. package/docs/reference/environment.md +347 -0
  80. package/docs/reference/handler-authoring.md +264 -0
  81. package/docs/reference/project-config.md +582 -0
  82. package/docs/reference/project-config.schema.json +981 -0
  83. package/docs/reference/release-manifest.json +441 -0
  84. package/docs/reference/shell-completion.md +23 -0
  85. package/documentation-site.json +668 -0
  86. package/documentation-versions.json +22 -0
  87. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  88. package/examples/01-hello-json/.pulse/config.ts +16 -0
  89. package/examples/01-hello-json/README.md +119 -0
  90. package/examples/01-hello-json/package.json +20 -0
  91. package/examples/01-hello-json/src/index.ts +9 -0
  92. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  93. package/examples/01-hello-json/tsconfig.json +14 -0
  94. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  95. package/examples/02-request-schema/.pulse/config.ts +17 -0
  96. package/examples/02-request-schema/README.md +145 -0
  97. package/examples/02-request-schema/package.json +20 -0
  98. package/examples/02-request-schema/src/index.ts +18 -0
  99. package/examples/02-request-schema/src/schemas.ts +20 -0
  100. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  101. package/examples/02-request-schema/tsconfig.json +14 -0
  102. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  103. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  104. package/examples/03-fetch-composition/README.md +210 -0
  105. package/examples/03-fetch-composition/package.json +20 -0
  106. package/examples/03-fetch-composition/src/index.ts +60 -0
  107. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  108. package/examples/03-fetch-composition/tsconfig.json +14 -0
  109. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  110. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  111. package/examples/05-fastly-capabilities/README.md +257 -0
  112. package/examples/05-fastly-capabilities/package.json +22 -0
  113. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  114. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  115. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  116. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  117. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  118. package/examples/07-opaque-proxy/README.md +150 -0
  119. package/examples/07-opaque-proxy/package.json +21 -0
  120. package/examples/07-opaque-proxy/src/index.ts +9 -0
  121. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  122. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  123. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  124. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  125. package/examples/09-router-lowering/README.md +287 -0
  126. package/examples/09-router-lowering/package.json +22 -0
  127. package/examples/09-router-lowering/src/index.ts +65 -0
  128. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  129. package/examples/09-router-lowering/tsconfig.json +14 -0
  130. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  131. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  132. package/examples/10-entities-tools/README.md +165 -0
  133. package/examples/10-entities-tools/package.json +21 -0
  134. package/examples/10-entities-tools/src/handlers.ts +15 -0
  135. package/examples/10-entities-tools/src/index.ts +31 -0
  136. package/examples/10-entities-tools/src/schemas.ts +18 -0
  137. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  138. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  139. package/examples/10-entities-tools/tsconfig.json +14 -0
  140. package/examples/11-events/.pulse/.gitignore +4 -0
  141. package/examples/11-events/.pulse/config.ts +24 -0
  142. package/examples/11-events/README.md +194 -0
  143. package/examples/11-events/package.json +20 -0
  144. package/examples/11-events/src/index.ts +27 -0
  145. package/examples/11-events/src/schemas.ts +19 -0
  146. package/examples/11-events/tests/pulse.harness.ts +34 -0
  147. package/examples/11-events/tsconfig.json +15 -0
  148. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  149. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  150. package/examples/12-mcp-proxy/README.md +149 -0
  151. package/examples/12-mcp-proxy/package.json +20 -0
  152. package/examples/12-mcp-proxy/src/index.ts +17 -0
  153. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  154. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  155. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  156. package/examples/13-jwt-es256/README.md +195 -0
  157. package/examples/13-jwt-es256/package.json +21 -0
  158. package/examples/13-jwt-es256/src/index.ts +21 -0
  159. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  160. package/examples/13-jwt-es256/tsconfig.json +14 -0
  161. package/examples/README.md +36 -0
  162. package/package.json +66 -6
  163. package/project-config.schema.json +981 -0
  164. package/release-manifest.json +441 -0
  165. package/src/command-spec.js +279 -0
  166. package/src/completion.js +113 -0
  167. package/src/diagnostics.js +350 -0
  168. package/src/documentation.js +45 -0
  169. package/src/index.d.ts +133 -0
  170. package/src/index.js +15 -0
  171. package/src/internal/command-executor.d.ts +29 -0
  172. package/src/internal/command-executor.js +143 -0
  173. package/src/internal/command-plan.d.ts +9 -0
  174. package/src/internal/command-plan.js +73 -0
  175. package/src/internal/command-reporter.d.ts +15 -0
  176. package/src/internal/command-reporter.js +133 -0
  177. package/src/internal/command-request.d.ts +35 -0
  178. package/src/internal/command-request.js +154 -0
  179. package/src/internal/node-http.js +54 -0
  180. package/src/internal/project-context.d.ts +66 -0
  181. package/src/internal/project-context.js +175 -0
  182. package/src/project-config-reference.js +4 -0
  183. package/src/project-config-schema.d.ts +23 -0
  184. package/src/project-config-schema.js +271 -0
  185. package/src/project-config.js +724 -0
  186. package/src/project-execution.js +2946 -0
  187. package/src/provider-drivers.js +3 -0
  188. package/src/target-support.js +3 -0
  189. package/src/typescript-module-loader.js +269 -0
  190. package/src/workflow.js +78 -0
  191. package/src/workspace.js +82 -0
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const { PulseProjectError } = require('../project-config.js');
5
+ const {
6
+ compileNativeProject,
7
+ buildProject,
8
+ runProjectTests,
9
+ inspectProject,
10
+ doctorProject,
11
+ startDevServer,
12
+ initProject,
13
+ errorSummary
14
+ } = require('../project-execution.js');
15
+ const { normalizeCommandRequest } = require('./command-request.js');
16
+ const { isCommandPlan } = require('./command-plan.js');
17
+ const { resolvedProjectForContext, isProjectContext } = require('./project-context.js');
18
+
19
+ const EXECUTOR_IDS = Object.freeze(['meta', 'init', 'artifact', 'compile', 'build', 'test', 'inspect', 'doctor', 'dev']);
20
+
21
+ const defaultOperations = Object.freeze({
22
+ compileNativeProject,
23
+ buildProject,
24
+ runProjectTests,
25
+ inspectProject,
26
+ doctorProject,
27
+ startDevServer,
28
+ initProject
29
+ });
30
+
31
+ function selectOperations(overrides) {
32
+ if (!overrides) return defaultOperations;
33
+ const selected = {};
34
+ for (const [name, operation] of Object.entries(defaultOperations)) {
35
+ selected[name] = typeof overrides[name] === 'function' ? overrides[name] : operation;
36
+ }
37
+ return Object.freeze(selected);
38
+ }
39
+
40
+ function inspectArtifact(file) {
41
+ if (!fs.existsSync(file)) {
42
+ throw new PulseProjectError('PULSE_INSPECT_TARGET_MISSING', `Inspect target does not exist: ${file}`, { file });
43
+ }
44
+ return Object.freeze({ status: 'ok', file, value: JSON.parse(fs.readFileSync(file, 'utf8')) });
45
+ }
46
+
47
+ function projectForExecution(plan, projectContext) {
48
+ if (plan.mode !== 'project') return undefined;
49
+ if (!isProjectContext(projectContext)) {
50
+ throw new TypeError(`Pulse ${plan.command} project execution requires the authoritative ProjectContext`);
51
+ }
52
+ return resolvedProjectForContext(projectContext);
53
+ }
54
+
55
+ async function executeDev(project, request, operations, options = {}) {
56
+ let eventCount = 0;
57
+ let lastEvent;
58
+ const onEvent = (event) => {
59
+ eventCount += 1;
60
+ lastEvent = event;
61
+ if (typeof options.onEvent === 'function') options.onEvent(event);
62
+ };
63
+ const running = await operations.startDevServer(project, {
64
+ host: request.host,
65
+ port: request.port,
66
+ watch: request.watch,
67
+ once: request.once,
68
+ onEvent
69
+ });
70
+ const signalSource = options.signalSource || process;
71
+ const close = () => {
72
+ if (!running.server.listening) return;
73
+ running.server.close();
74
+ if (typeof running.server.closeIdleConnections === 'function') running.server.closeIdleConnections();
75
+ };
76
+ signalSource.once('SIGINT', close);
77
+ signalSource.once('SIGTERM', close);
78
+ try {
79
+ await running.closed;
80
+ } finally {
81
+ signalSource.removeListener('SIGINT', close);
82
+ signalSource.removeListener('SIGTERM', close);
83
+ }
84
+ return { status: 0, result: { status: 'closed', ready: running.ready, eventCount, lastEvent } };
85
+ }
86
+
87
+ async function executeCommandPlan(plan, requestValue, options = {}) {
88
+ if (!isCommandPlan(plan)) throw new TypeError('Command execution requires a command plan');
89
+ const request = normalizeCommandRequest(requestValue);
90
+ const operations = selectOperations(options.operations);
91
+ const cliVersion = String(options.cliVersion || require('../../package.json').version);
92
+
93
+ if (request.kind === 'meta' || request.help) {
94
+ const invocation = request.kind === 'meta' ? request.invocation : 'help';
95
+ if (invocation === 'help') return { status: 0, help: true };
96
+ if (invocation === 'version') return { status: 0, version: cliVersion };
97
+ return { status: 0, completion: request.shell };
98
+ }
99
+ if (request.command === 'init') {
100
+ return { status: 0, result: operations.initProject(plan.target, { name: request.name, force: request.force }) };
101
+ }
102
+ if (plan.mode === 'artifact') return { status: 0, result: inspectArtifact(plan.artifact) };
103
+
104
+ const project = projectForExecution(plan, options.projectContext);
105
+ if (request.command === 'compile') {
106
+ return {
107
+ status: 0,
108
+ result: operations.compileNativeProject(project, {
109
+ clean: request.clean,
110
+ outDir: request.outDir,
111
+ experimentalNativeSize: request.experimentalNativeSize
112
+ })
113
+ };
114
+ }
115
+ if (request.command === 'build') {
116
+ return {
117
+ status: 0,
118
+ result: operations.buildProject(project, {
119
+ clean: request.clean,
120
+ outDir: request.outDir,
121
+ experimentalNativeSize: request.experimentalNativeSize
122
+ })
123
+ };
124
+ }
125
+ if (request.command === 'test') {
126
+ const result = await operations.runProjectTests(project, { caseName: request.caseName });
127
+ return { status: result.status === 'passed' ? 0 : 1, result };
128
+ }
129
+ if (request.command === 'inspect') return { status: 0, result: operations.inspectProject(project) };
130
+ if (request.command === 'doctor') {
131
+ const result = operations.doctorProject(project, { strict: request.strict });
132
+ return { status: result.status === 'failed' ? 1 : 0, result };
133
+ }
134
+ if (request.command === 'dev') return executeDev(project, request, operations, options);
135
+ throw new PulseProjectError('PULSE_COMMAND_UNIMPLEMENTED', `Pulse command is not implemented: ${request.command}`, { command: request.command });
136
+ }
137
+
138
+ module.exports = Object.freeze({
139
+ EXECUTOR_IDS,
140
+ executeCommandPlan,
141
+ inspectArtifact,
142
+ summarizeExecutionError: errorSummary
143
+ });
@@ -0,0 +1,9 @@
1
+ import type { NormalizedCommandRequest, MetaCommandRequest } from './command-request';
2
+ import type { ProjectContext } from './project-context';
3
+
4
+ export type CommandPlan = Readonly<Record<string, unknown> & { ok: true; command: string }>;
5
+ export declare function createCommandPlan(
6
+ request: NormalizedCommandRequest | MetaCommandRequest | Readonly<Record<string, unknown>>,
7
+ options?: Readonly<{ cwd?: string; version?: string; projectContext?: ProjectContext }>
8
+ ): CommandPlan;
9
+ export declare function isCommandPlan(value: unknown): value is CommandPlan;
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { PulseProjectError } = require('../project-config.js');
5
+ const { normalizeCommandRequest } = require('./command-request.js');
6
+ const { isProjectContext, projectDocumentForContext } = require('./project-context.js');
7
+
8
+ const EXPERIMENTAL_NATIVE_SIZE_PLAN = Object.freeze({
9
+ mode: 'experimental-native-size',
10
+ experimental: true,
11
+ goal: 'size',
12
+ owner: 'native-compiler'
13
+ });
14
+
15
+ function createCommandPlan(requestValue, options = {}) {
16
+ const request = normalizeCommandRequest(requestValue);
17
+ const cwd = path.resolve(options.cwd || process.cwd());
18
+ const version = String(options.version || require('../../package.json').version);
19
+
20
+ if (request.kind === 'meta') {
21
+ if (request.invocation === 'help') return Object.freeze({ ok: true, command: 'help' });
22
+ if (request.invocation === 'version') return Object.freeze({ ok: true, command: 'version', version });
23
+ return Object.freeze({ ok: true, command: 'completion', shell: request.shell, generatedFrom: 'pulse.command-spec' });
24
+ }
25
+ if (request.command === 'init') {
26
+ return Object.freeze({ ok: true, command: 'init', target: path.resolve(cwd, request.target || '.'), backgroundWork: false });
27
+ }
28
+
29
+ if (request.command === 'inspect' && request.artifact) {
30
+ return Object.freeze({ ok: true, command: 'inspect', mode: 'artifact', artifact: path.resolve(cwd, request.artifact), backgroundWork: false });
31
+ }
32
+
33
+ const projectContext = options.projectContext;
34
+ if (!isProjectContext(projectContext)) {
35
+ throw new TypeError(`Pulse ${request.command} planning requires the authoritative ProjectContext`);
36
+ }
37
+ const project = projectDocumentForContext(projectContext);
38
+ if (request.experimentalNativeSize === true && request.command === 'build' && projectContext.target !== 'native') {
39
+ throw new PulseProjectError(
40
+ 'PULSE_EXPERIMENTAL_NATIVE_SIZE_UNSUPPORTED',
41
+ 'The --experimental-native-size flag is available only for Native compilation.',
42
+ {
43
+ target: projectContext.target,
44
+ required: Object.freeze({ target: 'native' })
45
+ }
46
+ );
47
+ }
48
+ return Object.freeze({
49
+ ok: true,
50
+ command: request.command,
51
+ mode: 'project',
52
+ project,
53
+ provider: request.command === 'compile' ? null : projectContext.provider,
54
+ configuredProvider: request.command === 'compile' ? projectContext.provider : undefined,
55
+ buildMode: request.command === 'compile'
56
+ ? 'portable-native-wasm'
57
+ : (request.command === 'build'
58
+ ? ((projectContext.target || project.target || 'native') === 'javascript' ? 'javascript-source-package' : 'native-provider')
59
+ : undefined),
60
+ optimization: request.experimentalNativeSize === true ? EXPERIMENTAL_NATIVE_SIZE_PLAN : undefined,
61
+ backgroundWork: false,
62
+ foregroundServer: request.command === 'dev'
63
+ });
64
+ }
65
+
66
+ function isCommandPlan(value) {
67
+ return Boolean(value && value.ok === true && typeof value.command === 'string');
68
+ }
69
+
70
+ module.exports = Object.freeze({
71
+ createCommandPlan,
72
+ isCommandPlan
73
+ });
@@ -0,0 +1,15 @@
1
+ import type { MetaCommandRequest, NormalizedCommandRequest } from './command-request';
2
+ import type { CommandPlan } from './command-plan';
3
+ import type { CommandExecutionResult } from './command-executor';
4
+
5
+ export interface WritableLike { write(value: string): unknown }
6
+ export interface CommandReporter {
7
+ reportPlan(request: NormalizedCommandRequest, plan: CommandPlan): void;
8
+ reportEvent(request: NormalizedCommandRequest, event: Readonly<Record<string, unknown>>): void;
9
+ reportExecution(request: NormalizedCommandRequest | MetaCommandRequest, plan: CommandPlan, execution: CommandExecutionResult): CommandExecutionResult;
10
+ reportDiagnostic(summary: Readonly<Record<string, unknown>>, context?: Readonly<{ request?: NormalizedCommandRequest | MetaCommandRequest; argv?: readonly string[]; json?: boolean }>): void;
11
+ }
12
+
13
+ export declare function createCommandReporter(options?: Readonly<{ stdout?: WritableLike; stderr?: WritableLike; cliVersion?: string; usageText?: string }>): CommandReporter;
14
+ export declare function writeJson(stream: WritableLike, value: unknown): void;
15
+ export declare function writeHumanResult(stream: WritableLike, command: string, result: any): void;
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ const { renderCompletion } = require('../completion.js');
4
+ const { normalizeCommandRequest } = require('./command-request.js');
5
+
6
+ function writeJson(stream, value) {
7
+ stream.write(`${JSON.stringify(value, null, 2)}\n`);
8
+ }
9
+
10
+ function writeHumanResult(stream, command, result) {
11
+ if (command === 'init') {
12
+ stream.write(`Initialized Pulse project at ${result.root}\n`);
13
+ for (const file of result.files) stream.write(` ${file}\n`);
14
+ if (result.nextSteps) {
15
+ stream.write('Next steps:\n');
16
+ for (const step of result.nextSteps) stream.write(` ${step}\n`);
17
+ }
18
+ return;
19
+ }
20
+ if (command === 'build') {
21
+ stream.write(`Built ${result.project.entryFile} for ${result.provider}\n`);
22
+ stream.write(` ${result.files.manifest}\n`);
23
+ stream.write(` ${result.files.handler}\n`);
24
+ if (result.files.nativeWasm) stream.write(` ${result.files.nativeWasm}\n`);
25
+ if (result.files.providerWasm) stream.write(` ${result.files.providerWasm}\n`);
26
+ if (result.manifest && result.manifest.providerTarget && result.manifest.providerTarget.optimization && result.manifest.providerTarget.optimization.experimental) {
27
+ stream.write(' experimental optimization: native-size (compiler-owned profile)\n');
28
+ }
29
+ return;
30
+ }
31
+ if (command === 'compile') {
32
+ stream.write(`Compiled ${result.project.entryFile} to provider-neutral Pulse Wasm\n`);
33
+ stream.write(` ${result.files.nativeWasm}\n`);
34
+ stream.write(` ${result.files.manifest}\n`);
35
+ if (result.native && result.native.optimization && result.native.optimization.experimental) {
36
+ stream.write(' experimental optimization: native-size (compiler-owned profile)\n');
37
+ }
38
+ return;
39
+ }
40
+ if (command === 'test') {
41
+ for (const entry of result.cases || []) stream.write(`${entry.status === 'passed' ? 'ok' : 'not ok'} - ${entry.name}\n`);
42
+ stream.write(`${result.summary.passed}/${result.summary.total} Pulse project tests passed\n`);
43
+ return;
44
+ }
45
+ if (command === 'inspect') {
46
+ stream.write(`${result.project.entryFile}\n`);
47
+ stream.write(` workspace: ${result.project.workspace ? result.project.workspace.root : result.project.root}\n`);
48
+ if (result.project.selectedProfile) stream.write(` profile: ${result.project.selectedProfile.name} (${result.project.selectedProfile.source})\n`);
49
+ stream.write(` target: ${result.project.target || 'native'}\n`);
50
+ stream.write(` provider: ${result.project.provider}\n`);
51
+ stream.write(` capabilities: ${(result.compiler.capabilities || []).join(', ') || 'none'}\n`);
52
+ stream.write(` effects: ${result.compiler.effectCount}\n`);
53
+ stream.write(` continuations: ${result.compiler.continuationCount}\n`);
54
+ return;
55
+ }
56
+ if (command === 'doctor') {
57
+ for (const check of result.checks) stream.write(`${check.status.padEnd(7)} ${check.id}: ${check.message}\n`);
58
+ for (const entry of result.checks.filter((check) => check.status !== 'passed' && check.remediation)) {
59
+ for (const step of entry.remediation) stream.write(` help: ${step}\n`);
60
+ }
61
+ stream.write(`doctor: ${result.status}\n`);
62
+ }
63
+ }
64
+
65
+ function createCommandReporter(options = {}) {
66
+ const stdout = options.stdout || process.stdout;
67
+ const stderr = options.stderr || process.stderr;
68
+ const cliVersion = String(options.cliVersion || require('../../package.json').version);
69
+ const usageText = String(options.usageText || '');
70
+
71
+ function reportPlan(requestValue, plan) {
72
+ const request = normalizeCommandRequest(requestValue);
73
+ if (request.kind !== 'command') return;
74
+ if (request.json) writeJson(stdout, { status: 'ok', version: cliVersion, plan });
75
+ else stdout.write(`${plan.command}: ${plan.mode || 'ready'}\n`);
76
+ }
77
+
78
+ function reportEvent(requestValue, event) {
79
+ const request = normalizeCommandRequest(requestValue);
80
+ if (request.kind !== 'command') return;
81
+ if (request.json) stdout.write(`${JSON.stringify(event)}\n`);
82
+ else if (event.event === 'ready') stdout.write(`pulse dev: ${event.url}\n`);
83
+ else if (event.event === 'reloaded') stdout.write(`reloaded ${event.entry}\n`);
84
+ else if (event.event === 'compile-error') stderr.write(`compile error: ${event.error.message}\n`);
85
+ else if (event.event === 'request-error') stderr.write(`request error: ${event.error.message}\n`);
86
+ }
87
+
88
+ function reportExecution(requestValue, plan, execution) {
89
+ const request = normalizeCommandRequest(requestValue);
90
+ if (request.kind === 'meta' || request.help) {
91
+ const invocation = request.kind === 'meta' ? request.invocation : 'help';
92
+ if (invocation === 'help') stdout.write(usageText);
93
+ else if (invocation === 'version') stdout.write(`${execution.version}\n`);
94
+ else {
95
+ const completion = renderCompletion(request.shell);
96
+ stdout.write(completion);
97
+ return { ...execution, bytes: Buffer.byteLength(completion) };
98
+ }
99
+ return execution;
100
+ }
101
+ if (plan.mode === 'artifact') {
102
+ if (request.json) writeJson(stdout, execution.result);
103
+ else stdout.write(`${execution.result.file}: ok\n`);
104
+ return execution;
105
+ }
106
+ if (request.command === 'dev') return execution;
107
+ if (request.json) writeJson(stdout, execution.result);
108
+ else writeHumanResult(stdout, request.command, execution.result);
109
+ return execution;
110
+ }
111
+
112
+ function reportDiagnostic(summary, context = {}) {
113
+ const request = context.request;
114
+ const wantsJson = Boolean(context.json)
115
+ || Boolean(request && request.kind === 'command' && request.json)
116
+ || Array.from(context.argv || []).includes('--json');
117
+ if (wantsJson) {
118
+ writeJson(stderr, { status: 'error', version: cliVersion, error: summary });
119
+ return;
120
+ }
121
+ stderr.write(`pulse: ${summary.code ? `${summary.code}: ` : ''}${summary.message}\n`);
122
+ for (const step of summary.remediation || []) stderr.write(` help: ${step}\n`);
123
+ if (summary.docs) stderr.write(` docs: ${summary.docs}\n`);
124
+ }
125
+
126
+ return Object.freeze({ reportPlan, reportEvent, reportExecution, reportDiagnostic });
127
+ }
128
+
129
+ module.exports = Object.freeze({
130
+ createCommandReporter,
131
+ writeJson,
132
+ writeHumanResult
133
+ });
@@ -0,0 +1,35 @@
1
+ export type PulseCliCommand = 'init' | 'doctor' | 'inspect' | 'test' | 'dev' | 'compile' | 'build';
2
+
3
+ export interface CommonNormalizedRequest {
4
+ readonly kind: 'command';
5
+ readonly command: PulseCliCommand;
6
+ readonly json: boolean;
7
+ readonly dryRun: boolean;
8
+ readonly help?: boolean;
9
+ }
10
+
11
+ export type MetaCommandRequest =
12
+ | Readonly<{ kind: 'meta'; invocation: 'help' }>
13
+ | Readonly<{ kind: 'meta'; invocation: 'version' }>
14
+ | Readonly<{ kind: 'meta'; invocation: 'completion'; shell: 'bash' | 'zsh' | 'fish' }>;
15
+
16
+ export type NormalizedCommandRequest = Readonly<CommonNormalizedRequest & {
17
+ readonly directory?: string;
18
+ readonly profile?: string;
19
+ readonly target?: string;
20
+ readonly name?: string;
21
+ readonly force?: boolean;
22
+ readonly strict?: boolean;
23
+ readonly artifact?: string;
24
+ readonly caseName?: string;
25
+ readonly host?: string;
26
+ readonly port?: number;
27
+ readonly watch?: boolean;
28
+ readonly once?: boolean;
29
+ readonly outDir?: string;
30
+ readonly clean?: boolean;
31
+ readonly experimentalNativeSize?: boolean;
32
+ }>;
33
+
34
+ export declare function parseCommandRequest(argv: readonly string[]): NormalizedCommandRequest | MetaCommandRequest;
35
+ export declare function normalizeCommandRequest(value: readonly string[] | Readonly<Record<string, unknown>>): NormalizedCommandRequest | MetaCommandRequest;
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ const { PulseProjectError } = require('../project-config.js');
4
+ const { COMPLETION_SHELLS } = require('../completion.js');
5
+ const {
6
+ COMMANDS,
7
+ COMMAND_SPECS,
8
+ optionForToken,
9
+ optionAllowed,
10
+ publicOptionsForCommand
11
+ } = require('../command-spec.js');
12
+
13
+ function deepFreeze(value) {
14
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
15
+ for (const child of Object.values(value)) deepFreeze(child);
16
+ return Object.freeze(value);
17
+ }
18
+
19
+ function metaRequest(invocation, detail = {}) {
20
+ return deepFreeze({ kind: 'meta', invocation, ...detail });
21
+ }
22
+
23
+ function baseCommandRequest(command) {
24
+ const request = { kind: 'command', command, json: false, dryRun: false };
25
+ if (command === 'compile' || command === 'build') request.clean = true;
26
+ return request;
27
+ }
28
+
29
+ function allowedRequestKeys(command) {
30
+ const keys = new Set(['command', 'json', 'dryRun', 'help']);
31
+ for (const option of publicOptionsForCommand(command)) keys.add(option.key);
32
+ for (const positional of COMMAND_SPECS[command].positionals) keys.add(positional.key);
33
+ return keys;
34
+ }
35
+
36
+ function finishCommandRequest(request) {
37
+ const allowed = allowedRequestKeys(request.command);
38
+ for (const key of Object.keys(request)) {
39
+ if (key === 'kind') continue;
40
+ if (!allowed.has(key)) throw new Error(`CLI request normalization leaked unsupported field ${request.command}.${key}`);
41
+ }
42
+ return deepFreeze(request);
43
+ }
44
+
45
+ function parseCommandRequest(argv) {
46
+ const input = Array.from(argv || []);
47
+ if (input.length === 0 || ['help', '--help', '-h'].includes(input[0])) return metaRequest('help');
48
+ if (['--version', '-v', 'version'].includes(input[0])) return metaRequest('version');
49
+ if (String(input[0]).toLowerCase() === 'completion') {
50
+ input.shift();
51
+ const shell = input.shift();
52
+ if (!shell || input.length > 0 || !COMPLETION_SHELLS.includes(String(shell).toLowerCase())) {
53
+ throw new PulseProjectError('PULSE_ARGUMENT_UNEXPECTED', 'Usage: pulse completion <bash|zsh|fish>.', { shell, allowed: COMPLETION_SHELLS });
54
+ }
55
+ return metaRequest('completion', { shell: String(shell).toLowerCase() });
56
+ }
57
+
58
+ const command = String(input.shift()).toLowerCase();
59
+ if (!COMMANDS.includes(command)) {
60
+ throw new PulseProjectError('PULSE_COMMAND_UNKNOWN', `Unknown pulse command "${command}".`, { command, allowed: COMMANDS });
61
+ }
62
+
63
+ const request = baseCommandRequest(command);
64
+ for (let index = 0; index < input.length; index += 1) {
65
+ const token = input[index];
66
+ const next = () => {
67
+ const value = input[++index];
68
+ if (value === undefined) throw new PulseProjectError('PULSE_ARGUMENT_MISSING', `Missing value after ${token}.`, { token });
69
+ return value;
70
+ };
71
+
72
+ if (token === '--provider' || String(token).startsWith('--provider=')) {
73
+ throw new PulseProjectError(
74
+ 'PULSE_PROVIDER_FLAG_REMOVED',
75
+ 'The public --provider override was removed. Configure the deployment provider in .pulse/config.ts; pulse compile is always provider-neutral and pulse build realizes the configured provider.',
76
+ { token, command, replacement: command === 'compile' ? 'pulse compile' : 'active profile in .pulse/config.ts' }
77
+ );
78
+ }
79
+ if (token === '--source-only') {
80
+ throw new PulseProjectError(
81
+ 'PULSE_SOURCE_ONLY_REMOVED',
82
+ 'The --source-only build mode was removed. Native builds always emit generated source and provider Wasm together.',
83
+ { token, command, replacement: 'pulse build' }
84
+ );
85
+ }
86
+
87
+ const match = optionForToken(token);
88
+ if (match) {
89
+ const { option, flag } = match;
90
+ if (!optionAllowed(option, command)) {
91
+ throw new PulseProjectError('PULSE_ARGUMENT_UNEXPECTED', `Unexpected pulse ${command} argument: ${token}`, { token, command });
92
+ }
93
+ let value;
94
+ if (option.kind === 'boolean') value = true;
95
+ else if (option.kind === 'boolean-value') value = flag.value;
96
+ else {
97
+ const raw = token === flag.name ? next() : token.slice(flag.name.length + 1);
98
+ if (raw === '') throw new PulseProjectError('PULSE_ARGUMENT_MISSING', `Missing value after ${flag.name}.`, { token: flag.name });
99
+ value = option.parse === 'number' ? Number(raw) : raw;
100
+ }
101
+ request[option.key] = value;
102
+ continue;
103
+ }
104
+
105
+ if (!token.startsWith('-')) {
106
+ if (command === 'init' && !request.target) {
107
+ request.target = token;
108
+ continue;
109
+ }
110
+ if (command !== 'init' && !request.directory) {
111
+ request.directory = token;
112
+ continue;
113
+ }
114
+ }
115
+ throw new PulseProjectError('PULSE_ARGUMENT_UNEXPECTED', `Unexpected pulse ${command} argument: ${token}`, { token, command });
116
+ }
117
+
118
+ return finishCommandRequest(request);
119
+ }
120
+
121
+ function isCommandRequest(value) {
122
+ return Boolean(value && value.kind === 'command' && COMMANDS.includes(value.command));
123
+ }
124
+
125
+ function isMetaRequest(value) {
126
+ return Boolean(value && value.kind === 'meta' && ['help', 'version', 'completion'].includes(value.invocation));
127
+ }
128
+
129
+ function normalizeCommandRequest(value) {
130
+ if (Array.isArray(value)) return parseCommandRequest(value);
131
+ if (isCommandRequest(value) || isMetaRequest(value)) return value;
132
+ if (!value || typeof value !== 'object') throw new TypeError('CLI command request must be argv or a parsed request object');
133
+
134
+ if (value.help && !value.command) return metaRequest('help');
135
+ if (value.version && !value.command) return metaRequest('version');
136
+ if (value.completion && !value.command) return metaRequest('completion', { shell: String(value.completion).toLowerCase() });
137
+ if (!COMMANDS.includes(value.command)) {
138
+ throw new PulseProjectError('PULSE_COMMAND_UNKNOWN', `Unknown pulse command "${value.command}".`, { command: value.command, allowed: COMMANDS });
139
+ }
140
+
141
+ const request = baseCommandRequest(value.command);
142
+ for (const key of allowedRequestKeys(value.command)) {
143
+ if (key === 'command') continue;
144
+ if (Object.prototype.hasOwnProperty.call(value, key) && value[key] !== undefined) request[key] = value[key];
145
+ }
146
+ return finishCommandRequest(request);
147
+ }
148
+
149
+ module.exports = deepFreeze({
150
+ parseCommandRequest,
151
+ normalizeCommandRequest,
152
+ isCommandRequest,
153
+ isMetaRequest
154
+ });
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ function normalizeHeaders(headers) {
4
+ if (!headers) return [];
5
+ if (Array.isArray(headers)) return headers.map((pair) => [String(pair[0]), String(pair[1])]);
6
+ if (typeof headers[Symbol.iterator] === 'function') {
7
+ return Array.from(headers, ([name, value]) => [String(name), String(value)]);
8
+ }
9
+ return Object.entries(headers).map(([name, value]) => [String(name), String(value)]);
10
+ }
11
+
12
+ function groupHeaders(headers) {
13
+ const order = [];
14
+ const groups = new Map();
15
+ for (const [name, value] of normalizeHeaders(headers)) {
16
+ const display = String(name);
17
+ const lower = display.toLowerCase();
18
+ if (!groups.has(lower)) {
19
+ groups.set(lower, { name: display, values: [] });
20
+ order.push(lower);
21
+ }
22
+ groups.get(lower).values.push(String(value));
23
+ }
24
+ return order.map((key) => groups.get(key));
25
+ }
26
+
27
+ function writeNodeHttpResponse(response, hostResult) {
28
+ const result = hostResult || { status: 500, kind: 'empty', headers: [] };
29
+ response.statusCode = result.status || 500;
30
+ for (const group of groupHeaders(result.headers || [])) {
31
+ response.setHeader(group.name, group.values.length === 1 ? group.values[0] : group.values.slice());
32
+ }
33
+ if (result.kind === 'stream' && result.bodyStream) {
34
+ if (typeof result.bodyStream.pipe === 'function') {
35
+ result.bodyStream.pipe(response);
36
+ return result;
37
+ }
38
+ if (Array.isArray(result.bodyStream.chunks)) {
39
+ for (const chunk of result.bodyStream.chunks) {
40
+ response.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
41
+ }
42
+ response.end();
43
+ return result;
44
+ }
45
+ if (typeof result.bodyStream === 'string' || Buffer.isBuffer(result.bodyStream)) {
46
+ response.end(result.bodyStream);
47
+ return result;
48
+ }
49
+ }
50
+ response.end(result.body === undefined || result.body === null ? '' : String(result.body));
51
+ return result;
52
+ }
53
+
54
+ module.exports = Object.freeze({ groupHeaders, writeNodeHttpResponse });