@shrkcrft/generator 0.1.0-alpha.13 → 0.1.0-alpha.14
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/dry-run.d.ts.map +1 -1
- package/dist/dry-run.js +28 -8
- package/dist/planned-change.d.ts +16 -2
- package/dist/planned-change.d.ts.map +1 -1
- package/dist/planned-change.js +62 -0
- package/package.json +5 -5
package/dist/dry-run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dry-run.d.ts","sourceRoot":"","sources":["../src/dry-run.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,gEAAgE;IAChE,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,aAAa,
|
|
1
|
+
{"version":3,"file":"dry-run.d.ts","sourceRoot":"","sources":["../src/dry-run.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,gEAAgE;IAChE,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,aAAa,CA8Gf"}
|
package/dist/dry-run.js
CHANGED
|
@@ -30,6 +30,12 @@ export function planGeneration(template, request) {
|
|
|
30
30
|
}
|
|
31
31
|
const rendered = renderTemplate(template, validation.resolved);
|
|
32
32
|
const changes = [];
|
|
33
|
+
// Per-file content overlay so that MULTIPLE changes (or a files() create
|
|
34
|
+
// followed by a changes() op) targeting the SAME file COMPOSE. Without
|
|
35
|
+
// this every change is evaluated against the original on-disk bytes and
|
|
36
|
+
// the writer's last same-file write clobbers the earlier ones. Keyed by
|
|
37
|
+
// absolute path → the cumulative content after the prior change(s).
|
|
38
|
+
const overlay = new Map();
|
|
33
39
|
// 1) Legacy CREATE-only files() output — unchanged behaviour.
|
|
34
40
|
for (const file of rendered.files) {
|
|
35
41
|
let safe;
|
|
@@ -59,14 +65,16 @@ export function planGeneration(template, request) {
|
|
|
59
65
|
// unreadable existing file — treat as conflict
|
|
60
66
|
}
|
|
61
67
|
const decision = decideForExisting(file.overwrite ? OverwriteStrategy.Overwrite : overwriteStrategy, existing, file.content);
|
|
68
|
+
const contents = decision.type === FileChangeType.Skip ? existing : file.content;
|
|
62
69
|
changes.push({
|
|
63
70
|
type: decision.type,
|
|
64
71
|
absolutePath,
|
|
65
72
|
relativePath: relPath,
|
|
66
|
-
contents
|
|
73
|
+
contents,
|
|
67
74
|
reason: decision.reason,
|
|
68
75
|
sizeBytes: Buffer.byteLength(file.content, 'utf8'),
|
|
69
76
|
});
|
|
77
|
+
overlay.set(absolutePath, contents);
|
|
70
78
|
}
|
|
71
79
|
else {
|
|
72
80
|
changes.push({
|
|
@@ -77,11 +85,13 @@ export function planGeneration(template, request) {
|
|
|
77
85
|
reason: 'New file (does not exist)',
|
|
78
86
|
sizeBytes: Buffer.byteLength(file.content, 'utf8'),
|
|
79
87
|
});
|
|
88
|
+
overlay.set(absolutePath, file.content);
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
|
-
// 2) v2 planned changes — evaluate against live filesystem
|
|
91
|
+
// 2) v2 planned changes — evaluate against the live filesystem, threaded
|
|
92
|
+
// through the overlay so successive changes to one file compose.
|
|
83
93
|
for (const tplChange of rendered.changes) {
|
|
84
|
-
const evaluated = planOne(tplChange, request.projectRoot);
|
|
94
|
+
const evaluated = planOne(tplChange, request.projectRoot, overlay);
|
|
85
95
|
changes.push(evaluated);
|
|
86
96
|
}
|
|
87
97
|
const { hasConflicts } = summarizeConflicts(changes);
|
|
@@ -98,7 +108,7 @@ export function planGeneration(template, request) {
|
|
|
98
108
|
safe: !hasConflicts && changes.length > 0,
|
|
99
109
|
};
|
|
100
110
|
}
|
|
101
|
-
function planOne(tplChange, projectRoot) {
|
|
111
|
+
function planOne(tplChange, projectRoot, overlay) {
|
|
102
112
|
const op = tplChange.operation;
|
|
103
113
|
let safe;
|
|
104
114
|
try {
|
|
@@ -121,15 +131,23 @@ function planOne(tplChange, projectRoot) {
|
|
|
121
131
|
targetPath: tplChange.targetPath,
|
|
122
132
|
operation: op,
|
|
123
133
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
// Prefer the overlay (cumulative result of prior same-file changes) over
|
|
135
|
+
// the on-disk bytes so successive ops on one file compose deterministically.
|
|
136
|
+
const existing = overlay?.has(safe.absolutePath)
|
|
137
|
+
? (overlay.get(safe.absolutePath) ?? null)
|
|
138
|
+
: existsSync(safe.absolutePath)
|
|
139
|
+
? readFileSafe(safe.absolutePath)
|
|
140
|
+
: null;
|
|
141
|
+
const result = evaluatePlannedChange({
|
|
128
142
|
change,
|
|
129
143
|
absolutePath: safe.absolutePath,
|
|
130
144
|
relativePath: safe.relativePath,
|
|
131
145
|
existing,
|
|
132
146
|
});
|
|
147
|
+
// Record the cumulative content (Skip/Conflict carry the unchanged bytes,
|
|
148
|
+
// which is exactly what a later op on the same file should see).
|
|
149
|
+
overlay?.set(safe.absolutePath, result.contents);
|
|
150
|
+
return result;
|
|
133
151
|
}
|
|
134
152
|
function readFileSafe(absolutePath) {
|
|
135
153
|
try {
|
|
@@ -170,6 +188,8 @@ function previewContentForOperation(op) {
|
|
|
170
188
|
return `${op.enumName}.${op.entryName} = '${op.entryValue}'`;
|
|
171
189
|
case 'insert-object-entry':
|
|
172
190
|
return `${op.objectName}.${op.entryKey}: ${op.entryValue}`;
|
|
191
|
+
case 'insert-array-entry':
|
|
192
|
+
return `${op.arrayName} ⟵ ${op.entryValue}`;
|
|
173
193
|
case 'insert-before-closing-brace':
|
|
174
194
|
return op.snippet;
|
|
175
195
|
case 'insert-between-anchors':
|
package/dist/planned-change.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* - MCP stays read-only — this module is pure logic.
|
|
22
22
|
*/
|
|
23
23
|
import { FileChangeType, type IFileChange } from './file-change.js';
|
|
24
|
-
export type PlannedOperationKind = 'create' | 'append' | 'insert-after' | 'insert-before' | 'replace' | 'export' | 'ensure-import' | 'insert-enum-entry' | 'insert-object-entry' | 'insert-before-closing-brace' | 'insert-between-anchors';
|
|
24
|
+
export type PlannedOperationKind = 'create' | 'append' | 'insert-after' | 'insert-before' | 'replace' | 'export' | 'ensure-import' | 'insert-enum-entry' | 'insert-object-entry' | 'insert-array-entry' | 'insert-before-closing-brace' | 'insert-between-anchors';
|
|
25
25
|
interface ICreateOperation {
|
|
26
26
|
kind: 'create';
|
|
27
27
|
content: string;
|
|
@@ -123,6 +123,20 @@ interface IInsertObjectEntryOperation {
|
|
|
123
123
|
shorthand?: boolean;
|
|
124
124
|
description?: string;
|
|
125
125
|
}
|
|
126
|
+
interface IInsertArrayEntryOperation {
|
|
127
|
+
kind: 'insert-array-entry';
|
|
128
|
+
/**
|
|
129
|
+
* Array identifier — a `const`/`let`/`var` bound to an array literal,
|
|
130
|
+
* e.g. `editorScopeEntries` or `DEFAULT_PANELS`. The element is inserted
|
|
131
|
+
* before the array's matching closing bracket.
|
|
132
|
+
*/
|
|
133
|
+
arrayName: string;
|
|
134
|
+
/** Element source text to add (already source-formatted, no trailing comma). */
|
|
135
|
+
entryValue: string;
|
|
136
|
+
/** Optional idempotency marker (default = `entryValue`). */
|
|
137
|
+
ifMissing?: string;
|
|
138
|
+
description?: string;
|
|
139
|
+
}
|
|
126
140
|
interface IInsertBeforeClosingBraceOperation {
|
|
127
141
|
kind: 'insert-before-closing-brace';
|
|
128
142
|
/** Container identifier, e.g. an interface/class/enum name. */
|
|
@@ -142,7 +156,7 @@ interface IInsertBetweenAnchorsOperation {
|
|
|
142
156
|
ifMissing?: string;
|
|
143
157
|
description?: string;
|
|
144
158
|
}
|
|
145
|
-
export type IPlannedOperation = ICreateOperation | IAppendOperation | IInsertAfterOperation | IInsertBeforeOperation | IReplaceOperation | IExportOperation | IEnsureImportOperation | IInsertEnumEntryOperation | IInsertObjectEntryOperation | IInsertBeforeClosingBraceOperation | IInsertBetweenAnchorsOperation;
|
|
159
|
+
export type IPlannedOperation = ICreateOperation | IAppendOperation | IInsertAfterOperation | IInsertBeforeOperation | IReplaceOperation | IExportOperation | IEnsureImportOperation | IInsertEnumEntryOperation | IInsertObjectEntryOperation | IInsertArrayEntryOperation | IInsertBeforeClosingBraceOperation | IInsertBetweenAnchorsOperation;
|
|
146
160
|
export interface IPlannedChange {
|
|
147
161
|
/** Final file path relative to project root. */
|
|
148
162
|
targetPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planned-change.d.ts","sourceRoot":"","sources":["../src/planned-change.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"planned-change.d.ts","sourceRoot":"","sources":["../src/planned-change.ts"],"names":[],"mappings":"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"}
|
package/dist/planned-change.js
CHANGED
|
@@ -43,6 +43,8 @@ export function evaluatePlannedChange(input) {
|
|
|
43
43
|
return evaluateInsertEnumEntry(op, absolutePath, relativePath, existing);
|
|
44
44
|
case 'insert-object-entry':
|
|
45
45
|
return evaluateInsertObjectEntry(op, absolutePath, relativePath, existing);
|
|
46
|
+
case 'insert-array-entry':
|
|
47
|
+
return evaluateInsertArrayEntry(op, absolutePath, relativePath, existing);
|
|
46
48
|
case 'insert-before-closing-brace':
|
|
47
49
|
return evaluateInsertBeforeClosingBrace(op, absolutePath, relativePath, existing);
|
|
48
50
|
case 'insert-between-anchors':
|
|
@@ -298,6 +300,33 @@ function evaluateInsertObjectEntry(op, absolutePath, relativePath, existing) {
|
|
|
298
300
|
existing.slice(obj.openIdx + 1 + trailingTrim.length);
|
|
299
301
|
return mkChange(FileChangeType.InsertBefore, absolutePath, relativePath, next, `insert-object-entry: added ${op.objectName}.${op.entryKey}`, op);
|
|
300
302
|
}
|
|
303
|
+
function evaluateInsertArrayEntry(op, absolutePath, relativePath, existing) {
|
|
304
|
+
if (existing === null) {
|
|
305
|
+
return mkChange(FileChangeType.Conflict, absolutePath, relativePath, '', 'insert-array-entry: target file does not exist', op);
|
|
306
|
+
}
|
|
307
|
+
const arr = findArrayLiteralBlock(existing, op.arrayName);
|
|
308
|
+
if (!arr) {
|
|
309
|
+
return mkChange(FileChangeType.Conflict, absolutePath, relativePath, existing, `insert-array-entry: array "${op.arrayName}" not found`, op);
|
|
310
|
+
}
|
|
311
|
+
if (arr.duplicate) {
|
|
312
|
+
return mkChange(FileChangeType.Conflict, absolutePath, relativePath, existing, `insert-array-entry: array "${op.arrayName}" appears multiple times (ambiguous)`, op);
|
|
313
|
+
}
|
|
314
|
+
// Idempotency: skip when the element (or its caller-supplied marker) is
|
|
315
|
+
// already present anywhere inside the array body.
|
|
316
|
+
const body = existing.slice(arr.openIdx + 1, arr.closeIdx);
|
|
317
|
+
const marker = op.ifMissing ?? op.entryValue;
|
|
318
|
+
if (marker.length > 0 && body.includes(marker)) {
|
|
319
|
+
return mkChange(FileChangeType.Skip, absolutePath, relativePath, existing, `insert-array-entry: "${op.arrayName}" already contains entry (idempotent)`, op);
|
|
320
|
+
}
|
|
321
|
+
const indent = detectIndent(body) || ' ';
|
|
322
|
+
const trailingTrim = body.replace(/[\s,]+$/, '');
|
|
323
|
+
const needsComma = trailingTrim.length > 0;
|
|
324
|
+
const insertion = `${needsComma ? ',\n' : '\n'}${indent}${op.entryValue}`;
|
|
325
|
+
const next = existing.slice(0, arr.openIdx + 1 + trailingTrim.length) +
|
|
326
|
+
insertion +
|
|
327
|
+
existing.slice(arr.openIdx + 1 + trailingTrim.length);
|
|
328
|
+
return mkChange(FileChangeType.InsertBefore, absolutePath, relativePath, next, `insert-array-entry: added entry to ${op.arrayName}`, op);
|
|
329
|
+
}
|
|
301
330
|
function evaluateInsertBeforeClosingBrace(op, absolutePath, relativePath, existing) {
|
|
302
331
|
if (existing === null) {
|
|
303
332
|
return mkChange(FileChangeType.Conflict, absolutePath, relativePath, '', 'insert-before-closing-brace: target file does not exist', op);
|
|
@@ -465,6 +494,39 @@ function findObjectLiteralBlock(source, objectName) {
|
|
|
465
494
|
const re = new RegExp(`\\b(?:const|let|var)\\s+${escapeRegex(objectName)}\\b[^=]*=\\s*\\{`, 'g');
|
|
466
495
|
return findBraceBlock(source, re);
|
|
467
496
|
}
|
|
497
|
+
function findArrayLiteralBlock(source, arrayName) {
|
|
498
|
+
const re = new RegExp(`\\b(?:const|let|var)\\s+${escapeRegex(arrayName)}\\b[^=]*=\\s*\\[`, 'g');
|
|
499
|
+
return findBracketBlock(source, re);
|
|
500
|
+
}
|
|
501
|
+
function findBracketBlock(source, headRegex) {
|
|
502
|
+
headRegex.lastIndex = 0;
|
|
503
|
+
const first = headRegex.exec(source);
|
|
504
|
+
if (!first)
|
|
505
|
+
return null;
|
|
506
|
+
const openIdx = first.index + first[0].length - 1;
|
|
507
|
+
const second = headRegex.exec(source);
|
|
508
|
+
const duplicate = second !== null;
|
|
509
|
+
const closeIdx = findMatchingCloseBracket(source, openIdx);
|
|
510
|
+
if (closeIdx < 0)
|
|
511
|
+
return null;
|
|
512
|
+
return { openIdx, closeIdx, duplicate };
|
|
513
|
+
}
|
|
514
|
+
function findMatchingCloseBracket(source, openBracketIdx) {
|
|
515
|
+
let depth = 0;
|
|
516
|
+
let i = openBracketIdx;
|
|
517
|
+
while (i < source.length) {
|
|
518
|
+
const ch = source[i];
|
|
519
|
+
if (ch === '[')
|
|
520
|
+
depth += 1;
|
|
521
|
+
else if (ch === ']') {
|
|
522
|
+
depth -= 1;
|
|
523
|
+
if (depth === 0)
|
|
524
|
+
return i;
|
|
525
|
+
}
|
|
526
|
+
i += 1;
|
|
527
|
+
}
|
|
528
|
+
return -1;
|
|
529
|
+
}
|
|
468
530
|
function findBlockByName(source, name) {
|
|
469
531
|
// Matches `class Name {`, `interface Name {`, `enum Name {`, `namespace Name {`.
|
|
470
532
|
const re = new RegExp(`\\b(?:class|interface|enum|namespace|module)\\s+${escapeRegex(name)}\\b[^{]*\\{`, 'g');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/generator",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.14",
|
|
4
4
|
"description": "SharkCraft plan-first generator: GenerationPlan, FileChange, dry-run, safe writes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
47
|
-
"@shrkcrft/templates": "^0.1.0-alpha.
|
|
48
|
-
"@shrkcrft/rules": "^0.1.0-alpha.
|
|
49
|
-
"@shrkcrft/paths": "^0.1.0-alpha.
|
|
46
|
+
"@shrkcrft/core": "^0.1.0-alpha.14",
|
|
47
|
+
"@shrkcrft/templates": "^0.1.0-alpha.14",
|
|
48
|
+
"@shrkcrft/rules": "^0.1.0-alpha.14",
|
|
49
|
+
"@shrkcrft/paths": "^0.1.0-alpha.14"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|