@postxl/generator 1.6.3 → 1.7.0
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/dist/utils/custom-blocks.d.ts +32 -0
- package/dist/utils/custom-blocks.js +128 -28
- package/dist/utils/diff3.d.ts +20 -0
- package/dist/utils/diff3.js +258 -0
- package/dist/utils/merge-conflict.d.ts +27 -1
- package/dist/utils/merge-conflict.js +130 -1
- package/dist/utils/sync.d.ts +11 -1
- package/dist/utils/sync.js +18 -6
- package/package.json +2 -2
|
@@ -93,6 +93,18 @@ export type CustomBlockError = {
|
|
|
93
93
|
* @returns ExtractResult containing blocks, non-custom lines, and any errors
|
|
94
94
|
*/
|
|
95
95
|
export declare function extractCustomBlocks(content: string): ExtractResult;
|
|
96
|
+
/**
|
|
97
|
+
* Finds the line range of a class member (method/property with a body) in the
|
|
98
|
+
* given lines, identified by its name. Returns the half-open range
|
|
99
|
+
* `[start, end)` covering the member's signature through its closing brace, or
|
|
100
|
+
* `null` if the member is not found or has no brace-delimited body.
|
|
101
|
+
*
|
|
102
|
+
* Exported for reuse by the three-way merge's `@custom-override` handling.
|
|
103
|
+
*/
|
|
104
|
+
export declare function findMemberRange(targetLines: string[], memberName: string): {
|
|
105
|
+
start: number;
|
|
106
|
+
end: number;
|
|
107
|
+
} | null;
|
|
96
108
|
/**
|
|
97
109
|
* Finds the best position to insert a custom block in the target content
|
|
98
110
|
* based on anchor context matching.
|
|
@@ -117,6 +129,26 @@ export declare function insertCustomBlocks(blocks: CustomBlock[], targetContent:
|
|
|
117
129
|
* Checks if a string contains any custom block markers
|
|
118
130
|
*/
|
|
119
131
|
export declare function hasCustomBlockMarkers(content: string): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Parses a `@custom-override:<member>` marker line **without a regular
|
|
134
|
+
* expression** (plain string scanning — avoids a flagged slow-regex hotspot and
|
|
135
|
+
* is easy to follow). Accepts both the line-comment form (`// @custom-override:member`)
|
|
136
|
+
* and the single-line block-comment form (slash-star ... star-slash), each with
|
|
137
|
+
* an optional leading `@`.
|
|
138
|
+
*
|
|
139
|
+
* Returns the member name, or `undefined` when the line is not an override marker.
|
|
140
|
+
*/
|
|
141
|
+
export declare function parseOverrideMarkerMemberName(line: string): string | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Extracts the member names declared by `// @custom-override:<member>` markers,
|
|
144
|
+
* in document order and de-duplicated. Used by the three-way merge to keep the
|
|
145
|
+
* developer's version of the named generated members.
|
|
146
|
+
*/
|
|
147
|
+
export declare function extractOverrideMemberNames(content: string): string[];
|
|
148
|
+
/**
|
|
149
|
+
* Checks if a string contains any `@custom-override` markers.
|
|
150
|
+
*/
|
|
151
|
+
export declare function hasCustomOverrideMarkers(content: string): boolean;
|
|
120
152
|
/**
|
|
121
153
|
* Reconstructs content from non-custom lines, used when comparing
|
|
122
154
|
* the "real" content differences (excluding custom blocks)
|
|
@@ -37,9 +37,13 @@
|
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.customBlockMarkers = void 0;
|
|
39
39
|
exports.extractCustomBlocks = extractCustomBlocks;
|
|
40
|
+
exports.findMemberRange = findMemberRange;
|
|
40
41
|
exports.findInsertionPosition = findInsertionPosition;
|
|
41
42
|
exports.insertCustomBlocks = insertCustomBlocks;
|
|
42
43
|
exports.hasCustomBlockMarkers = hasCustomBlockMarkers;
|
|
44
|
+
exports.parseOverrideMarkerMemberName = parseOverrideMarkerMemberName;
|
|
45
|
+
exports.extractOverrideMemberNames = extractOverrideMemberNames;
|
|
46
|
+
exports.hasCustomOverrideMarkers = hasCustomOverrideMarkers;
|
|
43
47
|
exports.reconstructNonCustomContent = reconstructNonCustomContent;
|
|
44
48
|
/**
|
|
45
49
|
* Marker patterns for custom blocks
|
|
@@ -340,41 +344,65 @@ function countChar(line, char) {
|
|
|
340
344
|
}
|
|
341
345
|
return count;
|
|
342
346
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
347
|
+
/**
|
|
348
|
+
* True if the member signature at `start` opens a `{` body before any `;`. A
|
|
349
|
+
* `;` reached first means a bodyless declaration (overload/abstract), which has
|
|
350
|
+
* no range.
|
|
351
|
+
*/
|
|
352
|
+
function memberHasBody(targetLines, start) {
|
|
353
|
+
if (targetLines[start]?.includes('{')) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
for (let j = start + 1; j < targetLines.length; j++) {
|
|
357
|
+
const line = targetLines[j];
|
|
358
|
+
if (line?.includes('{')) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
if (line?.trim().endsWith(';')) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Given a member signature at `start` known to open a body, returns the index
|
|
369
|
+
* just past its balanced closing brace, or `null` if braces never balance.
|
|
370
|
+
*/
|
|
371
|
+
function findMemberBodyEnd(targetLines, start) {
|
|
372
|
+
let braceDepth = 0;
|
|
373
|
+
for (let k = start; k < targetLines.length; k++) {
|
|
374
|
+
const line = targetLines[k];
|
|
346
375
|
if (line === undefined) {
|
|
347
376
|
continue;
|
|
348
377
|
}
|
|
349
|
-
|
|
350
|
-
|
|
378
|
+
braceDepth += countChar(line, '{');
|
|
379
|
+
braceDepth -= countChar(line, '}');
|
|
380
|
+
if (braceDepth === 0 && k > start) {
|
|
381
|
+
return k + 1;
|
|
351
382
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Finds the line range of a class member (method/property with a body) in the
|
|
388
|
+
* given lines, identified by its name. Returns the half-open range
|
|
389
|
+
* `[start, end)` covering the member's signature through its closing brace, or
|
|
390
|
+
* `null` if the member is not found or has no brace-delimited body.
|
|
391
|
+
*
|
|
392
|
+
* Exported for reuse by the three-way merge's `@custom-override` handling.
|
|
393
|
+
*/
|
|
394
|
+
function findMemberRange(targetLines, memberName) {
|
|
395
|
+
for (let i = 0; i < targetLines.length; i++) {
|
|
396
|
+
const line = targetLines[i];
|
|
397
|
+
if (line === undefined || findMemberNameInLine(line) !== memberName) {
|
|
398
|
+
continue;
|
|
363
399
|
}
|
|
364
|
-
if (!
|
|
400
|
+
if (!memberHasBody(targetLines, i)) {
|
|
365
401
|
continue;
|
|
366
402
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (memberLine === undefined) {
|
|
371
|
-
continue;
|
|
372
|
-
}
|
|
373
|
-
braceDepth += countChar(memberLine, '{');
|
|
374
|
-
braceDepth -= countChar(memberLine, '}');
|
|
375
|
-
if (braceDepth === 0 && k > i) {
|
|
376
|
-
return { start: i, end: k + 1 };
|
|
377
|
-
}
|
|
403
|
+
const end = findMemberBodyEnd(targetLines, i);
|
|
404
|
+
if (end !== null) {
|
|
405
|
+
return { start: i, end };
|
|
378
406
|
}
|
|
379
407
|
}
|
|
380
408
|
return null;
|
|
@@ -592,6 +620,78 @@ function hasCustomBlockMarkers(content) {
|
|
|
592
620
|
const lines = content.split('\n');
|
|
593
621
|
return lines.some((line) => exports.customBlockMarkers.startPattern.test(line) || exports.customBlockMarkers.endPattern.test(line));
|
|
594
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Parses a `@custom-override:<member>` marker line **without a regular
|
|
625
|
+
* expression** (plain string scanning — avoids a flagged slow-regex hotspot and
|
|
626
|
+
* is easy to follow). Accepts both the line-comment form (`// @custom-override:member`)
|
|
627
|
+
* and the single-line block-comment form (slash-star ... star-slash), each with
|
|
628
|
+
* an optional leading `@`.
|
|
629
|
+
*
|
|
630
|
+
* Returns the member name, or `undefined` when the line is not an override marker.
|
|
631
|
+
*/
|
|
632
|
+
function parseOverrideMarkerMemberName(line) {
|
|
633
|
+
let text = line.trim();
|
|
634
|
+
if (text.startsWith('//')) {
|
|
635
|
+
text = text.slice(2).trim();
|
|
636
|
+
}
|
|
637
|
+
else if (text.startsWith('/*')) {
|
|
638
|
+
text = text.slice(2).trim();
|
|
639
|
+
if (text.endsWith('*/')) {
|
|
640
|
+
text = text.slice(0, -2).trim();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
if (text.startsWith('@')) {
|
|
647
|
+
text = text.slice(1);
|
|
648
|
+
}
|
|
649
|
+
const prefix = 'custom-override:';
|
|
650
|
+
if (!text.startsWith(prefix)) {
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
const member = text.slice(prefix.length).trim();
|
|
654
|
+
return isValidMemberName(member) ? member : undefined;
|
|
655
|
+
}
|
|
656
|
+
/** True for a non-empty JS identifier (`[A-Za-z_$][A-Za-z0-9_$]*`), checked char-by-char. */
|
|
657
|
+
function isValidMemberName(name) {
|
|
658
|
+
if (name.length === 0) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
for (let i = 0; i < name.length; i++) {
|
|
662
|
+
const ch = name[i];
|
|
663
|
+
const isLetter = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
|
664
|
+
const isSpecial = ch === '_' || ch === '$';
|
|
665
|
+
const isDigit = ch >= '0' && ch <= '9';
|
|
666
|
+
if (i === 0 ? !(isLetter || isSpecial) : !(isLetter || isSpecial || isDigit)) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Extracts the member names declared by `// @custom-override:<member>` markers,
|
|
674
|
+
* in document order and de-duplicated. Used by the three-way merge to keep the
|
|
675
|
+
* developer's version of the named generated members.
|
|
676
|
+
*/
|
|
677
|
+
function extractOverrideMemberNames(content) {
|
|
678
|
+
const names = [];
|
|
679
|
+
const seen = new Set();
|
|
680
|
+
for (const line of content.split('\n')) {
|
|
681
|
+
const memberName = parseOverrideMarkerMemberName(line);
|
|
682
|
+
if (memberName && !seen.has(memberName)) {
|
|
683
|
+
seen.add(memberName);
|
|
684
|
+
names.push(memberName);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return names;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Checks if a string contains any `@custom-override` markers.
|
|
691
|
+
*/
|
|
692
|
+
function hasCustomOverrideMarkers(content) {
|
|
693
|
+
return content.split('\n').some((line) => parseOverrideMarkerMemberName(line) !== undefined);
|
|
694
|
+
}
|
|
595
695
|
/**
|
|
596
696
|
* Reconstructs content from non-custom lines, used when comparing
|
|
597
697
|
* the "real" content differences (excluding custom blocks)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A merged region: either resolved lines, or an unresolved 3-way conflict. */
|
|
2
|
+
export type Diff3Chunk = {
|
|
3
|
+
type: 'stable';
|
|
4
|
+
lines: string[];
|
|
5
|
+
} | {
|
|
6
|
+
type: 'conflict';
|
|
7
|
+
local: string[];
|
|
8
|
+
base: string[];
|
|
9
|
+
incoming: string[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Performs a three-way line merge and returns the resolved/conflicting chunks.
|
|
13
|
+
*
|
|
14
|
+
* @param local - The developer's current version (on disk).
|
|
15
|
+
* @param base - The common ancestor (previous generation).
|
|
16
|
+
* @param incoming - The freshly generated version.
|
|
17
|
+
*/
|
|
18
|
+
export declare function diff3Merge(local: string[], base: string[], incoming: string[]): Diff3Chunk[];
|
|
19
|
+
/** Returns true if any chunk is an unresolved conflict. */
|
|
20
|
+
export declare function hasConflictChunk(chunks: Diff3Chunk[]): boolean;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.diff3Merge = diff3Merge;
|
|
37
|
+
exports.hasConflictChunk = hasConflictChunk;
|
|
38
|
+
/**
|
|
39
|
+
* Self-contained three-way (diff3) line merge.
|
|
40
|
+
*
|
|
41
|
+
* Given a common ancestor (`base`), the developer's on-disk version (`local`),
|
|
42
|
+
* and the freshly generated version (`incoming`), this module reproduces the
|
|
43
|
+
* classic diff3 chunk merge:
|
|
44
|
+
*
|
|
45
|
+
* - Regions changed by only ONE side relative to `base` are auto-applied.
|
|
46
|
+
* - Regions where both sides made the SAME change collapse to that change.
|
|
47
|
+
* - Regions where one side is unchanged from `base` take the other side.
|
|
48
|
+
* - Only regions where both sides diverge from `base` AND from each other are
|
|
49
|
+
* reported as conflicts for a human/agent to arbitrate.
|
|
50
|
+
*
|
|
51
|
+
* Unlike a 2-way diff (disk vs. fresh output), this can attribute a divergence
|
|
52
|
+
* to "the developer changed this" vs. "the generator changed this", so a model
|
|
53
|
+
* change in one region and a developer edit in a different region merge cleanly
|
|
54
|
+
* with no conflict.
|
|
55
|
+
*
|
|
56
|
+
* The matching is computed with jsdiff's `diffArrays` (line arrays), which we
|
|
57
|
+
* already depend on. Regions are grouped on the `base` axis: two changes only
|
|
58
|
+
* collide when their `base` ranges genuinely overlap, so directly-adjacent but
|
|
59
|
+
* disjoint edits from the two sides do not manufacture a false conflict.
|
|
60
|
+
*/
|
|
61
|
+
const Diff = __importStar(require("diff"));
|
|
62
|
+
/**
|
|
63
|
+
* Consumes the maximal run of added/removed parts starting at `start`, summing
|
|
64
|
+
* how many base lines it removes and how many side lines it adds. Returns the
|
|
65
|
+
* index of the first non-change part after the run.
|
|
66
|
+
*/
|
|
67
|
+
function consumeChangeRun(parts, start) {
|
|
68
|
+
let baseLength = 0;
|
|
69
|
+
let otherLength = 0;
|
|
70
|
+
let i = start;
|
|
71
|
+
while (i < parts.length) {
|
|
72
|
+
const change = parts[i];
|
|
73
|
+
if (change === undefined || (!change.added && !change.removed)) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (change.removed) {
|
|
77
|
+
baseLength += change.value.length;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
otherLength += change.value.length;
|
|
81
|
+
}
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
return { baseLength, otherLength, next: i };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Computes the differing hunks between `base` and `other`, anchored on `base`.
|
|
88
|
+
* Common (matching) regions advance both cursors and are not part of any hunk.
|
|
89
|
+
*/
|
|
90
|
+
function diffIndices(base, other) {
|
|
91
|
+
const parts = Diff.diffArrays(base, other);
|
|
92
|
+
const hunks = [];
|
|
93
|
+
let baseOffset = 0;
|
|
94
|
+
let otherOffset = 0;
|
|
95
|
+
let i = 0;
|
|
96
|
+
while (i < parts.length) {
|
|
97
|
+
const part = parts[i];
|
|
98
|
+
if (part === undefined) {
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!part.added && !part.removed) {
|
|
103
|
+
baseOffset += part.value.length;
|
|
104
|
+
otherOffset += part.value.length;
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Start of a change region: consume the maximal run of added/removed parts.
|
|
109
|
+
const baseStart = baseOffset;
|
|
110
|
+
const otherStart = otherOffset;
|
|
111
|
+
const run = consumeChangeRun(parts, i);
|
|
112
|
+
i = run.next;
|
|
113
|
+
baseOffset += run.baseLength;
|
|
114
|
+
otherOffset += run.otherLength;
|
|
115
|
+
hunks.push({
|
|
116
|
+
baseOffset: baseStart,
|
|
117
|
+
baseLength: run.baseLength,
|
|
118
|
+
sideOffset: otherStart,
|
|
119
|
+
sideLength: run.otherLength,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return hunks;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Maps the base range [regionStart, regionEnd) into one side's line coordinates,
|
|
126
|
+
* using that side's hunks. Base lines inside the region but outside a hunk are
|
|
127
|
+
* unchanged on this side and map 1:1.
|
|
128
|
+
*
|
|
129
|
+
* `lo` (where the region starts on the side) is anchored on the FIRST hunk and
|
|
130
|
+
* `hi` (where it ends) on the LAST — not min/max across all hunks. With a single
|
|
131
|
+
* hunk these are equivalent, but when one region groups several same-side hunks
|
|
132
|
+
* whose net length differs from base (deletions/insertions, not pure
|
|
133
|
+
* substitutions), a per-hunk min/max over-extrapolates each hunk to the full
|
|
134
|
+
* region independently and unions the results, pulling in unchanged context lines
|
|
135
|
+
* outside [regionStart, regionEnd). That duplicated context then appears both as a
|
|
136
|
+
* stable chunk and inside the conflict body. `sideHunks` arrive sorted ascending
|
|
137
|
+
* by `baseOffset` (and base hunks never overlap), so the first/last elements carry
|
|
138
|
+
* the region's true start/end deltas.
|
|
139
|
+
*/
|
|
140
|
+
function sliceSide(sideHunks, sideArr, regionStart, regionEnd) {
|
|
141
|
+
const first = sideHunks[0];
|
|
142
|
+
const last = sideHunks.at(-1);
|
|
143
|
+
if (first === undefined || last === undefined) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const lo = first.sideOffset - (first.baseOffset - regionStart);
|
|
147
|
+
const hi = last.sideOffset + last.sideLength + (regionEnd - (last.baseOffset + last.baseLength));
|
|
148
|
+
return sideArr.slice(lo, hi);
|
|
149
|
+
}
|
|
150
|
+
function arraysEqual(a, b) {
|
|
151
|
+
if (a.length !== b.length) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
for (let i = 0; i < a.length; i++) {
|
|
155
|
+
if (a[i] !== b[i]) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Starting from `first` at index `start`, groups every later hunk that genuinely
|
|
163
|
+
* overlaps the region on the base axis. Adjacent-but-disjoint edits
|
|
164
|
+
* (`next.baseOffset === regionEnd`) stay separate, so independent single-line
|
|
165
|
+
* edits from the two sides do not collide. Returns the region span, its hunks,
|
|
166
|
+
* and the index of the first hunk past it.
|
|
167
|
+
*/
|
|
168
|
+
function collectRegion(first, hunks, start) {
|
|
169
|
+
const regionStart = first.baseOffset;
|
|
170
|
+
let regionEnd = first.baseOffset + first.baseLength;
|
|
171
|
+
const regionHunks = [first];
|
|
172
|
+
let i = start + 1;
|
|
173
|
+
while (i < hunks.length) {
|
|
174
|
+
const next = hunks[i];
|
|
175
|
+
if (next === undefined || next.baseOffset >= regionEnd) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
regionEnd = Math.max(regionEnd, next.baseOffset + next.baseLength);
|
|
179
|
+
regionHunks.push(next);
|
|
180
|
+
i++;
|
|
181
|
+
}
|
|
182
|
+
return { regionStart, regionEnd, regionHunks, next: i };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolves a single grouped region into one merged chunk, or `null` when a
|
|
186
|
+
* one-sided region contributes no lines (e.g. a pure deletion). A region with
|
|
187
|
+
* changes from both sides is a conflict unless one side is unchanged from base
|
|
188
|
+
* (take the other) or both sides agree (take either).
|
|
189
|
+
*/
|
|
190
|
+
function resolveRegion(region, local, base, incoming) {
|
|
191
|
+
const { regionStart, regionEnd, regionHunks } = region;
|
|
192
|
+
const localHunks = regionHunks.filter((hunk) => hunk.side === 'local');
|
|
193
|
+
const incomingHunks = regionHunks.filter((hunk) => hunk.side === 'incoming');
|
|
194
|
+
// Only one side touched this region — apply its content verbatim.
|
|
195
|
+
if (localHunks.length === 0 || incomingHunks.length === 0) {
|
|
196
|
+
const oneSided = localHunks.length > 0 ? localHunks : incomingHunks;
|
|
197
|
+
const sideArr = localHunks.length > 0 ? local : incoming;
|
|
198
|
+
const lines = sliceSide(oneSided, sideArr, regionStart, regionEnd);
|
|
199
|
+
return lines.length > 0 ? { type: 'stable', lines } : null;
|
|
200
|
+
}
|
|
201
|
+
const localLines = sliceSide(localHunks, local, regionStart, regionEnd);
|
|
202
|
+
const incomingLines = sliceSide(incomingHunks, incoming, regionStart, regionEnd);
|
|
203
|
+
const baseLines = base.slice(regionStart, regionEnd);
|
|
204
|
+
// Both agree, or incoming never moved from base -> take local.
|
|
205
|
+
if (arraysEqual(localLines, incomingLines) || arraysEqual(incomingLines, baseLines)) {
|
|
206
|
+
return { type: 'stable', lines: localLines };
|
|
207
|
+
}
|
|
208
|
+
// Local never moved from base -> take the generator's version.
|
|
209
|
+
if (arraysEqual(localLines, baseLines)) {
|
|
210
|
+
return { type: 'stable', lines: incomingLines };
|
|
211
|
+
}
|
|
212
|
+
return { type: 'conflict', local: localLines, base: baseLines, incoming: incomingLines };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Performs a three-way line merge and returns the resolved/conflicting chunks.
|
|
216
|
+
*
|
|
217
|
+
* @param local - The developer's current version (on disk).
|
|
218
|
+
* @param base - The common ancestor (previous generation).
|
|
219
|
+
* @param incoming - The freshly generated version.
|
|
220
|
+
*/
|
|
221
|
+
function diff3Merge(local, base, incoming) {
|
|
222
|
+
const hunks = [
|
|
223
|
+
...diffIndices(base, local).map((hunk) => ({ ...hunk, side: 'local' })),
|
|
224
|
+
...diffIndices(base, incoming).map((hunk) => ({ ...hunk, side: 'incoming' })),
|
|
225
|
+
];
|
|
226
|
+
// Order by base position; ties resolve local-before-incoming for stable output.
|
|
227
|
+
hunks.sort((a, b) => a.baseOffset - b.baseOffset || (a.side === b.side ? 0 : a.side === 'local' ? -1 : 1));
|
|
228
|
+
const chunks = [];
|
|
229
|
+
let cursor = 0;
|
|
230
|
+
const emitBase = (end) => {
|
|
231
|
+
if (end > cursor) {
|
|
232
|
+
chunks.push({ type: 'stable', lines: base.slice(cursor, end) });
|
|
233
|
+
cursor = end;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
let i = 0;
|
|
237
|
+
while (i < hunks.length) {
|
|
238
|
+
const first = hunks[i];
|
|
239
|
+
if (first === undefined) {
|
|
240
|
+
i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const region = collectRegion(first, hunks, i);
|
|
244
|
+
i = region.next;
|
|
245
|
+
emitBase(region.regionStart);
|
|
246
|
+
const chunk = resolveRegion(region, local, base, incoming);
|
|
247
|
+
if (chunk !== null) {
|
|
248
|
+
chunks.push(chunk);
|
|
249
|
+
}
|
|
250
|
+
cursor = region.regionEnd;
|
|
251
|
+
}
|
|
252
|
+
emitBase(base.length);
|
|
253
|
+
return chunks;
|
|
254
|
+
}
|
|
255
|
+
/** Returns true if any chunk is an unresolved conflict. */
|
|
256
|
+
function hasConflictChunk(chunks) {
|
|
257
|
+
return chunks.some((chunk) => chunk.type === 'conflict');
|
|
258
|
+
}
|
|
@@ -35,12 +35,38 @@ export declare function hasMergeConflictMarkers(content: FileContent): boolean;
|
|
|
35
35
|
* // your custom code here
|
|
36
36
|
* // @custom-end:myFeature
|
|
37
37
|
*/
|
|
38
|
-
export declare function generateMergeConflict({ contentSource, contentIncoming, labelSource, labelIncoming, }: {
|
|
38
|
+
export declare function generateMergeConflict({ contentSource, contentIncoming, contentBase, labelSource, labelIncoming, onWarning, }: {
|
|
39
39
|
contentSource: FileContent;
|
|
40
40
|
contentIncoming: FileContent;
|
|
41
|
+
/**
|
|
42
|
+
* The common ancestor (previous generation, `G(model_old)`). When provided as
|
|
43
|
+
* a string, a true three-way (diff3) merge is performed: developer edits and
|
|
44
|
+
* model-driven changes in disjoint regions merge cleanly, and conflict markers
|
|
45
|
+
* are emitted only where they genuinely overlap. When omitted (legacy projects,
|
|
46
|
+
* first run after upgrade), the function falls back to the 2-way path below.
|
|
47
|
+
*/
|
|
48
|
+
contentBase?: FileContent;
|
|
41
49
|
labelSource?: string;
|
|
42
50
|
labelIncoming?: string;
|
|
51
|
+
/** Sink for `@custom-override` advisories. Defaults to a yellow `console.warn`. */
|
|
52
|
+
onWarning?: (message: string) => void;
|
|
43
53
|
}): FileContent;
|
|
54
|
+
/**
|
|
55
|
+
* Applies `@custom-override:<member>` regions: for each overridden member, the
|
|
56
|
+
* developer's version (from `local`) replaces the generator's version inside
|
|
57
|
+
* `incoming`, so diff3 sees both sides agree and auto-resolves to the developer's
|
|
58
|
+
* code. When the generator would itself have changed that member (its body
|
|
59
|
+
* differs between `base` and `incoming`), an advisory is emitted so the developer
|
|
60
|
+
* can deliberately re-sync rather than silently shadow a model-driven change.
|
|
61
|
+
*/
|
|
62
|
+
export declare function applyCustomOverrides({ base, local, incoming, onWarning, }: {
|
|
63
|
+
base: string;
|
|
64
|
+
local: string;
|
|
65
|
+
incoming: string;
|
|
66
|
+
onWarning: (message: string) => void;
|
|
67
|
+
}): {
|
|
68
|
+
incoming: string;
|
|
69
|
+
};
|
|
44
70
|
/**
|
|
45
71
|
* Generates a summary of the differences between two files: For each conflict,
|
|
46
72
|
* it will show the first 3 lines of each conflict with line counters, i.e.:
|
|
@@ -36,10 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.mergeConflictMarkers = void 0;
|
|
37
37
|
exports.hasMergeConflictMarkers = hasMergeConflictMarkers;
|
|
38
38
|
exports.generateMergeConflict = generateMergeConflict;
|
|
39
|
+
exports.applyCustomOverrides = applyCustomOverrides;
|
|
39
40
|
exports.generateDiffSummary = generateDiffSummary;
|
|
40
41
|
const Diff = __importStar(require("diff"));
|
|
41
42
|
const utils_1 = require("@postxl/utils");
|
|
42
43
|
const custom_blocks_1 = require("./custom-blocks");
|
|
44
|
+
const diff3_1 = require("./diff3");
|
|
43
45
|
/**
|
|
44
46
|
* Standard merge conflict markers used by git and our generator
|
|
45
47
|
*/
|
|
@@ -136,7 +138,7 @@ function isWhitespaceOnlyDifference(sourceLines, incomingLines) {
|
|
|
136
138
|
* // your custom code here
|
|
137
139
|
* // @custom-end:myFeature
|
|
138
140
|
*/
|
|
139
|
-
function generateMergeConflict({ contentSource, contentIncoming, labelSource = 'Manual', labelIncoming = 'Generated', }) {
|
|
141
|
+
function generateMergeConflict({ contentSource, contentIncoming, contentBase, labelSource = 'Manual', labelIncoming = 'Generated', onWarning = (message) => console.warn((0, utils_1.yellow)(message)), }) {
|
|
140
142
|
// In case the content is binary, we just return the incoming content
|
|
141
143
|
if (typeof contentSource !== 'string' || typeof contentIncoming !== 'string') {
|
|
142
144
|
return contentIncoming;
|
|
@@ -148,6 +150,17 @@ function generateMergeConflict({ contentSource, contentIncoming, labelSource = '
|
|
|
148
150
|
if (sourceNormalized === incomingNormalized) {
|
|
149
151
|
return contentIncoming;
|
|
150
152
|
}
|
|
153
|
+
// True three-way merge when a base (previous generation) is available.
|
|
154
|
+
if (typeof contentBase === 'string') {
|
|
155
|
+
return generateThreeWayMerge({
|
|
156
|
+
contentBase,
|
|
157
|
+
contentSource,
|
|
158
|
+
contentIncoming,
|
|
159
|
+
labelSource,
|
|
160
|
+
labelIncoming,
|
|
161
|
+
onWarning,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
151
164
|
// Handle custom blocks: extract from source, insert into incoming
|
|
152
165
|
if ((0, custom_blocks_1.hasCustomBlockMarkers)(contentSource)) {
|
|
153
166
|
return generateMergeConflictWithCustomBlocks({
|
|
@@ -165,6 +178,122 @@ function generateMergeConflict({ contentSource, contentIncoming, labelSource = '
|
|
|
165
178
|
labelIncoming,
|
|
166
179
|
});
|
|
167
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Generates a true three-way merge between the previous generation (`base`),
|
|
183
|
+
* the developer's on-disk file (`source`/local), and the freshly generated file
|
|
184
|
+
* (`incoming`).
|
|
185
|
+
*
|
|
186
|
+
* Steps:
|
|
187
|
+
* 1. Apply any `@custom-override:<member>` regions by substituting the developer's
|
|
188
|
+
* version of those members into the incoming content (so they auto-resolve),
|
|
189
|
+
* emitting an advisory when the generator itself would have changed the member.
|
|
190
|
+
* 2. Run diff3 over the line arrays.
|
|
191
|
+
* 3. Suppress whitespace-only conflicts (reformatting must not manufacture them).
|
|
192
|
+
* 4. Emit conflict markers only for genuinely overlapping changes.
|
|
193
|
+
*/
|
|
194
|
+
function generateThreeWayMerge({ contentBase, contentSource, contentIncoming, labelSource, labelIncoming, onWarning, }) {
|
|
195
|
+
const { incoming: effectiveIncoming } = applyCustomOverrides({
|
|
196
|
+
base: contentBase,
|
|
197
|
+
local: contentSource,
|
|
198
|
+
incoming: contentIncoming,
|
|
199
|
+
onWarning,
|
|
200
|
+
});
|
|
201
|
+
// Preserve `@custom-start`/`@custom-end` blocks the same way the 2-way path
|
|
202
|
+
// does: extract them from the developer's file, run diff3 on the remaining
|
|
203
|
+
// content, then re-insert the blocks. Without this, a model change in the same
|
|
204
|
+
// base region as a custom block would drag the block into conflict markers
|
|
205
|
+
// (a regression vs. the 2-way behavior). Removing the blocks before diff3
|
|
206
|
+
// guarantees they never appear inside markers; they are re-anchored afterwards.
|
|
207
|
+
if ((0, custom_blocks_1.hasCustomBlockMarkers)(contentSource)) {
|
|
208
|
+
const extractResult = (0, custom_blocks_1.extractCustomBlocks)(contentSource);
|
|
209
|
+
if (extractResult.blocks.length > 0) {
|
|
210
|
+
const sourceWithoutBlocks = (0, custom_blocks_1.reconstructNonCustomContent)(extractResult);
|
|
211
|
+
const chunks = (0, diff3_1.diff3Merge)(splitLines(sourceWithoutBlocks), splitLines(contentBase), splitLines(effectiveIncoming));
|
|
212
|
+
const merged = formatDiff3Chunks(chunks, labelSource, labelIncoming);
|
|
213
|
+
const { content, unplacedBlocks } = (0, custom_blocks_1.insertCustomBlocks)(extractResult.blocks, merged);
|
|
214
|
+
return handleUnplacedBlocks(content, unplacedBlocks);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const chunks = (0, diff3_1.diff3Merge)(splitLines(contentSource), splitLines(contentBase), splitLines(effectiveIncoming));
|
|
218
|
+
return formatDiff3Chunks(chunks, labelSource, labelIncoming);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Splits content into lines on normalized (Unix) line endings. A trailing
|
|
222
|
+
* newline yields a trailing empty line, which round-trips back to a trailing
|
|
223
|
+
* newline on join — preserving the file's exact terminal newline.
|
|
224
|
+
*/
|
|
225
|
+
function splitLines(content) {
|
|
226
|
+
return content.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Renders diff3 chunks into a string, emitting conflict markers for unresolved
|
|
230
|
+
* regions. Whitespace-only conflicts are resolved in favor of the incoming
|
|
231
|
+
* (generated) side so reformatting churn never surfaces as a conflict.
|
|
232
|
+
*/
|
|
233
|
+
function formatDiff3Chunks(chunks, labelSource, labelIncoming) {
|
|
234
|
+
const out = [];
|
|
235
|
+
for (const chunk of chunks) {
|
|
236
|
+
if (chunk.type === 'stable') {
|
|
237
|
+
out.push(...chunk.lines);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (isWhitespaceOnlyDifference(chunk.local, chunk.incoming)) {
|
|
241
|
+
out.push(...chunk.incoming);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
out.push(`${exports.mergeConflictMarkers.start} ${labelSource}`, ...chunk.local, exports.mergeConflictMarkers.separator, ...chunk.incoming, `${exports.mergeConflictMarkers.end} ${labelIncoming}`);
|
|
245
|
+
}
|
|
246
|
+
return out.join('\n');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Applies `@custom-override:<member>` regions: for each overridden member, the
|
|
250
|
+
* developer's version (from `local`) replaces the generator's version inside
|
|
251
|
+
* `incoming`, so diff3 sees both sides agree and auto-resolves to the developer's
|
|
252
|
+
* code. When the generator would itself have changed that member (its body
|
|
253
|
+
* differs between `base` and `incoming`), an advisory is emitted so the developer
|
|
254
|
+
* can deliberately re-sync rather than silently shadow a model-driven change.
|
|
255
|
+
*/
|
|
256
|
+
function applyCustomOverrides({ base, local, incoming, onWarning, }) {
|
|
257
|
+
if (!(0, custom_blocks_1.hasCustomOverrideMarkers)(local)) {
|
|
258
|
+
return { incoming };
|
|
259
|
+
}
|
|
260
|
+
const localLines = splitLines(local);
|
|
261
|
+
const baseLines = splitLines(base);
|
|
262
|
+
let incomingLines = splitLines(incoming);
|
|
263
|
+
for (const member of (0, custom_blocks_1.extractOverrideMemberNames)(local)) {
|
|
264
|
+
// `findMemberRange` starts at the member's signature line (it skips leading
|
|
265
|
+
// comments), so `localMember` deliberately excludes the `@custom-override`
|
|
266
|
+
// marker line that sits above it. That marker therefore stays in `local` as a
|
|
267
|
+
// local-only line relative to `base`/`incoming`, and diff3 auto-preserves it
|
|
268
|
+
// without it ever landing inside the substituted member body.
|
|
269
|
+
const localRange = (0, custom_blocks_1.findMemberRange)(localLines, member);
|
|
270
|
+
if (!localRange) {
|
|
271
|
+
onWarning(`@custom-override:${member} — member not found in the current file; ignoring this override.`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const localMember = localLines.slice(localRange.start, localRange.end);
|
|
275
|
+
const incomingRange = (0, custom_blocks_1.findMemberRange)(incomingLines, member);
|
|
276
|
+
if (!incomingRange) {
|
|
277
|
+
onWarning(`@custom-override:${member} — the generator no longer emits this member; your version is preserved.`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const incomingMember = incomingLines.slice(incomingRange.start, incomingRange.end);
|
|
281
|
+
const baseRange = (0, custom_blocks_1.findMemberRange)(baseLines, member);
|
|
282
|
+
if (baseRange) {
|
|
283
|
+
const baseMember = baseLines.slice(baseRange.start, baseRange.end);
|
|
284
|
+
if (!isWhitespaceOnlyDifference(baseMember, incomingMember)) {
|
|
285
|
+
onWarning(`@custom-override:${member} — the generator changed this member, but your override shadows it. ` +
|
|
286
|
+
`Remove the override and re-sync if you want the new generated version.`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
incomingLines = [
|
|
290
|
+
...incomingLines.slice(0, incomingRange.start),
|
|
291
|
+
...localMember,
|
|
292
|
+
...incomingLines.slice(incomingRange.end),
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
return { incoming: incomingLines.join('\n') };
|
|
296
|
+
}
|
|
168
297
|
/**
|
|
169
298
|
* Generates merge conflict output while preserving custom blocks.
|
|
170
299
|
*
|
package/dist/utils/sync.d.ts
CHANGED
|
@@ -83,6 +83,15 @@ type SyncParams = {
|
|
|
83
83
|
* `logSyncResult` to summarize the diff.
|
|
84
84
|
*/
|
|
85
85
|
dryRun?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* The previous generation (`G(model_old)`) as a virtual file system, keyed by
|
|
88
|
+
* the same POSIX paths as `vfs`. When a file ends up in a `MergeConflict` state
|
|
89
|
+
* and its content is available here, sync performs a true three-way (diff3)
|
|
90
|
+
* merge instead of the 2-way disk-vs-fresh diff: developer edits and
|
|
91
|
+
* model-driven changes in disjoint regions merge cleanly. Omit (legacy projects
|
|
92
|
+
* / first run after upgrade) to fall back to the 2-way path.
|
|
93
|
+
*/
|
|
94
|
+
baseVfs?: VirtualFileSystem;
|
|
86
95
|
};
|
|
87
96
|
/**
|
|
88
97
|
* Error returned when files with unresolved merge conflicts are detected
|
|
@@ -125,7 +134,7 @@ export type SyncResults = {
|
|
|
125
134
|
* merge conflict markers. If found, the sync will abort immediately and return an error with the
|
|
126
135
|
* list of files that need to be resolved before generation can continue.
|
|
127
136
|
*/
|
|
128
|
-
export declare function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, dryRun, }: SyncParams): Promise<SyncResults>;
|
|
137
|
+
export declare function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, dryRun, baseVfs, }: SyncParams): Promise<SyncResults>;
|
|
129
138
|
type FileState = {
|
|
130
139
|
state: 'empty';
|
|
131
140
|
} | {
|
|
@@ -158,6 +167,7 @@ type Action_MergeConflict = {
|
|
|
158
167
|
type: `MergeConflict`;
|
|
159
168
|
diskContent: FileContent;
|
|
160
169
|
virtualContent: FileContent;
|
|
170
|
+
baseContent?: FileContent;
|
|
161
171
|
};
|
|
162
172
|
export type ActionType = Action['type'];
|
|
163
173
|
export declare function executeAction(filePath: Path.PosixPath, action: Action): Promise<ActionResult>;
|
package/dist/utils/sync.js
CHANGED
|
@@ -121,7 +121,7 @@ const Path = __importStar(require("./path"));
|
|
|
121
121
|
* merge conflict markers. If found, the sync will abort immediately and return an error with the
|
|
122
122
|
* list of files that need to be resolved before generation can continue.
|
|
123
123
|
*/
|
|
124
|
-
async function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, dryRun, }) {
|
|
124
|
+
async function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, dryRun, baseVfs, }) {
|
|
125
125
|
const diskPathNormalized = Path.normalize(diskFilePath);
|
|
126
126
|
const files = await getFilesStates({ vfs, lockFilePath, diskFilePath, selectiveGeneration: !!selectiveGeneration });
|
|
127
127
|
// Check for unresolved merge conflicts before writing any files
|
|
@@ -155,7 +155,7 @@ async function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneratio
|
|
|
155
155
|
};
|
|
156
156
|
continue;
|
|
157
157
|
}
|
|
158
|
-
const [action, isEjectedFile] = determineActionState({ virtual, lock, disk }, force);
|
|
158
|
+
const [action, isEjectedFile] = determineActionState({ virtual, lock, disk }, force, baseVfs?.get(filePath));
|
|
159
159
|
if (dryRun) {
|
|
160
160
|
// Dry-run: record the planned action but do not touch disk.
|
|
161
161
|
result.files[filePath] = {
|
|
@@ -340,7 +340,7 @@ const actionMap = {
|
|
|
340
340
|
'V:empty-L:empty-D:Hash0': ['NoAction', false, 'NoAction'], // Indistinguishable from "empty-empty-HashX"
|
|
341
341
|
'V:empty-L:empty-D:empty': ['NoAction', false, 'NoAction'], // Cannot occur
|
|
342
342
|
};
|
|
343
|
-
function determineActionState({ virtual, lock, disk }, force) {
|
|
343
|
+
function determineActionState({ virtual, lock, disk }, force, baseContent) {
|
|
344
344
|
const stateKey = getStateKey({ virtual, lock, disk });
|
|
345
345
|
const [defaultActionType, isEjected, forceActionType] = actionMap[stateKey];
|
|
346
346
|
const actionType = force ? forceActionType : defaultActionType;
|
|
@@ -357,7 +357,15 @@ function determineActionState({ virtual, lock, disk }, force) {
|
|
|
357
357
|
if (virtual.state === 'empty') {
|
|
358
358
|
throw new Error(`This should not happen assuming that the actionMap only has 'Write' actions for entries starting with H1, ie a defined virtual file`);
|
|
359
359
|
}
|
|
360
|
-
return [
|
|
360
|
+
return [
|
|
361
|
+
{
|
|
362
|
+
type: 'MergeConflict',
|
|
363
|
+
diskContent: disk.content,
|
|
364
|
+
virtualContent: virtual.content,
|
|
365
|
+
...(baseContent === undefined ? {} : { baseContent }),
|
|
366
|
+
},
|
|
367
|
+
isEjected,
|
|
368
|
+
];
|
|
361
369
|
}
|
|
362
370
|
return [{ type: actionType }, isEjected];
|
|
363
371
|
}
|
|
@@ -375,7 +383,11 @@ async function executeAction(filePath, action) {
|
|
|
375
383
|
throw new utils_1.ExhaustiveSwitchCheck(action);
|
|
376
384
|
}
|
|
377
385
|
}
|
|
378
|
-
async function writeMergeConflictFile(filePath, { diskContent, virtualContent }) {
|
|
379
|
-
const content = (0, merge_conflict_1.generateMergeConflict)({
|
|
386
|
+
async function writeMergeConflictFile(filePath, { diskContent, virtualContent, baseContent }) {
|
|
387
|
+
const content = (0, merge_conflict_1.generateMergeConflict)({
|
|
388
|
+
contentSource: diskContent,
|
|
389
|
+
contentIncoming: virtualContent,
|
|
390
|
+
...(baseContent === undefined ? {} : { contentBase: baseContent }),
|
|
391
|
+
});
|
|
380
392
|
return (0, fs_utils_1.writeFile)(filePath, content);
|
|
381
393
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postxl/generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Core package that orchestrates the code generation of a PXL project",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"jszip": "3.10.1",
|
|
47
47
|
"minimatch": "^10.2.5",
|
|
48
48
|
"p-limit": "3.1.0",
|
|
49
|
-
"@postxl/schema": "^2.0
|
|
49
|
+
"@postxl/schema": "^2.1.0",
|
|
50
50
|
"@postxl/utils": "^1.4.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|