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 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 `npx prisma migrate dev` when ready.
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 fn = new Function("components", "projectName", `return ${ifMatch[1]}`);
293
- const result = fn(components, projectName);
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 fn = new Function("components", "projectName", `return ${elseIfMatch[1]}`);
305
- const result = fn(components, projectName);
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
- return { name, components, git: true, install: true };
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("pnpm")) {
884
- spinner7.start("Installing Fastify dependencies (pnpm install)");
885
- exec("pnpm install", join4(dest, "fastify"));
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
- spinner7.start("Installing Fastify dependencies (npm install)");
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
- spinner7.start("Installing Frontend dependencies (npm install)");
895
- exec("npm install", join4(dest, "frontend"));
896
- spinner7.stop("Frontend dependencies installed.");
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
- spinner7.start("Installing E2E dependencies (npm install)");
900
- exec("npm install", join4(dest, "e2e"));
901
- spinner7.stop("E2E dependencies installed.");
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 vars = { projectName: name, components: config.components, paths: componentPaths };
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("pnpm")) {
1231
- spinner7.start("Installing Fastify dependencies");
1232
- exec("pnpm install", join6(dest, "fastify"));
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
- spinner7.start("Installing Fastify dependencies");
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
- spinner7.start("Installing Frontend dependencies");
1242
- exec("npm install", join6(dest, "frontend"));
1243
- spinner7.stop("Frontend dependencies installed.");
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
- spinner7.start("Installing E2E dependencies");
1247
- exec("npm install", join6(dest, "e2e"));
1248
- spinner7.stop("E2E dependencies installed.");
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}?.toIso8601String()`;
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(` npx prisma migrate dev --name add_${toSnake(config.name)}`);
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.4.3",
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 && pnpm install && npx prisma migrate dev && pnpm dev
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 && npm install && npm run dev
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 %> && pnpm test
80
+ cd <%= paths.fastify %> && <%= pm.run %> test
81
81
  <% } %>
82
82
  <% if (components.includes('frontend')) { %>
83
- cd <%= paths.frontend %> && npx vitest run
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 %> && npx playwright test
89
+ cd <%= paths.e2e %> && <%= pm.exec %> playwright test
90
90
  <% } %>
91
91
  ```
92
92
 
@@ -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: pnpm
100
- cache-dependency-path: <%= paths.fastify %>/pnpm-lock.yaml
101
- - run: pnpm install --frozen-lockfile
102
- - run: npx prisma generate
103
- - run: npx prettier --check .
104
- - run: npx eslint .
105
- - run: npx tsc --noEmit
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: npm
123
- cache-dependency-path: <%= paths.frontend %>/package-lock.json
124
- - run: npm ci
125
- - run: npx prettier --check .
126
- - run: npx eslint 'src/**/*.{ts,tsx}'
127
- - run: npx tsc --noEmit
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: npm
165
- cache-dependency-path: <%= paths.e2e %>/package-lock.json
166
- - run: npm ci
167
- - run: npx prettier --check .
168
- - run: npx eslint '**/*.ts'
169
- - run: npx tsc --noEmit
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: ['pnpm', 'prisma', 'migrate', 'deploy']
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: ['pnpm', 'dev']
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 "npm install && npm run dev -- --host 0.0.0.0"
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: ["pnpm", "prisma", "migrate", "deploy"]
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 npx prettier --write --ignore-unknown
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 npx eslint --fix
52
- npx tsc --noEmit
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 npx prettier --write --ignore-unknown
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 npx eslint --fix
68
- npx tsc --noEmit
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 npx prettier --write --ignore-unknown
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 npx eslint --fix
84
- npx tsc --noEmit
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 %> && pnpm install --frozen-lockfile && cd ..
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 %> && npm ci && cd ..
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 %> && npm ci && cd ..
23
+ cd <%= paths.e2e %> && <%= pm.ci %> && cd ..
24
24
  echo "E2E dependencies installed."
25
25
  <% } %>
26
26
  <% if (components.includes('mobile')) { %>