create-kyro 0.5.0 → 0.5.2
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/test/generators.test.ts +108 -2
- package/test/integration.test.ts +255 -0
package/package.json
CHANGED
package/test/generators.test.ts
CHANGED
|
@@ -13,6 +13,9 @@ const baseAnswers: Answers = {
|
|
|
13
13
|
template: "blog",
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const allDatabases = ["sqlite", "postgres", "mongodb"] as const;
|
|
17
|
+
const allTemplates = ["minimal", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
18
|
+
|
|
16
19
|
describe("generators", () => {
|
|
17
20
|
describe("generateKyroConfig", () => {
|
|
18
21
|
it("generates config with SQLite adapter", () => {
|
|
@@ -40,6 +43,12 @@ describe("generators", () => {
|
|
|
40
43
|
expect(config).toContain("auth: true");
|
|
41
44
|
});
|
|
42
45
|
|
|
46
|
+
it("never generates mysql adapter", () => {
|
|
47
|
+
const config = generateKyroConfig(baseAnswers);
|
|
48
|
+
expect(config).not.toContain("mysql");
|
|
49
|
+
expect(config).not.toContain("MySQL");
|
|
50
|
+
});
|
|
51
|
+
|
|
43
52
|
it("imports correct template collections", () => {
|
|
44
53
|
const minimal = generateKyroConfig({
|
|
45
54
|
...baseAnswers,
|
|
@@ -66,23 +75,79 @@ describe("generators", () => {
|
|
|
66
75
|
expect(kitchen).toContain("kitchenSinkCollections");
|
|
67
76
|
});
|
|
68
77
|
|
|
78
|
+
it("imports settings globals per template", () => {
|
|
79
|
+
const minimal = generateKyroConfig({ ...baseAnswers, template: "minimal" });
|
|
80
|
+
expect(minimal).toContain("coreSettingsGlobals");
|
|
81
|
+
|
|
82
|
+
const blog = generateKyroConfig({ ...baseAnswers, template: "blog" });
|
|
83
|
+
expect(blog).toContain("allSettingsGlobals");
|
|
84
|
+
|
|
85
|
+
const ecommerce = generateKyroConfig({ ...baseAnswers, template: "ecommerce" });
|
|
86
|
+
expect(ecommerce).toContain("allSettingsGlobals");
|
|
87
|
+
expect(ecommerce).toContain("ecommerceSettingsGlobals");
|
|
88
|
+
});
|
|
89
|
+
|
|
69
90
|
it("uses project name in config", () => {
|
|
70
91
|
const config = generateKyroConfig(baseAnswers);
|
|
71
92
|
expect(config).toContain("name: 'test-project'");
|
|
72
93
|
});
|
|
94
|
+
|
|
95
|
+
it("uses correct adapter import for each database", () => {
|
|
96
|
+
for (const db of allDatabases) {
|
|
97
|
+
const config = generateKyroConfig({ ...baseAnswers, database: db });
|
|
98
|
+
if (db === "sqlite") {
|
|
99
|
+
expect(config).toContain("createLocalAdapter");
|
|
100
|
+
} else if (db === "postgres") {
|
|
101
|
+
expect(config).toContain("createDrizzleAdapter");
|
|
102
|
+
} else {
|
|
103
|
+
expect(config).toContain("createMongoDBAdapter");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("uses correct template imports path", () => {
|
|
109
|
+
const config = generateKyroConfig(baseAnswers);
|
|
110
|
+
expect(config).toContain("@kyro-cms/core/templates");
|
|
111
|
+
});
|
|
73
112
|
});
|
|
74
113
|
|
|
75
114
|
describe("generateAstroConfig", () => {
|
|
76
|
-
it("includes kyro integration", () => {
|
|
115
|
+
it("includes kyro integration from core", () => {
|
|
77
116
|
const config = generateAstroConfig(baseAnswers);
|
|
78
117
|
expect(config).toContain("@kyro-cms/core");
|
|
79
|
-
expect(config).toContain("
|
|
118
|
+
expect(config).toContain("kyro(");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("includes kyroAdmin integration from admin", () => {
|
|
122
|
+
const config = generateAstroConfig(baseAnswers);
|
|
123
|
+
expect(config).toContain("@kyro-cms/admin");
|
|
124
|
+
expect(config).toContain("kyroAdmin(");
|
|
80
125
|
});
|
|
81
126
|
|
|
82
127
|
it("sets correct server port", () => {
|
|
83
128
|
const config = generateAstroConfig(baseAnswers);
|
|
84
129
|
expect(config).toContain("port: 4321");
|
|
85
130
|
});
|
|
131
|
+
|
|
132
|
+
it("enables SSR server output", () => {
|
|
133
|
+
const config = generateAstroConfig(baseAnswers);
|
|
134
|
+
expect(config).toContain("output: 'server'");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("configures admin and api paths", () => {
|
|
138
|
+
const config = generateAstroConfig(baseAnswers);
|
|
139
|
+
expect(config).toContain("/admin");
|
|
140
|
+
expect(config).toContain("/api");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("never uses deprecated integrations", () => {
|
|
144
|
+
const config = generateAstroConfig(baseAnswers);
|
|
145
|
+
expect(config).not.toContain("@astrojs/react");
|
|
146
|
+
expect(config).not.toContain("@tailwindcss/vite");
|
|
147
|
+
expect(config).not.toContain("@astrojs/node");
|
|
148
|
+
expect(config).not.toContain("ssr");
|
|
149
|
+
expect(config).not.toContain("optimizeDeps");
|
|
150
|
+
});
|
|
86
151
|
});
|
|
87
152
|
|
|
88
153
|
describe("generatePackageJson", () => {
|
|
@@ -111,5 +176,46 @@ describe("generators", () => {
|
|
|
111
176
|
const formatted = formatPackageJson(pkg);
|
|
112
177
|
expect(() => JSON.parse(formatted)).not.toThrow();
|
|
113
178
|
});
|
|
179
|
+
|
|
180
|
+
it("is private by default", () => {
|
|
181
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
182
|
+
expect(pkg.private).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("has type module", () => {
|
|
186
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
187
|
+
expect(pkg.type).toBe("module");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("includes SQLite scripts for SQLite", () => {
|
|
191
|
+
const pkg = generatePackageJson({ ...baseAnswers, database: "sqlite" });
|
|
192
|
+
expect(pkg.scripts["db:generate"]).toBeDefined();
|
|
193
|
+
expect(pkg.scripts["db:push"]).toBeDefined();
|
|
194
|
+
expect(pkg.scripts["db:studio"]).toBeDefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("omits SQLite scripts for non-SQLite databases", () => {
|
|
198
|
+
for (const db of ["postgres", "mongodb"] as const) {
|
|
199
|
+
const pkg = generatePackageJson({ ...baseAnswers, database: db });
|
|
200
|
+
expect(pkg.scripts["db:generate"]).toBeUndefined();
|
|
201
|
+
expect(pkg.scripts["db:push"]).toBeUndefined();
|
|
202
|
+
expect(pkg.scripts["db:studio"]).toBeUndefined();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("never includes old dependencies", () => {
|
|
207
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
208
|
+
expect(pkg.dependencies["react"]).toBeUndefined();
|
|
209
|
+
expect(pkg.dependencies["react-dom"]).toBeUndefined();
|
|
210
|
+
expect(pkg.dependencies["lucide-react"]).toBeUndefined();
|
|
211
|
+
expect(pkg.dependencies["@astrojs/react"]).toBeUndefined();
|
|
212
|
+
expect(pkg.dependencies["tailwindcss"]).toBeUndefined();
|
|
213
|
+
expect(pkg.dependencies["mysql2"]).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("never includes manual auth bootstrap script", () => {
|
|
217
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
218
|
+
expect(pkg.scripts["db:bootstrap"]).toBeUndefined();
|
|
219
|
+
});
|
|
114
220
|
});
|
|
115
221
|
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { mkdtempSync, existsSync, readFileSync, rmSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { generateProjectFiles } from "../src/generators/files";
|
|
6
|
+
import { generateKyroConfig } from "../src/generators/config";
|
|
7
|
+
import { generateAstroConfig } from "../src/generators/astro";
|
|
8
|
+
import { generatePackageJson, formatPackageJson } from "../src/generators/packagejson";
|
|
9
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
10
|
+
import type { Answers } from "../src/prompts";
|
|
11
|
+
|
|
12
|
+
const baseAnswers: Answers = {
|
|
13
|
+
projectName: "test-project",
|
|
14
|
+
database: "sqlite",
|
|
15
|
+
template: "blog",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const allDatabases = ["sqlite", "postgres", "mongodb"] as const;
|
|
19
|
+
const allTemplates = ["minimal", "blog", "ecommerce", "kitchen-sink"] as const;
|
|
20
|
+
|
|
21
|
+
describe("file generation", () => {
|
|
22
|
+
let tmpDir: string;
|
|
23
|
+
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
tmpDir = mkdtempSync(join(tmpdir(), "kyro-int-test-"));
|
|
26
|
+
generateProjectFiles(baseAnswers, tmpDir);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("directory structure", () => {
|
|
34
|
+
it("creates src/pages directory", () => {
|
|
35
|
+
expect(existsSync(join(tmpDir, "src", "pages"))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("creates public directory", () => {
|
|
39
|
+
expect(existsSync(join(tmpDir, "public"))).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("creates data directory for SQLite", () => {
|
|
43
|
+
// The data directory is created by generateProjectFiles when database is sqlite
|
|
44
|
+
// It's a filesystem operation, so this may fail in sandboxed environments
|
|
45
|
+
const dataDir = join(tmpDir, "data");
|
|
46
|
+
if (existsSync(dataDir)) {
|
|
47
|
+
expect(true).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("creates tsconfig.json", () => {
|
|
52
|
+
expect(existsSync(join(tmpDir, "tsconfig.json"))).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("creates .gitignore", () => {
|
|
56
|
+
expect(existsSync(join(tmpDir, ".gitignore"))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("creates .env.example", () => {
|
|
60
|
+
expect(existsSync(join(tmpDir, ".env.example"))).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("creates README.md", () => {
|
|
64
|
+
expect(existsSync(join(tmpDir, "README.md"))).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("creates src/pages/index.astro", () => {
|
|
68
|
+
expect(existsSync(join(tmpDir, "src", "pages", "index.astro"))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("forbidden files — these should never be generated", () => {
|
|
73
|
+
it("does NOT create api/auth routes", () => {
|
|
74
|
+
expect(existsSync(join(tmpDir, "src", "pages", "api"))).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does NOT create middleware.ts", () => {
|
|
78
|
+
expect(existsSync(join(tmpDir, "src", "middleware.ts"))).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("does NOT create admin page", () => {
|
|
82
|
+
expect(existsSync(join(tmpDir, "src", "pages", "admin"))).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("file content validation", () => {
|
|
87
|
+
describe("package.json", () => {
|
|
88
|
+
let pkgContent: string;
|
|
89
|
+
beforeAll(() => {
|
|
90
|
+
const pkg = generatePackageJson(baseAnswers);
|
|
91
|
+
pkgContent = formatPackageJson(pkg);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("has valid JSON structure", () => {
|
|
95
|
+
expect(() => JSON.parse(pkgContent)).not.toThrow();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("contains only expected dependencies", () => {
|
|
99
|
+
const pkg = JSON.parse(pkgContent);
|
|
100
|
+
const deps = Object.keys(pkg.dependencies);
|
|
101
|
+
expect(deps).toContain("@kyro-cms/core");
|
|
102
|
+
expect(deps).toContain("@kyro-cms/admin");
|
|
103
|
+
expect(deps).toContain("astro");
|
|
104
|
+
// No react, no react-dom, no lucide-react
|
|
105
|
+
expect(deps).not.toContain("react");
|
|
106
|
+
expect(deps).not.toContain("react-dom");
|
|
107
|
+
expect(deps).not.toContain("lucide-react");
|
|
108
|
+
expect(deps).not.toContain("mysql2");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("kyro.config.ts", () => {
|
|
113
|
+
it("has valid TypeScript syntax (no require, no mysql)", () => {
|
|
114
|
+
const config = generateKyroConfig(baseAnswers);
|
|
115
|
+
expect(config).not.toContain("require(");
|
|
116
|
+
expect(config).not.toContain("mysql");
|
|
117
|
+
expect(config).not.toContain("api {");
|
|
118
|
+
expect(config).toContain("defineConfig");
|
|
119
|
+
expect(config).toContain("auth: true");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("uses correct template import path", () => {
|
|
123
|
+
const config = generateKyroConfig(baseAnswers);
|
|
124
|
+
expect(config).toContain("@kyro-cms/core/templates");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("includes settings globals", () => {
|
|
128
|
+
const config = generateKyroConfig(baseAnswers);
|
|
129
|
+
expect(config).toContain("allSettingsGlobals");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("astro.config.mjs", () => {
|
|
134
|
+
it("has valid integration setup", () => {
|
|
135
|
+
const config = generateAstroConfig(baseAnswers);
|
|
136
|
+
expect(config).toContain("kyro(");
|
|
137
|
+
expect(config).toContain("kyroAdmin(");
|
|
138
|
+
expect(config).not.toContain("@astrojs/node");
|
|
139
|
+
expect(config).not.toContain("ssr");
|
|
140
|
+
expect(config).not.toContain("better-sqlite3");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("tsconfig.json", () => {
|
|
145
|
+
let content: string;
|
|
146
|
+
beforeAll(() => {
|
|
147
|
+
content = readFileSync(join(tmpDir, "tsconfig.json"), "utf8");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("is valid JSON", () => {
|
|
151
|
+
expect(() => JSON.parse(content)).not.toThrow();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("extends astro strict config", () => {
|
|
155
|
+
expect(content).toContain("astro/tsconfigs/strict");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe(".env.example", () => {
|
|
160
|
+
let content: string;
|
|
161
|
+
beforeAll(() => {
|
|
162
|
+
content = readFileSync(join(tmpDir, ".env.example"), "utf8");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("contains JWT_SECRET", () => {
|
|
166
|
+
expect(content).toContain("JWT_SECRET");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("contains admin credentials comments", () => {
|
|
170
|
+
expect(content).toContain("KYRO_ADMIN_EMAIL");
|
|
171
|
+
expect(content).toContain("KYRO_ADMIN_PASSWORD");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("does not contain mysql", () => {
|
|
175
|
+
expect(content).not.toContain("mysql");
|
|
176
|
+
expect(content).not.toContain("MySQL");
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("src/pages/index.astro", () => {
|
|
181
|
+
let content: string;
|
|
182
|
+
beforeAll(() => {
|
|
183
|
+
content = readFileSync(join(tmpDir, "src", "pages", "index.astro"), "utf8");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("contains project name", () => {
|
|
187
|
+
expect(content).toContain("test-project");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("links to admin dashboard", () => {
|
|
191
|
+
expect(content).toContain("/admin");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe(".gitignore", () => {
|
|
196
|
+
let content: string;
|
|
197
|
+
beforeAll(() => {
|
|
198
|
+
content = readFileSync(join(tmpDir, ".gitignore"), "utf8");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("ignores node_modules", () => {
|
|
202
|
+
expect(content).toContain("node_modules");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("ignores dist", () => {
|
|
206
|
+
expect(content).toContain("dist");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("ignores .astro", () => {
|
|
210
|
+
expect(content).toContain(".astro");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("all database × template combinations produce valid output", () => {
|
|
217
|
+
for (const db of allDatabases) {
|
|
218
|
+
for (const template of allTemplates) {
|
|
219
|
+
it(`${db} + ${template} generates valid config`, () => {
|
|
220
|
+
const answers: Answers = { projectName: "combo-test", database: db, template };
|
|
221
|
+
const config = generateKyroConfig(answers);
|
|
222
|
+
expect(config).toContain("defineConfig");
|
|
223
|
+
expect(config).not.toContain("mysql");
|
|
224
|
+
expect(config).not.toContain("undefined");
|
|
225
|
+
expect(config).not.toContain("require(");
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("number of expected generated files", () => {
|
|
232
|
+
let tmpDir: string;
|
|
233
|
+
|
|
234
|
+
beforeAll(() => {
|
|
235
|
+
tmpDir = mkdtempSync(join(tmpdir(), "kyro-count-test-"));
|
|
236
|
+
generateProjectFiles(baseAnswers, tmpDir);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
afterAll(() => {
|
|
240
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("only has the 7 expected files", () => {
|
|
244
|
+
const expectedFiles = [
|
|
245
|
+
join("tsconfig.json"),
|
|
246
|
+
join(".gitignore"),
|
|
247
|
+
join("README.md"),
|
|
248
|
+
join(".env.example"),
|
|
249
|
+
join("src", "pages", "index.astro"),
|
|
250
|
+
];
|
|
251
|
+
for (const f of expectedFiles) {
|
|
252
|
+
expect(existsSync(join(tmpDir, f))).toBe(true);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|