@pulse-compute/wasm-compiler 0.0.0 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +53 -1
  2. package/bin/provider-proof-composition.js +34 -0
  3. package/bin/pulsewasm-extract.js +20 -0
  4. package/package.json +60 -5
  5. package/src/artifacts-dir.js +13 -0
  6. package/src/ast-json.js +52 -0
  7. package/src/build-manifest.js +354 -0
  8. package/src/canonical-api-compiler.js +269 -0
  9. package/src/canonical-native-compiler.js +411 -0
  10. package/src/canonical-native-plan.js +1478 -0
  11. package/src/canonical-project-compiler.js +1224 -0
  12. package/src/canonical-router-compiler.js +25 -0
  13. package/src/cli-intents.js +1235 -0
  14. package/src/cli.js +1927 -0
  15. package/src/codegen/assemblyscript-compile.js +3 -0
  16. package/src/codegen/assemblyscript-core.js +3 -0
  17. package/src/codegen/assemblyscript-shape.js +3 -0
  18. package/src/codegen/assemblyscript-wasm-smoke.js +3 -0
  19. package/src/codegen/backend-capabilities.js +3 -0
  20. package/src/codegen/channel-broadcaster.js +3 -0
  21. package/src/codegen/compiled-handlers.js +3 -0
  22. package/src/codegen/compiled-wasm-runtime.js +3 -0
  23. package/src/codegen/config-references.js +248 -0
  24. package/src/codegen/dispatch-ts.js +145 -0
  25. package/src/codegen/effect-composition.js +331 -0
  26. package/src/codegen/effect-runtime.js +418 -0
  27. package/src/codegen/execution-harness-ts.js +467 -0
  28. package/src/codegen/handler-bindings-ts.js +298 -0
  29. package/src/codegen/handler-library-contracts.js +3 -0
  30. package/src/codegen/host-capabilities.js +3 -0
  31. package/src/codegen/host-runtime-kernel.js +3 -0
  32. package/src/codegen/integrated-compiled-app.js +3 -0
  33. package/src/codegen/json-body.js +3 -0
  34. package/src/codegen/library-sidecars.js +3 -0
  35. package/src/codegen/local-harness-ts.js +453 -0
  36. package/src/codegen/pulse-wrapper.js +217 -0
  37. package/src/codegen/request-result-headers.js +3 -0
  38. package/src/codegen/schema-json-compile.js +3 -0
  39. package/src/codegen/schema-json-sidecar-v2.js +3 -0
  40. package/src/codegen/schema-json-sidecar.js +3 -0
  41. package/src/codegen/streaming-passthrough.js +3 -0
  42. package/src/codegen/wasm-host-abi.js +3 -0
  43. package/src/codegen/wasm-host-bridge.js +3 -0
  44. package/src/compiled-wasm-host-runtime-kv.js +3 -0
  45. package/src/config-resolver.js +813 -0
  46. package/src/crypto-requirement-planner.js +89 -0
  47. package/src/definitions/config-schema.js +14 -0
  48. package/src/definitions/handler-roles.js +14 -0
  49. package/src/definitions/path-grammar.js +14 -0
  50. package/src/definitions/router-api.js +14 -0
  51. package/src/diagnostics/codes.js +14 -0
  52. package/src/diagnostics/reporter.js +14 -0
  53. package/src/diagnostics.js +14 -0
  54. package/src/dispatch-table.js +400 -0
  55. package/src/events/event-emit.js +265 -0
  56. package/src/events/event-topology.js +127 -0
  57. package/src/execution-plan.js +463 -0
  58. package/src/extractor.js +2816 -0
  59. package/src/handler-eval.js +1154 -0
  60. package/src/handler-table.js +326 -0
  61. package/src/index.js +19 -0
  62. package/src/javascript-application-plan.js +181 -0
  63. package/src/kv-provider.js +3 -0
  64. package/src/path-table.js +60 -0
  65. package/src/path.js +14 -0
  66. package/src/patterns/config-define.js +30 -0
  67. package/src/patterns/dependency-call.js +18 -0
  68. package/src/patterns/env-lookup.js +13 -0
  69. package/src/patterns/handler-reference.js +29 -0
  70. package/src/patterns/path-literal.js +35 -0
  71. package/src/patterns/result.js +15 -0
  72. package/src/patterns/router-chain-call.js +50 -0
  73. package/src/patterns/router-construction.js +19 -0
  74. package/src/project/package-reachability.js +782 -0
  75. package/src/project/reachable-graph-builder.js +992 -0
  76. package/src/project/reachable-graph-contract.js +54 -0
  77. package/src/project/reachable-graph-implementation.js +36 -0
  78. package/src/project/router-module-linker.js +710 -0
  79. package/src/project-config-compiler.js +222 -0
  80. package/src/project-target-support.js +500 -0
  81. package/src/provider-toolchain.js +299 -0
  82. package/src/spine/async-surface-normalizer.js +328 -0
  83. package/src/spine/canonical-handler-ir.js +336 -0
  84. package/src/spine/canonical-native-module.js +87 -0
  85. package/src/spine/canonical-native-plan.js +89 -0
  86. package/src/spine/canonical-project.js +76 -0
  87. package/src/spine/canonical-router.js +155 -0
  88. package/src/spine/canonical-source.js +165 -0
  89. package/src/spine/diagnostic-authority.js +290 -0
  90. package/src/spine/equivalence.js +262 -0
  91. package/src/spine/guest-unit-stage.js +87 -0
  92. package/src/spine/handler-ir-emitter.js +455 -0
  93. package/src/spine/handler-ir-managed.js +1598 -0
  94. package/src/spine/handler-ir.js +797 -0
  95. package/src/spine/handler-surface-authority.js +588 -0
  96. package/src/spine/package-operation-seam.js +1015 -0
  97. package/src/spine/pipeline.js +202 -0
  98. package/src/spine/plain-handler-frontend.js +545 -0
  99. package/src/spine/provider-requirement-authority.js +208 -0
  100. package/src/spine/router-control-contract.js +17 -0
  101. package/src/spine/router-handler-frontend.js +514 -0
  102. package/src/spine/router-handler-ir.js +372 -0
  103. package/src/spine/router-topology-frontend.js +638 -0
  104. package/src/stable-id.js +14 -0
@@ -0,0 +1,992 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const ts = require('typescript');
7
+ const { discoverPackageContractCatalog } = require('../spine/package-operation-seam.js');
8
+ const {
9
+ PackageReachabilityError,
10
+ buildPackageBindingOwnership,
11
+ addKnownLifecycleEdges,
12
+ deriveReachableGraphProjections
13
+ } = require('./package-reachability.js');
14
+
15
+ function loadGraphContract() {
16
+ try {
17
+ return require('@pulse-compute/wasm-contracts/project/reachable-graph');
18
+ } catch (error) {
19
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
20
+ return require('../../../contracts/src/project/reachable-graph.js');
21
+ }
22
+ throw error;
23
+ }
24
+ }
25
+
26
+ const graph = loadGraphContract();
27
+
28
+ const PROJECT_GRAPH_BUILDER_VERSION = 'pulse.compiler-project-graph-builder.v1';
29
+ const PROJECT_GRAPH_CONTEXT_VERSION = 'pulse.compiler-project-graph-context.v1';
30
+ const CORE_AUTHORING_PACKAGES = new Set(['@pulse-compute/runtime', '@pulse-compute/pulse']);
31
+ const contexts = new WeakMap();
32
+
33
+ class ReachableProjectGraphError extends Error {
34
+ constructor(message, diagnostics = [], detail = {}) {
35
+ super(message);
36
+ this.name = 'ReachableProjectGraphError';
37
+ this.code = 'PULSE_PROJECT_GRAPH_FAILED';
38
+ this.diagnostics = Object.freeze([...diagnostics]);
39
+ this.detail = Object.freeze({ ...detail });
40
+ }
41
+ }
42
+
43
+ function sha256(value) {
44
+ return crypto.createHash('sha256').update(value).digest('hex');
45
+ }
46
+
47
+ function scriptKindForFile(filePath) {
48
+ const ext = path.extname(filePath).toLowerCase();
49
+ if (ext === '.ts' || ext === '.mts' || ext === '.cts' || ext === '.d.ts' || ext === '.d.mts' || ext === '.d.cts') return ts.ScriptKind.TS;
50
+ if (ext === '.tsx') return ts.ScriptKind.TSX;
51
+ if (ext === '.jsx') return ts.ScriptKind.JSX;
52
+ if (ext === '.json') return ts.ScriptKind.JSON;
53
+ return ts.ScriptKind.JS;
54
+ }
55
+
56
+ function moduleFormat(filePath) {
57
+ const normalized = String(filePath).toLowerCase();
58
+ if (/\.d\.(?:ts|mts|cts)$/.test(normalized)) return 'declaration';
59
+ const ext = path.extname(normalized);
60
+ if (['.ts', '.tsx', '.mts', '.cts'].includes(ext)) return 'typescript';
61
+ if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return 'javascript';
62
+ if (ext === '.json') return 'json';
63
+ return 'typescript';
64
+ }
65
+
66
+ function isPlainObject(value) {
67
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
68
+ const proto = Object.getPrototypeOf(value);
69
+ return proto === Object.prototype || proto === null;
70
+ }
71
+
72
+ function portablePath(rootDir, filePath) {
73
+ const relative = path.relative(rootDir, filePath).replace(/\\/g, '/');
74
+ if (!relative) return '.';
75
+ return graph.normalizePortablePath(relative, 'project module path', { allowDot: false });
76
+ }
77
+
78
+ function portableFilePath(rootDir, filePath) {
79
+ const value = portablePath(rootDir, filePath);
80
+ if (value === '.') throw new TypeError('Expected a file below the workspace root.');
81
+ return value;
82
+ }
83
+
84
+ function portableSourceFileName(rootDir, fileName) {
85
+ if (path.isAbsolute(fileName)) return portableFilePath(rootDir, fileName);
86
+ return graph.normalizePortablePath(String(fileName).replace(/\\/g, '/'), 'project module source', { allowDot: false });
87
+ }
88
+
89
+ function sourceLocation(rootDir, sourceFile, node) {
90
+ const target = node || sourceFile;
91
+ const offset = target && typeof target.getStart === 'function' ? target.getStart(sourceFile) : 0;
92
+ const point = sourceFile.getLineAndCharacterOfPosition(offset);
93
+ const file = path.isAbsolute(sourceFile.fileName)
94
+ ? portableFilePath(rootDir, sourceFile.fileName)
95
+ : graph.normalizePortablePath(sourceFile.fileName, 'source file');
96
+ return Object.freeze({
97
+ file,
98
+ line: point.line + 1,
99
+ column: point.character + 1
100
+ });
101
+ }
102
+
103
+ function diagnostic(rootDir, sourceFile, node, code, message, detail = {}) {
104
+ const location = sourceLocation(rootDir, sourceFile, node);
105
+ return Object.freeze({
106
+ code,
107
+ kind: 'ReachableProjectGraphDiagnostic',
108
+ severity: 'error',
109
+ message,
110
+ file: location.file,
111
+ position: Object.freeze({ line: location.line, column: location.column }),
112
+ detail: Object.freeze({ ...detail })
113
+ });
114
+ }
115
+
116
+ function hasModifier(node, kind) {
117
+ return Boolean(node && node.modifiers && node.modifiers.some((modifier) => modifier.kind === kind));
118
+ }
119
+
120
+ function isTypeOnlyImport(statement) {
121
+ const clause = statement.importClause;
122
+ if (!clause) return false;
123
+ if (clause.isTypeOnly) return true;
124
+ return Boolean(!clause.name && clause.namedBindings && ts.isNamedImports(clause.namedBindings)
125
+ && clause.namedBindings.elements.length > 0
126
+ && clause.namedBindings.elements.every((element) => element.isTypeOnly));
127
+ }
128
+
129
+ function importBindings(statement) {
130
+ const clause = statement.importClause;
131
+ if (!clause) return Object.freeze([]);
132
+ const out = [];
133
+ if (clause.name) out.push(Object.freeze({ localName: clause.name.text, importedName: 'default', typeOnly: clause.isTypeOnly === true }));
134
+ const bindings = clause.namedBindings;
135
+ if (bindings && ts.isNamespaceImport(bindings)) {
136
+ out.push(Object.freeze({ localName: bindings.name.text, importedName: '*', namespace: true, typeOnly: clause.isTypeOnly === true }));
137
+ } else if (bindings && ts.isNamedImports(bindings)) {
138
+ for (const element of bindings.elements) {
139
+ out.push(Object.freeze({
140
+ localName: element.name.text,
141
+ importedName: element.propertyName ? element.propertyName.text : element.name.text,
142
+ typeOnly: clause.isTypeOnly === true || element.isTypeOnly === true
143
+ }));
144
+ }
145
+ }
146
+ return Object.freeze(out);
147
+ }
148
+
149
+ function exportNameForDeclaration(statement) {
150
+ if (!hasModifier(statement, ts.SyntaxKind.ExportKeyword)) return undefined;
151
+ if (hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) return 'default';
152
+ return statement.name && ts.isIdentifier(statement.name) ? statement.name.text : undefined;
153
+ }
154
+
155
+ function propertyNameText(node) {
156
+ if (!node) return undefined;
157
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return String(node.text);
158
+ return undefined;
159
+ }
160
+
161
+ function parseModule(rootDir, absolutePath, logicalPathInput) {
162
+ const sourceText = fs.readFileSync(absolutePath, 'utf8');
163
+ const logicalPath = logicalPathInput ? graph.normalizePortablePath(logicalPathInput, 'project module path') : portableFilePath(rootDir, absolutePath);
164
+ const sourceFile = ts.createSourceFile(logicalPath, sourceText, ts.ScriptTarget.ES2022, true, scriptKindForFile(absolutePath));
165
+ const imports = [];
166
+ const reExports = [];
167
+ const localExports = new Map();
168
+ const localDeclarations = new Set();
169
+ const parseDiagnostics = [];
170
+
171
+ for (const entry of sourceFile.parseDiagnostics || []) {
172
+ const start = entry.start || 0;
173
+ const node = sourceFile;
174
+ const point = sourceFile.getLineAndCharacterOfPosition(start);
175
+ parseDiagnostics.push(Object.freeze({
176
+ code: 'PULSE_PROJECT_MODULE_SYNTAX_ERROR',
177
+ kind: 'ReachableProjectGraphDiagnostic',
178
+ severity: 'error',
179
+ message: ts.flattenDiagnosticMessageText(entry.messageText, '\n'),
180
+ file: logicalPath,
181
+ position: Object.freeze({ line: point.line + 1, column: point.character + 1 }),
182
+ detail: Object.freeze({ typescriptCode: entry.code })
183
+ }));
184
+ }
185
+
186
+ for (const statement of sourceFile.statements) {
187
+ if (ts.isImportDeclaration(statement)) {
188
+ if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
189
+ const bindings = importBindings(statement);
190
+ const typeBindings = bindings.filter((binding) => binding.typeOnly === true);
191
+ const runtimeBindings = bindings.filter((binding) => binding.typeOnly !== true);
192
+ const source = sourceLocation(rootDir, sourceFile, statement.moduleSpecifier);
193
+ if (!statement.importClause || runtimeBindings.length > 0) {
194
+ imports.push(Object.freeze({
195
+ kind: 'runtime-import',
196
+ specifier: statement.moduleSpecifier.text,
197
+ bindings: Object.freeze(runtimeBindings),
198
+ importedNames: Object.freeze(runtimeBindings.map((binding) => binding.importedName)),
199
+ exportedNames: Object.freeze([]),
200
+ source,
201
+ statement
202
+ }));
203
+ }
204
+ if (statement.importClause && (statement.importClause.isTypeOnly === true || typeBindings.length > 0)) {
205
+ const selected = statement.importClause.isTypeOnly === true ? bindings : typeBindings;
206
+ imports.push(Object.freeze({
207
+ kind: 'type-import',
208
+ specifier: statement.moduleSpecifier.text,
209
+ bindings: Object.freeze(selected),
210
+ importedNames: Object.freeze(selected.map((binding) => binding.importedName)),
211
+ exportedNames: Object.freeze([]),
212
+ source,
213
+ statement
214
+ }));
215
+ }
216
+ continue;
217
+ }
218
+
219
+ if (ts.isExportDeclaration(statement)) {
220
+ const moduleSpecifier = statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)
221
+ ? statement.moduleSpecifier.text
222
+ : undefined;
223
+ const typeOnly = statement.isTypeOnly === true;
224
+ const mappings = [];
225
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
226
+ for (const element of statement.exportClause.elements) {
227
+ mappings.push(Object.freeze({
228
+ localName: element.propertyName ? element.propertyName.text : element.name.text,
229
+ exportName: element.name.text,
230
+ typeOnly: typeOnly || element.isTypeOnly === true
231
+ }));
232
+ }
233
+ }
234
+ if (moduleSpecifier) {
235
+ const source = sourceLocation(rootDir, sourceFile, statement.moduleSpecifier);
236
+ if (!statement.exportClause) {
237
+ reExports.push(Object.freeze({
238
+ kind: 're-export',
239
+ specifier: moduleSpecifier,
240
+ star: true,
241
+ typeOnly,
242
+ mappings: Object.freeze([]),
243
+ importedNames: Object.freeze([]),
244
+ exportedNames: Object.freeze([]),
245
+ source,
246
+ statement
247
+ }));
248
+ } else {
249
+ const runtimeMappings = mappings.filter((mapping) => mapping.typeOnly !== true);
250
+ const typeMappings = mappings.filter((mapping) => mapping.typeOnly === true);
251
+ if (runtimeMappings.length > 0) reExports.push(Object.freeze({
252
+ kind: 're-export',
253
+ specifier: moduleSpecifier,
254
+ star: false,
255
+ typeOnly: false,
256
+ mappings: Object.freeze(runtimeMappings),
257
+ importedNames: Object.freeze(runtimeMappings.map((entry) => entry.localName)),
258
+ exportedNames: Object.freeze(runtimeMappings.map((entry) => entry.exportName)),
259
+ source,
260
+ statement
261
+ }));
262
+ if (typeOnly || typeMappings.length > 0) {
263
+ const selected = typeOnly ? mappings : typeMappings;
264
+ reExports.push(Object.freeze({
265
+ kind: 're-export',
266
+ specifier: moduleSpecifier,
267
+ star: false,
268
+ typeOnly: true,
269
+ mappings: Object.freeze(selected),
270
+ importedNames: Object.freeze(selected.map((entry) => entry.localName)),
271
+ exportedNames: Object.freeze(selected.map((entry) => entry.exportName)),
272
+ source,
273
+ statement
274
+ }));
275
+ }
276
+ }
277
+ } else {
278
+ for (const mapping of mappings) localExports.set(mapping.exportName, mapping.localName);
279
+ }
280
+ continue;
281
+ }
282
+
283
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) {
284
+ localExports.set('default', statement.expression.text);
285
+ continue;
286
+ }
287
+
288
+ if (ts.isFunctionDeclaration(statement)) {
289
+ if (statement.name) localDeclarations.add(statement.name.text);
290
+ const exported = exportNameForDeclaration(statement);
291
+ if (exported && statement.name) localExports.set(exported, statement.name.text);
292
+ continue;
293
+ }
294
+
295
+ if (ts.isClassDeclaration(statement)) {
296
+ if (statement.name) localDeclarations.add(statement.name.text);
297
+ const exported = exportNameForDeclaration(statement);
298
+ if (exported && statement.name) localExports.set(exported, statement.name.text);
299
+ continue;
300
+ }
301
+
302
+ if (ts.isVariableStatement(statement)) {
303
+ for (const declaration of statement.declarationList.declarations) {
304
+ if (!ts.isIdentifier(declaration.name)) continue;
305
+ localDeclarations.add(declaration.name.text);
306
+ if (hasModifier(statement, ts.SyntaxKind.ExportKeyword)) localExports.set(declaration.name.text, declaration.name.text);
307
+ }
308
+ continue;
309
+ }
310
+ }
311
+
312
+ return {
313
+ path: logicalPath,
314
+ absolutePath,
315
+ sourceText,
316
+ sourceFile,
317
+ contentHash: sha256(sourceText),
318
+ format: moduleFormat(absolutePath),
319
+ imports,
320
+ reExports,
321
+ localExports,
322
+ localDeclarations,
323
+ parseDiagnostics,
324
+ resolutions: []
325
+ };
326
+ }
327
+
328
+ function readJsonFile(filePath) {
329
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
330
+ catch (_) { return undefined; }
331
+ }
332
+
333
+ function canonicalExistingPath(value) {
334
+ const resolved = path.resolve(value);
335
+ return fs.existsSync(resolved) ? fs.realpathSync(resolved) : resolved;
336
+ }
337
+
338
+ function findTsconfig(rootDir, explicit) {
339
+ if (explicit) return canonicalExistingPath(explicit);
340
+
341
+ const candidate = path.join(rootDir, 'tsconfig.json');
342
+ return fs.existsSync(candidate)
343
+ ? canonicalExistingPath(candidate)
344
+ : undefined;
345
+ }
346
+
347
+ function resolverInputFromProject(rootDir, options = {}) {
348
+ if (options.resolver) return graph.normalizeModuleResolverInput(options.resolver);
349
+ const configFile = findTsconfig(rootDir, options.tsconfigFile);
350
+ if (!configFile || !fs.existsSync(configFile)) return graph.normalizeModuleResolverInput({ baseUrl: '.' });
351
+ const source = fs.readFileSync(configFile, 'utf8');
352
+ const parsed = ts.parseConfigFileTextToJson(configFile, source);
353
+ if (parsed.error) {
354
+ throw new ReachableProjectGraphError('Unable to parse the project tsconfig for reachable graph resolution.', [Object.freeze({
355
+ code: 'PULSE_PROJECT_TSCONFIG_INVALID',
356
+ kind: 'ReachableProjectGraphDiagnostic',
357
+ severity: 'error',
358
+ message: ts.flattenDiagnosticMessageText(parsed.error.messageText, '\n'),
359
+ file: portableFilePath(rootDir, configFile),
360
+ position: Object.freeze({ line: 1, column: 1 }),
361
+ detail: Object.freeze({ typescriptCode: parsed.error.code })
362
+ })], { configFile: portableFilePath(rootDir, configFile) });
363
+ }
364
+ if (parsed.config && parsed.config.extends != null) {
365
+ throw new ReachableProjectGraphError('The first reachable graph resolver does not implement tsconfig extends.', [Object.freeze({
366
+ code: 'PULSE_PROJECT_TSCONFIG_EXTENDS_UNSUPPORTED',
367
+ kind: 'ReachableProjectGraphDiagnostic',
368
+ severity: 'error',
369
+ message: 'Reachable graph resolution currently supports direct compilerOptions.baseUrl and compilerOptions.paths only.',
370
+ file: portableFilePath(rootDir, configFile),
371
+ position: Object.freeze({ line: 1, column: 1 }),
372
+ detail: Object.freeze({ extends: parsed.config.extends })
373
+ })], { configFile: portableFilePath(rootDir, configFile) });
374
+ }
375
+ const compilerOptions = parsed.config && parsed.config.compilerOptions || {};
376
+ const baseUrlAbsolute = path.resolve(path.dirname(configFile), compilerOptions.baseUrl || '.');
377
+ const baseUrl = portablePath(rootDir, baseUrlAbsolute);
378
+ const paths = compilerOptions.paths && isPlainObject(compilerOptions.paths) ? compilerOptions.paths : {};
379
+ const pathAliases = Object.entries(paths).map(([pattern, targets]) => ({
380
+ pattern,
381
+ targets: Array.isArray(targets) ? targets.map((target) => {
382
+ const absolute = path.resolve(baseUrlAbsolute, target);
383
+ return portablePath(rootDir, absolute.replace('*', '__PULSE_WILDCARD__')).replace('__PULSE_WILDCARD__', '*');
384
+ }) : []
385
+ })).filter((entry) => entry.targets.length > 0);
386
+ return graph.normalizeModuleResolverInput({
387
+ baseUrl,
388
+ pathAliases,
389
+ configFile: portableFilePath(rootDir, configFile),
390
+ configContentHash: sha256(source)
391
+ });
392
+ }
393
+
394
+ function findPackageWorkspaceRoot(startDir) {
395
+ const fallback = path.resolve(startDir);
396
+ let current = fallback;
397
+ while (true) {
398
+ const manifestPath = path.join(current, 'package.json');
399
+ const manifest = fs.existsSync(manifestPath) ? readJsonFile(manifestPath) : undefined;
400
+ if ((manifest && manifest.workspaces) || fs.existsSync(path.join(current, 'pnpm-workspace.yaml'))) return current;
401
+ const parent = path.dirname(current);
402
+ if (parent === current) return fallback;
403
+ current = parent;
404
+ }
405
+ }
406
+
407
+ function workspacePackageIndex(workspaceRoot) {
408
+ const out = new Map();
409
+ for (const parent of ['packages', path.join('wasm', 'packages')]) {
410
+ const root = path.join(workspaceRoot, parent);
411
+ if (!fs.existsSync(root)) continue;
412
+ for (const name of fs.readdirSync(root).sort()) {
413
+ const manifestPath = path.join(root, name, 'package.json');
414
+ if (!fs.existsSync(manifestPath)) continue;
415
+ const manifest = readJsonFile(manifestPath);
416
+ if (manifest && typeof manifest.name === 'string') out.set(manifest.name, manifestPath);
417
+ }
418
+ }
419
+ return out;
420
+ }
421
+
422
+ function searchNodeModulesPackage(importerFile, packageName, stopDir) {
423
+ let current = path.dirname(importerFile);
424
+ const stop = path.resolve(stopDir);
425
+ while (true) {
426
+ const candidate = path.join(current, 'node_modules', ...packageName.split('/'), 'package.json');
427
+ if (fs.existsSync(candidate)) return candidate;
428
+ if (current === stop || current === path.dirname(current)) break;
429
+ current = path.dirname(current);
430
+ }
431
+ return undefined;
432
+ }
433
+
434
+ function packageOwner(packageName) {
435
+ if (CORE_AUTHORING_PACKAGES.has(packageName)) return 'pulse-runtime';
436
+ if (packageName.startsWith('@pulse-compute/')) return 'pulse-package';
437
+ return 'third-party';
438
+ }
439
+
440
+ function packageModuleForSpecifier(context, importer, classification) {
441
+ const packageName = classification.packageName;
442
+ const specifier = classification.specifier;
443
+ const contract = context.packageContractsBySubpath.get(specifier) || null;
444
+ const productBinding = context.packageProductContractsBySpecifier.get(specifier) || null;
445
+ const productContract = productBinding && productBinding.contract || context.packageProductContractsByPackage.get(packageName) || null;
446
+ const productRole = productBinding && productBinding.role || null;
447
+ let manifestPath = context.workspacePackages.get(packageName);
448
+ if (!manifestPath) manifestPath = searchNodeModulesPackage(importer.absolutePath, packageName, context.workspaceRoot);
449
+ let manifest;
450
+ let manifestHash;
451
+ if (manifestPath) {
452
+ const source = fs.readFileSync(manifestPath, 'utf8');
453
+ manifest = JSON.parse(source);
454
+ manifestHash = sha256(source);
455
+ } else {
456
+ manifest = { name: packageName, version: 'unresolved' };
457
+ manifestHash = sha256(JSON.stringify(manifest));
458
+ }
459
+ const packageSubpath = classification.packageSubpath || '.';
460
+ const key = `package:${packageName}:${packageSubpath}:${manifestHash}`;
461
+ if (!context.packageModules.has(key)) {
462
+ context.packageModules.set(key, {
463
+ key,
464
+ kind: 'package',
465
+ owner: packageOwner(packageName),
466
+ format: 'javascript',
467
+ runtime: false,
468
+ packageName,
469
+ packageVersion: String(manifest.version || 'unresolved'),
470
+ packageSubpath,
471
+ packageManifestHash: manifestHash,
472
+ packageContract: productContract && productContract.contractId || contract && contract.contractId || null,
473
+ contentHash: sha256(`${manifestHash}:${packageSubpath}:${productContract && productContract.metadataHash || ''}:${productRole || ''}`),
474
+ exports: contract && contract.facadeSymbols || (productRole === 'helper' || productRole === 're-export'
475
+ ? [productContract && productContract.composition && productContract.composition.helperSymbol].filter(Boolean)
476
+ : productContract && productContract.authoring && productContract.authoring.symbols || []),
477
+ reExports: [],
478
+ source: null,
479
+ specifier,
480
+ classification,
481
+ contract,
482
+ productContract,
483
+ productRole,
484
+ coreAuthoring: CORE_AUTHORING_PACKAGES.has(packageName)
485
+ });
486
+ }
487
+ return context.packageModules.get(key);
488
+ }
489
+
490
+ function generatedConfigModule(context) {
491
+ if (!context.configFile) return undefined;
492
+ const key = 'generated:pulse-project-config';
493
+ if (!context.generatedModules.has(key)) {
494
+ const source = fs.existsSync(context.configFile) ? fs.readFileSync(context.configFile) : Buffer.from('');
495
+ context.generatedModules.set(key, {
496
+ key,
497
+ kind: 'generated',
498
+ owner: 'generated',
499
+ format: 'generated',
500
+ runtime: true,
501
+ generator: 'pulse.project-config',
502
+ logicalName: 'configured-project-plan',
503
+ contentHash: sha256(source),
504
+ exports: ['default'],
505
+ reExports: [],
506
+ source: null
507
+ });
508
+ }
509
+ return context.generatedModules.get(key);
510
+ }
511
+
512
+ function realContainedPath(rootDir, candidate) {
513
+ if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) return undefined;
514
+ const rootReal = fs.realpathSync(rootDir);
515
+ const candidateReal = fs.realpathSync(candidate);
516
+ const relative = path.relative(rootReal, candidateReal);
517
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return undefined;
518
+ return candidateReal;
519
+ }
520
+
521
+ function resolveProjectSpecifier(context, importer, specifier) {
522
+ const candidates = graph.projectResolutionCandidates(importer.path, specifier, context.resolver);
523
+ const existing = [];
524
+ for (const portable of candidates) {
525
+ const absolute = realContainedPath(context.rootDir, path.resolve(context.rootDir, portable));
526
+ if (absolute) existing.push({ portable, absolute });
527
+ }
528
+ const unique = Array.from(new Map(existing.map((entry) => [entry.absolute, entry])).values());
529
+ if (unique.length === 0) return { status: 'missing', candidates };
530
+ if (unique.length > 1) return { status: 'ambiguous', candidates: unique.map((entry) => entry.portable) };
531
+ return { status: 'resolved', ...unique[0] };
532
+ }
533
+
534
+ function scanUnsupportedBoundaries(context, module) {
535
+ function visit(node) {
536
+ if (ts.isCallExpression(node)) {
537
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
538
+ const argument = node.arguments[0];
539
+ const specifier = argument && ts.isStringLiteral(argument) ? argument.text : '<computed-dynamic-import>';
540
+ context.unsupportedBoundaries.push({
541
+ kind: argument && ts.isStringLiteral(argument) ? 'dynamic-import' : 'computed-specifier',
542
+ from: module.path,
543
+ specifier,
544
+ source: sourceLocation(context.rootDir, module.sourceFile, node)
545
+ });
546
+ } else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
547
+ const argument = node.arguments[0];
548
+ const specifier = argument && ts.isStringLiteral(argument) ? argument.text : '<computed-require>';
549
+ context.unsupportedBoundaries.push({
550
+ kind: argument && ts.isStringLiteral(argument) ? 'commonjs-require' : 'computed-specifier',
551
+ from: module.path,
552
+ specifier,
553
+ source: sourceLocation(context.rootDir, module.sourceFile, node)
554
+ });
555
+ }
556
+ }
557
+ ts.forEachChild(node, visit);
558
+ }
559
+ visit(module.sourceFile);
560
+ }
561
+
562
+ function resolveModuleEdges(context, module) {
563
+ const relations = [...module.imports, ...module.reExports];
564
+ for (const relation of relations) {
565
+ const classification = graph.classifyModuleSpecifier(relation.specifier, context.resolver);
566
+ if (classification.kind === 'project-relative' || classification.kind === 'tsconfig-path') {
567
+ const resolved = resolveProjectSpecifier(context, module, relation.specifier);
568
+ if (resolved.status === 'missing') {
569
+ context.diagnostics.push(diagnostic(context.rootDir, module.sourceFile, relation.statement, 'PULSE_PROJECT_MODULE_NOT_FOUND', `Unable to resolve project module ${JSON.stringify(relation.specifier)} from ${module.path}.`, { specifier: relation.specifier, candidates: resolved.candidates }));
570
+ continue;
571
+ }
572
+ if (resolved.status === 'ambiguous') {
573
+ context.diagnostics.push(diagnostic(context.rootDir, module.sourceFile, relation.statement, 'PULSE_PROJECT_MODULE_AMBIGUOUS', `Project module ${JSON.stringify(relation.specifier)} resolves to more than one file.`, { specifier: relation.specifier, candidates: resolved.candidates }));
574
+ continue;
575
+ }
576
+ if (context.configFile && path.resolve(resolved.absolute) === context.configFile) {
577
+ const target = generatedConfigModule(context);
578
+ module.resolutions.push(Object.freeze({ relation, classification: Object.freeze({ kind: 'generated', specifier: relation.specifier }), targetKey: target.key, generated: true }));
579
+ context.edges.push({
580
+ kind: relation.kind,
581
+ from: module.path,
582
+ to: target.key,
583
+ specifier: relation.specifier,
584
+ resolutionKind: 'generated',
585
+ runtime: relation.kind === 'type-import' ? false : relation.typeOnly !== true,
586
+ importedNames: relation.importedNames || [],
587
+ exportedNames: relation.exportedNames || [],
588
+ packageContract: null,
589
+ source: relation.source
590
+ });
591
+ continue;
592
+ }
593
+ const target = loadProjectModule(context, resolved.absolute, resolved.portable);
594
+ module.resolutions.push(Object.freeze({ relation, classification, targetKey: target.path }));
595
+ context.edges.push({
596
+ kind: relation.kind,
597
+ from: module.path,
598
+ to: target.path,
599
+ specifier: relation.specifier,
600
+ resolutionKind: classification.kind,
601
+ runtime: relation.kind === 'type-import' ? false : relation.typeOnly !== true,
602
+ importedNames: relation.importedNames || [],
603
+ exportedNames: relation.exportedNames || [],
604
+ packageContract: null,
605
+ source: relation.source
606
+ });
607
+ continue;
608
+ }
609
+
610
+ if (classification.kind === 'external') {
611
+ const key = `external:${classification.specifier}`;
612
+ if (!context.externalModules.has(key)) context.externalModules.set(key, {
613
+ key,
614
+ kind: 'external',
615
+ owner: 'external',
616
+ format: 'external',
617
+ runtime: relation.kind !== 'type-import',
618
+ specifier: classification.specifier,
619
+ contentHash: null,
620
+ exports: [],
621
+ reExports: [],
622
+ source: null
623
+ });
624
+ module.resolutions.push(Object.freeze({ relation, classification, targetKey: key }));
625
+ context.edges.push({
626
+ kind: relation.kind,
627
+ from: module.path,
628
+ to: key,
629
+ specifier: relation.specifier,
630
+ resolutionKind: 'external',
631
+ runtime: relation.kind !== 'type-import',
632
+ importedNames: relation.importedNames || [],
633
+ exportedNames: relation.exportedNames || [],
634
+ packageContract: null,
635
+ source: relation.source
636
+ });
637
+ continue;
638
+ }
639
+
640
+ const packageModule = packageModuleForSpecifier(context, module, classification);
641
+ module.resolutions.push(Object.freeze({ relation, classification, targetKey: packageModule.key }));
642
+ const packageContractEdge = relation.kind !== 'type-import' && packageModule.packageContract != null;
643
+ context.edges.push({
644
+ kind: packageContractEdge ? 'package-contract' : relation.kind,
645
+ from: module.path,
646
+ to: packageModule.key,
647
+ specifier: relation.specifier,
648
+ resolutionKind: classification.kind,
649
+ runtime: relation.kind !== 'type-import',
650
+ importedNames: relation.importedNames || [],
651
+ exportedNames: relation.exportedNames || [],
652
+ packageContract: packageContractEdge ? packageModule.packageContract : null,
653
+ source: relation.source
654
+ });
655
+ }
656
+ }
657
+
658
+ function loadProjectModule(context, absolutePath, logicalPath) {
659
+ const contained = realContainedPath(context.rootDir, absolutePath);
660
+ if (!contained) throw new ReachableProjectGraphError(`Project module ${absolutePath} is outside the workspace or missing.`, [], { rootDir: context.rootDir, file: absolutePath });
661
+ const key = logicalPath
662
+ ? graph.normalizePortablePath(logicalPath, 'project module path')
663
+ : portablePath(context.rootDir, path.resolve(absolutePath));
664
+ if (context.projectModules.has(key)) return context.projectModules.get(key);
665
+ const folded = key.toLocaleLowerCase('en-US');
666
+ const collision = [...context.projectModules.keys()].find((existing) => existing.toLocaleLowerCase('en-US') === folded && existing !== key);
667
+ if (collision) {
668
+ throw new ReachableProjectGraphError(`Project module path ${key} collides by case with ${collision}.`, [Object.freeze({
669
+ code: 'PULSE_PROJECT_MODULE_CASE_COLLISION',
670
+ kind: 'ReachableProjectGraphDiagnostic',
671
+ severity: 'error',
672
+ message: `Project module paths ${collision} and ${key} differ only by case.`,
673
+ file: key,
674
+ position: Object.freeze({ line: 1, column: 1 }),
675
+ detail: Object.freeze({ existing: collision, candidate: key })
676
+ })], { rootDir: context.rootDir, existing: collision, candidate: key });
677
+ }
678
+ const module = parseModule(context.rootDir, contained, key);
679
+ context.projectModules.set(key, module);
680
+ context.diagnostics.push(...module.parseDiagnostics);
681
+ scanUnsupportedBoundaries(context, module);
682
+ resolveModuleEdges(context, module);
683
+ return module;
684
+ }
685
+
686
+ function markRuntimeReachability(context, entryKey) {
687
+ const runtime = new Set([entryKey]);
688
+ let changed = true;
689
+ while (changed) {
690
+ changed = false;
691
+ for (const edge of context.edges) {
692
+ if (!edge.runtime || !runtime.has(edge.from) || runtime.has(edge.to)) continue;
693
+ runtime.add(edge.to);
694
+ changed = true;
695
+ }
696
+ }
697
+ for (const module of context.projectModules.values()) module.runtime = runtime.has(module.path);
698
+ for (const module of context.packageModules.values()) module.runtime = runtime.has(module.key);
699
+ for (const module of context.generatedModules.values()) module.runtime = runtime.has(module.key) || module.runtime === true;
700
+ for (const module of context.externalModules.values()) module.runtime = runtime.has(module.key);
701
+ return runtime;
702
+ }
703
+
704
+ function relationTarget(module, relation) {
705
+ const record = module.resolutions.find((entry) => entry.relation === relation);
706
+ return record && record.targetKey;
707
+ }
708
+
709
+ function exportedNamesForModule(module) {
710
+ const names = new Set(module.localExports.keys());
711
+ for (const relation of module.reExports) {
712
+ if (relation.star) names.add('*');
713
+ for (const mapping of relation.mappings) names.add(mapping.exportName);
714
+ }
715
+ return [...names].sort();
716
+ }
717
+
718
+ function moduleInputs(context) {
719
+ const modules = [];
720
+ for (const module of context.projectModules.values()) {
721
+ modules.push({
722
+ key: module.path,
723
+ kind: 'project',
724
+ owner: 'application',
725
+ format: module.format,
726
+ runtime: module.runtime === true,
727
+ path: module.path,
728
+ contentHash: module.contentHash,
729
+ exports: exportedNamesForModule(module),
730
+ reExports: module.reExports.flatMap((entry) => entry.star ? ['*'] : entry.mappings.map((mapping) => mapping.exportName)),
731
+ source: { file: module.path, line: 1, column: 1 }
732
+ });
733
+ }
734
+ for (const module of context.packageModules.values()) modules.push({
735
+ key: module.key,
736
+ kind: module.kind,
737
+ owner: module.owner,
738
+ format: module.format,
739
+ runtime: module.runtime,
740
+ packageName: module.packageName,
741
+ packageVersion: module.packageVersion,
742
+ packageSubpath: module.packageSubpath,
743
+ packageManifestHash: module.packageManifestHash,
744
+ packageContract: module.packageContract,
745
+ contentHash: module.contentHash,
746
+ exports: module.exports,
747
+ reExports: module.reExports,
748
+ source: module.source
749
+ });
750
+ for (const module of context.generatedModules.values()) modules.push(module);
751
+ for (const module of context.externalModules.values()) modules.push(module);
752
+ return modules;
753
+ }
754
+
755
+ function edgeInputs(context) {
756
+ return context.edges.map((edge) => ({ ...edge }));
757
+ }
758
+
759
+ function createContext(entryFile, options = {}) {
760
+ const requestedRoot = path.resolve(options.rootDir || path.dirname(path.resolve(entryFile)));
761
+ const rootDir = fs.existsSync(requestedRoot) ? fs.realpathSync(requestedRoot) : requestedRoot;
762
+ const requestedWorkspace = path.resolve(options.workspaceRoot || findPackageWorkspaceRoot(rootDir));
763
+ const workspaceRoot = fs.existsSync(requestedWorkspace) ? fs.realpathSync(requestedWorkspace) : requestedWorkspace;
764
+ const packageContractCatalog = options.packageContractCatalog || discoverPackageContractCatalog({
765
+ cwd: rootDir,
766
+ workspaceRoot,
767
+ scanNodeModules: true
768
+ });
769
+ const packageContractsBySubpath = new Map();
770
+ for (const entry of packageContractCatalog.contracts) {
771
+ packageContractsBySubpath.set(entry.lowerableSubpath, entry);
772
+ for (const compatibilitySubpath of entry.compatibilitySubpaths || []) packageContractsBySubpath.set(compatibilitySubpath, entry);
773
+ }
774
+ const packageContractsByPackage = new Map();
775
+ for (const entry of packageContractCatalog.contracts) {
776
+ if (!packageContractsByPackage.has(entry.packageName)) packageContractsByPackage.set(entry.packageName, []);
777
+ packageContractsByPackage.get(entry.packageName).push(entry);
778
+ }
779
+ const packageProductContractsByPackage = new Map();
780
+ const packageProductContractsBySpecifier = new Map();
781
+ for (const entry of packageContractCatalog.productCatalog.contracts) {
782
+ packageProductContractsByPackage.set(entry.npmPackage, entry);
783
+ packageProductContractsBySpecifier.set(entry.authoring.import, Object.freeze({ contract: entry, role: 'authoring' }));
784
+ for (const specifier of entry.ownership.helperImports) packageProductContractsBySpecifier.set(specifier, Object.freeze({ contract: entry, role: 'helper' }));
785
+ for (const specifier of entry.ownership.reExportImports) packageProductContractsBySpecifier.set(specifier, Object.freeze({ contract: entry, role: 're-export' }));
786
+ }
787
+ for (const entries of packageContractsByPackage.values()) entries.sort((left, right) => left.lowerableSubpath.localeCompare(right.lowerableSubpath));
788
+ const context = {
789
+ version: PROJECT_GRAPH_CONTEXT_VERSION,
790
+ rootDir,
791
+ workspaceRoot,
792
+ entryFile: canonicalExistingPath(entryFile),
793
+ configFile: options.configFile
794
+ ? canonicalExistingPath(options.configFile)
795
+ : undefined,
796
+ projectFragments: options.projectFragments && typeof options.projectFragments === 'object' && !Array.isArray(options.projectFragments)
797
+ ? Object.freeze({ ...options.projectFragments })
798
+ : Object.freeze({}),
799
+ resolver: resolverInputFromProject(rootDir, options),
800
+ workspacePackages: workspacePackageIndex(workspaceRoot),
801
+ packageContractCatalog,
802
+ packageContractsBySubpath,
803
+ packageContractsByPackage,
804
+ packageProductContractsByPackage,
805
+ packageProductContractsBySpecifier,
806
+ projectModules: new Map(),
807
+ packageModules: new Map(),
808
+ generatedModules: new Map(),
809
+ externalModules: new Map(),
810
+ edges: [],
811
+ unsupportedBoundaries: [],
812
+ diagnostics: []
813
+ };
814
+ return context;
815
+ }
816
+
817
+ function projectGraphContext(build) {
818
+ return build && typeof build === 'object' ? contexts.get(build) : undefined;
819
+ }
820
+
821
+ function finalizeReachableProjectGraph(build, handlerReferences = []) {
822
+ const context = projectGraphContext(build);
823
+ if (!context) throw new TypeError('finalizeReachableProjectGraph requires a project graph build result.');
824
+ return graph.normalizeReachableGraphManifestV2({
825
+ resolver: context.resolver,
826
+ entry: build.entryKey,
827
+ modules: moduleInputs(context),
828
+ edges: edgeInputs(context),
829
+ handlers: handlerReferences,
830
+ unsupportedBoundaries: context.unsupportedBoundaries
831
+ });
832
+ }
833
+
834
+ function reachableProjectGraphProjections(build, manifest) {
835
+ const context = projectGraphContext(build);
836
+ if (!context) throw new TypeError('reachableProjectGraphProjections requires a project graph build result.');
837
+ const targetManifest = manifest || build.graph;
838
+ return deriveReachableGraphProjections(context, targetManifest, context.packageBindingStates);
839
+ }
840
+
841
+ function buildReachableProjectGraph(entryFile, options = {}) {
842
+ const context = createContext(entryFile, options);
843
+ const entry = loadProjectModule(context, context.entryFile, portableFilePath(context.rootDir, context.entryFile));
844
+ markRuntimeReachability(context, entry.path);
845
+ let packageBindingStates;
846
+ try {
847
+ packageBindingStates = buildPackageBindingOwnership(context);
848
+ addKnownLifecycleEdges(context, packageBindingStates);
849
+ markRuntimeReachability(context, entry.path);
850
+ } catch (error) {
851
+ if (error instanceof PackageReachabilityError) {
852
+ throw new ReachableProjectGraphError(error.message, error.diagnostics, error.detail);
853
+ }
854
+ throw error;
855
+ }
856
+
857
+ const runtimeUnsupported = context.unsupportedBoundaries.filter((boundary) => {
858
+ const module = context.projectModules.get(boundary.from);
859
+ return module && module.runtime;
860
+ });
861
+ if (runtimeUnsupported.length > 0) {
862
+ for (const boundary of runtimeUnsupported) {
863
+ context.diagnostics.push(Object.freeze({
864
+ code: boundary.kind === 'dynamic-import' ? 'PULSE_PROJECT_DYNAMIC_IMPORT_UNSUPPORTED'
865
+ : boundary.kind === 'commonjs-require' ? 'PULSE_PROJECT_COMMONJS_REQUIRE_UNSUPPORTED'
866
+ : 'PULSE_PROJECT_COMPUTED_MODULE_SPECIFIER_UNSUPPORTED',
867
+ kind: 'ReachableProjectGraphDiagnostic',
868
+ severity: 'error',
869
+ message: `Unsupported ${boundary.kind} module boundary ${JSON.stringify(boundary.specifier)} in ${boundary.source.file}.`,
870
+ file: boundary.source.file,
871
+ position: Object.freeze({ line: boundary.source.line, column: boundary.source.column }),
872
+ detail: Object.freeze({ boundaryKind: boundary.kind, specifier: boundary.specifier })
873
+ }));
874
+ }
875
+ }
876
+
877
+ const preliminary = graph.normalizeReachableGraphManifestV2({
878
+ resolver: context.resolver,
879
+ entry: entry.path,
880
+ modules: moduleInputs(context),
881
+ edges: edgeInputs(context),
882
+ handlers: [],
883
+ unsupportedBoundaries: context.unsupportedBoundaries
884
+ });
885
+ if (preliminary.cycles.length > 0) {
886
+ const modulesById = new Map(preliminary.modules.map((module) => [module.id, module]));
887
+ for (const cycle of preliminary.cycles) {
888
+ const memberIds = new Set(cycle.modules);
889
+ const cycleEdges = preliminary.edges.filter((edge) => memberIds.has(edge.from) && memberIds.has(edge.to) && edge.runtime && edge.source);
890
+ cycleEdges.sort((left, right) => left.source.file.localeCompare(right.source.file) || left.source.line - right.source.line || left.source.column - right.source.column || left.id.localeCompare(right.id));
891
+ const source = cycleEdges[0] && cycleEdges[0].source || { file: entry.path, line: 1, column: 1 };
892
+ const modulePaths = cycle.modules.map((moduleId) => {
893
+ const module = modulesById.get(moduleId);
894
+ return module && (module.path || module.packageName || module.externalSpecifier) || moduleId;
895
+ });
896
+ context.diagnostics.push(Object.freeze({
897
+ code: 'PULSE_PROJECT_MODULE_CYCLE_UNSUPPORTED',
898
+ kind: 'ReachableProjectGraphDiagnostic',
899
+ severity: 'error',
900
+ message: `Runtime project module cycle ${modulePaths.join(' -> ')} is not supported in the first multi-module compiler implementation.`,
901
+ file: source.file,
902
+ position: Object.freeze({ line: source.line, column: source.column }),
903
+ detail: Object.freeze({ cycleId: cycle.id, modules: Object.freeze(modulePaths) })
904
+ }));
905
+ }
906
+ }
907
+
908
+ if (context.diagnostics.length > 0) {
909
+ throw new ReachableProjectGraphError(`Reachable project graph failed with ${context.diagnostics.length} diagnostic(s).`, context.diagnostics, { entryFile: context.entryFile, rootDir: context.rootDir });
910
+ }
911
+
912
+ const build = Object.freeze({
913
+ version: PROJECT_GRAPH_BUILDER_VERSION,
914
+ contractVersion: graph.REACHABLE_GRAPH_MANIFEST_V2_VERSION,
915
+ entryKey: entry.path,
916
+ graph: preliminary,
917
+ projectFiles: Object.freeze([...context.projectModules.values()].map((module) => module.absolutePath).sort()),
918
+ runtimeProjectFiles: Object.freeze([...context.projectModules.values()].filter((module) => module.runtime).map((module) => module.absolutePath).sort()),
919
+ projectModulePaths: Object.freeze([...context.projectModules.values()].map((module) => module.path).sort()),
920
+ runtimeProjectModulePaths: Object.freeze([...context.projectModules.values()].filter((module) => module.runtime).map((module) => module.path).sort()),
921
+ deferredPackageModules: Object.freeze([...context.packageModules.values()].map((module) => Object.freeze({
922
+ key: module.key,
923
+ packageName: module.packageName,
924
+ packageVersion: module.packageVersion,
925
+ packageSubpath: module.packageSubpath,
926
+ runtime: module.runtime,
927
+ packageContract: module.packageContract || null
928
+ })).sort((a, b) => a.key.localeCompare(b.key))),
929
+ packageContractCatalog: Object.freeze({
930
+ version: context.packageContractCatalog.version,
931
+ contracts: Object.freeze(context.packageContractCatalog.contracts.map((entry) => Object.freeze({
932
+ contractId: entry.contractId,
933
+ packageName: entry.packageName,
934
+ lowerableSubpath: entry.lowerableSubpath,
935
+ compatibilitySubpaths: entry.compatibilitySubpaths || Object.freeze([]),
936
+ facadeSymbols: entry.facadeSymbols,
937
+ hostCapabilities: entry.hostCapabilities,
938
+ targetSupport: entry.targetSupport,
939
+ productContract: entry.productContract
940
+ })))
941
+ }),
942
+ summary: Object.freeze({
943
+ projectModules: context.projectModules.size,
944
+ runtimeProjectModules: [...context.projectModules.values()].filter((module) => module.runtime).length,
945
+ typeOnlyProjectModules: [...context.projectModules.values()].filter((module) => !module.runtime).length,
946
+ packageModules: context.packageModules.size,
947
+ edges: context.edges.length,
948
+ unsupportedBoundaries: context.unsupportedBoundaries.length,
949
+ cycles: preliminary.cycles.length
950
+ })
951
+ });
952
+ context.packageBindingStates = packageBindingStates;
953
+ contexts.set(build, context);
954
+ const projections = deriveReachableGraphProjections(context, preliminary, packageBindingStates);
955
+ const result = Object.freeze({
956
+ ...build,
957
+ packageReachability: projections.packageReachability,
958
+ packageProduct: projections.packageProduct,
959
+ entrySafety: projections.entrySafety,
960
+ nativeEligibility: projections.nativeEligibility
961
+ });
962
+ contexts.set(result, context);
963
+ return result;
964
+ }
965
+
966
+ function resolutionForImport(build, modulePath, localName) {
967
+ const context = projectGraphContext(build);
968
+ const module = context && context.projectModules.get(modulePath);
969
+ if (!module) return undefined;
970
+ for (const relation of module.imports) {
971
+ const binding = relation.bindings.find((entry) => entry.localName === localName);
972
+ if (!binding) continue;
973
+ return Object.freeze({
974
+ module,
975
+ relation,
976
+ binding,
977
+ targetKey: relationTarget(module, relation)
978
+ });
979
+ }
980
+ return undefined;
981
+ }
982
+
983
+ module.exports = Object.freeze({
984
+ PROJECT_GRAPH_BUILDER_VERSION,
985
+ PROJECT_GRAPH_CONTEXT_VERSION,
986
+ ReachableProjectGraphError,
987
+ buildReachableProjectGraph,
988
+ finalizeReachableProjectGraph,
989
+ reachableProjectGraphProjections,
990
+ projectGraphContext,
991
+ resolutionForImport
992
+ });