create-projx 1.4.3 → 1.5.0
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 +11 -1
- package/dist/index.js +120 -42
- package/package.json +1 -1
- package/src/templates/README.md.ejs +5 -5
- package/src/templates/ci.yml.ejs +46 -19
- package/src/templates/docker-compose.dev.yml.ejs +3 -3
- package/src/templates/docker-compose.yml.ejs +1 -1
- package/src/templates/pre-commit.ejs +9 -9
- package/src/templates/setup.sh.ejs +3 -3
package/README.md
CHANGED
|
@@ -32,6 +32,16 @@ npx create-projx my-app --components fastify,frontend,e2e
|
|
|
32
32
|
npx create-projx my-app -y
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Package Manager Support
|
|
36
|
+
|
|
37
|
+
Projx supports **npm**, **pnpm**, **yarn**, and **bun**. During `create`, you're prompted to pick one. The choice is stored in `.projx` and used everywhere — setup.sh, Docker, CI, pre-commit hooks, and README.
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{ "packageManager": "pnpm" }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For `init`, the package manager is auto-detected from lockfiles (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `bun.lockb` → bun). Falls back to a prompt if no lockfile is found.
|
|
44
|
+
|
|
35
45
|
## Components
|
|
36
46
|
|
|
37
47
|
| Component | Stack | What You Get |
|
|
@@ -204,7 +214,7 @@ Override with `--ai` (fastapi) or `--backend` (fastify).
|
|
|
204
214
|
| `frontend` | `src/types/<name>.ts` — TypeScript interface + Create/Update variants |
|
|
205
215
|
| `mobile` | `lib/entities/<name>/model.dart` — Dart class with fromJson/toJson/copyWith |
|
|
206
216
|
|
|
207
|
-
No migrations — run `alembic revision --autogenerate` or `
|
|
217
|
+
No migrations — run `alembic revision --autogenerate` or `prisma migrate dev` (via your package manager) when ready.
|
|
208
218
|
|
|
209
219
|
### Sync Types
|
|
210
220
|
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,26 @@ var COMPONENTS = [
|
|
|
21
21
|
"e2e",
|
|
22
22
|
"infra"
|
|
23
23
|
];
|
|
24
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
25
|
+
function pmCommands(pm) {
|
|
26
|
+
switch (pm) {
|
|
27
|
+
case "npm":
|
|
28
|
+
return { name: "npm", install: "npm install", ci: "npm ci", run: "npm run", exec: "npx", dlx: "npx", lockfile: "package-lock.json", prismaExec: "npx prisma", runDev: "npm run dev" };
|
|
29
|
+
case "pnpm":
|
|
30
|
+
return { name: "pnpm", install: "pnpm install", ci: "pnpm install --frozen-lockfile", run: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", lockfile: "pnpm-lock.yaml", prismaExec: "pnpm prisma", runDev: "pnpm dev" };
|
|
31
|
+
case "yarn":
|
|
32
|
+
return { name: "yarn", install: "yarn", ci: "yarn --frozen-lockfile", run: "yarn", exec: "yarn", dlx: "yarn dlx", lockfile: "yarn.lock", prismaExec: "yarn prisma", runDev: "yarn dev" };
|
|
33
|
+
case "bun":
|
|
34
|
+
return { name: "bun", install: "bun install", ci: "bun install --frozen-lockfile", run: "bun run", exec: "bunx", dlx: "bunx", lockfile: "bun.lockb", prismaExec: "bunx prisma", runDev: "bun run dev" };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function detectPackageManager(cwd) {
|
|
38
|
+
if (existsSync(join(cwd, "bun.lockb"))) return "bun";
|
|
39
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
40
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
41
|
+
if (existsSync(join(cwd, "package-lock.json"))) return "npm";
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
24
44
|
function toKebab(s) {
|
|
25
45
|
return s.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
26
46
|
}
|
|
@@ -289,8 +309,9 @@ function render(template, vars) {
|
|
|
289
309
|
for (const line of lines) {
|
|
290
310
|
const ifMatch = line.match(/^<%\s*if\s*\((.+?)\)\s*\{?\s*%>$/);
|
|
291
311
|
if (ifMatch) {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
312
|
+
const pmName = vars.pm?.name ?? "npm";
|
|
313
|
+
const fn = new Function("components", "projectName", "pm", `return ${ifMatch[1]}`);
|
|
314
|
+
const result = fn(components, projectName, pmName);
|
|
294
315
|
stack.push({ active: result, matched: result });
|
|
295
316
|
continue;
|
|
296
317
|
}
|
|
@@ -301,8 +322,9 @@ function render(template, vars) {
|
|
|
301
322
|
if (top.matched) {
|
|
302
323
|
top.active = false;
|
|
303
324
|
} else {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
325
|
+
const pmN = vars.pm?.name ?? "npm";
|
|
326
|
+
const fn = new Function("components", "projectName", "pm", `return ${elseIfMatch[1]}`);
|
|
327
|
+
const result = fn(components, projectName, pmN);
|
|
306
328
|
top.active = result;
|
|
307
329
|
if (result) top.matched = true;
|
|
308
330
|
}
|
|
@@ -391,7 +413,18 @@ async function runPrompts(nameArg) {
|
|
|
391
413
|
if (components.length === 0) {
|
|
392
414
|
p.log.warn("No components selected. Creating an empty project.");
|
|
393
415
|
}
|
|
394
|
-
|
|
416
|
+
const hasJs = components.some((c) => ["fastify", "frontend", "e2e"].includes(c));
|
|
417
|
+
let packageManager = "npm";
|
|
418
|
+
if (hasJs) {
|
|
419
|
+
const pm = await p.select({
|
|
420
|
+
message: "Package manager",
|
|
421
|
+
options: PACKAGE_MANAGERS.map((pm2) => ({ value: pm2, label: pm2 })),
|
|
422
|
+
initialValue: "npm"
|
|
423
|
+
});
|
|
424
|
+
if (p.isCancel(pm)) process.exit(0);
|
|
425
|
+
packageManager = pm;
|
|
426
|
+
}
|
|
427
|
+
return { name, components, git: true, install: true, packageManager };
|
|
395
428
|
}
|
|
396
429
|
|
|
397
430
|
// src/scaffold.ts
|
|
@@ -686,6 +719,8 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
|
|
|
686
719
|
components,
|
|
687
720
|
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
688
721
|
};
|
|
722
|
+
const pmObj = vars.pm;
|
|
723
|
+
if (pmObj?.name) projxConfig.packageManager = pmObj.name;
|
|
689
724
|
await writeFile2(join3(dest, ".projx"), JSON.stringify(projxConfig, null, 2) + "\n");
|
|
690
725
|
}
|
|
691
726
|
async function substituteNames(dest, components, paths, name, nameSnake) {
|
|
@@ -774,6 +809,8 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
774
809
|
components,
|
|
775
810
|
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
776
811
|
};
|
|
812
|
+
const pmObj = vars.pm;
|
|
813
|
+
if (pmObj?.name) projxConfig.packageManager = pmObj.name;
|
|
777
814
|
await writeFile2(join3(cwd, ".projx"), JSON.stringify(projxConfig, null, 2) + "\n");
|
|
778
815
|
if (result.conflicted.length === 0) {
|
|
779
816
|
execSync2("git add -A", { cwd, stdio: "pipe" });
|
|
@@ -817,10 +854,11 @@ async function applyTemplate(cwd, repoDir, components, componentPaths, vars, ver
|
|
|
817
854
|
// src/scaffold.ts
|
|
818
855
|
async function scaffold(opts, dest, localRepo) {
|
|
819
856
|
const name = toKebab(opts.name);
|
|
857
|
+
const pm = opts.packageManager ?? "npm";
|
|
820
858
|
const paths = Object.fromEntries(
|
|
821
859
|
opts.components.map((c) => [c, c])
|
|
822
860
|
);
|
|
823
|
-
const vars = { projectName: name, components: opts.components, paths };
|
|
861
|
+
const vars = { projectName: name, components: opts.components, paths, pm: pmCommands(pm) };
|
|
824
862
|
const isLocal = !!localRepo;
|
|
825
863
|
await mkdir3(dest, { recursive: true });
|
|
826
864
|
const dlSpinner = p2.spinner();
|
|
@@ -844,7 +882,7 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
844
882
|
await applyTemplate(dest, repoDir, opts.components, paths, vars, version);
|
|
845
883
|
spinner7.stop("Scaffold complete.");
|
|
846
884
|
if (opts.install) {
|
|
847
|
-
await installDeps(dest, opts.components);
|
|
885
|
+
await installDeps(dest, opts.components, pm);
|
|
848
886
|
}
|
|
849
887
|
copyEnvExamples(dest, opts.components);
|
|
850
888
|
if (opts.git) {
|
|
@@ -865,7 +903,9 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
865
903
|
|
|
866
904
|
Like projx? Star it: https://github.com/ukanhaupa/projx`);
|
|
867
905
|
}
|
|
868
|
-
async function installDeps(dest, components) {
|
|
906
|
+
async function installDeps(dest, components, pm) {
|
|
907
|
+
const cmds = pmCommands(pm);
|
|
908
|
+
const pmBin = pm === "bun" ? "bun" : pm;
|
|
869
909
|
for (const component of components) {
|
|
870
910
|
const spinner7 = p2.spinner();
|
|
871
911
|
try {
|
|
@@ -880,25 +920,31 @@ async function installDeps(dest, components) {
|
|
|
880
920
|
}
|
|
881
921
|
break;
|
|
882
922
|
case "fastify":
|
|
883
|
-
if (hasCommand(
|
|
884
|
-
spinner7.start(
|
|
885
|
-
exec(
|
|
923
|
+
if (hasCommand(pmBin)) {
|
|
924
|
+
spinner7.start(`Installing Fastify dependencies (${cmds.install})`);
|
|
925
|
+
exec(cmds.install, join4(dest, "fastify"));
|
|
886
926
|
spinner7.stop("Fastify dependencies installed.");
|
|
887
927
|
} else {
|
|
888
|
-
|
|
889
|
-
exec("npm install", join4(dest, "fastify"));
|
|
890
|
-
spinner7.stop("Fastify dependencies installed.");
|
|
928
|
+
p2.log.warn(`${pm} not found \u2014 run 'cd fastify && ${cmds.install}' manually.`);
|
|
891
929
|
}
|
|
892
930
|
break;
|
|
893
931
|
case "frontend":
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
932
|
+
if (hasCommand(pmBin)) {
|
|
933
|
+
spinner7.start(`Installing Frontend dependencies (${cmds.install})`);
|
|
934
|
+
exec(cmds.install, join4(dest, "frontend"));
|
|
935
|
+
spinner7.stop("Frontend dependencies installed.");
|
|
936
|
+
} else {
|
|
937
|
+
p2.log.warn(`${pm} not found \u2014 run 'cd frontend && ${cmds.install}' manually.`);
|
|
938
|
+
}
|
|
897
939
|
break;
|
|
898
940
|
case "e2e":
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
941
|
+
if (hasCommand(pmBin)) {
|
|
942
|
+
spinner7.start(`Installing E2E dependencies (${cmds.install})`);
|
|
943
|
+
exec(cmds.install, join4(dest, "e2e"));
|
|
944
|
+
spinner7.stop("E2E dependencies installed.");
|
|
945
|
+
} else {
|
|
946
|
+
p2.log.warn(`${pm} not found \u2014 run 'cd e2e && ${cmds.install}' manually.`);
|
|
947
|
+
}
|
|
902
948
|
break;
|
|
903
949
|
case "mobile":
|
|
904
950
|
if (hasCommand("flutter")) {
|
|
@@ -993,7 +1039,9 @@ async function update(cwd, localRepo) {
|
|
|
993
1039
|
const pkg = JSON.parse(await readFile5(join5(repoDir, "cli/package.json"), "utf-8"));
|
|
994
1040
|
const version = pkg.version;
|
|
995
1041
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
996
|
-
const
|
|
1042
|
+
const raw = existsSync4(configPath) ? JSON.parse(await readFile5(configPath, "utf-8")) : {};
|
|
1043
|
+
const pm = raw.packageManager ?? "npm";
|
|
1044
|
+
const vars = { projectName: name, components: config.components, paths: componentPaths, pm: pmCommands(pm) };
|
|
997
1045
|
const spinner7 = p3.spinner();
|
|
998
1046
|
spinner7.start("Applying template update");
|
|
999
1047
|
const rootSkip = config.skip ?? [];
|
|
@@ -1184,8 +1232,9 @@ async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
|
1184
1232
|
const existingPaths = await discoverComponentPaths(cwd, existing);
|
|
1185
1233
|
const paths = { ...existingPaths };
|
|
1186
1234
|
for (const c of toAdd) paths[c] = c;
|
|
1235
|
+
const pm = config.packageManager ?? "npm";
|
|
1187
1236
|
const name = detectProjectName(cwd, existing, paths);
|
|
1188
|
-
const vars = { projectName: name, components: allComponents, paths };
|
|
1237
|
+
const vars = { projectName: name, components: allComponents, paths, pm: pmCommands(pm) };
|
|
1189
1238
|
const pkg = JSON.parse(await readFile6(join6(repoDir, "cli/package.json"), "utf-8"));
|
|
1190
1239
|
const version = pkg.version;
|
|
1191
1240
|
const spinner7 = p4.spinner();
|
|
@@ -1193,7 +1242,7 @@ async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
|
1193
1242
|
await writeTemplateToDir(cwd, repoDir, allComponents, paths, vars, version, "scaffold");
|
|
1194
1243
|
spinner7.stop("Components added.");
|
|
1195
1244
|
if (!skipInstall) {
|
|
1196
|
-
await installDeps2(cwd, toAdd);
|
|
1245
|
+
await installDeps2(cwd, toAdd, pm);
|
|
1197
1246
|
}
|
|
1198
1247
|
for (const component of toAdd) {
|
|
1199
1248
|
const example = join6(cwd, component, ".env.example");
|
|
@@ -1212,7 +1261,9 @@ async function add(cwd, newComponents, localRepo, skipInstall = false) {
|
|
|
1212
1261
|
await cleanupRepo(repoDir, isLocal);
|
|
1213
1262
|
}
|
|
1214
1263
|
}
|
|
1215
|
-
async function installDeps2(dest, components) {
|
|
1264
|
+
async function installDeps2(dest, components, pm) {
|
|
1265
|
+
const cmds = pmCommands(pm);
|
|
1266
|
+
const pmBin = pm === "bun" ? "bun" : pm;
|
|
1216
1267
|
for (const component of components) {
|
|
1217
1268
|
const spinner7 = p4.spinner();
|
|
1218
1269
|
try {
|
|
@@ -1227,25 +1278,31 @@ async function installDeps2(dest, components) {
|
|
|
1227
1278
|
}
|
|
1228
1279
|
break;
|
|
1229
1280
|
case "fastify":
|
|
1230
|
-
if (hasCommand(
|
|
1231
|
-
spinner7.start(
|
|
1232
|
-
exec(
|
|
1281
|
+
if (hasCommand(pmBin)) {
|
|
1282
|
+
spinner7.start(`Installing Fastify dependencies (${cmds.install})`);
|
|
1283
|
+
exec(cmds.install, join6(dest, "fastify"));
|
|
1233
1284
|
spinner7.stop("Fastify dependencies installed.");
|
|
1234
1285
|
} else {
|
|
1235
|
-
|
|
1236
|
-
exec("npm install", join6(dest, "fastify"));
|
|
1237
|
-
spinner7.stop("Fastify dependencies installed.");
|
|
1286
|
+
p4.log.warn(`${pm} not found \u2014 run 'cd fastify && ${cmds.install}' manually.`);
|
|
1238
1287
|
}
|
|
1239
1288
|
break;
|
|
1240
1289
|
case "frontend":
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1290
|
+
if (hasCommand(pmBin)) {
|
|
1291
|
+
spinner7.start(`Installing Frontend dependencies (${cmds.install})`);
|
|
1292
|
+
exec(cmds.install, join6(dest, "frontend"));
|
|
1293
|
+
spinner7.stop("Frontend dependencies installed.");
|
|
1294
|
+
} else {
|
|
1295
|
+
p4.log.warn(`${pm} not found \u2014 run 'cd frontend && ${cmds.install}' manually.`);
|
|
1296
|
+
}
|
|
1244
1297
|
break;
|
|
1245
1298
|
case "e2e":
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1299
|
+
if (hasCommand(pmBin)) {
|
|
1300
|
+
spinner7.start(`Installing E2E dependencies (${cmds.install})`);
|
|
1301
|
+
exec(cmds.install, join6(dest, "e2e"));
|
|
1302
|
+
spinner7.stop("E2E dependencies installed.");
|
|
1303
|
+
} else {
|
|
1304
|
+
p4.log.warn(`${pm} not found \u2014 run 'cd e2e && ${cmds.install}' manually.`);
|
|
1305
|
+
}
|
|
1249
1306
|
break;
|
|
1250
1307
|
case "mobile":
|
|
1251
1308
|
if (hasCommand("flutter")) {
|
|
@@ -1392,8 +1449,25 @@ async function init(cwd, localRepo) {
|
|
|
1392
1449
|
const paths = Object.fromEntries(
|
|
1393
1450
|
confirmed.map((c) => [c.component, c.directory])
|
|
1394
1451
|
);
|
|
1452
|
+
const hasJs = components.some((c) => ["fastify", "frontend", "e2e"].includes(c));
|
|
1453
|
+
let pm = "npm";
|
|
1454
|
+
if (hasJs) {
|
|
1455
|
+
const detected2 = detectPackageManager(cwd);
|
|
1456
|
+
if (detected2) {
|
|
1457
|
+
pm = detected2;
|
|
1458
|
+
p5.log.info(`Detected package manager: ${pm}`);
|
|
1459
|
+
} else if (process.stdin.isTTY) {
|
|
1460
|
+
const choice = await p5.select({
|
|
1461
|
+
message: "Package manager",
|
|
1462
|
+
options: PACKAGE_MANAGERS.map((v) => ({ value: v, label: v })),
|
|
1463
|
+
initialValue: "npm"
|
|
1464
|
+
});
|
|
1465
|
+
if (p5.isCancel(choice)) process.exit(0);
|
|
1466
|
+
pm = choice;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1395
1469
|
const projectName = toKebab(cwd.split("/").pop());
|
|
1396
|
-
const vars = { projectName, components, paths };
|
|
1470
|
+
const vars = { projectName, components, paths, pm: pmCommands(pm) };
|
|
1397
1471
|
const dlSpinner = p5.spinner();
|
|
1398
1472
|
dlSpinner.start(isLocal ? "Using local templates" : "Downloading latest templates");
|
|
1399
1473
|
const repoDir = await downloadRepo(localRepo).catch((err) => {
|
|
@@ -1992,7 +2066,7 @@ async function diff(cwd, localRepo) {
|
|
|
1992
2066
|
const version = pkg.version;
|
|
1993
2067
|
p8.log.info(`Current: v${config.version} \u2192 Template: v${version}`);
|
|
1994
2068
|
const name = detectProjectName(cwd, config.components, componentPaths);
|
|
1995
|
-
const vars = { projectName: name, components: config.components, paths: componentPaths };
|
|
2069
|
+
const vars = { projectName: name, components: config.components, paths: componentPaths, pm: pmCommands(raw.packageManager ?? "npm") };
|
|
1996
2070
|
const spinner7 = p8.spinner();
|
|
1997
2071
|
spinner7.start("Analyzing changes");
|
|
1998
2072
|
const tmpTemplate = join11(tmpdir3(), `projx-diff-${Date.now()}`);
|
|
@@ -2551,9 +2625,10 @@ function dartFromJson(fieldName, type, required) {
|
|
|
2551
2625
|
})();
|
|
2552
2626
|
return required ? `${key} as ${dartT}` : `${key} as ${dartT}?`;
|
|
2553
2627
|
}
|
|
2554
|
-
function dartToJson(fieldName, camelName, type) {
|
|
2628
|
+
function dartToJson(fieldName, camelName, type, required) {
|
|
2555
2629
|
const isDate = type === "date" || type === "datetime";
|
|
2556
|
-
if (isDate) return `'${fieldName}': ${camelName}
|
|
2630
|
+
if (isDate && required) return `'${fieldName}': ${camelName}.toIso8601String()`;
|
|
2631
|
+
if (isDate && !required) return `'${fieldName}': ${camelName}?.toIso8601String()`;
|
|
2557
2632
|
return `'${fieldName}': ${camelName}`;
|
|
2558
2633
|
}
|
|
2559
2634
|
function generateDartModel(config) {
|
|
@@ -2602,7 +2677,7 @@ function generateDartModel(config) {
|
|
|
2602
2677
|
lines.push(` Map<String, dynamic> toJson() {`);
|
|
2603
2678
|
lines.push(` return {`);
|
|
2604
2679
|
for (const f of allFields) {
|
|
2605
|
-
lines.push(` ${dartToJson(f.snake, f.camel, f.fieldType)},`);
|
|
2680
|
+
lines.push(` ${dartToJson(f.snake, f.camel, f.fieldType, f.required)},`);
|
|
2606
2681
|
}
|
|
2607
2682
|
lines.push(` };`);
|
|
2608
2683
|
lines.push(` }`);
|
|
@@ -2662,6 +2737,9 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
2662
2737
|
p9.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
2663
2738
|
process.exit(1);
|
|
2664
2739
|
}
|
|
2740
|
+
const projxData = JSON.parse(await readFile11(configPath, "utf-8"));
|
|
2741
|
+
const pmName = projxData.packageManager ?? "npm";
|
|
2742
|
+
const pm = pmCommands(pmName);
|
|
2665
2743
|
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
2666
2744
|
const hasFastapi = discovered.includes("fastapi");
|
|
2667
2745
|
const hasFastify = discovered.includes("fastify");
|
|
@@ -2799,7 +2877,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
2799
2877
|
if (genFastify) {
|
|
2800
2878
|
p9.log.info("");
|
|
2801
2879
|
p9.log.info("Fastify next steps:");
|
|
2802
|
-
p9.log.info(`
|
|
2880
|
+
p9.log.info(` ${pm.prismaExec} migrate dev --name add_${toSnake(config.name)}`);
|
|
2803
2881
|
}
|
|
2804
2882
|
if (hasFrontend) {
|
|
2805
2883
|
p9.log.info("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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": {
|
|
@@ -48,7 +48,7 @@ API docs at `http://localhost:7860/docs`.
|
|
|
48
48
|
### Fastify
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
cd <%= paths.fastify %> && cp .env.example .env &&
|
|
51
|
+
cd <%= paths.fastify %> && cp .env.example .env && <%= pm.install %> && <%= pm.exec %> prisma migrate dev && <%= pm.run %> dev
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
API docs at `http://localhost:3000/docs`.
|
|
@@ -58,7 +58,7 @@ API docs at `http://localhost:3000/docs`.
|
|
|
58
58
|
### Frontend
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
cd <%= paths.frontend %> && cp .env.example .env &&
|
|
61
|
+
cd <%= paths.frontend %> && cp .env.example .env && <%= pm.install %> && <%= pm.run %> dev
|
|
62
62
|
```
|
|
63
63
|
<% } %>
|
|
64
64
|
<% if (components.includes('mobile')) { %>
|
|
@@ -77,16 +77,16 @@ cd <%= paths.mobile %> && cp .env.example .env && flutter pub get && flutter run
|
|
|
77
77
|
cd <%= paths.fastapi %> && uv run pytest
|
|
78
78
|
<% } %>
|
|
79
79
|
<% if (components.includes('fastify')) { %>
|
|
80
|
-
cd <%= paths.fastify %> &&
|
|
80
|
+
cd <%= paths.fastify %> && <%= pm.run %> test
|
|
81
81
|
<% } %>
|
|
82
82
|
<% if (components.includes('frontend')) { %>
|
|
83
|
-
cd <%= paths.frontend %> &&
|
|
83
|
+
cd <%= paths.frontend %> && <%= pm.exec %> vitest run
|
|
84
84
|
<% } %>
|
|
85
85
|
<% if (components.includes('mobile')) { %>
|
|
86
86
|
cd <%= paths.mobile %> && flutter test
|
|
87
87
|
<% } %>
|
|
88
88
|
<% if (components.includes('e2e')) { %>
|
|
89
|
-
cd <%= paths.e2e %> &&
|
|
89
|
+
cd <%= paths.e2e %> && <%= pm.exec %> playwright test
|
|
90
90
|
<% } %>
|
|
91
91
|
```
|
|
92
92
|
|
package/src/templates/ci.yml.ejs
CHANGED
|
@@ -90,19 +90,26 @@ jobs:
|
|
|
90
90
|
working-directory: <%= paths.fastify %>
|
|
91
91
|
steps:
|
|
92
92
|
- uses: actions/checkout@v5
|
|
93
|
+
<% if (pm === 'pnpm') { %>
|
|
93
94
|
- uses: pnpm/action-setup@v4
|
|
94
95
|
with:
|
|
95
96
|
version: 9
|
|
97
|
+
<% } %>
|
|
98
|
+
<% if (pm === 'bun') { %>
|
|
99
|
+
- uses: oven-sh/setup-bun@v2
|
|
100
|
+
<% } %>
|
|
101
|
+
<% if (pm !== 'bun') { %>
|
|
96
102
|
- uses: actions/setup-node@v5
|
|
97
103
|
with:
|
|
98
104
|
node-version: 20
|
|
99
|
-
cache:
|
|
100
|
-
cache-dependency-path: <%= paths.fastify
|
|
101
|
-
|
|
102
|
-
- run:
|
|
103
|
-
- run:
|
|
104
|
-
- run:
|
|
105
|
-
- run:
|
|
105
|
+
cache: <%= pm.name %>
|
|
106
|
+
cache-dependency-path: <%= paths.fastify %>/<%= pm.lockfile %>
|
|
107
|
+
<% } %>
|
|
108
|
+
- run: <%= pm.ci %>
|
|
109
|
+
- run: <%= pm.prismaExec %> generate
|
|
110
|
+
- run: <%= pm.exec %> prettier --check .
|
|
111
|
+
- run: <%= pm.exec %> eslint .
|
|
112
|
+
- run: <%= pm.exec %> tsc --noEmit
|
|
106
113
|
<% } %>
|
|
107
114
|
<% if (components.includes('frontend')) { %>
|
|
108
115
|
|
|
@@ -116,15 +123,25 @@ jobs:
|
|
|
116
123
|
working-directory: <%= paths.frontend %>
|
|
117
124
|
steps:
|
|
118
125
|
- uses: actions/checkout@v5
|
|
126
|
+
<% if (pm === 'pnpm') { %>
|
|
127
|
+
- uses: pnpm/action-setup@v4
|
|
128
|
+
with:
|
|
129
|
+
version: 9
|
|
130
|
+
<% } %>
|
|
131
|
+
<% if (pm === 'bun') { %>
|
|
132
|
+
- uses: oven-sh/setup-bun@v2
|
|
133
|
+
<% } %>
|
|
134
|
+
<% if (pm !== 'bun') { %>
|
|
119
135
|
- uses: actions/setup-node@v5
|
|
120
136
|
with:
|
|
121
137
|
node-version: 22
|
|
122
|
-
cache:
|
|
123
|
-
cache-dependency-path: <%= paths.frontend
|
|
124
|
-
|
|
125
|
-
- run:
|
|
126
|
-
- run:
|
|
127
|
-
- run:
|
|
138
|
+
cache: <%= pm.name %>
|
|
139
|
+
cache-dependency-path: <%= paths.frontend %>/<%= pm.lockfile %>
|
|
140
|
+
<% } %>
|
|
141
|
+
- run: <%= pm.ci %>
|
|
142
|
+
- run: <%= pm.exec %> prettier --check .
|
|
143
|
+
- run: <%= pm.exec %> eslint 'src/**/*.{ts,tsx}'
|
|
144
|
+
- run: <%= pm.exec %> tsc --noEmit
|
|
128
145
|
<% } %>
|
|
129
146
|
<% if (components.includes('mobile')) { %>
|
|
130
147
|
|
|
@@ -158,15 +175,25 @@ jobs:
|
|
|
158
175
|
working-directory: <%= paths.e2e %>
|
|
159
176
|
steps:
|
|
160
177
|
- uses: actions/checkout@v5
|
|
178
|
+
<% if (pm === 'pnpm') { %>
|
|
179
|
+
- uses: pnpm/action-setup@v4
|
|
180
|
+
with:
|
|
181
|
+
version: 9
|
|
182
|
+
<% } %>
|
|
183
|
+
<% if (pm === 'bun') { %>
|
|
184
|
+
- uses: oven-sh/setup-bun@v2
|
|
185
|
+
<% } %>
|
|
186
|
+
<% if (pm !== 'bun') { %>
|
|
161
187
|
- uses: actions/setup-node@v5
|
|
162
188
|
with:
|
|
163
189
|
node-version: 22
|
|
164
|
-
cache:
|
|
165
|
-
cache-dependency-path: <%= paths.e2e
|
|
166
|
-
|
|
167
|
-
- run:
|
|
168
|
-
- run:
|
|
169
|
-
- run:
|
|
190
|
+
cache: <%= pm.name %>
|
|
191
|
+
cache-dependency-path: <%= paths.e2e %>/<%= pm.lockfile %>
|
|
192
|
+
<% } %>
|
|
193
|
+
- run: <%= pm.ci %>
|
|
194
|
+
- run: <%= pm.exec %> prettier --check .
|
|
195
|
+
- run: <%= pm.exec %> eslint '**/*.ts'
|
|
196
|
+
- run: <%= pm.exec %> tsc --noEmit
|
|
170
197
|
<% } %>
|
|
171
198
|
<% if (components.includes('infra')) { %>
|
|
172
199
|
|
|
@@ -84,7 +84,7 @@ services:
|
|
|
84
84
|
<% if (components.includes('fastify')) { %>
|
|
85
85
|
<%= paths.fastify %>-migrate:
|
|
86
86
|
build: ./<%= paths.fastify %>
|
|
87
|
-
command: [
|
|
87
|
+
command: ["sh", "-c", "<%= pm.prismaExec %> migrate deploy"]
|
|
88
88
|
environment:
|
|
89
89
|
- DATABASE_URL=postgresql://dev:dev@db:5432/app
|
|
90
90
|
depends_on:
|
|
@@ -99,7 +99,7 @@ services:
|
|
|
99
99
|
- app-network
|
|
100
100
|
<%= paths.fastify %>:
|
|
101
101
|
build: ./<%= paths.fastify %>
|
|
102
|
-
command: [
|
|
102
|
+
command: ["sh", "-c", "<%= pm.runDev %>"]
|
|
103
103
|
ports:
|
|
104
104
|
- '3000:3000'
|
|
105
105
|
environment:
|
|
@@ -131,7 +131,7 @@ services:
|
|
|
131
131
|
frontend:
|
|
132
132
|
image: node:20-alpine
|
|
133
133
|
working_dir: /app
|
|
134
|
-
command: sh -c "
|
|
134
|
+
command: sh -c "<%= pm.install %> && <%= pm.run %> dev -- --host 0.0.0.0"
|
|
135
135
|
ports:
|
|
136
136
|
- '5173:5173'
|
|
137
137
|
environment:
|
|
@@ -35,7 +35,7 @@ services:
|
|
|
35
35
|
<% if (components.includes('fastify')) { %>
|
|
36
36
|
<%= paths.fastify %>-migrate:
|
|
37
37
|
build: ./<%= paths.fastify %>
|
|
38
|
-
command: ["
|
|
38
|
+
command: ["sh", "-c", "<%= pm.prismaExec %> migrate deploy"]
|
|
39
39
|
env_file:
|
|
40
40
|
- ./<%= paths.fastify %>/.env
|
|
41
41
|
networks:
|
|
@@ -46,10 +46,10 @@ FASTIFY_ALL=$(echo "$STAGED_FILES" | grep '^<%= paths.fastify %>/' || true)
|
|
|
46
46
|
if [ -n "$FASTIFY_ALL" ]; then
|
|
47
47
|
echo "Formatting <%= paths.fastify %>..."
|
|
48
48
|
cd <%= paths.fastify %>
|
|
49
|
-
echo "$FASTIFY_ALL" | sed 's|^<%= paths.fastify %>/||' | xargs
|
|
49
|
+
echo "$FASTIFY_ALL" | sed 's|^<%= paths.fastify %>/||' | xargs <%= pm.exec %> prettier --write --ignore-unknown
|
|
50
50
|
if [ -n "$FASTIFY_TS" ]; then
|
|
51
|
-
echo "$FASTIFY_TS" | sed 's|^<%= paths.fastify %>/||' | xargs
|
|
52
|
-
|
|
51
|
+
echo "$FASTIFY_TS" | sed 's|^<%= paths.fastify %>/||' | xargs <%= pm.exec %> eslint --fix
|
|
52
|
+
<%= pm.exec %> tsc --noEmit
|
|
53
53
|
fi
|
|
54
54
|
cd ..
|
|
55
55
|
echo "$FASTIFY_ALL" | xargs git add
|
|
@@ -62,10 +62,10 @@ FRONTEND_ALL=$(echo "$STAGED_FILES" | grep '^<%= paths.frontend %>/' || true)
|
|
|
62
62
|
if [ -n "$FRONTEND_ALL" ]; then
|
|
63
63
|
echo "Formatting <%= paths.frontend %>..."
|
|
64
64
|
cd <%= paths.frontend %>
|
|
65
|
-
echo "$FRONTEND_ALL" | sed 's|^<%= paths.frontend %>/||' | xargs
|
|
65
|
+
echo "$FRONTEND_ALL" | sed 's|^<%= paths.frontend %>/||' | xargs <%= pm.exec %> prettier --write --ignore-unknown
|
|
66
66
|
if [ -n "$FRONTEND_TS" ]; then
|
|
67
|
-
echo "$FRONTEND_TS" | sed 's|^<%= paths.frontend %>/||' | xargs
|
|
68
|
-
|
|
67
|
+
echo "$FRONTEND_TS" | sed 's|^<%= paths.frontend %>/||' | xargs <%= pm.exec %> eslint --fix
|
|
68
|
+
<%= pm.exec %> tsc --noEmit
|
|
69
69
|
fi
|
|
70
70
|
cd ..
|
|
71
71
|
echo "$FRONTEND_ALL" | xargs git add
|
|
@@ -78,10 +78,10 @@ E2E_ALL=$(echo "$STAGED_FILES" | grep '^<%= paths.e2e %>/' || true)
|
|
|
78
78
|
if [ -n "$E2E_ALL" ]; then
|
|
79
79
|
echo "Formatting <%= paths.e2e %>..."
|
|
80
80
|
cd <%= paths.e2e %>
|
|
81
|
-
echo "$E2E_ALL" | sed 's|^<%= paths.e2e %>/||' | xargs
|
|
81
|
+
echo "$E2E_ALL" | sed 's|^<%= paths.e2e %>/||' | xargs <%= pm.exec %> prettier --write --ignore-unknown
|
|
82
82
|
if [ -n "$E2E_TS" ]; then
|
|
83
|
-
echo "$E2E_TS" | sed 's|^<%= paths.e2e %>/||' | xargs
|
|
84
|
-
|
|
83
|
+
echo "$E2E_TS" | sed 's|^<%= paths.e2e %>/||' | xargs <%= pm.exec %> eslint --fix
|
|
84
|
+
<%= pm.exec %> tsc --noEmit
|
|
85
85
|
fi
|
|
86
86
|
cd ..
|
|
87
87
|
echo "$E2E_ALL" | xargs git add
|
|
@@ -10,17 +10,17 @@ echo "FastAPI dependencies installed."
|
|
|
10
10
|
<% } %>
|
|
11
11
|
<% if (components.includes('fastify')) { %>
|
|
12
12
|
|
|
13
|
-
cd <%= paths.fastify %> &&
|
|
13
|
+
cd <%= paths.fastify %> && <%= pm.ci %> && cd ..
|
|
14
14
|
echo "Fastify dependencies installed."
|
|
15
15
|
<% } %>
|
|
16
16
|
<% if (components.includes('frontend')) { %>
|
|
17
17
|
|
|
18
|
-
cd <%= paths.frontend %> &&
|
|
18
|
+
cd <%= paths.frontend %> && <%= pm.ci %> && cd ..
|
|
19
19
|
echo "Frontend dependencies installed."
|
|
20
20
|
<% } %>
|
|
21
21
|
<% if (components.includes('e2e')) { %>
|
|
22
22
|
|
|
23
|
-
cd <%= paths.e2e %> &&
|
|
23
|
+
cd <%= paths.e2e %> && <%= pm.ci %> && cd ..
|
|
24
24
|
echo "E2E dependencies installed."
|
|
25
25
|
<% } %>
|
|
26
26
|
<% if (components.includes('mobile')) { %>
|