@pulse-compute/wasm-compiler 1.0.0-beta.7 → 1.0.0-beta.9

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/README.md CHANGED
@@ -8,9 +8,9 @@
8
8
  > **Stability:** Internal compiler interface; no application-author compatibility guarantee.<br>
9
9
  > **npm:** [`@pulse-compute/wasm-compiler`](https://www.npmjs.com/package/@pulse-compute/wasm-compiler)<br>
10
10
  > **Canonical replacement:** `@pulse-compute/cli`<br>
11
- > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.7/packages/implementation-packages/)
11
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.9/packages/implementation-packages/)
12
12
  >
13
- > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.7` package policy.
13
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.9` package policy.
14
14
  <!-- pulse-package-status:end -->
15
15
 
16
16
  Whole-project compiler implementation used by the `pulse` workflow. Application projects should use `@pulse-compute/cli`, not import this package directly.
@@ -63,4 +63,4 @@ The package exports its root implementation plus focused compiler, CLI, extracto
63
63
 
64
64
  Compiler changes should be covered by focused static/lowering tests, canonical project inspection, provider-plan parity, packed-package discovery where relevant, and the release documentation gates.
65
65
 
66
- See the installed [implementation package guide](https://pulsecompute.io/v1.0.0-beta.7/packages/implementation-packages/) and [compilation concepts](https://pulsecompute.io/v1.0.0-beta.7/concepts/compilation-and-lowering/).
66
+ See the installed [implementation package guide](https://pulsecompute.io/v1.0.0-beta.9/packages/implementation-packages/) and [compilation concepts](https://pulsecompute.io/v1.0.0-beta.9/concepts/compilation-and-lowering/).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-compute/wasm-compiler",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.9",
4
4
  "license": "Apache-2.0",
5
5
  "description": "PulseWasm compiler package inside the Pulse monorepo.",
6
6
  "type": "commonjs",
@@ -9,13 +9,13 @@
9
9
  "pulsewasm": "./bin/pulsewasm-extract.js"
10
10
  },
11
11
  "dependencies": {
12
- "@pulse-compute/wasm-build-support": "1.0.0-beta.7",
13
- "@pulse-compute/wasm-contracts": "1.0.0-beta.7",
14
- "@pulse-compute/wasm-guest-link": "1.0.0-beta.7",
15
- "@pulse-compute/wasm-host-runtime": "1.0.0-beta.7",
16
- "@pulse-compute/wasm-library-kit": "1.0.0-beta.7",
17
- "@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.7",
18
- "@pulse-compute/wasm-schema-json": "1.0.0-beta.7",
12
+ "@pulse-compute/wasm-build-support": "1.0.0-beta.9",
13
+ "@pulse-compute/wasm-contracts": "1.0.0-beta.9",
14
+ "@pulse-compute/wasm-guest-link": "1.0.0-beta.9",
15
+ "@pulse-compute/wasm-host-runtime": "1.0.0-beta.9",
16
+ "@pulse-compute/wasm-library-kit": "1.0.0-beta.9",
17
+ "@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.9",
18
+ "@pulse-compute/wasm-schema-json": "1.0.0-beta.9",
19
19
  "assemblyscript": "0.28.18",
20
20
  "json-as": "1.5.0",
21
21
  "typescript": "5.9.3"
@@ -56,7 +56,7 @@
56
56
  "url": "git+https://github.com/pulse-compute/pulse.git",
57
57
  "directory": "wasm/packages/compiler"
58
58
  },
59
- "homepage": "https://pulsecompute.io/v1.0.0-beta.7/packages/implementation-packages/",
59
+ "homepage": "https://pulsecompute.io/v1.0.0-beta.9/packages/implementation-packages/",
60
60
  "bugs": {
61
61
  "url": "https://github.com/pulse-compute/pulse/issues"
62
62
  },
@@ -142,7 +142,14 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
142
142
  source, sourceHash: sha256(source), manifest: Object.freeze({
143
143
  ...application.manifest, planVersion: plan.version, planHash: plan.planHash, sourceHash: sha256(source)
144
144
  })
145
- }) : runtimeCore.generateCanonicalNativeAssemblyScript(plan, options);
145
+ }) : runtimeCore.generateCanonicalNativeAssemblyScript(plan, {
146
+ ...options,
147
+ // This compiler emits the pulse_host artifact consumed by Node Native.
148
+ // Provider writers own their final emission policies. Linked guests retain
149
+ // their existing path until separately qualified; budgeted plans are gated
150
+ // by the canonical numeric proof itself.
151
+ numericRegions: guestUnits.length === 0 ? 'host-numeric-regions-v1' : undefined
152
+ });
146
153
  const schemaCodecsActive = generated.manifest.schemaCodecs && generated.manifest.schemaCodecs.active === true;
147
154
  let jsonAs;
148
155
  if (schemaCodecsActive) {
@@ -221,9 +228,10 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
221
228
  '--path', jsonAs.dependencyRoot
222
229
  );
223
230
  }
231
+ require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
224
232
  const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
225
233
  require(path.join(asc.packageRoot, 'package.json')).version, 'canonical-native.as/');
226
- const startedAt = Date.now();
234
+ const startedAt = process.hrtime.bigint();
227
235
  const result = spawnSync(asc.executable, args, {
228
236
  cwd: stagingDir,
229
237
  encoding: 'utf8',
@@ -233,7 +241,7 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
233
241
  ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
234
242
  : process.env
235
243
  });
236
- const durationMs = Date.now() - startedAt;
244
+ const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
237
245
  if (result.error || result.status !== 0 || !fs.existsSync(wasmFile)) {
238
246
  throw new CanonicalNativeCompileError('AssemblyScript failed to compile the canonical native plan.', {
239
247
  status: result.status,
@@ -253,9 +261,15 @@ function realizeCanonicalNativePlan(plan, options = {}, providerRequirements) {
253
261
  guestUnits,
254
262
  generated,
255
263
  reportAttribution: readReportCapture(wasm),
264
+ buildObservation: require('@pulse-compute/wasm-build-support/native-optimization').observeAssemblyScript(args, {
265
+ version: require(path.join(asc.packageRoot, 'package.json')).version,
266
+ assemblyScriptRoot: asc.packageRoot, timeoutMs: Number(options.timeoutMs || 120000),
267
+ compilerVersion: runtimeContract.CANONICAL_NATIVE_COMPILER_VERSION, durationMs, jsonAsVersion: jsonAs && jsonAs.version
268
+ }),
256
269
  wasm,
257
270
  wat: emitWat ? fs.readFileSync(watFile, 'utf8') : '',
258
271
  textEmitted: emitWat,
272
+ elideJsonCacheArena: options.elideJsonCacheArena === true,
259
273
  assemblyScriptVersion: require(path.join(asc.packageRoot, 'package.json')).version,
260
274
  jsonAsVersion: jsonAs && jsonAs.version,
261
275
  optimization,
@@ -286,8 +300,9 @@ function verifyCanonicalNativeRealization(realization) {
286
300
  compilerVersion: runtimeContract.CANONICAL_NATIVE_COMPILER_VERSION,
287
301
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: realization.assemblyScriptVersion }),
288
302
  optimization: resolveNativeOptimization(realization.optimization),
303
+ elideJsonCacheArena: realization.elideJsonCacheArena,
289
304
  jsonAs: realization.jsonAsVersion
290
- ? Object.freeze({ package: 'json-as', version: realization.jsonAsVersion, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
305
+ ? Object.freeze({ package: 'json-as', version: realization.jsonAsVersion, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: realization.elideJsonCacheArena })
291
306
  : null,
292
307
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
293
308
  wat: Object.freeze(realization.textEmitted
@@ -321,6 +336,7 @@ function verifyCanonicalNativeRealization(realization) {
321
336
  wat: realization.wat,
322
337
  inspection,
323
338
  manifest,
339
+ buildObservation: realization.buildObservation,
324
340
  durationMs: realization.durationMs,
325
341
  output: realization.output
326
342
  });
@@ -1905,6 +1905,7 @@ module.exports = Object.freeze({
1905
1905
  CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION: contract.CANONICAL_NATIVE_PLAN_OWNERSHIP_VERSION,
1906
1906
  CanonicalNativePlanError,
1907
1907
  buildCanonicalNativePlan,
1908
+ collectCanonicalReportReferences: require('./report-reference-provenance').collectCanonicalReportReferences,
1908
1909
  validateCanonicalNativePlan: assertCanonicalNativePlan,
1909
1910
  writeCanonicalNativePlan,
1910
1911
  stableStringify
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { retainSourceInputs, sourceInputsForCompiled } = require('./report-source-inputs');
4
+
3
5
  const fs = require('node:fs');
4
6
  const path = require('node:path');
5
7
  const ts = require('typescript');
@@ -395,6 +397,7 @@ function prepareReachableProjectPackageOperations(graphBuild, options = {}) {
395
397
  // identity checkout-independent. Absolute paths remain private graph
396
398
  // facts and must not feed project or native-plan hashes.
397
399
  ...(preserveSingleFilePublicEnvelope ? { publicSourceName: module.path } : {}),
400
+ packageDirs: context.packageContractCatalog.contracts.map(entry => entry.packageDir),
398
401
  selectedContracts: [...selectedContracts].sort()
399
402
  }));
400
403
  }
@@ -995,6 +998,7 @@ function compileCanonicalProjectLegacy(entryFile, options = {}) {
995
998
  try {
996
999
  graphBuild = buildReachableProjectGraph(absoluteEntry, {
997
1000
  rootDir,
1001
+ target: options.target,
998
1002
  workspaceRoot: options.workspaceRoot,
999
1003
  tsconfigFile: options.tsconfigFile,
1000
1004
  resolver: options.moduleResolver,
@@ -1230,10 +1234,12 @@ function compileCanonicalProjectLegacy(entryFile, options = {}) {
1230
1234
  ...(applicationEnvelope ? { application: applicationEnvelope } : {}),
1231
1235
  watchFiles: Object.freeze([...new Set([
1232
1236
  ...graphBuild.projectFiles,
1237
+ ...graphBuild.packageSourceManifests,
1233
1238
  ...schema.dependencies,
1234
1239
  ...(packageOperationRecognition.dependencies || [])
1235
1240
  ].map((file) => path.resolve(file)))])
1236
1241
  });
1242
+ retainSourceInputs(project, projectGraphContext(graphBuild));
1237
1243
  attachPackageOperationRecognition(project, packageOperationRecognition);
1238
1244
  if (terminalPackageApplication) attachTerminalPackageNative(project, {
1239
1245
  application: terminalPackageApplication, recognition: packageOperationRecognition,
@@ -1261,5 +1267,6 @@ module.exports = Object.freeze({
1261
1267
  compileProjectSchemas,
1262
1268
  compileProjectPackageExtensions,
1263
1269
  compileCanonicalProject,
1264
- packageRealizationArtifactsForCompiled
1270
+ packageRealizationArtifactsForCompiled,
1271
+ sourceInputsForCompiled
1265
1272
  });
@@ -112,6 +112,7 @@ function buildJavascriptApplicationPlan(entryFile, options = {}) {
112
112
  let graphBuild;
113
113
  try {
114
114
  graphBuild = buildReachableProjectGraph(absoluteEntry, {
115
+ target: 'javascript',
115
116
  rootDir: options.rootDir,
116
117
  workspaceRoot: options.workspaceRoot,
117
118
  configFile: options.configFile,
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const ts = require('typescript');
6
+ const { sourceResolution } = require('../report-source-inputs');
7
+ const reportOwners = new WeakMap();
8
+
9
+ // This resolver reads source; it never imports a package or its compiler metadata.
10
+ const CONDITIONS = new Set(['pulse', 'import', 'default']);
11
+
12
+ function fail(message) { throw new Error(message); }
13
+
14
+ function exportTarget(exports, subpath) {
15
+ let selected = exports;
16
+ if (exports && typeof exports === 'object' && !Array.isArray(exports)) {
17
+ const keys = Object.keys(exports);
18
+ if (keys.some(key => key.startsWith('.'))) {
19
+ if (keys.some(key => !key.startsWith('.'))) fail('Mixed export subpaths and conditions are unsupported.');
20
+ if (!Object.hasOwn(exports, subpath)) fail(`No exact declared export for ${subpath}; wildcard exports are unsupported.`);
21
+ selected = exports[subpath];
22
+ } else if (subpath !== '.') fail(`No exact declared export for ${subpath}.`);
23
+ } else if (subpath !== '.') fail(`No exact declared export for ${subpath}.`);
24
+ while (selected && typeof selected === 'object' && !Array.isArray(selected)) {
25
+ const key = Object.keys(selected).find(key => CONDITIONS.has(key));
26
+ if (!key) fail('No pulse, import or default export condition.');
27
+ selected = selected[key];
28
+ }
29
+ if (typeof selected !== 'string') fail('A source export must select one string target; blocked, missing and array targets are unsupported.');
30
+ if (!selected.startsWith('./') || /[\\%?#*\0]/.test(selected)
31
+ || selected.slice(2).split('/').some(part => !part || part === '.' || part === '..' || part.toLowerCase() === 'node_modules')) {
32
+ fail('A source export must name a contained relative file without traversal or node_modules.');
33
+ }
34
+ return selected;
35
+ }
36
+
37
+ function sourceFile(root, candidate, declaration = false, reportOwner, prefix) {
38
+ const relative = path.relative(root, candidate);
39
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)
40
+ || relative.split(path.sep).some(part => part.toLowerCase() === 'node_modules')) fail('Source file escapes its package root.');
41
+ const supported = file => /\.(?:ts|mts|js|mjs)$/.test(file) && (declaration || !/\.d\.(?:ts|mts)$/.test(file));
42
+ if (!supported(candidate)) fail('Only .ts, .mts, .js and .mjs ESM source files are supported.');
43
+ if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) fail('Declared source file does not exist.');
44
+ const real = fs.realpathSync(candidate);
45
+ if (!supported(real)) fail('Source symlink must resolve to a supported ESM source file.');
46
+ const realRelative = path.relative(root, real);
47
+ if (!realRelative || realRelative === '..' || realRelative.startsWith(`..${path.sep}`) || path.isAbsolute(realRelative)
48
+ || realRelative.split(path.sep).some(part => part.toLowerCase() === 'node_modules')) fail('Source symlink escapes its package root.');
49
+ sourceResolution(reportOwner, candidate, real, `${prefix}/${realRelative.split(path.sep).join('/')}`);
50
+ return real;
51
+ }
52
+
53
+ function packageSource(manifestPath, manifest, packageName, subpath, manifestHash, reportOwner) {
54
+ if (manifest.name !== packageName || typeof manifest.version !== 'string'
55
+ || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(manifest.version)) fail('Source packages require a matching name and concrete version.');
56
+ const root = fs.realpathSync(path.dirname(manifestPath));
57
+ const target = exportTarget(manifest.exports, subpath);
58
+ const prefix = `npm/${packageName}@${manifest.version}/${manifestHash}`;
59
+ const absolute = sourceFile(root, path.resolve(root, target), false, reportOwner, prefix);
60
+ if (manifest.type === 'commonjs' && /\.(?:js|ts)$/.test(absolute)) fail('Explicit CommonJS package sources are unsupported; declare an ESM source export.');
61
+ const owner = Object.freeze({ root, absolute, packageName, packageVersion: manifest.version, packageManifestHash: manifestHash, packageType: manifest.type,
62
+ prefix });
63
+ if (reportOwner) reportOwners.set(owner, reportOwner);
64
+ return owner;
65
+ }
66
+
67
+ function relativeSource(owner, importer, specifier, declaration = false) {
68
+ if (!specifier.startsWith('./') && !specifier.startsWith('../')) fail('Package source cannot use application tsconfig aliases.');
69
+ if (/[\\%?#\0]/.test(specifier)) fail('Unsupported package source specifier.');
70
+ // Exact ESM file links only: no extension substitution, directory or main fallback.
71
+ const absolute = sourceFile(owner.root, path.resolve(path.dirname(importer), specifier), declaration, reportOwners.get(owner), owner.prefix);
72
+ if (!declaration && owner.packageType === 'commonjs' && /\.(?:js|ts)$/.test(absolute)) fail('Explicit CommonJS package sources are unsupported.');
73
+ return absolute;
74
+ }
75
+
76
+ function unsupportedStatement(sourceFile) {
77
+ const functionValue = node => {
78
+ while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))) node = node.expression;
79
+ return node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node));
80
+ };
81
+ return sourceFile.statements.find(statement => {
82
+ if (ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement) || ts.isFunctionDeclaration(statement)
83
+ || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEmptyStatement(statement)) return false;
84
+ if (ts.isExportAssignment(statement)) return statement.isExportEquals || !(ts.isIdentifier(statement.expression) || functionValue(statement.expression));
85
+ if (ts.isVariableStatement(statement)) return !(statement.declarationList.flags & ts.NodeFlags.Const)
86
+ || statement.declarationList.declarations.some(declaration => !ts.isIdentifier(declaration.name) || !functionValue(declaration.initializer));
87
+ return true;
88
+ });
89
+ }
90
+
91
+ module.exports = Object.freeze({ packageSource, relativeSource, unsupportedStatement });
@@ -337,7 +337,7 @@ function packageRelationsForModule(context, targetKey) {
337
337
  for (const module of context.projectModules.values()) {
338
338
  for (const relation of [...module.imports, ...module.reExports]) {
339
339
  const resolution = module.resolutions.find((entry) => entry.relation === relation);
340
- if (!resolution || resolution.targetKey !== targetKey) continue;
340
+ if (!resolution || (resolution.packageKey || resolution.targetKey) !== targetKey) continue;
341
341
  records.push(Object.freeze({ module, relation }));
342
342
  }
343
343
  }
@@ -505,6 +505,7 @@ function derivePackageReachabilityProjection(context, manifest, states) {
505
505
  const reExportSources = relations.filter((entry) => entry.relation.kind === 're-export' && entry.relation.typeOnly !== true).map((entry) => entry.relation.source);
506
506
  let nativeStatus = 'unsupported';
507
507
  if (module.coreAuthoring) nativeStatus = 'core-authoring';
508
+ else if (module.sourceModule) nativeStatus = 'static-source';
508
509
  else if (module.packageContract && module.contract && module.contract.targetSupport.native === true) nativeStatus = 'trusted-lowerable';
509
510
  if (module.runtime && nativeStatus === 'trusted-lowerable') selectedContracts.add(module.packageContract);
510
511
  packages.push({
@@ -2,8 +2,10 @@
2
2
 
3
3
  const crypto = require('node:crypto');
4
4
  const fs = require('node:fs');
5
+ const { readSourceInput, missingManifest } = require('../report-source-inputs');
5
6
  const path = require('node:path');
6
7
  const ts = require('typescript');
8
+ const npmSource = require('./npm-source-resolution.js');
7
9
  const { discoverPackageContractCatalog } = require('../spine/package-operation-seam.js');
8
10
  const {
9
11
  PackageReachabilityError,
@@ -34,7 +36,8 @@ class ReachableProjectGraphError extends Error {
34
36
  constructor(message, diagnostics = [], detail = {}) {
35
37
  super(message);
36
38
  this.name = 'ReachableProjectGraphError';
37
- this.code = 'PULSE_PROJECT_GRAPH_FAILED';
39
+ this.code = diagnostics.length > 0 && diagnostics.every(entry => entry.code === 'PULSE_NATIVE_IMPORT_UNSUPPORTED')
40
+ ? 'PULSE_NATIVE_IMPORT_UNSUPPORTED' : 'PULSE_PROJECT_GRAPH_FAILED';
38
41
  this.diagnostics = Object.freeze([...diagnostics]);
39
42
  this.detail = Object.freeze({ ...detail });
40
43
  }
@@ -158,8 +161,8 @@ function propertyNameText(node) {
158
161
  return undefined;
159
162
  }
160
163
 
161
- function parseModule(rootDir, absolutePath, logicalPathInput) {
162
- const sourceText = fs.readFileSync(absolutePath, 'utf8');
164
+ function parseModule(rootDir, absolutePath, logicalPathInput, reportOwner) {
165
+ const sourceText = reportOwner ? readSourceInput(reportOwner, absolutePath, 'source', logicalPathInput) : fs.readFileSync(absolutePath, 'utf8');
163
166
  const logicalPath = logicalPathInput ? graph.normalizePortablePath(logicalPathInput, 'project module path') : portableFilePath(rootDir, absolutePath);
164
167
  const sourceFile = ts.createSourceFile(logicalPath, sourceText, ts.ScriptTarget.ES2022, true, scriptKindForFile(absolutePath));
165
168
  const imports = [];
@@ -419,12 +422,13 @@ function workspacePackageIndex(workspaceRoot) {
419
422
  return out;
420
423
  }
421
424
 
422
- function searchNodeModulesPackage(importerFile, packageName, stopDir) {
425
+ function searchNodeModulesPackage(importerFile, packageName, stopDir, reportOwner) {
423
426
  let current = path.dirname(importerFile);
424
427
  const stop = path.resolve(stopDir);
425
428
  while (true) {
426
429
  const candidate = path.join(current, 'node_modules', ...packageName.split('/'), 'package.json');
427
430
  if (fs.existsSync(candidate)) return candidate;
431
+ missingManifest(reportOwner, candidate, packageName);
428
432
  if (current === stop || current === path.dirname(current)) break;
429
433
  current = path.dirname(current);
430
434
  }
@@ -437,27 +441,73 @@ function packageOwner(packageName) {
437
441
  return 'third-party';
438
442
  }
439
443
 
444
+ function discoverResolvedPackageContract(context, manifestPath, packageName) {
445
+ if (!manifestPath || context.fixedPackageContractCatalog) return;
446
+ const packageDir = fs.realpathSync(path.dirname(manifestPath));
447
+ if (context.packageContractDirs.has(packageDir)) return;
448
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
449
+ if (!manifest.pulse?.contract && !manifest.pulsewasm?.manifest && !manifest.pulsewasm?.lowerableManifest) return;
450
+ if (manifest.name !== packageName) throw new TypeError(`Resolved lowerer package identity must match ${packageName}.`);
451
+ context.packageContractDirs.add(packageDir);
452
+ const catalog = discoverPackageContractCatalog({ cwd: context.rootDir, workspaceRoot: context.workspaceRoot,
453
+ packageDirs: [...context.packageContractDirs] });
454
+ context.packageContractCatalog = catalog;
455
+ for (const entry of catalog.contracts) {
456
+ context.packageContractsBySubpath.set(entry.lowerableSubpath, entry);
457
+ for (const subpath of entry.compatibilitySubpaths) context.packageContractsBySubpath.set(subpath, entry);
458
+ context.packageContractsByPackage.set(entry.packageName, catalog.contracts.filter(value => value.packageName === entry.packageName));
459
+ }
460
+ for (const entry of catalog.productCatalog.contracts) {
461
+ context.packageProductContractsByPackage.set(entry.npmPackage, entry);
462
+ context.packageProductContractsBySpecifier.set(entry.authoring.import, { contract: entry, role: 'authoring' });
463
+ for (const specifier of entry.ownership.helperImports) context.packageProductContractsBySpecifier.set(specifier, { contract: entry, role: 'helper' });
464
+ for (const specifier of entry.ownership.reExportImports) context.packageProductContractsBySpecifier.set(specifier, { contract: entry, role: 're-export' });
465
+ }
466
+ }
467
+
440
468
  function packageModuleForSpecifier(context, importer, classification) {
441
469
  const packageName = classification.packageName;
442
470
  const specifier = classification.specifier;
471
+ // Resolve the installed copy at this import before granting compiler authority.
472
+ const resolvedManifest = searchNodeModulesPackage(importer.absolutePath, packageName, context.workspaceRoot)
473
+ || context.workspacePackages.get(packageName);
474
+ discoverResolvedPackageContract(context, resolvedManifest, packageName);
443
475
  const contract = context.packageContractsBySubpath.get(specifier) || null;
476
+ const declaredOwners = context.packageContractsByPackage.get(packageName) || [];
477
+ if (resolvedManifest && declaredOwners.some(entry => entry.packageDir && fs.realpathSync(entry.packageDir) !== fs.realpathSync(path.dirname(resolvedManifest)))) {
478
+ throw new TypeError(`Resolved package ${packageName} does not match its selected lowerer owner.`);
479
+ }
444
480
  const productBinding = context.packageProductContractsBySpecifier.get(specifier) || null;
445
481
  const productContract = productBinding && productBinding.contract || context.packageProductContractsByPackage.get(packageName) || null;
446
482
  const productRole = productBinding && productBinding.role || null;
447
- let manifestPath = context.workspacePackages.get(packageName);
448
- if (!manifestPath) manifestPath = searchNodeModulesPackage(importer.absolutePath, packageName, context.workspaceRoot);
483
+ const trusted = CORE_AUTHORING_PACKAGES.has(packageName) || productContract || context.packageContractsByPackage.has(packageName);
484
+ let manifestPath = (trusted || !context.nativePackageSources) && resolvedManifest;
485
+ if (!manifestPath && importer.packageSource && importer.packageSource.packageName === packageName) manifestPath = path.join(importer.packageSource.root, 'package.json');
486
+ if (!manifestPath) manifestPath = searchNodeModulesPackage(importer.absolutePath, packageName, context.workspaceRoot, context.nativePackageSources && !trusted ? context : undefined);
449
487
  let manifest;
450
488
  let manifestHash;
451
489
  if (manifestPath) {
452
- const source = fs.readFileSync(manifestPath, 'utf8');
453
- manifest = JSON.parse(source);
454
- manifestHash = sha256(source);
490
+ try {
491
+ const source = context.nativePackageSources && !trusted
492
+ ? readSourceInput(context, manifestPath, 'manifest', packageName) : fs.readFileSync(manifestPath, 'utf8');
493
+ manifest = JSON.parse(source);
494
+ if (!isPlainObject(manifest)) throw new TypeError('Expected a package manifest object.');
495
+ manifestHash = sha256(source);
496
+ } catch (_) {
497
+ throw new ReachableProjectGraphError(`Invalid package manifest for ${packageName}.`, [diagnostic(context.rootDir, importer.sourceFile, null,
498
+ 'PULSE_NATIVE_IMPORT_UNSUPPORTED', `Cannot read a valid package manifest for ${packageName}.`, { packageName })]);
499
+ }
455
500
  } else {
456
501
  manifest = { name: packageName, version: 'unresolved' };
457
502
  manifestHash = sha256(JSON.stringify(manifest));
458
503
  }
459
504
  const packageSubpath = classification.packageSubpath || '.';
460
505
  const key = `package:${packageName}:${packageSubpath}:${manifestHash}`;
506
+ const existing = context.packageModules.get(key);
507
+ if (context.nativePackageSources && existing && existing.sourceCandidate && existing.manifestPath && manifestPath && existing.manifestPath !== fs.realpathSync(manifestPath)) {
508
+ throw new ReachableProjectGraphError(`Ambiguous installed copies of ${packageName}@${manifest.version}.`, [diagnostic(context.rootDir, importer.sourceFile, null,
509
+ 'PULSE_NATIVE_IMPORT_UNSUPPORTED', 'Distinct installed package roots share one package identity; deduplicate the dependency.', { packageName, packageSubpath })]);
510
+ }
461
511
  if (!context.packageModules.has(key)) {
462
512
  context.packageModules.set(key, {
463
513
  key,
@@ -478,6 +528,9 @@ function packageModuleForSpecifier(context, importer, classification) {
478
528
  source: null,
479
529
  specifier,
480
530
  classification,
531
+ manifestPath: manifestPath && fs.realpathSync(manifestPath),
532
+ manifest,
533
+ sourceCandidate: !trusted && Boolean(manifestPath),
481
534
  contract,
482
535
  productContract,
483
536
  productRole,
@@ -487,6 +540,35 @@ function packageModuleForSpecifier(context, importer, classification) {
487
540
  return context.packageModules.get(key);
488
541
  }
489
542
 
543
+ function loadPackageSource(context, packageModule, importer, relation) {
544
+ if (packageModule.sourceModule) return packageModule.sourceModule;
545
+ let owner;
546
+ try {
547
+ owner = npmSource.packageSource(packageModule.manifestPath, packageModule.manifest, packageModule.packageName,
548
+ packageModule.packageSubpath, packageModule.packageManifestHash, context);
549
+ const identity = `${owner.packageName}:${owner.packageManifestHash}`;
550
+ const priorRoot = context.packageSourceRoots.get(identity);
551
+ if (priorRoot && priorRoot !== owner.root) throw new Error('Distinct installed roots share this package identity; deduplicate the dependency.');
552
+ context.packageSourceRoots.set(identity, owner.root);
553
+ } catch (error) {
554
+ context.diagnostics.push(diagnostic(context.rootDir, importer.sourceFile, relation.statement, 'PULSE_NATIVE_IMPORT_UNSUPPORTED',
555
+ `Cannot resolve static source export ${relation.specifier}: ${error.message}`, { packageName: packageModule.packageName,
556
+ packageVersion: packageModule.packageVersion, packageSubpath: packageModule.packageSubpath, automaticFallback: false }));
557
+ return undefined;
558
+ }
559
+ const sourcePath = `${owner.prefix}/${portableFilePath(owner.root, owner.absolute)}`;
560
+ // Register before following imports so cycles are reported by the graph, not recursion.
561
+ packageModule.sourceModule = sourcePath;
562
+ packageModule.source = { file: sourcePath, line: 1, column: 1 };
563
+ const module = loadProjectModule(context, owner.absolute, sourcePath, owner);
564
+ packageModule.contentHash = sha256(`${packageModule.contentHash}:${module.contentHash}`);
565
+ packageModule.exports = exportedNamesForModule(module);
566
+ context.edges.push({ kind: 're-export', from: packageModule.key, to: sourcePath,
567
+ specifier: `./${portableFilePath(owner.root, owner.absolute)}`, resolutionKind: 'project-relative', runtime: true,
568
+ importedNames: [], exportedNames: packageModule.exports, packageContract: null, source: packageModule.source });
569
+ return sourcePath;
570
+ }
571
+
490
572
  function generatedConfigModule(context) {
491
573
  if (!context.configFile) return undefined;
492
574
  const key = 'generated:pulse-project-config';
@@ -562,9 +644,19 @@ function scanUnsupportedBoundaries(context, module) {
562
644
  function resolveModuleEdges(context, module) {
563
645
  const relations = [...module.imports, ...module.reExports];
564
646
  for (const relation of relations) {
565
- const classification = graph.classifyModuleSpecifier(relation.specifier, context.resolver);
647
+ const classification = graph.classifyModuleSpecifier(relation.specifier, module.packageSource ? graph.normalizeModuleResolverInput({ baseUrl: '.' }) : context.resolver);
566
648
  if (classification.kind === 'project-relative' || classification.kind === 'tsconfig-path') {
567
- const resolved = resolveProjectSpecifier(context, module, relation.specifier);
649
+ let resolved;
650
+ if (module.packageSource) {
651
+ try {
652
+ const absolute = npmSource.relativeSource(module.packageSource, module.absolutePath, relation.specifier, relation.kind === 'type-import' || relation.typeOnly === true);
653
+ resolved = { status: 'resolved', absolute, portable: `${module.packageSource.prefix}/${portableFilePath(module.packageSource.root, absolute)}` };
654
+ } catch (error) {
655
+ context.diagnostics.push(diagnostic(context.rootDir, module.sourceFile, relation.statement, 'PULSE_NATIVE_IMPORT_UNSUPPORTED',
656
+ `Cannot resolve package source ${relation.specifier}: ${error.message}`, { automaticFallback: false }));
657
+ continue;
658
+ }
659
+ } else resolved = resolveProjectSpecifier(context, module, relation.specifier);
568
660
  if (resolved.status === 'missing') {
569
661
  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
662
  continue;
@@ -590,7 +682,7 @@ function resolveModuleEdges(context, module) {
590
682
  });
591
683
  continue;
592
684
  }
593
- const target = loadProjectModule(context, resolved.absolute, resolved.portable);
685
+ const target = loadProjectModule(context, resolved.absolute, resolved.portable, module.packageSource);
594
686
  module.resolutions.push(Object.freeze({ relation, classification, targetKey: target.path }));
595
687
  context.edges.push({
596
688
  kind: relation.kind,
@@ -638,7 +730,9 @@ function resolveModuleEdges(context, module) {
638
730
  }
639
731
 
640
732
  const packageModule = packageModuleForSpecifier(context, module, classification);
641
- module.resolutions.push(Object.freeze({ relation, classification, targetKey: packageModule.key }));
733
+ const sourceKey = context.nativePackageSources && relation.kind !== 'type-import' && relation.typeOnly !== true && packageModule.sourceCandidate
734
+ ? loadPackageSource(context, packageModule, module, relation) : undefined;
735
+ module.resolutions.push(Object.freeze({ relation, classification, targetKey: sourceKey || packageModule.key, packageKey: packageModule.key }));
642
736
  const packageContractEdge = relation.kind !== 'type-import' && packageModule.packageContract != null;
643
737
  context.edges.push({
644
738
  kind: packageContractEdge ? 'package-contract' : relation.kind,
@@ -655,13 +749,17 @@ function resolveModuleEdges(context, module) {
655
749
  }
656
750
  }
657
751
 
658
- function loadProjectModule(context, absolutePath, logicalPath) {
659
- const contained = realContainedPath(context.rootDir, absolutePath);
752
+ function loadProjectModule(context, absolutePath, logicalPath, packageSource) {
753
+ const contained = realContainedPath(packageSource ? packageSource.root : context.rootDir, absolutePath);
660
754
  if (!contained) throw new ReachableProjectGraphError(`Project module ${absolutePath} is outside the workspace or missing.`, [], { rootDir: context.rootDir, file: absolutePath });
661
755
  const key = logicalPath
662
756
  ? graph.normalizePortablePath(logicalPath, 'project module path')
663
757
  : portablePath(context.rootDir, path.resolve(absolutePath));
664
- if (context.projectModules.has(key)) return context.projectModules.get(key);
758
+ if (context.projectModules.has(key)) {
759
+ const existing = context.projectModules.get(key);
760
+ if (existing.absolutePath !== contained) throw new ReachableProjectGraphError(`Source identity collision at ${key}.`);
761
+ return existing;
762
+ }
665
763
  const folded = key.toLocaleLowerCase('en-US');
666
764
  const collision = [...context.projectModules.keys()].find((existing) => existing.toLocaleLowerCase('en-US') === folded && existing !== key);
667
765
  if (collision) {
@@ -675,9 +773,16 @@ function loadProjectModule(context, absolutePath, logicalPath) {
675
773
  detail: Object.freeze({ existing: collision, candidate: key })
676
774
  })], { rootDir: context.rootDir, existing: collision, candidate: key });
677
775
  }
678
- const module = parseModule(context.rootDir, contained, key);
776
+ const module = parseModule(context.rootDir, contained, key, packageSource ? context : undefined);
777
+ module.packageSource = packageSource;
679
778
  context.projectModules.set(key, module);
680
779
  context.diagnostics.push(...module.parseDiagnostics);
780
+ if (packageSource) {
781
+ const unsupported = npmSource.unsupportedStatement(module.sourceFile);
782
+ if (unsupported) context.diagnostics.push(diagnostic(context.rootDir, module.sourceFile, unsupported, 'PULSE_NATIVE_IMPORT_UNSUPPORTED',
783
+ 'Package source permits static imports/re-exports, types and function declarations only; module initialization and captured values are unsupported.',
784
+ { packageName: packageSource.packageName, packageVersion: packageSource.packageVersion, automaticFallback: false }));
785
+ }
681
786
  scanUnsupportedBoundaries(context, module);
682
787
  resolveModuleEdges(context, module);
683
788
  return module;
@@ -721,7 +826,9 @@ function moduleInputs(context) {
721
826
  modules.push({
722
827
  key: module.path,
723
828
  kind: 'project',
724
- owner: 'application',
829
+ owner: module.packageSource ? packageOwner(module.packageSource.packageName) : 'application',
830
+ ...(module.packageSource ? { packageName: module.packageSource.packageName, packageVersion: module.packageSource.packageVersion,
831
+ packageManifestHash: module.packageSource.packageManifestHash, packageSubpath: `./${portableFilePath(module.packageSource.root, module.absolutePath)}` } : {}),
725
832
  format: module.format,
726
833
  runtime: module.runtime === true,
727
834
  path: module.path,
@@ -764,7 +871,7 @@ function createContext(entryFile, options = {}) {
764
871
  const packageContractCatalog = options.packageContractCatalog || discoverPackageContractCatalog({
765
872
  cwd: rootDir,
766
873
  workspaceRoot,
767
- scanNodeModules: true
874
+ packageDirs: []
768
875
  });
769
876
  const packageContractsBySubpath = new Map();
770
877
  for (const entry of packageContractCatalog.contracts) {
@@ -797,14 +904,18 @@ function createContext(entryFile, options = {}) {
797
904
  ? Object.freeze({ ...options.projectFragments })
798
905
  : Object.freeze({}),
799
906
  resolver: resolverInputFromProject(rootDir, options),
907
+ nativePackageSources: options.target !== 'javascript',
800
908
  workspacePackages: workspacePackageIndex(workspaceRoot),
801
909
  packageContractCatalog,
910
+ fixedPackageContractCatalog: Boolean(options.packageContractCatalog),
911
+ packageContractDirs: new Set(),
802
912
  packageContractsBySubpath,
803
913
  packageContractsByPackage,
804
914
  packageProductContractsByPackage,
805
915
  packageProductContractsBySpecifier,
806
916
  projectModules: new Map(),
807
917
  packageModules: new Map(),
918
+ packageSourceRoots: new Map(),
808
919
  generatedModules: new Map(),
809
920
  externalModules: new Map(),
810
921
  edges: [],
@@ -915,6 +1026,7 @@ function buildReachableProjectGraph(entryFile, options = {}) {
915
1026
  entryKey: entry.path,
916
1027
  graph: preliminary,
917
1028
  projectFiles: Object.freeze([...context.projectModules.values()].map((module) => module.absolutePath).sort()),
1029
+ packageSourceManifests: Object.freeze([...context.packageModules.values()].filter(module => module.sourceModule).map(module => module.manifestPath).sort()),
918
1030
  runtimeProjectFiles: Object.freeze([...context.projectModules.values()].filter((module) => module.runtime).map((module) => module.absolutePath).sort()),
919
1031
  projectModulePaths: Object.freeze([...context.projectModules.values()].map((module) => module.path).sort()),
920
1032
  runtimeProjectModulePaths: Object.freeze([...context.projectModules.values()].filter((module) => module.runtime).map((module) => module.path).sort()),