@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.
- package/API.md +539 -0
- package/CHANGELOG.md +47 -0
- package/README.md +70 -1
- package/bin/pulse.js +15 -0
- package/bin/pulsewasm-extract.js +22 -0
- package/cli-spec.json +575 -0
- package/completions/_pulse +106 -0
- package/completions/pulse.bash +31 -0
- package/completions/pulse.fish +76 -0
- package/docs/README.md +77 -0
- package/docs/architecture/current-contracts.md +381 -0
- package/docs/architecture/overview.md +93 -0
- package/docs/architecture/vision.md +515 -0
- package/docs/concepts/bodies.md +186 -0
- package/docs/concepts/compilation-and-lowering.md +496 -0
- package/docs/concepts/contracts-and-providers.md +158 -0
- package/docs/concepts/effects-and-continuations.md +243 -0
- package/docs/concepts/entities-and-adapters.md +71 -0
- package/docs/concepts/package-owned-lowering.md +175 -0
- package/docs/concepts/targets-and-hosts.md +62 -0
- package/docs/contributing/README.md +38 -0
- package/docs/contributing/adding-core-provider.md +315 -0
- package/docs/contributing/adding-first-party-lowerer.md +327 -0
- package/docs/contributing/entities-lowering.md +91 -0
- package/docs/contributing/package-lowerer-contract.md +383 -0
- package/docs/contributing/pulse-aware-packages.md +149 -0
- package/docs/examples.md +112 -0
- package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
- package/docs/getting-started.md +128 -0
- package/docs/guides/compatibility-imports.md +60 -0
- package/docs/guides/deploying-fastly.md +188 -0
- package/docs/guides/deploying-node.md +143 -0
- package/docs/guides/events.md +233 -0
- package/docs/guides/fastly-capabilities.md +153 -0
- package/docs/guides/fetching-and-composition.md +101 -0
- package/docs/guides/grip.md +94 -0
- package/docs/guides/json-schemas.md +233 -0
- package/docs/guides/migrating-from-express.md +192 -0
- package/docs/guides/project-lifecycle.md +143 -0
- package/docs/guides/routing.md +184 -0
- package/docs/guides/troubleshooting.md +47 -0
- package/docs/maintainers/README.md +60 -0
- package/docs/maintainers/codex-maintainer.md +71 -0
- package/docs/maintainers/documentation-deployment.md +147 -0
- package/docs/maintainers/documentation-system.md +58 -0
- package/docs/maintainers/documentation-versioning.md +65 -0
- package/docs/maintainers/maintainer-charter.md +84 -0
- package/docs/maintainers/maintenance-policy.json +778 -0
- package/docs/maintainers/maintenance-policy.md +124 -0
- package/docs/maintainers/npm-publishing.md +154 -0
- package/docs/maintainers/plugin-readiness.json +19 -0
- package/docs/maintainers/plugin-readiness.md +31 -0
- package/docs/maintainers/public-site.md +158 -0
- package/docs/maintainers/release-acceptance.md +250 -0
- package/docs/maintainers/release-manifest.md +88 -0
- package/docs/maintainers/repository-setup.md +145 -0
- package/docs/maintainers/scope-policy.md +93 -0
- package/docs/maintainers/support-and-triage.md +74 -0
- package/docs/maintainers/testing.md +288 -0
- package/docs/packages/README.md +59 -0
- package/docs/packages/assets.md +85 -0
- package/docs/packages/cli.md +190 -0
- package/docs/packages/crypto.md +71 -0
- package/docs/packages/entities.md +129 -0
- package/docs/packages/grip.md +82 -0
- package/docs/packages/implementation-packages.md +113 -0
- package/docs/packages/jwt.md +55 -0
- package/docs/packages/provider-fastly.md +217 -0
- package/docs/packages/pulse.md +159 -0
- package/docs/packages/runtime.md +256 -0
- package/docs/preview-scope.md +142 -0
- package/docs/reference/README.md +33 -0
- package/docs/reference/cli-spec.json +575 -0
- package/docs/reference/cli.md +457 -0
- package/docs/reference/compatibility-matrix.md +169 -0
- package/docs/reference/diagnostics.md +1872 -0
- package/docs/reference/documentation-versions.json +14 -0
- package/docs/reference/environment.md +347 -0
- package/docs/reference/handler-authoring.md +264 -0
- package/docs/reference/project-config.md +554 -0
- package/docs/reference/project-config.schema.json +918 -0
- package/docs/reference/release-manifest.json +427 -0
- package/docs/reference/shell-completion.md +23 -0
- package/documentation-site.json +661 -0
- package/documentation-versions.json +14 -0
- package/examples/01-hello-json/.pulse/.gitignore +3 -0
- package/examples/01-hello-json/.pulse/config.ts +16 -0
- package/examples/01-hello-json/README.md +119 -0
- package/examples/01-hello-json/package.json +20 -0
- package/examples/01-hello-json/src/index.ts +9 -0
- package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
- package/examples/01-hello-json/tsconfig.json +14 -0
- package/examples/02-request-schema/.pulse/.gitignore +3 -0
- package/examples/02-request-schema/.pulse/config.ts +17 -0
- package/examples/02-request-schema/README.md +145 -0
- package/examples/02-request-schema/package.json +20 -0
- package/examples/02-request-schema/src/index.ts +18 -0
- package/examples/02-request-schema/src/schemas.ts +20 -0
- package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
- package/examples/02-request-schema/tsconfig.json +14 -0
- package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
- package/examples/03-fetch-composition/.pulse/config.ts +31 -0
- package/examples/03-fetch-composition/README.md +210 -0
- package/examples/03-fetch-composition/package.json +20 -0
- package/examples/03-fetch-composition/src/index.ts +60 -0
- package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
- package/examples/03-fetch-composition/tsconfig.json +14 -0
- package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
- package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
- package/examples/05-fastly-capabilities/README.md +257 -0
- package/examples/05-fastly-capabilities/package.json +22 -0
- package/examples/05-fastly-capabilities/src/index.ts +55 -0
- package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
- package/examples/05-fastly-capabilities/tsconfig.json +14 -0
- package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
- package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
- package/examples/07-opaque-proxy/README.md +150 -0
- package/examples/07-opaque-proxy/package.json +21 -0
- package/examples/07-opaque-proxy/src/index.ts +9 -0
- package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
- package/examples/07-opaque-proxy/tsconfig.json +14 -0
- package/examples/09-router-lowering/.pulse/.gitignore +3 -0
- package/examples/09-router-lowering/.pulse/config.ts +22 -0
- package/examples/09-router-lowering/README.md +287 -0
- package/examples/09-router-lowering/package.json +22 -0
- package/examples/09-router-lowering/src/index.ts +65 -0
- package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
- package/examples/09-router-lowering/tsconfig.json +14 -0
- package/examples/10-entities-tools/.pulse/.gitignore +4 -0
- package/examples/10-entities-tools/.pulse/config.ts +23 -0
- package/examples/10-entities-tools/README.md +165 -0
- package/examples/10-entities-tools/package.json +21 -0
- package/examples/10-entities-tools/src/handlers.ts +15 -0
- package/examples/10-entities-tools/src/index.ts +31 -0
- package/examples/10-entities-tools/src/schemas.ts +18 -0
- package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
- package/examples/10-entities-tools/tools-facade.cjs +158 -0
- package/examples/10-entities-tools/tsconfig.json +14 -0
- package/examples/11-events/.pulse/.gitignore +4 -0
- package/examples/11-events/.pulse/config.ts +24 -0
- package/examples/11-events/README.md +194 -0
- package/examples/11-events/package.json +20 -0
- package/examples/11-events/src/index.ts +27 -0
- package/examples/11-events/src/schemas.ts +19 -0
- package/examples/11-events/tests/pulse.harness.ts +34 -0
- package/examples/11-events/tsconfig.json +15 -0
- package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
- package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
- package/examples/12-mcp-proxy/README.md +149 -0
- package/examples/12-mcp-proxy/package.json +20 -0
- package/examples/12-mcp-proxy/src/index.ts +17 -0
- package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
- package/examples/12-mcp-proxy/tsconfig.json +14 -0
- package/examples/13-jwt-es256/.pulse/config.ts +19 -0
- package/examples/13-jwt-es256/README.md +195 -0
- package/examples/13-jwt-es256/package.json +21 -0
- package/examples/13-jwt-es256/src/index.ts +21 -0
- package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
- package/examples/13-jwt-es256/tsconfig.json +14 -0
- package/examples/README.md +36 -0
- package/package.json +66 -6
- package/project-config.schema.json +918 -0
- package/release-manifest.json +427 -0
- package/src/command-spec.js +279 -0
- package/src/completion.js +113 -0
- package/src/diagnostics.js +350 -0
- package/src/documentation.js +45 -0
- package/src/index.d.ts +133 -0
- package/src/index.js +15 -0
- package/src/internal/command-executor.d.ts +29 -0
- package/src/internal/command-executor.js +143 -0
- package/src/internal/command-plan.d.ts +9 -0
- package/src/internal/command-plan.js +73 -0
- package/src/internal/command-reporter.d.ts +15 -0
- package/src/internal/command-reporter.js +133 -0
- package/src/internal/command-request.d.ts +35 -0
- package/src/internal/command-request.js +154 -0
- package/src/internal/node-http.js +54 -0
- package/src/internal/project-context.d.ts +66 -0
- package/src/internal/project-context.js +175 -0
- package/src/project-config-reference.js +4 -0
- package/src/project-config-schema.d.ts +23 -0
- package/src/project-config-schema.js +271 -0
- package/src/project-config.js +724 -0
- package/src/project-execution.js +2930 -0
- package/src/provider-drivers.js +3 -0
- package/src/target-support.js +3 -0
- package/src/typescript-module-loader.js +269 -0
- package/src/workflow.js +78 -0
- package/src/workspace.js +82 -0
|
@@ -0,0 +1,2930 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { once } = require('node:events');
|
|
9
|
+
const {
|
|
10
|
+
loadCanonicalModule,
|
|
11
|
+
writeCanonicalBuild
|
|
12
|
+
} = require('@pulse-compute/wasm-compiler/canonical-api-compiler');
|
|
13
|
+
const {
|
|
14
|
+
compileCanonicalProject,
|
|
15
|
+
packageRealizationArtifactsForCompiled
|
|
16
|
+
} = require('@pulse-compute/wasm-compiler/canonical-project-compiler');
|
|
17
|
+
const { createCanonicalSchemaCodecs } = require('@pulse-compute/wasm-schema-json/compiler/canonical-schema-codecs');
|
|
18
|
+
const { buildJavascriptApplicationPlan } = require('@pulse-compute/wasm-compiler/javascript-application-plan');
|
|
19
|
+
const { buildCanonicalNativePlan } = require('@pulse-compute/wasm-compiler/canonical-native-plan');
|
|
20
|
+
const {
|
|
21
|
+
compileCanonicalNativePlan,
|
|
22
|
+
writeCanonicalNativeModule
|
|
23
|
+
} = require('@pulse-compute/wasm-compiler/canonical-native-compiler');
|
|
24
|
+
const {
|
|
25
|
+
executeCanonicalNativeModule
|
|
26
|
+
} = require('@pulse-compute/wasm-host-runtime/runtime/canonical-native-host');
|
|
27
|
+
const { PulseProjectError, projectJson, relative } = require('./project-config.js');
|
|
28
|
+
const { describeDiagnostic, decorateDiagnostic, httpStatusForDiagnostic } = require('./diagnostics.js');
|
|
29
|
+
const {
|
|
30
|
+
getProviderDriver,
|
|
31
|
+
getProviderTargetDescriptor,
|
|
32
|
+
getProviderTargetSupportDeclaration,
|
|
33
|
+
providerTargetAvailability,
|
|
34
|
+
providerIds
|
|
35
|
+
} = require('./provider-drivers.js');
|
|
36
|
+
const {
|
|
37
|
+
buildJavascriptTargetSupportEvidence,
|
|
38
|
+
buildEventTargetSupportEvidence
|
|
39
|
+
} = require('./target-support.js');
|
|
40
|
+
const eventContracts = require(path.join(
|
|
41
|
+
path.dirname(require.resolve('@pulse-compute/wasm-contracts')),
|
|
42
|
+
'events',
|
|
43
|
+
'contracts.js'
|
|
44
|
+
));
|
|
45
|
+
const {
|
|
46
|
+
PROVIDER_TARGET_INVOCATION_VERSION,
|
|
47
|
+
PROVIDER_PLAN_INPUT_VERSION,
|
|
48
|
+
createProviderNativeArtifact,
|
|
49
|
+
createProviderPlanInput,
|
|
50
|
+
createProviderTargetInvocation,
|
|
51
|
+
normalizeProviderTargetResult,
|
|
52
|
+
projectProviderRequirements
|
|
53
|
+
} = require('@pulse-compute/wasm-contracts/provider/toolchain');
|
|
54
|
+
const { loadJavascriptApplication } = require('./typescript-module-loader.js');
|
|
55
|
+
const { writeNodeHttpResponse } = require('./internal/node-http.js');
|
|
56
|
+
const {
|
|
57
|
+
HANDLER_AUTHORING_MODES
|
|
58
|
+
} = require('@pulse-compute/wasm-contracts/handler/surface-contract');
|
|
59
|
+
const {
|
|
60
|
+
CANONICAL_PACKAGE_INSPECTION_VERSION
|
|
61
|
+
} = require('@pulse-compute/wasm-contracts/package/package-contract');
|
|
62
|
+
|
|
63
|
+
const PROJECT_EXECUTION_VERSION = 'pulse.project-execution.v10';
|
|
64
|
+
const EVENT_INSPECTION_VERSION = 'pulse.event-inspection.v1';
|
|
65
|
+
const BUILD_MANIFEST = 'pulse-build.json';
|
|
66
|
+
const COMPILE_MANIFEST = 'pulse-compile.json';
|
|
67
|
+
const cliPackageVersion = require('../package.json').version;
|
|
68
|
+
const releaseCatalog = require('../release-manifest.json');
|
|
69
|
+
const releaseNode = releaseCatalog.publication;
|
|
70
|
+
|
|
71
|
+
function nodeVersionSatisfiesEngines(version, engines) {
|
|
72
|
+
const actual = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version));
|
|
73
|
+
if (!actual) return false;
|
|
74
|
+
const actualParts = actual.slice(1).map((part) => Number.parseInt(part, 10));
|
|
75
|
+
return String(engines).split(/\s*\|\|\s*/).some((range) => {
|
|
76
|
+
const minimum = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range);
|
|
77
|
+
if (!minimum) return false;
|
|
78
|
+
const minimumParts = minimum.slice(1).map((part) => Number.parseInt(part, 10));
|
|
79
|
+
if (actualParts[0] !== minimumParts[0]) return false;
|
|
80
|
+
for (let index = 1; index < 3; index += 1) {
|
|
81
|
+
if (actualParts[index] !== minimumParts[index]) return actualParts[index] > minimumParts[index];
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stableJson(value) {
|
|
88
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writePackageInspectionArtifacts(compiled, outDir) {
|
|
92
|
+
const inspection = compiled && compiled.packageInspection;
|
|
93
|
+
const artifacts = inspection && Array.isArray(inspection.artifacts) ? inspection.artifacts : [];
|
|
94
|
+
const files = artifacts.map((artifact) => {
|
|
95
|
+
const relativeFile = String(artifact.file || '').replace(/\\/g, '/');
|
|
96
|
+
const target = path.resolve(outDir, relativeFile);
|
|
97
|
+
const relativeTarget = path.relative(path.resolve(outDir), target);
|
|
98
|
+
if (!relativeFile || relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget)) {
|
|
99
|
+
throw new PulseProjectError(
|
|
100
|
+
'PULSE_PACKAGE_INSPECTION_ARTIFACT_UNSAFE',
|
|
101
|
+
`Package inspection artifact ${artifact.id || '<unknown>'} must remain inside the build output.`,
|
|
102
|
+
{ id: artifact.id, file: relativeFile }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
106
|
+
const bytes = stableJson(artifact.data);
|
|
107
|
+
fs.writeFileSync(target, bytes);
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
id: artifact.id,
|
|
110
|
+
contractId: artifact.contractId,
|
|
111
|
+
kind: artifact.kind,
|
|
112
|
+
mediaType: artifact.mediaType,
|
|
113
|
+
file: relativeFile,
|
|
114
|
+
path: target,
|
|
115
|
+
sha256: crypto.createHash('sha256').update(bytes).digest('hex')
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
version: inspection && inspection.version || CANONICAL_PACKAGE_INSPECTION_VERSION,
|
|
120
|
+
artifacts: Object.freeze(files),
|
|
121
|
+
summary: inspection && inspection.summary || Object.freeze({ packages: 0, artifacts: 0, declaredHandlers: 0, handlerEffects: 0 })
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function packageInspectionManifest(written) {
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
version: written.version,
|
|
128
|
+
artifacts: Object.freeze(written.artifacts.map((artifact) => Object.freeze({
|
|
129
|
+
id: artifact.id,
|
|
130
|
+
contractId: artifact.contractId,
|
|
131
|
+
kind: artifact.kind,
|
|
132
|
+
mediaType: artifact.mediaType,
|
|
133
|
+
file: artifact.file,
|
|
134
|
+
sha256: artifact.sha256
|
|
135
|
+
}))),
|
|
136
|
+
summary: written.summary
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function selectedEventTargetSupport(project, compiled, descriptor = targetDescriptor(project)) {
|
|
141
|
+
const driver = providerDriver(project);
|
|
142
|
+
return buildEventTargetSupportEvidence(compiled, {
|
|
143
|
+
provider: project.provider,
|
|
144
|
+
target: project.target || 'native',
|
|
145
|
+
descriptor,
|
|
146
|
+
eventTestTargets: driver.events && driver.events.targets || []
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sortedUnique(values) {
|
|
151
|
+
return Object.freeze([...new Set((values || []).filter((value) => value !== null && value !== undefined).map(String))].sort());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function eventInspectionProjection(project, compiled, support = selectedEventTargetSupport(project, compiled)) {
|
|
155
|
+
if (!support) return null;
|
|
156
|
+
const catalog = compiled.eventCatalog || compiled.metadata && compiled.metadata.events && compiled.metadata.events.catalog;
|
|
157
|
+
const outbound = compiled.eventOutboundRequirements || Object.freeze({
|
|
158
|
+
version: compiled.metadata.events.outboundRequirementsVersion,
|
|
159
|
+
callsites: Object.freeze([]),
|
|
160
|
+
capabilities: Object.freeze([]),
|
|
161
|
+
hostRequirements: Object.freeze([]),
|
|
162
|
+
summary: Object.freeze({ callsites: 0, schemaBound: 0, noPayload: 0, grouped: 0, handlers: 0 })
|
|
163
|
+
});
|
|
164
|
+
const registrations = catalog && catalog.events || [];
|
|
165
|
+
const callsites = outbound.callsites || [];
|
|
166
|
+
const capabilities = sortedUnique([
|
|
167
|
+
...registrations.flatMap((event) => event.capabilities || []),
|
|
168
|
+
...(outbound.capabilities || [])
|
|
169
|
+
]);
|
|
170
|
+
const hostRequirements = sortedUnique([
|
|
171
|
+
...registrations.flatMap((event) => event.hostRequirements || []),
|
|
172
|
+
...(outbound.hostRequirements || [])
|
|
173
|
+
]);
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
version: EVENT_INSPECTION_VERSION,
|
|
176
|
+
contractId: eventContracts.EVENT_CONTRACT_ID,
|
|
177
|
+
catalog,
|
|
178
|
+
outboundRequirements: outbound,
|
|
179
|
+
registrations: Object.freeze(registrations),
|
|
180
|
+
callsites: Object.freeze(callsites),
|
|
181
|
+
schemas: Object.freeze({
|
|
182
|
+
registrations: sortedUnique(registrations.map((event) => event.schemaId)),
|
|
183
|
+
emissions: sortedUnique(callsites.map((callsite) => callsite.schemaId)),
|
|
184
|
+
all: sortedUnique([
|
|
185
|
+
...registrations.map((event) => event.schemaId),
|
|
186
|
+
...callsites.map((callsite) => callsite.schemaId)
|
|
187
|
+
])
|
|
188
|
+
}),
|
|
189
|
+
capabilities,
|
|
190
|
+
hostRequirements,
|
|
191
|
+
targetSupport: support,
|
|
192
|
+
policy: Object.freeze({
|
|
193
|
+
providerNeutralCatalog: true,
|
|
194
|
+
publicInjectionCommand: false,
|
|
195
|
+
automaticLoopback: false,
|
|
196
|
+
automaticFallback: false
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function writeEventInspectionArtifacts(project, compiled, outDir, support) {
|
|
202
|
+
const inspection = eventInspectionProjection(project, compiled, support);
|
|
203
|
+
if (!inspection) return null;
|
|
204
|
+
const catalogFile = path.join(outDir, 'event-catalog.json');
|
|
205
|
+
const inspectionFile = path.join(outDir, 'event-inspection.json');
|
|
206
|
+
fs.writeFileSync(catalogFile, stableJson(inspection.catalog));
|
|
207
|
+
fs.writeFileSync(inspectionFile, stableJson(inspection));
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
inspection,
|
|
210
|
+
catalogFile,
|
|
211
|
+
inspectionFile,
|
|
212
|
+
files: Object.freeze({ catalog: path.basename(catalogFile), inspection: path.basename(inspectionFile) })
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function eventManifestProjection(written) {
|
|
217
|
+
return written ? Object.freeze({ ...written.inspection, files: written.files }) : undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function assertEventCommandEligible(project, compiled, command, descriptor = targetDescriptor(project)) {
|
|
221
|
+
const support = selectedEventTargetSupport(project, compiled, descriptor);
|
|
222
|
+
if (!support) return null;
|
|
223
|
+
const commandSupport = support.commands.find((entry) => entry.id === command);
|
|
224
|
+
if (!commandSupport || commandSupport.status !== 'eligible') {
|
|
225
|
+
const code = support.diagnosticCode || 'PULSE_EVENT_TARGET_UNSUPPORTED';
|
|
226
|
+
throw new PulseProjectError(
|
|
227
|
+
code,
|
|
228
|
+
`pulse ${command} cannot realize the event plane for ${project.provider}/${project.target || 'native'}.`,
|
|
229
|
+
{
|
|
230
|
+
provider: project.provider,
|
|
231
|
+
target: project.target || 'native',
|
|
232
|
+
command,
|
|
233
|
+
eventTargetSupport: support,
|
|
234
|
+
automaticFallback: false
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return support;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function projectPlanInspection(project, compiled) {
|
|
242
|
+
const profile = project.selectedProfile || project.profile || null;
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
mode: 'conventional',
|
|
245
|
+
workspace: project.workspace ? Object.freeze({
|
|
246
|
+
root: project.workspace.root,
|
|
247
|
+
configFile: project.workspace.configFile,
|
|
248
|
+
source: project.workspace.source
|
|
249
|
+
}) : null,
|
|
250
|
+
projectHash: project.projectHash,
|
|
251
|
+
planHash: project.planHash,
|
|
252
|
+
selectedProfile: profile ? Object.freeze({
|
|
253
|
+
name: profile.name,
|
|
254
|
+
source: profile.source,
|
|
255
|
+
host: profile.host,
|
|
256
|
+
target: profile.target,
|
|
257
|
+
reporting: profile.reporting,
|
|
258
|
+
reportingLevel: project.reportingLevel
|
|
259
|
+
}) : null,
|
|
260
|
+
strict: project.strict !== false,
|
|
261
|
+
bindings: Object.freeze({
|
|
262
|
+
config: Object.freeze([...(project.bindings && project.bindings.config || [])]),
|
|
263
|
+
secret: Object.freeze([...(project.bindings && project.bindings.secret || [])])
|
|
264
|
+
}),
|
|
265
|
+
fragments: Object.freeze({
|
|
266
|
+
keys: Object.freeze(Object.keys(project.fragments || {}).sort()),
|
|
267
|
+
ownership: compiled && compiled.packageProduct
|
|
268
|
+
? compiled.packageProduct.fragments
|
|
269
|
+
: Object.freeze([]),
|
|
270
|
+
unclaimed: compiled && compiled.packageProduct
|
|
271
|
+
? Object.freeze(compiled.packageProduct.fragments.filter((entry) => entry.present && !entry.contractId).map((entry) => entry.key))
|
|
272
|
+
: Object.freeze(Object.keys(project.fragments || {}).sort())
|
|
273
|
+
}),
|
|
274
|
+
crypto: Object.freeze({
|
|
275
|
+
declaration: project.profilePlan && project.profilePlan.crypto || null,
|
|
276
|
+
realizationPlan: compiled && compiled.cryptoRealizationPlan || null
|
|
277
|
+
}),
|
|
278
|
+
application: compiled && (compiled.application || compiled.metadata && compiled.metadata.application) || null
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function jsonPolicyInspection(project, compiled, nativePlan) {
|
|
283
|
+
const policy = compiled && compiled.metadata && compiled.metadata.json;
|
|
284
|
+
return Object.freeze({
|
|
285
|
+
strict: project.strict !== false,
|
|
286
|
+
schemaBoundRequestCount: Number(policy && policy.schemaBoundRequestCount || 0),
|
|
287
|
+
genericRequestCount: Number(policy && policy.genericRequestCount || 0),
|
|
288
|
+
genericParserRequired: Boolean(policy && policy.genericParserRequired),
|
|
289
|
+
maxBytes: Number(policy && policy.maxBytes || project.schemas && project.schemas.maxBytes || 65536),
|
|
290
|
+
native: nativePlan && nativePlan.json || null
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function compileProject(project, options = {}) {
|
|
295
|
+
const handlerAuthoring = options.handlerAuthoring || HANDLER_AUTHORING_MODES.ASYNC_REQUIRED;
|
|
296
|
+
const configuredTarget = project.target || 'native';
|
|
297
|
+
const packageTargetDescriptor = getProviderTargetDescriptor(providerDriver(project), configuredTarget);
|
|
298
|
+
return compileCanonicalProject(project.entryFile, {
|
|
299
|
+
rootDir: project.root,
|
|
300
|
+
// Package lowerers are discovered from the nearest package-manager workspace.
|
|
301
|
+
// The Pulse project root remains authoritative for config and paths, but a
|
|
302
|
+
// conventional project may live inside a larger source workspace.
|
|
303
|
+
workspaceRoot: undefined,
|
|
304
|
+
configFile: project.configFile,
|
|
305
|
+
schemas: project.schemas,
|
|
306
|
+
strict: project.strict !== false,
|
|
307
|
+
emitJsonPolicy: true,
|
|
308
|
+
handlerAuthoring,
|
|
309
|
+
requireAsync: handlerAuthoring === HANDLER_AUTHORING_MODES.ASYNC_REQUIRED,
|
|
310
|
+
requireEffectAwait: false,
|
|
311
|
+
nativeEligibilityMode: (project.target || 'native') === 'javascript' ? 'record' : 'enforce',
|
|
312
|
+
packageTargetDescriptor,
|
|
313
|
+
packageTarget: configuredTarget,
|
|
314
|
+
applicationProjectMetadata: Object.freeze({
|
|
315
|
+
constructionMode: 'auto',
|
|
316
|
+
projectHash: project.projectHash,
|
|
317
|
+
configPlanHash: project.planHash,
|
|
318
|
+
selectedProfile: project.profile,
|
|
319
|
+
strict: project.strict !== false,
|
|
320
|
+
bindings: project.bindings,
|
|
321
|
+
fragments: project.fragments,
|
|
322
|
+
crypto: project.profilePlan && project.profilePlan.crypto,
|
|
323
|
+
reporting: project.reporting,
|
|
324
|
+
reportingLevel: project.reportingLevel,
|
|
325
|
+
target: project.target || 'native',
|
|
326
|
+
host: project.provider || null
|
|
327
|
+
})
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function compileNativeProjectInMemory(project, options = {}) {
|
|
332
|
+
const compiled = options.compiled || compileProject(project);
|
|
333
|
+
const plan = buildCanonicalNativePlan(compiled, { reporting: project.reporting });
|
|
334
|
+
const native = compileCanonicalNativePlan(plan, {
|
|
335
|
+
cwd: project.root,
|
|
336
|
+
projectRoot: project.root,
|
|
337
|
+
profile: project.selectedProfile && project.selectedProfile.name || project.profile && project.profile.name || 'native',
|
|
338
|
+
targetDescriptor: getProviderTargetDescriptor(providerDriver(project), 'native'),
|
|
339
|
+
synchronizedPackages: releaseCatalog.packages.map(({ name, version }) => Object.freeze({ name, version })),
|
|
340
|
+
timeoutMs: options.timeoutMs,
|
|
341
|
+
nativeOptimization: options.experimentalNativeSize === true
|
|
342
|
+
? 'experimental-native-size'
|
|
343
|
+
: options.nativeOptimization
|
|
344
|
+
});
|
|
345
|
+
return Object.freeze({ compiled, plan, native });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function providerDriver(project) {
|
|
349
|
+
return getProviderDriver(project.providerSelector || project.provider, {
|
|
350
|
+
projectRoot: project.root
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function targetDescriptor(project, target = project.target || 'native') {
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
...getProviderTargetDescriptor(providerDriver(project), target),
|
|
357
|
+
resolvedReporting: project.reportingPolicy
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function selectedProviderTarget(project, target = project.target || 'native') {
|
|
362
|
+
return getProviderTargetDescriptor(providerDriver(project), target);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function assertImplementedTarget(project, command) {
|
|
366
|
+
const target = project.target || 'native';
|
|
367
|
+
const descriptor = targetDescriptor(project, target);
|
|
368
|
+
if (!descriptor.commands || descriptor.commands[command] !== true) {
|
|
369
|
+
throw new PulseProjectError(
|
|
370
|
+
'PULSE_TARGET_IMPLEMENTATION_PENDING',
|
|
371
|
+
`pulse ${command} cannot realize the selected ${target} target yet.`,
|
|
372
|
+
{
|
|
373
|
+
target,
|
|
374
|
+
command,
|
|
375
|
+
provider: project.provider,
|
|
376
|
+
selectedProfile: project.profile,
|
|
377
|
+
supportedCommands: Object.freeze(Object.entries(descriptor.commands || {}).filter(([, supported]) => supported).map(([name]) => name)),
|
|
378
|
+
targetDescriptor: descriptor,
|
|
379
|
+
targetSupport: getProviderTargetSupportDeclaration(providerDriver(project), target),
|
|
380
|
+
automaticFallback: false
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return descriptor;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function javascriptLoadFailure(error) {
|
|
388
|
+
if (!error) return null;
|
|
389
|
+
return Object.freeze({
|
|
390
|
+
name: error.name ? String(error.name) : 'Error',
|
|
391
|
+
code: error.code ? String(error.code) : null,
|
|
392
|
+
message: error.message ? String(error.message) : String(error),
|
|
393
|
+
detail: error.detail && typeof error.detail === 'object' ? Object.freeze({ ...error.detail }) : null,
|
|
394
|
+
diagnostics: Array.isArray(error.diagnostics) ? Object.freeze([...error.diagnostics]) : Object.freeze([])
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function prepareJavascriptApplication(project, options = {}) {
|
|
399
|
+
const descriptor = targetDescriptor(project, 'javascript');
|
|
400
|
+
const javascript = providerDriver(project).javascript;
|
|
401
|
+
const plan = buildJavascriptApplicationPlan(project.entryFile, {
|
|
402
|
+
rootDir: project.root,
|
|
403
|
+
workspaceRoot: undefined,
|
|
404
|
+
configFile: project.configFile,
|
|
405
|
+
projectFragments: project.fragments,
|
|
406
|
+
provider: project.provider,
|
|
407
|
+
targetId: descriptor.targetId,
|
|
408
|
+
providerLifecycleDeferred: descriptor.lifecycle !== true,
|
|
409
|
+
capabilityEnvelope: descriptor.capabilities || Object.freeze({
|
|
410
|
+
version: 'pulse.javascript-capability-envelope.v1',
|
|
411
|
+
provider: project.provider,
|
|
412
|
+
status: descriptor.requestAdapter === true ? 'request-lifecycle-ready' : 'provider-adapters-deferred',
|
|
413
|
+
core: Object.freeze({ request: descriptor.requestAdapter === true, response: descriptor.requestAdapter === true, state: true }),
|
|
414
|
+
effects: Object.freeze({ fetch: false, config: false, secret: false, kv: false }),
|
|
415
|
+
packages: Object.freeze([])
|
|
416
|
+
})
|
|
417
|
+
});
|
|
418
|
+
let loaded = null;
|
|
419
|
+
let loadError = null;
|
|
420
|
+
let loadAttempted = false;
|
|
421
|
+
let loadSkippedReason = null;
|
|
422
|
+
const localEmulation = options.localEmulation === true && javascript && javascript.localEmulation === true;
|
|
423
|
+
if (descriptor.applicationLoader === true && javascript && (javascript.validateApplication || (localEmulation && javascript.loadApplication))) {
|
|
424
|
+
if (options.load === false) {
|
|
425
|
+
loadSkippedReason = 'plan-only-evidence';
|
|
426
|
+
} else if (options.captureLoadError === true && plan.loadable !== true) {
|
|
427
|
+
loadSkippedReason = 'application-plan-not-loadable';
|
|
428
|
+
} else {
|
|
429
|
+
loadAttempted = true;
|
|
430
|
+
try {
|
|
431
|
+
loaded = localEmulation && javascript.loadApplication
|
|
432
|
+
? javascript.loadApplication(plan, {
|
|
433
|
+
projectRoot: project.root,
|
|
434
|
+
schemaBundle: options.schemaBundle
|
|
435
|
+
})
|
|
436
|
+
: loadJavascriptApplication(plan, {
|
|
437
|
+
workspaceRoot: project.root,
|
|
438
|
+
validateApplication: javascript.validateApplication,
|
|
439
|
+
allowedPackages: options.allowedPackages
|
|
440
|
+
});
|
|
441
|
+
} catch (error) {
|
|
442
|
+
if (options.captureLoadError !== true) throw error;
|
|
443
|
+
loadError = javascriptLoadFailure(error);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} else if (descriptor.applicationLoader === true && javascript && javascript.buildTimeLoader === true) {
|
|
447
|
+
loadSkippedReason = 'provider-build-time-bundle-loader';
|
|
448
|
+
} else {
|
|
449
|
+
loadSkippedReason = 'provider-application-loader-unavailable';
|
|
450
|
+
}
|
|
451
|
+
return Object.freeze({ descriptor, plan, loaded, loadError, loadAttempted, loadSkippedReason });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function selectedTargetSupportEvidence(project, compiled, javascriptApplication, descriptor, options = {}) {
|
|
455
|
+
if ((project.target || 'native') !== 'javascript') return null;
|
|
456
|
+
const javascript = providerDriver(project).javascript;
|
|
457
|
+
const policy = javascript && javascript.targetSupportPolicy;
|
|
458
|
+
if (!policy) return null;
|
|
459
|
+
return buildJavascriptTargetSupportEvidence(project, {
|
|
460
|
+
compiled,
|
|
461
|
+
javascriptApplication,
|
|
462
|
+
descriptor: descriptor || javascriptApplication && javascriptApplication.descriptor,
|
|
463
|
+
declaration: options.targetSupportDeclaration
|
|
464
|
+
}, policy);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function assertExecutableProvider(project, command) {
|
|
468
|
+
const driver = providerDriver(project);
|
|
469
|
+
if (!driver.executable) {
|
|
470
|
+
throw new PulseProjectError(
|
|
471
|
+
command === 'test' ? 'PULSE_TEST_PROVIDER_REQUIRED' : 'PULSE_DEV_PROVIDER_UNSUPPORTED',
|
|
472
|
+
`pulse ${command} requires an executable provider.`,
|
|
473
|
+
{ provider: project.provider, supported: providerIds({ executable: true }) }
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return driver;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function providerExecutionOptions(project, values = {}) {
|
|
480
|
+
const driver = providerDriver(project);
|
|
481
|
+
const resolved = { ...values, reporting: project.reporting };
|
|
482
|
+
return driver.executionOptions ? driver.executionOptions(project.providerConfig, resolved) : resolved;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function requiresExactNativeCrypto(compiled) {
|
|
486
|
+
return Boolean(
|
|
487
|
+
compiled
|
|
488
|
+
&& compiled.cryptoRealizationPlan
|
|
489
|
+
&& Array.isArray(compiled.cryptoRealizationPlan.algorithms)
|
|
490
|
+
&& compiled.cryptoRealizationPlan.algorithms.some((entry) => entry.kind === 'guest-linked')
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function providerLoweringPlan(project, metadata) {
|
|
495
|
+
const driver = providerDriver(project);
|
|
496
|
+
if (typeof driver.createLoweringPlan === 'function') {
|
|
497
|
+
const input = createProviderPlanInput({
|
|
498
|
+
version: PROVIDER_PLAN_INPUT_VERSION,
|
|
499
|
+
capabilities: metadata && metadata.capabilities || [],
|
|
500
|
+
providerOperations: metadata && metadata.providerOperations || [],
|
|
501
|
+
opaqueReturnCount: metadata && metadata.opaqueReturnCount || 0
|
|
502
|
+
});
|
|
503
|
+
return driver.createLoweringPlan(input, project.providerConfig);
|
|
504
|
+
}
|
|
505
|
+
return Object.freeze({
|
|
506
|
+
version: 'pulse.canonical-provider-plan.v1',
|
|
507
|
+
contractVersion: 'pulse.canonical-provider-contract.v1',
|
|
508
|
+
provider: 'none',
|
|
509
|
+
providerVersion: 'internal',
|
|
510
|
+
package: '',
|
|
511
|
+
runtime: '',
|
|
512
|
+
buildTarget: 'portable-native-wasm',
|
|
513
|
+
deployable: false,
|
|
514
|
+
localExecution: false,
|
|
515
|
+
requirements: Object.freeze([]),
|
|
516
|
+
bindings: Object.freeze({}),
|
|
517
|
+
operations: Object.freeze([]),
|
|
518
|
+
providerSpecificUserland: false,
|
|
519
|
+
providerSdkUserland: false,
|
|
520
|
+
capabilityDiscoveryFromUserland: false
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function synchronizedPackageFacts() {
|
|
525
|
+
return Object.freeze(releaseCatalog.packages.map(
|
|
526
|
+
({ name, version }) => Object.freeze({ name, version })
|
|
527
|
+
));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function selectedProfileName(project) {
|
|
531
|
+
return project.selectedProfile && project.selectedProfile.name
|
|
532
|
+
|| project.profile && project.profile.name
|
|
533
|
+
|| 'native';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function providerTargetInvocation(project, action, applicationPlan, providerPlan, options = {}) {
|
|
537
|
+
const nativeArtifact = options.native ? createProviderNativeArtifact(options.native) : null;
|
|
538
|
+
return createProviderTargetInvocation({
|
|
539
|
+
version: PROVIDER_TARGET_INVOCATION_VERSION,
|
|
540
|
+
action,
|
|
541
|
+
provider: project.provider,
|
|
542
|
+
selectedTarget: selectedProviderTarget(project, action.endsWith('javascript') ? 'javascript' : 'native'),
|
|
543
|
+
applicationPlan,
|
|
544
|
+
providerPlan,
|
|
545
|
+
providerConfig: project.providerConfig,
|
|
546
|
+
requirements: projectProviderRequirements(applicationPlan, providerPlan),
|
|
547
|
+
nativeArtifact,
|
|
548
|
+
javascript: options.javascript || null,
|
|
549
|
+
project: Object.freeze({
|
|
550
|
+
root: project.root,
|
|
551
|
+
outDir: options.outDir || null,
|
|
552
|
+
profile: selectedProfileName(project)
|
|
553
|
+
}),
|
|
554
|
+
synchronizedPackages: synchronizedPackageFacts(),
|
|
555
|
+
optimization: options.optimization,
|
|
556
|
+
timeoutMs: options.timeoutMs
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function canonicalFuturePath(candidate) {
|
|
561
|
+
const requested = path.resolve(candidate);
|
|
562
|
+
const suffix = [];
|
|
563
|
+
let existing = requested;
|
|
564
|
+
|
|
565
|
+
while (!fs.existsSync(existing)) {
|
|
566
|
+
const parent = path.dirname(existing);
|
|
567
|
+
if (parent === existing) break;
|
|
568
|
+
suffix.unshift(path.basename(existing));
|
|
569
|
+
existing = parent;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return Object.freeze({
|
|
573
|
+
requested,
|
|
574
|
+
existing,
|
|
575
|
+
target: path.resolve(fs.realpathSync(existing), ...suffix)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resolveOutputDirectory(project, outDir) {
|
|
580
|
+
const projectRoot = fs.realpathSync(path.resolve(project.root));
|
|
581
|
+
const requestedTarget = path.isAbsolute(outDir)
|
|
582
|
+
? path.resolve(outDir)
|
|
583
|
+
: path.resolve(projectRoot, outDir);
|
|
584
|
+
|
|
585
|
+
const physical = canonicalFuturePath(requestedTarget);
|
|
586
|
+
const target = physical.target;
|
|
587
|
+
const relativeTarget = path.relative(projectRoot, target);
|
|
588
|
+
|
|
589
|
+
if (
|
|
590
|
+
!relativeTarget
|
|
591
|
+
|| relativeTarget.startsWith('..')
|
|
592
|
+
|| path.isAbsolute(relativeTarget)
|
|
593
|
+
) {
|
|
594
|
+
throw new PulseProjectError(
|
|
595
|
+
'PULSE_BUILD_OUT_UNSAFE',
|
|
596
|
+
`Pulse build output must be a child directory of the project root: ${requestedTarget}`,
|
|
597
|
+
{ outDir: requestedTarget, projectRoot }
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
let current = physical.existing;
|
|
602
|
+
|
|
603
|
+
for (;;) {
|
|
604
|
+
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
605
|
+
throw new PulseProjectError(
|
|
606
|
+
'PULSE_BUILD_OUT_UNSAFE',
|
|
607
|
+
`Pulse build output must not traverse a symbolic link: ${current}`,
|
|
608
|
+
{ outDir: requestedTarget, projectRoot, symbolicLink: current }
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (fs.realpathSync(current) === projectRoot) break;
|
|
613
|
+
|
|
614
|
+
const parent = path.dirname(current);
|
|
615
|
+
if (parent === current) {
|
|
616
|
+
throw new PulseProjectError(
|
|
617
|
+
'PULSE_BUILD_OUT_UNSAFE',
|
|
618
|
+
`Pulse build output resolves outside the project root: ${requestedTarget}`,
|
|
619
|
+
{ outDir: requestedTarget, projectRoot }
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
current = parent;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return target;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function cleanOutputDirectory(project, outDir) {
|
|
630
|
+
const target = resolveOutputDirectory(project, outDir);
|
|
631
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
632
|
+
fs.mkdirSync(target, { recursive: true });
|
|
633
|
+
return target;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function buildProject(project, options = {}) {
|
|
637
|
+
const driver = providerDriver(project);
|
|
638
|
+
if (options.sourceOnly === true) {
|
|
639
|
+
throw new PulseProjectError(
|
|
640
|
+
'PULSE_SOURCE_ONLY_REMOVED',
|
|
641
|
+
'pulse build --source-only was removed. Native builds always emit generated source and provider Wasm together.',
|
|
642
|
+
{ provider: project.provider, replacement: 'pulse build' }
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (options.experimentalNativeSize === true && (project.target || 'native') !== 'native') {
|
|
646
|
+
throw new PulseProjectError(
|
|
647
|
+
'PULSE_EXPERIMENTAL_NATIVE_SIZE_UNSUPPORTED',
|
|
648
|
+
'The --experimental-native-size flag is available only for Native compilation.',
|
|
649
|
+
{
|
|
650
|
+
target: project.target || 'native',
|
|
651
|
+
required: Object.freeze({ target: 'native' })
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (!driver.executable) {
|
|
656
|
+
throw new PulseProjectError(
|
|
657
|
+
'PULSE_BUILD_PROVIDER_REQUIRED',
|
|
658
|
+
'pulse build requires a deployment provider in the active .pulse/config.ts profile. Use pulse compile for provider-neutral Wasm.',
|
|
659
|
+
{ provider: project.provider, supported: providerIds({ executable: true }), replacement: 'pulse compile' }
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const descriptor = assertImplementedTarget(project, 'build');
|
|
663
|
+
|
|
664
|
+
if ((project.target || 'native') === 'javascript') {
|
|
665
|
+
const javascriptApplication = prepareJavascriptApplication(project, {
|
|
666
|
+
load: false,
|
|
667
|
+
captureLoadError: true,
|
|
668
|
+
allowedPackages: options.allowedPackages
|
|
669
|
+
});
|
|
670
|
+
if (javascriptApplication.plan.loadable !== true) {
|
|
671
|
+
throw new PulseProjectError(
|
|
672
|
+
'PULSE_JAVASCRIPT_SOURCE_PACKAGE_PLAN_UNAVAILABLE',
|
|
673
|
+
'pulse build requires a loadable JavaScript application plan.',
|
|
674
|
+
{
|
|
675
|
+
provider: project.provider,
|
|
676
|
+
target: 'javascript',
|
|
677
|
+
blockers: javascriptApplication.plan.blockers,
|
|
678
|
+
automaticFallback: false
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
const compiled = compileProject(project);
|
|
683
|
+
const eventSupport = assertEventCommandEligible(project, compiled, 'build', descriptor);
|
|
684
|
+
const targetSupport = selectedTargetSupportEvidence(project, compiled, javascriptApplication, descriptor, options);
|
|
685
|
+
if (targetSupport && targetSupport.project.status !== 'eligible') {
|
|
686
|
+
throw new PulseProjectError(
|
|
687
|
+
'PULSE_PROVIDER_CAPABILITY_UNSUPPORTED',
|
|
688
|
+
'pulse build cannot realize one or more project capabilities for the selected JavaScript target.',
|
|
689
|
+
{
|
|
690
|
+
provider: project.provider,
|
|
691
|
+
target: 'javascript',
|
|
692
|
+
status: targetSupport.project.status,
|
|
693
|
+
reasonIds: targetSupport.project.reasonIds,
|
|
694
|
+
targetSupport,
|
|
695
|
+
automaticFallback: false
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const requestedOutDir = options.outDir || project.outDir;
|
|
700
|
+
const outDir = options.clean === false
|
|
701
|
+
? resolveOutputDirectory(project, requestedOutDir)
|
|
702
|
+
: cleanOutputDirectory(project, requestedOutDir);
|
|
703
|
+
const javascript = driver.javascript;
|
|
704
|
+
if (!javascript || typeof javascript.writeSourcePackage !== 'function') {
|
|
705
|
+
throw new PulseProjectError(
|
|
706
|
+
'PULSE_TARGET_IMPLEMENTATION_PENDING',
|
|
707
|
+
'The selected provider does not implement JavaScript source packaging.',
|
|
708
|
+
{ provider: project.provider, target: 'javascript', automaticFallback: false }
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
const providerPlan = providerLoweringPlan(project, compiled.metadata);
|
|
712
|
+
const invocation = providerTargetInvocation(
|
|
713
|
+
project,
|
|
714
|
+
'write-javascript',
|
|
715
|
+
javascriptApplication.plan,
|
|
716
|
+
providerPlan,
|
|
717
|
+
{
|
|
718
|
+
outDir,
|
|
719
|
+
javascript: Object.freeze({
|
|
720
|
+
schemaBundle: compiled.schema.bundle,
|
|
721
|
+
reporting: project.reporting,
|
|
722
|
+
strict: project.strict !== false,
|
|
723
|
+
maxStructuredBodyBytes: project.schemas && project.schemas.maxBytes,
|
|
724
|
+
targetSupport
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
const sourcePackage = javascript.writeSourcePackage(invocation);
|
|
729
|
+
const sourcePackageDescription = javascript.describeSourcePackage(sourcePackage, {
|
|
730
|
+
outDir,
|
|
731
|
+
plan: javascriptApplication.plan
|
|
732
|
+
});
|
|
733
|
+
const packageInspection = writePackageInspectionArtifacts(compiled, outDir);
|
|
734
|
+
const eventInspection = writeEventInspectionArtifacts(project, compiled, outDir, eventSupport);
|
|
735
|
+
const manifest = Object.freeze({
|
|
736
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
737
|
+
status: 'built',
|
|
738
|
+
buildMode: 'javascript-source-package',
|
|
739
|
+
provider: project.provider,
|
|
740
|
+
configuredTarget: 'javascript',
|
|
741
|
+
automaticFallback: false,
|
|
742
|
+
reporting: project.reportingPolicy,
|
|
743
|
+
cryptoRealizationPlan: compiled.cryptoRealizationPlan,
|
|
744
|
+
targetDescriptor: Object.freeze({ ...descriptor, resolvedReporting: project.reportingPolicy }),
|
|
745
|
+
providerTarget: Object.freeze({
|
|
746
|
+
package: driver.descriptor && driver.descriptor.package,
|
|
747
|
+
target: descriptor.targetId,
|
|
748
|
+
status: descriptor.status,
|
|
749
|
+
deployable: true,
|
|
750
|
+
deploymentCandidate: sourcePackageDescription.deploymentCandidate,
|
|
751
|
+
deploymentValidated: false,
|
|
752
|
+
providerRealityValidated: false,
|
|
753
|
+
sourcePackage: true,
|
|
754
|
+
sourceOnly: true,
|
|
755
|
+
nativeWasm: false,
|
|
756
|
+
compiledWasmPresent: false,
|
|
757
|
+
javascriptRuntime: true,
|
|
758
|
+
jsComputeRuntime: false,
|
|
759
|
+
downstreamJavascriptRuntimeWasm: sourcePackageDescription.downstreamJavascriptRuntimeWasm,
|
|
760
|
+
executionReady: descriptor.requestAdapter === true && descriptor.lifecycle === true,
|
|
761
|
+
applicationLoader: descriptor.applicationLoader === true,
|
|
762
|
+
requestAdapter: descriptor.requestAdapter === true,
|
|
763
|
+
lifecycle: descriptor.lifecycle === true
|
|
764
|
+
}),
|
|
765
|
+
project: Object.freeze({
|
|
766
|
+
root: '.',
|
|
767
|
+
config: project.configFile ? relative(project.configFile, project.root) : undefined,
|
|
768
|
+
entry: relative(project.entryFile, project.root),
|
|
769
|
+
outDir: '.'
|
|
770
|
+
}),
|
|
771
|
+
application: Object.freeze({
|
|
772
|
+
plan: path.basename(sourcePackage.paths.applicationPlan),
|
|
773
|
+
planHash: javascriptApplication.plan.planHash,
|
|
774
|
+
graphHash: javascriptApplication.plan.graph.graphHash,
|
|
775
|
+
loadable: true,
|
|
776
|
+
sourcePackage: path.basename(sourcePackage.paths.manifest),
|
|
777
|
+
entry: sourcePackageDescription.application.entry,
|
|
778
|
+
package: path.basename(sourcePackage.paths.package),
|
|
779
|
+
modules: sourcePackageDescription.application.modules,
|
|
780
|
+
dependencies: sourcePackage.manifest.package.dependencies,
|
|
781
|
+
...sourcePackageDescription.application
|
|
782
|
+
}),
|
|
783
|
+
schemas: sourcePackage.manifest.schemas,
|
|
784
|
+
packageInspection: packageInspectionManifest(packageInspection),
|
|
785
|
+
...(eventInspection ? { events: eventManifestProjection(eventInspection) } : {}),
|
|
786
|
+
targetSupport
|
|
787
|
+
});
|
|
788
|
+
const manifestFile = path.join(outDir, BUILD_MANIFEST);
|
|
789
|
+
fs.writeFileSync(manifestFile, stableJson(manifest));
|
|
790
|
+
return Object.freeze({
|
|
791
|
+
status: 'built',
|
|
792
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
793
|
+
buildMode: 'javascript-source-package',
|
|
794
|
+
provider: project.provider,
|
|
795
|
+
configuredTarget: 'javascript',
|
|
796
|
+
automaticFallback: false,
|
|
797
|
+
project: projectJson(project),
|
|
798
|
+
outDir,
|
|
799
|
+
files: Object.freeze({
|
|
800
|
+
entry: sourcePackage.paths.entry,
|
|
801
|
+
package: sourcePackage.paths.package,
|
|
802
|
+
applicationPlan: sourcePackage.paths.applicationPlan,
|
|
803
|
+
schemaRegistry: sourcePackage.paths.schemaRegistry,
|
|
804
|
+
schemaCodecs: sourcePackage.paths.schemaCodecs,
|
|
805
|
+
...sourcePackageDescription.files,
|
|
806
|
+
sourcePackage: sourcePackage.paths.manifest,
|
|
807
|
+
...(eventInspection ? {
|
|
808
|
+
eventCatalog: eventInspection.catalogFile,
|
|
809
|
+
eventInspection: eventInspection.inspectionFile
|
|
810
|
+
} : {}),
|
|
811
|
+
manifest: manifestFile
|
|
812
|
+
}),
|
|
813
|
+
targetSupport,
|
|
814
|
+
packageInspection,
|
|
815
|
+
...(eventInspection ? { events: eventInspection.inspection } : {}),
|
|
816
|
+
sourcePackage: sourcePackage.manifest,
|
|
817
|
+
manifest
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const prepared = compileNativeProjectInMemory(project, options);
|
|
822
|
+
const eventSupport = assertEventCommandEligible(project, prepared.compiled, 'build', descriptor);
|
|
823
|
+
const requestedOutDir = options.outDir || project.outDir;
|
|
824
|
+
const outDir = options.clean === false
|
|
825
|
+
? resolveOutputDirectory(project, requestedOutDir)
|
|
826
|
+
: cleanOutputDirectory(project, requestedOutDir);
|
|
827
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
828
|
+
|
|
829
|
+
const build = writeCanonicalBuild(prepared.compiled, outDir);
|
|
830
|
+
const nativeBuild = writeCanonicalNativeModule(prepared.native, outDir);
|
|
831
|
+
const packageInspection = writePackageInspectionArtifacts(prepared.compiled, outDir);
|
|
832
|
+
const eventInspection = writeEventInspectionArtifacts(project, prepared.compiled, outDir, eventSupport);
|
|
833
|
+
const lowering = providerLoweringPlan(project, prepared.compiled.metadata);
|
|
834
|
+
const providerBuild = driver.writeTarget
|
|
835
|
+
? normalizeProviderTargetResult(driver.writeTarget(providerTargetInvocation(
|
|
836
|
+
project,
|
|
837
|
+
'write-native',
|
|
838
|
+
prepared.plan,
|
|
839
|
+
lowering,
|
|
840
|
+
{
|
|
841
|
+
outDir,
|
|
842
|
+
native: prepared.native,
|
|
843
|
+
optimization: prepared.native.manifest.optimization,
|
|
844
|
+
timeoutMs: options.timeoutMs
|
|
845
|
+
}
|
|
846
|
+
)), { action: 'write-native', provider: project.provider, target: 'native' })
|
|
847
|
+
: undefined;
|
|
848
|
+
const providerFiles = providerBuild ? providerBuild.files : Object.freeze({});
|
|
849
|
+
const providerBuildMetadata = providerBuild && providerBuild.build;
|
|
850
|
+
const nativeManifest = prepared.native.manifest;
|
|
851
|
+
|
|
852
|
+
const manifest = Object.freeze({
|
|
853
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
854
|
+
status: 'built',
|
|
855
|
+
buildMode: 'native-provider',
|
|
856
|
+
provider: project.provider,
|
|
857
|
+
configuredTarget: 'native',
|
|
858
|
+
reporting: project.reportingPolicy,
|
|
859
|
+
cryptoRealizationPlan: prepared.compiled.cryptoRealizationPlan,
|
|
860
|
+
targetDescriptor: targetDescriptor(project, 'native'),
|
|
861
|
+
providerTarget: Object.freeze({
|
|
862
|
+
package: driver.descriptor && driver.descriptor.package,
|
|
863
|
+
buildTarget: driver.descriptor && driver.descriptor.buildTarget,
|
|
864
|
+
target: providerBuildMetadata && providerBuildMetadata.target,
|
|
865
|
+
deployable: driver.deployable === true,
|
|
866
|
+
deploymentValidated: driver.deploymentValidated === true,
|
|
867
|
+
sourcePackage: driver.sourcePackage === true,
|
|
868
|
+
sourceOnly: false,
|
|
869
|
+
sourceOnlySupported: false,
|
|
870
|
+
nativeWasm: true,
|
|
871
|
+
providerNeutralInput: true,
|
|
872
|
+
compiledWasmPresent: true,
|
|
873
|
+
javascriptRuntime: false,
|
|
874
|
+
jsComputeRuntime: false,
|
|
875
|
+
compiler: providerBuildMetadata && providerBuildMetadata.compiler,
|
|
876
|
+
optimization: providerBuildMetadata && providerBuildMetadata.optimization,
|
|
877
|
+
wasm: providerBuildMetadata && providerBuildMetadata.wasm,
|
|
878
|
+
wat: providerBuildMetadata && providerBuildMetadata.wat,
|
|
879
|
+
importModules: providerBuildMetadata && providerBuildMetadata.importModules,
|
|
880
|
+
imports: providerBuildMetadata && providerBuildMetadata.imports,
|
|
881
|
+
exports: providerBuildMetadata && providerBuildMetadata.exports,
|
|
882
|
+
localConformanceRuntime: driver.localExecution === true,
|
|
883
|
+
lowering
|
|
884
|
+
}),
|
|
885
|
+
project: Object.freeze({
|
|
886
|
+
root: '.',
|
|
887
|
+
config: project.configFile ? relative(project.configFile, project.root) : undefined,
|
|
888
|
+
entry: relative(project.entryFile, project.root),
|
|
889
|
+
outDir: relative(outDir, project.root)
|
|
890
|
+
}),
|
|
891
|
+
program: Object.freeze({
|
|
892
|
+
file: path.basename(build.programFile),
|
|
893
|
+
handler: path.basename(build.handlerFile),
|
|
894
|
+
sourceHash: prepared.compiled.metadata.sourceHash,
|
|
895
|
+
compilerVersion: prepared.compiled.metadata.compilerVersion,
|
|
896
|
+
runtimeProtocolVersion: prepared.compiled.metadata.runtimeProtocolVersion,
|
|
897
|
+
authoring: prepared.compiled.metadata.authoring,
|
|
898
|
+
application: prepared.compiled.metadata.application,
|
|
899
|
+
routing: prepared.plan.routing,
|
|
900
|
+
json: prepared.compiled.metadata.json,
|
|
901
|
+
warnings: prepared.compiled.metadata.warnings,
|
|
902
|
+
warningCount: prepared.compiled.metadata.warningCount || 0,
|
|
903
|
+
capabilities: prepared.compiled.metadata.capabilities,
|
|
904
|
+
effectCount: prepared.compiled.metadata.effectCount,
|
|
905
|
+
continuationCount: prepared.compiled.metadata.continuationCount,
|
|
906
|
+
groupedContinuationCount: prepared.compiled.metadata.groupedContinuationCount,
|
|
907
|
+
opaqueReturnCount: prepared.compiled.metadata.opaqueReturnCount,
|
|
908
|
+
projectSourceHash: prepared.compiled.metadata.projectSourceHash,
|
|
909
|
+
schemaReferenceCount: prepared.compiled.metadata.schemaReferenceCount,
|
|
910
|
+
schemaSourceHash: prepared.compiled.metadata.schemaSourceHash
|
|
911
|
+
}),
|
|
912
|
+
portable: Object.freeze({
|
|
913
|
+
target: 'portable-native-wasm',
|
|
914
|
+
providerNeutral: true,
|
|
915
|
+
version: prepared.native.version,
|
|
916
|
+
compilerVersion: prepared.native.compilerVersion,
|
|
917
|
+
planVersion: prepared.plan.version,
|
|
918
|
+
planCompilerVersion: prepared.plan.compilerVersion,
|
|
919
|
+
planHash: prepared.plan.planHash,
|
|
920
|
+
ownership: prepared.plan.ownership,
|
|
921
|
+
summary: prepared.plan.summary,
|
|
922
|
+
logging: prepared.plan.logging,
|
|
923
|
+
...(prepared.plan.json ? { json: prepared.plan.json } : {}),
|
|
924
|
+
...(prepared.plan.requestState ? { requestState: prepared.plan.requestState } : {}),
|
|
925
|
+
assemblyScript: nativeManifest.assemblyScript,
|
|
926
|
+
optimization: nativeManifest.optimization,
|
|
927
|
+
source: Object.freeze({ file: path.basename(nativeBuild.sourceFile), sha256: prepared.native.sourceHash }),
|
|
928
|
+
wasm: Object.freeze({ file: path.basename(nativeBuild.wasmFile), ...nativeManifest.wasm }),
|
|
929
|
+
wat: Object.freeze({ file: path.basename(nativeBuild.watFile), ...nativeManifest.wat }),
|
|
930
|
+
plan: path.basename(nativeBuild.planFile),
|
|
931
|
+
manifest: path.basename(nativeBuild.manifestFile),
|
|
932
|
+
packageRealizationArtifacts: Object.freeze({
|
|
933
|
+
...(nativeBuild.realizationArtifactsFile
|
|
934
|
+
? { file: path.basename(nativeBuild.realizationArtifactsFile) }
|
|
935
|
+
: {}),
|
|
936
|
+
...nativeManifest.packageRealizationArtifacts
|
|
937
|
+
}),
|
|
938
|
+
...(nativeBuild.guestUnitPlanFile ? {
|
|
939
|
+
guestUnit: Object.freeze({
|
|
940
|
+
plan: path.basename(nativeBuild.guestUnitPlanFile),
|
|
941
|
+
report: path.basename(nativeBuild.guestLinkReportFile),
|
|
942
|
+
audit: path.basename(nativeBuild.finalWasmAuditFile),
|
|
943
|
+
finalArtifact: nativeManifest.guestLinkReport.finalArtifact,
|
|
944
|
+
providerPackaging: nativeManifest.providerPackaging
|
|
945
|
+
})
|
|
946
|
+
} : {}),
|
|
947
|
+
importModules: nativeManifest.importModules,
|
|
948
|
+
imports: nativeManifest.imports,
|
|
949
|
+
exports: nativeManifest.exports,
|
|
950
|
+
policy: nativeManifest.policy
|
|
951
|
+
}),
|
|
952
|
+
schemas: Object.freeze({
|
|
953
|
+
active: prepared.compiled.schema.active,
|
|
954
|
+
registry: build.schemaRegistryFile ? path.basename(build.schemaRegistryFile) : undefined,
|
|
955
|
+
codecs: build.schemaCodecsFile ? path.basename(build.schemaCodecsFile) : undefined,
|
|
956
|
+
ids: prepared.compiled.metadata.schemaIds,
|
|
957
|
+
references: prepared.compiled.metadata.schemaReferences,
|
|
958
|
+
sourceHash: prepared.compiled.metadata.schemaSourceHash,
|
|
959
|
+
policy: prepared.compiled.metadata.json
|
|
960
|
+
}),
|
|
961
|
+
packageInspection: packageInspectionManifest(packageInspection),
|
|
962
|
+
...(eventInspection ? { events: eventManifestProjection(eventInspection) } : {})
|
|
963
|
+
});
|
|
964
|
+
const manifestFile = path.join(outDir, BUILD_MANIFEST);
|
|
965
|
+
fs.writeFileSync(manifestFile, stableJson(manifest));
|
|
966
|
+
return Object.freeze({
|
|
967
|
+
status: 'built',
|
|
968
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
969
|
+
buildMode: 'native-provider',
|
|
970
|
+
provider: project.provider,
|
|
971
|
+
project: projectJson(project),
|
|
972
|
+
outDir,
|
|
973
|
+
files: Object.freeze({
|
|
974
|
+
handler: build.handlerFile,
|
|
975
|
+
program: build.programFile,
|
|
976
|
+
schemaRegistry: build.schemaRegistryFile,
|
|
977
|
+
schemaCodecs: build.schemaCodecsFile,
|
|
978
|
+
nativeSource: nativeBuild.sourceFile,
|
|
979
|
+
nativeWasm: nativeBuild.wasmFile,
|
|
980
|
+
nativeWat: nativeBuild.watFile,
|
|
981
|
+
nativePlan: nativeBuild.planFile,
|
|
982
|
+
nativeManifest: nativeBuild.manifestFile,
|
|
983
|
+
nativePackageArtifacts: nativeBuild.realizationArtifactsFile,
|
|
984
|
+
guestUnitPlan: nativeBuild.guestUnitPlanFile,
|
|
985
|
+
guestLinkReport: nativeBuild.guestLinkReportFile,
|
|
986
|
+
finalWasmAudit: nativeBuild.finalWasmAuditFile,
|
|
987
|
+
...(eventInspection ? {
|
|
988
|
+
eventCatalog: eventInspection.catalogFile,
|
|
989
|
+
eventInspection: eventInspection.inspectionFile
|
|
990
|
+
} : {}),
|
|
991
|
+
manifest: manifestFile,
|
|
992
|
+
...providerFiles
|
|
993
|
+
}),
|
|
994
|
+
metadata: prepared.compiled.metadata,
|
|
995
|
+
packageInspection,
|
|
996
|
+
...(eventInspection ? { events: eventInspection.inspection } : {}),
|
|
997
|
+
native: Object.freeze({
|
|
998
|
+
version: prepared.native.version,
|
|
999
|
+
compilerVersion: prepared.native.compilerVersion,
|
|
1000
|
+
planVersion: prepared.plan.version,
|
|
1001
|
+
planHash: prepared.plan.planHash,
|
|
1002
|
+
summary: prepared.plan.summary,
|
|
1003
|
+
logging: prepared.plan.logging,
|
|
1004
|
+
...(prepared.plan.json ? { json: prepared.plan.json } : {}),
|
|
1005
|
+
...(prepared.plan.requestState ? { requestState: prepared.plan.requestState } : {}),
|
|
1006
|
+
assemblyScript: nativeManifest.assemblyScript,
|
|
1007
|
+
optimization: nativeManifest.optimization,
|
|
1008
|
+
wasm: nativeManifest.wasm,
|
|
1009
|
+
importModules: nativeManifest.importModules,
|
|
1010
|
+
exports: nativeManifest.exports
|
|
1011
|
+
}),
|
|
1012
|
+
manifest
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function compileNativeProject(project, options = {}) {
|
|
1017
|
+
const requestedOutDir = options.outDir || project.outDir;
|
|
1018
|
+
const outDir = options.clean === false
|
|
1019
|
+
? resolveOutputDirectory(project, requestedOutDir)
|
|
1020
|
+
: cleanOutputDirectory(project, requestedOutDir);
|
|
1021
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1022
|
+
|
|
1023
|
+
const prepared = compileNativeProjectInMemory(project, options);
|
|
1024
|
+
const javascriptApplication = (project.target || 'native') === 'javascript'
|
|
1025
|
+
? prepareJavascriptApplication(project, { load: false, captureLoadError: true, allowedPackages: options.allowedPackages })
|
|
1026
|
+
: null;
|
|
1027
|
+
const targetSupport = javascriptApplication
|
|
1028
|
+
? selectedTargetSupportEvidence(project, prepared.compiled, javascriptApplication, javascriptApplication.descriptor, options)
|
|
1029
|
+
: null;
|
|
1030
|
+
const eventSupport = selectedEventTargetSupport(project, prepared.compiled);
|
|
1031
|
+
const build = writeCanonicalBuild(prepared.compiled, outDir);
|
|
1032
|
+
const nativeBuild = writeCanonicalNativeModule(prepared.native, outDir);
|
|
1033
|
+
const packageInspection = writePackageInspectionArtifacts(prepared.compiled, outDir);
|
|
1034
|
+
const eventInspection = writeEventInspectionArtifacts(project, prepared.compiled, outDir, eventSupport);
|
|
1035
|
+
const nativeManifest = prepared.native.manifest;
|
|
1036
|
+
const manifest = Object.freeze({
|
|
1037
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
1038
|
+
status: 'compiled',
|
|
1039
|
+
target: 'portable-native-wasm',
|
|
1040
|
+
provider: null,
|
|
1041
|
+
providerNeutral: true,
|
|
1042
|
+
configuredProvider: project.provider,
|
|
1043
|
+
configuredTarget: project.target || 'native',
|
|
1044
|
+
automaticFallback: false,
|
|
1045
|
+
reporting: project.reportingPolicy,
|
|
1046
|
+
cryptoRealizationPlan: prepared.compiled.cryptoRealizationPlan,
|
|
1047
|
+
...(targetSupport ? { targetSupport } : {}),
|
|
1048
|
+
project: Object.freeze({
|
|
1049
|
+
root: '.',
|
|
1050
|
+
config: project.configFile ? relative(project.configFile, project.root) : undefined,
|
|
1051
|
+
entry: relative(project.entryFile, project.root),
|
|
1052
|
+
outDir: relative(outDir, project.root)
|
|
1053
|
+
}),
|
|
1054
|
+
program: Object.freeze({
|
|
1055
|
+
file: path.basename(build.programFile),
|
|
1056
|
+
handler: path.basename(build.handlerFile),
|
|
1057
|
+
sourceHash: prepared.compiled.metadata.sourceHash,
|
|
1058
|
+
compilerVersion: prepared.compiled.metadata.compilerVersion,
|
|
1059
|
+
runtimeProtocolVersion: prepared.compiled.metadata.runtimeProtocolVersion,
|
|
1060
|
+
authoring: prepared.compiled.metadata.authoring,
|
|
1061
|
+
application: prepared.compiled.metadata.application,
|
|
1062
|
+
routing: prepared.plan.routing,
|
|
1063
|
+
json: prepared.compiled.metadata.json,
|
|
1064
|
+
warnings: prepared.compiled.metadata.warnings,
|
|
1065
|
+
warningCount: prepared.compiled.metadata.warningCount || 0,
|
|
1066
|
+
capabilities: prepared.compiled.metadata.capabilities,
|
|
1067
|
+
effectCount: prepared.compiled.metadata.effectCount,
|
|
1068
|
+
continuationCount: prepared.compiled.metadata.continuationCount,
|
|
1069
|
+
groupedContinuationCount: prepared.compiled.metadata.groupedContinuationCount,
|
|
1070
|
+
opaqueReturnCount: prepared.compiled.metadata.opaqueReturnCount,
|
|
1071
|
+
projectSourceHash: prepared.compiled.metadata.projectSourceHash,
|
|
1072
|
+
schemaReferenceCount: prepared.compiled.metadata.schemaReferenceCount,
|
|
1073
|
+
schemaSourceHash: prepared.compiled.metadata.schemaSourceHash
|
|
1074
|
+
}),
|
|
1075
|
+
native: Object.freeze({
|
|
1076
|
+
version: prepared.native.version,
|
|
1077
|
+
compilerVersion: prepared.native.compilerVersion,
|
|
1078
|
+
planVersion: prepared.plan.version,
|
|
1079
|
+
planCompilerVersion: prepared.plan.compilerVersion,
|
|
1080
|
+
planHash: prepared.plan.planHash,
|
|
1081
|
+
ownership: prepared.plan.ownership,
|
|
1082
|
+
summary: prepared.plan.summary,
|
|
1083
|
+
logging: prepared.plan.logging,
|
|
1084
|
+
...(prepared.plan.json ? { json: prepared.plan.json } : {}),
|
|
1085
|
+
...(prepared.plan.requestState ? { requestState: prepared.plan.requestState } : {}),
|
|
1086
|
+
assemblyScript: nativeManifest.assemblyScript,
|
|
1087
|
+
optimization: nativeManifest.optimization,
|
|
1088
|
+
source: Object.freeze({
|
|
1089
|
+
file: path.basename(nativeBuild.sourceFile),
|
|
1090
|
+
sha256: prepared.native.sourceHash
|
|
1091
|
+
}),
|
|
1092
|
+
wasm: Object.freeze({
|
|
1093
|
+
file: path.basename(nativeBuild.wasmFile),
|
|
1094
|
+
...nativeManifest.wasm
|
|
1095
|
+
}),
|
|
1096
|
+
wat: Object.freeze({
|
|
1097
|
+
file: path.basename(nativeBuild.watFile),
|
|
1098
|
+
...nativeManifest.wat
|
|
1099
|
+
}),
|
|
1100
|
+
plan: path.basename(nativeBuild.planFile),
|
|
1101
|
+
manifest: path.basename(nativeBuild.manifestFile),
|
|
1102
|
+
packageRealizationArtifacts: Object.freeze({
|
|
1103
|
+
...(nativeBuild.realizationArtifactsFile
|
|
1104
|
+
? { file: path.basename(nativeBuild.realizationArtifactsFile) }
|
|
1105
|
+
: {}),
|
|
1106
|
+
...nativeManifest.packageRealizationArtifacts
|
|
1107
|
+
}),
|
|
1108
|
+
...(nativeBuild.guestUnitPlanFile ? {
|
|
1109
|
+
guestUnit: Object.freeze({
|
|
1110
|
+
plan: path.basename(nativeBuild.guestUnitPlanFile),
|
|
1111
|
+
report: path.basename(nativeBuild.guestLinkReportFile),
|
|
1112
|
+
audit: path.basename(nativeBuild.finalWasmAuditFile),
|
|
1113
|
+
finalArtifact: nativeManifest.guestLinkReport.finalArtifact,
|
|
1114
|
+
providerPackaging: nativeManifest.providerPackaging
|
|
1115
|
+
})
|
|
1116
|
+
} : {}),
|
|
1117
|
+
importModules: nativeManifest.importModules,
|
|
1118
|
+
imports: nativeManifest.imports,
|
|
1119
|
+
exports: nativeManifest.exports,
|
|
1120
|
+
policy: nativeManifest.policy
|
|
1121
|
+
}),
|
|
1122
|
+
schemas: Object.freeze({
|
|
1123
|
+
active: prepared.compiled.schema.active,
|
|
1124
|
+
registry: build.schemaRegistryFile ? path.basename(build.schemaRegistryFile) : undefined,
|
|
1125
|
+
codecs: build.schemaCodecsFile ? path.basename(build.schemaCodecsFile) : undefined,
|
|
1126
|
+
ids: prepared.compiled.metadata.schemaIds,
|
|
1127
|
+
references: prepared.compiled.metadata.schemaReferences,
|
|
1128
|
+
sourceHash: prepared.compiled.metadata.schemaSourceHash,
|
|
1129
|
+
policy: prepared.compiled.metadata.json
|
|
1130
|
+
}),
|
|
1131
|
+
packageInspection: packageInspectionManifest(packageInspection),
|
|
1132
|
+
...(eventInspection ? { events: eventManifestProjection(eventInspection) } : {})
|
|
1133
|
+
});
|
|
1134
|
+
const manifestFile = path.join(outDir, COMPILE_MANIFEST);
|
|
1135
|
+
fs.writeFileSync(manifestFile, stableJson(manifest));
|
|
1136
|
+
|
|
1137
|
+
return Object.freeze({
|
|
1138
|
+
status: 'compiled',
|
|
1139
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
1140
|
+
target: 'portable-native-wasm',
|
|
1141
|
+
provider: null,
|
|
1142
|
+
providerNeutral: true,
|
|
1143
|
+
configuredProvider: project.provider,
|
|
1144
|
+
configuredTarget: project.target || 'native',
|
|
1145
|
+
automaticFallback: false,
|
|
1146
|
+
targetSupport,
|
|
1147
|
+
project: projectJson(project),
|
|
1148
|
+
outDir,
|
|
1149
|
+
files: Object.freeze({
|
|
1150
|
+
handler: build.handlerFile,
|
|
1151
|
+
program: build.programFile,
|
|
1152
|
+
schemaRegistry: build.schemaRegistryFile,
|
|
1153
|
+
schemaCodecs: build.schemaCodecsFile,
|
|
1154
|
+
nativeSource: nativeBuild.sourceFile,
|
|
1155
|
+
nativeWasm: nativeBuild.wasmFile,
|
|
1156
|
+
nativeWat: nativeBuild.watFile,
|
|
1157
|
+
nativePlan: nativeBuild.planFile,
|
|
1158
|
+
nativeManifest: nativeBuild.manifestFile,
|
|
1159
|
+
nativePackageArtifacts: nativeBuild.realizationArtifactsFile,
|
|
1160
|
+
guestUnitPlan: nativeBuild.guestUnitPlanFile,
|
|
1161
|
+
guestLinkReport: nativeBuild.guestLinkReportFile,
|
|
1162
|
+
finalWasmAudit: nativeBuild.finalWasmAuditFile,
|
|
1163
|
+
...(eventInspection ? {
|
|
1164
|
+
eventCatalog: eventInspection.catalogFile,
|
|
1165
|
+
eventInspection: eventInspection.inspectionFile
|
|
1166
|
+
} : {}),
|
|
1167
|
+
manifest: manifestFile
|
|
1168
|
+
}),
|
|
1169
|
+
metadata: prepared.compiled.metadata,
|
|
1170
|
+
packageInspection,
|
|
1171
|
+
...(eventInspection ? { events: eventInspection.inspection } : {}),
|
|
1172
|
+
native: Object.freeze({
|
|
1173
|
+
version: prepared.native.version,
|
|
1174
|
+
compilerVersion: prepared.native.compilerVersion,
|
|
1175
|
+
planVersion: prepared.plan.version,
|
|
1176
|
+
planHash: prepared.plan.planHash,
|
|
1177
|
+
summary: prepared.plan.summary,
|
|
1178
|
+
logging: prepared.plan.logging,
|
|
1179
|
+
...(prepared.plan.json ? { json: prepared.plan.json } : {}),
|
|
1180
|
+
...(prepared.plan.requestState ? { requestState: prepared.plan.requestState } : {}),
|
|
1181
|
+
assemblyScript: nativeManifest.assemblyScript,
|
|
1182
|
+
optimization: nativeManifest.optimization,
|
|
1183
|
+
wasm: nativeManifest.wasm,
|
|
1184
|
+
importModules: nativeManifest.importModules,
|
|
1185
|
+
exports: nativeManifest.exports
|
|
1186
|
+
}),
|
|
1187
|
+
manifest
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function headerValue(headers, name) {
|
|
1192
|
+
const lower = String(name).toLowerCase();
|
|
1193
|
+
const values = (headers || []).filter(([key]) => String(key).toLowerCase() === lower).map(([, value]) => String(value));
|
|
1194
|
+
return values.length <= 1 ? values[0] : values;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function responseJson(response) {
|
|
1198
|
+
if (response.bodyClass === 'opaque') throw new Error('Opaque response cannot be decoded as JSON.');
|
|
1199
|
+
return JSON.parse(response.body || 'null');
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function statusAllowsResponseBody(status) {
|
|
1203
|
+
const value = Number(status);
|
|
1204
|
+
return value !== 204 && value !== 205 && value !== 304 && !(value >= 100 && value < 200);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function observableTestResponse(testCase, response) {
|
|
1208
|
+
const method = String(testCase.request && testCase.request.method || 'GET').toUpperCase();
|
|
1209
|
+
if (method !== 'HEAD' && statusAllowsResponseBody(response.status)) return response;
|
|
1210
|
+
return Object.freeze({ ...response, body: '' });
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function materializeTestResponse(response) {
|
|
1214
|
+
if (!response || response.bodyClass !== 'opaque' || response.body !== undefined) return response;
|
|
1215
|
+
const chunks = response.bodyStream && Array.isArray(response.bodyStream.chunks)
|
|
1216
|
+
? response.bodyStream.chunks
|
|
1217
|
+
: undefined;
|
|
1218
|
+
if (!chunks) return response;
|
|
1219
|
+
const body = Buffer.concat(chunks.map((chunk) => Buffer.isBuffer(chunk)
|
|
1220
|
+
? chunk
|
|
1221
|
+
: chunk instanceof Uint8Array
|
|
1222
|
+
? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)
|
|
1223
|
+
: Buffer.from(String(chunk)))).toString('utf8');
|
|
1224
|
+
return Object.freeze({ ...response, body });
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function assertExpectation(testCase, execution) {
|
|
1228
|
+
const expected = testCase.expect;
|
|
1229
|
+
const response = observableTestResponse(testCase, execution.response);
|
|
1230
|
+
if (expected.status !== undefined) assert.equal(response.status, expected.status, `${testCase.name}: unexpected status`);
|
|
1231
|
+
if (expected.bodyClass !== undefined) assert.equal(response.bodyClass, expected.bodyClass, `${testCase.name}: unexpected body class`);
|
|
1232
|
+
if (Object.prototype.hasOwnProperty.call(expected, 'json') && expected.json !== undefined) assert.deepEqual(responseJson(response), expected.json, `${testCase.name}: unexpected JSON body`);
|
|
1233
|
+
if (expected.text !== undefined) assert.equal(String(response.body || ''), expected.text, `${testCase.name}: unexpected text body`);
|
|
1234
|
+
if (expected.headers) {
|
|
1235
|
+
for (const [name, value] of expected.headers) assert.deepEqual(headerValue(response.headers, name), value, `${testCase.name}: unexpected header ${name}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function secretTokens(secrets) {
|
|
1240
|
+
return [...new Set(Object.values(secrets || {})
|
|
1241
|
+
.filter((value) => typeof value === 'string' && value.length > 0))]
|
|
1242
|
+
.sort((left, right) => right.length - left.length);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function redactString(value, tokens) {
|
|
1246
|
+
let text = String(value);
|
|
1247
|
+
for (const token of tokens) text = text.split(token).join('<redacted>');
|
|
1248
|
+
return text;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function redactValue(value, tokens, seen = new WeakSet()) {
|
|
1252
|
+
if (typeof value === 'string') return redactString(value, tokens);
|
|
1253
|
+
if (value === null || value === undefined || typeof value !== 'object') return value;
|
|
1254
|
+
if (Buffer.isBuffer(value)) return '<binary>';
|
|
1255
|
+
if (seen.has(value)) return '<circular>';
|
|
1256
|
+
seen.add(value);
|
|
1257
|
+
if (Array.isArray(value)) return value.map((entry) => redactValue(entry, tokens, seen));
|
|
1258
|
+
const out = {};
|
|
1259
|
+
for (const [key, entry] of Object.entries(value)) out[key] = redactValue(entry, tokens, seen);
|
|
1260
|
+
return out;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function errorSummary(error, secrets) {
|
|
1264
|
+
const tokens = secretTokens(secrets);
|
|
1265
|
+
const code = error && error.code ? String(error.code) : undefined;
|
|
1266
|
+
const descriptor = describeDiagnostic(code);
|
|
1267
|
+
const diagnostics = error && Array.isArray(error.diagnostics)
|
|
1268
|
+
? Object.freeze(redactValue(error.diagnostics, tokens).map((entry) => decorateDiagnostic(entry)))
|
|
1269
|
+
: undefined;
|
|
1270
|
+
return Object.freeze({
|
|
1271
|
+
name: error && error.name ? String(error.name) : 'Error',
|
|
1272
|
+
code,
|
|
1273
|
+
title: descriptor.title,
|
|
1274
|
+
summary: descriptor.summary,
|
|
1275
|
+
category: descriptor.category,
|
|
1276
|
+
message: redactString(error && error.message ? error.message : String(error), tokens),
|
|
1277
|
+
remediation: descriptor.remediation,
|
|
1278
|
+
stability: descriptor.stability,
|
|
1279
|
+
scope: descriptor.scope,
|
|
1280
|
+
docs: descriptor.docs,
|
|
1281
|
+
detail: error && error.detail ? Object.freeze(redactValue(error.detail, tokens)) : undefined,
|
|
1282
|
+
diagnostics,
|
|
1283
|
+
execution: error && error.execution ? Object.freeze(redactValue({
|
|
1284
|
+
executionId: error.execution.executionId,
|
|
1285
|
+
resolutionOrder: error.execution.resolutionOrder,
|
|
1286
|
+
continuations: error.execution.continuations
|
|
1287
|
+
}, tokens)) : undefined
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function assertExpectedError(testCase, error) {
|
|
1292
|
+
const expected = testCase.expect.error;
|
|
1293
|
+
if (!expected) throw error;
|
|
1294
|
+
if (expected.name !== undefined) assert.equal(error.name, expected.name, `${testCase.name}: unexpected error name`);
|
|
1295
|
+
if (expected.code !== undefined) assert.equal(error.code, expected.code, `${testCase.name}: unexpected error code`);
|
|
1296
|
+
if (expected.category !== undefined) assert.equal(error.category, expected.category, `${testCase.name}: unexpected error category`);
|
|
1297
|
+
if (expected.message !== undefined) assert.match(String(error.message || ''), new RegExp(String(expected.message)), `${testCase.name}: unexpected error message`);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function assertEventExpectation(testCase, execution) {
|
|
1301
|
+
const expected = testCase.expect;
|
|
1302
|
+
const result = eventContracts.normalizeEventExecutionResult(execution.result);
|
|
1303
|
+
assert.equal(result.status, expected.status, `${testCase.name}: unexpected event execution status`);
|
|
1304
|
+
if (expected.error) {
|
|
1305
|
+
assert.ok(result.error, `${testCase.name}: expected an event execution error`);
|
|
1306
|
+
if (expected.error.name !== undefined) assert.equal(result.error.name, expected.error.name, `${testCase.name}: unexpected event error name`);
|
|
1307
|
+
if (expected.error.code !== undefined) assert.equal(result.error.code, expected.error.code, `${testCase.name}: unexpected event error code`);
|
|
1308
|
+
if (expected.error.category !== undefined) assert.equal(result.error.category, expected.error.category, `${testCase.name}: unexpected event error category`);
|
|
1309
|
+
if (expected.error.message !== undefined) assert.match(String(result.error.message || ''), new RegExp(String(expected.error.message)), `${testCase.name}: unexpected event error message`);
|
|
1310
|
+
}
|
|
1311
|
+
assert.deepEqual(execution.emittedFrames, expected.emitted, `${testCase.name}: unexpected emitted event frames`);
|
|
1312
|
+
return result;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
async function executeProviderEventTestCase(driver, target, application, testCase, options = {}) {
|
|
1316
|
+
if (!driver.events || !driver.events.targets.includes(target) || typeof driver.events.executeTestCase !== 'function') {
|
|
1317
|
+
throw new PulseProjectError(
|
|
1318
|
+
'PULSE_EVENT_TARGET_UNSUPPORTED',
|
|
1319
|
+
`The selected ${driver.id}/${target} provider target does not implement event test execution.`,
|
|
1320
|
+
{ provider: driver.id, target, command: 'test', automaticFallback: false }
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
return driver.events.executeTestCase(Object.freeze({
|
|
1324
|
+
target,
|
|
1325
|
+
application,
|
|
1326
|
+
testCase,
|
|
1327
|
+
options: Object.freeze({ ...options })
|
|
1328
|
+
}));
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function eventTestCaseResult(testCase, execution, result, started) {
|
|
1332
|
+
const evidence = execution.executionEvidence || {};
|
|
1333
|
+
const effectCount = Number(evidence.effectCount === undefined ? evidence.ownedEffectCount || 0 : evidence.effectCount);
|
|
1334
|
+
return Object.freeze({
|
|
1335
|
+
name: testCase.name,
|
|
1336
|
+
kind: 'event',
|
|
1337
|
+
status: 'passed',
|
|
1338
|
+
durationMs: Number(process.hrtime.bigint() - started) / 1e6,
|
|
1339
|
+
event: Object.freeze({ type: testCase.event.type, schemaId: testCase.event.schemaId }),
|
|
1340
|
+
result,
|
|
1341
|
+
emittedFrames: Object.freeze(redactValue(execution.emittedFrames, secretTokens(testCase.secrets))),
|
|
1342
|
+
emittedFrameCount: execution.emittedFrames.length,
|
|
1343
|
+
effects: effectCount,
|
|
1344
|
+
adapter: execution.adapter
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
async function runJavascriptProjectTests(project, options = {}) {
|
|
1349
|
+
const descriptor = assertImplementedTarget(project, 'test');
|
|
1350
|
+
const javascript = providerDriver(project).javascript;
|
|
1351
|
+
if (!javascript || typeof javascript.executeTestCase !== 'function' || descriptor.applicationLoader !== true || descriptor.requestAdapter !== true) {
|
|
1352
|
+
throw new PulseProjectError(
|
|
1353
|
+
'PULSE_TARGET_IMPLEMENTATION_PENDING',
|
|
1354
|
+
`pulse test cannot realize the selected javascript target for ${project.provider} yet.`,
|
|
1355
|
+
{ target: 'javascript', command: 'test', provider: project.provider, targetDescriptor: descriptor, automaticFallback: false }
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
const selected = options.caseName ? project.tests.filter((entry) => entry.name === options.caseName) : project.tests;
|
|
1359
|
+
if (selected.length === 0) {
|
|
1360
|
+
throw new PulseProjectError('PULSE_TEST_CASES_MISSING', options.caseName ? `No Pulse test case named ${options.caseName}.` : 'Pulse project defines no test cases.', { available: project.tests.map((entry) => entry.name) });
|
|
1361
|
+
}
|
|
1362
|
+
const localEmulation = javascript.localEmulation === true;
|
|
1363
|
+
const compiled = compileProject(project);
|
|
1364
|
+
const eventSupport = selectedEventTargetSupport(project, compiled, descriptor);
|
|
1365
|
+
if (eventSupport) assertEventCommandEligible(project, compiled, 'test', descriptor);
|
|
1366
|
+
if (selected.some((testCase) => testCase.kind === 'event') && !eventSupport) {
|
|
1367
|
+
throw new PulseProjectError('PULSE_TEST_EVENT_INVALID', 'The test harness contains an event case, but the application has no reachable event topology.');
|
|
1368
|
+
}
|
|
1369
|
+
const prepared = prepareJavascriptApplication(project, {
|
|
1370
|
+
allowedPackages: options.allowedPackages,
|
|
1371
|
+
localEmulation,
|
|
1372
|
+
schemaBundle: compiled.schema.bundle
|
|
1373
|
+
});
|
|
1374
|
+
if (!prepared.loaded) {
|
|
1375
|
+
throw new PulseProjectError('PULSE_JAVASCRIPT_APPLICATION_UNAVAILABLE', 'The graph-backed JavaScript application could not be loaded for project tests.', {
|
|
1376
|
+
planHash: prepared.plan.planHash,
|
|
1377
|
+
blockers: prepared.plan.blockers,
|
|
1378
|
+
automaticFallback: false
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
const schemaCodecs = createCanonicalSchemaCodecs(compiled.schema.bundle.registry);
|
|
1382
|
+
const cases = [];
|
|
1383
|
+
let failed = 0;
|
|
1384
|
+
for (const testCase of selected) {
|
|
1385
|
+
const started = process.hrtime.bigint();
|
|
1386
|
+
const jsonTrace = [];
|
|
1387
|
+
try {
|
|
1388
|
+
if (testCase.kind === 'event') {
|
|
1389
|
+
const execution = await executeProviderEventTestCase(providerDriver(project), 'javascript', prepared.loaded.application, testCase, {
|
|
1390
|
+
application: Object.freeze({
|
|
1391
|
+
projectHash: project.projectHash,
|
|
1392
|
+
planHash: project.planHash,
|
|
1393
|
+
profile: project.profile,
|
|
1394
|
+
target: 'javascript',
|
|
1395
|
+
provider: project.provider
|
|
1396
|
+
}),
|
|
1397
|
+
schemaCodecs,
|
|
1398
|
+
reporting: project.reporting,
|
|
1399
|
+
networkFetch: project.dev.networkFetch
|
|
1400
|
+
});
|
|
1401
|
+
const result = assertEventExpectation(testCase, execution);
|
|
1402
|
+
cases.push(eventTestCaseResult(testCase, execution, result, started));
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
const runtimeOptions = {
|
|
1406
|
+
application: Object.freeze({
|
|
1407
|
+
projectHash: project.projectHash,
|
|
1408
|
+
planHash: project.planHash,
|
|
1409
|
+
profile: project.profile,
|
|
1410
|
+
target: 'javascript',
|
|
1411
|
+
provider: project.provider
|
|
1412
|
+
}),
|
|
1413
|
+
maxBodyBytes: testCase.maxBodyBytes === undefined
|
|
1414
|
+
? (project.schemas.active ? project.schemas.maxBytes : undefined)
|
|
1415
|
+
: testCase.maxBodyBytes,
|
|
1416
|
+
schemaCodecs,
|
|
1417
|
+
strict: project.strict !== false,
|
|
1418
|
+
provider: project.provider,
|
|
1419
|
+
reporting: project.reporting,
|
|
1420
|
+
grip: testCase.grip,
|
|
1421
|
+
onJsonTrace(event) { jsonTrace.push(event); }
|
|
1422
|
+
};
|
|
1423
|
+
const execution = await javascript.executeTestCase(prepared.loaded.application, testCase, {
|
|
1424
|
+
...runtimeOptions,
|
|
1425
|
+
bindings: project.providerConfig.bindings,
|
|
1426
|
+
networkFetch: project.dev.networkFetch
|
|
1427
|
+
});
|
|
1428
|
+
if (testCase.expect.error) throw new assert.AssertionError({ message: `${testCase.name}: expected ${testCase.expect.error.name || 'an error'} but execution completed` });
|
|
1429
|
+
assertExpectation(testCase, Object.freeze({
|
|
1430
|
+
...execution,
|
|
1431
|
+
response: materializeTestResponse(execution.response)
|
|
1432
|
+
}));
|
|
1433
|
+
cases.push(Object.freeze({
|
|
1434
|
+
name: testCase.name,
|
|
1435
|
+
status: 'passed',
|
|
1436
|
+
durationMs: Number(process.hrtime.bigint() - started) / 1e6,
|
|
1437
|
+
response: Object.freeze({ status: execution.response.status, bodyClass: execution.response.bodyClass, kind: execution.response.kind }),
|
|
1438
|
+
effects: execution.effectCount,
|
|
1439
|
+
...(localEmulation ? { providerExecution: execution.provider } : {}),
|
|
1440
|
+
jsonTrace: Object.freeze(jsonTrace),
|
|
1441
|
+
continuations: execution.continuations,
|
|
1442
|
+
resolutionOrder: execution.resolutionOrder
|
|
1443
|
+
}));
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
try {
|
|
1446
|
+
if (testCase.kind === 'event') throw error;
|
|
1447
|
+
assertExpectedError(testCase, error);
|
|
1448
|
+
cases.push(Object.freeze({ name: testCase.name, status: 'passed', expectedError: errorSummary(error, testCase.secrets), durationMs: Number(process.hrtime.bigint() - started) / 1e6 }));
|
|
1449
|
+
} catch (assertion) {
|
|
1450
|
+
failed += 1;
|
|
1451
|
+
cases.push(Object.freeze({ name: testCase.name, status: 'failed', error: errorSummary(assertion === error ? error : assertion, testCase.secrets), actualError: assertion === error ? undefined : errorSummary(error, testCase.secrets), durationMs: Number(process.hrtime.bigint() - started) / 1e6 }));
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return Object.freeze({
|
|
1456
|
+
status: failed === 0 ? 'passed' : 'failed',
|
|
1457
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
1458
|
+
provider: project.provider,
|
|
1459
|
+
target: 'javascript',
|
|
1460
|
+
targetId: descriptor.targetId,
|
|
1461
|
+
automaticFallback: false,
|
|
1462
|
+
project: projectJson(project),
|
|
1463
|
+
applicationPlan: Object.freeze({
|
|
1464
|
+
version: prepared.plan.version,
|
|
1465
|
+
planHash: prepared.plan.planHash,
|
|
1466
|
+
graphHash: prepared.plan.graph.graphHash,
|
|
1467
|
+
nativeEligibility: prepared.plan.nativeEligibility,
|
|
1468
|
+
automaticFallback: false
|
|
1469
|
+
}),
|
|
1470
|
+
metadata: Object.freeze({
|
|
1471
|
+
runtimeClass: descriptor.runtimeClass,
|
|
1472
|
+
sourceApplication: true,
|
|
1473
|
+
schemaRegistryHash: compiled.metadata.schemaRegistryHash,
|
|
1474
|
+
schemaCodecTableHash: compiled.metadata.schemaCodecTableHash,
|
|
1475
|
+
schemaIds: compiled.metadata.schemaIds,
|
|
1476
|
+
responseCaseIds: compiled.metadata.responseCaseIds,
|
|
1477
|
+
...(localEmulation ? {
|
|
1478
|
+
localExecution: Object.freeze({
|
|
1479
|
+
mode: javascript.localExecutionMode,
|
|
1480
|
+
providerReality: false,
|
|
1481
|
+
realityRunner: javascript.realityRunner,
|
|
1482
|
+
automaticFallback: false,
|
|
1483
|
+
loader: Object.freeze({
|
|
1484
|
+
version: prepared.loaded.version,
|
|
1485
|
+
kind: 'provider-owned-bundled-closure',
|
|
1486
|
+
sourceHash: prepared.loaded.closure.sourceHash,
|
|
1487
|
+
bundledInputs: prepared.loaded.closure.inputs.length,
|
|
1488
|
+
packagesLoaded: prepared.loaded.packagesLoaded
|
|
1489
|
+
})
|
|
1490
|
+
})
|
|
1491
|
+
} : {})
|
|
1492
|
+
}),
|
|
1493
|
+
summary: Object.freeze({ total: cases.length, passed: cases.length - failed, failed }),
|
|
1494
|
+
cases: Object.freeze(cases)
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
async function runProjectTests(project, options = {}) {
|
|
1499
|
+
if ((project.target || 'native') === 'javascript') return runJavascriptProjectTests(project, options);
|
|
1500
|
+
const selected = options.caseName ? project.tests.filter((entry) => entry.name === options.caseName) : project.tests;
|
|
1501
|
+
if (selected.length === 0) {
|
|
1502
|
+
throw new PulseProjectError('PULSE_TEST_CASES_MISSING', options.caseName ? `No Pulse test case named ${options.caseName}.` : 'Pulse project defines no test cases.', { available: project.tests.map((entry) => entry.name) });
|
|
1503
|
+
}
|
|
1504
|
+
const hasEventCase = selected.some((testCase) => testCase.kind === 'event');
|
|
1505
|
+
const descriptor = targetDescriptor(project, 'native');
|
|
1506
|
+
let compiled = hasEventCase ? compileProject(project) : null;
|
|
1507
|
+
const eventSupport = hasEventCase ? selectedEventTargetSupport(project, compiled, descriptor) : null;
|
|
1508
|
+
if (hasEventCase && eventSupport) assertEventCommandEligible(project, compiled, 'test', descriptor);
|
|
1509
|
+
if (hasEventCase && !eventSupport) {
|
|
1510
|
+
throw new PulseProjectError('PULSE_TEST_EVENT_INVALID', 'The test harness contains an event case, but the application has no reachable event topology.');
|
|
1511
|
+
}
|
|
1512
|
+
const driver = assertExecutableProvider(project, 'test');
|
|
1513
|
+
assertImplementedTarget(project, 'test');
|
|
1514
|
+
const executeCanonicalProgram = driver.execute;
|
|
1515
|
+
if (!compiled) compiled = compileProject(project);
|
|
1516
|
+
const exactNativeCrypto = requiresExactNativeCrypto(compiled);
|
|
1517
|
+
const program = exactNativeCrypto ? null : loadCanonicalModule(compiled);
|
|
1518
|
+
const native = hasEventCase || exactNativeCrypto
|
|
1519
|
+
? compileNativeProjectInMemory(project, { ...options, compiled }).native
|
|
1520
|
+
: null;
|
|
1521
|
+
const cases = [];
|
|
1522
|
+
let failed = 0;
|
|
1523
|
+
for (const testCase of selected) {
|
|
1524
|
+
const started = process.hrtime.bigint();
|
|
1525
|
+
try {
|
|
1526
|
+
if (testCase.kind === 'event') {
|
|
1527
|
+
const schemaCodecs = createCanonicalSchemaCodecs(compiled.schema.bundle.registry);
|
|
1528
|
+
const execution = await executeProviderEventTestCase(driver, 'native', native, testCase, {
|
|
1529
|
+
application: Object.freeze({
|
|
1530
|
+
projectHash: project.projectHash,
|
|
1531
|
+
planHash: project.planHash,
|
|
1532
|
+
profile: project.profile,
|
|
1533
|
+
target: 'native',
|
|
1534
|
+
provider: project.provider
|
|
1535
|
+
}),
|
|
1536
|
+
schemaCodecs,
|
|
1537
|
+
reporting: project.reporting,
|
|
1538
|
+
networkFetch: project.dev.networkFetch
|
|
1539
|
+
});
|
|
1540
|
+
const result = assertEventExpectation(testCase, execution);
|
|
1541
|
+
cases.push(eventTestCaseResult(testCase, execution, result, started));
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
const executionOptions = providerExecutionOptions(project, {
|
|
1545
|
+
packageArtifacts: packageRealizationArtifactsForCompiled(compiled),
|
|
1546
|
+
request: testCase.request,
|
|
1547
|
+
fetches: testCase.fetches,
|
|
1548
|
+
config: testCase.config,
|
|
1549
|
+
secrets: testCase.secrets,
|
|
1550
|
+
kv: testCase.kv,
|
|
1551
|
+
grip: testCase.grip,
|
|
1552
|
+
maxBodyBytes: testCase.maxBodyBytes === undefined
|
|
1553
|
+
? (project.schemas.active ? project.schemas.maxBytes : undefined)
|
|
1554
|
+
: testCase.maxBodyBytes,
|
|
1555
|
+
continuationTtlMs: testCase.continuationTtlMs,
|
|
1556
|
+
executionId: `test:${testCase.name}`
|
|
1557
|
+
});
|
|
1558
|
+
const execution = exactNativeCrypto
|
|
1559
|
+
? await executeCanonicalNativeModule(native, executionOptions)
|
|
1560
|
+
: await executeCanonicalProgram(program, executionOptions);
|
|
1561
|
+
if (testCase.expect.error) throw new assert.AssertionError({ message: `${testCase.name}: expected ${testCase.expect.error.name || 'an error'} but execution completed` });
|
|
1562
|
+
assertExpectation(testCase, Object.freeze({
|
|
1563
|
+
...execution,
|
|
1564
|
+
response: materializeTestResponse(execution.response)
|
|
1565
|
+
}));
|
|
1566
|
+
cases.push(Object.freeze({
|
|
1567
|
+
name: testCase.name,
|
|
1568
|
+
status: 'passed',
|
|
1569
|
+
durationMs: Number(process.hrtime.bigint() - started) / 1e6,
|
|
1570
|
+
response: Object.freeze({ status: execution.response.status, bodyClass: execution.response.bodyClass, kind: execution.response.kind }),
|
|
1571
|
+
effects: execution.effectCount,
|
|
1572
|
+
continuations: execution.continuations.map((entry) => Object.freeze({ id: entry.id, state: entry.state, effectIds: entry.effectIds })),
|
|
1573
|
+
resolutionOrder: execution.resolutionOrder
|
|
1574
|
+
}));
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
try {
|
|
1577
|
+
if (testCase.kind === 'event') throw error;
|
|
1578
|
+
assertExpectedError(testCase, error);
|
|
1579
|
+
cases.push(Object.freeze({ name: testCase.name, status: 'passed', expectedError: errorSummary(error, testCase.secrets), durationMs: Number(process.hrtime.bigint() - started) / 1e6 }));
|
|
1580
|
+
} catch (assertion) {
|
|
1581
|
+
failed += 1;
|
|
1582
|
+
cases.push(Object.freeze({ name: testCase.name, status: 'failed', error: errorSummary(assertion === error ? error : assertion, testCase.secrets), actualError: assertion === error ? undefined : errorSummary(error, testCase.secrets), durationMs: Number(process.hrtime.bigint() - started) / 1e6 }));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return Object.freeze({
|
|
1587
|
+
status: failed === 0 ? 'passed' : 'failed',
|
|
1588
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
1589
|
+
provider: project.provider,
|
|
1590
|
+
project: projectJson(project),
|
|
1591
|
+
metadata: compiled.metadata,
|
|
1592
|
+
summary: Object.freeze({ total: cases.length, passed: cases.length - failed, failed }),
|
|
1593
|
+
cases: Object.freeze(cases)
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function inspectProject(project, options = {}) {
|
|
1598
|
+
const compiled = compileProject(project);
|
|
1599
|
+
const selectedTarget = project.target || 'native';
|
|
1600
|
+
let nativePlan = null;
|
|
1601
|
+
let native = null;
|
|
1602
|
+
let nativeInspectionError = null;
|
|
1603
|
+
try {
|
|
1604
|
+
const prepared = compileNativeProjectInMemory(project, { ...options, compiled });
|
|
1605
|
+
nativePlan = prepared.plan;
|
|
1606
|
+
native = prepared.native;
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
if (selectedTarget !== 'javascript' && !compiled.packageApplication) throw error;
|
|
1609
|
+
nativeInspectionError = errorSummary(error, project.dev && project.dev.secrets);
|
|
1610
|
+
}
|
|
1611
|
+
const driver = providerDriver(project);
|
|
1612
|
+
const availability = providerTargetAvailability(driver);
|
|
1613
|
+
const selectedDescriptor = targetDescriptor(project);
|
|
1614
|
+
const eventSupport = selectedEventTargetSupport(project, compiled, selectedDescriptor);
|
|
1615
|
+
const eventInspection = eventInspectionProjection(project, compiled, eventSupport);
|
|
1616
|
+
const javascriptApplication = selectedTarget === 'javascript'
|
|
1617
|
+
? prepareJavascriptApplication(project, { captureLoadError: true })
|
|
1618
|
+
: null;
|
|
1619
|
+
const targetSupport = javascriptApplication
|
|
1620
|
+
? selectedTargetSupportEvidence(project, compiled, javascriptApplication, selectedDescriptor, options)
|
|
1621
|
+
: null;
|
|
1622
|
+
const effectiveAvailability = targetSupport && options.targetSupportDeclaration
|
|
1623
|
+
? Object.freeze({ ...availability, javascript: targetSupport.availability.generalAvailable })
|
|
1624
|
+
: availability;
|
|
1625
|
+
const referencedSchemaIds = new Set(compiled.metadata.schemaReferences.map((entry) => entry.id));
|
|
1626
|
+
for (const schemaId of eventInspection && eventInspection.schemas.all || []) referencedSchemaIds.add(schemaId);
|
|
1627
|
+
const realization = selectedTarget === 'native'
|
|
1628
|
+
? (nativePlan && native && driver.inspectRealization && (!eventSupport || eventSupport.status !== 'blocked')
|
|
1629
|
+
? normalizeProviderTargetResult(driver.inspectRealization(providerTargetInvocation(
|
|
1630
|
+
project,
|
|
1631
|
+
'inspect-native',
|
|
1632
|
+
nativePlan,
|
|
1633
|
+
providerLoweringPlan(project, compiled.metadata),
|
|
1634
|
+
{
|
|
1635
|
+
native,
|
|
1636
|
+
optimization: native && native.manifest && native.manifest.optimization,
|
|
1637
|
+
timeoutMs: options.timeoutMs
|
|
1638
|
+
}
|
|
1639
|
+
)), { action: 'inspect-native', provider: project.provider, target: 'native' }).realization
|
|
1640
|
+
: Object.freeze({
|
|
1641
|
+
target: selectedDescriptor.targetId,
|
|
1642
|
+
provider: project.provider,
|
|
1643
|
+
runtimeClass: selectedDescriptor.runtimeClass,
|
|
1644
|
+
status: 'unavailable-for-project',
|
|
1645
|
+
nativeWasm: false,
|
|
1646
|
+
providerRealityValidated: false,
|
|
1647
|
+
automaticFallback: false,
|
|
1648
|
+
error: eventSupport && eventSupport.status === 'blocked'
|
|
1649
|
+
? Object.freeze({ code: eventSupport.diagnosticCode, eventTargetSupport: eventSupport })
|
|
1650
|
+
: nativeInspectionError
|
|
1651
|
+
}))
|
|
1652
|
+
: (javascriptApplication ? Object.freeze({
|
|
1653
|
+
target: selectedDescriptor.targetId,
|
|
1654
|
+
provider: project.provider,
|
|
1655
|
+
runtimeClass: selectedDescriptor.runtimeClass,
|
|
1656
|
+
sourceApplication: true,
|
|
1657
|
+
status: eventSupport && eventSupport.status === 'blocked'
|
|
1658
|
+
? 'unavailable-for-project'
|
|
1659
|
+
: selectedDescriptor.status,
|
|
1660
|
+
javascriptRuntime: true,
|
|
1661
|
+
nativeWasm: false,
|
|
1662
|
+
automaticFallback: false,
|
|
1663
|
+
applicationPlan: Object.freeze({
|
|
1664
|
+
version: javascriptApplication.plan.version,
|
|
1665
|
+
plannerVersion: javascriptApplication.plan.plannerVersion,
|
|
1666
|
+
planHash: javascriptApplication.plan.planHash,
|
|
1667
|
+
graphHash: javascriptApplication.plan.graph.graphHash,
|
|
1668
|
+
loadable: javascriptApplication.plan.loadable,
|
|
1669
|
+
blockers: javascriptApplication.plan.blockers,
|
|
1670
|
+
modules: javascriptApplication.plan.graph.modules.filter((entry) => entry.runtime && entry.kind === 'project').length,
|
|
1671
|
+
packages: javascriptApplication.plan.packages,
|
|
1672
|
+
nativeEligibility: javascriptApplication.plan.nativeEligibility,
|
|
1673
|
+
entrySafety: javascriptApplication.plan.entrySafety
|
|
1674
|
+
}),
|
|
1675
|
+
loader: targetSupport ? Object.freeze({
|
|
1676
|
+
...targetSupport.loader,
|
|
1677
|
+
applicationValid: targetSupport.loader.loaded === true
|
|
1678
|
+
|| (targetSupport.loader.skippedReason === 'provider-build-time-bundle-loader'
|
|
1679
|
+
&& javascriptApplication.plan.loadable === true)
|
|
1680
|
+
}) : null,
|
|
1681
|
+
loadError: javascriptApplication.loadError,
|
|
1682
|
+
supportEvidenceHash: targetSupport && targetSupport.evidenceHash,
|
|
1683
|
+
coreExecutionReady: targetSupport && targetSupport.availability.coreExecutionReady,
|
|
1684
|
+
fullTargetSupportReady: targetSupport && targetSupport.availability.fullTargetSupportReady,
|
|
1685
|
+
generalAvailable: targetSupport && targetSupport.availability.generalAvailable,
|
|
1686
|
+
projectEligibility: targetSupport && targetSupport.project.status,
|
|
1687
|
+
requestAdapter: selectedDescriptor.requestAdapter,
|
|
1688
|
+
lifecycle: selectedDescriptor.lifecycle,
|
|
1689
|
+
...(eventSupport && eventSupport.status === 'blocked' ? {
|
|
1690
|
+
error: Object.freeze({ code: eventSupport.diagnosticCode, eventTargetSupport: eventSupport })
|
|
1691
|
+
} : {}),
|
|
1692
|
+
...(driver.javascript && driver.javascript.localExecutionMode ? {
|
|
1693
|
+
localTooling: Object.freeze({
|
|
1694
|
+
test: driver.javascript.localExecutionMode,
|
|
1695
|
+
dev: driver.javascript.localExecutionMode,
|
|
1696
|
+
loader: 'provider-owned-bundled-closure',
|
|
1697
|
+
packageClosure: 'esbuild-bundled',
|
|
1698
|
+
providerReality: false,
|
|
1699
|
+
realityRunner: driver.javascript.realityRunner,
|
|
1700
|
+
automaticFallback: false
|
|
1701
|
+
}),
|
|
1702
|
+
deploymentCandidate: Object.freeze({
|
|
1703
|
+
status: 'structurally-deployable',
|
|
1704
|
+
emittedBy: 'pulse build',
|
|
1705
|
+
providerRealityValidated: false,
|
|
1706
|
+
deployed: false
|
|
1707
|
+
})
|
|
1708
|
+
} : {})
|
|
1709
|
+
}) : undefined);
|
|
1710
|
+
return Object.freeze({
|
|
1711
|
+
status: 'ok',
|
|
1712
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
1713
|
+
project: projectJson(project),
|
|
1714
|
+
provider: Object.freeze({
|
|
1715
|
+
id: project.provider,
|
|
1716
|
+
package: driver.descriptor && driver.descriptor.package,
|
|
1717
|
+
buildTarget: driver.descriptor && driver.descriptor.buildTarget,
|
|
1718
|
+
selectedTarget,
|
|
1719
|
+
targetAvailability: Object.freeze({ native: effectiveAvailability.native, javascript: effectiveAvailability.javascript }),
|
|
1720
|
+
targetDescriptors: effectiveAvailability.descriptors,
|
|
1721
|
+
selectedTargetDescriptor: selectedDescriptor,
|
|
1722
|
+
targetSupport,
|
|
1723
|
+
eventTargetSupport: eventSupport,
|
|
1724
|
+
defaultBuildMode: driver.defaultBuildMode,
|
|
1725
|
+
sourceOnlySupported: false,
|
|
1726
|
+
realization
|
|
1727
|
+
}),
|
|
1728
|
+
plan: projectPlanInspection(project, compiled),
|
|
1729
|
+
compiler: Object.freeze({
|
|
1730
|
+
version: compiled.version,
|
|
1731
|
+
file: compiled.metadata.file,
|
|
1732
|
+
sourceHash: compiled.metadata.sourceHash,
|
|
1733
|
+
authoring: compiled.metadata.authoring || Object.freeze({ kind: 'handler' }),
|
|
1734
|
+
...(compiled.metadata.application ? { application: compiled.metadata.application } : {}),
|
|
1735
|
+
...(compiled.metadata.json ? { json: compiled.metadata.json } : {}),
|
|
1736
|
+
...((compiled.metadata.warnings && compiled.metadata.warnings.length > 0) ? { warnings: compiled.metadata.warnings } : {}),
|
|
1737
|
+
warningCount: compiled.metadata.warningCount || 0,
|
|
1738
|
+
jsonPolicy: jsonPolicyInspection(project, compiled, nativePlan),
|
|
1739
|
+
requestState: nativePlan && nativePlan.requestState || Object.freeze({ enabled: false, representation: 'absent', reset: 'not-required' }),
|
|
1740
|
+
routing: nativePlan && nativePlan.routing || null,
|
|
1741
|
+
capabilities: compiled.metadata.capabilities,
|
|
1742
|
+
effects: compiled.metadata.effectSites,
|
|
1743
|
+
continuations: compiled.metadata.continuationSites,
|
|
1744
|
+
effectCount: compiled.metadata.effectCount,
|
|
1745
|
+
continuationCount: compiled.metadata.continuationCount,
|
|
1746
|
+
groupedContinuationCount: compiled.metadata.groupedContinuationCount,
|
|
1747
|
+
opaqueReturnCount: compiled.metadata.opaqueReturnCount,
|
|
1748
|
+
providerOperations: compiled.metadata.providerOperations,
|
|
1749
|
+
providerLowering: eventSupport && eventSupport.status === 'blocked'
|
|
1750
|
+
? Object.freeze({
|
|
1751
|
+
status: 'blocked',
|
|
1752
|
+
code: eventSupport.diagnosticCode,
|
|
1753
|
+
eventTargetSupport: eventSupport,
|
|
1754
|
+
automaticFallback: false
|
|
1755
|
+
})
|
|
1756
|
+
: providerLoweringPlan(project, compiled.metadata),
|
|
1757
|
+
reachableGraph: Object.freeze({
|
|
1758
|
+
version: compiled.reachableGraph.version,
|
|
1759
|
+
graphHash: compiled.reachableGraph.graphHash,
|
|
1760
|
+
modules: compiled.reachableGraph.modules.length,
|
|
1761
|
+
runtimeModules: compiled.reachableGraph.modules.filter((entry) => entry.runtime).length,
|
|
1762
|
+
edges: compiled.reachableGraph.edges.length,
|
|
1763
|
+
handlers: compiled.reachableGraph.handlers.length,
|
|
1764
|
+
cycles: compiled.reachableGraph.cycles.length,
|
|
1765
|
+
unsupportedBoundaries: compiled.reachableGraph.unsupportedBoundaries.length
|
|
1766
|
+
}),
|
|
1767
|
+
packageReachability: compiled.packageReachability,
|
|
1768
|
+
packageProduct: compiled.packageProduct,
|
|
1769
|
+
packageInspection: compiled.packageInspection,
|
|
1770
|
+
...(eventInspection ? { events: eventInspection } : {}),
|
|
1771
|
+
...(compiled.packageApplication ? { packageApplication: compiled.packageApplication } : {}),
|
|
1772
|
+
applicationEntrySafety: compiled.entrySafety,
|
|
1773
|
+
nativeEligibility: compiled.nativeEligibility,
|
|
1774
|
+
native: nativePlan && native ? Object.freeze({
|
|
1775
|
+
status: 'available',
|
|
1776
|
+
target: 'portable-native-wasm',
|
|
1777
|
+
providerNeutral: true,
|
|
1778
|
+
requiredForSelectedTarget: selectedTarget === 'native',
|
|
1779
|
+
selectedTarget,
|
|
1780
|
+
automaticFallback: false,
|
|
1781
|
+
planVersion: nativePlan.version,
|
|
1782
|
+
planCompilerVersion: nativePlan.compilerVersion,
|
|
1783
|
+
planHash: nativePlan.planHash,
|
|
1784
|
+
ownership: nativePlan.ownership,
|
|
1785
|
+
summary: nativePlan.summary,
|
|
1786
|
+
...(nativePlan.json ? { json: nativePlan.json } : {}),
|
|
1787
|
+
...(nativePlan.requestState ? { requestState: nativePlan.requestState } : {}),
|
|
1788
|
+
compilerVersion: native.compilerVersion,
|
|
1789
|
+
assemblyScript: native.manifest.assemblyScript,
|
|
1790
|
+
wasm: native.manifest.wasm,
|
|
1791
|
+
wat: native.manifest.wat,
|
|
1792
|
+
importModules: native.manifest.importModules,
|
|
1793
|
+
imports: native.manifest.imports,
|
|
1794
|
+
exports: native.manifest.exports,
|
|
1795
|
+
policy: native.manifest.policy
|
|
1796
|
+
}) : Object.freeze({
|
|
1797
|
+
status: 'unavailable-for-project',
|
|
1798
|
+
target: 'portable-native-wasm',
|
|
1799
|
+
providerNeutral: true,
|
|
1800
|
+
requiredForSelectedTarget: selectedTarget === 'native',
|
|
1801
|
+
selectedTarget,
|
|
1802
|
+
automaticFallback: false,
|
|
1803
|
+
error: nativeInspectionError
|
|
1804
|
+
}),
|
|
1805
|
+
schemas: Object.freeze({
|
|
1806
|
+
active: compiled.schema.active,
|
|
1807
|
+
compilerVersion: compiled.schema.compilerVersion,
|
|
1808
|
+
sourceHash: compiled.metadata.schemaSourceHash,
|
|
1809
|
+
registry: compiled.metadata.schemaRegistry,
|
|
1810
|
+
references: compiled.metadata.schemaReferences,
|
|
1811
|
+
unused: Object.freeze(compiled.metadata.schemaIds.filter((id) => !referencedSchemaIds.has(id))),
|
|
1812
|
+
diagnostics: compiled.schema.diagnostics,
|
|
1813
|
+
warnings: compiled.schema.warnings
|
|
1814
|
+
})
|
|
1815
|
+
}),
|
|
1816
|
+
...(eventInspection ? { events: eventInspection } : {}),
|
|
1817
|
+
testCases: project.tests.map((entry) => entry.name)
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function doctorProject(project, options = {}) {
|
|
1822
|
+
const checks = [];
|
|
1823
|
+
const selectedTarget = project.target || 'native';
|
|
1824
|
+
const selectedTargetDescriptor = targetDescriptor(project, selectedTarget);
|
|
1825
|
+
const targetSupportDeclaration = options.targetSupportDeclaration || getProviderTargetSupportDeclaration(providerDriver(project), selectedTarget);
|
|
1826
|
+
const providerAvailability = providerTargetAvailability(providerDriver(project));
|
|
1827
|
+
const targetAvailability = options.targetSupportDeclaration && targetSupportDeclaration
|
|
1828
|
+
? Object.freeze({ ...providerAvailability, javascript: targetSupportDeclaration.availability.generalAvailable })
|
|
1829
|
+
: providerAvailability;
|
|
1830
|
+
checks.push(Object.freeze({
|
|
1831
|
+
id: 'execution-target',
|
|
1832
|
+
status: selectedTarget === 'javascript' && targetAvailability.javascript !== true ? 'warning' : 'passed',
|
|
1833
|
+
message: selectedTarget === 'javascript'
|
|
1834
|
+
? (targetSupportDeclaration
|
|
1835
|
+
? (targetSupportDeclaration.availability.generalAvailable
|
|
1836
|
+
? `${project.provider} JavaScript full target support is sealed and generally available; the selected JavaScript target remains authoritative and no automatic fallback will occur.`
|
|
1837
|
+
: `Core ${project.provider} JavaScript execution is ready, but general availability remains false under the full-target-support policy while ${targetSupportDeclaration.availability.summary.pending} gate(s) remain pending; no automatic fallback will occur.`)
|
|
1838
|
+
: selectedTargetDescriptor.applicationLoader
|
|
1839
|
+
? 'The JavaScript application loader is present, but this provider does not declare full target support; no automatic fallback will occur.'
|
|
1840
|
+
: selectedTargetDescriptor.status === 'source-packaging-ready'
|
|
1841
|
+
? 'The selected JavaScript source-packaging boundary is ready, while provider execution remains deferred; no automatic fallback will occur.'
|
|
1842
|
+
: 'The selected JavaScript target is recorded but not realized for this provider; no automatic fallback will occur.')
|
|
1843
|
+
: 'The selected native target is available.',
|
|
1844
|
+
detail: Object.freeze({
|
|
1845
|
+
selected: selectedTarget,
|
|
1846
|
+
native: targetAvailability.native,
|
|
1847
|
+
javascript: targetAvailability.javascript,
|
|
1848
|
+
descriptor: selectedTargetDescriptor,
|
|
1849
|
+
targetSupport: targetSupportDeclaration,
|
|
1850
|
+
automaticFallback: false
|
|
1851
|
+
})
|
|
1852
|
+
}));
|
|
1853
|
+
function check(id, status, message, detail = {}, explicitCode) {
|
|
1854
|
+
const code = explicitCode || detail.code;
|
|
1855
|
+
const descriptor = code ? describeDiagnostic(code) : undefined;
|
|
1856
|
+
checks.push(Object.freeze({
|
|
1857
|
+
id,
|
|
1858
|
+
status,
|
|
1859
|
+
message,
|
|
1860
|
+
code,
|
|
1861
|
+
title: descriptor && descriptor.title,
|
|
1862
|
+
summary: descriptor && descriptor.summary,
|
|
1863
|
+
category: descriptor && descriptor.category,
|
|
1864
|
+
remediation: descriptor && descriptor.remediation,
|
|
1865
|
+
stability: descriptor && descriptor.stability,
|
|
1866
|
+
scope: descriptor && descriptor.scope,
|
|
1867
|
+
docs: descriptor && descriptor.docs,
|
|
1868
|
+
detail
|
|
1869
|
+
}));
|
|
1870
|
+
}
|
|
1871
|
+
const nodeSupported = nodeVersionSatisfiesEngines(process.versions.node, releaseNode.nodeEngines);
|
|
1872
|
+
check(
|
|
1873
|
+
'node-version',
|
|
1874
|
+
nodeSupported ? 'passed' : 'failed',
|
|
1875
|
+
`Node ${process.versions.node}`,
|
|
1876
|
+
{
|
|
1877
|
+
engines: releaseNode.nodeEngines,
|
|
1878
|
+
minimumVersion: releaseNode.nodeMinimumVersion,
|
|
1879
|
+
sealRange: releaseNode.nodeReleaseRange,
|
|
1880
|
+
reproducibleToolchainVersion: releaseNode.nodeVersion
|
|
1881
|
+
},
|
|
1882
|
+
nodeSupported ? undefined : 'PULSE_NODE_VERSION_UNSUPPORTED'
|
|
1883
|
+
);
|
|
1884
|
+
check('config', project.configFile ? 'passed' : 'warning', project.configFile ? `Loaded ${relative(project.configFile, project.root)}` : 'No config file; using explicit entry defaults.', {}, project.configFile ? undefined : 'PULSE_CONFIG_IMPLICIT');
|
|
1885
|
+
check('entry', fs.existsSync(project.entryFile) ? 'passed' : 'failed', relative(project.entryFile, project.root), { file: relative(project.entryFile, project.root) }, fs.existsSync(project.entryFile) ? undefined : 'PULSE_ENTRY_NOT_FOUND');
|
|
1886
|
+
if (project.profilePlan) {
|
|
1887
|
+
check('project-plan', 'passed', `Selected profile ${project.profile.name} from ${project.profile.source} with plan ${String(project.planHash).slice(0, 12)}…`, projectPlanInspection(project));
|
|
1888
|
+
}
|
|
1889
|
+
let compiled;
|
|
1890
|
+
let nativePrepared;
|
|
1891
|
+
let preparedJavascript;
|
|
1892
|
+
let targetSupport;
|
|
1893
|
+
let eventSupport;
|
|
1894
|
+
let eventInspection;
|
|
1895
|
+
try {
|
|
1896
|
+
compiled = compileProject(project);
|
|
1897
|
+
check('canonical-compile', (compiled.metadata.warningCount || 0) > 0 ? 'warning' : 'passed', `Canonical source lowered with ${compiled.metadata.effectCount} effect(s), ${compiled.metadata.continuationCount} continuation(s), and ${compiled.metadata.warningCount || 0} warning(s).`, { capabilities: compiled.metadata.capabilities, warnings: compiled.metadata.warnings || [] });
|
|
1898
|
+
eventSupport = selectedEventTargetSupport(project, compiled, selectedTargetDescriptor);
|
|
1899
|
+
if (eventSupport) {
|
|
1900
|
+
eventInspection = eventInspectionProjection(project, compiled, eventSupport);
|
|
1901
|
+
check(
|
|
1902
|
+
'events',
|
|
1903
|
+
eventSupport.status === 'eligible' ? 'passed' : (eventSupport.status === 'inspection-only' ? 'warning' : 'failed'),
|
|
1904
|
+
eventSupport.status === 'eligible'
|
|
1905
|
+
? `The ${project.provider}/${selectedTarget} target satisfies ${eventSupport.requirements.length} reachable event requirement(s).`
|
|
1906
|
+
: eventSupport.status === 'inspection-only'
|
|
1907
|
+
? 'The event catalog is available for compile-only inspection; no execution provider is selected.'
|
|
1908
|
+
: `The ${project.provider}/${selectedTarget} target cannot realize the reachable event plane and no fallback will occur.`,
|
|
1909
|
+
eventInspection,
|
|
1910
|
+
eventSupport.status === 'blocked' ? eventSupport.diagnosticCode : undefined
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
const referencedSchemaIds = new Set(compiled.metadata.schemaReferences.map((entry) => entry.id));
|
|
1914
|
+
for (const schemaId of eventInspection && eventInspection.schemas.all || []) referencedSchemaIds.add(schemaId);
|
|
1915
|
+
const unusedSchemaIds = compiled.metadata.schemaIds.filter((id) => !referencedSchemaIds.has(id));
|
|
1916
|
+
check(
|
|
1917
|
+
'schemas',
|
|
1918
|
+
unusedSchemaIds.length > 0 ? 'warning' : 'passed',
|
|
1919
|
+
compiled.schema.active
|
|
1920
|
+
? `Compiled ${compiled.metadata.schemaIds.length} JSON schema(s) with ${compiled.metadata.schemaReferenceCount} linked HTTP handler reference(s) and ${eventInspection ? eventInspection.schemas.all.length : 0} linked event schema(s).`
|
|
1921
|
+
: 'No explicit JSON schemas are configured.',
|
|
1922
|
+
{
|
|
1923
|
+
ids: compiled.metadata.schemaIds,
|
|
1924
|
+
references: compiled.metadata.schemaReferences,
|
|
1925
|
+
eventReferences: eventInspection ? eventInspection.schemas.all : Object.freeze([]),
|
|
1926
|
+
unused: unusedSchemaIds,
|
|
1927
|
+
sourceHash: compiled.metadata.schemaSourceHash,
|
|
1928
|
+
dependencies: compiled.watchFiles.map((file) => relative(file, project.root))
|
|
1929
|
+
}
|
|
1930
|
+
);
|
|
1931
|
+
const jsonPolicy = jsonPolicyInspection(project, compiled);
|
|
1932
|
+
check(
|
|
1933
|
+
'json-policy',
|
|
1934
|
+
'passed',
|
|
1935
|
+
jsonPolicy.genericParserRequired
|
|
1936
|
+
? `Non-strict schema-less JSON is enabled for ${jsonPolicy.genericRequestCount} reachable call(s); native realization uses the explicit host generic parser path.`
|
|
1937
|
+
: (jsonPolicy.strict ? 'Strict schema-bound JSON policy is active.' : 'Non-strict JSON policy is active, but no schema-less JSON call is reachable.'),
|
|
1938
|
+
jsonPolicy
|
|
1939
|
+
);
|
|
1940
|
+
nativePrepared = compileNativeProjectInMemory(project, { compiled });
|
|
1941
|
+
if (nativePrepared.plan.requestState && nativePrepared.plan.requestState.enabled) {
|
|
1942
|
+
check('request-state', 'passed', 'Request-local ctx.state is guest-owned, survives continuations, and resets at pulse_start.', nativePrepared.plan.requestState);
|
|
1943
|
+
}
|
|
1944
|
+
check(
|
|
1945
|
+
'canonical-native-plan',
|
|
1946
|
+
'passed',
|
|
1947
|
+
`Provider-neutral native plan ${nativePrepared.plan.planHash.slice(0, 12)}… contains ${nativePrepared.plan.summary.statementCount} statement(s).`,
|
|
1948
|
+
{
|
|
1949
|
+
version: nativePrepared.plan.version,
|
|
1950
|
+
compilerVersion: nativePrepared.plan.compilerVersion,
|
|
1951
|
+
planHash: nativePrepared.plan.planHash,
|
|
1952
|
+
ownership: nativePrepared.plan.ownership,
|
|
1953
|
+
summary: nativePrepared.plan.summary
|
|
1954
|
+
}
|
|
1955
|
+
);
|
|
1956
|
+
check(
|
|
1957
|
+
'canonical-native-wasm',
|
|
1958
|
+
'passed',
|
|
1959
|
+
`Compiled ${nativePrepared.native.manifest.wasm.bytes} bytes of provider-neutral Pulse Wasm with AssemblyScript ${nativePrepared.native.manifest.assemblyScript.version}.`,
|
|
1960
|
+
{
|
|
1961
|
+
version: nativePrepared.native.version,
|
|
1962
|
+
compilerVersion: nativePrepared.native.compilerVersion,
|
|
1963
|
+
assemblyScript: nativePrepared.native.manifest.assemblyScript,
|
|
1964
|
+
wasm: nativePrepared.native.manifest.wasm,
|
|
1965
|
+
importModules: nativePrepared.native.manifest.importModules,
|
|
1966
|
+
planHash: nativePrepared.plan.planHash
|
|
1967
|
+
}
|
|
1968
|
+
);
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
const nativeFailure = error && ['PULSE_CANONICAL_NATIVE_PLAN_FAILED', 'PULSE_CANONICAL_NATIVE_AS_GENERATION_FAILED', 'PULSE_CANONICAL_NATIVE_COMPILE_FAILED'].includes(error.code);
|
|
1971
|
+
if (nativeFailure && compiled) {
|
|
1972
|
+
const selectedTargetRequiresNative = selectedTarget === 'native';
|
|
1973
|
+
check(
|
|
1974
|
+
error.code === 'PULSE_CANONICAL_NATIVE_PLAN_FAILED' ? 'canonical-native-plan' : 'canonical-native-wasm',
|
|
1975
|
+
selectedTargetRequiresNative ? 'failed' : 'warning',
|
|
1976
|
+
selectedTargetRequiresNative
|
|
1977
|
+
? error.message
|
|
1978
|
+
: `Provider-neutral native realization is unavailable for this project, but the selected JavaScript target is inspected independently and no native fallback will occur: ${error.message}`,
|
|
1979
|
+
{
|
|
1980
|
+
code: error.code,
|
|
1981
|
+
diagnostics: error.diagnostics,
|
|
1982
|
+
detail: error.detail,
|
|
1983
|
+
selectedTarget,
|
|
1984
|
+
requiredForSelectedTarget: selectedTargetRequiresNative,
|
|
1985
|
+
automaticFallback: false
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
} else {
|
|
1989
|
+
check('canonical-compile', 'failed', error.message, { code: error.code, diagnostics: error.diagnostics });
|
|
1990
|
+
}
|
|
1991
|
+
const schemaFailure = error.code === 'PULSE_SCHEMA_COMPILE_FAILED' || (error.diagnostics || []).some((entry) => String(entry.code || '').includes('SCHEMA'));
|
|
1992
|
+
if (schemaFailure) check('schemas', 'failed', error.message, { code: error.code, diagnostics: error.diagnostics, detail: error.detail });
|
|
1993
|
+
}
|
|
1994
|
+
if (selectedTarget === 'javascript') {
|
|
1995
|
+
try {
|
|
1996
|
+
const javascript = providerDriver(project).javascript;
|
|
1997
|
+
preparedJavascript = prepareJavascriptApplication(project, { captureLoadError: true });
|
|
1998
|
+
targetSupport = selectedTargetSupportEvidence(project, compiled, preparedJavascript, selectedTargetDescriptor, options);
|
|
1999
|
+
const applicationDecision = targetSupport && targetSupport.project.application.find((entry) => entry.id === 'application.plan');
|
|
2000
|
+
const buildTimeLoaderReady = javascript && javascript.buildTimeLoader === true
|
|
2001
|
+
&& preparedJavascript.plan.loadable === true
|
|
2002
|
+
&& targetSupport
|
|
2003
|
+
&& targetSupport.loader.skippedReason === 'provider-build-time-bundle-loader'
|
|
2004
|
+
&& targetSupport.project.status === 'eligible';
|
|
2005
|
+
const applicationStatus = applicationDecision && applicationDecision.status === 'pending'
|
|
2006
|
+
? 'warning'
|
|
2007
|
+
: preparedJavascript.loaded || buildTimeLoaderReady
|
|
2008
|
+
? 'passed'
|
|
2009
|
+
: targetSupport && targetSupport.project.status === 'pending'
|
|
2010
|
+
? 'warning'
|
|
2011
|
+
: 'failed';
|
|
2012
|
+
const applicationMessage = applicationDecision && applicationDecision.status === 'pending'
|
|
2013
|
+
? `The JavaScript application graph is deterministic, but reachable package realization remains pending for ${targetSupport.project.reasonIds.join(', ')}.`
|
|
2014
|
+
: preparedJavascript.loaded
|
|
2015
|
+
? `Graph-backed JavaScript application ${preparedJavascript.plan.planHash.slice(0, 12)}… loaded ${preparedJavascript.loaded.modulesLoaded} project module(s).`
|
|
2016
|
+
: buildTimeLoaderReady
|
|
2017
|
+
? `${project.provider} JavaScript application ${preparedJavascript.plan.planHash.slice(0, 12)}… is loadable through the deterministic build-time bundle loader and available to explicit local provider emulation.`
|
|
2018
|
+
: targetSupport && targetSupport.project.status === 'pending'
|
|
2019
|
+
? `The JavaScript application graph is deterministic, but reachable package/capability realization remains pending for ${targetSupport.project.reasonIds.join(', ')}.`
|
|
2020
|
+
: (preparedJavascript.loadError
|
|
2021
|
+
? `The JavaScript application loader failed: ${preparedJavascript.loadError.message}`
|
|
2022
|
+
: 'The JavaScript application plan is blocked for the selected target.');
|
|
2023
|
+
check(
|
|
2024
|
+
'javascript-application',
|
|
2025
|
+
applicationStatus,
|
|
2026
|
+
applicationMessage,
|
|
2027
|
+
{
|
|
2028
|
+
target: preparedJavascript.descriptor,
|
|
2029
|
+
planVersion: preparedJavascript.plan.version,
|
|
2030
|
+
planHash: preparedJavascript.plan.planHash,
|
|
2031
|
+
graphHash: preparedJavascript.plan.graph.graphHash,
|
|
2032
|
+
loadable: preparedJavascript.plan.loadable,
|
|
2033
|
+
blockers: preparedJavascript.plan.blockers,
|
|
2034
|
+
packages: preparedJavascript.plan.packages,
|
|
2035
|
+
nativeEligibility: preparedJavascript.plan.nativeEligibility,
|
|
2036
|
+
entrySafety: preparedJavascript.plan.entrySafety,
|
|
2037
|
+
loader: preparedJavascript.loaded ? {
|
|
2038
|
+
version: preparedJavascript.loaded.version,
|
|
2039
|
+
modulesLoaded: preparedJavascript.loaded.modulesLoaded,
|
|
2040
|
+
packagesLoaded: preparedJavascript.loaded.packagesLoaded
|
|
2041
|
+
} : null,
|
|
2042
|
+
loadError: preparedJavascript.loadError,
|
|
2043
|
+
targetSupportEvidenceHash: targetSupport && targetSupport.evidenceHash,
|
|
2044
|
+
projectEligibility: targetSupport && targetSupport.project.status,
|
|
2045
|
+
automaticFallback: false
|
|
2046
|
+
},
|
|
2047
|
+
applicationStatus === 'passed'
|
|
2048
|
+
? undefined
|
|
2049
|
+
: targetSupport && targetSupport.project.packages.find((entry) => entry.required && entry.status === 'pending' && entry.reasonCode)?.reasonCode
|
|
2050
|
+
|| preparedJavascript.plan.blockers[0]?.code
|
|
2051
|
+
|| preparedJavascript.loadError?.code
|
|
2052
|
+
|| 'PULSE_TARGET_IMPLEMENTATION_PENDING'
|
|
2053
|
+
);
|
|
2054
|
+
if (targetSupport) {
|
|
2055
|
+
const pendingProjectItems = targetSupport.project.summary.application.pending
|
|
2056
|
+
+ targetSupport.project.summary.capabilities.pending
|
|
2057
|
+
+ targetSupport.project.summary.providerRequirements.pending
|
|
2058
|
+
+ targetSupport.project.summary.packages.pending;
|
|
2059
|
+
const blockedProjectItems = targetSupport.project.summary.application.blocked
|
|
2060
|
+
+ targetSupport.project.summary.capabilities.blocked
|
|
2061
|
+
+ targetSupport.project.summary.providerRequirements.blocked
|
|
2062
|
+
+ targetSupport.project.summary.packages.blocked;
|
|
2063
|
+
check(
|
|
2064
|
+
'target-support',
|
|
2065
|
+
targetSupport.project.status === 'blocked'
|
|
2066
|
+
? 'failed'
|
|
2067
|
+
: targetSupport.project.status === 'eligible' && targetSupport.availability.generalAvailable
|
|
2068
|
+
? 'passed'
|
|
2069
|
+
: 'warning',
|
|
2070
|
+
targetSupport.project.status === 'eligible'
|
|
2071
|
+
? (targetSupport.availability.generalAvailable
|
|
2072
|
+
? `This project is eligible for the sealed and generally available ${project.provider} JavaScript target; the selected target remains authoritative and automatic fallback is disabled.`
|
|
2073
|
+
: `This project is eligible for the sealed core ${project.provider} JavaScript lane, but general availability remains false until ${targetSupport.availability.summary.pending} full-support gate(s) close.`)
|
|
2074
|
+
: targetSupport.project.status === 'pending'
|
|
2075
|
+
? (targetSupport.availability.generalAvailable
|
|
2076
|
+
? `The ${project.provider} JavaScript target is generally available, but this project has ${pendingProjectItems} pending capability/package decision(s) and is not yet eligible.`
|
|
2077
|
+
: `This project has ${pendingProjectItems} pending JavaScript capability/package decision(s); general availability also remains false under the full-target-support policy.`)
|
|
2078
|
+
: `This project has ${blockedProjectItems} blocked JavaScript capability/package decision(s) and cannot execute on the selected target.`,
|
|
2079
|
+
targetSupport
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
if (javascript && javascript.localExecutionMode) {
|
|
2083
|
+
check(
|
|
2084
|
+
'javascript-local-tooling',
|
|
2085
|
+
'passed',
|
|
2086
|
+
`pulse test and pulse dev use explicit ${project.provider} provider emulation; provider reality remains a separate ${javascript.realityRunner} step.`,
|
|
2087
|
+
{
|
|
2088
|
+
mode: javascript.localExecutionMode,
|
|
2089
|
+
loader: 'provider-owned-bundled-closure',
|
|
2090
|
+
packageClosure: 'esbuild-bundled',
|
|
2091
|
+
providerReality: false,
|
|
2092
|
+
realityRunner: javascript.realityRunner,
|
|
2093
|
+
deploymentCandidate: 'pulse build',
|
|
2094
|
+
automaticFallback: false
|
|
2095
|
+
}
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
check('javascript-application', 'failed', error.message, { code: error.code, detail: error.detail, diagnostics: error.diagnostics }, error.code);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const driver = providerDriver(project);
|
|
2103
|
+
if (!driver.executable) check('provider', 'warning', 'Compile-only provider selected; pulse build, dev, and test are unavailable. Use pulse compile for provider-neutral Wasm.', { provider: project.provider }, 'PULSE_PROVIDER_COMPILE_ONLY');
|
|
2104
|
+
else {
|
|
2105
|
+
try {
|
|
2106
|
+
const lowering = compiled ? driver.createLoweringPlan(compiled.metadata, project.providerConfig) : undefined;
|
|
2107
|
+
check('provider', selectedTarget === 'javascript' && targetAvailability.javascript !== true ? 'warning' : 'passed', selectedTarget === 'javascript'
|
|
2108
|
+
? (targetSupportDeclaration
|
|
2109
|
+
? (targetSupportDeclaration.availability.generalAvailable
|
|
2110
|
+
? `${project.provider} JavaScript full target support is sealed and generally available; bounded GRIP realization is included and automatic fallback remains disabled.`
|
|
2111
|
+
: `${project.provider} JavaScript core execution and bounded GRIP realization are ready, but one or more full-target-support gates remain pending, so general availability remains false.`)
|
|
2112
|
+
: `${project.provider} JavaScript application loading is not declared as a fully supported target.`)
|
|
2113
|
+
: `${project.provider} native provider contract and local conformance runtime are available.`, {
|
|
2114
|
+
package: driver.descriptor.package,
|
|
2115
|
+
buildTarget: driver.descriptor.buildTarget,
|
|
2116
|
+
deployable: driver.deployable === true,
|
|
2117
|
+
deploymentValidated: driver.deploymentValidated === true,
|
|
2118
|
+
sourcePackage: driver.sourcePackage === true,
|
|
2119
|
+
compiledWasm: driver.compiledWasm === true,
|
|
2120
|
+
selectedTarget,
|
|
2121
|
+
targetAvailability: Object.freeze({ native: targetAvailability.native, javascript: targetAvailability.javascript }),
|
|
2122
|
+
targetDescriptors: targetAvailability.descriptors,
|
|
2123
|
+
selectedTargetDescriptor,
|
|
2124
|
+
targetSupportDeclaration,
|
|
2125
|
+
targetSupportEvidenceHash: targetSupport && targetSupport.evidenceHash,
|
|
2126
|
+
defaultBuildMode: driver.defaultBuildMode,
|
|
2127
|
+
sourceOnlySupported: false,
|
|
2128
|
+
localExecution: driver.localExecution === true,
|
|
2129
|
+
requirements: lowering && lowering.requirements
|
|
2130
|
+
});
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
check('provider', 'failed', error.message, { code: error.code, detail: error.detail });
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (selectedTarget === 'native' && nativePrepared && driver.executable && driver.inspectRealization && (!eventSupport || eventSupport.status !== 'blocked')) {
|
|
2136
|
+
try {
|
|
2137
|
+
const realization = normalizeProviderTargetResult(driver.inspectRealization(providerTargetInvocation(
|
|
2138
|
+
project,
|
|
2139
|
+
'inspect-native',
|
|
2140
|
+
nativePrepared.plan,
|
|
2141
|
+
providerLoweringPlan(project, nativePrepared.compiled.metadata),
|
|
2142
|
+
{
|
|
2143
|
+
native: nativePrepared.native,
|
|
2144
|
+
optimization: nativePrepared.native.manifest.optimization,
|
|
2145
|
+
timeoutMs: options.timeoutMs
|
|
2146
|
+
}
|
|
2147
|
+
)), { action: 'inspect-native', provider: project.provider, target: 'native' }).realization;
|
|
2148
|
+
check(
|
|
2149
|
+
'provider-native-realization',
|
|
2150
|
+
'passed',
|
|
2151
|
+
`${project.provider} realizes the portable plan as ${realization.wasm.bytes} bytes of native provider Wasm.`,
|
|
2152
|
+
{
|
|
2153
|
+
target: realization.target,
|
|
2154
|
+
provider: project.provider,
|
|
2155
|
+
nativeWasm: realization.nativeWasm === true,
|
|
2156
|
+
javascriptRuntime: realization.javascriptRuntime === true,
|
|
2157
|
+
jsComputeRuntime: realization.jsComputeRuntime === true,
|
|
2158
|
+
compiler: realization.compiler,
|
|
2159
|
+
wasm: realization.wasm,
|
|
2160
|
+
importModules: realization.importModules,
|
|
2161
|
+
imports: realization.imports,
|
|
2162
|
+
exports: realization.exports,
|
|
2163
|
+
policy: realization.policy
|
|
2164
|
+
}
|
|
2165
|
+
);
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
check('provider-native-realization', 'failed', error.message, { code: error.code, detail: error.detail, diagnostics: error.diagnostics });
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
const output = resolveOutputDirectory(project, project.outDir);
|
|
2172
|
+
check('build-output', 'passed', relative(output, project.root));
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
check('build-output', 'failed', error.message, { code: error.code, outDir: project.outDir });
|
|
2175
|
+
}
|
|
2176
|
+
if (driver.executable && project.dev.networkFetch) {
|
|
2177
|
+
check('network-fetch', typeof globalThis.fetch === 'function' ? 'passed' : 'failed', typeof globalThis.fetch === 'function' ? `${project.provider} local live fetch is available for pulse dev.` : 'Local live fetch is unavailable.', { enabled: true, provider: project.provider }, typeof globalThis.fetch === 'function' ? undefined : 'PULSE_FETCH_IMPLEMENTATION_UNAVAILABLE');
|
|
2178
|
+
} else {
|
|
2179
|
+
check('network-fetch', 'passed', project.dev.networkFetch ? 'Provider owns outbound fetch.' : 'Live network fetch is disabled; configured fixtures are required.', { enabled: project.dev.networkFetch });
|
|
2180
|
+
}
|
|
2181
|
+
check('tests', project.tests.length > 0 ? 'passed' : 'warning', project.tests.length > 0 ? `${project.tests.length} project test case(s) configured.` : 'No project test cases configured.', { count: project.tests.length }, project.tests.length > 0 ? undefined : 'PULSE_TEST_CASES_MISSING');
|
|
2182
|
+
const failed = checks.filter((entry) => entry.status === 'failed').length;
|
|
2183
|
+
const warnings = checks.filter((entry) => entry.status === 'warning').length;
|
|
2184
|
+
const ok = failed === 0 && (!options.strict || warnings === 0);
|
|
2185
|
+
return Object.freeze({
|
|
2186
|
+
status: ok ? (warnings > 0 ? 'warning' : 'passed') : 'failed',
|
|
2187
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
2188
|
+
project: projectJson(project),
|
|
2189
|
+
summary: Object.freeze({ total: checks.length, passed: checks.filter((entry) => entry.status === 'passed').length, warnings, failed }),
|
|
2190
|
+
checks: Object.freeze(checks),
|
|
2191
|
+
targetSupport,
|
|
2192
|
+
...(eventSupport ? { events: eventInspectionProjection(project, compiled, eventSupport) } : {}),
|
|
2193
|
+
metadata: compiled && compiled.metadata
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
async function readRequestBody(req, maxBytes) {
|
|
2198
|
+
const chunks = [];
|
|
2199
|
+
let bytes = 0;
|
|
2200
|
+
for await (const chunk of req) {
|
|
2201
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2202
|
+
bytes += buffer.byteLength;
|
|
2203
|
+
if (bytes > maxBytes) throw new PulseProjectError('PULSE_REQUEST_BODY_TOO_LARGE', `Request body exceeds ${maxBytes} bytes.`, { maxBytes, bytes });
|
|
2204
|
+
chunks.push(buffer);
|
|
2205
|
+
}
|
|
2206
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
function requestHeaders(req) {
|
|
2210
|
+
if (Array.isArray(req.rawHeaders) && req.rawHeaders.length > 0) {
|
|
2211
|
+
const pairs = [];
|
|
2212
|
+
for (let index = 0; index < req.rawHeaders.length; index += 2) pairs.push([req.rawHeaders[index], req.rawHeaders[index + 1] || '']);
|
|
2213
|
+
return pairs;
|
|
2214
|
+
}
|
|
2215
|
+
return Object.entries(req.headers || {}).flatMap(([name, value]) => Array.isArray(value) ? value.map((entry) => [name, entry]) : [[name, value === undefined ? '' : value]]);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function devErrorResponse(res, error, secrets) {
|
|
2219
|
+
if (res.headersSent) {
|
|
2220
|
+
if (!res.writableEnded) res.end();
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const summary = errorSummary(error, secrets);
|
|
2224
|
+
res.statusCode = httpStatusForDiagnostic(summary.code, 500);
|
|
2225
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
2226
|
+
res.end(stableJson({
|
|
2227
|
+
error: summary.name,
|
|
2228
|
+
code: summary.code,
|
|
2229
|
+
category: summary.category,
|
|
2230
|
+
message: summary.message,
|
|
2231
|
+
remediation: summary.remediation,
|
|
2232
|
+
docs: summary.docs
|
|
2233
|
+
}));
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
|
|
2237
|
+
function javascriptProjectWatchFiles(project, prepared, compiled) {
|
|
2238
|
+
const files = new Set();
|
|
2239
|
+
if (project.configFile) files.add(path.resolve(project.configFile));
|
|
2240
|
+
for (const module of prepared.plan.graph.modules || []) {
|
|
2241
|
+
if (module.kind === 'project' && module.path) files.add(path.resolve(project.root, module.path));
|
|
2242
|
+
}
|
|
2243
|
+
for (const file of compiled && compiled.watchFiles || []) files.add(path.resolve(file));
|
|
2244
|
+
return Object.freeze([...files].sort());
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
async function writeJavascriptWebResponse(response, res, method = 'GET') {
|
|
2248
|
+
res.statusCode = response.status;
|
|
2249
|
+
const grouped = new Map();
|
|
2250
|
+
for (const [name, value] of response.headers.entries()) {
|
|
2251
|
+
const key = String(name).toLowerCase();
|
|
2252
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
2253
|
+
grouped.get(key).push(String(value));
|
|
2254
|
+
}
|
|
2255
|
+
if (typeof response.headers.getSetCookie === 'function') {
|
|
2256
|
+
const cookies = response.headers.getSetCookie();
|
|
2257
|
+
if (cookies.length > 0) grouped.set('set-cookie', cookies);
|
|
2258
|
+
}
|
|
2259
|
+
for (const [name, values] of grouped) res.setHeader(name, values.length === 1 ? values[0] : values);
|
|
2260
|
+
if (String(method).toUpperCase() === 'HEAD' || !statusAllowsResponseBody(response.status) || !response.body) {
|
|
2261
|
+
if (response.body && typeof response.body.cancel === 'function') {
|
|
2262
|
+
try { await response.body.cancel(); } catch (_) { /* Best-effort local stream cleanup. */ }
|
|
2263
|
+
}
|
|
2264
|
+
res.end();
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
async function startBundledJavascriptDevServer(project, options = {}) {
|
|
2271
|
+
const descriptor = assertImplementedTarget(project, 'dev');
|
|
2272
|
+
const javascript = providerDriver(project).javascript;
|
|
2273
|
+
if (
|
|
2274
|
+
!javascript
|
|
2275
|
+
|| typeof javascript.createLocalEnvironment !== 'function'
|
|
2276
|
+
|| typeof javascript.createTestRequest !== 'function'
|
|
2277
|
+
|| typeof javascript.executeLocalRequest !== 'function'
|
|
2278
|
+
|| descriptor.requestAdapter !== true
|
|
2279
|
+
|| descriptor.lifecycle !== true
|
|
2280
|
+
) {
|
|
2281
|
+
throw new PulseProjectError(
|
|
2282
|
+
'PULSE_TARGET_IMPLEMENTATION_PENDING',
|
|
2283
|
+
'pulse dev cannot realize the selected provider-bundled JavaScript target yet.',
|
|
2284
|
+
{ target: 'javascript', provider: project.provider, targetDescriptor: descriptor, automaticFallback: false }
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
let prepared;
|
|
2289
|
+
let application;
|
|
2290
|
+
let compileError;
|
|
2291
|
+
let schemaCodecs;
|
|
2292
|
+
let reloadTimer;
|
|
2293
|
+
const watchEnabled = project.dev.watch && options.watch !== false;
|
|
2294
|
+
const watched = new Map();
|
|
2295
|
+
const events = typeof options.onEvent === 'function' ? options.onEvent : () => {};
|
|
2296
|
+
const environment = javascript.createLocalEnvironment({
|
|
2297
|
+
bindings: project.providerConfig.bindings,
|
|
2298
|
+
config: project.dev.config,
|
|
2299
|
+
secrets: project.dev.secrets,
|
|
2300
|
+
kv: project.dev.kv,
|
|
2301
|
+
fetches: project.dev.fetches,
|
|
2302
|
+
networkFetch: project.dev.networkFetch,
|
|
2303
|
+
fetchImplementation: project.dev.networkFetch ? globalThis.fetch : undefined
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
function syncWatchFiles(files) {
|
|
2307
|
+
if (!watchEnabled) return;
|
|
2308
|
+
const desired = new Set((files || []).map((file) => path.resolve(file)));
|
|
2309
|
+
for (const [file, listener] of watched) {
|
|
2310
|
+
if (desired.has(file)) continue;
|
|
2311
|
+
fs.unwatchFile(file, listener);
|
|
2312
|
+
watched.delete(file);
|
|
2313
|
+
}
|
|
2314
|
+
for (const file of desired) {
|
|
2315
|
+
if (watched.has(file)) continue;
|
|
2316
|
+
const listener = scheduleReload;
|
|
2317
|
+
fs.watchFile(file, { interval: 200 }, listener);
|
|
2318
|
+
watched.set(file, listener);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
function reload(reason) {
|
|
2323
|
+
try {
|
|
2324
|
+
const nextCompiled = compileProject(project);
|
|
2325
|
+
const next = prepareJavascriptApplication(project, {
|
|
2326
|
+
allowedPackages: options.allowedPackages,
|
|
2327
|
+
localEmulation: true,
|
|
2328
|
+
schemaBundle: nextCompiled.schema.bundle
|
|
2329
|
+
});
|
|
2330
|
+
if (!next.loaded) {
|
|
2331
|
+
throw new PulseProjectError(
|
|
2332
|
+
'PULSE_JAVASCRIPT_APPLICATION_UNAVAILABLE',
|
|
2333
|
+
'The graph-backed JavaScript application could not be loaded for local provider emulation.',
|
|
2334
|
+
{ planHash: next.plan.planHash, blockers: next.plan.blockers, automaticFallback: false }
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
prepared = next;
|
|
2338
|
+
application = next.loaded.application;
|
|
2339
|
+
schemaCodecs = createCanonicalSchemaCodecs(nextCompiled.schema.bundle.registry);
|
|
2340
|
+
compileError = undefined;
|
|
2341
|
+
const watchFiles = javascriptProjectWatchFiles(project, next, nextCompiled);
|
|
2342
|
+
syncWatchFiles(watchFiles);
|
|
2343
|
+
events(Object.freeze({
|
|
2344
|
+
event: reason === 'initial' ? 'compiled' : 'reloaded',
|
|
2345
|
+
provider: project.provider,
|
|
2346
|
+
target: 'javascript',
|
|
2347
|
+
targetId: descriptor.targetId,
|
|
2348
|
+
executionMode: javascript.localExecutionMode,
|
|
2349
|
+
providerReality: false,
|
|
2350
|
+
entry: relative(project.entryFile, project.root),
|
|
2351
|
+
planHash: next.plan.planHash,
|
|
2352
|
+
graphHash: next.plan.graph.graphHash,
|
|
2353
|
+
localClosureHash: next.loaded.closure.sourceHash,
|
|
2354
|
+
bundledInputs: next.loaded.closure.inputs.length,
|
|
2355
|
+
packagesLoaded: next.loaded.packagesLoaded,
|
|
2356
|
+
watchFiles: Object.freeze(watchFiles.map((file) => relative(file, project.root)))
|
|
2357
|
+
}));
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
if (!application) compileError = error;
|
|
2360
|
+
events(Object.freeze({
|
|
2361
|
+
event: 'compile-error',
|
|
2362
|
+
provider: project.provider,
|
|
2363
|
+
target: 'javascript',
|
|
2364
|
+
executionMode: javascript.localExecutionMode,
|
|
2365
|
+
retainedLastGoodProgram: Boolean(application),
|
|
2366
|
+
error: errorSummary(error, project.dev.secrets)
|
|
2367
|
+
}));
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function scheduleReload() {
|
|
2372
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2373
|
+
reloadTimer = setTimeout(() => {
|
|
2374
|
+
reloadTimer = undefined;
|
|
2375
|
+
reload('change');
|
|
2376
|
+
}, 60);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
reload('initial');
|
|
2380
|
+
if (compileError) throw compileError;
|
|
2381
|
+
|
|
2382
|
+
let handled = 0;
|
|
2383
|
+
const structuredBodyLimit = project.schemas.active
|
|
2384
|
+
? Math.min(project.dev.maxBodyBytes, project.schemas.maxBytes)
|
|
2385
|
+
: project.dev.maxBodyBytes;
|
|
2386
|
+
const server = http.createServer(async (req, res) => {
|
|
2387
|
+
if (options.once) res.once('finish', () => server.close());
|
|
2388
|
+
try {
|
|
2389
|
+
if (compileError) throw compileError;
|
|
2390
|
+
const body = await readRequestBody(req, structuredBodyLimit);
|
|
2391
|
+
const host = req.headers.host || `${project.dev.host}:${project.dev.port}`;
|
|
2392
|
+
const url = new URL(req.url || '/', `http://${host}`);
|
|
2393
|
+
const request = javascript.createTestRequest({
|
|
2394
|
+
method: req.method || 'GET',
|
|
2395
|
+
url: url.href,
|
|
2396
|
+
path: url.pathname,
|
|
2397
|
+
headers: requestHeaders(req),
|
|
2398
|
+
body: body || undefined
|
|
2399
|
+
});
|
|
2400
|
+
const execution = await javascript.executeLocalRequest(application, request, {
|
|
2401
|
+
environment,
|
|
2402
|
+
bindings: project.providerConfig.bindings,
|
|
2403
|
+
application: Object.freeze({
|
|
2404
|
+
projectHash: project.projectHash,
|
|
2405
|
+
planHash: project.planHash,
|
|
2406
|
+
profile: project.profile,
|
|
2407
|
+
target: 'javascript',
|
|
2408
|
+
provider: project.provider
|
|
2409
|
+
}),
|
|
2410
|
+
maxBodyBytes: structuredBodyLimit,
|
|
2411
|
+
maxRequestBodyBytes: structuredBodyLimit,
|
|
2412
|
+
maxFetchBodyBytes: structuredBodyLimit,
|
|
2413
|
+
schemaCodecs,
|
|
2414
|
+
strict: project.strict !== false,
|
|
2415
|
+
reporting: project.reporting
|
|
2416
|
+
});
|
|
2417
|
+
await writeJavascriptWebResponse(execution.response, res, req.method || 'GET');
|
|
2418
|
+
handled += 1;
|
|
2419
|
+
events(Object.freeze({
|
|
2420
|
+
event: 'request',
|
|
2421
|
+
provider: project.provider,
|
|
2422
|
+
target: 'javascript',
|
|
2423
|
+
executionMode: javascript.localExecutionMode,
|
|
2424
|
+
providerReality: false,
|
|
2425
|
+
requestId: `dev:${handled}`,
|
|
2426
|
+
method: req.method || 'GET',
|
|
2427
|
+
path: url.pathname,
|
|
2428
|
+
status: execution.response.status,
|
|
2429
|
+
effects: execution.effectSummary ? execution.effectSummary.ownedEffectCount : 0
|
|
2430
|
+
}));
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
devErrorResponse(res, error, project.dev.secrets);
|
|
2433
|
+
events(Object.freeze({
|
|
2434
|
+
event: 'request-error',
|
|
2435
|
+
provider: project.provider,
|
|
2436
|
+
target: 'javascript',
|
|
2437
|
+
executionMode: javascript.localExecutionMode,
|
|
2438
|
+
method: req.method || 'GET',
|
|
2439
|
+
path: req.url || '/',
|
|
2440
|
+
error: errorSummary(error, project.dev.secrets)
|
|
2441
|
+
}));
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
function cleanup() {
|
|
2446
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2447
|
+
for (const [file, listener] of watched) fs.unwatchFile(file, listener);
|
|
2448
|
+
watched.clear();
|
|
2449
|
+
}
|
|
2450
|
+
server.once('close', cleanup);
|
|
2451
|
+
server.once('error', cleanup);
|
|
2452
|
+
const listenHost = options.host || project.dev.host;
|
|
2453
|
+
const listenPort = options.port !== undefined ? Number(options.port) : project.dev.port;
|
|
2454
|
+
server.listen(listenPort, listenHost);
|
|
2455
|
+
await once(server, 'listening');
|
|
2456
|
+
const address = server.address();
|
|
2457
|
+
const boundHost = typeof address === 'object' && address ? address.address : listenHost;
|
|
2458
|
+
const boundPort = typeof address === 'object' && address ? address.port : listenPort;
|
|
2459
|
+
const advertisedHost = listenHost === '0.0.0.0' || listenHost === '::' ? '127.0.0.1' : listenHost;
|
|
2460
|
+
const urlHost = advertisedHost.includes(':') ? `[${advertisedHost}]` : advertisedHost;
|
|
2461
|
+
const ready = Object.freeze({
|
|
2462
|
+
event: 'ready',
|
|
2463
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
2464
|
+
provider: project.provider,
|
|
2465
|
+
target: 'javascript',
|
|
2466
|
+
targetId: descriptor.targetId,
|
|
2467
|
+
runtimeClass: descriptor.runtimeClass,
|
|
2468
|
+
executionMode: javascript.localExecutionMode,
|
|
2469
|
+
providerReality: false,
|
|
2470
|
+
realityRunner: javascript.realityRunner,
|
|
2471
|
+
host: boundHost,
|
|
2472
|
+
port: boundPort,
|
|
2473
|
+
url: `http://${urlHost}:${boundPort}`,
|
|
2474
|
+
watch: watched.size > 0,
|
|
2475
|
+
watchFiles: Object.freeze([...watched.keys()].map((file) => relative(file, project.root))),
|
|
2476
|
+
once: Boolean(options.once),
|
|
2477
|
+
applicationPlan: Object.freeze({ planHash: prepared.plan.planHash, graphHash: prepared.plan.graph.graphHash }),
|
|
2478
|
+
localLoader: Object.freeze({
|
|
2479
|
+
version: prepared.loaded.version,
|
|
2480
|
+
kind: 'provider-owned-bundled-closure',
|
|
2481
|
+
sourceHash: prepared.loaded.closure.sourceHash,
|
|
2482
|
+
bundledInputs: prepared.loaded.closure.inputs.length,
|
|
2483
|
+
packagesLoaded: prepared.loaded.packagesLoaded
|
|
2484
|
+
}),
|
|
2485
|
+
automaticFallback: false,
|
|
2486
|
+
project: projectJson(project)
|
|
2487
|
+
});
|
|
2488
|
+
events(ready);
|
|
2489
|
+
return Object.freeze({ server, ready, closed: once(server, 'close') });
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
async function startJavascriptDevServer(project, options = {}) {
|
|
2493
|
+
const javascript = providerDriver(project).javascript;
|
|
2494
|
+
if (javascript && javascript.kind === 'provider-bundled') return startBundledJavascriptDevServer(project, options);
|
|
2495
|
+
const descriptor = assertImplementedTarget(project, 'dev');
|
|
2496
|
+
if (!javascript || typeof javascript.createServer !== 'function' || descriptor.requestAdapter !== true || descriptor.lifecycle !== true) {
|
|
2497
|
+
throw new PulseProjectError(
|
|
2498
|
+
'PULSE_TARGET_IMPLEMENTATION_PENDING',
|
|
2499
|
+
`pulse dev cannot realize the selected javascript target for ${project.provider} yet.`,
|
|
2500
|
+
{ target: 'javascript', provider: project.provider, targetDescriptor: descriptor, automaticFallback: false }
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
let prepared;
|
|
2505
|
+
let application;
|
|
2506
|
+
let compileError;
|
|
2507
|
+
let schemaCodecs;
|
|
2508
|
+
let reloadTimer;
|
|
2509
|
+
const watchEnabled = project.dev.watch && options.watch !== false;
|
|
2510
|
+
const watched = new Map();
|
|
2511
|
+
const events = typeof options.onEvent === 'function' ? options.onEvent : () => {};
|
|
2512
|
+
|
|
2513
|
+
function syncWatchFiles(files) {
|
|
2514
|
+
if (!watchEnabled) return;
|
|
2515
|
+
const desired = new Set((files || []).map((file) => path.resolve(file)));
|
|
2516
|
+
for (const [file, listener] of watched) {
|
|
2517
|
+
if (desired.has(file)) continue;
|
|
2518
|
+
fs.unwatchFile(file, listener);
|
|
2519
|
+
watched.delete(file);
|
|
2520
|
+
}
|
|
2521
|
+
for (const file of desired) {
|
|
2522
|
+
if (watched.has(file)) continue;
|
|
2523
|
+
const listener = scheduleReload;
|
|
2524
|
+
fs.watchFile(file, { interval: 200 }, listener);
|
|
2525
|
+
watched.set(file, listener);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
function reload(reason) {
|
|
2530
|
+
try {
|
|
2531
|
+
const next = prepareJavascriptApplication(project, { allowedPackages: options.allowedPackages });
|
|
2532
|
+
if (!next.loaded) {
|
|
2533
|
+
throw new PulseProjectError(
|
|
2534
|
+
'PULSE_JAVASCRIPT_APPLICATION_UNAVAILABLE',
|
|
2535
|
+
'The graph-backed JavaScript application could not be loaded.',
|
|
2536
|
+
{ planHash: next.plan.planHash, blockers: next.plan.blockers, automaticFallback: false }
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
const nextCompiled = compileProject(project);
|
|
2540
|
+
prepared = next;
|
|
2541
|
+
application = next.loaded.application;
|
|
2542
|
+
schemaCodecs = createCanonicalSchemaCodecs(nextCompiled.schema.bundle.registry);
|
|
2543
|
+
compileError = undefined;
|
|
2544
|
+
const watchFiles = javascriptProjectWatchFiles(project, next, nextCompiled);
|
|
2545
|
+
syncWatchFiles(watchFiles);
|
|
2546
|
+
events(Object.freeze({
|
|
2547
|
+
event: reason === 'initial' ? 'compiled' : 'reloaded',
|
|
2548
|
+
target: 'javascript',
|
|
2549
|
+
targetId: descriptor.targetId,
|
|
2550
|
+
entry: relative(project.entryFile, project.root),
|
|
2551
|
+
planHash: next.plan.planHash,
|
|
2552
|
+
graphHash: next.plan.graph.graphHash,
|
|
2553
|
+
watchFiles: Object.freeze(watchFiles.map((file) => relative(file, project.root)))
|
|
2554
|
+
}));
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
if (!application) compileError = error;
|
|
2557
|
+
events(Object.freeze({ event: 'compile-error', target: 'javascript', retainedLastGoodProgram: Boolean(application), error: errorSummary(error, project.dev.secrets) }));
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function scheduleReload() {
|
|
2562
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2563
|
+
reloadTimer = setTimeout(() => { reloadTimer = undefined; reload('change'); }, 60);
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
reload('initial');
|
|
2567
|
+
if (compileError) throw compileError;
|
|
2568
|
+
|
|
2569
|
+
let handled = 0;
|
|
2570
|
+
let server;
|
|
2571
|
+
const structuredBodyLimit = project.schemas.active
|
|
2572
|
+
? Math.min(project.dev.maxBodyBytes, project.schemas.maxBytes)
|
|
2573
|
+
: project.dev.maxBodyBytes;
|
|
2574
|
+
server = javascript.createServer(null, {
|
|
2575
|
+
getApplication() {
|
|
2576
|
+
if (compileError) throw compileError;
|
|
2577
|
+
if (!application) throw new PulseProjectError('PULSE_JAVASCRIPT_APPLICATION_UNAVAILABLE', 'No last-good JavaScript application is available.');
|
|
2578
|
+
return application;
|
|
2579
|
+
},
|
|
2580
|
+
schemaCodecs() {
|
|
2581
|
+
if (compileError) throw compileError;
|
|
2582
|
+
return schemaCodecs;
|
|
2583
|
+
},
|
|
2584
|
+
strict: project.strict !== false,
|
|
2585
|
+
provider: project.provider,
|
|
2586
|
+
maxBodyBytes: structuredBodyLimit,
|
|
2587
|
+
application: Object.freeze({
|
|
2588
|
+
projectHash: project.projectHash,
|
|
2589
|
+
planHash: project.planHash,
|
|
2590
|
+
profile: project.profile,
|
|
2591
|
+
target: 'javascript',
|
|
2592
|
+
provider: project.provider
|
|
2593
|
+
}),
|
|
2594
|
+
capabilities: options.capabilities,
|
|
2595
|
+
config: project.dev.config,
|
|
2596
|
+
secrets: project.dev.secrets,
|
|
2597
|
+
kv: project.dev.kv,
|
|
2598
|
+
fetchImplementation: javascript.createFixtureFetch(
|
|
2599
|
+
project.dev.fetches,
|
|
2600
|
+
project.dev.networkFetch ? globalThis.fetch : undefined
|
|
2601
|
+
),
|
|
2602
|
+
maxRequestBodyBytes: structuredBodyLimit,
|
|
2603
|
+
maxFetchBodyBytes: structuredBodyLimit,
|
|
2604
|
+
onRequest(event) {
|
|
2605
|
+
handled += 1;
|
|
2606
|
+
events(Object.freeze({ event: 'request', target: 'javascript', ...event, requestId: `dev:${handled}` }));
|
|
2607
|
+
if (options.once) server.close();
|
|
2608
|
+
},
|
|
2609
|
+
onError(error, context) {
|
|
2610
|
+
const request = context && context.request;
|
|
2611
|
+
events(Object.freeze({
|
|
2612
|
+
event: 'request-error',
|
|
2613
|
+
target: 'javascript',
|
|
2614
|
+
method: request && request.method || 'GET',
|
|
2615
|
+
path: request && request.url || '/',
|
|
2616
|
+
error: errorSummary(error, project.dev.secrets)
|
|
2617
|
+
}));
|
|
2618
|
+
if (options.once) server.close();
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2622
|
+
function cleanup() {
|
|
2623
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2624
|
+
for (const [file, listener] of watched) fs.unwatchFile(file, listener);
|
|
2625
|
+
watched.clear();
|
|
2626
|
+
}
|
|
2627
|
+
server.once('close', cleanup);
|
|
2628
|
+
server.once('error', cleanup);
|
|
2629
|
+
const listenHost = options.host || project.dev.host;
|
|
2630
|
+
const listenPort = options.port !== undefined ? Number(options.port) : project.dev.port;
|
|
2631
|
+
server.listen(listenPort, listenHost);
|
|
2632
|
+
await once(server, 'listening');
|
|
2633
|
+
const address = server.address();
|
|
2634
|
+
const boundHost = typeof address === 'object' && address ? address.address : listenHost;
|
|
2635
|
+
const boundPort = typeof address === 'object' && address ? address.port : listenPort;
|
|
2636
|
+
const advertisedHost = listenHost === '0.0.0.0' || listenHost === '::' ? '127.0.0.1' : listenHost;
|
|
2637
|
+
const urlHost = advertisedHost.includes(':') ? `[${advertisedHost}]` : advertisedHost;
|
|
2638
|
+
const ready = Object.freeze({
|
|
2639
|
+
event: 'ready',
|
|
2640
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
2641
|
+
provider: project.provider,
|
|
2642
|
+
target: 'javascript',
|
|
2643
|
+
targetId: descriptor.targetId,
|
|
2644
|
+
runtimeClass: descriptor.runtimeClass,
|
|
2645
|
+
host: boundHost,
|
|
2646
|
+
port: boundPort,
|
|
2647
|
+
url: `http://${urlHost}:${boundPort}`,
|
|
2648
|
+
watch: watched.size > 0,
|
|
2649
|
+
watchFiles: Object.freeze([...watched.keys()].map((file) => relative(file, project.root))),
|
|
2650
|
+
once: Boolean(options.once),
|
|
2651
|
+
applicationPlan: Object.freeze({ planHash: prepared.plan.planHash, graphHash: prepared.plan.graph.graphHash }),
|
|
2652
|
+
automaticFallback: false,
|
|
2653
|
+
project: projectJson(project)
|
|
2654
|
+
});
|
|
2655
|
+
events(ready);
|
|
2656
|
+
return Object.freeze({ server, ready, closed: once(server, 'close') });
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
async function startDevServer(project, options = {}) {
|
|
2660
|
+
if ((project.target || 'native') === 'javascript') return startJavascriptDevServer(project, options);
|
|
2661
|
+
const driver = assertExecutableProvider(project, 'dev');
|
|
2662
|
+
assertImplementedTarget(project, 'dev');
|
|
2663
|
+
const executeCanonicalProgram = driver.execute;
|
|
2664
|
+
let compiled;
|
|
2665
|
+
let program;
|
|
2666
|
+
let native;
|
|
2667
|
+
let exactNativeCrypto = false;
|
|
2668
|
+
let packageArtifacts = Object.freeze([]);
|
|
2669
|
+
let compileError;
|
|
2670
|
+
let reloadTimer;
|
|
2671
|
+
const watchEnabled = project.dev.watch && options.watch !== false;
|
|
2672
|
+
const watched = new Map();
|
|
2673
|
+
const events = typeof options.onEvent === 'function' ? options.onEvent : () => {};
|
|
2674
|
+
function syncWatchFiles(files) {
|
|
2675
|
+
if (!watchEnabled) return;
|
|
2676
|
+
const desired = new Set((files || []).map((file) => path.resolve(file)));
|
|
2677
|
+
for (const [file, listener] of watched) {
|
|
2678
|
+
if (desired.has(file)) continue;
|
|
2679
|
+
fs.unwatchFile(file, listener);
|
|
2680
|
+
watched.delete(file);
|
|
2681
|
+
}
|
|
2682
|
+
for (const file of desired) {
|
|
2683
|
+
if (watched.has(file)) continue;
|
|
2684
|
+
const listener = scheduleReload;
|
|
2685
|
+
fs.watchFile(file, { interval: 200 }, listener);
|
|
2686
|
+
watched.set(file, listener);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
function reload(reason) {
|
|
2690
|
+
try {
|
|
2691
|
+
const nextCompiled = compileProject(project);
|
|
2692
|
+
const nextExactNativeCrypto = requiresExactNativeCrypto(nextCompiled);
|
|
2693
|
+
const nextProgram = nextExactNativeCrypto ? null : loadCanonicalModule(nextCompiled);
|
|
2694
|
+
const nextNative = nextExactNativeCrypto
|
|
2695
|
+
? compileNativeProjectInMemory(project, { ...options, compiled: nextCompiled }).native
|
|
2696
|
+
: null;
|
|
2697
|
+
compiled = nextCompiled;
|
|
2698
|
+
program = nextProgram;
|
|
2699
|
+
native = nextNative;
|
|
2700
|
+
exactNativeCrypto = nextExactNativeCrypto;
|
|
2701
|
+
packageArtifacts = packageRealizationArtifactsForCompiled(nextCompiled);
|
|
2702
|
+
compileError = undefined;
|
|
2703
|
+
syncWatchFiles(compiled.watchFiles);
|
|
2704
|
+
events(Object.freeze({
|
|
2705
|
+
event: reason === 'initial' ? 'compiled' : 'reloaded',
|
|
2706
|
+
entry: relative(project.entryFile, project.root),
|
|
2707
|
+
sourceHash: compiled.metadata.sourceHash,
|
|
2708
|
+
projectSourceHash: compiled.metadata.projectSourceHash,
|
|
2709
|
+
schemaSourceHash: compiled.metadata.schemaSourceHash,
|
|
2710
|
+
watchFiles: Object.freeze(compiled.watchFiles.map((file) => relative(file, project.root)))
|
|
2711
|
+
}));
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
if (!program) compileError = error;
|
|
2714
|
+
events(Object.freeze({ event: 'compile-error', retainedLastGoodProgram: Boolean(program), error: errorSummary(error, project.dev.secrets) }));
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
function scheduleReload() {
|
|
2718
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2719
|
+
reloadTimer = setTimeout(() => { reloadTimer = undefined; reload('change'); }, 60);
|
|
2720
|
+
}
|
|
2721
|
+
reload('initial');
|
|
2722
|
+
if (compileError) throw compileError;
|
|
2723
|
+
|
|
2724
|
+
let handled = 0;
|
|
2725
|
+
const server = http.createServer(async (req, res) => {
|
|
2726
|
+
if (options.once) res.once('finish', () => server.close());
|
|
2727
|
+
try {
|
|
2728
|
+
if (compileError) throw compileError;
|
|
2729
|
+
const bodyLimit = project.schemas.active ? Math.min(project.dev.maxBodyBytes, project.schemas.maxBytes) : project.dev.maxBodyBytes;
|
|
2730
|
+
const body = await readRequestBody(req, bodyLimit);
|
|
2731
|
+
const host = req.headers.host || `${project.dev.host}:${project.dev.port}`;
|
|
2732
|
+
const url = new URL(req.url || '/', `http://${host}`);
|
|
2733
|
+
const executionOptions = providerExecutionOptions(project, {
|
|
2734
|
+
packageArtifacts,
|
|
2735
|
+
request: { method: req.method || 'GET', url: url.href, path: url.pathname, headers: requestHeaders(req), body: body || undefined },
|
|
2736
|
+
config: project.dev.config,
|
|
2737
|
+
secrets: project.dev.secrets,
|
|
2738
|
+
kv: project.dev.kv,
|
|
2739
|
+
fetches: project.dev.fetches,
|
|
2740
|
+
maxBodyBytes: bodyLimit,
|
|
2741
|
+
fetchImplementation: project.dev.networkFetch ? globalThis.fetch : undefined,
|
|
2742
|
+
liveFetch: project.dev.networkFetch,
|
|
2743
|
+
executionId: `dev:${++handled}`
|
|
2744
|
+
});
|
|
2745
|
+
const execution = exactNativeCrypto
|
|
2746
|
+
? await executeCanonicalNativeModule(native, executionOptions)
|
|
2747
|
+
: await executeCanonicalProgram(program, executionOptions);
|
|
2748
|
+
writeNodeHttpResponse(res, execution.response);
|
|
2749
|
+
events(Object.freeze({ event: 'request', method: req.method || 'GET', path: url.pathname, status: execution.response.status, effects: execution.effectCount }));
|
|
2750
|
+
} catch (error) {
|
|
2751
|
+
devErrorResponse(res, error, project.dev.secrets);
|
|
2752
|
+
events(Object.freeze({ event: 'request-error', method: req.method || 'GET', path: req.url || '/', error: errorSummary(error, project.dev.secrets) }));
|
|
2753
|
+
}
|
|
2754
|
+
});
|
|
2755
|
+
|
|
2756
|
+
function cleanup() {
|
|
2757
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2758
|
+
for (const [file, listener] of watched) fs.unwatchFile(file, listener);
|
|
2759
|
+
watched.clear();
|
|
2760
|
+
}
|
|
2761
|
+
server.once('close', cleanup);
|
|
2762
|
+
server.once('error', cleanup);
|
|
2763
|
+
const listenHost = options.host || project.dev.host;
|
|
2764
|
+
const listenPort = options.port !== undefined ? Number(options.port) : project.dev.port;
|
|
2765
|
+
server.listen(listenPort, listenHost);
|
|
2766
|
+
await once(server, 'listening');
|
|
2767
|
+
const address = server.address();
|
|
2768
|
+
const boundHost = typeof address === 'object' && address ? address.address : listenHost;
|
|
2769
|
+
const boundPort = typeof address === 'object' && address ? address.port : listenPort;
|
|
2770
|
+
const advertisedHost = listenHost === '0.0.0.0' || listenHost === '::' ? '127.0.0.1' : listenHost;
|
|
2771
|
+
const urlHost = advertisedHost.includes(':') ? `[${advertisedHost}]` : advertisedHost;
|
|
2772
|
+
const ready = Object.freeze({
|
|
2773
|
+
event: 'ready',
|
|
2774
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
2775
|
+
provider: project.provider,
|
|
2776
|
+
host: boundHost,
|
|
2777
|
+
port: boundPort,
|
|
2778
|
+
url: `http://${urlHost}:${boundPort}`,
|
|
2779
|
+
watch: watched.size > 0,
|
|
2780
|
+
watchFiles: Object.freeze([...watched.keys()].map((file) => relative(file, project.root))),
|
|
2781
|
+
once: Boolean(options.once),
|
|
2782
|
+
project: projectJson(project)
|
|
2783
|
+
});
|
|
2784
|
+
events(ready);
|
|
2785
|
+
return Object.freeze({ server, ready, closed: once(server, 'close') });
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
function initProject(target, options = {}) {
|
|
2789
|
+
const root = path.resolve(target || '.');
|
|
2790
|
+
if (fs.existsSync(root)) {
|
|
2791
|
+
const entries = fs.readdirSync(root);
|
|
2792
|
+
if (entries.length > 0 && !options.force) throw new PulseProjectError('PULSE_INIT_NOT_EMPTY', `Target directory is not empty: ${root}`, { root, hint: 'Use --force only when replacing the generated Pulse files is intentional.' });
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
const normalizedName = String(options.name || path.basename(root) || 'pulse-app').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
2796
|
+
const name = normalizedName || 'pulse-app';
|
|
2797
|
+
const provider = String(options.provider || 'node').toLowerCase();
|
|
2798
|
+
let driver;
|
|
2799
|
+
try {
|
|
2800
|
+
driver = getProviderDriver(provider);
|
|
2801
|
+
} catch (_) {
|
|
2802
|
+
throw new PulseProjectError(
|
|
2803
|
+
'PULSE_INIT_PROVIDER_UNSUPPORTED',
|
|
2804
|
+
'pulse init requires a registered executable provider template.',
|
|
2805
|
+
{ provider, supported: providerIds({ executable: true }) }
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
if (!driver.executable || typeof driver.initTemplate !== 'function') {
|
|
2809
|
+
throw new PulseProjectError(
|
|
2810
|
+
'PULSE_INIT_PROVIDER_UNSUPPORTED',
|
|
2811
|
+
'pulse init requires a registered executable provider template.',
|
|
2812
|
+
{ provider, supported: providerIds({ executable: true }) }
|
|
2813
|
+
);
|
|
2814
|
+
}
|
|
2815
|
+
const template = driver.initTemplate(name);
|
|
2816
|
+
const profileFragments = ` outDir: 'dist',
|
|
2817
|
+
dev: { host: '127.0.0.1', port: 8787 },
|
|
2818
|
+
${template.profileFragment || ''}`;
|
|
2819
|
+
|
|
2820
|
+
const dependencies = { '@pulse-compute/pulse': cliPackageVersion };
|
|
2821
|
+
Object.assign(dependencies, template.dependencies || {});
|
|
2822
|
+
|
|
2823
|
+
const files = {
|
|
2824
|
+
'.gitignore': 'dist\nnode_modules\n',
|
|
2825
|
+
'.pulse/.gitignore': '*\n!.gitignore\n!config.ts\n',
|
|
2826
|
+
'.pulse/config.ts': `import { defineConfig } from '@pulse-compute/pulse';
|
|
2827
|
+
|
|
2828
|
+
export default defineConfig((_scope) => ({
|
|
2829
|
+
pulse: {
|
|
2830
|
+
entry: 'src/index.ts',
|
|
2831
|
+
tests: 'tests/pulse.harness.ts',
|
|
2832
|
+
defaultProfile: 'local',
|
|
2833
|
+
strict: true,
|
|
2834
|
+
},
|
|
2835
|
+
local: {
|
|
2836
|
+
host: '${provider}',
|
|
2837
|
+
target: 'native',
|
|
2838
|
+
${profileFragments}
|
|
2839
|
+
},
|
|
2840
|
+
}));
|
|
2841
|
+
`,
|
|
2842
|
+
'README.md': `# ${name}\n\nInstall dependencies with \`npm install\`, then run \`npm run doctor\`.\n\nThe generated project uses the conventional \`.pulse/config.ts\` workspace, an async \`Pulse\` application root, and a dedicated test harness.\n\nUse \`npm run dev\`, \`npm test\`, \`npm run inspect\`, \`npm run compile\`, and \`npm run build\` for the canonical project workflow.\n`,
|
|
2843
|
+
'package.json': stableJson({
|
|
2844
|
+
name,
|
|
2845
|
+
version: '0.0.0',
|
|
2846
|
+
private: true,
|
|
2847
|
+
scripts: {
|
|
2848
|
+
build: 'pulse build',
|
|
2849
|
+
compile: 'pulse compile',
|
|
2850
|
+
dev: 'pulse dev',
|
|
2851
|
+
doctor: 'pulse doctor',
|
|
2852
|
+
inspect: 'pulse inspect',
|
|
2853
|
+
test: 'pulse test'
|
|
2854
|
+
},
|
|
2855
|
+
dependencies,
|
|
2856
|
+
devDependencies: { '@pulse-compute/cli': cliPackageVersion, typescript: '5.9.3' }
|
|
2857
|
+
}),
|
|
2858
|
+
'src/index.ts': `import { Pulse } from '@pulse-compute/pulse';
|
|
2859
|
+
|
|
2860
|
+
const app = new Pulse({ auto: true });
|
|
2861
|
+
|
|
2862
|
+
app.get('/health', async (ctx) => {
|
|
2863
|
+
return ctx.json({ ok: true });
|
|
2864
|
+
});
|
|
2865
|
+
|
|
2866
|
+
app.get('/', async (ctx) => {
|
|
2867
|
+
return ctx.text('Pulse is running');
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
export default app;
|
|
2871
|
+
`,
|
|
2872
|
+
'tests/pulse.harness.ts': `export default {
|
|
2873
|
+
cases: [
|
|
2874
|
+
{
|
|
2875
|
+
name: 'health',
|
|
2876
|
+
request: { method: 'GET', path: '/health' },
|
|
2877
|
+
expect: { status: 200, json: { ok: true } },
|
|
2878
|
+
},
|
|
2879
|
+
],
|
|
2880
|
+
};
|
|
2881
|
+
`,
|
|
2882
|
+
'tsconfig.json': stableJson({
|
|
2883
|
+
compilerOptions: {
|
|
2884
|
+
target: 'ES2022',
|
|
2885
|
+
module: 'NodeNext',
|
|
2886
|
+
moduleResolution: 'NodeNext',
|
|
2887
|
+
strict: true,
|
|
2888
|
+
noEmit: true,
|
|
2889
|
+
skipLibCheck: false
|
|
2890
|
+
},
|
|
2891
|
+
include: ['src/**/*.ts', '.pulse/**/*.ts', 'tests/**/*.ts']
|
|
2892
|
+
})
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
const fileNames = Object.keys(files).sort();
|
|
2896
|
+
for (const name of fileNames) {
|
|
2897
|
+
const file = path.join(root, name);
|
|
2898
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
2899
|
+
if (fs.existsSync(file) && !options.force) throw new PulseProjectError('PULSE_INIT_FILE_EXISTS', `Refusing to overwrite ${file}.`, { file });
|
|
2900
|
+
fs.writeFileSync(file, files[name]);
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
return Object.freeze({
|
|
2904
|
+
status: 'initialized',
|
|
2905
|
+
version: PROJECT_EXECUTION_VERSION,
|
|
2906
|
+
root,
|
|
2907
|
+
files: Object.freeze(fileNames),
|
|
2908
|
+
packageManager: 'npm',
|
|
2909
|
+
nextSteps: Object.freeze([`cd ${root}`, 'npm install', 'npm run doctor'])
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
module.exports = {
|
|
2914
|
+
PROJECT_EXECUTION_VERSION,
|
|
2915
|
+
BUILD_MANIFEST,
|
|
2916
|
+
COMPILE_MANIFEST,
|
|
2917
|
+
resolveOutputDirectory,
|
|
2918
|
+
compileProject,
|
|
2919
|
+
compileNativeProjectInMemory,
|
|
2920
|
+
prepareJavascriptApplication,
|
|
2921
|
+
compileNativeProject,
|
|
2922
|
+
buildProject,
|
|
2923
|
+
runProjectTests,
|
|
2924
|
+
inspectProject,
|
|
2925
|
+
doctorProject,
|
|
2926
|
+
startDevServer,
|
|
2927
|
+
initProject,
|
|
2928
|
+
errorSummary,
|
|
2929
|
+
responseJson
|
|
2930
|
+
};
|