@shapeshift-labs/frontier-lang-compiler 0.2.122 → 0.2.124
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 +22 -2
- package/dist/declarations/js-ts-safe-project-merge.d.ts +70 -0
- package/dist/js-ts-safe-project-merge-graph-conflicts.js +61 -8
- package/dist/js-ts-safe-project-merge-graph-delta-conflicts.js +279 -0
- package/dist/js-ts-safe-project-merge-graph.js +135 -17
- package/dist/js-ts-safe-project-merge.js +26 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -289,8 +289,28 @@ the semantic index and project symbol graph.
|
|
|
289
289
|
`safeMergeJsTsProject` stays synchronous. When a caller already has parser-backed
|
|
290
290
|
native import results for merged output files, pass them as `outputProjectImports`
|
|
291
291
|
with `includeOutputProjectSymbolGraph`. The graph builder matches supplied
|
|
292
|
-
imports by `sourcePath` and `sourceHash`,
|
|
293
|
-
|
|
292
|
+
imports by `sourcePath` and `sourceHash`, requires hash-verified matches when
|
|
293
|
+
the merged source has a hash, uses them for output graph artifacts, and falls
|
|
294
|
+
back to the lightweight scanner for missing or stale files.
|
|
295
|
+
|
|
296
|
+
For admission queues that need bounded cross-branch API checks, enable
|
|
297
|
+
`includeProjectGraphDelta`. This additionally builds base, worker, head, and
|
|
298
|
+
output project graph stages and blocks the merge when worker and head both
|
|
299
|
+
change the same public contract, re-export identity, or import target in
|
|
300
|
+
incompatible ways. Parser-backed stage imports can be supplied with
|
|
301
|
+
`baseProjectImports`, `workerProjectImports`, `headProjectImports`, and
|
|
302
|
+
`outputProjectImports`; missing or hash-stale stages fall back to the synchronous
|
|
303
|
+
lightweight scanner. This is a conservative admission gate only: results still
|
|
304
|
+
keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`.
|
|
305
|
+
|
|
306
|
+
Artifact-size and runtime note: these graph options are deliberately opt-in.
|
|
307
|
+
On a local Node v26.1.0 smoke fixture with 10 small JS/TS files and 36 scanned
|
|
308
|
+
stage files for the delta case, baseline project merge JSON was 115 KB at a
|
|
309
|
+
21.6 ms median. `includeOutputProjectSymbolGraph` raised the returned JSON to
|
|
310
|
+
17.8 MB at a 303.1 ms median, and `includeProjectGraphDelta` raised it to
|
|
311
|
+
83.0 MB at a 1,466.8 ms median. Keep these paths behind admission-queue caps
|
|
312
|
+
for file count, total source bytes, graph edge count, and serialized artifact
|
|
313
|
+
bytes, or expose summary/lazy graph materialization before using them broadly.
|
|
294
314
|
|
|
295
315
|
High-risk native features also have explicit evidence policies. These policies are advisory in this package: they tell a swarm or admission queue what evidence is missing without silently changing the existing readiness classification.
|
|
296
316
|
|
|
@@ -51,6 +51,15 @@ export type JsTsProjectSafeMergeOutputProjectImports =
|
|
|
51
51
|
| ReadonlyMap<string, NativeSourceImportResult>
|
|
52
52
|
| Readonly<Record<string, NativeSourceImportResult>>;
|
|
53
53
|
|
|
54
|
+
export type JsTsProjectGraphStageName = 'base' | 'worker' | 'head' | 'output' | string;
|
|
55
|
+
|
|
56
|
+
export type JsTsProjectSafeMergeProjectGraphImportsByStage = Readonly<{
|
|
57
|
+
base?: JsTsProjectSafeMergeOutputProjectImports;
|
|
58
|
+
worker?: JsTsProjectSafeMergeOutputProjectImports;
|
|
59
|
+
head?: JsTsProjectSafeMergeOutputProjectImports;
|
|
60
|
+
output?: JsTsProjectSafeMergeOutputProjectImports;
|
|
61
|
+
} & Record<string, JsTsProjectSafeMergeOutputProjectImports | undefined>>;
|
|
62
|
+
|
|
54
63
|
export interface JsTsProjectSafeMergeInput {
|
|
55
64
|
readonly id?: string;
|
|
56
65
|
readonly language?: FrontierSourceLanguage | string;
|
|
@@ -62,7 +71,12 @@ export interface JsTsProjectSafeMergeInput {
|
|
|
62
71
|
readonly allowFileAdditions?: boolean;
|
|
63
72
|
readonly allowFileDeletes?: boolean;
|
|
64
73
|
readonly includeOutputProjectSymbolGraph?: boolean;
|
|
74
|
+
readonly includeProjectGraphDelta?: boolean;
|
|
65
75
|
readonly outputProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
76
|
+
readonly baseProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
77
|
+
readonly workerProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
78
|
+
readonly headProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
79
|
+
readonly projectGraphImports?: JsTsProjectSafeMergeProjectGraphImportsByStage;
|
|
66
80
|
readonly moduleResolution?: NativeProjectModuleResolutionOptions;
|
|
67
81
|
readonly tsconfig?: NativeProjectModuleResolutionOptions;
|
|
68
82
|
readonly workerChangeSetId?: string;
|
|
@@ -115,6 +129,56 @@ export interface JsTsProjectSafeMergeAdmission {
|
|
|
115
129
|
readonly conflictKeys: readonly string[];
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
export interface JsTsProjectGraphDeltaStageSummary {
|
|
133
|
+
readonly stage: JsTsProjectGraphStageName;
|
|
134
|
+
readonly sourceFiles: number;
|
|
135
|
+
readonly documents: number;
|
|
136
|
+
readonly symbols: number;
|
|
137
|
+
readonly fileHashes: number;
|
|
138
|
+
readonly importEdges: number;
|
|
139
|
+
readonly exportEdges: number;
|
|
140
|
+
readonly publicContractRegions: number;
|
|
141
|
+
readonly reExportIdentities: number;
|
|
142
|
+
readonly unresolvedImportEdges: number;
|
|
143
|
+
readonly suppliedImports: number;
|
|
144
|
+
readonly matchedSuppliedImports: number;
|
|
145
|
+
readonly scannerFallbackImports: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface JsTsProjectGraphDeltaStage {
|
|
149
|
+
readonly kind: 'frontier.lang.jsTsProjectGraphStage';
|
|
150
|
+
readonly version: 1;
|
|
151
|
+
readonly stage: JsTsProjectGraphStageName;
|
|
152
|
+
readonly projectImport?: NativeProjectImportResult;
|
|
153
|
+
readonly projectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
154
|
+
readonly summary: JsTsProjectGraphDeltaStageSummary;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface JsTsProjectGraphDeltaSummary {
|
|
158
|
+
readonly stages: number;
|
|
159
|
+
readonly sourceFiles: number;
|
|
160
|
+
readonly publicContractRegions: number;
|
|
161
|
+
readonly reExportIdentities: number;
|
|
162
|
+
readonly importEdges: number;
|
|
163
|
+
readonly exportEdges: number;
|
|
164
|
+
readonly unresolvedImportEdges: number;
|
|
165
|
+
readonly suppliedImports: number;
|
|
166
|
+
readonly matchedSuppliedImports: number;
|
|
167
|
+
readonly scannerFallbackImports: number;
|
|
168
|
+
readonly conflicts: number;
|
|
169
|
+
readonly publicContractConflicts: number;
|
|
170
|
+
readonly reExportIdentityConflicts: number;
|
|
171
|
+
readonly importTargetConflicts: number;
|
|
172
|
+
readonly stageSummaries: Readonly<Record<string, JsTsProjectGraphDeltaStageSummary>>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface JsTsProjectGraphDelta {
|
|
176
|
+
readonly kind: 'frontier.lang.jsTsProjectGraphDelta';
|
|
177
|
+
readonly version: 1;
|
|
178
|
+
readonly stages: Readonly<Record<string, JsTsProjectGraphDeltaStage>>;
|
|
179
|
+
readonly summary: JsTsProjectGraphDeltaSummary;
|
|
180
|
+
}
|
|
181
|
+
|
|
118
182
|
export interface JsTsProjectSafeMergeResult {
|
|
119
183
|
readonly kind: 'frontier.lang.jsTsProjectSafeMerge';
|
|
120
184
|
readonly version: 1;
|
|
@@ -126,6 +190,7 @@ export interface JsTsProjectSafeMergeResult {
|
|
|
126
190
|
readonly outputFiles: readonly JsTsProjectSafeMergeOutputFile[];
|
|
127
191
|
readonly outputProjectImport?: NativeProjectImportResult;
|
|
128
192
|
readonly outputProjectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
193
|
+
readonly projectGraphDelta?: JsTsProjectGraphDelta;
|
|
129
194
|
readonly conflicts: readonly JsTsSafeMergeConflict[];
|
|
130
195
|
readonly admission: JsTsProjectSafeMergeAdmission;
|
|
131
196
|
readonly summary: {
|
|
@@ -134,6 +199,11 @@ export interface JsTsProjectSafeMergeResult {
|
|
|
134
199
|
readonly blockedFiles: number;
|
|
135
200
|
readonly outputFiles: number;
|
|
136
201
|
readonly projectGraphConflicts: number;
|
|
202
|
+
readonly outputProjectGraphConflicts: number;
|
|
203
|
+
readonly projectGraphDeltaConflicts: number;
|
|
204
|
+
readonly projectGraphPublicContractConflicts: number;
|
|
205
|
+
readonly projectGraphReExportIdentityConflicts: number;
|
|
206
|
+
readonly projectGraphImportTargetConflicts: number;
|
|
137
207
|
readonly semanticArtifactFiles: number;
|
|
138
208
|
readonly operations: Readonly<Record<string, number>>;
|
|
139
209
|
};
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
2
|
+
import { projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
|
|
2
3
|
|
|
3
4
|
function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
4
5
|
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
5
|
-
const
|
|
6
|
+
const missingModuleGroups = new Map();
|
|
7
|
+
const missingSymbolGroups = new Map();
|
|
6
8
|
for (const edge of importEdges) {
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (isMissingProjectImportEdge(edge)) {
|
|
10
|
+
const key = [edge.sourcePath, edge.moduleSpecifier, edge.resolutionKind, edge.resolvedModulePath].join('\u0000');
|
|
11
|
+
const group = missingModuleGroups.get(key) ?? [];
|
|
12
|
+
group.push(edge);
|
|
13
|
+
missingModuleGroups.set(key, group);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (isMissingProjectImportTargetEdge(edge)) {
|
|
17
|
+
const key = [edge.sourcePath, edge.moduleSpecifier, projectImportTargetName(edge), edge.resolvedModulePath].join('\u0000');
|
|
18
|
+
const group = missingSymbolGroups.get(key) ?? [];
|
|
19
|
+
group.push(edge);
|
|
20
|
+
missingSymbolGroups.set(key, group);
|
|
21
|
+
}
|
|
12
22
|
}
|
|
13
|
-
return [
|
|
23
|
+
return [
|
|
24
|
+
...[...missingModuleGroups.values()].map(projectGraphMissingImportConflict),
|
|
25
|
+
...[...missingSymbolGroups.values()].map(projectGraphMissingTargetConflict)
|
|
26
|
+
];
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
function projectGraphMissingImportConflict(group) {
|
|
@@ -34,12 +47,52 @@ function projectGraphMissingImportConflict(group) {
|
|
|
34
47
|
};
|
|
35
48
|
}
|
|
36
49
|
|
|
50
|
+
function projectGraphMissingTargetConflict(group) {
|
|
51
|
+
const edge = group[0] ?? {};
|
|
52
|
+
const targetName = projectImportTargetName(edge);
|
|
53
|
+
return {
|
|
54
|
+
code: 'project-output-symbol-unresolved',
|
|
55
|
+
gateId: 'project-symbol-graph',
|
|
56
|
+
message: `Output project graph import ${JSON.stringify(edge.moduleSpecifier ?? 'unknown')} resolves to a module without exported symbol ${JSON.stringify(targetName ?? 'unknown')}.`,
|
|
57
|
+
sourcePath: edge.sourcePath,
|
|
58
|
+
details: compactRecord({
|
|
59
|
+
reasonCode: 'project-output-symbol-unresolved',
|
|
60
|
+
conflictKey: `project-symbol#${edge.sourcePath ?? 'unknown'}#${edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown'}#${targetName ?? 'unknown'}`,
|
|
61
|
+
sourcePath: edge.sourcePath,
|
|
62
|
+
moduleSpecifier: edge.moduleSpecifier,
|
|
63
|
+
resolutionKind: edge.resolutionKind,
|
|
64
|
+
resolvedModulePath: edge.resolvedModulePath,
|
|
65
|
+
targetDocumentId: edge.targetDocumentId,
|
|
66
|
+
targetExportName: targetName,
|
|
67
|
+
edgeIds: uniqueStrings(group.map((record) => record.id)),
|
|
68
|
+
importKinds: uniqueStrings(group.map((record) => record.importKind)),
|
|
69
|
+
importedNames: uniqueStrings(group.map((record) => record.importedName)),
|
|
70
|
+
localNames: uniqueStrings(group.map((record) => record.localName))
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
37
75
|
function isMissingProjectImportEdge(edge) {
|
|
38
76
|
return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
|
|
39
77
|
}
|
|
40
78
|
|
|
79
|
+
function isMissingProjectImportTargetEdge(edge) {
|
|
80
|
+
return hasResolvedProjectModule(edge) && Boolean(projectImportTargetName(edge)) && !edge.resolvedTargetSymbolId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasResolvedProjectModule(edge) {
|
|
84
|
+
return Boolean(edge?.targetDocumentId) && typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-source');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function projectImportTargetName(edge) {
|
|
88
|
+
if (edge?.importKind === 'side-effect' || edge?.importKind === 'namespace') return undefined;
|
|
89
|
+
const name = edge?.importedName ?? edge?.localName ?? edge?.exportedName;
|
|
90
|
+
if (!name || name === '*') return undefined;
|
|
91
|
+
return String(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
41
94
|
function uniqueStrings(values) {
|
|
42
95
|
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
43
96
|
}
|
|
44
97
|
|
|
45
|
-
export { outputProjectGraphConflicts };
|
|
98
|
+
export { outputProjectGraphConflicts, projectGraphDeltaConflicts };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
3
|
+
|
|
4
|
+
function projectGraphDeltaConflicts(projectGraphDelta) {
|
|
5
|
+
const baseGraph = projectGraphDelta?.stages?.base?.projectSymbolGraph;
|
|
6
|
+
const workerGraph = projectGraphDelta?.stages?.worker?.projectSymbolGraph;
|
|
7
|
+
const headGraph = projectGraphDelta?.stages?.head?.projectSymbolGraph;
|
|
8
|
+
const outputGraph = projectGraphDelta?.stages?.output?.projectSymbolGraph;
|
|
9
|
+
if (!baseGraph || !workerGraph || !headGraph) return [];
|
|
10
|
+
return [
|
|
11
|
+
...changedIdentityConflicts({
|
|
12
|
+
code: 'project-public-contract-delta-conflict',
|
|
13
|
+
label: 'public contract',
|
|
14
|
+
baseRecords: baseGraph.publicContractRegions,
|
|
15
|
+
workerRecords: workerGraph.publicContractRegions,
|
|
16
|
+
headRecords: headGraph.publicContractRegions,
|
|
17
|
+
outputRecords: outputGraph?.publicContractRegions,
|
|
18
|
+
identityKey: publicContractIdentityKey,
|
|
19
|
+
fingerprint: publicContractFingerprint,
|
|
20
|
+
details: publicContractDetails
|
|
21
|
+
}),
|
|
22
|
+
...changedIdentityConflicts({
|
|
23
|
+
code: 'project-re-export-identity-delta-conflict',
|
|
24
|
+
label: 're-export identity',
|
|
25
|
+
baseRecords: baseGraph.reExportIdentities,
|
|
26
|
+
workerRecords: workerGraph.reExportIdentities,
|
|
27
|
+
headRecords: headGraph.reExportIdentities,
|
|
28
|
+
outputRecords: outputGraph?.reExportIdentities,
|
|
29
|
+
identityKey: reExportIdentityKey,
|
|
30
|
+
fingerprint: reExportIdentityFingerprint,
|
|
31
|
+
details: reExportIdentityDetails
|
|
32
|
+
}),
|
|
33
|
+
...projectImportTargetDeltaConflicts(projectGraphDelta)
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function addProjectGraphDeltaConflictSummary(projectGraphDelta, conflicts) {
|
|
38
|
+
if (!projectGraphDelta) return undefined;
|
|
39
|
+
return {
|
|
40
|
+
...projectGraphDelta,
|
|
41
|
+
summary: {
|
|
42
|
+
...projectGraphDelta.summary,
|
|
43
|
+
conflicts: conflicts.length,
|
|
44
|
+
publicContractConflicts: conflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
45
|
+
reExportIdentityConflicts: conflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
46
|
+
importTargetConflicts: conflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function changedIdentityConflicts(input) {
|
|
52
|
+
const base = recordsByIdentityKey(input.baseRecords, input.identityKey);
|
|
53
|
+
const worker = recordsByIdentityKey(input.workerRecords, input.identityKey);
|
|
54
|
+
const head = recordsByIdentityKey(input.headRecords, input.identityKey);
|
|
55
|
+
const output = recordsByIdentityKey(input.outputRecords, input.identityKey);
|
|
56
|
+
const keys = uniqueStrings([...base.keys(), ...worker.keys(), ...head.keys()]);
|
|
57
|
+
return keys.flatMap((identityKey) => {
|
|
58
|
+
const baseRecord = base.get(identityKey);
|
|
59
|
+
const workerRecord = worker.get(identityKey);
|
|
60
|
+
const headRecord = head.get(identityKey);
|
|
61
|
+
const baseFingerprint = optionalFingerprint(baseRecord, input.fingerprint);
|
|
62
|
+
const workerFingerprint = optionalFingerprint(workerRecord, input.fingerprint);
|
|
63
|
+
const headFingerprint = optionalFingerprint(headRecord, input.fingerprint);
|
|
64
|
+
if (baseFingerprint === workerFingerprint || baseFingerprint === headFingerprint || workerFingerprint === headFingerprint) return [];
|
|
65
|
+
return [projectGraphDeltaConflict({
|
|
66
|
+
code: input.code,
|
|
67
|
+
label: input.label,
|
|
68
|
+
identityKey,
|
|
69
|
+
baseRecord,
|
|
70
|
+
workerRecord,
|
|
71
|
+
headRecord,
|
|
72
|
+
outputRecord: output.get(identityKey),
|
|
73
|
+
details: input.details
|
|
74
|
+
})];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function projectImportTargetDeltaConflicts(projectGraphDelta) {
|
|
79
|
+
const baseStage = projectGraphDelta?.stages?.base;
|
|
80
|
+
const workerStage = projectGraphDelta?.stages?.worker;
|
|
81
|
+
const headStage = projectGraphDelta?.stages?.head;
|
|
82
|
+
const outputStage = projectGraphDelta?.stages?.output;
|
|
83
|
+
const workerEdges = importEdgesByIdentityKey(workerStage?.projectSymbolGraph?.importEdges);
|
|
84
|
+
const baseSymbolIds = semanticSymbolIds(baseStage);
|
|
85
|
+
const headSymbolIds = semanticSymbolIds(headStage);
|
|
86
|
+
const conflicts = [];
|
|
87
|
+
for (const [identityKey, edge] of importEdgesByIdentityKey(outputStage?.projectSymbolGraph?.importEdges)) {
|
|
88
|
+
if (!edge.resolvedTargetSymbolId) continue;
|
|
89
|
+
const workerEdge = workerEdges.get(identityKey);
|
|
90
|
+
if (!workerEdge || workerEdge.resolvedTargetSymbolId === edge.resolvedTargetSymbolId) continue;
|
|
91
|
+
if (baseSymbolIds.has(edge.resolvedTargetSymbolId) || !headSymbolIds.has(edge.resolvedTargetSymbolId)) continue;
|
|
92
|
+
conflicts.push(projectImportTargetDeltaConflict(identityKey, edge, workerEdge));
|
|
93
|
+
}
|
|
94
|
+
return conflicts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function projectGraphDeltaConflict(input) {
|
|
98
|
+
const sourcePath = input.workerRecord?.sourcePath ?? input.headRecord?.sourcePath ?? input.baseRecord?.sourcePath;
|
|
99
|
+
const conflictKey = `project-graph-delta#${input.label.replace(/\s+/g, '-')}#${input.identityKey}`;
|
|
100
|
+
return {
|
|
101
|
+
code: input.code,
|
|
102
|
+
gateId: 'project-graph-delta',
|
|
103
|
+
message: `Worker and head both changed ${input.label} ${JSON.stringify(input.identityKey)} in incompatible ways.`,
|
|
104
|
+
sourcePath,
|
|
105
|
+
details: compactRecord({
|
|
106
|
+
reasonCode: input.code,
|
|
107
|
+
conflictKey,
|
|
108
|
+
identityKey: input.identityKey,
|
|
109
|
+
sourcePath,
|
|
110
|
+
base: input.details(input.baseRecord),
|
|
111
|
+
worker: input.details(input.workerRecord),
|
|
112
|
+
head: input.details(input.headRecord),
|
|
113
|
+
output: input.details(input.outputRecord)
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function projectImportTargetDeltaConflict(identityKey, edge, workerEdge) {
|
|
119
|
+
return {
|
|
120
|
+
code: 'project-import-target-delta-conflict',
|
|
121
|
+
gateId: 'project-graph-delta',
|
|
122
|
+
message: `Output import ${JSON.stringify(projectImportTargetName(edge) ?? edge.moduleSpecifier ?? 'unknown')} resolves to a head-branch exported symbol that the worker import did not resolve against.`,
|
|
123
|
+
sourcePath: edge.sourcePath,
|
|
124
|
+
details: compactRecord({
|
|
125
|
+
reasonCode: 'project-import-target-delta-conflict',
|
|
126
|
+
conflictKey: `project-graph-delta#import-target#${identityKey}`,
|
|
127
|
+
identityKey,
|
|
128
|
+
sourcePath: edge.sourcePath,
|
|
129
|
+
moduleSpecifier: edge.moduleSpecifier,
|
|
130
|
+
importedName: edge.importedName,
|
|
131
|
+
localName: edge.localName,
|
|
132
|
+
importKind: edge.importKind,
|
|
133
|
+
resolvedModulePath: edge.resolvedModulePath,
|
|
134
|
+
outputTargetSymbolId: edge.resolvedTargetSymbolId,
|
|
135
|
+
workerTargetSymbolId: workerEdge.resolvedTargetSymbolId,
|
|
136
|
+
workerResolutionKind: workerEdge.resolutionKind,
|
|
137
|
+
outputResolutionKind: edge.resolutionKind
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function recordsByIdentityKey(records, identityKey) {
|
|
143
|
+
const result = new Map();
|
|
144
|
+
for (const record of records ?? []) {
|
|
145
|
+
const key = identityKey(record);
|
|
146
|
+
if (!key || result.has(key)) continue;
|
|
147
|
+
result.set(key, record);
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function publicContractIdentityKey(record) {
|
|
153
|
+
return firstString(
|
|
154
|
+
record?.key,
|
|
155
|
+
stableKey(['public-contract', record?.sourcePath, record?.apiSurfaceKind, record?.edgeKind, record?.moduleSpecifier, record?.exportedName ?? record?.symbolName ?? record?.symbolId])
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function publicContractFingerprint(record) {
|
|
160
|
+
return hashSemanticValue({
|
|
161
|
+
kind: 'frontier.lang.projectGraphDelta.publicContractFingerprint',
|
|
162
|
+
contractHash: record.contractHash,
|
|
163
|
+
signatureHash: record.signatureHash,
|
|
164
|
+
symbolName: record.symbolName,
|
|
165
|
+
symbolKind: record.symbolKind,
|
|
166
|
+
apiSurfaceKind: record.apiSurfaceKind,
|
|
167
|
+
exportedName: record.exportedName,
|
|
168
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
169
|
+
edgeKind: record.edgeKind
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function publicContractDetails(record) {
|
|
174
|
+
if (!record) return undefined;
|
|
175
|
+
return compactRecord({
|
|
176
|
+
sourcePath: record.sourcePath,
|
|
177
|
+
key: publicContractIdentityKey(record),
|
|
178
|
+
symbolName: record.symbolName,
|
|
179
|
+
symbolKind: record.symbolKind,
|
|
180
|
+
apiSurfaceKind: record.apiSurfaceKind,
|
|
181
|
+
exportedName: record.exportedName,
|
|
182
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
183
|
+
edgeKind: record.edgeKind,
|
|
184
|
+
signatureHash: record.signatureHash,
|
|
185
|
+
contractHash: record.contractHash,
|
|
186
|
+
sourceHash: record.sourceHash
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function reExportIdentityKey(record) {
|
|
191
|
+
return stableKey(['re-export-identity', record?.sourcePath, record?.exportedName ?? record?.localName ?? record?.namespace ?? (record?.exportStar || record?.isExportStar ? '*' : undefined)]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function reExportIdentityFingerprint(record) {
|
|
195
|
+
return hashSemanticValue({
|
|
196
|
+
kind: 'frontier.lang.projectGraphDelta.reExportIdentityFingerprint',
|
|
197
|
+
sourcePath: record.sourcePath,
|
|
198
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
199
|
+
exportedName: record.exportedName,
|
|
200
|
+
importedName: record.importedName,
|
|
201
|
+
localName: record.localName,
|
|
202
|
+
namespace: record.namespace,
|
|
203
|
+
isTypeOnly: record.isTypeOnly,
|
|
204
|
+
exportStar: record.exportStar,
|
|
205
|
+
isExportStar: record.isExportStar,
|
|
206
|
+
originSymbolId: record.originSymbolId,
|
|
207
|
+
exportedSymbolId: record.exportedSymbolId,
|
|
208
|
+
localSymbolId: record.localSymbolId
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function reExportIdentityDetails(record) {
|
|
213
|
+
if (!record) return undefined;
|
|
214
|
+
return compactRecord({
|
|
215
|
+
sourcePath: record.sourcePath,
|
|
216
|
+
key: reExportIdentityKey(record),
|
|
217
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
218
|
+
exportedName: record.exportedName,
|
|
219
|
+
importedName: record.importedName,
|
|
220
|
+
localName: record.localName,
|
|
221
|
+
namespace: record.namespace,
|
|
222
|
+
isTypeOnly: record.isTypeOnly,
|
|
223
|
+
exportStar: record.exportStar,
|
|
224
|
+
isExportStar: record.isExportStar,
|
|
225
|
+
originSymbolId: record.originSymbolId,
|
|
226
|
+
exportedSymbolId: record.exportedSymbolId,
|
|
227
|
+
localSymbolId: record.localSymbolId,
|
|
228
|
+
sourceHash: record.sourceHash
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function importEdgesByIdentityKey(records) {
|
|
233
|
+
const result = new Map();
|
|
234
|
+
for (const record of records ?? []) {
|
|
235
|
+
const key = importEdgeIdentityKey(record);
|
|
236
|
+
if (!key || result.has(key)) continue;
|
|
237
|
+
result.set(key, record);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function importEdgeIdentityKey(edge) {
|
|
243
|
+
const targetName = projectImportTargetName(edge);
|
|
244
|
+
if (!edge?.sourcePath || !edge.moduleSpecifier || !targetName) return undefined;
|
|
245
|
+
return stableKey(['import-target', edge.sourcePath, edge.moduleSpecifier, targetName, edge.importKind, edge.isTypeOnly]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function projectImportTargetName(edge) {
|
|
249
|
+
if (edge?.importKind === 'side-effect' || edge?.importKind === 'namespace') return undefined;
|
|
250
|
+
const name = edge?.importedName ?? edge?.localName ?? edge?.exportedName;
|
|
251
|
+
if (!name || name === '*') return undefined;
|
|
252
|
+
return String(name);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function optionalFingerprint(record, fingerprint) {
|
|
256
|
+
return record ? fingerprint(record) : undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function semanticSymbolIds(stage) {
|
|
260
|
+
return new Set((stage?.projectImport?.semanticIndex?.symbols ?? []).map((symbol) => symbol.id).filter(Boolean));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function stableKey(parts) {
|
|
264
|
+
const values = parts.map((part) => part === undefined || part === null ? '' : String(part));
|
|
265
|
+
return values.some(Boolean) ? values.join('#') : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function firstString(...values) {
|
|
269
|
+
for (const value of values) {
|
|
270
|
+
if (value !== undefined && value !== null && String(value)) return String(value);
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function uniqueStrings(values) {
|
|
276
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export { addProjectGraphDeltaConflictSummary, projectGraphDeltaConflicts };
|
|
@@ -1,47 +1,90 @@
|
|
|
1
1
|
import { idFragment } from './native-import-utils.js';
|
|
2
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
3
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
2
4
|
import { createNativeProjectImportResult } from './internal/index-impl/createNativeProjectImportResult.js';
|
|
3
5
|
import { importNativeSource } from './internal/index-impl/importNativeSource.js';
|
|
4
6
|
|
|
5
7
|
function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
return createProjectGraphStageArtifacts(input, outputFiles, mergeId, 'output', projectImportsForStage(input, 'output'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createJsTsProjectSafeMergeGraphDelta(input, files, outputFiles, mergeId) {
|
|
12
|
+
const stageFiles = projectGraphDeltaStageFiles(files, outputFiles, input);
|
|
13
|
+
const stages = {};
|
|
14
|
+
for (const stageName of ['base', 'worker', 'head', 'output']) {
|
|
15
|
+
const files = stageFiles?.[stageName];
|
|
16
|
+
if (!Array.isArray(files)) continue;
|
|
17
|
+
stages[stageName] = createProjectGraphStageArtifacts(input, files, mergeId, stageName, projectImportsForStage(input, stageName));
|
|
18
|
+
}
|
|
19
|
+
const stageSummaries = Object.fromEntries(Object.entries(stages).map(([stageName, artifacts]) => [stageName, artifacts.summary]));
|
|
20
|
+
return {
|
|
21
|
+
kind: 'frontier.lang.jsTsProjectGraphDelta',
|
|
22
|
+
version: 1,
|
|
23
|
+
stages,
|
|
24
|
+
summary: {
|
|
25
|
+
stages: Object.keys(stages).length,
|
|
26
|
+
sourceFiles: sumStageSummary(stageSummaries, 'sourceFiles'),
|
|
27
|
+
publicContractRegions: sumStageSummary(stageSummaries, 'publicContractRegions'),
|
|
28
|
+
reExportIdentities: sumStageSummary(stageSummaries, 'reExportIdentities'),
|
|
29
|
+
importEdges: sumStageSummary(stageSummaries, 'importEdges'),
|
|
30
|
+
exportEdges: sumStageSummary(stageSummaries, 'exportEdges'),
|
|
31
|
+
unresolvedImportEdges: sumStageSummary(stageSummaries, 'unresolvedImportEdges'),
|
|
32
|
+
suppliedImports: sumStageSummary(stageSummaries, 'suppliedImports'),
|
|
33
|
+
matchedSuppliedImports: sumStageSummary(stageSummaries, 'matchedSuppliedImports'),
|
|
34
|
+
scannerFallbackImports: sumStageSummary(stageSummaries, 'scannerFallbackImports'),
|
|
35
|
+
stageSummaries
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stageImports) {
|
|
41
|
+
const sources = files.map((file) => ({
|
|
42
|
+
id: `${mergeId}_${stageName}_${idFragment(file.sourcePath)}`,
|
|
8
43
|
language: file.language ?? input.language ?? languageForPath(file.sourcePath),
|
|
9
44
|
sourcePath: file.sourcePath,
|
|
10
45
|
sourceText: file.sourceText,
|
|
11
46
|
sourceHash: file.sourceHash,
|
|
12
|
-
metadata: { semanticImportExpected: true, projectSafeMergeOutput:
|
|
47
|
+
metadata: { semanticImportExpected: true, projectSafeMergeStage: stageName, projectSafeMergeOutput: stageName === 'output' }
|
|
13
48
|
}));
|
|
14
|
-
const suppliedImports =
|
|
49
|
+
const suppliedImports = normalizeProjectImports(stageImports);
|
|
15
50
|
const importSelections = sources.map((source) => {
|
|
16
|
-
const suppliedImport =
|
|
51
|
+
const suppliedImport = matchingProjectImport(source, suppliedImports);
|
|
17
52
|
return {
|
|
18
53
|
importResult: suppliedImport ?? importNativeSource(source),
|
|
19
|
-
sourceKind: suppliedImport ?
|
|
54
|
+
sourceKind: suppliedImport ? `supplied-${stageName}-project-import` : `lightweight-${stageName}-project-scan`
|
|
20
55
|
};
|
|
21
56
|
});
|
|
22
57
|
const imports = importSelections.map((selection) => selection.importResult);
|
|
58
|
+
const projectGraphImportSource = {
|
|
59
|
+
stage: stageName,
|
|
60
|
+
suppliedImports: suppliedImports.length,
|
|
61
|
+
matchedSuppliedImports: importSelections.filter((selection) => selection.sourceKind === `supplied-${stageName}-project-import`).length,
|
|
62
|
+
scannerFallbackImports: importSelections.filter((selection) => selection.sourceKind === `lightweight-${stageName}-project-scan`).length
|
|
63
|
+
};
|
|
23
64
|
const projectImport = createNativeProjectImportResult({
|
|
24
|
-
id: `${mergeId}
|
|
65
|
+
id: `${mergeId}_${stageName}_project_import`,
|
|
25
66
|
projectRoot: input.projectRoot,
|
|
26
67
|
moduleResolution: input.moduleResolution ?? input.tsconfig,
|
|
27
68
|
sources,
|
|
28
69
|
metadata: {
|
|
29
70
|
projectSafeMergeId: mergeId,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
scannerFallbackImports: importSelections.filter((selection) => selection.sourceKind === 'lightweight-output-project-scan').length
|
|
35
|
-
}
|
|
71
|
+
projectGraphStage: stageName,
|
|
72
|
+
outputProjectSymbolGraph: stageName === 'output',
|
|
73
|
+
projectGraphImportSource,
|
|
74
|
+
...(stageName === 'output' ? { outputProjectImportSource: projectGraphImportSource } : {})
|
|
36
75
|
}
|
|
37
76
|
}, imports);
|
|
38
77
|
return {
|
|
78
|
+
kind: 'frontier.lang.jsTsProjectGraphStage',
|
|
79
|
+
version: 1,
|
|
80
|
+
stage: stageName,
|
|
39
81
|
projectImport,
|
|
40
|
-
projectSymbolGraph: projectImport.projectSymbolGraph
|
|
82
|
+
projectSymbolGraph: projectImport.projectSymbolGraph,
|
|
83
|
+
summary: projectGraphStageSummary(stageName, projectImport.projectSymbolGraph, projectGraphImportSource)
|
|
41
84
|
};
|
|
42
85
|
}
|
|
43
86
|
|
|
44
|
-
function
|
|
87
|
+
function normalizeProjectImports(value) {
|
|
45
88
|
if (!value) return [];
|
|
46
89
|
if (Array.isArray(value)) return value.filter(Boolean);
|
|
47
90
|
if (value instanceof Map) return [...value.values()].filter(Boolean);
|
|
@@ -49,18 +92,93 @@ function normalizeOutputProjectImports(value) {
|
|
|
49
92
|
return [];
|
|
50
93
|
}
|
|
51
94
|
|
|
52
|
-
function
|
|
95
|
+
function projectImportsForStage(input, stageName) {
|
|
96
|
+
if (stageName === 'output') return input.outputProjectImports ?? input.projectGraphImports?.output;
|
|
97
|
+
if (stageName === 'base') return input.baseProjectImports ?? input.projectGraphImports?.base;
|
|
98
|
+
if (stageName === 'worker') return input.workerProjectImports ?? input.projectGraphImports?.worker;
|
|
99
|
+
if (stageName === 'head') return input.headProjectImports ?? input.projectGraphImports?.head;
|
|
100
|
+
return input.projectGraphImports?.[stageName];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function matchingProjectImport(source, imports) {
|
|
53
104
|
const sourcePath = String(source.sourcePath ?? '');
|
|
54
105
|
const sourceHash = String(source.sourceHash ?? '');
|
|
55
106
|
return imports.find((importResult) => {
|
|
56
107
|
const importSourcePath = sourcePathForImport(importResult);
|
|
57
108
|
if (importSourcePath && sourcePath && importSourcePath !== sourcePath) return false;
|
|
58
109
|
const importSourceHash = sourceHashForImport(importResult);
|
|
110
|
+
if (sourceHash && importSourceHash !== sourceHash) return false;
|
|
59
111
|
if (importSourceHash && sourceHash && importSourceHash !== sourceHash) return false;
|
|
60
112
|
return importSourcePath === sourcePath || importSourceHash === sourceHash;
|
|
61
113
|
});
|
|
62
114
|
}
|
|
63
115
|
|
|
116
|
+
function projectGraphStageSummary(stageName, projectSymbolGraph, importSource) {
|
|
117
|
+
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
118
|
+
return {
|
|
119
|
+
stage: stageName,
|
|
120
|
+
sourceFiles: projectSymbolGraph?.sourceCount ?? 0,
|
|
121
|
+
documents: projectSymbolGraph?.documentCount ?? 0,
|
|
122
|
+
symbols: projectSymbolGraph?.symbolCount ?? 0,
|
|
123
|
+
fileHashes: projectSymbolGraph?.fileHashes?.length ?? 0,
|
|
124
|
+
importEdges: importEdges.length,
|
|
125
|
+
exportEdges: projectSymbolGraph?.exportEdges?.length ?? 0,
|
|
126
|
+
publicContractRegions: projectSymbolGraph?.publicContractRegions?.length ?? 0,
|
|
127
|
+
reExportIdentities: projectSymbolGraph?.reExportIdentities?.length ?? 0,
|
|
128
|
+
unresolvedImportEdges: importEdges.filter(isMissingProjectImportEdge).length,
|
|
129
|
+
suppliedImports: importSource.suppliedImports,
|
|
130
|
+
matchedSuppliedImports: importSource.matchedSuppliedImports,
|
|
131
|
+
scannerFallbackImports: importSource.scannerFallbackImports
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isMissingProjectImportEdge(edge) {
|
|
136
|
+
return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sumStageSummary(stageSummaries, field) {
|
|
140
|
+
return Object.values(stageSummaries).reduce((total, summary) => total + Number(summary?.[field] ?? 0), 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function projectGraphDeltaStageFiles(files, outputFiles, input) {
|
|
144
|
+
return {
|
|
145
|
+
base: files
|
|
146
|
+
.map((file) => stageFile(file, file.baseSourceText, input))
|
|
147
|
+
.filter(Boolean),
|
|
148
|
+
worker: files
|
|
149
|
+
.map((file) => stageFile(file, workerStageSourceText(file), input))
|
|
150
|
+
.filter(Boolean),
|
|
151
|
+
head: files
|
|
152
|
+
.map((file) => stageFile(file, headStageSourceText(file), input))
|
|
153
|
+
.filter(Boolean),
|
|
154
|
+
output: outputFiles
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function workerStageSourceText(file) {
|
|
159
|
+
if (file.workerDeleted) return undefined;
|
|
160
|
+
return file.workerSourceText ?? file.baseSourceText;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function headStageSourceText(file) {
|
|
164
|
+
if (file.headDeleted) return undefined;
|
|
165
|
+
return file.headSourceText ?? file.baseSourceText;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function stageFile(file, sourceText, input) {
|
|
169
|
+
if (typeof sourceText !== 'string' || !file.sourcePath) return undefined;
|
|
170
|
+
return compactRecord({
|
|
171
|
+
sourcePath: file.sourcePath,
|
|
172
|
+
language: file.language ?? input.language,
|
|
173
|
+
sourceText,
|
|
174
|
+
sourceHash: hashText(sourceText)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hashText(sourceText) {
|
|
179
|
+
return hashSemanticValue(sourceText);
|
|
180
|
+
}
|
|
181
|
+
|
|
64
182
|
function sourcePathForImport(importResult) {
|
|
65
183
|
return firstString(
|
|
66
184
|
importResult?.sourcePath,
|
|
@@ -92,4 +210,4 @@ function firstString(...values) {
|
|
|
92
210
|
return undefined;
|
|
93
211
|
}
|
|
94
212
|
|
|
95
|
-
export { createJsTsProjectSafeMergeGraphArtifacts };
|
|
213
|
+
export { createJsTsProjectSafeMergeGraphArtifacts, createJsTsProjectSafeMergeGraphDelta };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import { safeMergeJsTsSource } from './js-ts-safe-merge-composed.js';
|
|
3
3
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
4
|
-
import { createJsTsProjectSafeMergeGraphArtifacts } from './js-ts-safe-project-merge-graph.js';
|
|
5
|
-
import {
|
|
4
|
+
import { createJsTsProjectSafeMergeGraphArtifacts, createJsTsProjectSafeMergeGraphDelta } from './js-ts-safe-project-merge-graph.js';
|
|
5
|
+
import { addProjectGraphDeltaConflictSummary } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
|
|
6
|
+
import { outputProjectGraphConflicts, projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-conflicts.js';
|
|
6
7
|
|
|
7
8
|
function safeMergeJsTsProject(input = {}) {
|
|
8
9
|
const id = String(input.id ?? 'js_ts_project_safe_merge');
|
|
@@ -18,10 +19,16 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
18
19
|
sourceHash: file.outputHash,
|
|
19
20
|
operation: file.operation
|
|
20
21
|
}));
|
|
21
|
-
const
|
|
22
|
-
?
|
|
22
|
+
const projectGraphDelta = blockedFiles.length === 0 && input.includeProjectGraphDelta
|
|
23
|
+
? createJsTsProjectSafeMergeGraphDelta(input, files, outputFiles, id)
|
|
23
24
|
: undefined;
|
|
24
|
-
const
|
|
25
|
+
const graphArtifacts = projectGraphDelta?.stages?.output ?? (blockedFiles.length === 0 && input.includeOutputProjectSymbolGraph
|
|
26
|
+
? createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, id)
|
|
27
|
+
: undefined);
|
|
28
|
+
const outputGraphConflicts = outputProjectGraphConflicts(graphArtifacts?.projectSymbolGraph);
|
|
29
|
+
const deltaGraphConflicts = projectGraphDeltaConflicts(projectGraphDelta);
|
|
30
|
+
const projectGraphDeltaWithConflicts = addProjectGraphDeltaConflictSummary(projectGraphDelta, deltaGraphConflicts);
|
|
31
|
+
const graphConflicts = [...outputGraphConflicts, ...deltaGraphConflicts];
|
|
25
32
|
const status = blockedFiles.length || graphConflicts.length ? 'blocked' : 'merged';
|
|
26
33
|
const reasonCodes = uniqueStrings([
|
|
27
34
|
...blockedFiles.flatMap((file) => file.admission.reasonCodes),
|
|
@@ -41,6 +48,7 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
41
48
|
outputFiles,
|
|
42
49
|
outputProjectImport: graphArtifacts?.projectImport,
|
|
43
50
|
outputProjectSymbolGraph: graphArtifacts?.projectSymbolGraph,
|
|
51
|
+
projectGraphDelta: projectGraphDeltaWithConflicts,
|
|
44
52
|
conflicts: [...fileResults.flatMap((file) => file.conflicts), ...graphConflicts],
|
|
45
53
|
admission: {
|
|
46
54
|
status: status === 'merged' ? 'auto-merge-candidate' : 'blocked',
|
|
@@ -59,7 +67,10 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
59
67
|
projectRoot: input.projectRoot,
|
|
60
68
|
filesInput: Array.isArray(input.files) ? 'records' : 'maps',
|
|
61
69
|
outputProjectSymbolGraph: Boolean(graphArtifacts?.projectSymbolGraph),
|
|
70
|
+
projectGraphDelta: Boolean(projectGraphDeltaWithConflicts),
|
|
62
71
|
projectGraphConflicts: graphConflicts.length || undefined,
|
|
72
|
+
outputProjectGraphConflicts: outputGraphConflicts.length || undefined,
|
|
73
|
+
projectGraphDeltaConflicts: deltaGraphConflicts.length || undefined,
|
|
63
74
|
autoMergeClaim: false,
|
|
64
75
|
semanticEquivalenceClaim: false
|
|
65
76
|
})
|
|
@@ -240,20 +251,24 @@ function policyForFile(input, sourcePath) {
|
|
|
240
251
|
|
|
241
252
|
function sourceLedgersForFile(input, sourcePath) {
|
|
242
253
|
const byPath = input.sourceLedgersByPath?.[sourcePath] ?? input.sourceLedgers?.[sourcePath];
|
|
243
|
-
|
|
244
|
-
if (input.sourceLedgers?.base || input.sourceLedgers?.worker || input.sourceLedgers?.head) return input.sourceLedgers;
|
|
245
|
-
return undefined;
|
|
254
|
+
return byPath ?? (input.sourceLedgers?.base || input.sourceLedgers?.worker || input.sourceLedgers?.head ? input.sourceLedgers : undefined);
|
|
246
255
|
}
|
|
247
256
|
|
|
248
257
|
function projectSummary(files, graphConflicts = []) {
|
|
249
258
|
const byOperation = {};
|
|
250
259
|
for (const file of files) byOperation[file.operation] = (byOperation[file.operation] ?? 0) + 1;
|
|
260
|
+
const deltaConflicts = graphConflicts.filter((conflict) => conflict.gateId === 'project-graph-delta');
|
|
251
261
|
return {
|
|
252
262
|
files: files.length,
|
|
253
263
|
mergedFiles: files.filter((file) => file.status === 'merged').length,
|
|
254
264
|
blockedFiles: files.filter((file) => file.status === 'blocked').length,
|
|
255
265
|
outputFiles: files.filter((file) => typeof file.outputSourceText === 'string').length,
|
|
256
266
|
projectGraphConflicts: graphConflicts.length,
|
|
267
|
+
outputProjectGraphConflicts: graphConflicts.length - deltaConflicts.length,
|
|
268
|
+
projectGraphDeltaConflicts: deltaConflicts.length,
|
|
269
|
+
projectGraphPublicContractConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
270
|
+
projectGraphReExportIdentityConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
271
|
+
projectGraphImportTargetConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length,
|
|
257
272
|
semanticArtifactFiles: files.filter((file) => file.semanticArtifacts).length,
|
|
258
273
|
operations: byOperation
|
|
259
274
|
};
|
|
@@ -283,21 +298,15 @@ function blockedAdmission(reasonCode) {
|
|
|
283
298
|
};
|
|
284
299
|
}
|
|
285
300
|
|
|
286
|
-
function hashText(text) {
|
|
287
|
-
return typeof text === 'string' ? hashSemanticValue(text) : undefined;
|
|
288
|
-
}
|
|
301
|
+
function hashText(text) { return typeof text === 'string' ? hashSemanticValue(text) : undefined; }
|
|
289
302
|
|
|
290
|
-
function stringOrUndefined(value) {
|
|
291
|
-
return typeof value === 'string' ? value : undefined;
|
|
292
|
-
}
|
|
303
|
+
function stringOrUndefined(value) { return typeof value === 'string' ? value : undefined; }
|
|
293
304
|
|
|
294
305
|
function safeId(value) {
|
|
295
306
|
return String(value ?? 'unknown').replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'file';
|
|
296
307
|
}
|
|
297
308
|
|
|
298
|
-
function bySourcePath(left, right) {
|
|
299
|
-
return String(left.sourcePath ?? '').localeCompare(String(right.sourcePath ?? ''));
|
|
300
|
-
}
|
|
309
|
+
function bySourcePath(left, right) { return String(left.sourcePath ?? '').localeCompare(String(right.sourcePath ?? '')); }
|
|
301
310
|
|
|
302
311
|
function uniqueStrings(values) {
|
|
303
312
|
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
package/package.json
CHANGED