create-kyro 0.5.5 → 0.7.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/dist/index.js +163 -51
- package/package.json +1 -1
- package/src/generators/config.ts +19 -17
- package/src/generators/files.ts +17 -1
- package/src/generators/packagejson.ts +10 -0
- package/src/prompts.ts +13 -6
- package/test/generators.test.ts +36 -7
- package/test/integration.test.ts +9 -4
package/dist/index.js
CHANGED
|
@@ -87,6 +87,12 @@ async function promptUser() {
|
|
|
87
87
|
value: "kitchen-sink"
|
|
88
88
|
}
|
|
89
89
|
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
name: "adminEmail",
|
|
94
|
+
message: "Admin email:",
|
|
95
|
+
initial: (prev, values) => `admin@${values.projectName}.local`
|
|
90
96
|
}
|
|
91
97
|
],
|
|
92
98
|
{
|
|
@@ -151,16 +157,25 @@ ${cyan("?")} ${bold(msg)}`);
|
|
|
151
157
|
function generatePackageJson(answers) {
|
|
152
158
|
const deps = {
|
|
153
159
|
"astro": "^6.3.1",
|
|
160
|
+
"@astrojs/react": "^5.0.4",
|
|
161
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
162
|
+
"tailwindcss": "^4.0.0",
|
|
163
|
+
"react": "^19.0.0",
|
|
164
|
+
"react-dom": "^19.0.0",
|
|
154
165
|
"@kyro-cms/core": "latest",
|
|
155
166
|
"@kyro-cms/admin": "latest"
|
|
156
167
|
};
|
|
157
168
|
const devDeps = {
|
|
158
|
-
"typescript": "^5.7.3"
|
|
169
|
+
"typescript": "^5.7.3",
|
|
170
|
+
"@types/react": "^19.0.0",
|
|
171
|
+
"@types/react-dom": "^19.0.0"
|
|
159
172
|
};
|
|
160
173
|
const scripts = {
|
|
161
174
|
"dev": "astro dev",
|
|
162
175
|
"build": "astro build",
|
|
163
|
-
"preview": "astro preview"
|
|
176
|
+
"preview": "astro preview",
|
|
177
|
+
"kyro:dev": "kyro dev",
|
|
178
|
+
"kyro:generate": "kyro generate"
|
|
164
179
|
};
|
|
165
180
|
if (answers.database === "sqlite") {
|
|
166
181
|
scripts["db:generate"] = "kyro generate";
|
|
@@ -174,7 +189,10 @@ function generatePackageJson(answers) {
|
|
|
174
189
|
private: true,
|
|
175
190
|
scripts,
|
|
176
191
|
dependencies: deps,
|
|
177
|
-
devDependencies: devDeps
|
|
192
|
+
devDependencies: devDeps,
|
|
193
|
+
overrides: {
|
|
194
|
+
"vite": "^7"
|
|
195
|
+
}
|
|
178
196
|
};
|
|
179
197
|
}
|
|
180
198
|
function formatPackageJson(pkg) {
|
|
@@ -207,19 +225,19 @@ function generateKyroConfig(answers) {
|
|
|
207
225
|
let templateGlobals = "";
|
|
208
226
|
switch (answers.template) {
|
|
209
227
|
case "minimal":
|
|
210
|
-
templateCollections = "import { minimalCollections } from '@kyro-cms/core/templates';";
|
|
228
|
+
templateCollections = "import { minimalCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
211
229
|
templateGlobals = "import { coreSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
212
230
|
break;
|
|
213
231
|
case "blog":
|
|
214
|
-
templateCollections = "import { blogCollections } from '@kyro-cms/core/templates';";
|
|
232
|
+
templateCollections = "import { blogCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
215
233
|
templateGlobals = "import { allSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
216
234
|
break;
|
|
217
235
|
case "ecommerce":
|
|
218
|
-
templateCollections = "import { ecommerceCollections } from '@kyro-cms/core/templates';";
|
|
236
|
+
templateCollections = "import { ecommerceCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
219
237
|
templateGlobals = "import { allSettingsGlobals, ecommerceSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
220
238
|
break;
|
|
221
239
|
case "kitchen-sink":
|
|
222
|
-
templateCollections = `import { minimalCollections, blogCollections, ecommerceCollections, kitchenSinkCollections } from '@kyro-cms/core/templates';`;
|
|
240
|
+
templateCollections = `import { minimalCollections, blogCollections, ecommerceCollections, kitchenSinkCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';`;
|
|
223
241
|
templateGlobals = "import { allSettingsGlobals, ecommerceSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
224
242
|
break;
|
|
225
243
|
}
|
|
@@ -227,17 +245,31 @@ function generateKyroConfig(answers) {
|
|
|
227
245
|
if (templateGlobals) imports.push(templateGlobals);
|
|
228
246
|
let collectionsConfig = "";
|
|
229
247
|
if (answers.template === "minimal") {
|
|
230
|
-
collectionsConfig = ` collections:
|
|
248
|
+
collectionsConfig = ` collections: [
|
|
249
|
+
...Object.values(minimalCollections),
|
|
250
|
+
...Object.values(mediaCollections),
|
|
251
|
+
...Object.values(authCollections),
|
|
252
|
+
],`;
|
|
231
253
|
} else if (answers.template === "blog") {
|
|
232
|
-
collectionsConfig = ` collections:
|
|
254
|
+
collectionsConfig = ` collections: [
|
|
255
|
+
...Object.values(blogCollections),
|
|
256
|
+
...Object.values(mediaCollections),
|
|
257
|
+
...Object.values(authCollections),
|
|
258
|
+
],`;
|
|
233
259
|
} else if (answers.template === "ecommerce") {
|
|
234
|
-
collectionsConfig = ` collections:
|
|
260
|
+
collectionsConfig = ` collections: [
|
|
261
|
+
...Object.values(ecommerceCollections),
|
|
262
|
+
...Object.values(mediaCollections),
|
|
263
|
+
...Object.values(authCollections),
|
|
264
|
+
],`;
|
|
235
265
|
} else if (answers.template === "kitchen-sink") {
|
|
236
266
|
collectionsConfig = ` collections: [
|
|
237
267
|
...Object.values(minimalCollections),
|
|
238
268
|
...Object.values(blogCollections),
|
|
239
269
|
...Object.values(ecommerceCollections),
|
|
240
270
|
...Object.values(kitchenSinkCollections),
|
|
271
|
+
...Object.values(mediaCollections),
|
|
272
|
+
...Object.values(authCollections),
|
|
241
273
|
],`;
|
|
242
274
|
}
|
|
243
275
|
let globalsConfig = "";
|
|
@@ -256,7 +288,9 @@ export default defineConfig({
|
|
|
256
288
|
${adapterLines.join("\n")}
|
|
257
289
|
${collectionsConfig}
|
|
258
290
|
${globalsConfig}
|
|
259
|
-
auth:
|
|
291
|
+
auth: {
|
|
292
|
+
secret: process.env.APP_SECRET,
|
|
293
|
+
},
|
|
260
294
|
});`;
|
|
261
295
|
}
|
|
262
296
|
|
|
@@ -265,13 +299,15 @@ function generateAstroConfig(answers) {
|
|
|
265
299
|
return `import { defineConfig } from 'astro/config';
|
|
266
300
|
import { kyro } from '@kyro-cms/core';
|
|
267
301
|
import { kyroAdmin } from '@kyro-cms/admin';
|
|
302
|
+
import react from '@astrojs/react';
|
|
303
|
+
import tailwind from '@tailwindcss/vite';
|
|
268
304
|
|
|
269
305
|
export default defineConfig({
|
|
270
306
|
output: 'server',
|
|
271
|
-
integrations: [
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
integrations: [react(), kyro({ adminPath: '/admin', apiPath: '/api' }), kyroAdmin({ basePath: '/admin', apiPath: '/api' })],
|
|
308
|
+
vite: {
|
|
309
|
+
plugins: [tailwind()],
|
|
310
|
+
},
|
|
275
311
|
server: {
|
|
276
312
|
port: 4321,
|
|
277
313
|
host: true,
|
|
@@ -282,19 +318,30 @@ export default defineConfig({
|
|
|
282
318
|
// src/generators/files.ts
|
|
283
319
|
import { writeFileSync, mkdirSync } from "fs";
|
|
284
320
|
import { join } from "path";
|
|
285
|
-
|
|
321
|
+
import { randomBytes } from "crypto";
|
|
322
|
+
function generateAppSecret() {
|
|
323
|
+
return randomBytes(32).toString("hex");
|
|
324
|
+
}
|
|
325
|
+
function generateProjectFiles(answers, projectDir, adminCredentials) {
|
|
286
326
|
const srcDir = join(projectDir, "src");
|
|
287
327
|
const pagesDir = join(srcDir, "pages");
|
|
328
|
+
const stylesDir = join(srcDir, "styles");
|
|
288
329
|
const publicDir = join(projectDir, "public");
|
|
289
330
|
mkdirSync(pagesDir, { recursive: true });
|
|
331
|
+
mkdirSync(stylesDir, { recursive: true });
|
|
290
332
|
mkdirSync(publicDir, { recursive: true });
|
|
333
|
+
if (answers.database === "sqlite") {
|
|
334
|
+
mkdirSync(join(projectDir, "data"), { recursive: true });
|
|
335
|
+
}
|
|
291
336
|
const tsconfig = `{
|
|
292
337
|
"extends": "astro/tsconfigs/strict",
|
|
293
338
|
"compilerOptions": {
|
|
294
339
|
"baseUrl": ".",
|
|
295
340
|
"paths": {
|
|
296
341
|
"@/*": ["./src/*"]
|
|
297
|
-
}
|
|
342
|
+
},
|
|
343
|
+
"jsx": "react-jsx",
|
|
344
|
+
"jsxImportSource": "react"
|
|
298
345
|
}
|
|
299
346
|
}`;
|
|
300
347
|
writeFileSync(join(projectDir, "tsconfig.json"), tsconfig);
|
|
@@ -333,17 +380,50 @@ Visit [https://kyro.dev](https://kyro.dev) for full documentation.
|
|
|
333
380
|
const envExample = `# Kyro CMS Configuration
|
|
334
381
|
# Copy this file to .env and fill in your values
|
|
335
382
|
|
|
383
|
+
NODE_ENV=development
|
|
384
|
+
APP_URL=http://localhost:4321
|
|
385
|
+
|
|
336
386
|
${answers.database === "sqlite" ? "# SQLite (local) - no additional config needed" : answers.database === "postgres" ? "# Database connection (PostgreSQL)\nDATABASE_URL=postgresql://user:password@localhost:5432/kyro_cms" : "# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/kyro_cms"}
|
|
337
387
|
|
|
338
|
-
#
|
|
339
|
-
|
|
388
|
+
# App secret (set once; on first run it's stored in the database and can be managed via admin UI)
|
|
389
|
+
APP_SECRET=your-secret-here
|
|
340
390
|
|
|
341
391
|
# Admin credentials (used for first-user bootstrap)
|
|
342
392
|
# KYRO_ADMIN_EMAIL=admin@example.com
|
|
343
393
|
# KYRO_ADMIN_PASSWORD=SecurePass123!
|
|
344
394
|
`;
|
|
345
395
|
writeFileSync(join(projectDir, ".env.example"), envExample);
|
|
396
|
+
if (adminCredentials) {
|
|
397
|
+
const envFile = `# Kyro CMS Configuration
|
|
398
|
+
# Copy this file to .env and fill in your values
|
|
399
|
+
|
|
400
|
+
NODE_ENV=development
|
|
401
|
+
APP_URL=http://localhost:4321
|
|
402
|
+
|
|
403
|
+
${answers.database === "sqlite" ? "# SQLite (local) - no additional config needed" : answers.database === "postgres" ? "# Database connection (PostgreSQL)\nDATABASE_URL=postgresql://user:password@localhost:5432/kyro_cms" : "# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/kyro_cms"}
|
|
404
|
+
|
|
405
|
+
# App secret (auto-generated; stored in database on first run)
|
|
406
|
+
APP_SECRET=${generateAppSecret()}
|
|
407
|
+
|
|
408
|
+
# Admin credentials (used for first-user bootstrap)
|
|
409
|
+
KYRO_ADMIN_EMAIL=${adminCredentials.adminEmail}
|
|
410
|
+
KYRO_ADMIN_PASSWORD=${adminCredentials.adminPassword}
|
|
411
|
+
`;
|
|
412
|
+
writeFileSync(join(projectDir, ".env"), envFile);
|
|
413
|
+
}
|
|
414
|
+
const mainCss = `@import "tailwindcss";
|
|
415
|
+
|
|
416
|
+
@source "./pages/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
417
|
+
@source "./components/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
418
|
+
@source "./layouts/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
419
|
+
|
|
420
|
+
@theme {
|
|
421
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
writeFileSync(join(stylesDir, "main.css"), mainCss);
|
|
346
425
|
const indexPage = `---
|
|
426
|
+
import "../styles/main.css";
|
|
347
427
|
const title = "${answers.projectName}";
|
|
348
428
|
---
|
|
349
429
|
<!DOCTYPE html>
|
|
@@ -353,38 +433,52 @@ const title = "${answers.projectName}";
|
|
|
353
433
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
354
434
|
<title>{title}</title>
|
|
355
435
|
</head>
|
|
356
|
-
<body
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
436
|
+
<body
|
|
437
|
+
class="min-h-screen bg-gradient-to-br from-stone-50 via-white to-stone-100 dark:from-stone-950 dark:via-stone-900 dark:to-stone-950 text-stone-900 dark:text-stone-100 antialiased"
|
|
438
|
+
>
|
|
439
|
+
<div class="relative min-h-screen flex flex-col items-center justify-center px-4">
|
|
440
|
+
<!-- Decorative background blurs -->
|
|
441
|
+
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
|
442
|
+
<div class="absolute -top-40 -right-40 w-96 h-96 rounded-full bg-indigo-100/60 dark:bg-indigo-900/20 blur-3xl"></div>
|
|
443
|
+
<div class="absolute -bottom-40 -left-40 w-96 h-96 rounded-full bg-indigo-100/60 dark:bg-indigo-900/20 blur-3xl"></div>
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<main class="relative text-center max-w-lg">
|
|
447
|
+
<!-- Badge -->
|
|
448
|
+
<div class="mb-8 inline-flex items-center gap-2 px-3 py-1 rounded-full border border-stone-200 dark:border-stone-700 text-xs font-medium text-stone-500 dark:text-stone-400">
|
|
449
|
+
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
|
450
|
+
Powered by Kyro CMS
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<!-- Title -->
|
|
454
|
+
<h1 class="mb-4 text-5xl sm:text-6xl font-black tracking-tight bg-gradient-to-r from-stone-900 to-stone-500 dark:from-white dark:to-stone-400 bg-clip-text text-transparent">
|
|
455
|
+
{title}
|
|
456
|
+
</h1>
|
|
457
|
+
|
|
458
|
+
<!-- Tagline -->
|
|
459
|
+
<p class="mb-10 text-lg sm:text-xl text-stone-500 dark:text-stone-400 leading-relaxed">
|
|
460
|
+
Your content management system is ready. Start building something amazing.
|
|
461
|
+
</p>
|
|
462
|
+
|
|
463
|
+
<!-- Admin link -->
|
|
464
|
+
<a
|
|
465
|
+
href="/admin"
|
|
466
|
+
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-stone-900 dark:bg-white text-white dark:text-stone-900 font-semibold text-sm hover:bg-stone-800 dark:hover:bg-stone-100 transition-all shadow-lg hover:shadow-xl active:scale-[0.97]"
|
|
467
|
+
>
|
|
468
|
+
Go to Admin Dashboard
|
|
469
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
470
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
|
|
471
|
+
</svg>
|
|
472
|
+
</a>
|
|
473
|
+
</main>
|
|
474
|
+
|
|
475
|
+
<!-- Footer -->
|
|
476
|
+
<footer class="absolute bottom-8 text-xs text-stone-400 dark:text-stone-500">
|
|
477
|
+
© {new Date().getFullYear()} {title}
|
|
478
|
+
</footer>
|
|
479
|
+
</div>
|
|
362
480
|
</body>
|
|
363
481
|
</html>
|
|
364
|
-
|
|
365
|
-
<style>
|
|
366
|
-
main {
|
|
367
|
-
max-width: 800px;
|
|
368
|
-
margin: 4rem auto;
|
|
369
|
-
padding: 2rem;
|
|
370
|
-
text-align: center;
|
|
371
|
-
font-family: system-ui, sans-serif;
|
|
372
|
-
}
|
|
373
|
-
h1 {
|
|
374
|
-
font-size: 2.5rem;
|
|
375
|
-
margin-bottom: 1rem;
|
|
376
|
-
}
|
|
377
|
-
p {
|
|
378
|
-
color: #666;
|
|
379
|
-
}
|
|
380
|
-
a {
|
|
381
|
-
color: #6366f1;
|
|
382
|
-
text-decoration: none;
|
|
383
|
-
}
|
|
384
|
-
a:hover {
|
|
385
|
-
text-decoration: underline;
|
|
386
|
-
}
|
|
387
|
-
</style>
|
|
388
482
|
`;
|
|
389
483
|
writeFileSync(join(pagesDir, "index.astro"), indexPage);
|
|
390
484
|
}
|
|
@@ -393,6 +487,16 @@ const title = "${answers.projectName}";
|
|
|
393
487
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
394
488
|
import { join as join2 } from "path";
|
|
395
489
|
import { execSync } from "child_process";
|
|
490
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
491
|
+
function generatePassword(length = 24) {
|
|
492
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
|
|
493
|
+
const bytes = randomBytes2(length);
|
|
494
|
+
let password = "";
|
|
495
|
+
for (let i = 0; i < length; i++) {
|
|
496
|
+
password += chars[bytes[i] % chars.length];
|
|
497
|
+
}
|
|
498
|
+
return password;
|
|
499
|
+
}
|
|
396
500
|
var VERSION = "0.4.0";
|
|
397
501
|
async function main() {
|
|
398
502
|
logger.intro("create-kyro", VERSION);
|
|
@@ -402,6 +506,7 @@ async function main() {
|
|
|
402
506
|
logger.error(`Directory "${answers.projectName}" already exists.`);
|
|
403
507
|
process.exit(1);
|
|
404
508
|
}
|
|
509
|
+
const adminPassword = generatePassword();
|
|
405
510
|
const steps = [
|
|
406
511
|
"Creating project directory",
|
|
407
512
|
"Generating configuration files",
|
|
@@ -424,7 +529,7 @@ async function main() {
|
|
|
424
529
|
const astroConfig = generateAstroConfig(answers);
|
|
425
530
|
writeFileSync2(join2(projectDir, "astro.config.mjs"), astroConfig);
|
|
426
531
|
logger.success("astro.config.mjs generated");
|
|
427
|
-
generateProjectFiles(answers, projectDir);
|
|
532
|
+
generateProjectFiles(answers, projectDir, { adminEmail: answers.adminEmail, adminPassword });
|
|
428
533
|
logger.success("Project files generated");
|
|
429
534
|
logger.step(3, steps.length, steps[2]);
|
|
430
535
|
console.log(" Installing dependencies...");
|
|
@@ -457,7 +562,14 @@ async function main() {
|
|
|
457
562
|
console.log(" Visit http://localhost:4321 to see your app.");
|
|
458
563
|
console.log(" Visit http://localhost:4321/admin for the admin dashboard.");
|
|
459
564
|
console.log("");
|
|
460
|
-
console.log("
|
|
565
|
+
console.log(" \x1B[33m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\x1B[0m");
|
|
566
|
+
console.log(" \x1B[33m Admin Account Created\x1B[0m");
|
|
567
|
+
console.log(" \x1B[33m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\x1B[0m");
|
|
568
|
+
console.log(` Email: \x1B[36m${answers.adminEmail}\x1B[0m`);
|
|
569
|
+
console.log(` Password: \x1B[36m${adminPassword}\x1B[0m`);
|
|
570
|
+
console.log(" \x1B[33m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\x1B[0m");
|
|
571
|
+
console.log(" The admin user will be created automatically on first server start.");
|
|
572
|
+
console.log(" You can change these credentials in the .env file.");
|
|
461
573
|
console.log("");
|
|
462
574
|
}
|
|
463
575
|
main().catch((error) => {
|
package/package.json
CHANGED
package/src/generators/config.ts
CHANGED
|
@@ -29,25 +29,24 @@ export function generateKyroConfig(answers: Answers): string {
|
|
|
29
29
|
|
|
30
30
|
switch (answers.template) {
|
|
31
31
|
case "minimal":
|
|
32
|
-
templateCollections =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
templateCollections = "import { minimalCollections } from '@kyro-cms/core/templates';";
|
|
33
|
+
templateGlobals = "import { siteSettingsGlobal, seoSettingsGlobal } from '@kyro-cms/core/templates';";
|
|
34
|
+
break;
|
|
35
|
+
case "starter":
|
|
36
|
+
templateCollections = "import { starterCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
37
|
+
templateGlobals = "import { coreSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
36
38
|
break;
|
|
37
39
|
case "blog":
|
|
38
40
|
templateCollections = "import { blogCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
39
|
-
templateGlobals =
|
|
40
|
-
"import { allSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
41
|
+
templateGlobals = "import { allSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
41
42
|
break;
|
|
42
43
|
case "ecommerce":
|
|
43
44
|
templateCollections = "import { ecommerceCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';";
|
|
44
|
-
templateGlobals =
|
|
45
|
-
"import { allSettingsGlobals, ecommerceSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
45
|
+
templateGlobals = "import { allSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
46
46
|
break;
|
|
47
47
|
case "kitchen-sink":
|
|
48
|
-
templateCollections = `import { minimalCollections, blogCollections, ecommerceCollections, kitchenSinkCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';`;
|
|
49
|
-
templateGlobals =
|
|
50
|
-
"import { allSettingsGlobals, ecommerceSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
48
|
+
templateCollections = `import { minimalCollections, starterCollections, blogCollections, ecommerceCollections, kitchenSinkCollections, mediaCollections, authCollections } from '@kyro-cms/core/templates';`;
|
|
49
|
+
templateGlobals = "import { allSettingsGlobals } from '@kyro-cms/core/templates';";
|
|
51
50
|
break;
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -58,6 +57,10 @@ export function generateKyroConfig(answers: Answers): string {
|
|
|
58
57
|
if (answers.template === "minimal") {
|
|
59
58
|
collectionsConfig = ` collections: [
|
|
60
59
|
...Object.values(minimalCollections),
|
|
60
|
+
],`;
|
|
61
|
+
} else if (answers.template === "starter") {
|
|
62
|
+
collectionsConfig = ` collections: [
|
|
63
|
+
...Object.values(starterCollections),
|
|
61
64
|
...Object.values(mediaCollections),
|
|
62
65
|
...Object.values(authCollections),
|
|
63
66
|
],`;
|
|
@@ -76,6 +79,7 @@ export function generateKyroConfig(answers: Answers): string {
|
|
|
76
79
|
} else if (answers.template === "kitchen-sink") {
|
|
77
80
|
collectionsConfig = ` collections: [
|
|
78
81
|
...Object.values(minimalCollections),
|
|
82
|
+
...Object.values(starterCollections),
|
|
79
83
|
...Object.values(blogCollections),
|
|
80
84
|
...Object.values(ecommerceCollections),
|
|
81
85
|
...Object.values(kitchenSinkCollections),
|
|
@@ -86,13 +90,11 @@ export function generateKyroConfig(answers: Answers): string {
|
|
|
86
90
|
|
|
87
91
|
let globalsConfig = "";
|
|
88
92
|
if (answers.template === "minimal") {
|
|
93
|
+
globalsConfig = ` globals: [siteSettingsGlobal, seoSettingsGlobal],`;
|
|
94
|
+
} else if (answers.template === "starter") {
|
|
89
95
|
globalsConfig = ` globals: coreSettingsGlobals,`;
|
|
90
|
-
} else if (answers.template === "blog" || answers.template === "ecommerce") {
|
|
91
|
-
globalsConfig =
|
|
92
|
-
? ` globals: [...allSettingsGlobals, ...ecommerceSettingsGlobals],`
|
|
93
|
-
: ` globals: allSettingsGlobals,`;
|
|
94
|
-
} else if (answers.template === "kitchen-sink") {
|
|
95
|
-
globalsConfig = ` globals: [...allSettingsGlobals, ...ecommerceSettingsGlobals],`;
|
|
96
|
+
} else if (answers.template === "blog" || answers.template === "ecommerce" || answers.template === "kitchen-sink") {
|
|
97
|
+
globalsConfig = ` globals: allSettingsGlobals,`;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
return `${imports.join("\n")}
|
package/src/generators/files.ts
CHANGED
|
@@ -26,13 +26,19 @@ export function generateProjectFiles(
|
|
|
26
26
|
mkdirSync(stylesDir, { recursive: true });
|
|
27
27
|
mkdirSync(publicDir, { recursive: true });
|
|
28
28
|
|
|
29
|
+
if (answers.database === "sqlite") {
|
|
30
|
+
mkdirSync(join(projectDir, "data"), { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
const tsconfig = `{
|
|
30
34
|
"extends": "astro/tsconfigs/strict",
|
|
31
35
|
"compilerOptions": {
|
|
32
36
|
"baseUrl": ".",
|
|
33
37
|
"paths": {
|
|
34
38
|
"@/*": ["./src/*"]
|
|
35
|
-
}
|
|
39
|
+
},
|
|
40
|
+
"jsx": "react-jsx",
|
|
41
|
+
"jsxImportSource": "react"
|
|
36
42
|
}
|
|
37
43
|
}`;
|
|
38
44
|
|
|
@@ -77,6 +83,9 @@ Visit [https://kyro.dev](https://kyro.dev) for full documentation.
|
|
|
77
83
|
const envExample = `# Kyro CMS Configuration
|
|
78
84
|
# Copy this file to .env and fill in your values
|
|
79
85
|
|
|
86
|
+
NODE_ENV=development
|
|
87
|
+
APP_URL=http://localhost:4321
|
|
88
|
+
|
|
80
89
|
${answers.database === "sqlite"
|
|
81
90
|
? "# SQLite (local) - no additional config needed"
|
|
82
91
|
: answers.database === "postgres"
|
|
@@ -98,6 +107,9 @@ APP_SECRET=your-secret-here
|
|
|
98
107
|
const envFile = `# Kyro CMS Configuration
|
|
99
108
|
# Copy this file to .env and fill in your values
|
|
100
109
|
|
|
110
|
+
NODE_ENV=development
|
|
111
|
+
APP_URL=http://localhost:4321
|
|
112
|
+
|
|
101
113
|
${answers.database === "sqlite"
|
|
102
114
|
? "# SQLite (local) - no additional config needed"
|
|
103
115
|
: answers.database === "postgres"
|
|
@@ -118,6 +130,10 @@ KYRO_ADMIN_PASSWORD=${adminCredentials.adminPassword}
|
|
|
118
130
|
|
|
119
131
|
const mainCss = `@import "tailwindcss";
|
|
120
132
|
|
|
133
|
+
@source "./pages/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
134
|
+
@source "./components/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
135
|
+
@source "./layouts/**/*.{astro,html,js,jsx,ts,tsx}";
|
|
136
|
+
|
|
121
137
|
@theme {
|
|
122
138
|
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
123
139
|
}
|
|
@@ -8,6 +8,7 @@ export interface PackageJson {
|
|
|
8
8
|
scripts: Record<string, string>;
|
|
9
9
|
dependencies: Record<string, string>;
|
|
10
10
|
devDependencies: Record<string, string>;
|
|
11
|
+
overrides?: Record<string, string>;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function generatePackageJson(
|
|
@@ -18,18 +19,24 @@ export function generatePackageJson(
|
|
|
18
19
|
"@astrojs/react": "^5.0.4",
|
|
19
20
|
"@tailwindcss/vite": "^4.0.0",
|
|
20
21
|
"tailwindcss": "^4.0.0",
|
|
22
|
+
"react": "^19.0.0",
|
|
23
|
+
"react-dom": "^19.0.0",
|
|
21
24
|
"@kyro-cms/core": "latest",
|
|
22
25
|
"@kyro-cms/admin": "latest",
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
const devDeps: Record<string, string> = {
|
|
26
29
|
"typescript": "^5.7.3",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"@types/react-dom": "^19.0.0",
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
const scripts: Record<string, string> = {
|
|
30
35
|
"dev": "astro dev",
|
|
31
36
|
"build": "astro build",
|
|
32
37
|
"preview": "astro preview",
|
|
38
|
+
"kyro:dev": "kyro dev",
|
|
39
|
+
"kyro:generate": "kyro generate",
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
if (answers.database === "sqlite") {
|
|
@@ -46,6 +53,9 @@ export function generatePackageJson(
|
|
|
46
53
|
scripts,
|
|
47
54
|
dependencies: deps,
|
|
48
55
|
devDependencies: devDeps,
|
|
56
|
+
overrides: {
|
|
57
|
+
"vite": "^7",
|
|
58
|
+
},
|
|
49
59
|
};
|
|
50
60
|
}
|
|
51
61
|
|
package/src/prompts.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { validateProjectName } from "./validators.js";
|
|
|
4
4
|
export interface Answers {
|
|
5
5
|
projectName: string;
|
|
6
6
|
database: "sqlite" | "postgres" | "mongodb";
|
|
7
|
-
template: "minimal" | "blog" | "ecommerce" | "kitchen-sink";
|
|
7
|
+
template: "minimal" | "starter" | "blog" | "ecommerce" | "kitchen-sink";
|
|
8
8
|
adminEmail: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -47,29 +47,36 @@ export async function promptUser(): Promise<Answers> {
|
|
|
47
47
|
name: "template",
|
|
48
48
|
message: "Starting template:",
|
|
49
49
|
hint: " ",
|
|
50
|
-
initial:
|
|
50
|
+
initial: 0,
|
|
51
51
|
choices: [
|
|
52
52
|
{
|
|
53
53
|
title: "Minimal",
|
|
54
54
|
description:
|
|
55
|
-
"
|
|
55
|
+
"Single Posts collection — just title & content. Perfect for getting started fast.",
|
|
56
56
|
value: "minimal",
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
title: "Starter",
|
|
60
|
+
description:
|
|
61
|
+
"Pages, Posts, Categories, Menu + core settings. Great for blogs and small sites.",
|
|
62
|
+
value: "starter",
|
|
63
|
+
},
|
|
58
64
|
{
|
|
59
65
|
title: "Blog",
|
|
60
|
-
description:
|
|
66
|
+
description:
|
|
67
|
+
"Posts, categories, media library + all settings. Full blog setup.",
|
|
61
68
|
value: "blog",
|
|
62
69
|
},
|
|
63
70
|
{
|
|
64
71
|
title: "E-commerce",
|
|
65
72
|
description:
|
|
66
|
-
"Products, orders, customers, coupons +
|
|
73
|
+
"Products, orders, customers, coupons + all settings. Online store ready.",
|
|
67
74
|
value: "ecommerce",
|
|
68
75
|
},
|
|
69
76
|
{
|
|
70
77
|
title: "Kitchen Sink",
|
|
71
78
|
description:
|
|
72
|
-
"Everything:
|
|
79
|
+
"Everything: all collections + all settings. Maximum feature set.",
|
|
73
80
|
value: "kitchen-sink",
|
|
74
81
|
},
|
|
75
82
|
],
|
package/test/generators.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ const baseAnswers: Answers = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const allDatabases = ["sqlite", "postgres", "mongodb"] as const;
|
|
18
|
-
const allTemplates = ["minimal", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
18
|
+
const allTemplates = ["minimal", "starter", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
19
19
|
|
|
20
20
|
describe("generators", () => {
|
|
21
21
|
describe("generateKyroConfig", () => {
|
|
@@ -56,8 +56,16 @@ describe("generators", () => {
|
|
|
56
56
|
template: "minimal",
|
|
57
57
|
});
|
|
58
58
|
expect(minimal).toContain("minimalCollections");
|
|
59
|
-
expect(minimal).toContain("mediaCollections");
|
|
60
|
-
expect(minimal).toContain("authCollections");
|
|
59
|
+
expect(minimal).not.toContain("mediaCollections");
|
|
60
|
+
expect(minimal).not.toContain("authCollections");
|
|
61
|
+
|
|
62
|
+
const starter = generateKyroConfig({
|
|
63
|
+
...baseAnswers,
|
|
64
|
+
template: "starter",
|
|
65
|
+
});
|
|
66
|
+
expect(starter).toContain("starterCollections");
|
|
67
|
+
expect(starter).toContain("mediaCollections");
|
|
68
|
+
expect(starter).toContain("authCollections");
|
|
61
69
|
|
|
62
70
|
const blog = generateKyroConfig({ ...baseAnswers, template: "blog" });
|
|
63
71
|
expect(blog).toContain("blogCollections");
|
|
@@ -77,6 +85,7 @@ describe("generators", () => {
|
|
|
77
85
|
template: "kitchen-sink",
|
|
78
86
|
});
|
|
79
87
|
expect(kitchen).toContain("minimalCollections");
|
|
88
|
+
expect(kitchen).toContain("starterCollections");
|
|
80
89
|
expect(kitchen).toContain("blogCollections");
|
|
81
90
|
expect(kitchen).toContain("ecommerceCollections");
|
|
82
91
|
expect(kitchen).toContain("kitchenSinkCollections");
|
|
@@ -86,14 +95,22 @@ describe("generators", () => {
|
|
|
86
95
|
|
|
87
96
|
it("imports settings globals per template", () => {
|
|
88
97
|
const minimal = generateKyroConfig({ ...baseAnswers, template: "minimal" });
|
|
89
|
-
expect(minimal).toContain("
|
|
98
|
+
expect(minimal).toContain("siteSettingsGlobal");
|
|
99
|
+
expect(minimal).toContain("seoSettingsGlobal");
|
|
100
|
+
expect(minimal).not.toContain("coreSettingsGlobals");
|
|
101
|
+
|
|
102
|
+
const starter = generateKyroConfig({ ...baseAnswers, template: "starter" });
|
|
103
|
+
expect(starter).toContain("coreSettingsGlobals");
|
|
90
104
|
|
|
91
105
|
const blog = generateKyroConfig({ ...baseAnswers, template: "blog" });
|
|
92
106
|
expect(blog).toContain("allSettingsGlobals");
|
|
93
107
|
|
|
94
108
|
const ecommerce = generateKyroConfig({ ...baseAnswers, template: "ecommerce" });
|
|
95
109
|
expect(ecommerce).toContain("allSettingsGlobals");
|
|
96
|
-
expect(ecommerce).toContain("ecommerceSettingsGlobals");
|
|
110
|
+
expect(ecommerce).not.toContain("ecommerceSettingsGlobals");
|
|
111
|
+
|
|
112
|
+
const kitchen = generateKyroConfig({ ...baseAnswers, template: "kitchen-sink" });
|
|
113
|
+
expect(kitchen).toContain("allSettingsGlobals");
|
|
97
114
|
});
|
|
98
115
|
|
|
99
116
|
it("uses project name in config", () => {
|
|
@@ -216,18 +233,30 @@ describe("generators", () => {
|
|
|
216
233
|
|
|
217
234
|
it("never includes old dependencies", () => {
|
|
218
235
|
const pkg = generatePackageJson(baseAnswers);
|
|
219
|
-
expect(pkg.dependencies["react"]).
|
|
220
|
-
expect(pkg.dependencies["react-dom"]).
|
|
236
|
+
expect(pkg.dependencies["react"]).toBeDefined();
|
|
237
|
+
expect(pkg.dependencies["react-dom"]).toBeDefined();
|
|
221
238
|
expect(pkg.dependencies["lucide-react"]).toBeUndefined();
|
|
222
239
|
expect(pkg.dependencies["mysql2"]).toBeUndefined();
|
|
223
240
|
});
|
|
224
241
|
|
|
242
|
+
it("includes react type definitions", () => {
|
|
243
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
244
|
+
expect(pkg.devDependencies["@types/react"]).toBeDefined();
|
|
245
|
+
expect(pkg.devDependencies["@types/react-dom"]).toBeDefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
225
248
|
it("includes tailwindcss and @tailwindcss/vite", () => {
|
|
226
249
|
const pkg = generatePackageJson(baseAnswers);
|
|
227
250
|
expect(pkg.dependencies["tailwindcss"]).toBeDefined();
|
|
228
251
|
expect(pkg.dependencies["@tailwindcss/vite"]).toBeDefined();
|
|
229
252
|
});
|
|
230
253
|
|
|
254
|
+
it("includes vite override for Astro compatibility", () => {
|
|
255
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
256
|
+
expect(pkg.overrides).toBeDefined();
|
|
257
|
+
expect(pkg.overrides!["vite"]).toBe("^7");
|
|
258
|
+
});
|
|
259
|
+
|
|
231
260
|
it("never includes manual auth bootstrap script", () => {
|
|
232
261
|
const pkg = generatePackageJson(baseAnswers);
|
|
233
262
|
expect(pkg.scripts["db:bootstrap"]).toBeUndefined();
|
package/test/integration.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ const baseAnswers: Answers = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const allDatabases = ["sqlite", "postgres", "mongodb"] as const;
|
|
20
|
-
const allTemplates = ["minimal", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
20
|
+
const allTemplates = ["minimal", "starter", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
21
21
|
|
|
22
22
|
describe("file generation", () => {
|
|
23
23
|
let tmpDir: string;
|
|
@@ -102,12 +102,17 @@ describe("file generation", () => {
|
|
|
102
102
|
expect(deps).toContain("@kyro-cms/core");
|
|
103
103
|
expect(deps).toContain("@kyro-cms/admin");
|
|
104
104
|
expect(deps).toContain("astro");
|
|
105
|
-
|
|
106
|
-
expect(deps).
|
|
107
|
-
expect(deps).not.toContain("react-dom");
|
|
105
|
+
expect(deps).toContain("react");
|
|
106
|
+
expect(deps).toContain("react-dom");
|
|
108
107
|
expect(deps).not.toContain("lucide-react");
|
|
109
108
|
expect(deps).not.toContain("mysql2");
|
|
110
109
|
});
|
|
110
|
+
|
|
111
|
+
it("includes vite override for Astro compatibility", () => {
|
|
112
|
+
const pkg = JSON.parse(pkgContent);
|
|
113
|
+
expect(pkg.overrides).toBeDefined();
|
|
114
|
+
expect(pkg.overrides["vite"]).toBe("^7");
|
|
115
|
+
});
|
|
111
116
|
});
|
|
112
117
|
|
|
113
118
|
describe("kyro.config.ts", () => {
|