create-projx 1.3.0 → 1.3.1
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/index.js +85 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -185,16 +185,32 @@ async function readFileOrNull(path) {
|
|
|
185
185
|
return null;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
async function
|
|
188
|
+
async function readComponentMarker(dir) {
|
|
189
|
+
const raw = await readFileOrNull(join(dir, COMPONENT_MARKER));
|
|
190
|
+
if (!raw) return null;
|
|
191
|
+
try {
|
|
192
|
+
const data = JSON.parse(raw);
|
|
193
|
+
return {
|
|
194
|
+
components: data.components ?? (data.component ? [data.component] : []),
|
|
195
|
+
origin: data.origin ?? "scaffold",
|
|
196
|
+
skip: data.skip
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function writeComponentMarker(dir, component, origin = "scaffold", skip) {
|
|
189
203
|
const markerPath = join(dir, COMPONENT_MARKER);
|
|
190
204
|
let components = [component];
|
|
191
205
|
let existingOrigin = origin;
|
|
206
|
+
let existingSkip = skip;
|
|
192
207
|
const existing = await readFileOrNull(markerPath);
|
|
193
208
|
if (existing) {
|
|
194
209
|
try {
|
|
195
210
|
const data = JSON.parse(existing);
|
|
196
211
|
const prev = data.components ?? (data.component ? [data.component] : []);
|
|
197
212
|
existingOrigin = data.origin ?? origin;
|
|
213
|
+
existingSkip = skip ?? data.skip;
|
|
198
214
|
if (!prev.includes(component)) {
|
|
199
215
|
components = [...prev, component];
|
|
200
216
|
} else {
|
|
@@ -203,10 +219,9 @@ async function writeComponentMarker(dir, component, origin = "scaffold") {
|
|
|
203
219
|
} catch {
|
|
204
220
|
}
|
|
205
221
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
222
|
+
const marker = { components, origin: existingOrigin };
|
|
223
|
+
if (existingSkip && existingSkip.length > 0) marker.skip = existingSkip;
|
|
224
|
+
await writeFile(markerPath, JSON.stringify(marker, null, 2) + "\n");
|
|
210
225
|
}
|
|
211
226
|
async function discoverComponentPaths(cwd, components) {
|
|
212
227
|
const paths = {};
|
|
@@ -433,7 +448,43 @@ function removeWorktree(cwd, worktree) {
|
|
|
433
448
|
}
|
|
434
449
|
}
|
|
435
450
|
}
|
|
436
|
-
|
|
451
|
+
function matchesSkip(filePath, patterns) {
|
|
452
|
+
for (const pattern of patterns) {
|
|
453
|
+
if (pattern === "**") return true;
|
|
454
|
+
if (pattern.endsWith("/**")) {
|
|
455
|
+
const prefix = pattern.slice(0, -3);
|
|
456
|
+
if (filePath.startsWith(prefix + "/") || filePath === prefix) return true;
|
|
457
|
+
}
|
|
458
|
+
if (pattern.startsWith("**/")) {
|
|
459
|
+
const suffix = pattern.slice(3);
|
|
460
|
+
if (filePath.endsWith(suffix) || filePath.includes("/" + suffix)) return true;
|
|
461
|
+
}
|
|
462
|
+
if (pattern.startsWith("*.")) {
|
|
463
|
+
const ext = pattern.slice(1);
|
|
464
|
+
if (filePath.endsWith(ext)) return true;
|
|
465
|
+
}
|
|
466
|
+
if (filePath === pattern) return true;
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
async function removeSkippedFiles(dir, skipPatterns) {
|
|
471
|
+
if (skipPatterns.length === 0) return;
|
|
472
|
+
const { readdir: readdir3, unlink } = await import("fs/promises");
|
|
473
|
+
const walk = async (current, base) => {
|
|
474
|
+
const entries = await readdir3(current, { withFileTypes: true });
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
const full = join3(current, entry.name);
|
|
477
|
+
const rel = full.slice(base.length + 1);
|
|
478
|
+
if (entry.isDirectory()) {
|
|
479
|
+
await walk(full, base);
|
|
480
|
+
} else if (entry.name !== ".projx-component" && matchesSkip(rel, skipPatterns)) {
|
|
481
|
+
await unlink(full);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
await walk(dir, dir);
|
|
486
|
+
}
|
|
487
|
+
async function writeTemplateToDir(dest, repoDir, components, componentPaths, vars, version, origin, componentSkips) {
|
|
437
488
|
const name = vars.projectName;
|
|
438
489
|
const nameSnake = toSnake(name);
|
|
439
490
|
for (const component of components) {
|
|
@@ -450,7 +501,11 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
|
|
|
450
501
|
}
|
|
451
502
|
await rm2(join3(dest, "__tmp__"), { recursive: true, force: true });
|
|
452
503
|
}
|
|
453
|
-
|
|
504
|
+
const skipPatterns = componentSkips?.[component] ?? [];
|
|
505
|
+
if (skipPatterns.length > 0) {
|
|
506
|
+
await removeSkippedFiles(join3(dest, targetDir), skipPatterns);
|
|
507
|
+
}
|
|
508
|
+
await writeComponentMarker(join3(dest, targetDir), component, origin, skipPatterns.length > 0 ? skipPatterns : void 0);
|
|
454
509
|
}
|
|
455
510
|
await substituteNames(dest, components, componentPaths, name, nameSnake);
|
|
456
511
|
const hasBackend = components.includes("fastapi") || components.includes("fastify");
|
|
@@ -498,10 +553,10 @@ async function substituteNames(dest, components, paths, name, nameSnake) {
|
|
|
498
553
|
await replaceInDir(join3(dest, `${paths.mobile}`), "package:projx_mobile/", `package:${nameSnake}_mobile/`, ".dart");
|
|
499
554
|
}
|
|
500
555
|
}
|
|
501
|
-
async function createBaseline(cwd, repoDir, components, componentPaths, vars, version, origin = "scaffold") {
|
|
556
|
+
async function createBaseline(cwd, repoDir, components, componentPaths, vars, version, origin = "scaffold", componentSkips) {
|
|
502
557
|
const worktree = createWorktree(cwd, BASELINE_BRANCH, true);
|
|
503
558
|
try {
|
|
504
|
-
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, origin);
|
|
559
|
+
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, origin, componentSkips);
|
|
505
560
|
execSync2("git add -A", { cwd: worktree, stdio: "pipe" });
|
|
506
561
|
execSync2(
|
|
507
562
|
`git commit --no-verify -m "projx: baseline template v${version} [${components.join(", ")}]"`,
|
|
@@ -511,11 +566,11 @@ async function createBaseline(cwd, repoDir, components, componentPaths, vars, ve
|
|
|
511
566
|
removeWorktree(cwd, worktree);
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
|
-
async function updateBaseline(cwd, repoDir, components, componentPaths, vars, version) {
|
|
569
|
+
async function updateBaseline(cwd, repoDir, components, componentPaths, vars, version, componentSkips) {
|
|
515
570
|
const worktree = createWorktree(cwd, BASELINE_BRANCH, false);
|
|
516
571
|
try {
|
|
517
572
|
execSync2("git rm -rf .", { cwd: worktree, stdio: "pipe" });
|
|
518
|
-
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, "scaffold");
|
|
573
|
+
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, "scaffold", componentSkips);
|
|
519
574
|
execSync2("git add -A", { cwd: worktree, stdio: "pipe" });
|
|
520
575
|
const diff = execSync2("git diff --cached --stat", { cwd: worktree, stdio: "pipe" }).toString().trim();
|
|
521
576
|
if (!diff) {
|
|
@@ -571,9 +626,9 @@ function mergeBaseline(cwd, message, allowUnrelated = false, oursOnConflict = fa
|
|
|
571
626
|
};
|
|
572
627
|
}
|
|
573
628
|
}
|
|
574
|
-
async function reconstructBaseline(cwd, repoDir, components, componentPaths, vars, version) {
|
|
629
|
+
async function reconstructBaseline(cwd, repoDir, components, componentPaths, vars, version, componentSkips) {
|
|
575
630
|
p2.log.warn("projx/baseline branch not found. Reconstructing...");
|
|
576
|
-
await createBaseline(cwd, repoDir, components, componentPaths, vars, version);
|
|
631
|
+
await createBaseline(cwd, repoDir, components, componentPaths, vars, version, "scaffold", componentSkips);
|
|
577
632
|
mergeBaseline(
|
|
578
633
|
cwd,
|
|
579
634
|
`projx: reconstructed baseline for template v${version}`,
|
|
@@ -767,15 +822,25 @@ async function update(cwd, localRepo) {
|
|
|
767
822
|
const version = pkg.version;
|
|
768
823
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
769
824
|
const vars = { projectName: name, components: config.components, paths: componentPaths };
|
|
825
|
+
const componentSkips = {};
|
|
826
|
+
for (const component of config.components) {
|
|
827
|
+
const dir = componentPaths[component];
|
|
828
|
+
const marker = await readComponentMarker(join5(cwd, dir));
|
|
829
|
+
if (marker?.skip && marker.skip.length > 0) {
|
|
830
|
+
componentSkips[component] = marker.skip;
|
|
831
|
+
} else if (marker?.origin === "init") {
|
|
832
|
+
componentSkips[component] = ["**"];
|
|
833
|
+
}
|
|
834
|
+
}
|
|
770
835
|
if (!hasBaseline(cwd)) {
|
|
771
836
|
const rebuildSpinner = p4.spinner();
|
|
772
837
|
rebuildSpinner.start("Establishing baseline (first-time migration)");
|
|
773
|
-
await reconstructBaseline(cwd, repoDir, config.components, componentPaths, vars, config.version || version);
|
|
838
|
+
await reconstructBaseline(cwd, repoDir, config.components, componentPaths, vars, config.version || version, componentSkips);
|
|
774
839
|
rebuildSpinner.stop("Baseline established.");
|
|
775
840
|
}
|
|
776
841
|
const updateSpinner = p4.spinner();
|
|
777
842
|
updateSpinner.start("Updating baseline to latest template");
|
|
778
|
-
const { changed } = await updateBaseline(cwd, repoDir, config.components, componentPaths, vars, version);
|
|
843
|
+
const { changed } = await updateBaseline(cwd, repoDir, config.components, componentPaths, vars, version, componentSkips);
|
|
779
844
|
if (!changed) {
|
|
780
845
|
updateSpinner.stop("Already up to date.");
|
|
781
846
|
p4.outro("No template changes to apply.");
|
|
@@ -1140,9 +1205,13 @@ async function init(cwd, localRepo) {
|
|
|
1140
1205
|
try {
|
|
1141
1206
|
const pkg = JSON.parse(await readFile6(join8(repoDir, "cli/package.json"), "utf-8"));
|
|
1142
1207
|
const version = pkg.version;
|
|
1208
|
+
const componentSkips = {};
|
|
1209
|
+
for (const c of components) {
|
|
1210
|
+
componentSkips[c] = ["**"];
|
|
1211
|
+
}
|
|
1143
1212
|
const baselineSpinner = p6.spinner();
|
|
1144
1213
|
baselineSpinner.start("Creating template baseline");
|
|
1145
|
-
await createBaseline(cwd, repoDir, components, paths, vars, version, "init");
|
|
1214
|
+
await createBaseline(cwd, repoDir, components, paths, vars, version, "init", componentSkips);
|
|
1146
1215
|
baselineSpinner.stop("Baseline created.");
|
|
1147
1216
|
const mergeSpinner = p6.spinner();
|
|
1148
1217
|
mergeSpinner.start("Merging baseline (preserving your code)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Scaffold production-grade fullstack projects in seconds. FastAPI, Fastify, React, Flutter, Terraform — with auth, database, CI/CD, E2E tests, and Docker. One command, ready to deploy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|