scale-stack 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,6 +9,27 @@ import { resolve as resolve3 } from "path";
9
9
  import { InvalidArgumentError } from "commander";
10
10
  import pc5 from "picocolors";
11
11
 
12
+ // src/config/feature-predicates.ts
13
+ function hasAuth(selection) {
14
+ const strategy = typeof selection === "string" ? selection : selection.authStrategy;
15
+ return strategy !== "none";
16
+ }
17
+ function hasPrisma(config) {
18
+ return config.orm === "prisma";
19
+ }
20
+ function hasPlausible(config) {
21
+ return config.analytics === "plausible";
22
+ }
23
+ function hasGithubActions(config) {
24
+ return config.ci === "github";
25
+ }
26
+ function isImplementedAnalyticsProvider(provider) {
27
+ return provider === "plausible";
28
+ }
29
+ function isImplementedCiProvider(provider) {
30
+ return provider === "github";
31
+ }
32
+
12
33
  // src/cli/config.ts
13
34
  var DEFAULT_CI_PROVIDER = "github";
14
35
  var DEFAULT_ANALYTICS_PROVIDER = "plausible";
@@ -45,6 +66,7 @@ function resolveConfig(flags, answers) {
45
66
  jobs: false,
46
67
  a11y: false,
47
68
  apiClient: false,
69
+ utilityLibs: flags.utilityLibs ?? optMods.has("utilityLibs"),
48
70
  preCommit: flags.preCommit ?? optMods.has("preCommit")
49
71
  };
50
72
  }
@@ -114,9 +136,12 @@ async function runTechPicker(flags) {
114
136
  answers.auth = auth;
115
137
  }
116
138
  if (flags.jobs === void 0) {
117
- console.log(
118
- ` ${pc2.yellow("\u25B8")} Background jobs ` + ikea.muted("\u2014 Inngest (not implemented; skipped)")
119
- );
139
+ const jobs = await p.confirm({
140
+ message: "Add background jobs? " + pc2.yellow("\u2014 COMING SOON") + ikea.muted(" \xB7 Inngest event workflows, retries, and local dev server"),
141
+ initialValue: false
142
+ });
143
+ if (p.isCancel(jobs)) cancelled();
144
+ answers.jobs = jobs;
120
145
  }
121
146
  if (flags.chat === void 0) {
122
147
  const chat = await p.confirm({
@@ -185,6 +210,7 @@ async function runTechPicker(flags) {
185
210
  if (flags.a11y) alreadySelected.add("a11y");
186
211
  if (flags.apiClient) alreadySelected.add("apiClient");
187
212
  if (flags.i18n) alreadySelected.add("i18n");
213
+ if (flags.utilityLibs) alreadySelected.add("utilityLibs");
188
214
  if (flags.preCommit) alreadySelected.add("preCommit");
189
215
  const availableModules = [
190
216
  {
@@ -202,6 +228,11 @@ async function runTechPicker(flags) {
202
228
  label: "Internationalization",
203
229
  hint: ikea.muted("next-intl, locale routing, <LocaleSwitcher>")
204
230
  },
231
+ {
232
+ value: "utilityLibs",
233
+ label: "Utility libraries",
234
+ hint: ikea.muted("Motion, date-fns, es-toolkit, useHooks, ts-pattern")
235
+ },
205
236
  {
206
237
  value: "preCommit",
207
238
  label: "Pre-commit hooks",
@@ -283,6 +314,7 @@ function buildLines(config) {
283
314
  ["a11y", config.a11y],
284
315
  ["API Client", config.apiClient],
285
316
  ["i18n", config.i18n],
317
+ ["Utility Libs", config.utilityLibs],
286
318
  ["Pre-commit", config.preCommit]
287
319
  ];
288
320
  const allEntries = [...coreEntries, ...featureEntries, ...moduleEntries];
@@ -416,6 +448,9 @@ var VirtualFS = class {
416
448
  merge(path, mergeFn) {
417
449
  this.operations.push({ type: "merge", path, mergeFn });
418
450
  }
451
+ delete(path) {
452
+ this.operations.push({ type: "delete", path });
453
+ }
419
454
  async commit() {
420
455
  for (const op of this.operations) {
421
456
  switch (op.type) {
@@ -443,6 +478,12 @@ var VirtualFS = class {
443
478
  await writeFile(abs, merged, "utf-8");
444
479
  break;
445
480
  }
481
+ case "delete": {
482
+ const abs = this.abs(op.path);
483
+ await this.trackOriginal(abs);
484
+ await rm(abs, { recursive: true, force: true });
485
+ break;
486
+ }
446
487
  }
447
488
  }
448
489
  }
@@ -532,6 +573,18 @@ var VirtualFS = class {
532
573
  }
533
574
  break;
534
575
  }
576
+ case "delete": {
577
+ const abs = this.abs(op.path);
578
+ if (existsSync(abs)) {
579
+ const existing = await readFile(abs, "utf-8");
580
+ results.push({
581
+ path: op.path,
582
+ action: "delete",
583
+ diff: createPatch(op.path, existing, "")
584
+ });
585
+ }
586
+ break;
587
+ }
535
588
  }
536
589
  }
537
590
  return results;
@@ -937,17 +990,31 @@ function registerInitCommand(program2) {
937
990
  "--analytics [provider]",
938
991
  "Analytics provider: plausible, posthog, or none (default: plausible)",
939
992
  parseAnalyticsProvider
940
- ).option("--jobs", "Background jobs (Inngest; not implemented, ignored)").option("--a11y", "Accessibility tooling (coming soon, ignored)").option("--api-client", "API client generation (coming soon, ignored)").option("--pre-commit", "Enable prek pre-commit hooks").option("-y, --yes", "Skip prompts, accept defaults").option("--dry-run", "Show what would be generated without writing files").action(async (positionalName, opts) => {
993
+ ).option("--jobs", "Background jobs (Inngest; not implemented, ignored)").option("--a11y", "Accessibility tooling (coming soon, ignored)").option("--api-client", "API client generation (coming soon, ignored)").option("--utility-libs", "Add utility libraries and matching Agent Skills").option("--pre-commit", "Enable prek pre-commit hooks").option("-y, --yes", "Skip prompts, accept defaults").option("--dry-run", "Show what would be generated without writing files").action(async (positionalName, opts) => {
941
994
  const flags = {
942
995
  ...opts,
943
996
  name: opts.name ?? positionalName
944
997
  };
945
998
  const answers = flags.yes ? {} : await runTechPicker(flags);
946
999
  const config = resolveConfig(flags, answers);
947
- if (flags.jobs) {
1000
+ if (config.ci !== "none" && !isImplementedCiProvider(config.ci)) {
1001
+ console.log(
1002
+ ikea.muted(
1003
+ ` ${pc5.yellow("\u25B8")} CircleCI is coming soon; skipping CI generator.`
1004
+ )
1005
+ );
1006
+ }
1007
+ if (config.analytics !== "none" && !isImplementedAnalyticsProvider(config.analytics)) {
948
1008
  console.log(
949
1009
  ikea.muted(
950
- ` ${pc5.yellow("\u25B8")} Inngest jobs are not implemented yet; skipping --jobs.`
1010
+ ` ${pc5.yellow("\u25B8")} PostHog analytics is coming soon; skipping analytics generator.`
1011
+ )
1012
+ );
1013
+ }
1014
+ if (flags.jobs || answers.jobs) {
1015
+ console.log(
1016
+ ikea.muted(
1017
+ ` ${pc5.yellow("\u25B8")} Background jobs are coming soon; skipping Inngest.`
951
1018
  )
952
1019
  );
953
1020
  }
@@ -1097,12 +1164,12 @@ function getCliVersion() {
1097
1164
  }
1098
1165
 
1099
1166
  // src/sync/targets.ts
1100
- import { existsSync as existsSync5 } from "fs";
1167
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
1101
1168
  import { join as join6 } from "path";
1102
1169
 
1103
1170
  // src/config/eslint-config-contents.ts
1104
1171
  function shouldIgnorePrismaGeneratedOutput(config) {
1105
- return config.orm === "prisma";
1172
+ return hasPrisma(config);
1106
1173
  }
1107
1174
  function shouldIgnoreAiElementsGeneratedOutput(config) {
1108
1175
  return config.aiChat === true;
@@ -1126,9 +1193,11 @@ var AI_CHAT_DEPENDENCIES = {
1126
1193
  var BETTER_AUTH_VERSION = "^1.6.9";
1127
1194
  var BETTER_AUTH_PRISMA_ADAPTER_VERSION = "^1.6.9";
1128
1195
  var AUTH_DEPENDENCIES = {
1129
- "@better-auth/prisma-adapter": BETTER_AUTH_PRISMA_ADAPTER_VERSION,
1130
1196
  "better-auth": BETTER_AUTH_VERSION
1131
1197
  };
1198
+ var AUTH_PRISMA_DEPENDENCIES = {
1199
+ "@better-auth/prisma-adapter": BETTER_AUTH_PRISMA_ADAPTER_VERSION
1200
+ };
1132
1201
  function buildAuthEnvFragment() {
1133
1202
  return {
1134
1203
  variables: [
@@ -1276,17 +1345,42 @@ var STATE_DEPENDENCIES = {
1276
1345
  zustand: ZUSTAND_VERSION
1277
1346
  };
1278
1347
 
1348
+ // src/config/utility-libs-fragments.ts
1349
+ var MOTION_VERSION = "^12.38.0";
1350
+ var DATE_FNS_VERSION = "^4.1.0";
1351
+ var ES_TOOLKIT_VERSION = "^1.46.1";
1352
+ var UIDOTDEV_USEHOOKS_VERSION = "^2.4.1";
1353
+ var TS_PATTERN_VERSION = "^5.9.0";
1354
+ var UTILITY_LIBS_DEPENDENCIES = {
1355
+ "@uidotdev/usehooks": UIDOTDEV_USEHOOKS_VERSION,
1356
+ "date-fns": DATE_FNS_VERSION,
1357
+ "es-toolkit": ES_TOOLKIT_VERSION,
1358
+ motion: MOTION_VERSION,
1359
+ "ts-pattern": TS_PATTERN_VERSION
1360
+ };
1361
+
1279
1362
  // src/config/package-deps-baseline.ts
1280
- var PACKAGE_DEPS_BASELINE = {
1281
- ...AI_CHAT_DEPENDENCIES,
1282
- ...AUTH_DEPENDENCIES,
1363
+ var CORE_PACKAGE_DEPS_BASELINE = {
1283
1364
  ...ENV_MANAGEMENT_DEPENDENCIES,
1284
1365
  ...FORM_HANDLING_DEPENDENCIES,
1285
- ...I18N_DEPENDENCIES,
1286
- ...ORM_DEPENDENCIES,
1287
1366
  ...SERVER_ACTIONS_DEPENDENCIES,
1288
1367
  ...STATE_DEPENDENCIES
1289
1368
  };
1369
+ function buildPackageDepsBaseline(config) {
1370
+ return {
1371
+ ...CORE_PACKAGE_DEPS_BASELINE,
1372
+ ...hasPrisma(config) ? ORM_DEPENDENCIES : {},
1373
+ ...hasAuth(config) ? AUTH_DEPENDENCIES : {},
1374
+ ...config.authStrategy === "stateful" ? AUTH_PRISMA_DEPENDENCIES : {},
1375
+ ...hasAuth(config) ? SERVER_ACTIONS_AUTH_DEPENDENCIES : {},
1376
+ ...config.aiChat ? AI_CHAT_DEPENDENCIES : {},
1377
+ ...config.i18n ? I18N_DEPENDENCIES : {},
1378
+ ...config.utilityLibs ? UTILITY_LIBS_DEPENDENCIES : {}
1379
+ };
1380
+ }
1381
+ var PACKAGE_DEPS_BASELINE = {
1382
+ ...CORE_PACKAGE_DEPS_BASELINE
1383
+ };
1290
1384
 
1291
1385
  // src/config/eslint-prettier-fragments.ts
1292
1386
  var PRETTIER_VERSION = "^3.8.3";
@@ -1310,18 +1404,37 @@ var ESLINT_PRETTIER_SCRIPTS = {
1310
1404
  };
1311
1405
 
1312
1406
  // src/config/package-dev-deps-baseline.ts
1407
+ var CORE_PACKAGE_DEV_DEPS_BASELINE = {
1408
+ ...ESLINT_PRETTIER_DEV_DEPS
1409
+ };
1410
+ function buildPackageDevDepsBaseline(config) {
1411
+ return {
1412
+ ...CORE_PACKAGE_DEV_DEPS_BASELINE,
1413
+ ...hasPrisma(config) ? ORM_DEV_DEPENDENCIES : {}
1414
+ };
1415
+ }
1313
1416
  var PACKAGE_DEV_DEPS_BASELINE = {
1314
- ...ESLINT_PRETTIER_DEV_DEPS,
1315
- ...ORM_DEV_DEPENDENCIES
1417
+ ...CORE_PACKAGE_DEV_DEPS_BASELINE
1316
1418
  };
1317
1419
 
1318
1420
  // src/config/package-scripts-baseline.ts
1319
- var PACKAGE_SCRIPTS_BASELINE = {
1421
+ var CORE_PACKAGE_SCRIPTS = {
1320
1422
  dev: "next dev",
1321
1423
  build: "next build",
1322
- start: "next start",
1323
- ...ESLINT_PRETTIER_SCRIPTS,
1324
- ...ORM_SCRIPTS
1424
+ start: "next start"
1425
+ };
1426
+ var CORE_PACKAGE_SCRIPTS_BASELINE = {
1427
+ ...CORE_PACKAGE_SCRIPTS,
1428
+ ...ESLINT_PRETTIER_SCRIPTS
1429
+ };
1430
+ function buildPackageScriptsBaseline(config) {
1431
+ return {
1432
+ ...CORE_PACKAGE_SCRIPTS_BASELINE,
1433
+ ...hasPrisma(config) ? ORM_SCRIPTS : {}
1434
+ };
1435
+ }
1436
+ var PACKAGE_SCRIPTS_BASELINE = {
1437
+ ...CORE_PACKAGE_SCRIPTS_BASELINE
1325
1438
  };
1326
1439
 
1327
1440
  // src/config/pre-commit-fragments.ts
@@ -1357,7 +1470,7 @@ function buildPreCommitScripts(mode) {
1357
1470
 
1358
1471
  // src/config/next-config-contents.ts
1359
1472
  function shouldEnableAuthInterrupts(context) {
1360
- return context ? context.config.authStrategy !== "none" : false;
1473
+ return context ? hasAuth(context.config) : false;
1361
1474
  }
1362
1475
  function shouldEnableNextIntl(context) {
1363
1476
  return context ? context.config.i18n === true : false;
@@ -1435,7 +1548,8 @@ function inferOrmProvider(context) {
1435
1548
  }
1436
1549
  function inferAuthStrategy(context) {
1437
1550
  if (!context) return "none";
1438
- return existsSync5(join6(context.projectRoot, "src/lib/auth.ts")) ? "stateless" : "none";
1551
+ if (!existsSync5(join6(context.projectRoot, "src/lib/auth.ts"))) return "none";
1552
+ return inferOrmProvider(context) === "prisma" ? "stateful" : "stateless";
1439
1553
  }
1440
1554
  function inferAiChat(context) {
1441
1555
  if (!context) return false;
@@ -1445,6 +1559,24 @@ function inferI18n(context) {
1445
1559
  if (!context) return false;
1446
1560
  return existsSync5(join6(context.projectRoot, "src/i18n/routing.ts")) || existsSync5(join6(context.projectRoot, "messages/en.json"));
1447
1561
  }
1562
+ function inferUtilityLibs(context) {
1563
+ if (!context) return false;
1564
+ const packageJsonPath = join6(context.projectRoot, "package.json");
1565
+ if (!existsSync5(packageJsonPath)) return false;
1566
+ try {
1567
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
1568
+ const dependencies = packageJson.dependencies ?? {};
1569
+ return [
1570
+ "motion",
1571
+ "date-fns",
1572
+ "es-toolkit",
1573
+ "@uidotdev/usehooks",
1574
+ "ts-pattern"
1575
+ ].every((dependency) => dependency in dependencies);
1576
+ } catch {
1577
+ return false;
1578
+ }
1579
+ }
1448
1580
  function inferScaleStackConfig(context) {
1449
1581
  return {
1450
1582
  projectName: context ? "synced-project" : "project",
@@ -1457,6 +1589,7 @@ function inferScaleStackConfig(context) {
1457
1589
  jobs: false,
1458
1590
  a11y: false,
1459
1591
  apiClient: false,
1592
+ utilityLibs: inferUtilityLibs(context),
1460
1593
  preCommit: context ? hasPrekConfig(context) : false
1461
1594
  };
1462
1595
  }
@@ -1474,14 +1607,17 @@ var SYNC_TARGETS = [
1474
1607
  {
1475
1608
  relativePath: "package.json",
1476
1609
  kind: "json",
1477
- getDesiredFragment: (context) => ({
1478
- scripts: {
1479
- ...structuredClone(PACKAGE_SCRIPTS_BASELINE),
1480
- ...context && hasPrekConfig(context) ? buildPreCommitScripts(inferPreCommitMode(context.projectRoot)) : {}
1481
- },
1482
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE),
1483
- devDependencies: structuredClone(PACKAGE_DEV_DEPS_BASELINE)
1484
- })
1610
+ getDesiredFragment: (context) => {
1611
+ const config = inferScaleStackConfig(context);
1612
+ return {
1613
+ scripts: {
1614
+ ...buildPackageScriptsBaseline(config),
1615
+ ...context && hasPrekConfig(context) ? buildPreCommitScripts(inferPreCommitMode(context.projectRoot)) : {}
1616
+ },
1617
+ dependencies: buildPackageDepsBaseline(config),
1618
+ devDependencies: buildPackageDevDepsBaseline(config)
1619
+ };
1620
+ }
1485
1621
  },
1486
1622
  {
1487
1623
  relativePath: "next.config.ts",
@@ -2464,7 +2600,7 @@ var AI_CHAT_FILES = [
2464
2600
  ];
2465
2601
  function mergePackageJson(existing) {
2466
2602
  const desiredFragment = {
2467
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
2603
+ dependencies: structuredClone(AI_CHAT_DEPENDENCIES)
2468
2604
  };
2469
2605
  return mergeManagedPackageJson({
2470
2606
  existingContent: existing,
@@ -2511,9 +2647,6 @@ var FALLBACK_AGENTS_MD = `# AGENTS.md
2511
2647
  `;
2512
2648
  var FALLBACK_CLAUDE_MD = `@AGENTS.md
2513
2649
  `;
2514
- function hasAuth(config) {
2515
- return config.authStrategy !== "none";
2516
- }
2517
2650
  function buildTechnologyGuides(config) {
2518
2651
  const guides = [
2519
2652
  {
@@ -2562,11 +2695,6 @@ function buildTechnologyGuides(config) {
2562
2695
  purpose: "local service orchestration",
2563
2696
  selected: false
2564
2697
  },
2565
- {
2566
- name: "OpenTelemetry",
2567
- purpose: "provider-agnostic tracing",
2568
- selected: false
2569
- },
2570
2698
  {
2571
2699
  name: "Agent DevTools MCP",
2572
2700
  purpose: "local Next.js agent debugging",
@@ -2580,7 +2708,7 @@ function buildTechnologyGuides(config) {
2580
2708
  { name: "ESLint", purpose: "linting", selected: false },
2581
2709
  { name: "Prettier", purpose: "formatting", selected: false }
2582
2710
  ];
2583
- if (config.orm === "prisma") {
2711
+ if (hasPrisma(config)) {
2584
2712
  guides.push(
2585
2713
  { name: "Prisma v7", purpose: "ORM", selected: true },
2586
2714
  { name: "PostgreSQL", purpose: "database", selected: true },
@@ -2618,14 +2746,35 @@ function buildTechnologyGuides(config) {
2618
2746
  selected: true
2619
2747
  });
2620
2748
  }
2621
- if (config.ci === "github") {
2749
+ if (config.utilityLibs) {
2750
+ guides.push(
2751
+ { name: "Motion", purpose: "React animations", selected: true },
2752
+ { name: "date-fns", purpose: "date utilities", selected: true },
2753
+ {
2754
+ name: "es-toolkit",
2755
+ purpose: "general-purpose utilities",
2756
+ selected: true
2757
+ },
2758
+ {
2759
+ name: "@uidotdev/usehooks",
2760
+ purpose: "client-side React hooks",
2761
+ selected: true
2762
+ },
2763
+ {
2764
+ name: "ts-pattern",
2765
+ purpose: "exhaustive TypeScript pattern matching",
2766
+ selected: true
2767
+ }
2768
+ );
2769
+ }
2770
+ if (hasGithubActions(config)) {
2622
2771
  guides.push({
2623
2772
  name: "GitHub Actions",
2624
2773
  purpose: "CI/CD pipeline; pnpm lint and Docker build are active, while typecheck/test/e2e steps are TODO no-ops until scripts exist",
2625
2774
  selected: true
2626
2775
  });
2627
2776
  }
2628
- if (config.analytics === "plausible") {
2777
+ if (hasPlausible(config)) {
2629
2778
  guides.push(
2630
2779
  {
2631
2780
  name: "Plausible",
@@ -2700,11 +2849,17 @@ function buildInstalledSkillGuides(config) {
2700
2849
  purpose: "write validated, typed Server Actions"
2701
2850
  }
2702
2851
  ];
2703
- if (config.orm === "prisma") {
2704
- guides.push({
2705
- name: "prisma/skills",
2706
- purpose: "work with Prisma schema, migrations, and client code"
2707
- });
2852
+ if (hasPrisma(config)) {
2853
+ guides.push(
2854
+ {
2855
+ name: "prisma-cli",
2856
+ purpose: "run Prisma CLI commands safely"
2857
+ },
2858
+ {
2859
+ name: "prisma-client-api",
2860
+ purpose: "write Prisma Client query code"
2861
+ }
2862
+ );
2708
2863
  }
2709
2864
  if (hasAuth(config)) {
2710
2865
  guides.push({
@@ -2724,6 +2879,34 @@ function buildInstalledSkillGuides(config) {
2724
2879
  }
2725
2880
  );
2726
2881
  }
2882
+ if (config.utilityLibs) {
2883
+ guides.push(
2884
+ {
2885
+ name: "guide",
2886
+ purpose: "follow toss/es-toolkit usage guidance for installed utilities"
2887
+ },
2888
+ {
2889
+ name: "recommend",
2890
+ purpose: "choose es-toolkit utilities before adding utility alternatives"
2891
+ },
2892
+ {
2893
+ name: "motion",
2894
+ purpose: "build React animations with the installed Motion package"
2895
+ },
2896
+ {
2897
+ name: "date-fns",
2898
+ purpose: "parse, format, compare, and transform dates consistently"
2899
+ },
2900
+ {
2901
+ name: "usehooks",
2902
+ purpose: "use @uidotdev/usehooks for common client-side React hooks"
2903
+ },
2904
+ {
2905
+ name: "ts-pattern",
2906
+ purpose: "write exhaustive pattern matching for TypeScript unions"
2907
+ }
2908
+ );
2909
+ }
2727
2910
  return guides;
2728
2911
  }
2729
2912
  function replaceManagedSection(existing, startMarker, endMarker, section) {
@@ -2756,12 +2939,14 @@ function buildAgentsScaleStackSection(config) {
2756
2939
  const skillLines = buildInstalledSkillGuides(config).map(
2757
2940
  ({ name, purpose }) => `- \`${name}\`: ${purpose}.`
2758
2941
  );
2942
+ const utilityGuidanceLine = config.utilityLibs ? "- For animations, dates, utility functions, browser hooks, and pattern matching, prefer the installed utility libraries before adding alternatives." : void 0;
2759
2943
  return [
2760
2944
  SCALE_STACK_AGENTS_START,
2761
2945
  "## Scale Stack Guidance",
2762
2946
  "",
2763
2947
  "- Keep route-specific UI, hooks, state, actions, and AI artifacts colocated under private `_folders` inside the route segment that consumes them.",
2764
2948
  "- Use the listed Technology Stack by default; do not add alternatives unless the task requires it.",
2949
+ ...utilityGuidanceLine ? [utilityGuidanceLine] : [],
2765
2950
  "- When adding or changing environment variables, update `src/env.ts` and `env.example`; never bypass the schema.",
2766
2951
  "- Never commit `.env`, credentials, API keys, tokens, or other secrets.",
2767
2952
  "",
@@ -2822,8 +3007,9 @@ var aiAssistantGenerator = {
2822
3007
  resolveDependencies: (config) => [
2823
3008
  ...config.preCommit ? ["pre-commit"] : [],
2824
3009
  ...config.aiChat ? ["ai-chat"] : [],
2825
- ...config.ci !== "none" ? ["ci"] : [],
2826
- ...config.analytics !== "none" ? ["analytics"] : []
3010
+ ...hasGithubActions(config) ? ["ci"] : [],
3011
+ ...hasPlausible(config) ? ["analytics"] : [],
3012
+ ...config.utilityLibs ? ["utility-libs"] : []
2827
3013
  ],
2828
3014
  async execute(ctx) {
2829
3015
  ctx.fs.merge(
@@ -2838,7 +3024,6 @@ var aiAssistantGenerator = {
2838
3024
  // src/generators/analytics/analytics.ts
2839
3025
  var ANALYTICS_PROVIDER_PATH = "src/app/_providers/analytics-provider.tsx";
2840
3026
  var ANALYTICS_HELPER_PATH = "src/lib/analytics.ts";
2841
- var POSTHOG_NOT_IMPLEMENTED_MESSAGE = "PostHog analytics is not implemented yet; no analytics runtime files or dependencies generated.";
2842
3027
  var PLAUSIBLE_ENV_FRAGMENT = {
2843
3028
  variables: [
2844
3029
  {
@@ -2879,20 +3064,20 @@ async function writePlausibleFiles(ctx) {
2879
3064
  var analyticsGenerator = {
2880
3065
  name: "analytics",
2881
3066
  dependencies: ["env-management", "ui"],
2882
- condition: (config) => config.analytics !== "none",
3067
+ condition: (config) => isImplementedAnalyticsProvider(config.analytics),
2883
3068
  async execute(ctx) {
2884
- if (ctx.config.analytics === "posthog") {
2885
- ctx.logger.warn("analytics", POSTHOG_NOT_IMPLEMENTED_MESSAGE);
2886
- return;
2887
- }
2888
3069
  await writePlausibleFiles(ctx);
2889
3070
  }
2890
3071
  };
2891
3072
 
2892
3073
  // src/generators/orm/orm.ts
3074
+ var PHASE_13_ORM_AGENT_SKILLS = [
3075
+ "prisma-cli",
3076
+ "prisma-client-api"
3077
+ ];
2893
3078
  var PHASE_13_ORM_AGENT_SKILL_COMMAND = buildSkillsAddCommand(
2894
3079
  "prisma/skills",
2895
- []
3080
+ PHASE_13_ORM_AGENT_SKILLS
2896
3081
  );
2897
3082
  var PHASE_13_PRISMA_GENERATE_COMMAND = "pnpm db:generate";
2898
3083
  var ORM_FILES = [
@@ -2911,9 +3096,9 @@ var ORM_FILES = [
2911
3096
  ];
2912
3097
  function mergePackageJson2(existing) {
2913
3098
  const desiredFragment = {
2914
- scripts: structuredClone(PACKAGE_SCRIPTS_BASELINE),
2915
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE),
2916
- devDependencies: structuredClone(PACKAGE_DEV_DEPS_BASELINE)
3099
+ scripts: structuredClone(ORM_SCRIPTS),
3100
+ dependencies: structuredClone(ORM_DEPENDENCIES),
3101
+ devDependencies: structuredClone(ORM_DEV_DEPENDENCIES)
2917
3102
  };
2918
3103
  return mergeManagedPackageJson({
2919
3104
  existingContent: existing,
@@ -2930,7 +3115,7 @@ async function writeOrmFiles(ctx) {
2930
3115
  var ormGenerator = {
2931
3116
  name: "orm",
2932
3117
  dependencies: ["env-management"],
2933
- condition: (config) => config.orm === "prisma",
3118
+ condition: (config) => hasPrisma(config),
2934
3119
  async execute(ctx) {
2935
3120
  ctx.fs.merge("package.json", mergePackageJson2);
2936
3121
  await writeOrmFiles(ctx);
@@ -3001,9 +3186,15 @@ function buildAuthAfterInstallEnv(strategy, projectName) {
3001
3186
  }
3002
3187
  return env;
3003
3188
  }
3004
- function mergePackageJson3(existing) {
3189
+ function buildDependencies(strategy) {
3190
+ return {
3191
+ ...AUTH_DEPENDENCIES,
3192
+ ...isStatefulAuth(strategy) ? AUTH_PRISMA_DEPENDENCIES : {}
3193
+ };
3194
+ }
3195
+ function mergePackageJson3(existing, strategy = "none") {
3005
3196
  const desiredFragment = {
3006
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
3197
+ dependencies: buildDependencies(strategy)
3007
3198
  };
3008
3199
  return mergeManagedPackageJson({
3009
3200
  existingContent: existing,
@@ -3028,9 +3219,12 @@ var authGenerator = {
3028
3219
  name: "auth",
3029
3220
  dependencies: ["ui", "env-management"],
3030
3221
  resolveDependencies: (config) => isStatefulAuth(config.authStrategy) ? ["orm"] : [],
3031
- condition: (config) => config.authStrategy !== "none",
3222
+ condition: (config) => hasAuth(config),
3032
3223
  async execute(ctx) {
3033
- ctx.fs.merge("package.json", mergePackageJson3);
3224
+ ctx.fs.merge(
3225
+ "package.json",
3226
+ (existing) => mergePackageJson3(existing, ctx.config.authStrategy)
3227
+ );
3034
3228
  await writeAuthFiles(ctx);
3035
3229
  await ctx.exec(PHASE_14_AUTH_AGENT_SKILL_COMMAND, {
3036
3230
  cwd: ctx.targetDir,
@@ -3061,10 +3255,6 @@ var authGenerator = {
3061
3255
 
3062
3256
  // src/generators/ci/ci.ts
3063
3257
  var GITHUB_ACTIONS_WORKFLOW_PATH = ".github/workflows/ci.yml";
3064
- var CIRCLECI_CONFIG_PATH = ".circleci/config.yml";
3065
- function hasPrisma(config) {
3066
- return config.orm === "prisma";
3067
- }
3068
3258
  function buildPostgresServiceYaml() {
3069
3259
  return [
3070
3260
  " services:",
@@ -3156,16 +3346,9 @@ var ciGenerator = {
3156
3346
  return hasPrisma(config) ? ["orm"] : [];
3157
3347
  },
3158
3348
  condition(config) {
3159
- return config.ci !== "none";
3349
+ return isImplementedCiProvider(config.ci);
3160
3350
  },
3161
3351
  async execute(ctx) {
3162
- if (ctx.config.ci === "circleci") {
3163
- ctx.logger.warn(
3164
- "ci",
3165
- `CircleCI coming soon; no ${CIRCLECI_CONFIG_PATH} generated yet.`
3166
- );
3167
- return;
3168
- }
3169
3352
  ctx.fs.write(
3170
3353
  GITHUB_ACTIONS_WORKFLOW_PATH,
3171
3354
  buildGithubActionsWorkflow(ctx.config)
@@ -3186,8 +3369,8 @@ function buildPrettierIgnoreContents(config) {
3186
3369
  }
3187
3370
  function mergePackageJson4(existing) {
3188
3371
  const desiredFragment = {
3189
- scripts: structuredClone(PACKAGE_SCRIPTS_BASELINE),
3190
- devDependencies: structuredClone(PACKAGE_DEV_DEPS_BASELINE)
3372
+ scripts: structuredClone(ESLINT_PRETTIER_SCRIPTS),
3373
+ devDependencies: structuredClone(ESLINT_PRETTIER_DEV_DEPS)
3191
3374
  };
3192
3375
  return mergeManagedPackageJson({
3193
3376
  existingContent: existing,
@@ -3293,7 +3476,7 @@ var BASELINE_ENV_FRAGMENT = {
3293
3476
  };
3294
3477
  function mergePackageJson5(existing) {
3295
3478
  const desiredFragment = {
3296
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
3479
+ dependencies: structuredClone(ENV_MANAGEMENT_DEPENDENCIES)
3297
3480
  };
3298
3481
  return mergeManagedPackageJson({
3299
3482
  existingContent: existing,
@@ -3352,6 +3535,15 @@ function mergeGitignoreContent(existing) {
3352
3535
  return `${parts.join("\n")}
3353
3536
  `;
3354
3537
  }
3538
+ function mergePackageJson6(existing) {
3539
+ return mergeManagedPackageJson({
3540
+ existingContent: existing,
3541
+ desiredFragment: {
3542
+ scripts: structuredClone(CORE_PACKAGE_SCRIPTS)
3543
+ },
3544
+ sortSections: []
3545
+ });
3546
+ }
3355
3547
  async function writeNextConfig(ctx) {
3356
3548
  ctx.fs.write(
3357
3549
  "next.config.ts",
@@ -3364,12 +3556,7 @@ async function writeNextConfig(ctx) {
3364
3556
  );
3365
3557
  }
3366
3558
  async function writeAppOverlays(ctx) {
3367
- const { projectName } = ctx.config;
3368
- const [layout, loading] = await Promise.all([
3369
- ctx.template.renderFile("core/layout.tsx.ejs", { projectName }),
3370
- ctx.template.renderFile("core/loading.tsx.ejs", {})
3371
- ]);
3372
- ctx.fs.write("src/app/layout.tsx", layout);
3559
+ const loading = await ctx.template.renderFile("core/loading.tsx.ejs", {});
3373
3560
  ctx.fs.write(
3374
3561
  localizeAppRoutePath("src/app/loading.tsx", ctx.config.i18n),
3375
3562
  loading
@@ -3385,6 +3572,7 @@ var projectInitGenerator = {
3385
3572
  await ctx.exec(cmd, { cwd: parentDir });
3386
3573
  await writeNextConfig(ctx);
3387
3574
  await writeAppOverlays(ctx);
3575
+ ctx.fs.merge("package.json", mergePackageJson6);
3388
3576
  ctx.fs.merge(
3389
3577
  "tsconfig.json",
3390
3578
  (existing) => mergeTsconfigContent(existing, ctx.config)
@@ -3402,18 +3590,12 @@ var POSTGRES_IMAGE = "postgres:16-alpine";
3402
3590
  var CLICKHOUSE_IMAGE = "clickhouse/clickhouse-server:24.12-alpine";
3403
3591
  var PLAUSIBLE_IMAGE = "ghcr.io/plausible/community-edition:v3.2.0";
3404
3592
  var PLAUSIBLE_LOCAL_SECRET_KEY_BASE = "local-only-change-me-local-only-change-me-local-only-change-me-local-only-change-me";
3405
- function hasPrisma2(config) {
3406
- return config.orm === "prisma";
3407
- }
3408
- function hasPlausible(config) {
3409
- return config.analytics === "plausible";
3410
- }
3411
3593
  function buildComposeDatabaseUrl(projectName) {
3412
3594
  return `postgresql://postgres:postgres@postgres:5432/${buildPostgresDatabaseName(projectName)}`;
3413
3595
  }
3414
3596
  function buildAppEnvironment(config) {
3415
3597
  const lines = [' PORT: "3000"', ' HOSTNAME: "0.0.0.0"'];
3416
- if (hasPrisma2(config)) {
3598
+ if (hasPrisma(config)) {
3417
3599
  lines.push(
3418
3600
  ` DATABASE_URL: ${buildComposeDatabaseUrl(config.projectName)}`
3419
3601
  );
@@ -3423,7 +3605,7 @@ function buildAppEnvironment(config) {
3423
3605
  }
3424
3606
  function buildAppDependsOn(config) {
3425
3607
  const lines = [];
3426
- if (hasPrisma2(config)) {
3608
+ if (hasPrisma(config)) {
3427
3609
  lines.push(" postgres:");
3428
3610
  lines.push(" condition: service_healthy");
3429
3611
  }
@@ -3434,7 +3616,7 @@ function buildAppDependsOn(config) {
3434
3616
  return lines;
3435
3617
  }
3436
3618
  function buildPostgresService(config) {
3437
- if (!hasPrisma2(config)) return [];
3619
+ if (!hasPrisma(config)) return [];
3438
3620
  const databaseName = buildPostgresDatabaseName(config.projectName);
3439
3621
  return [
3440
3622
  " postgres:",
@@ -3528,7 +3710,7 @@ function buildPlausibleServices() {
3528
3710
  }
3529
3711
  function buildVolumes(config) {
3530
3712
  const lines = [];
3531
- if (hasPrisma2(config)) {
3713
+ if (hasPrisma(config)) {
3532
3714
  lines.push(" postgres-data:");
3533
3715
  }
3534
3716
  if (hasPlausible(config)) {
@@ -3563,7 +3745,7 @@ function buildDockerComposeYaml(config) {
3563
3745
  ].join("\n");
3564
3746
  }
3565
3747
  function buildPrismaRunnerAdditions(config) {
3566
- if (!hasPrisma2(config)) return [];
3748
+ if (!hasPrisma(config)) return [];
3567
3749
  return [
3568
3750
  "RUN apt-get update \\",
3569
3751
  " && apt-get install -y --no-install-recommends openssl postgresql-client \\",
@@ -3579,7 +3761,7 @@ function buildPrismaRunnerAdditions(config) {
3579
3761
  ];
3580
3762
  }
3581
3763
  function buildPrismaBuilderEnvironment(config) {
3582
- if (!hasPrisma2(config)) return [];
3764
+ if (!hasPrisma(config)) return [];
3583
3765
  return [
3584
3766
  `ENV DATABASE_URL=${buildComposeDatabaseUrl(config.projectName)}`,
3585
3767
  ""
@@ -3643,7 +3825,7 @@ function buildStartSh(config) {
3643
3825
  "",
3644
3826
  'if [ "${RUN_MIGRATIONS:-false}" = "true" ]; then'
3645
3827
  ];
3646
- if (hasPrisma2(config)) {
3828
+ if (hasPrisma(config)) {
3647
3829
  lines.push(
3648
3830
  ' until pg_isready -d "${DATABASE_URL}"; do',
3649
3831
  ' echo "Waiting for database..."',
@@ -3684,7 +3866,7 @@ var dockerGenerator = {
3684
3866
  name: "docker",
3685
3867
  dependencies: ["project-init"],
3686
3868
  resolveDependencies(config) {
3687
- return hasPrisma2(config) ? ["orm"] : [];
3869
+ return hasPrisma(config) ? ["orm"] : [];
3688
3870
  },
3689
3871
  async execute(ctx) {
3690
3872
  ctx.fs.write("Dockerfile", buildDockerfile(ctx.config));
@@ -3721,66 +3903,12 @@ var errorHandlingGenerator = {
3721
3903
  const content = await ctx.template.renderFile(file.templatePath, {
3722
3904
  i18n: ctx.config.i18n
3723
3905
  });
3724
- const outputPath = file.outputPath === "src/app/global-error.tsx" || file.outputPath === "src/app/[...not-found]/page.tsx" ? file.outputPath : localizeAppRoutePath(file.outputPath, ctx.config.i18n);
3906
+ const outputPath = file.outputPath === "src/app/global-error.tsx" ? file.outputPath : localizeAppRoutePath(file.outputPath, ctx.config.i18n);
3725
3907
  ctx.fs.write(outputPath, content);
3726
3908
  }
3727
3909
  }
3728
3910
  };
3729
3911
 
3730
- // src/generators/server-actions/server-actions.ts
3731
- var PHASE_15_SERVER_ACTIONS_AGENT_SKILL_COMMAND = buildSkillsAddCommand("next-safe-action/skills", []);
3732
- var SERVER_ACTIONS_FILES = [
3733
- {
3734
- templatePath: "server-actions/safe-action.ts.ejs",
3735
- outputPath: "src/lib/safe-action.ts"
3736
- }
3737
- ];
3738
- function hasAuth2(strategy) {
3739
- return strategy !== "none";
3740
- }
3741
- function buildDependencies(strategy) {
3742
- const dependencies = structuredClone(PACKAGE_DEPS_BASELINE);
3743
- if (hasAuth2(strategy)) {
3744
- Object.assign(dependencies, SERVER_ACTIONS_AUTH_DEPENDENCIES);
3745
- }
3746
- return dependencies;
3747
- }
3748
- function mergePackageJson6(existing, strategy = "none") {
3749
- const desiredFragment = {
3750
- dependencies: buildDependencies(strategy)
3751
- };
3752
- return mergeManagedPackageJson({
3753
- existingContent: existing,
3754
- desiredFragment,
3755
- sortSections: ["dependencies"]
3756
- });
3757
- }
3758
- async function writeServerActionFiles(ctx) {
3759
- const hasAuthClient = hasAuth2(ctx.config.authStrategy);
3760
- for (const file of SERVER_ACTIONS_FILES) {
3761
- const content = await ctx.template.renderFile(file.templatePath, {
3762
- hasAuth: hasAuthClient
3763
- });
3764
- ctx.fs.write(file.outputPath, content);
3765
- }
3766
- }
3767
- var serverActionsGenerator = {
3768
- name: "server-actions",
3769
- dependencies: ["ui", "env-management"],
3770
- resolveDependencies: (config) => hasAuth2(config.authStrategy) ? ["auth"] : [],
3771
- async execute(ctx) {
3772
- ctx.fs.merge(
3773
- "package.json",
3774
- (existing) => mergePackageJson6(existing, ctx.config.authStrategy)
3775
- );
3776
- await writeServerActionFiles(ctx);
3777
- await ctx.exec(PHASE_15_SERVER_ACTIONS_AGENT_SKILL_COMMAND, {
3778
- cwd: ctx.targetDir,
3779
- env: { ...AGENT_SKILLS_EXEC_ENV }
3780
- });
3781
- }
3782
- };
3783
-
3784
3912
  // src/generators/form-handling/form-handling.ts
3785
3913
  var FORM_HANDLING_FILES = [
3786
3914
  {
@@ -3802,7 +3930,7 @@ var FORM_HANDLING_FILES = [
3802
3930
  ];
3803
3931
  function mergePackageJson7(existing) {
3804
3932
  const desiredFragment = {
3805
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
3933
+ dependencies: structuredClone(FORM_HANDLING_DEPENDENCIES)
3806
3934
  };
3807
3935
  return mergeManagedPackageJson({
3808
3936
  existingContent: existing,
@@ -3811,7 +3939,7 @@ function mergePackageJson7(existing) {
3811
3939
  });
3812
3940
  }
3813
3941
  async function writeFormHandlingFiles(ctx) {
3814
- const hasAuthClient = hasAuth2(ctx.config.authStrategy);
3942
+ const hasAuthClient = hasAuth(ctx.config.authStrategy);
3815
3943
  for (const file of FORM_HANDLING_FILES) {
3816
3944
  const content = await ctx.template.renderFile(file.templatePath, {
3817
3945
  hasAuth: hasAuthClient,
@@ -3832,6 +3960,24 @@ var formHandlingGenerator = {
3832
3960
  }
3833
3961
  };
3834
3962
 
3963
+ // src/generators/app-layout.ts
3964
+ function getAppLayoutTemplateData({
3965
+ projectName,
3966
+ variant
3967
+ }) {
3968
+ return {
3969
+ templatePath: variant === "locale" ? "i18n/locale-layout.tsx.ejs" : "core/layout.tsx.ejs",
3970
+ data: {
3971
+ projectName,
3972
+ includeClientSideWrappers: variant === "root-with-wrappers"
3973
+ }
3974
+ };
3975
+ }
3976
+ async function renderAppLayoutContents(template, options) {
3977
+ const { templatePath, data } = getAppLayoutTemplateData(options);
3978
+ return template.renderFile(templatePath, data);
3979
+ }
3980
+
3835
3981
  // src/generators/i18n/i18n.ts
3836
3982
  var I18N_FILES = [
3837
3983
  {
@@ -3851,7 +3997,7 @@ var I18N_FILES = [
3851
3997
  outputPath: "src/i18n/next-intl.d.ts"
3852
3998
  },
3853
3999
  {
3854
- templatePath: "i18n/locale-layout.tsx.ejs",
4000
+ templatePath: null,
3855
4001
  outputPath: "src/app/[locale]/layout.tsx"
3856
4002
  },
3857
4003
  {
@@ -3865,7 +4011,7 @@ var I18N_FILES = [
3865
4011
  ];
3866
4012
  function mergePackageJson8(existing) {
3867
4013
  const desiredFragment = {
3868
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
4014
+ dependencies: structuredClone(I18N_DEPENDENCIES)
3869
4015
  };
3870
4016
  return mergeManagedPackageJson({
3871
4017
  existingContent: existing,
@@ -3873,9 +4019,16 @@ function mergePackageJson8(existing) {
3873
4019
  sortSections: ["dependencies"]
3874
4020
  });
3875
4021
  }
4022
+ function deleteRootRouteFiles(ctx) {
4023
+ ctx.fs.delete("src/app/layout.tsx");
4024
+ ctx.fs.delete("src/app/page.tsx");
4025
+ }
3876
4026
  async function writeI18nFiles(ctx) {
3877
4027
  for (const file of I18N_FILES) {
3878
- const content = await ctx.template.renderFile(file.templatePath, {
4028
+ const content = file.templatePath === null ? await renderAppLayoutContents(ctx.template, {
4029
+ projectName: ctx.config.projectName,
4030
+ variant: "locale"
4031
+ }) : await ctx.template.renderFile(file.templatePath, {
3879
4032
  projectName: ctx.config.projectName
3880
4033
  });
3881
4034
  ctx.fs.write(file.outputPath, content);
@@ -3885,12 +4038,13 @@ var i18nGenerator = {
3885
4038
  name: "i18n",
3886
4039
  dependencies: ["ui", "error-handling", "form-handling"],
3887
4040
  resolveDependencies: (config) => [
3888
- ...config.authStrategy !== "none" ? ["auth"] : [],
4041
+ ...hasAuth(config) ? ["auth"] : [],
3889
4042
  ...config.aiChat ? ["ai-chat"] : []
3890
4043
  ],
3891
4044
  condition: (config) => config.i18n === true,
3892
4045
  async execute(ctx) {
3893
4046
  ctx.fs.merge("package.json", mergePackageJson8);
4047
+ deleteRootRouteFiles(ctx);
3894
4048
  await writeI18nFiles(ctx);
3895
4049
  }
3896
4050
  };
@@ -3930,18 +4084,69 @@ var proxyGenerator = {
3930
4084
  name: "proxy",
3931
4085
  dependencies: ["project-init"],
3932
4086
  resolveDependencies: (config) => [
3933
- ...config.authStrategy !== "none" ? ["auth"] : [],
4087
+ ...hasAuth(config) ? ["auth"] : [],
3934
4088
  ...config.i18n ? ["i18n"] : []
3935
4089
  ],
3936
4090
  async execute(ctx) {
3937
4091
  const content = await ctx.template.renderFile("proxy/proxy.ts.ejs", {
3938
- hasAuth: ctx.config.authStrategy !== "none",
4092
+ hasAuth: hasAuth(ctx.config),
3939
4093
  hasI18n: ctx.config.i18n
3940
4094
  });
3941
4095
  ctx.fs.write(PROXY_OUTPUT_PATH, content);
3942
4096
  }
3943
4097
  };
3944
4098
 
4099
+ // src/generators/server-actions/server-actions.ts
4100
+ var PHASE_15_SERVER_ACTIONS_AGENT_SKILL_COMMAND = buildSkillsAddCommand("next-safe-action/skills", []);
4101
+ var SERVER_ACTIONS_FILES = [
4102
+ {
4103
+ templatePath: "server-actions/safe-action.ts.ejs",
4104
+ outputPath: "src/lib/safe-action.ts"
4105
+ }
4106
+ ];
4107
+ function buildDependencies2(strategy) {
4108
+ const dependencies = structuredClone(SERVER_ACTIONS_DEPENDENCIES);
4109
+ if (hasAuth(strategy)) {
4110
+ Object.assign(dependencies, SERVER_ACTIONS_AUTH_DEPENDENCIES);
4111
+ }
4112
+ return dependencies;
4113
+ }
4114
+ function mergePackageJson10(existing, strategy = "none") {
4115
+ const desiredFragment = {
4116
+ dependencies: buildDependencies2(strategy)
4117
+ };
4118
+ return mergeManagedPackageJson({
4119
+ existingContent: existing,
4120
+ desiredFragment,
4121
+ sortSections: ["dependencies"]
4122
+ });
4123
+ }
4124
+ async function writeServerActionFiles(ctx) {
4125
+ const hasAuthClient = hasAuth(ctx.config.authStrategy);
4126
+ for (const file of SERVER_ACTIONS_FILES) {
4127
+ const content = await ctx.template.renderFile(file.templatePath, {
4128
+ hasAuth: hasAuthClient
4129
+ });
4130
+ ctx.fs.write(file.outputPath, content);
4131
+ }
4132
+ }
4133
+ var serverActionsGenerator = {
4134
+ name: "server-actions",
4135
+ dependencies: ["ui", "env-management"],
4136
+ resolveDependencies: (config) => hasAuth(config.authStrategy) ? ["auth"] : [],
4137
+ async execute(ctx) {
4138
+ ctx.fs.merge(
4139
+ "package.json",
4140
+ (existing) => mergePackageJson10(existing, ctx.config.authStrategy)
4141
+ );
4142
+ await writeServerActionFiles(ctx);
4143
+ await ctx.exec(PHASE_15_SERVER_ACTIONS_AGENT_SKILL_COMMAND, {
4144
+ cwd: ctx.targetDir,
4145
+ env: { ...AGENT_SKILLS_EXEC_ENV }
4146
+ });
4147
+ }
4148
+ };
4149
+
3945
4150
  // src/generators/state/state.ts
3946
4151
  var PHASE_9_STATE_AGENT_SKILL_COMMANDS = [
3947
4152
  buildSkillsAddCommand("https://github.com/pproenca/dot-skills", ["nuqs"]),
@@ -3949,9 +4154,9 @@ var PHASE_9_STATE_AGENT_SKILL_COMMANDS = [
3949
4154
  "zustand-5"
3950
4155
  ])
3951
4156
  ];
3952
- function mergePackageJson10(existing) {
4157
+ function mergePackageJson11(existing) {
3953
4158
  const desiredFragment = {
3954
- dependencies: structuredClone(PACKAGE_DEPS_BASELINE)
4159
+ dependencies: structuredClone(STATE_DEPENDENCIES)
3955
4160
  };
3956
4161
  return mergeManagedPackageJson({
3957
4162
  existingContent: existing,
@@ -3963,7 +4168,7 @@ var stateGenerator = {
3963
4168
  name: "state",
3964
4169
  dependencies: ["ui"],
3965
4170
  async execute(ctx) {
3966
- ctx.fs.merge("package.json", mergePackageJson10);
4171
+ ctx.fs.merge("package.json", mergePackageJson11);
3967
4172
  for (const command of PHASE_9_STATE_AGENT_SKILL_COMMANDS) {
3968
4173
  await ctx.exec(command, {
3969
4174
  cwd: ctx.targetDir,
@@ -3975,8 +4180,6 @@ var stateGenerator = {
3975
4180
 
3976
4181
  // src/generators/ui/ui.ts
3977
4182
  var SHADCN_PRESET = "nova";
3978
- var REACT_SUSPENSE_IMPORT = 'import { Suspense } from "react";';
3979
- var CLIENT_SIDE_WRAPPERS_IMPORT = 'import { ClientSideWrappers } from "./_providers/client-side-wrappers";';
3980
4183
  var SONNER_OPTIONAL_THEME_CAST = 'theme={theme as ToasterProps["theme"]}';
3981
4184
  var SONNER_NON_NULLABLE_THEME_CAST = 'theme={theme as NonNullable<ToasterProps["theme"]>}';
3982
4185
  var SELF_REFERENTIAL_FONT_SANS = "--font-sans: var(--font-sans);";
@@ -3992,33 +4195,6 @@ function buildShadcnAddAllCommand() {
3992
4195
  function buildShadcnSkillAddCommand() {
3993
4196
  return "pnpm dlx skills add shadcn/ui -y";
3994
4197
  }
3995
- function mergeRootLayoutWithClientSideWrappers(existing) {
3996
- if (existing.includes("<ClientSideWrappers>")) return existing;
3997
- const importMarker = 'import "./globals.css";\n';
3998
- if (!existing.includes(importMarker)) {
3999
- throw new Error("Could not find globals.css import in root layout.");
4000
- }
4001
- const childrenMarker = [
4002
- " {/* Scale Stack: wrap with NuqsAdapter and other root providers under src/app/_providers/ */}",
4003
- " {children}"
4004
- ].join("\n");
4005
- if (!existing.includes(childrenMarker)) {
4006
- throw new Error("Could not find root layout children slot.");
4007
- }
4008
- return existing.replace(
4009
- importMarker,
4010
- `${importMarker}${REACT_SUSPENSE_IMPORT}
4011
- ${CLIENT_SIDE_WRAPPERS_IMPORT}
4012
- `
4013
- ).replace(
4014
- childrenMarker,
4015
- [
4016
- " <Suspense>",
4017
- " <ClientSideWrappers>{children}</ClientSideWrappers>",
4018
- " </Suspense>"
4019
- ].join("\n")
4020
- );
4021
- }
4022
4198
  function fixSonnerExactOptionalTheme(existing) {
4023
4199
  if (existing.includes(SONNER_NON_NULLABLE_THEME_CAST)) return existing;
4024
4200
  return existing.replace(
@@ -4033,13 +4209,13 @@ async function writeUiFiles(ctx) {
4033
4209
  const [page, clientSideWrappers] = await Promise.all([
4034
4210
  ctx.template.renderFile("ui/page.tsx.ejs", {
4035
4211
  projectName: ctx.config.projectName,
4036
- hasAuth: ctx.config.authStrategy !== "none",
4212
+ hasAuth: hasAuth(ctx.config),
4037
4213
  hasAiChat: ctx.config.aiChat,
4038
4214
  isStatefulAuth: ctx.config.authStrategy === "stateful",
4039
4215
  i18n: ctx.config.i18n
4040
4216
  }),
4041
4217
  ctx.template.renderFile("ui/client-side-wrappers.tsx.ejs", {
4042
- hasAnalyticsProvider: ctx.config.analytics === "plausible"
4218
+ hasAnalyticsProvider: hasPlausible(ctx.config)
4043
4219
  })
4044
4220
  ]);
4045
4221
  ctx.fs.write(localizeAppRoutePath("src/app/page.tsx", ctx.config.i18n), page);
@@ -4049,7 +4225,14 @@ async function writeUiFiles(ctx) {
4049
4225
  );
4050
4226
  }
4051
4227
  async function wireRootLayout(ctx) {
4052
- ctx.fs.merge("src/app/layout.tsx", mergeRootLayoutWithClientSideWrappers);
4228
+ if (ctx.config.i18n) return;
4229
+ ctx.fs.write(
4230
+ "src/app/layout.tsx",
4231
+ await renderAppLayoutContents(ctx.template, {
4232
+ projectName: ctx.config.projectName,
4233
+ variant: "root-with-wrappers"
4234
+ })
4235
+ );
4053
4236
  }
4054
4237
  async function patchGeneratedComponentFiles(ctx) {
4055
4238
  ctx.fs.merge("src/app/globals.css", fixGeneratedFontVariables);
@@ -4068,6 +4251,63 @@ var uiGenerator = {
4068
4251
  }
4069
4252
  };
4070
4253
 
4254
+ // src/generators/utility-libs/utility-libs.ts
4255
+ var UTILITY_LIBS_ES_TOOLKIT_AGENT_SKILLS = [
4256
+ "guide",
4257
+ "recommend"
4258
+ ];
4259
+ var UTILITY_LIBS_ES_TOOLKIT_AGENT_SKILL_COMMAND = buildSkillsAddCommand(
4260
+ "toss/es-toolkit",
4261
+ UTILITY_LIBS_ES_TOOLKIT_AGENT_SKILLS
4262
+ );
4263
+ var UTILITY_LIBS_LOCAL_SKILL_FILES = [
4264
+ {
4265
+ templatePath: "utility-libs/motion.SKILL.md.ejs",
4266
+ outputPath: ".agents/skills/motion/SKILL.md"
4267
+ },
4268
+ {
4269
+ templatePath: "utility-libs/date-fns.SKILL.md.ejs",
4270
+ outputPath: ".agents/skills/date-fns/SKILL.md"
4271
+ },
4272
+ {
4273
+ templatePath: "utility-libs/usehooks.SKILL.md.ejs",
4274
+ outputPath: ".agents/skills/usehooks/SKILL.md"
4275
+ },
4276
+ {
4277
+ templatePath: "utility-libs/ts-pattern.SKILL.md.ejs",
4278
+ outputPath: ".agents/skills/ts-pattern/SKILL.md"
4279
+ }
4280
+ ];
4281
+ function mergePackageJson12(existing) {
4282
+ const desiredFragment = {
4283
+ dependencies: structuredClone(UTILITY_LIBS_DEPENDENCIES)
4284
+ };
4285
+ return mergeManagedPackageJson({
4286
+ existingContent: existing,
4287
+ desiredFragment,
4288
+ sortSections: ["dependencies"]
4289
+ });
4290
+ }
4291
+ async function writeLocalSkillFiles(ctx) {
4292
+ for (const file of UTILITY_LIBS_LOCAL_SKILL_FILES) {
4293
+ const content = await ctx.template.renderFile(file.templatePath, {});
4294
+ ctx.fs.write(file.outputPath, content);
4295
+ }
4296
+ }
4297
+ var utilityLibsGenerator = {
4298
+ name: "utility-libs",
4299
+ dependencies: ["agent-skills-init"],
4300
+ condition: (config) => config.utilityLibs === true,
4301
+ async execute(ctx) {
4302
+ ctx.fs.merge("package.json", mergePackageJson12);
4303
+ await ctx.exec(UTILITY_LIBS_ES_TOOLKIT_AGENT_SKILL_COMMAND, {
4304
+ cwd: ctx.targetDir,
4305
+ env: { ...AGENT_SKILLS_EXEC_ENV }
4306
+ });
4307
+ await writeLocalSkillFiles(ctx);
4308
+ }
4309
+ };
4310
+
4071
4311
  // src/generators/register.ts
4072
4312
  registerGenerator(projectInitGenerator);
4073
4313
  registerGenerator(proxyGenerator);
@@ -4087,6 +4327,7 @@ registerGenerator(ciGenerator);
4087
4327
  registerGenerator(analyticsGenerator);
4088
4328
  registerGenerator(aiChatGenerator);
4089
4329
  registerGenerator(i18nGenerator);
4330
+ registerGenerator(utilityLibsGenerator);
4090
4331
  registerGenerator(aiAssistantGenerator);
4091
4332
 
4092
4333
  // src/cli/ui/banner.ts