create-cronus-stack 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cronus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # create-cronus-stack
2
+
3
+ CLI generator for the Cronus Stack Builder.
4
+
5
+ ```sh
6
+ bun create cronus-stack@latest my-app --yes --web next --ui cronus
7
+ ```
8
+
9
+ The generator always writes `stack.json` and `KICKOFF.md`. The default stack
10
+ creates a runnable Next.js + Cronus UI app. Non-default framework/backend choices
11
+ are captured honestly in those artifacts and in the generated README so the next
12
+ developer or agent knows what remains to implement.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { type StackFlagValues } from "@cronus-ui/stack";
3
+ export declare function stackFlagHelpLines(): string;
4
+ interface ParsedCli {
5
+ name: string;
6
+ yes: boolean;
7
+ help: boolean;
8
+ version: boolean;
9
+ values: StackFlagValues;
10
+ }
11
+ export declare function parseCli(args: string[]): ParsedCli;
12
+ export {};
13
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { argv } from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseArgs } from "node:util";
7
+ import { CLI_FLAGS, catalog, flagValue, resolveStackFlags, sanitizeProjectName, } from "@cronus-ui/stack";
8
+ import { assertWritableTarget, initGit, runInstall, scaffoldStack } from "./scaffold.js";
9
+ import { c, log, packageManagerFromConfig } from "./utils.js";
10
+ import { CREATE_STACK_VERSION } from "./version.js";
11
+ const STACK_FLAG_OPTIONS = Object.fromEntries(CLI_FLAGS.map(({ flag }) => [flag, { type: "string" }]));
12
+ export function stackFlagHelpLines() {
13
+ return CLI_FLAGS.map(({ catId, flag, kind }) => {
14
+ const category = catalog.find((candidate) => candidate.id === catId);
15
+ const values = category?.options.map((option) => flagValue(option.id)).join("|") ?? "value";
16
+ const suffix = kind === "multi" ? " — comma-separated list or none" : "";
17
+ return ` --${flag} <${values}>${suffix}`;
18
+ }).join("\n");
19
+ }
20
+ const HELP = `${c.bold("create-cronus-stack")} — scaffold from the Cronus Stack Builder.
21
+
22
+ ${c.bold("Usage")}
23
+ create-cronus-stack [project-name] [options]
24
+ bun create cronus-stack@latest [project-name] [options]
25
+
26
+ ${c.bold("Stack flags")}
27
+ ${stackFlagHelpLines()}
28
+ --vibe
29
+ --git / --no-git
30
+ --install / --no-install
31
+ -y, --yes Accept defaults
32
+ -h, --help Show this help
33
+ -v, --version Show the version
34
+
35
+ ${c.bold("Examples")}
36
+ bun create cronus-stack@latest my-app --yes --no-install
37
+ bun create cronus-stack@latest admin --web next --ui cronus --ai claude-code,cursor
38
+ `;
39
+ export function parseCli(args) {
40
+ const { values, positionals } = parseArgs({
41
+ args,
42
+ allowPositionals: true,
43
+ options: {
44
+ ...STACK_FLAG_OPTIONS,
45
+ vibe: { type: "boolean", default: false },
46
+ git: { type: "boolean", default: false },
47
+ "no-git": { type: "boolean", default: false },
48
+ install: { type: "boolean", default: false },
49
+ "no-install": { type: "boolean", default: false },
50
+ yes: { type: "boolean", short: "y", default: false },
51
+ help: { type: "boolean", short: "h", default: false },
52
+ version: { type: "boolean", short: "v", default: false },
53
+ },
54
+ });
55
+ return {
56
+ name: sanitizeProjectName(positionals[0] ?? "my-cronus-app"),
57
+ yes: values.yes === true,
58
+ help: values.help === true,
59
+ version: values.version === true,
60
+ values: values,
61
+ };
62
+ }
63
+ async function main() {
64
+ const parsed = parseCli(process.argv.slice(2));
65
+ if (parsed.help) {
66
+ process.stdout.write(`${HELP}\n`);
67
+ return;
68
+ }
69
+ if (parsed.version) {
70
+ process.stdout.write(`${CREATE_STACK_VERSION}\n`);
71
+ return;
72
+ }
73
+ log.intro();
74
+ if (!parsed.yes && process.stdin.isTTY) {
75
+ log.warn("Using Stack Builder defaults. Pass flags or -y to skip this notice.");
76
+ }
77
+ const config = resolveStackFlags(parsed.values, { catalog });
78
+ const targetDir = join(process.cwd(), parsed.name);
79
+ assertWritableTarget(targetDir);
80
+ log.step(`Scaffolding into ${c.cyan(parsed.name)}…`);
81
+ const result = scaffoldStack({ targetDir, projectName: parsed.name, config, catalog });
82
+ log.ok(`Created ${result.fileCount} files.`);
83
+ for (const note of result.unsupported)
84
+ log.warn(note);
85
+ if (config.git === true) {
86
+ try {
87
+ log.step("Initializing git repository…");
88
+ initGit(targetDir);
89
+ log.ok("Git initialized.");
90
+ }
91
+ catch (err) {
92
+ log.warn(`Git init failed (${err.message}).`);
93
+ }
94
+ }
95
+ const pm = packageManagerFromConfig(config);
96
+ if (config.install === true) {
97
+ try {
98
+ log.step(`Installing dependencies with ${c.cyan(pm)}…`);
99
+ runInstall(pm, targetDir);
100
+ log.ok("Dependencies installed.");
101
+ }
102
+ catch (err) {
103
+ log.warn(`Install failed (${err.message}). Install manually later.`);
104
+ }
105
+ }
106
+ log.outro(parsed.name, pm, config.install === true);
107
+ }
108
+ function isEntrypoint() {
109
+ const entry = argv[1];
110
+ if (!entry)
111
+ return false;
112
+ return existsSync(entry) && fileURLToPath(import.meta.url) === realpathSync(entry);
113
+ }
114
+ if (isEntrypoint()) {
115
+ main().catch((err) => {
116
+ log.error(err.message);
117
+ process.exitCode = 1;
118
+ });
119
+ }
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import type { Catalog, StackConfig } from "@cronus-ui/stack";
2
+ import { type PackageManager } from "./utils.js";
3
+ export interface ScaffoldStackOptions {
4
+ targetDir: string;
5
+ projectName: string;
6
+ config: StackConfig;
7
+ catalog?: Catalog;
8
+ }
9
+ export interface ScaffoldStackResult {
10
+ fileCount: number;
11
+ unsupported: string[];
12
+ }
13
+ export declare function assertWritableTarget(targetDir: string): void;
14
+ export declare function scaffoldStack(options: ScaffoldStackOptions): ScaffoldStackResult;
15
+ export declare function initGit(cwd: string): void;
16
+ export declare function runInstall(pm: PackageManager, cwd: string): void;
17
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1,481 @@
1
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { writeAiKit, writeDesignDocuments } from "@cronus-ui/ai-kit";
4
+ import { catalog as defaultCatalog, generateKickoff, generateStackJson, sanitizeProjectName, } from "@cronus-ui/stack";
5
+ import { packageManagerFromConfig, runCommand } from "./utils.js";
6
+ import { CREATE_STACK_VERSION } from "./version.js";
7
+ function single(config, key) {
8
+ const value = config[key];
9
+ return typeof value === "string" ? value : undefined;
10
+ }
11
+ function multi(config, key) {
12
+ const value = config[key];
13
+ return Array.isArray(value) ? value : [];
14
+ }
15
+ function add(deps, name, range) {
16
+ deps[name] = range;
17
+ }
18
+ const CRONUS_UI_VERSION_RANGE = `^${CREATE_STACK_VERSION}`;
19
+ const CRONUS_UI_REGISTRY = `https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v${CREATE_STACK_VERSION}/registry`;
20
+ function write(targetDir, rel, content) {
21
+ const path = join(targetDir, rel);
22
+ mkdirSync(dirname(path), { recursive: true });
23
+ writeFileSync(path, content);
24
+ }
25
+ function appDir(config) {
26
+ return single(config, "structure") === "structure-root" ? "app" : "src/app";
27
+ }
28
+ function aliasTarget(config) {
29
+ if (single(config, "importAlias") !== "import-alias")
30
+ return undefined;
31
+ return appDir(config).startsWith("src/") ? "./src/*" : "./*";
32
+ }
33
+ function cronusUiPaths(config) {
34
+ const prefix = appDir(config).startsWith("src/") ? "src/" : "";
35
+ return {
36
+ ui: `${prefix}components/ui`,
37
+ lib: `${prefix}lib`,
38
+ blocks: `${prefix}components/blocks`,
39
+ };
40
+ }
41
+ function usesCronusUi(config) {
42
+ return single(config, "ui") === "ui-cronus";
43
+ }
44
+ function packageJson(projectName, config) {
45
+ const isNext = single(config, "web") === "web-next";
46
+ const isCronusUi = usesCronusUi(config);
47
+ const deps = {};
48
+ const devDeps = {
49
+ typescript: "^6.0.3",
50
+ };
51
+ if (isNext) {
52
+ add(deps, "next", "16.2.10");
53
+ add(deps, "react", "^19.2.0");
54
+ add(deps, "react-dom", "^19.2.0");
55
+ add(devDeps, "@types/node", "^22.10.0");
56
+ add(devDeps, "@types/react", "^19.0.0");
57
+ add(devDeps, "@types/react-dom", "^19.0.0");
58
+ }
59
+ if (isCronusUi) {
60
+ add(deps, "@cronus-ui/theme", CRONUS_UI_VERSION_RANGE);
61
+ add(deps, "@cronus-ui/tokens", CRONUS_UI_VERSION_RANGE);
62
+ add(deps, "@cronus-ui/ui", CRONUS_UI_VERSION_RANGE);
63
+ add(devDeps, "@tailwindcss/postcss", "^4.3.0");
64
+ add(devDeps, "tailwindcss", "^4.3.0");
65
+ }
66
+ if (multi(config, "addons").includes("addon-biome")) {
67
+ add(devDeps, "@biomejs/biome", "^2.0.6");
68
+ }
69
+ if (single(config, "commitStyle") === "commit-conventional") {
70
+ add(devDeps, "@commitlint/cli", "^20.2.0");
71
+ add(devDeps, "@commitlint/config-conventional", "^20.2.0");
72
+ }
73
+ const scripts = isNext
74
+ ? {
75
+ dev: "next dev",
76
+ build: "next build",
77
+ start: "next start",
78
+ typecheck: "tsc --noEmit",
79
+ }
80
+ : {
81
+ dev: 'echo "Open KICKOFF.md and wire the selected framework scaffold."',
82
+ build: "tsc --noEmit",
83
+ typecheck: "tsc --noEmit",
84
+ };
85
+ if (multi(config, "addons").includes("addon-biome")) {
86
+ scripts.lint = "biome check .";
87
+ scripts.format = "biome format --write .";
88
+ }
89
+ if (single(config, "database") !== "db-none") {
90
+ scripts["db:push"] = 'echo "Configure the selected database/ORM before syncing schema."';
91
+ }
92
+ return `${JSON.stringify({
93
+ name: projectName,
94
+ version: "0.1.0",
95
+ private: true,
96
+ type: "module",
97
+ scripts,
98
+ dependencies: deps,
99
+ devDependencies: devDeps,
100
+ }, null, 2)}\n`;
101
+ }
102
+ function tsconfig(config) {
103
+ const alias = aliasTarget(config);
104
+ const strict = single(config, "tsStrict") === "ts-strict";
105
+ const compilerOptions = {
106
+ target: "ES2022",
107
+ lib: ["dom", "dom.iterable", "ES2022"],
108
+ allowJs: true,
109
+ skipLibCheck: true,
110
+ strict,
111
+ noEmit: true,
112
+ esModuleInterop: true,
113
+ module: "esnext",
114
+ moduleResolution: "bundler",
115
+ resolveJsonModule: true,
116
+ isolatedModules: true,
117
+ jsx: "preserve",
118
+ incremental: true,
119
+ };
120
+ if (strict) {
121
+ compilerOptions.noUncheckedIndexedAccess = true;
122
+ compilerOptions.noImplicitOverride = true;
123
+ }
124
+ if (alias)
125
+ compilerOptions.paths = { "@/*": [alias] };
126
+ if (single(config, "web") === "web-next")
127
+ compilerOptions.plugins = [{ name: "next" }];
128
+ return `${JSON.stringify({
129
+ compilerOptions,
130
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
131
+ exclude: ["node_modules"],
132
+ }, null, 2)}\n`;
133
+ }
134
+ function globalsCss(config) {
135
+ const app = appDir(config);
136
+ const sourceNodeModules = app.startsWith("src/")
137
+ ? "../../node_modules/@cronus-ui/ui/dist/**/*.js"
138
+ : "../node_modules/@cronus-ui/ui/dist/**/*.js";
139
+ const sourceApp = app.startsWith("src/") ? "../**/*.{ts,tsx}" : "./**/*.{ts,tsx}";
140
+ return `@import "tailwindcss";
141
+ @import "@cronus-ui/tokens/styles.css";
142
+
143
+ @source "${sourceNodeModules}";
144
+ @source "${sourceApp}";
145
+ `;
146
+ }
147
+ function neutralGlobalsCss() {
148
+ return `:root {
149
+ color-scheme: light dark;
150
+ font-family:
151
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
152
+ }
153
+
154
+ body {
155
+ margin: 0;
156
+ min-height: 100vh;
157
+ background: #0f172a;
158
+ color: #f8fafc;
159
+ }
160
+
161
+ a {
162
+ color: inherit;
163
+ }
164
+ `;
165
+ }
166
+ function layoutTsx(projectName) {
167
+ return `import { CronusThemeScript, CronusUIProvider } from "@cronus-ui/theme";
168
+ import type { Metadata } from "next";
169
+ import type { ReactNode } from "react";
170
+ import "./globals.css";
171
+
172
+ export const metadata: Metadata = {
173
+ title: "${projectName}",
174
+ description: "A Next.js app built with Cronus UI.",
175
+ };
176
+
177
+ export default function RootLayout({ children }: { children: ReactNode }) {
178
+ return (
179
+ <html lang="en" suppressHydrationWarning>
180
+ <head>
181
+ <CronusThemeScript storageKey="theme" defaultThemeName="aurora" defaultModeName="dark" />
182
+ </head>
183
+ <body>
184
+ <CronusUIProvider
185
+ asRoot
186
+ storageKey="theme"
187
+ defaultThemeName="aurora"
188
+ defaultModeName="dark"
189
+ >
190
+ {children}
191
+ </CronusUIProvider>
192
+ </body>
193
+ </html>
194
+ );
195
+ }
196
+ `;
197
+ }
198
+ function neutralLayoutTsx(projectName) {
199
+ return `import type { Metadata } from "next";
200
+ import type { ReactNode } from "react";
201
+ import "./globals.css";
202
+
203
+ export const metadata: Metadata = {
204
+ title: "${projectName}",
205
+ description: "A Next.js app generated by create-cronus-stack.",
206
+ };
207
+
208
+ export default function RootLayout({ children }: { children: ReactNode }) {
209
+ return (
210
+ <html lang="en">
211
+ <body>{children}</body>
212
+ </html>
213
+ );
214
+ }
215
+ `;
216
+ }
217
+ function pageTsx() {
218
+ return `import { Badge } from "@cronus-ui/ui/badge";
219
+ import { Button } from "@cronus-ui/ui/button";
220
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@cronus-ui/ui/card";
221
+
222
+ const metrics = [
223
+ { label: "Revenue", value: "R$ 48.2k" },
224
+ { label: "Active users", value: "2,318" },
225
+ { label: "NPS", value: "72" },
226
+ ];
227
+
228
+ export default function Page() {
229
+ return (
230
+ <main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col gap-8 px-6 py-12">
231
+ <header className="flex flex-col gap-4">
232
+ <Badge variant="primary" className="w-fit">
233
+ Built with Cronus UI
234
+ </Badge>
235
+ <div className="flex flex-col gap-3">
236
+ <h1 className="text-4xl font-semibold tracking-tight text-fg">Your stack is ready</h1>
237
+ <p className="max-w-prose text-fg-secondary">
238
+ This app was scaffolded from the Cronus Stack Builder. Read KICKOFF.md before changing
239
+ frameworks, databases, auth, payments, or design-system rules.
240
+ </p>
241
+ </div>
242
+ <div className="flex flex-wrap gap-3">
243
+ <Button variant="primary">Start building</Button>
244
+ <Button variant="outline">Read KICKOFF.md</Button>
245
+ </div>
246
+ </header>
247
+
248
+ <section className="grid gap-4 sm:grid-cols-3">
249
+ {metrics.map((metric) => (
250
+ <Card key={metric.label}>
251
+ <CardHeader>
252
+ <CardDescription>{metric.label}</CardDescription>
253
+ <CardTitle>{metric.value}</CardTitle>
254
+ </CardHeader>
255
+ <CardContent className="text-sm text-fg-secondary">
256
+ Replace this with your real product data.
257
+ </CardContent>
258
+ </Card>
259
+ ))}
260
+ </section>
261
+ </main>
262
+ );
263
+ }
264
+ `;
265
+ }
266
+ function neutralPageTsx(config) {
267
+ const uiStep = single(config, "ui") === "ui-none"
268
+ ? "No UI library was selected; add product UI intentionally when the design direction is clear."
269
+ : "Install and configure the selected UI library from stack.json.";
270
+ return `const nextSteps = [
271
+ "Read KICKOFF.md before installing framework, UI, database, auth, or payment adapters.",
272
+ "${uiStep}",
273
+ "Replace this neutral starter with product-specific routes and components.",
274
+ ];
275
+
276
+ export default function Page() {
277
+ return (
278
+ <main style={{ margin: "0 auto", maxWidth: "64rem", padding: "4rem 1.5rem" }}>
279
+ <p style={{ color: "#38bdf8", fontSize: "0.875rem", fontWeight: 700 }}>
280
+ Generated by create-cronus-stack
281
+ </p>
282
+ <h1 style={{ fontSize: "3rem", letterSpacing: "-0.04em", margin: "1rem 0" }}>
283
+ Your neutral stack starter is ready
284
+ </h1>
285
+ <p style={{ color: "#cbd5e1", fontSize: "1.125rem", lineHeight: 1.7, maxWidth: "42rem" }}>
286
+ This project records your selected stack in KICKOFF.md and stack.json. The default Cronus UI
287
+ stack is runnable immediately; non-Cronus UI choices are intentionally left as explicit
288
+ follow-up so the scaffold does not install or import the wrong design system.
289
+ </p>
290
+ <ul style={{ color: "#e2e8f0", lineHeight: 1.8, marginTop: "2rem", paddingLeft: "1.25rem" }}>
291
+ {nextSteps.map((step) => (
292
+ <li key={step}>{step}</li>
293
+ ))}
294
+ </ul>
295
+ </main>
296
+ );
297
+ }
298
+ `;
299
+ }
300
+ function basicIndex(projectName) {
301
+ return `console.log("${projectName}: read KICKOFF.md and wire the selected framework scaffold.");
302
+ `;
303
+ }
304
+ function readme(projectName, config, unsupported) {
305
+ const pm = packageManagerFromConfig(config);
306
+ const dev = pm === "npm" ? "npm run dev" : `${pm} dev`;
307
+ const install = pm === "yarn" ? "yarn" : `${pm} install`;
308
+ return `# ${projectName}
309
+
310
+ Generated by \`create-cronus-stack\`.
311
+
312
+ Read \`KICKOFF.md\` first. It is the source of truth for stack choices,
313
+ conventions, AI capabilities, guardrails, and Definition of Done.
314
+
315
+ ## Run
316
+
317
+ \`\`\`sh
318
+ ${install}
319
+ ${dev}
320
+ \`\`\`
321
+
322
+ ## Generated artifacts
323
+
324
+ - \`stack.json\` — machine-readable resolved stack.
325
+ - \`KICKOFF.md\` — handoff prompt for the coding agent.
326
+ - App starter files for the supported scaffold path.
327
+
328
+ ${unsupported.length ? `## Manual follow-up\n\n${unsupported.map((item) => `- ${item}`).join("\n")}\n` : ""}
329
+ `;
330
+ }
331
+ function envExample(config) {
332
+ const lines = [];
333
+ if (single(config, "database") !== "db-none")
334
+ lines.push("DATABASE_URL=");
335
+ if (single(config, "auth") !== "auth-none")
336
+ lines.push("AUTH_SECRET=");
337
+ if (single(config, "payments") !== "pay-none") {
338
+ lines.push("PAYMENTS_SECRET_KEY=");
339
+ lines.push("PAYMENTS_WEBHOOK_SECRET=");
340
+ }
341
+ return lines.length ? `${lines.join("\n")}\n` : undefined;
342
+ }
343
+ function assistantIds(config) {
344
+ const picked = new Set();
345
+ for (const id of multi(config, "assistants")) {
346
+ if (id === "ai-claude-code")
347
+ picked.add("claude");
348
+ if (id === "ai-cursor")
349
+ picked.add("cursor");
350
+ if (id === "ai-copilot")
351
+ picked.add("copilot");
352
+ if (id === "ai-windsurf")
353
+ picked.add("windsurf");
354
+ }
355
+ return [...picked];
356
+ }
357
+ function unsupportedNotes(config) {
358
+ const notes = [];
359
+ if (single(config, "web") !== "web-next") {
360
+ notes.push("The selected web framework is captured in KICKOFF.md; this generator currently writes a runnable app only for Next.js.");
361
+ }
362
+ const ui = single(config, "ui");
363
+ if (ui && ui !== "ui-cronus" && ui !== "ui-none") {
364
+ notes.push("The selected UI library is captured in KICKOFF.md; this generator writes a neutral Next.js starter unless Cronus UI is selected, so install and wire the chosen UI library manually.");
365
+ }
366
+ if (single(config, "backend") !== "backend-none" &&
367
+ single(config, "backend") !== "backend-fullstack-next") {
368
+ notes.push("Add the selected dedicated backend service described in KICKOFF.md.");
369
+ }
370
+ if (single(config, "database") !== "db-none") {
371
+ notes.push("Wire the selected database/ORM/provider before running db:push.");
372
+ }
373
+ if (single(config, "auth") !== "auth-none") {
374
+ notes.push("Implement the selected auth provider and protect mutating routes.");
375
+ }
376
+ if (single(config, "payments") !== "pay-none") {
377
+ notes.push("Implement payment webhooks server-side; never trust client-side amounts.");
378
+ }
379
+ const assistantIds = multi(config, "assistants");
380
+ if (assistantIds.includes("ai-cline")) {
381
+ notes.push("Add Cline workspace configuration manually; AI Kit does not emit Cline files yet.");
382
+ }
383
+ const mcpIds = multi(config, "mcp");
384
+ const unsupportedMcp = mcpIds.filter((id) => id !== "mcp-cronus-ui");
385
+ if (unsupportedMcp.length > 0) {
386
+ notes.push("Configure the selected non-Cronus MCP servers manually; the generated AI Kit template only ships the cronus-ui MCP entry today.");
387
+ }
388
+ if (mcpIds.includes("mcp-cronus-ui") && !usesCronusUi(config)) {
389
+ notes.push("The cronus-ui MCP server is not generated for stacks that do not use Cronus UI.");
390
+ }
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
+ const unsupportedAddons = multi(config, "addons").filter((id) => id !== "addon-biome");
395
+ if (unsupportedAddons.length > 0) {
396
+ notes.push("Wire the selected addons manually unless noted otherwise; this generator currently scaffolds Biome config and records the rest in KICKOFF.md.");
397
+ }
398
+ return notes;
399
+ }
400
+ export function assertWritableTarget(targetDir) {
401
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
402
+ throw new Error(`Directory "${targetDir}" already exists and is not empty.`);
403
+ }
404
+ }
405
+ export function scaffoldStack(options) {
406
+ const { targetDir, config, catalog = defaultCatalog } = options;
407
+ const projectName = sanitizeProjectName(options.projectName);
408
+ const unsupported = unsupportedNotes(config);
409
+ let fileCount = 0;
410
+ const emit = (rel, content) => {
411
+ if (content === undefined)
412
+ return;
413
+ write(targetDir, rel, content);
414
+ fileCount += 1;
415
+ };
416
+ mkdirSync(targetDir, { recursive: true });
417
+ emit("package.json", packageJson(projectName, config));
418
+ emit("README.md", readme(projectName, config, unsupported));
419
+ emit("KICKOFF.md", generateKickoff(config, projectName, catalog));
420
+ emit("stack.json", `${generateStackJson(config, projectName)}\n`);
421
+ emit(".gitignore", "node_modules\n.next\ndist\n.env*\n!.env.example\n.DS_Store\n");
422
+ emit("tsconfig.json", tsconfig(config));
423
+ emit(".env.example", envExample(config));
424
+ if (single(config, "commitStyle") === "commit-conventional") {
425
+ emit("commitlint.config.cjs", 'module.exports = { extends: ["@commitlint/config-conventional"] };\n');
426
+ }
427
+ if (multi(config, "addons").includes("addon-biome")) {
428
+ emit("biome.json", `${JSON.stringify({ $schema: "https://biomejs.dev/schemas/2.0.6/schema.json", formatter: { enabled: true }, linter: { enabled: true } }, null, 2)}\n`);
429
+ }
430
+ if (single(config, "web") === "web-next") {
431
+ const isCronusUi = usesCronusUi(config);
432
+ emit("next.config.mjs", "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n");
433
+ const app = appDir(config);
434
+ if (isCronusUi) {
435
+ emit("postcss.config.mjs", 'export default { plugins: { "@tailwindcss/postcss": {} } };\n');
436
+ emit(`${app}/globals.css`, globalsCss(config));
437
+ emit(`${app}/layout.tsx`, layoutTsx(projectName));
438
+ emit(`${app}/page.tsx`, pageTsx());
439
+ emit("cronus-ui.json", `${JSON.stringify({
440
+ aliases: { ui: "@/components/ui", lib: "@/lib", blocks: "@/components/blocks" },
441
+ paths: cronusUiPaths(config),
442
+ registry: CRONUS_UI_REGISTRY,
443
+ theme: { name: "aurora", mode: "dark" },
444
+ }, null, 2)}\n`);
445
+ }
446
+ else {
447
+ emit(`${app}/globals.css`, neutralGlobalsCss());
448
+ emit(`${app}/layout.tsx`, neutralLayoutTsx(projectName));
449
+ emit(`${app}/page.tsx`, neutralPageTsx(config));
450
+ }
451
+ }
452
+ else {
453
+ emit("src/index.ts", basicIndex(projectName));
454
+ }
455
+ const assistants = assistantIds(config);
456
+ const isCronusUi = usesCronusUi(config);
457
+ const cronusUiMcp = isCronusUi && multi(config, "mcp").includes("mcp-cronus-ui");
458
+ if (assistants.length > 0 || cronusUiMcp) {
459
+ const { written } = writeAiKit({
460
+ targetDir,
461
+ name: projectName,
462
+ assistants,
463
+ preset: "standard",
464
+ includeCronusUi: isCronusUi,
465
+ cronusUiMcp,
466
+ });
467
+ fileCount += written.length;
468
+ }
469
+ else if (isCronusUi) {
470
+ const { written } = writeDesignDocuments(targetDir);
471
+ fileCount += written.length;
472
+ }
473
+ return { fileCount, unsupported };
474
+ }
475
+ export function initGit(cwd) {
476
+ runCommand("git", ["init"], cwd);
477
+ }
478
+ export function runInstall(pm, cwd) {
479
+ runCommand(pm, ["install"], cwd);
480
+ }
481
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1,23 @@
1
+ import type { StackConfig } from "@cronus-ui/stack";
2
+ export declare const PACKAGE_MANAGERS: readonly ["bun", "npm", "pnpm", "yarn"];
3
+ export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
4
+ export declare const c: {
5
+ bold: (s: string) => string;
6
+ dim: (s: string) => string;
7
+ green: (s: string) => string;
8
+ yellow: (s: string) => string;
9
+ red: (s: string) => string;
10
+ cyan: (s: string) => string;
11
+ magenta: (s: string) => string;
12
+ };
13
+ export declare const log: {
14
+ intro(): void;
15
+ step(msg: string): void;
16
+ ok(msg: string): void;
17
+ warn(msg: string): void;
18
+ error(msg: string): void;
19
+ outro(name: string, pm: PackageManager, installed: boolean): void;
20
+ };
21
+ export declare function packageManagerFromConfig(config: StackConfig): PackageManager;
22
+ export declare function runCommand(command: string, args: string[], cwd: string): void;
23
+ //# sourceMappingURL=utils.d.ts.map
package/dist/utils.js ADDED
@@ -0,0 +1,70 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
3
+ const useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
4
+ const wrap = (open, close) => (s) => useColor ? `\x1b[${open}m${s}\x1b[${close}m` : s;
5
+ export const c = {
6
+ bold: wrap(1, 22),
7
+ dim: wrap(2, 22),
8
+ green: wrap(32, 39),
9
+ yellow: wrap(33, 39),
10
+ red: wrap(31, 39),
11
+ cyan: wrap(36, 39),
12
+ magenta: wrap(35, 39),
13
+ };
14
+ export const log = {
15
+ intro() {
16
+ process.stdout.write(`\n${c.magenta(c.bold("create-cronus-stack"))}\n\n`);
17
+ },
18
+ step(msg) {
19
+ process.stdout.write(`${c.cyan("›")} ${msg}\n`);
20
+ },
21
+ ok(msg) {
22
+ process.stdout.write(`${c.green("✓")} ${msg}\n`);
23
+ },
24
+ warn(msg) {
25
+ process.stdout.write(`${c.yellow("!")} ${msg}\n`);
26
+ },
27
+ error(msg) {
28
+ process.stderr.write(`${c.red("✗")} ${msg}\n`);
29
+ },
30
+ outro(name, pm, installed) {
31
+ const install = pm === "yarn" ? "yarn" : `${pm} install`;
32
+ const dev = pm === "npm" ? "npm run dev" : `${pm} dev`;
33
+ const lines = [
34
+ "",
35
+ `${c.green(c.bold("Done!"))} Your Cronus stack is ready in ${c.cyan(name)}.`,
36
+ "",
37
+ "Next steps:",
38
+ ` ${c.dim("$")} cd ${name}`,
39
+ ...(installed ? [] : [` ${c.dim("$")} ${install}`]),
40
+ ` ${c.dim("$")} ${dev}`,
41
+ "",
42
+ `Read ${c.cyan("KICKOFF.md")} before changing the generated stack.`,
43
+ "",
44
+ ];
45
+ process.stdout.write(`${lines.join("\n")}\n`);
46
+ },
47
+ };
48
+ export function packageManagerFromConfig(config) {
49
+ switch (config.packageManager) {
50
+ case "pm-npm":
51
+ return "npm";
52
+ case "pm-pnpm":
53
+ return "pnpm";
54
+ default:
55
+ return "bun";
56
+ }
57
+ }
58
+ export function runCommand(command, args, cwd) {
59
+ const result = spawnSync(command, args, {
60
+ cwd,
61
+ stdio: "inherit",
62
+ shell: process.platform === "win32",
63
+ });
64
+ if (result.error)
65
+ throw result.error;
66
+ if (typeof result.status === "number" && result.status !== 0) {
67
+ throw new Error(`${command} ${args.join(" ")} exited with code ${result.status}`);
68
+ }
69
+ }
70
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1,2 @@
1
+ export declare const CREATE_STACK_VERSION = "0.6.0";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const CREATE_STACK_VERSION = "0.6.0";
2
+ //# sourceMappingURL=version.js.map
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "create-cronus-stack",
3
+ "version": "0.6.0",
4
+ "description": "Scaffold a Cronus stack from the Cronus Stack Builder contract: app files, stack.json, KICKOFF.md, and optional AI Kit.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Cronus",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pedrogbraz/cronus-ui.git",
11
+ "directory": "packages/create-cronus-stack"
12
+ },
13
+ "homepage": "https://aicronus.com",
14
+ "bugs": {
15
+ "url": "https://github.com/pedrogbraz/cronus-ui/issues"
16
+ },
17
+ "keywords": [
18
+ "cronus",
19
+ "create-cronus-stack",
20
+ "stack",
21
+ "scaffold",
22
+ "next",
23
+ "cronus-ui",
24
+ "ai"
25
+ ],
26
+ "bin": {
27
+ "create-cronus-stack": "./dist/index.js",
28
+ "cronus-stack": "./dist/index.js"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "LICENSE",
33
+ "README.md",
34
+ "!dist/**/*.map"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit",
42
+ "test": "vitest run --config vitest.config.ts",
43
+ "test:watch": "vitest --config vitest.config.ts",
44
+ "prepublishOnly": "tsc -p tsconfig.json"
45
+ },
46
+ "dependencies": {
47
+ "@cronus-ui/ai-kit": "0.6.0",
48
+ "@cronus-ui/stack": "0.6.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.10.0",
52
+ "typescript": "^6.0.3",
53
+ "vitest": "^4.1.9"
54
+ },
55
+ "engines": {
56
+ "node": ">=20"
57
+ }
58
+ }