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.
Files changed (2) hide show
  1. package/dist/index.js +85 -16
  2. 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 writeComponentMarker(dir, component, origin = "scaffold") {
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
- await writeFile(
207
- markerPath,
208
- JSON.stringify({ components, origin: existingOrigin }, null, 2) + "\n"
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
- async function writeTemplateToDir(dest, repoDir, components, componentPaths, vars, version, origin) {
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
- await writeComponentMarker(join3(dest, targetDir), component, origin);
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.0",
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": {