create-projx 1.3.4 → 1.3.5
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 -12
- package/dist/index.js +196 -317
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,24 +87,30 @@ cd my-app
|
|
|
87
87
|
npx create-projx@latest update
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- **Files only the template changed** — auto-merged, no action needed
|
|
93
|
-
- **Files only you changed** — preserved, untouched
|
|
94
|
-
- **Files both sides changed** — git conflict, you resolve
|
|
90
|
+
If the template merges cleanly with your code, it's auto-committed — done. If there are differences, template files are written directly and you review with `git diff`:
|
|
95
91
|
|
|
96
92
|
```bash
|
|
97
|
-
#
|
|
98
|
-
git
|
|
99
|
-
|
|
100
|
-
git add . && git commit # finish the merge
|
|
101
|
-
|
|
102
|
-
# Or abort:
|
|
103
|
-
git merge --abort
|
|
93
|
+
git diff # see what changed
|
|
94
|
+
git checkout -- path/to/file # discard a change you don't want
|
|
95
|
+
git add . && git commit -m "projx: update to vX.X.X" # commit when ready
|
|
104
96
|
```
|
|
105
97
|
|
|
106
98
|
Your custom files (controllers, pages, middleware) are never deleted. Files you created that don't exist in the template are always preserved.
|
|
107
99
|
|
|
100
|
+
### Skip Files
|
|
101
|
+
|
|
102
|
+
If a file keeps getting overwritten on every update, add it to `.projx-component`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"components": ["fastapi"],
|
|
107
|
+
"origin": "init",
|
|
108
|
+
"skip": ["src/**", "tests/**"]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Skipped files are excluded from template updates. Tooling files (Dockerfile, eslint, tsconfig) still get updated.
|
|
113
|
+
|
|
108
114
|
## Options
|
|
109
115
|
|
|
110
116
|
```
|
package/dist/index.js
CHANGED
|
@@ -130,11 +130,6 @@ async function copyStaticFiles(repoDir, dest) {
|
|
|
130
130
|
manifest.push(file);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
const gitignore = join(tpl, ".gitignore");
|
|
134
|
-
if (existsSync(gitignore)) {
|
|
135
|
-
await cp(gitignore, join(dest, ".gitignore"));
|
|
136
|
-
manifest.push(".gitignore");
|
|
137
|
-
}
|
|
138
133
|
const extensionsJson = join(tpl, ".vscode/extensions.json");
|
|
139
134
|
if (existsSync(extensionsJson)) {
|
|
140
135
|
await mkdir(join(dest, ".vscode"), { recursive: true });
|
|
@@ -282,8 +277,8 @@ function render(template, vars) {
|
|
|
282
277
|
(_, expr) => {
|
|
283
278
|
const parts = expr.split(".");
|
|
284
279
|
let val = vars;
|
|
285
|
-
for (const
|
|
286
|
-
val = val?.[
|
|
280
|
+
for (const p6 of parts) {
|
|
281
|
+
val = val?.[p6];
|
|
287
282
|
}
|
|
288
283
|
return String(val ?? "");
|
|
289
284
|
}
|
|
@@ -337,7 +332,7 @@ async function runPrompts(nameArg) {
|
|
|
337
332
|
import { copyFileSync, existsSync as existsSync3 } from "fs";
|
|
338
333
|
import { mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
|
|
339
334
|
import { join as join4 } from "path";
|
|
340
|
-
import * as
|
|
335
|
+
import * as p2 from "@clack/prompts";
|
|
341
336
|
|
|
342
337
|
// src/baseline.ts
|
|
343
338
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -345,7 +340,6 @@ import { chmod, mkdir as mkdir2, writeFile as writeFile2, rm as rm2 } from "fs/p
|
|
|
345
340
|
import { execSync as execSync2 } from "child_process";
|
|
346
341
|
import { join as join3 } from "path";
|
|
347
342
|
import { tmpdir as tmpdir2 } from "os";
|
|
348
|
-
import * as p2 from "@clack/prompts";
|
|
349
343
|
|
|
350
344
|
// src/generators/index.ts
|
|
351
345
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -407,47 +401,6 @@ function generateVscodeSettings(vars) {
|
|
|
407
401
|
}
|
|
408
402
|
|
|
409
403
|
// src/baseline.ts
|
|
410
|
-
var BASELINE_BRANCH = "projx/baseline";
|
|
411
|
-
function hasBaseline(cwd) {
|
|
412
|
-
try {
|
|
413
|
-
execSync2(`git show-ref --verify --quiet refs/heads/${BASELINE_BRANCH}`, {
|
|
414
|
-
cwd,
|
|
415
|
-
stdio: "pipe"
|
|
416
|
-
});
|
|
417
|
-
return true;
|
|
418
|
-
} catch {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function createWorktree(cwd, branch, orphan) {
|
|
423
|
-
const worktree = join3(tmpdir2(), `projx-baseline-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
424
|
-
if (orphan) {
|
|
425
|
-
execSync2(`git worktree add --orphan -b ${branch} "${worktree}"`, {
|
|
426
|
-
cwd,
|
|
427
|
-
stdio: "pipe"
|
|
428
|
-
});
|
|
429
|
-
} else {
|
|
430
|
-
execSync2(`git worktree add "${worktree}" ${branch}`, {
|
|
431
|
-
cwd,
|
|
432
|
-
stdio: "pipe"
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
return worktree;
|
|
436
|
-
}
|
|
437
|
-
function removeWorktree(cwd, worktree) {
|
|
438
|
-
try {
|
|
439
|
-
execSync2(`git worktree remove "${worktree}" --force`, {
|
|
440
|
-
cwd,
|
|
441
|
-
stdio: "pipe"
|
|
442
|
-
});
|
|
443
|
-
} catch {
|
|
444
|
-
try {
|
|
445
|
-
rm2(worktree, { recursive: true, force: true });
|
|
446
|
-
execSync2("git worktree prune", { cwd, stdio: "pipe" });
|
|
447
|
-
} catch {
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
404
|
function matchesSkip(filePath, patterns) {
|
|
452
405
|
for (const pattern of patterns) {
|
|
453
406
|
if (pattern === "**") return true;
|
|
@@ -472,6 +425,35 @@ function matchesSkip(filePath, patterns) {
|
|
|
472
425
|
}
|
|
473
426
|
return false;
|
|
474
427
|
}
|
|
428
|
+
function createOrphanWorktree(cwd) {
|
|
429
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
430
|
+
const branch = `projx/tmp-${id}`;
|
|
431
|
+
const worktree = join3(tmpdir2(), `projx-wt-${id}`);
|
|
432
|
+
try {
|
|
433
|
+
execSync2("git worktree prune", { cwd, stdio: "pipe" });
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
execSync2(`git worktree add --orphan -b ${branch} "${worktree}"`, {
|
|
437
|
+
cwd,
|
|
438
|
+
stdio: "pipe"
|
|
439
|
+
});
|
|
440
|
+
return { worktree, branch };
|
|
441
|
+
}
|
|
442
|
+
function cleanupWorktree(cwd, worktree, branch) {
|
|
443
|
+
try {
|
|
444
|
+
execSync2(`git worktree remove "${worktree}" --force`, { cwd, stdio: "pipe" });
|
|
445
|
+
} catch {
|
|
446
|
+
try {
|
|
447
|
+
rm2(worktree, { recursive: true, force: true });
|
|
448
|
+
execSync2("git worktree prune", { cwd, stdio: "pipe" });
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
execSync2(`git branch -D ${branch}`, { cwd, stdio: "pipe" });
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
475
457
|
async function removeSkippedFiles(dir, skipPatterns) {
|
|
476
458
|
if (skipPatterns.length === 0) return;
|
|
477
459
|
const { readdir: readdir3, unlink } = await import("fs/promises");
|
|
@@ -494,22 +476,20 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
|
|
|
494
476
|
const nameSnake = toSnake(name);
|
|
495
477
|
for (const component of components) {
|
|
496
478
|
const targetDir = componentPaths[component];
|
|
497
|
-
if (targetDir === component) {
|
|
498
|
-
await copyComponent(repoDir, component, dest);
|
|
499
|
-
} else {
|
|
500
|
-
await copyComponent(repoDir, component, join3(dest, "__tmp__"));
|
|
501
|
-
const { cp: cp2 } = await import("fs/promises");
|
|
502
|
-
const srcDir = join3(dest, "__tmp__", component);
|
|
503
|
-
const outDir = join3(dest, targetDir);
|
|
504
|
-
if (existsSync2(srcDir)) {
|
|
505
|
-
await cp2(srcDir, outDir, { recursive: true, force: true });
|
|
506
|
-
}
|
|
507
|
-
await rm2(join3(dest, "__tmp__"), { recursive: true, force: true });
|
|
508
|
-
}
|
|
509
479
|
const skipPatterns = componentSkips?.[component] ?? [];
|
|
480
|
+
const tmpDir = join3(dest, "__cptmp__");
|
|
481
|
+
await copyComponent(repoDir, component, tmpDir);
|
|
482
|
+
const srcDir = join3(tmpDir, component);
|
|
510
483
|
if (skipPatterns.length > 0) {
|
|
511
|
-
await removeSkippedFiles(
|
|
484
|
+
await removeSkippedFiles(srcDir, skipPatterns);
|
|
485
|
+
}
|
|
486
|
+
const outDir = join3(dest, targetDir);
|
|
487
|
+
await mkdir2(outDir, { recursive: true });
|
|
488
|
+
const { cp: cp2 } = await import("fs/promises");
|
|
489
|
+
if (existsSync2(srcDir)) {
|
|
490
|
+
await cp2(srcDir, outDir, { recursive: true, force: true });
|
|
512
491
|
}
|
|
492
|
+
await rm2(tmpDir, { recursive: true, force: true });
|
|
513
493
|
await writeComponentMarker(join3(dest, targetDir), component, origin, skipPatterns.length > 0 ? skipPatterns : void 0);
|
|
514
494
|
}
|
|
515
495
|
await substituteNames(dest, components, componentPaths, name, nameSnake);
|
|
@@ -532,11 +512,7 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
|
|
|
532
512
|
const projxConfig = {
|
|
533
513
|
version,
|
|
534
514
|
components,
|
|
535
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
536
|
-
baseline: {
|
|
537
|
-
branch: BASELINE_BRANCH,
|
|
538
|
-
templateVersion: version
|
|
539
|
-
}
|
|
515
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
540
516
|
};
|
|
541
517
|
await writeFile2(join3(dest, ".projx"), JSON.stringify(projxConfig, null, 2) + "\n");
|
|
542
518
|
}
|
|
@@ -558,90 +534,68 @@ async function substituteNames(dest, components, paths, name, nameSnake) {
|
|
|
558
534
|
await replaceInDir(join3(dest, `${paths.mobile}`), "package:projx_mobile/", `package:${nameSnake}_mobile/`, ".dart");
|
|
559
535
|
}
|
|
560
536
|
}
|
|
561
|
-
async function
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
537
|
+
async function applyTemplate(cwd, repoDir, components, componentPaths, vars, version, origin = "scaffold", componentSkips) {
|
|
538
|
+
const hasHead = (() => {
|
|
539
|
+
try {
|
|
540
|
+
execSync2("git rev-parse HEAD", { cwd, stdio: "pipe" });
|
|
541
|
+
return true;
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
})();
|
|
546
|
+
if (!hasHead) {
|
|
547
|
+
await writeTemplateToDir(cwd, repoDir, components, componentPaths, vars, version, origin, componentSkips);
|
|
548
|
+
return { status: "clean" };
|
|
572
549
|
}
|
|
573
|
-
}
|
|
574
|
-
async function updateBaseline(cwd, repoDir, components, componentPaths, vars, version, componentSkips) {
|
|
575
|
-
const worktree = createWorktree(cwd, BASELINE_BRANCH, false);
|
|
550
|
+
const { worktree, branch } = createOrphanWorktree(cwd);
|
|
576
551
|
try {
|
|
577
|
-
|
|
578
|
-
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, "scaffold", componentSkips);
|
|
552
|
+
await writeTemplateToDir(worktree, repoDir, components, componentPaths, vars, version, origin, componentSkips);
|
|
579
553
|
execSync2("git add -A", { cwd: worktree, stdio: "pipe" });
|
|
580
554
|
const diff = execSync2("git diff --cached --stat", { cwd: worktree, stdio: "pipe" }).toString().trim();
|
|
581
555
|
if (!diff) {
|
|
582
|
-
|
|
556
|
+
cleanupWorktree(cwd, worktree, branch);
|
|
557
|
+
return { status: "clean" };
|
|
583
558
|
}
|
|
584
559
|
execSync2(
|
|
585
|
-
`git commit --no-verify -m "projx:
|
|
560
|
+
`git commit --no-verify -m "projx: template v${version} [${components.join(", ")}]"`,
|
|
586
561
|
{ cwd: worktree, stdio: "pipe" }
|
|
587
562
|
);
|
|
588
|
-
return { changed: true };
|
|
589
|
-
} finally {
|
|
590
|
-
removeWorktree(cwd, worktree);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
async function addToBaseline(cwd, repoDir, newComponents, allComponents, componentPaths, vars, version) {
|
|
594
|
-
const worktree = createWorktree(cwd, BASELINE_BRANCH, false);
|
|
595
|
-
try {
|
|
596
|
-
await writeTemplateToDir(worktree, repoDir, allComponents, componentPaths, vars, version, "scaffold");
|
|
597
|
-
execSync2("git add -A", { cwd: worktree, stdio: "pipe" });
|
|
598
|
-
execSync2(
|
|
599
|
-
`git commit --no-verify -m "projx: add ${newComponents.join(", ")} template v${version}"`,
|
|
600
|
-
{ cwd: worktree, stdio: "pipe" }
|
|
601
|
-
);
|
|
602
|
-
} finally {
|
|
603
|
-
removeWorktree(cwd, worktree);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
function mergeBaseline(cwd, message, allowUnrelated = false, oursOnConflict = false) {
|
|
607
|
-
const args2 = [`git merge ${BASELINE_BRANCH}`];
|
|
608
|
-
args2.push(`-m "${message}"`);
|
|
609
|
-
if (allowUnrelated) args2.push("--allow-unrelated-histories");
|
|
610
|
-
if (oursOnConflict) {
|
|
611
563
|
try {
|
|
612
|
-
execSync2(
|
|
564
|
+
execSync2(`git worktree remove "${worktree}" --force`, { cwd, stdio: "pipe" });
|
|
613
565
|
} catch {
|
|
566
|
+
try {
|
|
567
|
+
await rm2(worktree, { recursive: true, force: true });
|
|
568
|
+
execSync2("git worktree prune", { cwd, stdio: "pipe" });
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
614
571
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
572
|
+
let mergeClean = false;
|
|
573
|
+
try {
|
|
574
|
+
execSync2(
|
|
575
|
+
`git merge ${branch} --allow-unrelated-histories -m "projx: update to template v${version}"`,
|
|
576
|
+
{ cwd, stdio: "pipe" }
|
|
577
|
+
);
|
|
578
|
+
mergeClean = true;
|
|
579
|
+
} catch {
|
|
580
|
+
try {
|
|
581
|
+
execSync2("git merge --abort", { cwd, stdio: "pipe" });
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
execSync2(`git branch -D ${branch}`, { cwd, stdio: "pipe" });
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
if (mergeClean) {
|
|
626
590
|
return { status: "clean" };
|
|
627
591
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
592
|
+
await writeTemplateToDir(cwd, repoDir, components, componentPaths, vars, version, origin, componentSkips);
|
|
593
|
+
return { status: "conflicts" };
|
|
594
|
+
} catch (err) {
|
|
595
|
+
cleanupWorktree(cwd, worktree, branch);
|
|
596
|
+
throw err;
|
|
632
597
|
}
|
|
633
598
|
}
|
|
634
|
-
async function reconstructBaseline(cwd, repoDir, components, componentPaths, vars, version, componentSkips) {
|
|
635
|
-
p2.log.warn("projx/baseline branch not found. Reconstructing...");
|
|
636
|
-
await createBaseline(cwd, repoDir, components, componentPaths, vars, version, "scaffold", componentSkips);
|
|
637
|
-
mergeBaseline(
|
|
638
|
-
cwd,
|
|
639
|
-
`projx: reconstructed baseline for template v${version}`,
|
|
640
|
-
true,
|
|
641
|
-
true
|
|
642
|
-
);
|
|
643
|
-
p2.log.success("Baseline reconstructed.");
|
|
644
|
-
}
|
|
645
599
|
|
|
646
600
|
// src/scaffold.ts
|
|
647
601
|
async function scaffold(opts, dest, localRepo) {
|
|
@@ -652,39 +606,26 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
652
606
|
const vars = { projectName: name, components: opts.components, paths };
|
|
653
607
|
const isLocal = !!localRepo;
|
|
654
608
|
await mkdir3(dest, { recursive: true });
|
|
655
|
-
const dlSpinner =
|
|
609
|
+
const dlSpinner = p2.spinner();
|
|
656
610
|
dlSpinner.start(isLocal ? "Using local templates" : "Downloading latest templates");
|
|
657
611
|
const repoDir = await downloadRepo(localRepo).catch((err) => {
|
|
658
612
|
dlSpinner.stop("Failed.");
|
|
659
|
-
|
|
613
|
+
p2.log.error(String(err));
|
|
660
614
|
process.exit(1);
|
|
661
615
|
});
|
|
662
616
|
dlSpinner.stop(isLocal ? "Local templates loaded." : "Templates downloaded.");
|
|
663
617
|
try {
|
|
664
618
|
const pkg = JSON.parse(await readFile3(join4(repoDir, "cli/package.json"), "utf-8"));
|
|
665
619
|
const version = pkg.version;
|
|
666
|
-
|
|
620
|
+
p2.log.info(`Scaffolding project in ${dest}`);
|
|
667
621
|
if (opts.git) {
|
|
668
622
|
exec("git init", dest);
|
|
669
623
|
exec("git config core.hooksPath .githooks", dest);
|
|
670
|
-
const spinner5 = p3.spinner();
|
|
671
|
-
spinner5.start("Creating baseline and scaffold");
|
|
672
|
-
await createBaseline(dest, repoDir, opts.components, paths, vars, version);
|
|
673
|
-
const result = mergeBaseline(
|
|
674
|
-
dest,
|
|
675
|
-
`projx: initial scaffold from template v${version}`,
|
|
676
|
-
true
|
|
677
|
-
);
|
|
678
|
-
spinner5.stop("Scaffold complete.");
|
|
679
|
-
if (result.status === "conflicts") {
|
|
680
|
-
p3.log.warn("Unexpected conflicts during scaffold \u2014 this shouldn't happen.");
|
|
681
|
-
}
|
|
682
|
-
} else {
|
|
683
|
-
const spinner5 = p3.spinner();
|
|
684
|
-
spinner5.start("Copying template files");
|
|
685
|
-
await createBaseline(dest, repoDir, opts.components, paths, vars, version);
|
|
686
|
-
spinner5.stop("Template files copied.");
|
|
687
624
|
}
|
|
625
|
+
const spinner5 = p2.spinner();
|
|
626
|
+
spinner5.start("Scaffolding project");
|
|
627
|
+
await applyTemplate(dest, repoDir, opts.components, paths, vars, version);
|
|
628
|
+
spinner5.stop("Scaffold complete.");
|
|
688
629
|
if (opts.install) {
|
|
689
630
|
await installDeps(dest, opts.components);
|
|
690
631
|
}
|
|
@@ -699,7 +640,7 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
699
640
|
} finally {
|
|
700
641
|
await cleanupRepo(repoDir, isLocal);
|
|
701
642
|
}
|
|
702
|
-
|
|
643
|
+
p2.outro(`Done! Next steps:
|
|
703
644
|
|
|
704
645
|
cd ${name}
|
|
705
646
|
./setup.sh
|
|
@@ -708,7 +649,7 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
708
649
|
}
|
|
709
650
|
async function installDeps(dest, components) {
|
|
710
651
|
for (const component of components) {
|
|
711
|
-
const spinner5 =
|
|
652
|
+
const spinner5 = p2.spinner();
|
|
712
653
|
try {
|
|
713
654
|
switch (component) {
|
|
714
655
|
case "fastapi":
|
|
@@ -717,7 +658,7 @@ async function installDeps(dest, components) {
|
|
|
717
658
|
exec("uv sync --all-extras", join4(dest, "fastapi"));
|
|
718
659
|
spinner5.stop("FastAPI dependencies installed.");
|
|
719
660
|
} else {
|
|
720
|
-
|
|
661
|
+
p2.log.warn("uv not found \u2014 run 'cd fastapi && uv sync' manually.");
|
|
721
662
|
}
|
|
722
663
|
break;
|
|
723
664
|
case "fastify":
|
|
@@ -747,7 +688,7 @@ async function installDeps(dest, components) {
|
|
|
747
688
|
exec("flutter pub get", join4(dest, "mobile"));
|
|
748
689
|
spinner5.stop("Flutter dependencies installed.");
|
|
749
690
|
} else {
|
|
750
|
-
|
|
691
|
+
p2.log.warn("Flutter not found \u2014 run 'cd mobile && flutter pub get' manually.");
|
|
751
692
|
}
|
|
752
693
|
break;
|
|
753
694
|
case "infra":
|
|
@@ -776,49 +717,55 @@ import { existsSync as existsSync4, readFileSync } from "fs";
|
|
|
776
717
|
import { readFile as readFile4 } from "fs/promises";
|
|
777
718
|
import { execSync as execSync3 } from "child_process";
|
|
778
719
|
import { join as join5 } from "path";
|
|
779
|
-
import * as
|
|
720
|
+
import * as p3 from "@clack/prompts";
|
|
780
721
|
async function update(cwd, localRepo) {
|
|
781
|
-
|
|
722
|
+
p3.intro("projx update");
|
|
782
723
|
const isLocal = !!localRepo;
|
|
783
724
|
if (!isGitRepo(cwd)) {
|
|
784
|
-
|
|
725
|
+
p3.log.error("projx update requires a git repo.");
|
|
785
726
|
process.exit(1);
|
|
786
727
|
}
|
|
728
|
+
try {
|
|
729
|
+
execSync3("git worktree prune", { cwd, stdio: "pipe" });
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
787
732
|
if (hasUncommittedChanges(cwd)) {
|
|
788
|
-
|
|
733
|
+
p3.log.error("You have uncommitted changes. Commit or stash them first.");
|
|
789
734
|
process.exit(1);
|
|
790
735
|
}
|
|
791
736
|
const configPath = join5(cwd, ".projx");
|
|
792
737
|
let config;
|
|
793
738
|
if (existsSync4(configPath)) {
|
|
794
739
|
config = JSON.parse(await readFile4(configPath, "utf-8"));
|
|
795
|
-
|
|
740
|
+
p3.log.info(`Found .projx (v${config.version}, components: ${config.components.join(", ")})`);
|
|
796
741
|
} else {
|
|
797
|
-
|
|
742
|
+
p3.log.warn("No .projx file found. Detecting components from directories.");
|
|
798
743
|
const detected = COMPONENTS.filter((c) => existsSync4(join5(cwd, c)));
|
|
799
744
|
if (detected.length === 0) {
|
|
800
|
-
|
|
745
|
+
p3.log.error("No projx components found. Run 'projx init' first.");
|
|
801
746
|
process.exit(1);
|
|
802
747
|
}
|
|
803
|
-
config = {
|
|
804
|
-
|
|
805
|
-
components: detected,
|
|
806
|
-
createdAt: "unknown"
|
|
807
|
-
};
|
|
808
|
-
p4.log.info(`Detected: ${detected.join(", ")}`);
|
|
748
|
+
config = { version: "0.0.0", components: detected, createdAt: "unknown" };
|
|
749
|
+
p3.log.info(`Detected: ${detected.join(", ")}`);
|
|
809
750
|
}
|
|
810
751
|
const componentPaths = await discoverComponentPaths(cwd, config.components);
|
|
811
752
|
const remapped = config.components.filter((c) => componentPaths[c] !== c);
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
753
|
+
for (const c of remapped) {
|
|
754
|
+
p3.log.info(`${c} \u2192 ${componentPaths[c]}/`);
|
|
755
|
+
}
|
|
756
|
+
const componentSkips = {};
|
|
757
|
+
for (const component of config.components) {
|
|
758
|
+
const dir = componentPaths[component];
|
|
759
|
+
const marker = await readComponentMarker(join5(cwd, dir));
|
|
760
|
+
if (marker?.skip && marker.skip.length > 0) {
|
|
761
|
+
componentSkips[component] = marker.skip;
|
|
815
762
|
}
|
|
816
763
|
}
|
|
817
|
-
const dlSpinner =
|
|
764
|
+
const dlSpinner = p3.spinner();
|
|
818
765
|
dlSpinner.start(isLocal ? "Using local templates" : "Downloading latest templates");
|
|
819
766
|
const repoDir = await downloadRepo(localRepo).catch((err) => {
|
|
820
767
|
dlSpinner.stop("Failed.");
|
|
821
|
-
|
|
768
|
+
p3.log.error(String(err));
|
|
822
769
|
process.exit(1);
|
|
823
770
|
});
|
|
824
771
|
dlSpinner.stop(isLocal ? "Local templates loaded." : "Templates downloaded.");
|
|
@@ -827,73 +774,27 @@ async function update(cwd, localRepo) {
|
|
|
827
774
|
const version = pkg.version;
|
|
828
775
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
829
776
|
const vars = { projectName: name, components: config.components, paths: componentPaths };
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (marker?.skip && marker.skip.length > 0) {
|
|
835
|
-
componentSkips[component] = marker.skip;
|
|
836
|
-
} else if (marker?.origin === "init" || !marker?.origin) {
|
|
837
|
-
componentSkips[component] = ["**"];
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
if (!hasBaseline(cwd)) {
|
|
841
|
-
const rebuildSpinner = p4.spinner();
|
|
842
|
-
rebuildSpinner.start("Establishing baseline (first-time migration)");
|
|
843
|
-
await reconstructBaseline(cwd, repoDir, config.components, componentPaths, vars, config.version || version, componentSkips);
|
|
844
|
-
rebuildSpinner.stop("Baseline established.");
|
|
845
|
-
}
|
|
846
|
-
const updateSpinner = p4.spinner();
|
|
847
|
-
updateSpinner.start("Updating baseline to latest template");
|
|
848
|
-
const { changed } = await updateBaseline(cwd, repoDir, config.components, componentPaths, vars, version, componentSkips);
|
|
849
|
-
if (!changed) {
|
|
850
|
-
updateSpinner.stop("Already up to date.");
|
|
851
|
-
p4.outro("No template changes to apply.");
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
updateSpinner.stop("Baseline updated.");
|
|
855
|
-
const mergeSpinner = p4.spinner();
|
|
856
|
-
mergeSpinner.start("Merging template changes");
|
|
857
|
-
const result = mergeBaseline(cwd, `projx: update to template v${version}`);
|
|
858
|
-
mergeSpinner.stop("Merge complete.");
|
|
859
|
-
if (result.status === "clean") {
|
|
860
|
-
const { writeFile: writeFile3 } = await import("fs/promises");
|
|
861
|
-
const updatedConfig = {
|
|
862
|
-
...config,
|
|
863
|
-
version,
|
|
864
|
-
baseline: { branch: "projx/baseline", templateVersion: version }
|
|
865
|
-
};
|
|
866
|
-
await writeFile3(join5(cwd, ".projx"), JSON.stringify(updatedConfig, null, 2) + "\n");
|
|
867
|
-
for (const component of config.components) {
|
|
868
|
-
const dir = componentPaths[component];
|
|
869
|
-
const skip = componentSkips[component];
|
|
870
|
-
await writeComponentMarker(
|
|
871
|
-
join5(cwd, dir),
|
|
872
|
-
component,
|
|
873
|
-
skip?.includes("**") ? "init" : "scaffold",
|
|
874
|
-
skip
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
execSync3('git add -A && git commit --no-verify -m "projx: post-update config"', { cwd, stdio: "pipe" });
|
|
878
|
-
}
|
|
777
|
+
const spinner5 = p3.spinner();
|
|
778
|
+
spinner5.start("Applying template update");
|
|
779
|
+
const result = await applyTemplate(cwd, repoDir, config.components, componentPaths, vars, version, "scaffold", componentSkips);
|
|
780
|
+
spinner5.stop("Template applied.");
|
|
879
781
|
if (result.status === "conflicts") {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
);
|
|
782
|
+
p3.log.warn("Some template files differ from your code. Changes written directly.");
|
|
783
|
+
p3.log.info("Review changes:");
|
|
784
|
+
p3.log.info(" git diff");
|
|
785
|
+
p3.log.info("");
|
|
786
|
+
p3.log.info("Keep a change: git add <file>");
|
|
787
|
+
p3.log.info("Discard a change: git checkout -- <file>");
|
|
788
|
+
p3.log.info('Commit when ready: git add . && git commit -m "projx: update to v' + version + '"');
|
|
789
|
+
p3.log.info("");
|
|
790
|
+
p3.log.info("To skip files on future updates, add to .projx-component:");
|
|
791
|
+
p3.log.info(' { "skip": ["src/**", "tests/**"] }');
|
|
792
|
+
p3.outro(`Template v${version} applied. Review with git diff.`);
|
|
887
793
|
} else {
|
|
888
|
-
|
|
794
|
+
p3.outro(`Updated to template v${version}.`);
|
|
889
795
|
}
|
|
890
796
|
} catch (err) {
|
|
891
|
-
|
|
892
|
-
execSync3("git merge --abort", { cwd, stdio: "pipe" });
|
|
893
|
-
} catch {
|
|
894
|
-
}
|
|
895
|
-
p4.log.error(`Update failed: ${err}`);
|
|
896
|
-
p4.log.info("Your code is safe. Run 'git merge --abort' if needed.");
|
|
797
|
+
p3.log.error(`Update failed: ${err}`);
|
|
897
798
|
process.exit(1);
|
|
898
799
|
} finally {
|
|
899
800
|
await cleanupRepo(repoDir, isLocal);
|
|
@@ -937,32 +838,32 @@ function detectProjectName(cwd, components, componentPaths) {
|
|
|
937
838
|
import { copyFileSync as copyFileSync2, existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
938
839
|
import { readFile as readFile5 } from "fs/promises";
|
|
939
840
|
import { join as join6 } from "path";
|
|
940
|
-
import * as
|
|
841
|
+
import * as p4 from "@clack/prompts";
|
|
941
842
|
async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
942
|
-
|
|
843
|
+
p4.intro("projx add");
|
|
943
844
|
const isLocal = !!localRepo;
|
|
944
845
|
const configPath = join6(cwd, ".projx");
|
|
945
846
|
if (!existsSync5(configPath)) {
|
|
946
|
-
|
|
847
|
+
p4.log.error("No .projx file found. Run 'npx create-projx <name>' to create a project first.");
|
|
947
848
|
process.exit(1);
|
|
948
849
|
}
|
|
949
850
|
const config = JSON.parse(await readFile5(configPath, "utf-8"));
|
|
950
851
|
const existing = config.components;
|
|
951
852
|
const alreadyExists = newComponents.filter((c) => existing.includes(c));
|
|
952
853
|
if (alreadyExists.length > 0) {
|
|
953
|
-
|
|
854
|
+
p4.log.warn(`Already present: ${alreadyExists.join(", ")}. Skipping those.`);
|
|
954
855
|
}
|
|
955
856
|
const toAdd = newComponents.filter((c) => !existing.includes(c));
|
|
956
857
|
if (toAdd.length === 0) {
|
|
957
|
-
|
|
858
|
+
p4.log.info("Nothing new to add.");
|
|
958
859
|
process.exit(0);
|
|
959
860
|
}
|
|
960
|
-
|
|
961
|
-
const dlSpinner =
|
|
861
|
+
p4.log.info(`Adding: ${toAdd.join(", ")}`);
|
|
862
|
+
const dlSpinner = p4.spinner();
|
|
962
863
|
dlSpinner.start(isLocal ? "Using local templates" : "Downloading latest templates");
|
|
963
864
|
const repoDir = await downloadRepo(localRepo).catch((err) => {
|
|
964
865
|
dlSpinner.stop("Failed.");
|
|
965
|
-
|
|
866
|
+
p4.log.error(String(err));
|
|
966
867
|
process.exit(1);
|
|
967
868
|
});
|
|
968
869
|
dlSpinner.stop(isLocal ? "Local templates loaded." : "Templates downloaded.");
|
|
@@ -975,31 +876,10 @@ async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
|
975
876
|
const vars = { projectName: name, components: allComponents, paths };
|
|
976
877
|
const pkg = JSON.parse(await readFile5(join6(repoDir, "cli/package.json"), "utf-8"));
|
|
977
878
|
const version = pkg.version;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
cwd,
|
|
983
|
-
repoDir,
|
|
984
|
-
existing,
|
|
985
|
-
existingPaths,
|
|
986
|
-
{ projectName: name, components: existing, paths: existingPaths },
|
|
987
|
-
config.version || version
|
|
988
|
-
);
|
|
989
|
-
rebuildSpinner.stop("Baseline established.");
|
|
990
|
-
}
|
|
991
|
-
const spinner5 = p5.spinner();
|
|
992
|
-
spinner5.start("Adding to baseline");
|
|
993
|
-
await addToBaseline(cwd, repoDir, toAdd, allComponents, paths, vars, version);
|
|
994
|
-
spinner5.stop("Baseline updated.");
|
|
995
|
-
const result = mergeBaseline(cwd, `projx: add ${toAdd.join(", ")} from template v${version}`);
|
|
996
|
-
if (result.status === "conflicts") {
|
|
997
|
-
p5.log.warn(`Merge conflicts in ${result.conflictedFiles.length} file(s):`);
|
|
998
|
-
for (const f of result.conflictedFiles) {
|
|
999
|
-
p5.log.message(` ${f}`);
|
|
1000
|
-
}
|
|
1001
|
-
p5.log.info("Resolve conflicts, then: git add . && git commit");
|
|
1002
|
-
}
|
|
879
|
+
const spinner5 = p4.spinner();
|
|
880
|
+
spinner5.start("Adding components");
|
|
881
|
+
await writeTemplateToDir(cwd, repoDir, allComponents, paths, vars, version, "scaffold");
|
|
882
|
+
spinner5.stop("Components added.");
|
|
1003
883
|
if (!skipInstall) {
|
|
1004
884
|
await installDeps2(cwd, toAdd);
|
|
1005
885
|
}
|
|
@@ -1013,16 +893,16 @@ async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
|
1013
893
|
}
|
|
1014
894
|
}
|
|
1015
895
|
}
|
|
896
|
+
p4.outro(`Added ${toAdd.join(", ")}.
|
|
897
|
+
|
|
898
|
+
Like projx? Star it: https://github.com/ukanhaupa/projx`);
|
|
1016
899
|
} finally {
|
|
1017
900
|
await cleanupRepo(repoDir, isLocal);
|
|
1018
901
|
}
|
|
1019
|
-
p5.outro(`Added ${toAdd.join(", ")}.
|
|
1020
|
-
|
|
1021
|
-
Like projx? Star it: https://github.com/ukanhaupa/projx`);
|
|
1022
902
|
}
|
|
1023
903
|
async function installDeps2(dest, components) {
|
|
1024
904
|
for (const component of components) {
|
|
1025
|
-
const spinner5 =
|
|
905
|
+
const spinner5 = p4.spinner();
|
|
1026
906
|
try {
|
|
1027
907
|
switch (component) {
|
|
1028
908
|
case "fastapi":
|
|
@@ -1031,7 +911,7 @@ async function installDeps2(dest, components) {
|
|
|
1031
911
|
exec("uv sync --all-extras", join6(dest, "fastapi"));
|
|
1032
912
|
spinner5.stop("FastAPI dependencies installed.");
|
|
1033
913
|
} else {
|
|
1034
|
-
|
|
914
|
+
p4.log.warn("uv not found \u2014 run 'cd fastapi && uv sync' manually.");
|
|
1035
915
|
}
|
|
1036
916
|
break;
|
|
1037
917
|
case "fastify":
|
|
@@ -1061,7 +941,7 @@ async function installDeps2(dest, components) {
|
|
|
1061
941
|
exec("flutter pub get", join6(dest, "mobile"));
|
|
1062
942
|
spinner5.stop("Flutter dependencies installed.");
|
|
1063
943
|
} else {
|
|
1064
|
-
|
|
944
|
+
p4.log.warn("Flutter not found \u2014 run 'cd mobile && flutter pub get' manually.");
|
|
1065
945
|
}
|
|
1066
946
|
break;
|
|
1067
947
|
case "infra":
|
|
@@ -1095,7 +975,7 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
1095
975
|
import { readFile as readFile6 } from "fs/promises";
|
|
1096
976
|
import { execSync as execSync4 } from "child_process";
|
|
1097
977
|
import { join as join8 } from "path";
|
|
1098
|
-
import * as
|
|
978
|
+
import * as p5 from "@clack/prompts";
|
|
1099
979
|
|
|
1100
980
|
// src/detect.ts
|
|
1101
981
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -1183,21 +1063,21 @@ async function readPkg(dir) {
|
|
|
1183
1063
|
|
|
1184
1064
|
// src/init.ts
|
|
1185
1065
|
async function init(cwd, localRepo) {
|
|
1186
|
-
|
|
1066
|
+
p5.intro("projx init");
|
|
1187
1067
|
const isLocal = !!localRepo;
|
|
1188
1068
|
if (existsSync7(join8(cwd, ".projx"))) {
|
|
1189
|
-
|
|
1069
|
+
p5.log.error("This project is already initialized. Use 'npx create-projx update' or 'npx create-projx add' instead.");
|
|
1190
1070
|
process.exit(1);
|
|
1191
1071
|
}
|
|
1192
1072
|
if (!isGitRepo2(cwd)) {
|
|
1193
|
-
|
|
1073
|
+
p5.log.error(`projx init requires a git repo. Run 'git init && git add -A && git commit -m "initial"' first.`);
|
|
1194
1074
|
process.exit(1);
|
|
1195
1075
|
}
|
|
1196
1076
|
if (hasUncommittedChanges2(cwd)) {
|
|
1197
|
-
|
|
1077
|
+
p5.log.error("You have uncommitted changes. Commit or stash them first.");
|
|
1198
1078
|
process.exit(1);
|
|
1199
1079
|
}
|
|
1200
|
-
const spinner5 =
|
|
1080
|
+
const spinner5 = p5.spinner();
|
|
1201
1081
|
spinner5.start("Scanning for components");
|
|
1202
1082
|
const detected = await detectComponents(cwd);
|
|
1203
1083
|
spinner5.stop(
|
|
@@ -1210,7 +1090,7 @@ async function init(cwd, localRepo) {
|
|
|
1210
1090
|
confirmed = await manualSelect(cwd);
|
|
1211
1091
|
}
|
|
1212
1092
|
if (confirmed.length === 0) {
|
|
1213
|
-
|
|
1093
|
+
p5.log.warn("No components selected. Nothing to do.");
|
|
1214
1094
|
process.exit(0);
|
|
1215
1095
|
}
|
|
1216
1096
|
const components = confirmed.map((c) => c.component);
|
|
@@ -1219,55 +1099,54 @@ async function init(cwd, localRepo) {
|
|
|
1219
1099
|
);
|
|
1220
1100
|
const projectName = toKebab(cwd.split("/").pop());
|
|
1221
1101
|
const vars = { projectName, components, paths };
|
|
1222
|
-
const dlSpinner =
|
|
1102
|
+
const dlSpinner = p5.spinner();
|
|
1223
1103
|
dlSpinner.start(isLocal ? "Using local templates" : "Downloading latest templates");
|
|
1224
1104
|
const repoDir = await downloadRepo(localRepo).catch((err) => {
|
|
1225
1105
|
dlSpinner.stop("Failed.");
|
|
1226
|
-
|
|
1106
|
+
p5.log.error(String(err));
|
|
1227
1107
|
process.exit(1);
|
|
1228
1108
|
});
|
|
1229
1109
|
dlSpinner.stop(isLocal ? "Local templates loaded." : "Templates downloaded.");
|
|
1230
1110
|
try {
|
|
1231
1111
|
const pkg = JSON.parse(await readFile6(join8(repoDir, "cli/package.json"), "utf-8"));
|
|
1232
1112
|
const version = pkg.version;
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
baselineSpinner.start("Creating template baseline");
|
|
1239
|
-
await createBaseline(cwd, repoDir, components, paths, vars, version, "init", componentSkips);
|
|
1240
|
-
baselineSpinner.stop("Baseline created.");
|
|
1241
|
-
const mergeSpinner = p6.spinner();
|
|
1242
|
-
mergeSpinner.start("Merging baseline (preserving your code)");
|
|
1243
|
-
mergeBaseline(
|
|
1244
|
-
cwd,
|
|
1245
|
-
`projx: adopt template v${version} as baseline`,
|
|
1246
|
-
true,
|
|
1247
|
-
true
|
|
1248
|
-
);
|
|
1249
|
-
mergeSpinner.stop("Baseline merged. Your code is preserved.");
|
|
1250
|
-
if (!existsSync7(join8(cwd, ".githooks"))) {
|
|
1113
|
+
const applySpinner = p5.spinner();
|
|
1114
|
+
applySpinner.start("Applying template");
|
|
1115
|
+
const result = await applyTemplate(cwd, repoDir, components, paths, vars, version, "init");
|
|
1116
|
+
applySpinner.stop("Template applied.");
|
|
1117
|
+
if (existsSync7(join8(cwd, ".githooks"))) {
|
|
1251
1118
|
try {
|
|
1252
1119
|
execSync4("git config core.hooksPath .githooks", { cwd, stdio: "pipe" });
|
|
1253
|
-
p6.log.success("Git hooks configured.");
|
|
1254
1120
|
} catch {
|
|
1255
|
-
p6.log.warn("Failed to configure git hooks.");
|
|
1256
1121
|
}
|
|
1257
1122
|
}
|
|
1123
|
+
if (result.status === "conflicts") {
|
|
1124
|
+
p5.log.warn("Some template files differ from your code. Changes written directly.");
|
|
1125
|
+
p5.log.info("Review changes:");
|
|
1126
|
+
p5.log.info(" git diff");
|
|
1127
|
+
p5.log.info("");
|
|
1128
|
+
p5.log.info("Keep a change: git add <file>");
|
|
1129
|
+
p5.log.info("Discard a change: git checkout -- <file>");
|
|
1130
|
+
p5.log.info('Commit when ready: git add . && git commit -m "projx: init"');
|
|
1131
|
+
p5.log.info("");
|
|
1132
|
+
p5.log.info("To skip files on future updates, add to .projx-component:");
|
|
1133
|
+
p5.log.info(' { "skip": ["src/**", "tests/**"] }');
|
|
1134
|
+
p5.outro("Template applied. Review with git diff.\n\n Like projx? Star it: https://github.com/ukanhaupa/projx");
|
|
1135
|
+
} else {
|
|
1136
|
+
p5.outro("Project initialized.\n\n Like projx? Star it: https://github.com/ukanhaupa/projx");
|
|
1137
|
+
}
|
|
1258
1138
|
} finally {
|
|
1259
1139
|
await cleanupRepo(repoDir, isLocal);
|
|
1260
1140
|
}
|
|
1261
|
-
p6.outro("Project initialized. Run './setup.sh' to install dependencies.\n\n Like projx? Star it: https://github.com/ukanhaupa/projx");
|
|
1262
1141
|
}
|
|
1263
1142
|
async function confirmDetections(detected) {
|
|
1264
1143
|
const confirmed = [];
|
|
1265
1144
|
for (const d of detected) {
|
|
1266
|
-
const yes = await
|
|
1145
|
+
const yes = await p5.confirm({
|
|
1267
1146
|
message: `Found ${LABELS[d.component].label} in ${d.directory}/ \u2014 register as "${d.component}"?`,
|
|
1268
1147
|
initialValue: true
|
|
1269
1148
|
});
|
|
1270
|
-
if (
|
|
1149
|
+
if (p5.isCancel(yes)) process.exit(0);
|
|
1271
1150
|
if (yes) {
|
|
1272
1151
|
confirmed.push({ component: d.component, directory: d.directory });
|
|
1273
1152
|
}
|
|
@@ -1275,7 +1154,7 @@ async function confirmDetections(detected) {
|
|
|
1275
1154
|
return confirmed;
|
|
1276
1155
|
}
|
|
1277
1156
|
async function manualSelect(cwd) {
|
|
1278
|
-
const selected = await
|
|
1157
|
+
const selected = await p5.multiselect({
|
|
1279
1158
|
message: "No components detected. Select manually:",
|
|
1280
1159
|
options: COMPONENTS.map((c) => ({
|
|
1281
1160
|
value: c,
|
|
@@ -1284,17 +1163,17 @@ async function manualSelect(cwd) {
|
|
|
1284
1163
|
})),
|
|
1285
1164
|
required: false
|
|
1286
1165
|
});
|
|
1287
|
-
if (
|
|
1166
|
+
if (p5.isCancel(selected)) process.exit(0);
|
|
1288
1167
|
const result = [];
|
|
1289
1168
|
for (const component of selected) {
|
|
1290
|
-
const dir = await
|
|
1169
|
+
const dir = await p5.text({
|
|
1291
1170
|
message: `Directory for ${LABELS[component].label}?`,
|
|
1292
1171
|
placeholder: component,
|
|
1293
1172
|
defaultValue: component
|
|
1294
1173
|
});
|
|
1295
|
-
if (
|
|
1174
|
+
if (p5.isCancel(dir)) process.exit(0);
|
|
1296
1175
|
if (!existsSync7(join8(cwd, dir))) {
|
|
1297
|
-
|
|
1176
|
+
p5.log.warn(`${dir}/ does not exist \u2014 skipping.`);
|
|
1298
1177
|
continue;
|
|
1299
1178
|
}
|
|
1300
1179
|
result.push({ component, directory: dir });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
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": {
|