create-projx 1.6.2 → 1.6.4
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 +18 -6
- package/dist/{baseline-KTCFW2FK.js → baseline-PZM4KJJW.js} +2 -6
- package/dist/{chunk-LTIJPVRZ.js → chunk-6YRBHJ2V.js} +151 -38
- package/dist/{chunk-D33FXCNT.js → chunk-XQ7FE4U3.js} +343 -153
- package/dist/index.js +749 -205
- package/dist/{utils-VY5BBJBQ.js → utils-AVKSTHIF.js} +1 -1
- package/package.json +1 -1
- package/src/templates/README.md.ejs +1 -1
- package/src/templates/ci.yml.ejs +63 -63
- package/src/templates/docker-compose.dev.yml.ejs +44 -29
- package/src/templates/docker-compose.yml.ejs +27 -25
- package/src/templates/pre-commit.ejs +52 -52
- package/src/templates/setup.sh.ejs +16 -16
|
@@ -13,11 +13,18 @@ import {
|
|
|
13
13
|
toSnake,
|
|
14
14
|
upsertComponentMarker,
|
|
15
15
|
writeProjxConfig
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-6YRBHJ2V.js";
|
|
17
17
|
|
|
18
18
|
// src/baseline.ts
|
|
19
19
|
import { existsSync, writeFileSync, unlinkSync } from "fs";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
chmod,
|
|
22
|
+
mkdir,
|
|
23
|
+
writeFile,
|
|
24
|
+
rm,
|
|
25
|
+
readFile as readFile2,
|
|
26
|
+
copyFile
|
|
27
|
+
} from "fs/promises";
|
|
21
28
|
import { execSync } from "child_process";
|
|
22
29
|
import { join as join2, dirname } from "path";
|
|
23
30
|
import { tmpdir } from "os";
|
|
@@ -25,30 +32,60 @@ import { tmpdir } from "os";
|
|
|
25
32
|
// src/generators/index.ts
|
|
26
33
|
import { readFile } from "fs/promises";
|
|
27
34
|
import { join } from "path";
|
|
35
|
+
function shellSafeUpper(s) {
|
|
36
|
+
return s.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
var CANONICAL_DISPLAY = {
|
|
39
|
+
fastapi: "FastAPI",
|
|
40
|
+
fastify: "Fastify",
|
|
41
|
+
frontend: "Frontend",
|
|
42
|
+
mobile: "Flutter",
|
|
43
|
+
e2e: "E2E",
|
|
44
|
+
infra: "Terraform"
|
|
45
|
+
};
|
|
46
|
+
function withInstances(vars) {
|
|
47
|
+
const base = vars.instances && vars.instances.length > 0 ? vars.instances : vars.components.map((type) => ({
|
|
48
|
+
type,
|
|
49
|
+
path: vars.paths[type] ?? type
|
|
50
|
+
}));
|
|
51
|
+
const enriched = base.map((inst) => ({
|
|
52
|
+
...inst,
|
|
53
|
+
upper: shellSafeUpper(inst.path),
|
|
54
|
+
display: inst.path === inst.type ? CANONICAL_DISPLAY[inst.type] : inst.path
|
|
55
|
+
}));
|
|
56
|
+
const byType = (type) => enriched.filter((i) => i.type === type).sort((a, b) => a.path.localeCompare(b.path));
|
|
57
|
+
return {
|
|
58
|
+
...vars,
|
|
59
|
+
instances: enriched,
|
|
60
|
+
fastapiInstances: byType("fastapi"),
|
|
61
|
+
fastifyInstances: byType("fastify"),
|
|
62
|
+
frontendInstances: byType("frontend"),
|
|
63
|
+
mobileInstances: byType("mobile"),
|
|
64
|
+
e2eInstances: byType("e2e"),
|
|
65
|
+
infraInstances: byType("infra")
|
|
66
|
+
};
|
|
67
|
+
}
|
|
28
68
|
async function renderShared(filename, vars) {
|
|
29
|
-
const tpl = await readFile(
|
|
30
|
-
join(sharedTemplateDir(), filename),
|
|
31
|
-
"utf-8"
|
|
32
|
-
);
|
|
69
|
+
const tpl = await readFile(join(sharedTemplateDir(), filename), "utf-8");
|
|
33
70
|
return render(tpl, vars);
|
|
34
71
|
}
|
|
35
72
|
async function generateDockerCompose(vars) {
|
|
36
|
-
return renderShared("docker-compose.yml.ejs", vars);
|
|
73
|
+
return renderShared("docker-compose.yml.ejs", withInstances(vars));
|
|
37
74
|
}
|
|
38
75
|
async function generateDockerComposeDev(vars) {
|
|
39
|
-
return renderShared("docker-compose.dev.yml.ejs", vars);
|
|
76
|
+
return renderShared("docker-compose.dev.yml.ejs", withInstances(vars));
|
|
40
77
|
}
|
|
41
78
|
async function generatePreCommit(vars) {
|
|
42
|
-
return renderShared("pre-commit.ejs", vars);
|
|
79
|
+
return renderShared("pre-commit.ejs", withInstances(vars));
|
|
43
80
|
}
|
|
44
81
|
async function generateSetupSh(vars) {
|
|
45
|
-
return renderShared("setup.sh.ejs", vars);
|
|
82
|
+
return renderShared("setup.sh.ejs", withInstances(vars));
|
|
46
83
|
}
|
|
47
84
|
async function generateCiYml(vars) {
|
|
48
|
-
return renderShared("ci.yml.ejs", vars);
|
|
85
|
+
return renderShared("ci.yml.ejs", withInstances(vars));
|
|
49
86
|
}
|
|
50
87
|
async function generateReadme(vars) {
|
|
51
|
-
return renderShared("README.md.ejs", vars);
|
|
88
|
+
return renderShared("README.md.ejs", withInstances(vars));
|
|
52
89
|
}
|
|
53
90
|
function generateVscodeSettings(vars) {
|
|
54
91
|
const settings = {};
|
|
@@ -58,9 +95,15 @@ function generateVscodeSettings(vars) {
|
|
|
58
95
|
"editor.codeActionsOnSave": { "source.fixAll.ruff": "explicit" }
|
|
59
96
|
};
|
|
60
97
|
}
|
|
61
|
-
settings["[typescript]"] = {
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
settings["[typescript]"] = {
|
|
99
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
100
|
+
};
|
|
101
|
+
settings["[typescriptreact]"] = {
|
|
102
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
103
|
+
};
|
|
104
|
+
settings["[javascript]"] = {
|
|
105
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
106
|
+
};
|
|
64
107
|
settings["[json]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
65
108
|
settings["[css]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
66
109
|
settings["[yaml]"] = { "editor.defaultFormatter": "esbenp.prettier-vscode" };
|
|
@@ -82,32 +125,9 @@ function generateVscodeSettings(vars) {
|
|
|
82
125
|
}
|
|
83
126
|
|
|
84
127
|
// src/baseline.ts
|
|
85
|
-
function buildPathsUpper(paths) {
|
|
86
|
-
const result = {};
|
|
87
|
-
for (const [component, dir] of Object.entries(paths)) {
|
|
88
|
-
result[component] = dir.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
89
|
-
}
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
var CANONICAL_DISPLAY_NAMES = {
|
|
93
|
-
fastapi: "FastAPI",
|
|
94
|
-
fastify: "Fastify",
|
|
95
|
-
frontend: "Frontend",
|
|
96
|
-
mobile: "Flutter",
|
|
97
|
-
e2e: "E2E",
|
|
98
|
-
infra: "Terraform"
|
|
99
|
-
};
|
|
100
|
-
function buildDisplayNames(paths) {
|
|
101
|
-
const result = {};
|
|
102
|
-
for (const [component, dir] of Object.entries(paths)) {
|
|
103
|
-
const canonical = component;
|
|
104
|
-
result[canonical] = dir === canonical ? CANONICAL_DISPLAY_NAMES[canonical] : dir;
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
128
|
var BASELINE_REF = "refs/projx/baseline";
|
|
109
129
|
async function migrateComponentMarkers(cwd, components, componentPaths, applyDefaults) {
|
|
110
|
-
const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-
|
|
130
|
+
const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-AVKSTHIF.js");
|
|
111
131
|
for (const component of components) {
|
|
112
132
|
const dir = componentPaths[component];
|
|
113
133
|
const markerDir = join2(cwd, dir);
|
|
@@ -175,11 +195,17 @@ function saveBaselineRef(cwd) {
|
|
|
175
195
|
}
|
|
176
196
|
function getBaselineRef(cwd) {
|
|
177
197
|
try {
|
|
178
|
-
return execSync(`git rev-parse --verify ${BASELINE_REF}`, {
|
|
198
|
+
return execSync(`git rev-parse --verify ${BASELINE_REF}`, {
|
|
199
|
+
cwd,
|
|
200
|
+
stdio: "pipe"
|
|
201
|
+
}).toString().trim();
|
|
179
202
|
} catch {
|
|
180
203
|
}
|
|
181
204
|
try {
|
|
182
|
-
const sha = execSync("git log -1 --format=%H -- .projx", {
|
|
205
|
+
const sha = execSync("git log -1 --format=%H -- .projx", {
|
|
206
|
+
cwd,
|
|
207
|
+
stdio: "pipe"
|
|
208
|
+
}).toString().trim();
|
|
183
209
|
if (sha) return sha;
|
|
184
210
|
} catch {
|
|
185
211
|
}
|
|
@@ -187,7 +213,10 @@ function getBaselineRef(cwd) {
|
|
|
187
213
|
}
|
|
188
214
|
function getFileAtRef(cwd, ref, filePath) {
|
|
189
215
|
try {
|
|
190
|
-
return execSync(`git show ${ref}:"${filePath}"`, {
|
|
216
|
+
return execSync(`git show ${ref}:"${filePath}"`, {
|
|
217
|
+
cwd,
|
|
218
|
+
stdio: "pipe"
|
|
219
|
+
}).toString();
|
|
191
220
|
} catch {
|
|
192
221
|
return null;
|
|
193
222
|
}
|
|
@@ -258,15 +287,22 @@ async function tryThreeWayMerge(cwd, templateDir, baselineRef, componentPaths) {
|
|
|
258
287
|
const pathFallbacks = buildPathFallbacks(componentPaths);
|
|
259
288
|
for (const file of templateFiles) {
|
|
260
289
|
if (file === ".projx") continue;
|
|
261
|
-
|
|
290
|
+
const isMarker = file.endsWith("/.projx-component") || file === ".projx-component";
|
|
262
291
|
const oursPath = join2(cwd, file);
|
|
292
|
+
if (isMarker && existsSync(oursPath)) continue;
|
|
263
293
|
if (!existsSync(oursPath)) {
|
|
264
294
|
await mkdir(dirname(oursPath), { recursive: true });
|
|
265
295
|
await copyFile(join2(templateDir, file), oursPath);
|
|
266
296
|
merged.push(file);
|
|
267
297
|
continue;
|
|
268
298
|
}
|
|
269
|
-
|
|
299
|
+
if (isMarker) continue;
|
|
300
|
+
const baseContent = lookupBaseContent(
|
|
301
|
+
cwd,
|
|
302
|
+
baselineRef,
|
|
303
|
+
file,
|
|
304
|
+
pathFallbacks
|
|
305
|
+
);
|
|
270
306
|
if (baseContent === null) continue;
|
|
271
307
|
let theirsContent;
|
|
272
308
|
try {
|
|
@@ -307,7 +343,10 @@ function createOrphanWorktree(cwd) {
|
|
|
307
343
|
}
|
|
308
344
|
function cleanupWorktree(cwd, worktree, branch) {
|
|
309
345
|
try {
|
|
310
|
-
execSync(`git worktree remove "${worktree}" --force`, {
|
|
346
|
+
execSync(`git worktree remove "${worktree}" --force`, {
|
|
347
|
+
cwd,
|
|
348
|
+
stdio: "pipe"
|
|
349
|
+
});
|
|
311
350
|
} catch {
|
|
312
351
|
try {
|
|
313
352
|
rm(worktree, { recursive: true, force: true });
|
|
@@ -332,7 +371,8 @@ async function removeSkippedFiles(dir, skipPatterns, realDir) {
|
|
|
332
371
|
await walk(full, base);
|
|
333
372
|
} else if (entry.name !== ".projx-component") {
|
|
334
373
|
const targetRel = rel.endsWith(".ejs") ? rel.slice(0, -".ejs".length) : rel;
|
|
335
|
-
if (!matchesSkip(targetRel, skipPatterns) && !matchesSkip(rel, skipPatterns))
|
|
374
|
+
if (!matchesSkip(targetRel, skipPatterns) && !matchesSkip(rel, skipPatterns))
|
|
375
|
+
continue;
|
|
336
376
|
if (realDir && !existsSync(join2(realDir, targetRel))) continue;
|
|
337
377
|
await unlink(full);
|
|
338
378
|
}
|
|
@@ -341,41 +381,38 @@ async function removeSkippedFiles(dir, skipPatterns, realDir) {
|
|
|
341
381
|
await walk(dir, dir);
|
|
342
382
|
}
|
|
343
383
|
async function writeTemplateToDir(dest, repoDir, components, componentPaths, vars, version, options = {}) {
|
|
344
|
-
const {
|
|
384
|
+
const {
|
|
385
|
+
componentSkips,
|
|
386
|
+
rootSkip,
|
|
387
|
+
applyDefaults = false,
|
|
388
|
+
realCwd = dest,
|
|
389
|
+
extraInstances = [],
|
|
390
|
+
instancesToScaffold
|
|
391
|
+
} = options;
|
|
345
392
|
const name = vars.projectName;
|
|
346
393
|
const nameSnake = toSnake(name);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
369
|
-
await renderEjsInDir(outDir, vars);
|
|
370
|
-
await upsertComponentMarker(join2(dest, targetDir), component, skipPatterns.length > 0 ? skipPatterns : void 0);
|
|
371
|
-
}
|
|
372
|
-
if (!vars.pathsUpper) {
|
|
373
|
-
vars.pathsUpper = buildPathsUpper(componentPaths);
|
|
374
|
-
}
|
|
375
|
-
if (!vars.displayNames) {
|
|
376
|
-
vars.displayNames = buildDisplayNames(componentPaths);
|
|
394
|
+
const primaryInstances = components.map((type) => ({
|
|
395
|
+
type,
|
|
396
|
+
path: componentPaths[type]
|
|
397
|
+
}));
|
|
398
|
+
const allInstances = [
|
|
399
|
+
...primaryInstances,
|
|
400
|
+
...extraInstances
|
|
401
|
+
];
|
|
402
|
+
const toScaffold = instancesToScaffold ?? allInstances;
|
|
403
|
+
for (const inst of toScaffold) {
|
|
404
|
+
await writeOneInstance(inst, {
|
|
405
|
+
dest,
|
|
406
|
+
repoDir,
|
|
407
|
+
vars,
|
|
408
|
+
componentPaths,
|
|
409
|
+
realCwd,
|
|
410
|
+
applyDefaults,
|
|
411
|
+
baseSkip: componentSkips?.[inst.type] ?? [],
|
|
412
|
+
projectName: name,
|
|
413
|
+
nameSnake
|
|
414
|
+
});
|
|
377
415
|
}
|
|
378
|
-
await substituteNames(dest, components, componentPaths, name, nameSnake, vars.nameOverrides);
|
|
379
416
|
const hasBackend = components.includes("fastapi") || components.includes("fastify");
|
|
380
417
|
const userSkip = rootSkip ?? [];
|
|
381
418
|
const defaultRootSkip = applyDefaults ? DEFAULT_ROOT_SKIP_PATTERNS : [];
|
|
@@ -386,53 +423,147 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
|
|
|
386
423
|
};
|
|
387
424
|
if (hasBackend || components.includes("frontend")) {
|
|
388
425
|
if (shouldWrite("docker-compose.yml"))
|
|
389
|
-
await writeFile(
|
|
426
|
+
await writeFile(
|
|
427
|
+
join2(dest, "docker-compose.yml"),
|
|
428
|
+
await generateDockerCompose(vars)
|
|
429
|
+
);
|
|
390
430
|
if (shouldWrite("docker-compose.dev.yml"))
|
|
391
|
-
await writeFile(
|
|
431
|
+
await writeFile(
|
|
432
|
+
join2(dest, "docker-compose.dev.yml"),
|
|
433
|
+
await generateDockerComposeDev(vars)
|
|
434
|
+
);
|
|
392
435
|
}
|
|
393
436
|
if (shouldWrite("README.md"))
|
|
394
437
|
await writeFile(join2(dest, "README.md"), await generateReadme(vars));
|
|
395
438
|
if (shouldWrite(".githooks/pre-commit")) {
|
|
396
439
|
await mkdir(join2(dest, ".githooks"), { recursive: true });
|
|
397
|
-
await writeFile(
|
|
440
|
+
await writeFile(
|
|
441
|
+
join2(dest, ".githooks/pre-commit"),
|
|
442
|
+
await generatePreCommit(vars)
|
|
443
|
+
);
|
|
398
444
|
await chmod(join2(dest, ".githooks/pre-commit"), 493);
|
|
399
445
|
}
|
|
400
446
|
if (shouldWrite(".github/workflows/ci.yml")) {
|
|
401
447
|
await mkdir(join2(dest, ".github/workflows"), { recursive: true });
|
|
402
|
-
await writeFile(
|
|
448
|
+
await writeFile(
|
|
449
|
+
join2(dest, ".github/workflows/ci.yml"),
|
|
450
|
+
await generateCiYml(vars)
|
|
451
|
+
);
|
|
403
452
|
}
|
|
404
|
-
if (shouldWrite("setup.sh")) {
|
|
405
|
-
await
|
|
406
|
-
await
|
|
453
|
+
if (shouldWrite("scripts/setup.sh")) {
|
|
454
|
+
await mkdir(join2(dest, "scripts"), { recursive: true });
|
|
455
|
+
await writeFile(
|
|
456
|
+
join2(dest, "scripts/setup.sh"),
|
|
457
|
+
await generateSetupSh(vars)
|
|
458
|
+
);
|
|
459
|
+
await chmod(join2(dest, "scripts/setup.sh"), 493);
|
|
407
460
|
}
|
|
408
461
|
await copyStaticFiles(repoDir, dest);
|
|
409
462
|
if (shouldWrite(".vscode/settings.json")) {
|
|
410
463
|
await mkdir(join2(dest, ".vscode"), { recursive: true });
|
|
411
|
-
await writeFile(
|
|
464
|
+
await writeFile(
|
|
465
|
+
join2(dest, ".vscode/settings.json"),
|
|
466
|
+
generateVscodeSettings(vars)
|
|
467
|
+
);
|
|
412
468
|
}
|
|
413
469
|
await writeManagedProjx(dest, version, vars, applyDefaults);
|
|
414
470
|
}
|
|
415
|
-
async function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
471
|
+
async function writeOneInstance(inst, opts) {
|
|
472
|
+
const {
|
|
473
|
+
dest,
|
|
474
|
+
repoDir,
|
|
475
|
+
vars,
|
|
476
|
+
componentPaths,
|
|
477
|
+
realCwd,
|
|
478
|
+
applyDefaults,
|
|
479
|
+
baseSkip,
|
|
480
|
+
projectName,
|
|
481
|
+
nameSnake
|
|
482
|
+
} = opts;
|
|
483
|
+
const { type, path: targetDir } = inst;
|
|
484
|
+
const realMarker = await readComponentMarker(join2(realCwd, targetDir));
|
|
485
|
+
const isNewMarker = !realMarker;
|
|
486
|
+
const shouldApplyComponentDefault = isNewMarker || applyDefaults;
|
|
487
|
+
const markerSkip = realMarker?.skip ?? [];
|
|
488
|
+
const defaultSkip = shouldApplyComponentDefault ? DEFAULT_COMPONENT_SKIP_PATTERNS[type] ?? [] : [];
|
|
489
|
+
const skipPatterns = [
|
|
490
|
+
.../* @__PURE__ */ new Set([...baseSkip, ...markerSkip, ...defaultSkip])
|
|
491
|
+
];
|
|
492
|
+
const tmpDir = join2(dest, "__cptmp__");
|
|
493
|
+
await copyComponent(repoDir, type, tmpDir);
|
|
494
|
+
const srcDir = join2(tmpDir, type);
|
|
495
|
+
if (skipPatterns.length > 0) {
|
|
496
|
+
await removeSkippedFiles(srcDir, skipPatterns, join2(realCwd, targetDir));
|
|
497
|
+
}
|
|
498
|
+
const outDir = join2(dest, targetDir);
|
|
499
|
+
await mkdir(outDir, { recursive: true });
|
|
500
|
+
const { cp } = await import("fs/promises");
|
|
501
|
+
if (existsSync(srcDir)) {
|
|
502
|
+
await cp(srcDir, outDir, { recursive: true, force: true });
|
|
503
|
+
}
|
|
504
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
505
|
+
const instancePaths = {
|
|
506
|
+
...componentPaths,
|
|
507
|
+
[type]: targetDir
|
|
508
|
+
};
|
|
509
|
+
await renderEjsInDir(outDir, { ...vars, paths: instancePaths });
|
|
510
|
+
await upsertComponentMarker(
|
|
511
|
+
join2(dest, targetDir),
|
|
512
|
+
type,
|
|
513
|
+
skipPatterns.length > 0 ? skipPatterns : void 0
|
|
514
|
+
);
|
|
515
|
+
await substituteNamesForInstance(
|
|
516
|
+
inst,
|
|
517
|
+
dest,
|
|
518
|
+
projectName,
|
|
519
|
+
nameSnake,
|
|
520
|
+
vars.nameOverrides
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
async function substituteNamesForInstance(inst, dest, name, nameSnake, overrides) {
|
|
524
|
+
const { type, path } = inst;
|
|
525
|
+
const isCanonical = path === type;
|
|
526
|
+
if (type === "fastapi") {
|
|
527
|
+
const target = isCanonical ? overrides?.fastapi ?? `${name}-fastapi` : `${name}-${path}`;
|
|
528
|
+
await replaceInFile(
|
|
529
|
+
join2(dest, `${path}/pyproject.toml`),
|
|
530
|
+
"projx-fastapi",
|
|
531
|
+
target
|
|
532
|
+
);
|
|
533
|
+
} else if (type === "fastify") {
|
|
534
|
+
const target = isCanonical ? overrides?.fastify ?? `${name}-fastify` : `${name}-${path}`;
|
|
535
|
+
await replaceInFile(
|
|
536
|
+
join2(dest, `${path}/package.json`),
|
|
537
|
+
"projx-fastify",
|
|
538
|
+
target
|
|
539
|
+
);
|
|
540
|
+
} else if (type === "frontend") {
|
|
541
|
+
const target = isCanonical ? overrides?.frontend ?? `${name}-frontend` : `${name}-${path}`;
|
|
542
|
+
await replaceInFile(
|
|
543
|
+
join2(dest, `${path}/package.json`),
|
|
544
|
+
"projx-frontend",
|
|
545
|
+
target
|
|
546
|
+
);
|
|
547
|
+
} else if (type === "e2e") {
|
|
548
|
+
const target = isCanonical ? overrides?.e2e ?? `${name}-e2e` : `${name}-${path}`;
|
|
549
|
+
await replaceInFile(
|
|
550
|
+
join2(dest, `${path}/package.json`),
|
|
551
|
+
"projx-e2e",
|
|
552
|
+
target
|
|
553
|
+
);
|
|
554
|
+
} else if (type === "mobile") {
|
|
555
|
+
const target = isCanonical ? overrides?.mobile ?? `${nameSnake}_mobile` : toSnake(`${nameSnake}_${path}`);
|
|
556
|
+
await replaceInFile(
|
|
557
|
+
join2(dest, `${path}/pubspec.yaml`),
|
|
558
|
+
"projx_mobile",
|
|
559
|
+
target
|
|
560
|
+
);
|
|
561
|
+
await replaceInDir(
|
|
562
|
+
join2(dest, path),
|
|
563
|
+
"package:projx_mobile/",
|
|
564
|
+
`package:${target}/`,
|
|
565
|
+
".dart"
|
|
566
|
+
);
|
|
436
567
|
}
|
|
437
568
|
}
|
|
438
569
|
async function detectPackageNameOverrides(cwd, components, componentPaths) {
|
|
@@ -484,7 +615,7 @@ async function readPubspecName(file) {
|
|
|
484
615
|
return null;
|
|
485
616
|
}
|
|
486
617
|
}
|
|
487
|
-
async function applyTemplate(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip, applyDefaults = false) {
|
|
618
|
+
async function applyTemplate(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip, applyDefaults = false, extraInstances = [], instancesToScaffold) {
|
|
488
619
|
const hasHead = (() => {
|
|
489
620
|
try {
|
|
490
621
|
execSync("git rev-parse HEAD", { cwd, stdio: "pipe" });
|
|
@@ -494,24 +625,47 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
494
625
|
}
|
|
495
626
|
})();
|
|
496
627
|
if (!hasHead) {
|
|
497
|
-
await writeTemplateToDir(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
628
|
+
await writeTemplateToDir(
|
|
629
|
+
cwd,
|
|
630
|
+
repoDir,
|
|
631
|
+
components,
|
|
632
|
+
componentPaths,
|
|
633
|
+
vars,
|
|
634
|
+
version,
|
|
635
|
+
{
|
|
636
|
+
componentSkips,
|
|
637
|
+
rootSkip,
|
|
638
|
+
applyDefaults,
|
|
639
|
+
realCwd: cwd,
|
|
640
|
+
extraInstances,
|
|
641
|
+
instancesToScaffold
|
|
642
|
+
}
|
|
643
|
+
);
|
|
503
644
|
return { status: "clean" };
|
|
504
645
|
}
|
|
505
646
|
const { worktree, branch } = createOrphanWorktree(cwd);
|
|
506
647
|
try {
|
|
507
|
-
await writeTemplateToDir(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
648
|
+
await writeTemplateToDir(
|
|
649
|
+
worktree,
|
|
650
|
+
repoDir,
|
|
651
|
+
components,
|
|
652
|
+
componentPaths,
|
|
653
|
+
vars,
|
|
654
|
+
version,
|
|
655
|
+
{
|
|
656
|
+
componentSkips,
|
|
657
|
+
rootSkip,
|
|
658
|
+
applyDefaults,
|
|
659
|
+
realCwd: cwd,
|
|
660
|
+
extraInstances,
|
|
661
|
+
instancesToScaffold
|
|
662
|
+
}
|
|
663
|
+
);
|
|
513
664
|
execSync("git add -A", { cwd: worktree, stdio: "pipe" });
|
|
514
|
-
const diff = execSync("git diff --cached --stat", {
|
|
665
|
+
const diff = execSync("git diff --cached --stat", {
|
|
666
|
+
cwd: worktree,
|
|
667
|
+
stdio: "pipe"
|
|
668
|
+
}).toString().trim();
|
|
515
669
|
if (!diff) {
|
|
516
670
|
cleanupWorktree(cwd, worktree, branch);
|
|
517
671
|
return { status: "clean" };
|
|
@@ -521,7 +675,10 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
521
675
|
{ cwd: worktree, stdio: "pipe" }
|
|
522
676
|
);
|
|
523
677
|
try {
|
|
524
|
-
execSync(`git worktree remove "${worktree}" --force`, {
|
|
678
|
+
execSync(`git worktree remove "${worktree}" --force`, {
|
|
679
|
+
cwd,
|
|
680
|
+
stdio: "pipe"
|
|
681
|
+
});
|
|
525
682
|
} catch {
|
|
526
683
|
try {
|
|
527
684
|
await rm(worktree, { recursive: true, force: true });
|
|
@@ -547,7 +704,12 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
547
704
|
} catch {
|
|
548
705
|
}
|
|
549
706
|
if (mergeClean) {
|
|
550
|
-
await migrateComponentMarkers(
|
|
707
|
+
await migrateComponentMarkers(
|
|
708
|
+
cwd,
|
|
709
|
+
components,
|
|
710
|
+
componentPaths,
|
|
711
|
+
applyDefaults
|
|
712
|
+
);
|
|
551
713
|
saveBaselineRef(cwd);
|
|
552
714
|
return { status: "clean" };
|
|
553
715
|
}
|
|
@@ -555,32 +717,47 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
555
717
|
if (baselineRef) {
|
|
556
718
|
const tmpTemplate = join2(tmpdir(), `projx-tpl-${Date.now()}`);
|
|
557
719
|
await mkdir(tmpTemplate, { recursive: true });
|
|
558
|
-
await writeTemplateToDir(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
720
|
+
await writeTemplateToDir(
|
|
721
|
+
tmpTemplate,
|
|
722
|
+
repoDir,
|
|
723
|
+
components,
|
|
724
|
+
componentPaths,
|
|
725
|
+
vars,
|
|
726
|
+
version,
|
|
727
|
+
{
|
|
728
|
+
componentSkips,
|
|
729
|
+
rootSkip,
|
|
730
|
+
applyDefaults,
|
|
731
|
+
realCwd: cwd,
|
|
732
|
+
extraInstances,
|
|
733
|
+
instancesToScaffold
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
const result = await tryThreeWayMerge(
|
|
737
|
+
cwd,
|
|
738
|
+
tmpTemplate,
|
|
739
|
+
baselineRef,
|
|
740
|
+
componentPaths
|
|
741
|
+
);
|
|
565
742
|
await rm(tmpTemplate, { recursive: true, force: true });
|
|
566
|
-
await migrateComponentMarkers(
|
|
743
|
+
await migrateComponentMarkers(
|
|
744
|
+
cwd,
|
|
745
|
+
components,
|
|
746
|
+
componentPaths,
|
|
747
|
+
applyDefaults
|
|
748
|
+
);
|
|
567
749
|
if (result.conflicted.length === 0) {
|
|
568
750
|
await writeManagedProjx(cwd, version, vars, applyDefaults);
|
|
569
751
|
execSync("git add -A", { cwd, stdio: "pipe" });
|
|
570
|
-
const staged = execSync("git diff --cached --stat", {
|
|
752
|
+
const staged = execSync("git diff --cached --stat", {
|
|
753
|
+
cwd,
|
|
754
|
+
stdio: "pipe"
|
|
755
|
+
}).toString().trim();
|
|
571
756
|
if (staged) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
);
|
|
577
|
-
} catch (err) {
|
|
578
|
-
throw new Error(
|
|
579
|
-
`Pre-commit hook rejected the merged template content. Resolve the issues and commit manually:
|
|
580
|
-
git commit -m "projx: update to template v${version} (3-way merge)"`,
|
|
581
|
-
{ cause: err }
|
|
582
|
-
);
|
|
583
|
-
}
|
|
757
|
+
execSync(
|
|
758
|
+
`git -c core.hooksPath=/dev/null commit -m "projx: update to template v${version} (3-way merge)"`,
|
|
759
|
+
{ cwd, stdio: "pipe" }
|
|
760
|
+
);
|
|
584
761
|
}
|
|
585
762
|
saveBaselineRef(cwd);
|
|
586
763
|
return result.merged.length > 0 ? { status: "merged", mergedFiles: result.merged } : { status: "clean" };
|
|
@@ -609,13 +786,28 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
609
786
|
conflictedFiles: result.conflicted
|
|
610
787
|
};
|
|
611
788
|
}
|
|
612
|
-
await writeTemplateToDir(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
789
|
+
await writeTemplateToDir(
|
|
790
|
+
cwd,
|
|
791
|
+
repoDir,
|
|
792
|
+
components,
|
|
793
|
+
componentPaths,
|
|
794
|
+
vars,
|
|
795
|
+
version,
|
|
796
|
+
{
|
|
797
|
+
componentSkips,
|
|
798
|
+
rootSkip,
|
|
799
|
+
applyDefaults,
|
|
800
|
+
realCwd: cwd,
|
|
801
|
+
extraInstances,
|
|
802
|
+
instancesToScaffold
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
await migrateComponentMarkers(
|
|
806
|
+
cwd,
|
|
807
|
+
components,
|
|
808
|
+
componentPaths,
|
|
809
|
+
applyDefaults
|
|
810
|
+
);
|
|
619
811
|
return { status: "conflicts" };
|
|
620
812
|
} catch (err) {
|
|
621
813
|
cleanupWorktree(cwd, worktree, branch);
|
|
@@ -624,8 +816,6 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
624
816
|
}
|
|
625
817
|
|
|
626
818
|
export {
|
|
627
|
-
buildPathsUpper,
|
|
628
|
-
buildDisplayNames,
|
|
629
819
|
BASELINE_REF,
|
|
630
820
|
matchesSkip,
|
|
631
821
|
saveBaselineRef,
|