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/CHANGELOG.md +17 -0
- package/README.md +34 -66
- package/dist/index.js +438 -197
- package/package.json +1 -1
- package/templates/ai-chat/layout.tsx.ejs +2 -6
- package/templates/ai-chat/page.tsx.ejs +8 -2
- package/templates/core/layout.tsx.ejs +10 -0
- package/templates/form-handling/dashboard-page.tsx.ejs +8 -2
- package/templates/i18n/locale-layout.tsx.ejs +44 -8
- package/templates/ui/page.tsx.ejs +8 -2
- package/templates/utility-libs/date-fns.SKILL.md.ejs +34 -0
- package/templates/utility-libs/motion.SKILL.md.ejs +234 -0
- package/templates/utility-libs/ts-pattern.SKILL.md.ejs +44 -0
- package/templates/utility-libs/usehooks.SKILL.md.ejs +38 -0
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
|
-
|
|
118
|
-
|
|
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 (
|
|
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")}
|
|
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
|
|
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
|
|
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
|
-
...
|
|
1315
|
-
...ORM_DEV_DEPENDENCIES
|
|
1417
|
+
...CORE_PACKAGE_DEV_DEPS_BASELINE
|
|
1316
1418
|
};
|
|
1317
1419
|
|
|
1318
1420
|
// src/config/package-scripts-baseline.ts
|
|
1319
|
-
var
|
|
1421
|
+
var CORE_PACKAGE_SCRIPTS = {
|
|
1320
1422
|
dev: "next dev",
|
|
1321
1423
|
build: "next build",
|
|
1322
|
-
start: "next start"
|
|
1323
|
-
|
|
1324
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
2704
|
-
guides.push(
|
|
2705
|
-
|
|
2706
|
-
|
|
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
|
|
2826
|
-
...config
|
|
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
|
|
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(
|
|
2915
|
-
dependencies: structuredClone(
|
|
2916
|
-
devDependencies: structuredClone(
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
3222
|
+
condition: (config) => hasAuth(config),
|
|
3032
3223
|
async execute(ctx) {
|
|
3033
|
-
ctx.fs.merge(
|
|
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
|
|
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(
|
|
3190
|
-
devDependencies: structuredClone(
|
|
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(
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
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"
|
|
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(
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4157
|
+
function mergePackageJson11(existing) {
|
|
3953
4158
|
const desiredFragment = {
|
|
3954
|
-
dependencies: structuredClone(
|
|
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",
|
|
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
|
|
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
|
|
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.
|
|
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
|