@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 +3 -3
- package/package.json +9 -9
- package/src/canonical-native-compiler.js +20 -4
- package/src/canonical-native-plan.js +1 -0
- package/src/canonical-project-compiler.js +8 -1
- package/src/javascript-application-plan.js +1 -0
- package/src/project/npm-source-resolution.js +91 -0
- package/src/project/package-reachability.js +2 -1
- package/src/project/reachable-graph-builder.js +131 -19
- package/src/report-handler-topology.js +104 -0
- package/src/report-reference-provenance.js +123 -0
- package/src/report-response-payloads.js +62 -0
- package/src/report-route-behavior.js +15 -0
- package/src/report-router-control.js +42 -0
- package/src/report-source-inputs.js +55 -0
- package/src/spine/binding-ownership.js +13 -0
- package/src/spine/canonical-router.js +1 -0
- package/src/spine/handler-surface-authority.js +71 -0
- package/src/spine/package-operation-seam.js +21 -7
- package/src/spine/router-handler-frontend.js +6 -13
- package/src/spine/terminal-package-native.js +4 -2
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
13
|
-
"@pulse-compute/wasm-contracts": "1.0.0-beta.
|
|
14
|
-
"@pulse-compute/wasm-guest-link": "1.0.0-beta.
|
|
15
|
-
"@pulse-compute/wasm-host-runtime": "1.0.0-beta.
|
|
16
|
-
"@pulse-compute/wasm-library-kit": "1.0.0-beta.
|
|
17
|
-
"@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.
|
|
18
|
-
"@pulse-compute/wasm-schema-json": "1.0.0-beta.
|
|
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.
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 = '
|
|
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
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()),
|