create-flow-os 0.0.51-dev.1772054686 → 0.0.51-dev.1772055928
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/package.json +1 -1
- package/src/init/merge.ts +19 -3
- package/src/init/scaffold.ts +15 -11
package/package.json
CHANGED
package/src/init/merge.ts
CHANGED
|
@@ -87,13 +87,20 @@ export async function resolveConflicts(conflicts: Conflict[], path: string): Pro
|
|
|
87
87
|
return overrides;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/**
|
|
90
|
+
/** Normalizza contenuto per confronto (trim, line endings) */
|
|
91
|
+
function normalizeForCompare(s: string): string {
|
|
92
|
+
return s.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Never overwrite: file vuoto → template, file con contenuto → mantieni. Mai corrompere.
|
|
96
|
+
* Se supersededContent è fornito e userContent coincide (normalizzato), usa templateContent:
|
|
97
|
+
* un pacchetto "figlio" (es. router) può sostituire il template di un pacchetto "genitore" (es. client). */
|
|
91
98
|
export async function mergeFile(
|
|
92
99
|
userContent: string | null,
|
|
93
100
|
templateContent: string,
|
|
94
101
|
relPath: string,
|
|
95
102
|
promptConflict: (path: string, conflicts: Conflict[]) => Promise<Record<string, unknown>>,
|
|
96
|
-
_options?: Merge3WayOptions
|
|
103
|
+
_options?: Merge3WayOptions & { supersededContent?: string | null }
|
|
97
104
|
): Promise<string> {
|
|
98
105
|
const isJson = relPath.toLowerCase().endsWith(".json");
|
|
99
106
|
|
|
@@ -108,9 +115,18 @@ export async function mergeFile(
|
|
|
108
115
|
return merged;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
// Never overwrite: vuoto → template. Con contenuto → mantieni. Eccezione: file incompleto (manca export che template ha)
|
|
112
118
|
const content = (userContent ?? "").trim();
|
|
113
119
|
if (!content) return templateContent;
|
|
120
|
+
|
|
121
|
+
// Template supersede template: se il file utente è identico al template precedente (sostituito da un pkg figlio), usa il nuovo
|
|
122
|
+
const superseded = _options?.supersededContent;
|
|
123
|
+
if (superseded != null && superseded.trim()) {
|
|
124
|
+
if (normalizeForCompare(userContent!) === normalizeForCompare(superseded)) {
|
|
125
|
+
return templateContent;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Never overwrite: eccezione file incompleto (manca export che template ha)
|
|
114
130
|
const tHasExport = /\bexport\s+default\b/.test(templateContent);
|
|
115
131
|
const uHasExport = /\bexport\s+default\b/.test(userContent ?? "");
|
|
116
132
|
if (tHasExport && !uHasExport) {
|
package/src/init/scaffold.ts
CHANGED
|
@@ -30,38 +30,42 @@ function collectConfigFiles(configDir: string, relPath = ""): Map<string, string
|
|
|
30
30
|
return out;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
type TemplateEntry = { current: string; superseded?: string };
|
|
34
|
+
|
|
35
|
+
/** Merge template A con template B (stesso relPath). Quando B sovrascrive A, A va in superseded. */
|
|
36
|
+
function mergeTemplateInto(combined: Map<string, TemplateEntry>, pkgFiles: Map<string, string>): void {
|
|
35
37
|
for (const [relPath, content] of pkgFiles) {
|
|
36
38
|
const existing = combined.get(relPath);
|
|
37
39
|
const isJson = relPath.toLowerCase().endsWith(".json");
|
|
38
40
|
const isCode = /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(relPath);
|
|
39
41
|
if (existing === undefined) {
|
|
40
|
-
combined.set(relPath, content);
|
|
42
|
+
combined.set(relPath, { current: content });
|
|
41
43
|
} else if (isJson) {
|
|
42
|
-
combined.set(relPath, mergeJsonTemplates(existing, content));
|
|
44
|
+
combined.set(relPath, { current: mergeJsonTemplates(existing.current, content) });
|
|
43
45
|
} else if (isCode) {
|
|
44
|
-
combined.set(relPath, mergeCodeTemplates(existing, content));
|
|
46
|
+
combined.set(relPath, { current: mergeCodeTemplates(existing.current, content), superseded: existing.current });
|
|
45
47
|
} else {
|
|
46
|
-
combined.set(relPath, content);
|
|
48
|
+
combined.set(relPath, { current: content, superseded: existing.current });
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
/** Scrive combined template in cwd. Never overwrite: file con contenuto → mantieni. */
|
|
53
|
+
/** Scrive combined template in cwd. Never overwrite: file con contenuto → mantieni. Se coincide con superseded → usa nuovo template. */
|
|
52
54
|
async function writeMergedWithUser(
|
|
53
|
-
combined: Map<string,
|
|
55
|
+
combined: Map<string, TemplateEntry>,
|
|
54
56
|
cwd: string,
|
|
55
57
|
promptConflict: (path: string, conflicts: import("./merge").Conflict[]) => Promise<Record<string, unknown>>
|
|
56
58
|
): Promise<void> {
|
|
57
|
-
for (const [relPath,
|
|
59
|
+
for (const [relPath, entry] of combined) {
|
|
58
60
|
const dest = join(cwd, relPath);
|
|
59
61
|
const destDir = dirname(dest);
|
|
60
62
|
if (!existsSync(destDir)) {
|
|
61
63
|
mkdirSync(destDir, { recursive: true });
|
|
62
64
|
}
|
|
63
65
|
const userContent = existsSync(dest) ? readFileSync(dest, "utf-8") : null;
|
|
64
|
-
const merged = await mergeFile(userContent,
|
|
66
|
+
const merged = await mergeFile(userContent, entry.current, relPath, promptConflict, {
|
|
67
|
+
supersededContent: entry.superseded ?? null,
|
|
68
|
+
});
|
|
65
69
|
writeFileSync(dest, merged);
|
|
66
70
|
}
|
|
67
71
|
}
|
|
@@ -455,7 +459,7 @@ export async function initLib(
|
|
|
455
459
|
}
|
|
456
460
|
|
|
457
461
|
onProgress?.("templates");
|
|
458
|
-
const combined = new Map<string, string>();
|
|
462
|
+
const combined = new Map<string, { current: string; superseded?: string }>();
|
|
459
463
|
const done = new Set<string>();
|
|
460
464
|
const order: string[] = [];
|
|
461
465
|
const tmpDirs: string[] = [];
|