create-cronus-stack 0.6.0 → 0.6.1
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/scaffold.js +456 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/scaffold.js
CHANGED
|
@@ -41,6 +41,79 @@ function cronusUiPaths(config) {
|
|
|
41
41
|
function usesCronusUi(config) {
|
|
42
42
|
return single(config, "ui") === "ui-cronus";
|
|
43
43
|
}
|
|
44
|
+
const SQL_DATABASES = new Set(["db-sqlite", "db-postgres", "db-mysql"]);
|
|
45
|
+
const HOSTED_DB_SETUPS = new Set([
|
|
46
|
+
"dbsetup-turso",
|
|
47
|
+
"dbsetup-neon",
|
|
48
|
+
"dbsetup-supabase",
|
|
49
|
+
"dbsetup-planetscale",
|
|
50
|
+
"dbsetup-d1",
|
|
51
|
+
"dbsetup-atlas",
|
|
52
|
+
]);
|
|
53
|
+
function sqlDialect(config) {
|
|
54
|
+
switch (single(config, "database")) {
|
|
55
|
+
case "db-sqlite":
|
|
56
|
+
return "sqlite";
|
|
57
|
+
case "db-postgres":
|
|
58
|
+
return "postgresql";
|
|
59
|
+
case "db-mysql":
|
|
60
|
+
return "mysql";
|
|
61
|
+
default:
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function emitsDrizzle(config) {
|
|
66
|
+
return (single(config, "web") === "web-next" &&
|
|
67
|
+
single(config, "orm") === "orm-drizzle" &&
|
|
68
|
+
SQL_DATABASES.has(single(config, "database") ?? ""));
|
|
69
|
+
}
|
|
70
|
+
function emitsBetterAuth(config) {
|
|
71
|
+
return emitsDrizzle(config) && single(config, "auth") === "auth-better-auth";
|
|
72
|
+
}
|
|
73
|
+
function usesImportAlias(config) {
|
|
74
|
+
return single(config, "importAlias") === "import-alias";
|
|
75
|
+
}
|
|
76
|
+
function sourceRoot(config) {
|
|
77
|
+
return single(config, "structure") === "structure-root" ? "" : "src/";
|
|
78
|
+
}
|
|
79
|
+
function dbDir(config) {
|
|
80
|
+
return `${sourceRoot(config)}db`;
|
|
81
|
+
}
|
|
82
|
+
function libDir(config) {
|
|
83
|
+
return `${sourceRoot(config)}lib`;
|
|
84
|
+
}
|
|
85
|
+
function defaultDatabaseUrl(config) {
|
|
86
|
+
switch (single(config, "database")) {
|
|
87
|
+
case "db-sqlite":
|
|
88
|
+
return "file:./data/app.db";
|
|
89
|
+
case "db-postgres":
|
|
90
|
+
return "postgres://postgres:postgres@localhost:5432/app";
|
|
91
|
+
case "db-mysql":
|
|
92
|
+
return "mysql://root:password@localhost:3306/app";
|
|
93
|
+
default:
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function betterAuthProvider(config) {
|
|
98
|
+
switch (single(config, "database")) {
|
|
99
|
+
case "db-postgres":
|
|
100
|
+
return "pg";
|
|
101
|
+
case "db-mysql":
|
|
102
|
+
return "mysql";
|
|
103
|
+
default:
|
|
104
|
+
return "sqlite";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function dbModuleImport(config, subpath) {
|
|
108
|
+
const target = subpath ? `db/${subpath}` : "db";
|
|
109
|
+
return usesImportAlias(config) ? `@/${target}` : `../${target}`;
|
|
110
|
+
}
|
|
111
|
+
function authModuleImport(config) {
|
|
112
|
+
return usesImportAlias(config) ? "@/lib/auth" : "../../../../lib/auth";
|
|
113
|
+
}
|
|
114
|
+
function scriptCommand(pm, script) {
|
|
115
|
+
return pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
|
|
116
|
+
}
|
|
44
117
|
function packageJson(projectName, config) {
|
|
45
118
|
const isNext = single(config, "web") === "web-next";
|
|
46
119
|
const isCronusUi = usesCronusUi(config);
|
|
@@ -70,6 +143,25 @@ function packageJson(projectName, config) {
|
|
|
70
143
|
add(devDeps, "@commitlint/cli", "^20.2.0");
|
|
71
144
|
add(devDeps, "@commitlint/config-conventional", "^20.2.0");
|
|
72
145
|
}
|
|
146
|
+
if (emitsDrizzle(config)) {
|
|
147
|
+
add(deps, "drizzle-orm", "^0.45.2");
|
|
148
|
+
add(devDeps, "drizzle-kit", "^0.31.10");
|
|
149
|
+
switch (single(config, "database")) {
|
|
150
|
+
case "db-sqlite":
|
|
151
|
+
add(deps, "better-sqlite3", "^13.0.3");
|
|
152
|
+
add(devDeps, "@types/better-sqlite3", "^9.6.0");
|
|
153
|
+
break;
|
|
154
|
+
case "db-postgres":
|
|
155
|
+
add(deps, "postgres", "^3.4.9");
|
|
156
|
+
break;
|
|
157
|
+
case "db-mysql":
|
|
158
|
+
add(deps, "mysql2", "^3.24.2");
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (emitsBetterAuth(config)) {
|
|
163
|
+
add(deps, "better-auth", "^1.7.2");
|
|
164
|
+
}
|
|
73
165
|
const scripts = isNext
|
|
74
166
|
? {
|
|
75
167
|
dev: "next dev",
|
|
@@ -86,7 +178,12 @@ function packageJson(projectName, config) {
|
|
|
86
178
|
scripts.lint = "biome check .";
|
|
87
179
|
scripts.format = "biome format --write .";
|
|
88
180
|
}
|
|
89
|
-
if (
|
|
181
|
+
if (emitsDrizzle(config)) {
|
|
182
|
+
scripts["db:push"] = "drizzle-kit push";
|
|
183
|
+
scripts["db:generate"] = "drizzle-kit generate";
|
|
184
|
+
scripts["db:studio"] = "drizzle-kit studio";
|
|
185
|
+
}
|
|
186
|
+
else if (single(config, "database") !== "db-none") {
|
|
90
187
|
scripts["db:push"] = 'echo "Configure the selected database/ORM before syncing schema."';
|
|
91
188
|
}
|
|
92
189
|
return `${JSON.stringify({
|
|
@@ -303,8 +400,12 @@ function basicIndex(projectName) {
|
|
|
303
400
|
}
|
|
304
401
|
function readme(projectName, config, unsupported) {
|
|
305
402
|
const pm = packageManagerFromConfig(config);
|
|
306
|
-
const dev = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
307
403
|
const install = pm === "yarn" ? "yarn" : `${pm} install`;
|
|
404
|
+
const runLines = [install];
|
|
405
|
+
if (emitsDrizzle(config) && single(config, "database") === "db-sqlite") {
|
|
406
|
+
runLines.push(scriptCommand(pm, "db:push"));
|
|
407
|
+
}
|
|
408
|
+
runLines.push(scriptCommand(pm, "dev"));
|
|
308
409
|
return `# ${projectName}
|
|
309
410
|
|
|
310
411
|
Generated by \`create-cronus-stack\`.
|
|
@@ -315,8 +416,7 @@ conventions, AI capabilities, guardrails, and Definition of Done.
|
|
|
315
416
|
## Run
|
|
316
417
|
|
|
317
418
|
\`\`\`sh
|
|
318
|
-
${
|
|
319
|
-
${dev}
|
|
419
|
+
${runLines.join("\n")}
|
|
320
420
|
\`\`\`
|
|
321
421
|
|
|
322
422
|
## Generated artifacts
|
|
@@ -330,16 +430,42 @@ ${unsupported.length ? `## Manual follow-up\n\n${unsupported.map((item) => `- ${
|
|
|
330
430
|
}
|
|
331
431
|
function envExample(config) {
|
|
332
432
|
const lines = [];
|
|
333
|
-
if (single(config, "database") !== "db-none")
|
|
334
|
-
|
|
335
|
-
|
|
433
|
+
if (single(config, "database") !== "db-none") {
|
|
434
|
+
const url = emitsDrizzle(config) ? (defaultDatabaseUrl(config) ?? "") : "";
|
|
435
|
+
lines.push(`DATABASE_URL=${url}`);
|
|
436
|
+
}
|
|
437
|
+
if (emitsBetterAuth(config)) {
|
|
438
|
+
lines.push("BETTER_AUTH_SECRET=change-me-to-a-32-character-secret");
|
|
439
|
+
lines.push("BETTER_AUTH_URL=http://localhost:3000");
|
|
440
|
+
}
|
|
441
|
+
else if (single(config, "auth") !== "auth-none") {
|
|
336
442
|
lines.push("AUTH_SECRET=");
|
|
443
|
+
}
|
|
337
444
|
if (single(config, "payments") !== "pay-none") {
|
|
338
445
|
lines.push("PAYMENTS_SECRET_KEY=");
|
|
339
446
|
lines.push("PAYMENTS_WEBHOOK_SECRET=");
|
|
340
447
|
}
|
|
341
448
|
return lines.length ? `${lines.join("\n")}\n` : undefined;
|
|
342
449
|
}
|
|
450
|
+
function gitignore(config) {
|
|
451
|
+
const lines = ["node_modules", ".next", "dist", ".env*", "!.env.example", ".DS_Store"];
|
|
452
|
+
if (emitsDrizzle(config)) {
|
|
453
|
+
lines.push("*.db", "data/", "drizzle/");
|
|
454
|
+
}
|
|
455
|
+
return `${lines.join("\n")}\n`;
|
|
456
|
+
}
|
|
457
|
+
function nextConfigMjs(config) {
|
|
458
|
+
const body = emitsDrizzle(config) && single(config, "database") === "db-sqlite"
|
|
459
|
+
? `{
|
|
460
|
+
serverExternalPackages: ["better-sqlite3"],
|
|
461
|
+
}`
|
|
462
|
+
: "{}";
|
|
463
|
+
return `/** @type {import('next').NextConfig} */
|
|
464
|
+
const nextConfig = ${body};
|
|
465
|
+
|
|
466
|
+
export default nextConfig;
|
|
467
|
+
`;
|
|
468
|
+
}
|
|
343
469
|
function assistantIds(config) {
|
|
344
470
|
const picked = new Set();
|
|
345
471
|
for (const id of multi(config, "assistants")) {
|
|
@@ -354,6 +480,309 @@ function assistantIds(config) {
|
|
|
354
480
|
}
|
|
355
481
|
return [...picked];
|
|
356
482
|
}
|
|
483
|
+
const CATALOG_SKILL_TO_KIT = {
|
|
484
|
+
"skill-ui-add": "ui-add",
|
|
485
|
+
"skill-theme": "theme",
|
|
486
|
+
"skill-compose": "compose",
|
|
487
|
+
"skill-upgrade": "upgrade",
|
|
488
|
+
"skill-code-review": "code-review",
|
|
489
|
+
"skill-ship-pr": "ship-pr",
|
|
490
|
+
"skill-evidence-check": "evidence-check",
|
|
491
|
+
};
|
|
492
|
+
/** Catalog skill ids → AI Kit skills. Empty selection keeps the kit default. */
|
|
493
|
+
function kitSkillsFromConfig(config) {
|
|
494
|
+
const selected = multi(config, "skills");
|
|
495
|
+
if (selected.length === 0)
|
|
496
|
+
return undefined;
|
|
497
|
+
const mapped = [];
|
|
498
|
+
for (const id of selected) {
|
|
499
|
+
const skill = CATALOG_SKILL_TO_KIT[id];
|
|
500
|
+
if (skill)
|
|
501
|
+
mapped.push(skill);
|
|
502
|
+
}
|
|
503
|
+
return mapped;
|
|
504
|
+
}
|
|
505
|
+
function drizzleConfig(config) {
|
|
506
|
+
const dialect = sqlDialect(config) ?? "sqlite";
|
|
507
|
+
const fallback = defaultDatabaseUrl(config) ?? "file:./data/app.db";
|
|
508
|
+
return `import { defineConfig } from "drizzle-kit";
|
|
509
|
+
|
|
510
|
+
export default defineConfig({
|
|
511
|
+
dialect: "${dialect}",
|
|
512
|
+
schema: "./${dbDir(config)}/schema.ts",
|
|
513
|
+
out: "./drizzle",
|
|
514
|
+
dbCredentials: { url: process.env.DATABASE_URL ?? "${fallback}" },
|
|
515
|
+
});
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
function sqliteAuthTables() {
|
|
519
|
+
return `
|
|
520
|
+
export const user = sqliteTable("user", {
|
|
521
|
+
id: text("id").primaryKey(),
|
|
522
|
+
name: text("name").notNull(),
|
|
523
|
+
email: text("email").notNull().unique(),
|
|
524
|
+
emailVerified: integer("email_verified", { mode: "boolean" }).notNull(),
|
|
525
|
+
image: text("image"),
|
|
526
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
527
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
export const session = sqliteTable("session", {
|
|
531
|
+
id: text("id").primaryKey(),
|
|
532
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
533
|
+
token: text("token").notNull().unique(),
|
|
534
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
535
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
536
|
+
ipAddress: text("ip_address"),
|
|
537
|
+
userAgent: text("user_agent"),
|
|
538
|
+
userId: text("user_id")
|
|
539
|
+
.notNull()
|
|
540
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
export const account = sqliteTable("account", {
|
|
544
|
+
id: text("id").primaryKey(),
|
|
545
|
+
issuer: text("issuer").notNull(),
|
|
546
|
+
accountId: text("account_id").notNull(),
|
|
547
|
+
providerId: text("provider_id").notNull(),
|
|
548
|
+
userId: text("user_id")
|
|
549
|
+
.notNull()
|
|
550
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
551
|
+
accessToken: text("access_token"),
|
|
552
|
+
refreshToken: text("refresh_token"),
|
|
553
|
+
idToken: text("id_token"),
|
|
554
|
+
accessTokenExpiresAt: integer("access_token_expires_at", { mode: "timestamp" }),
|
|
555
|
+
refreshTokenExpiresAt: integer("refresh_token_expires_at", { mode: "timestamp" }),
|
|
556
|
+
scope: text("scope"),
|
|
557
|
+
password: text("password"),
|
|
558
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
559
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
export const verification = sqliteTable("verification", {
|
|
563
|
+
id: text("id").primaryKey(),
|
|
564
|
+
identifier: text("identifier").notNull(),
|
|
565
|
+
value: text("value").notNull(),
|
|
566
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
567
|
+
createdAt: integer("created_at", { mode: "timestamp" }),
|
|
568
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }),
|
|
569
|
+
});
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
function pgAuthTables() {
|
|
573
|
+
return `
|
|
574
|
+
export const user = pgTable("user", {
|
|
575
|
+
id: text("id").primaryKey(),
|
|
576
|
+
name: text("name").notNull(),
|
|
577
|
+
email: text("email").notNull().unique(),
|
|
578
|
+
emailVerified: boolean("email_verified").notNull(),
|
|
579
|
+
image: text("image"),
|
|
580
|
+
createdAt: timestamp("created_at").notNull(),
|
|
581
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
export const session = pgTable("session", {
|
|
585
|
+
id: text("id").primaryKey(),
|
|
586
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
587
|
+
token: text("token").notNull().unique(),
|
|
588
|
+
createdAt: timestamp("created_at").notNull(),
|
|
589
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
590
|
+
ipAddress: text("ip_address"),
|
|
591
|
+
userAgent: text("user_agent"),
|
|
592
|
+
userId: text("user_id")
|
|
593
|
+
.notNull()
|
|
594
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
export const account = pgTable("account", {
|
|
598
|
+
id: text("id").primaryKey(),
|
|
599
|
+
issuer: text("issuer").notNull(),
|
|
600
|
+
accountId: text("account_id").notNull(),
|
|
601
|
+
providerId: text("provider_id").notNull(),
|
|
602
|
+
userId: text("user_id")
|
|
603
|
+
.notNull()
|
|
604
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
605
|
+
accessToken: text("access_token"),
|
|
606
|
+
refreshToken: text("refresh_token"),
|
|
607
|
+
idToken: text("id_token"),
|
|
608
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
609
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
610
|
+
scope: text("scope"),
|
|
611
|
+
password: text("password"),
|
|
612
|
+
createdAt: timestamp("created_at").notNull(),
|
|
613
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
export const verification = pgTable("verification", {
|
|
617
|
+
id: text("id").primaryKey(),
|
|
618
|
+
identifier: text("identifier").notNull(),
|
|
619
|
+
value: text("value").notNull(),
|
|
620
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
621
|
+
createdAt: timestamp("created_at"),
|
|
622
|
+
updatedAt: timestamp("updated_at"),
|
|
623
|
+
});
|
|
624
|
+
`;
|
|
625
|
+
}
|
|
626
|
+
function mysqlAuthTables() {
|
|
627
|
+
return `
|
|
628
|
+
export const user = mysqlTable("user", {
|
|
629
|
+
id: varchar("id", { length: 36 }).primaryKey(),
|
|
630
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
631
|
+
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
632
|
+
emailVerified: boolean("email_verified").notNull(),
|
|
633
|
+
image: text("image"),
|
|
634
|
+
createdAt: timestamp("created_at").notNull(),
|
|
635
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
export const session = mysqlTable("session", {
|
|
639
|
+
id: varchar("id", { length: 36 }).primaryKey(),
|
|
640
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
641
|
+
token: varchar("token", { length: 255 }).notNull().unique(),
|
|
642
|
+
createdAt: timestamp("created_at").notNull(),
|
|
643
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
644
|
+
ipAddress: text("ip_address"),
|
|
645
|
+
userAgent: text("user_agent"),
|
|
646
|
+
userId: varchar("user_id", { length: 36 })
|
|
647
|
+
.notNull()
|
|
648
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
export const account = mysqlTable("account", {
|
|
652
|
+
id: varchar("id", { length: 36 }).primaryKey(),
|
|
653
|
+
issuer: varchar("issuer", { length: 255 }).notNull(),
|
|
654
|
+
accountId: varchar("account_id", { length: 255 }).notNull(),
|
|
655
|
+
providerId: varchar("provider_id", { length: 255 }).notNull(),
|
|
656
|
+
userId: varchar("user_id", { length: 36 })
|
|
657
|
+
.notNull()
|
|
658
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
659
|
+
accessToken: text("access_token"),
|
|
660
|
+
refreshToken: text("refresh_token"),
|
|
661
|
+
idToken: text("id_token"),
|
|
662
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
663
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
664
|
+
scope: text("scope"),
|
|
665
|
+
password: text("password"),
|
|
666
|
+
createdAt: timestamp("created_at").notNull(),
|
|
667
|
+
updatedAt: timestamp("updated_at").notNull(),
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
export const verification = mysqlTable("verification", {
|
|
671
|
+
id: varchar("id", { length: 36 }).primaryKey(),
|
|
672
|
+
identifier: varchar("identifier", { length: 255 }).notNull(),
|
|
673
|
+
value: text("value").notNull(),
|
|
674
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
675
|
+
createdAt: timestamp("created_at"),
|
|
676
|
+
updatedAt: timestamp("updated_at"),
|
|
677
|
+
});
|
|
678
|
+
`;
|
|
679
|
+
}
|
|
680
|
+
function drizzleSchema(config) {
|
|
681
|
+
const withAuth = emitsBetterAuth(config);
|
|
682
|
+
switch (sqlDialect(config)) {
|
|
683
|
+
case "postgresql": {
|
|
684
|
+
const imports = withAuth
|
|
685
|
+
? 'import { boolean, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";'
|
|
686
|
+
: 'import { integer, pgTable, text } from "drizzle-orm/pg-core";';
|
|
687
|
+
return `${imports}
|
|
688
|
+
|
|
689
|
+
export const items = pgTable("items", {
|
|
690
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
691
|
+
title: text("title").notNull(),
|
|
692
|
+
});
|
|
693
|
+
${withAuth ? pgAuthTables() : ""}`;
|
|
694
|
+
}
|
|
695
|
+
case "mysql": {
|
|
696
|
+
const imports = withAuth
|
|
697
|
+
? 'import { boolean, int, mysqlTable, text, timestamp, varchar } from "drizzle-orm/mysql-core";'
|
|
698
|
+
: 'import { int, mysqlTable, varchar } from "drizzle-orm/mysql-core";';
|
|
699
|
+
return `${imports}
|
|
700
|
+
|
|
701
|
+
export const items = mysqlTable("items", {
|
|
702
|
+
id: int("id").autoincrement().primaryKey(),
|
|
703
|
+
title: varchar("title", { length: 255 }).notNull(),
|
|
704
|
+
});
|
|
705
|
+
${withAuth ? mysqlAuthTables() : ""}`;
|
|
706
|
+
}
|
|
707
|
+
default:
|
|
708
|
+
return `import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
709
|
+
|
|
710
|
+
export const items = sqliteTable("items", {
|
|
711
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
712
|
+
title: text("title").notNull(),
|
|
713
|
+
});
|
|
714
|
+
${withAuth ? sqliteAuthTables() : ""}`;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function drizzleClient(config) {
|
|
718
|
+
const fallback = defaultDatabaseUrl(config) ?? "file:./data/app.db";
|
|
719
|
+
switch (sqlDialect(config)) {
|
|
720
|
+
case "postgresql":
|
|
721
|
+
return `import { drizzle } from "drizzle-orm/postgres-js";
|
|
722
|
+
import postgres from "postgres";
|
|
723
|
+
import * as schema from "./schema";
|
|
724
|
+
|
|
725
|
+
const url = process.env.DATABASE_URL ?? "${fallback}";
|
|
726
|
+
const client = postgres(url);
|
|
727
|
+
|
|
728
|
+
export const db = drizzle(client, { schema });
|
|
729
|
+
`;
|
|
730
|
+
case "mysql":
|
|
731
|
+
return `import { drizzle } from "drizzle-orm/mysql2";
|
|
732
|
+
import mysql from "mysql2/promise";
|
|
733
|
+
import * as schema from "./schema";
|
|
734
|
+
|
|
735
|
+
const url = process.env.DATABASE_URL ?? "${fallback}";
|
|
736
|
+
const pool = mysql.createPool(url);
|
|
737
|
+
|
|
738
|
+
export const db = drizzle(pool, { schema, mode: "default" });
|
|
739
|
+
`;
|
|
740
|
+
default:
|
|
741
|
+
return `import { mkdirSync } from "node:fs";
|
|
742
|
+
import { dirname } from "node:path";
|
|
743
|
+
import Database from "better-sqlite3";
|
|
744
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
745
|
+
import * as schema from "./schema";
|
|
746
|
+
|
|
747
|
+
const url = process.env.DATABASE_URL ?? "${fallback}";
|
|
748
|
+
const fileFromUrl = url.startsWith("file:") ? url.slice("file:".length) : url;
|
|
749
|
+
mkdirSync(dirname(fileFromUrl) || ".", { recursive: true });
|
|
750
|
+
const sqlite = new Database(fileFromUrl);
|
|
751
|
+
|
|
752
|
+
export const db = drizzle(sqlite, { schema });
|
|
753
|
+
`;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function betterAuthServer(config) {
|
|
757
|
+
const provider = betterAuthProvider(config);
|
|
758
|
+
const dbImport = dbModuleImport(config);
|
|
759
|
+
const schemaImport = dbModuleImport(config, "schema");
|
|
760
|
+
return `import { betterAuth } from "better-auth";
|
|
761
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
762
|
+
import { db } from "${dbImport}";
|
|
763
|
+
import * as schema from "${schemaImport}";
|
|
764
|
+
|
|
765
|
+
export const auth = betterAuth({
|
|
766
|
+
database: drizzleAdapter(db, { provider: "${provider}", schema }),
|
|
767
|
+
emailAndPassword: { enabled: true },
|
|
768
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
769
|
+
baseURL: process.env.BETTER_AUTH_URL,
|
|
770
|
+
});
|
|
771
|
+
`;
|
|
772
|
+
}
|
|
773
|
+
function betterAuthClient() {
|
|
774
|
+
return `import { createAuthClient } from "better-auth/react";
|
|
775
|
+
|
|
776
|
+
export const authClient = createAuthClient();
|
|
777
|
+
`;
|
|
778
|
+
}
|
|
779
|
+
function betterAuthRoute(config) {
|
|
780
|
+
return `import { auth } from "${authModuleImport(config)}";
|
|
781
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
782
|
+
|
|
783
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
784
|
+
`;
|
|
785
|
+
}
|
|
357
786
|
function unsupportedNotes(config) {
|
|
358
787
|
const notes = [];
|
|
359
788
|
if (single(config, "web") !== "web-next") {
|
|
@@ -367,10 +796,15 @@ function unsupportedNotes(config) {
|
|
|
367
796
|
single(config, "backend") !== "backend-fullstack-next") {
|
|
368
797
|
notes.push("Add the selected dedicated backend service described in KICKOFF.md.");
|
|
369
798
|
}
|
|
370
|
-
if (
|
|
799
|
+
if (emitsDrizzle(config)) {
|
|
800
|
+
if (HOSTED_DB_SETUPS.has(single(config, "dbSetup") ?? "")) {
|
|
801
|
+
notes.push("Configure the selected hosted database provider; generated Drizzle files use a local DATABASE_URL.");
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
else if (single(config, "database") !== "db-none") {
|
|
371
805
|
notes.push("Wire the selected database/ORM/provider before running db:push.");
|
|
372
806
|
}
|
|
373
|
-
if (single(config, "auth") !== "auth-none") {
|
|
807
|
+
if (!emitsBetterAuth(config) && single(config, "auth") !== "auth-none") {
|
|
374
808
|
notes.push("Implement the selected auth provider and protect mutating routes.");
|
|
375
809
|
}
|
|
376
810
|
if (single(config, "payments") !== "pay-none") {
|
|
@@ -388,9 +822,6 @@ function unsupportedNotes(config) {
|
|
|
388
822
|
if (mcpIds.includes("mcp-cronus-ui") && !usesCronusUi(config)) {
|
|
389
823
|
notes.push("The cronus-ui MCP server is not generated for stacks that do not use Cronus UI.");
|
|
390
824
|
}
|
|
391
|
-
if (multi(config, "skills").length > 0) {
|
|
392
|
-
notes.push("Install the selected agent skill packs in your agent environment; KICKOFF.md records the choices but the scaffold does not install external skills.");
|
|
393
|
-
}
|
|
394
825
|
const unsupportedAddons = multi(config, "addons").filter((id) => id !== "addon-biome");
|
|
395
826
|
if (unsupportedAddons.length > 0) {
|
|
396
827
|
notes.push("Wire the selected addons manually unless noted otherwise; this generator currently scaffolds Biome config and records the rest in KICKOFF.md.");
|
|
@@ -418,7 +849,7 @@ export function scaffoldStack(options) {
|
|
|
418
849
|
emit("README.md", readme(projectName, config, unsupported));
|
|
419
850
|
emit("KICKOFF.md", generateKickoff(config, projectName, catalog));
|
|
420
851
|
emit("stack.json", `${generateStackJson(config, projectName)}\n`);
|
|
421
|
-
emit(".gitignore",
|
|
852
|
+
emit(".gitignore", gitignore(config));
|
|
422
853
|
emit("tsconfig.json", tsconfig(config));
|
|
423
854
|
emit(".env.example", envExample(config));
|
|
424
855
|
if (single(config, "commitStyle") === "commit-conventional") {
|
|
@@ -429,7 +860,7 @@ export function scaffoldStack(options) {
|
|
|
429
860
|
}
|
|
430
861
|
if (single(config, "web") === "web-next") {
|
|
431
862
|
const isCronusUi = usesCronusUi(config);
|
|
432
|
-
emit("next.config.mjs",
|
|
863
|
+
emit("next.config.mjs", nextConfigMjs(config));
|
|
433
864
|
const app = appDir(config);
|
|
434
865
|
if (isCronusUi) {
|
|
435
866
|
emit("postcss.config.mjs", 'export default { plugins: { "@tailwindcss/postcss": {} } };\n');
|
|
@@ -448,6 +879,16 @@ export function scaffoldStack(options) {
|
|
|
448
879
|
emit(`${app}/layout.tsx`, neutralLayoutTsx(projectName));
|
|
449
880
|
emit(`${app}/page.tsx`, neutralPageTsx(config));
|
|
450
881
|
}
|
|
882
|
+
if (emitsDrizzle(config)) {
|
|
883
|
+
emit("drizzle.config.ts", drizzleConfig(config));
|
|
884
|
+
emit(`${dbDir(config)}/schema.ts`, drizzleSchema(config));
|
|
885
|
+
emit(`${dbDir(config)}/index.ts`, drizzleClient(config));
|
|
886
|
+
}
|
|
887
|
+
if (emitsBetterAuth(config)) {
|
|
888
|
+
emit(`${libDir(config)}/auth.ts`, betterAuthServer(config));
|
|
889
|
+
emit(`${libDir(config)}/auth-client.ts`, betterAuthClient());
|
|
890
|
+
emit(`${app}/api/auth/[...all]/route.ts`, betterAuthRoute(config));
|
|
891
|
+
}
|
|
451
892
|
}
|
|
452
893
|
else {
|
|
453
894
|
emit("src/index.ts", basicIndex(projectName));
|
|
@@ -461,6 +902,7 @@ export function scaffoldStack(options) {
|
|
|
461
902
|
name: projectName,
|
|
462
903
|
assistants,
|
|
463
904
|
preset: "standard",
|
|
905
|
+
skills: kitSkillsFromConfig(config),
|
|
464
906
|
includeCronusUi: isCronusUi,
|
|
465
907
|
cronusUiMcp,
|
|
466
908
|
});
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CREATE_STACK_VERSION = "0.6.
|
|
1
|
+
export declare const CREATE_STACK_VERSION = "0.6.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const CREATE_STACK_VERSION = "0.6.
|
|
1
|
+
export const CREATE_STACK_VERSION = "0.6.1";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cronus-stack",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Scaffold a Cronus stack from the Cronus Stack Builder contract: app files, stack.json, KICKOFF.md, and optional AI Kit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"prepublishOnly": "tsc -p tsconfig.json"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@cronus-ui/ai-kit": "0.6.
|
|
48
|
-
"@cronus-ui/stack": "0.6.
|
|
47
|
+
"@cronus-ui/ai-kit": "0.6.1",
|
|
48
|
+
"@cronus-ui/stack": "0.6.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.10.0",
|