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