create-kyro 0.5.4 → 0.5.5
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/package.json +1 -1
- package/src/generators/astro.ts +6 -4
- package/src/generators/config.ts +24 -8
- package/src/generators/files.ts +97 -39
- package/src/generators/packagejson.ts +3 -0
- package/src/index.ts +22 -2
- package/src/prompts.ts +7 -0
- package/test/generators.test.ts +25 -10
- package/test/integration.test.ts +5 -4
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,16 +1,29 @@
|
|
|
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
|
|
|
16
29
|
const tsconfig = `{
|
|
@@ -64,16 +77,15 @@ Visit [https://kyro.dev](https://kyro.dev) for full documentation.
|
|
|
64
77
|
const envExample = `# Kyro CMS Configuration
|
|
65
78
|
# Copy this file to .env and fill in your values
|
|
66
79
|
|
|
67
|
-
${
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
80
|
+
${answers.database === "sqlite"
|
|
81
|
+
? "# SQLite (local) - no additional config needed"
|
|
82
|
+
: answers.database === "postgres"
|
|
83
|
+
? "# Database connection (PostgreSQL)\nDATABASE_URL=postgresql://user:password@localhost:5432/kyro_cms"
|
|
84
|
+
: "# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/kyro_cms"
|
|
85
|
+
}
|
|
74
86
|
|
|
75
|
-
#
|
|
76
|
-
|
|
87
|
+
# App secret (set once; on first run it's stored in the database and can be managed via admin UI)
|
|
88
|
+
APP_SECRET=your-secret-here
|
|
77
89
|
|
|
78
90
|
# Admin credentials (used for first-user bootstrap)
|
|
79
91
|
# KYRO_ADMIN_EMAIL=admin@example.com
|
|
@@ -82,7 +94,39 @@ JWT_SECRET=change-this-to-a-random-64-character-string
|
|
|
82
94
|
|
|
83
95
|
writeFileSync(join(projectDir, ".env.example"), envExample);
|
|
84
96
|
|
|
97
|
+
if (adminCredentials) {
|
|
98
|
+
const envFile = `# Kyro CMS Configuration
|
|
99
|
+
# Copy this file to .env and fill in your values
|
|
100
|
+
|
|
101
|
+
${answers.database === "sqlite"
|
|
102
|
+
? "# SQLite (local) - no additional config needed"
|
|
103
|
+
: answers.database === "postgres"
|
|
104
|
+
? "# Database connection (PostgreSQL)\nDATABASE_URL=postgresql://user:password@localhost:5432/kyro_cms"
|
|
105
|
+
: "# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/kyro_cms"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# App secret (auto-generated; stored in database on first run)
|
|
109
|
+
APP_SECRET=${generateAppSecret()}
|
|
110
|
+
|
|
111
|
+
# Admin credentials (used for first-user bootstrap)
|
|
112
|
+
KYRO_ADMIN_EMAIL=${adminCredentials.adminEmail}
|
|
113
|
+
KYRO_ADMIN_PASSWORD=${adminCredentials.adminPassword}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
writeFileSync(join(projectDir, ".env"), envFile);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const mainCss = `@import "tailwindcss";
|
|
120
|
+
|
|
121
|
+
@theme {
|
|
122
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
writeFileSync(join(stylesDir, "main.css"), mainCss);
|
|
127
|
+
|
|
85
128
|
const indexPage = `---
|
|
129
|
+
import "../styles/main.css";
|
|
86
130
|
const title = "${answers.projectName}";
|
|
87
131
|
---
|
|
88
132
|
<!DOCTYPE html>
|
|
@@ -92,38 +136,52 @@ const title = "${answers.projectName}";
|
|
|
92
136
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
93
137
|
<title>{title}</title>
|
|
94
138
|
</head>
|
|
95
|
-
<body
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
139
|
+
<body
|
|
140
|
+
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"
|
|
141
|
+
>
|
|
142
|
+
<div class="relative min-h-screen flex flex-col items-center justify-center px-4">
|
|
143
|
+
<!-- Decorative background blurs -->
|
|
144
|
+
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
|
145
|
+
<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>
|
|
146
|
+
<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>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<main class="relative text-center max-w-lg">
|
|
150
|
+
<!-- Badge -->
|
|
151
|
+
<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">
|
|
152
|
+
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
|
153
|
+
Powered by Kyro CMS
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Title -->
|
|
157
|
+
<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">
|
|
158
|
+
{title}
|
|
159
|
+
</h1>
|
|
160
|
+
|
|
161
|
+
<!-- Tagline -->
|
|
162
|
+
<p class="mb-10 text-lg sm:text-xl text-stone-500 dark:text-stone-400 leading-relaxed">
|
|
163
|
+
Your content management system is ready. Start building something amazing.
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
<!-- Admin link -->
|
|
167
|
+
<a
|
|
168
|
+
href="/admin"
|
|
169
|
+
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]"
|
|
170
|
+
>
|
|
171
|
+
Go to Admin Dashboard
|
|
172
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
173
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
|
|
174
|
+
</svg>
|
|
175
|
+
</a>
|
|
176
|
+
</main>
|
|
177
|
+
|
|
178
|
+
<!-- Footer -->
|
|
179
|
+
<footer class="absolute bottom-8 text-xs text-stone-400 dark:text-stone-500">
|
|
180
|
+
© {new Date().getFullYear()} {title}
|
|
181
|
+
</footer>
|
|
182
|
+
</div>
|
|
101
183
|
</body>
|
|
102
184
|
</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
185
|
`;
|
|
128
186
|
|
|
129
187
|
writeFileSync(join(pagesDir, "index.astro"), indexPage);
|
|
@@ -15,6 +15,9 @@ 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",
|
|
18
21
|
"@kyro-cms/core": "latest",
|
|
19
22
|
"@kyro-cms/admin": "latest",
|
|
20
23
|
};
|
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
|
|
|
@@ -208,11 +219,15 @@ describe("generators", () => {
|
|
|
208
219
|
expect(pkg.dependencies["react"]).toBeUndefined();
|
|
209
220
|
expect(pkg.dependencies["react-dom"]).toBeUndefined();
|
|
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 tailwindcss and @tailwindcss/vite", () => {
|
|
226
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
227
|
+
expect(pkg.dependencies["tailwindcss"]).toBeDefined();
|
|
228
|
+
expect(pkg.dependencies["@tailwindcss/vite"]).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
216
231
|
it("never includes manual auth bootstrap script", () => {
|
|
217
232
|
const pkg = generatePackageJson(baseAnswers);
|
|
218
233
|
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;
|
|
@@ -116,7 +117,7 @@ describe("file generation", () => {
|
|
|
116
117
|
expect(config).not.toContain("mysql");
|
|
117
118
|
expect(config).not.toContain("api {");
|
|
118
119
|
expect(config).toContain("defineConfig");
|
|
119
|
-
expect(config).toContain("
|
|
120
|
+
expect(config).toContain("APP_SECRET");
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
it("uses correct template import path", () => {
|
|
@@ -162,8 +163,8 @@ describe("file generation", () => {
|
|
|
162
163
|
content = readFileSync(join(tmpDir, ".env.example"), "utf8");
|
|
163
164
|
});
|
|
164
165
|
|
|
165
|
-
it("contains
|
|
166
|
-
expect(content).toContain("
|
|
166
|
+
it("contains APP_SECRET", () => {
|
|
167
|
+
expect(content).toContain("APP_SECRET");
|
|
167
168
|
});
|
|
168
169
|
|
|
169
170
|
it("contains admin credentials comments", () => {
|
|
@@ -217,7 +218,7 @@ describe("all database × template combinations produce valid output", () => {
|
|
|
217
218
|
for (const db of allDatabases) {
|
|
218
219
|
for (const template of allTemplates) {
|
|
219
220
|
it(`${db} + ${template} generates valid config`, () => {
|
|
220
|
-
const answers: Answers = { projectName: "combo-test", database: db, template };
|
|
221
|
+
const answers: Answers = { projectName: "combo-test", database: db, template, adminEmail: "admin@combo-test.local" };
|
|
221
222
|
const config = generateKyroConfig(answers);
|
|
222
223
|
expect(config).toContain("defineConfig");
|
|
223
224
|
expect(config).not.toContain("mysql");
|