create-kyro 0.2.2 → 0.2.10
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 +31 -3
- package/package.json +10 -3
- package/src/generators/astro.ts +30 -2
- package/test/generators.test.ts +202 -0
- package/test/validators.test.ts +86 -0
- package/vitest.config.ts +14 -0
package/dist/index.js
CHANGED
|
@@ -350,6 +350,7 @@ ${apiConfig.join("\n")}
|
|
|
350
350
|
function generateAstroConfig(answers) {
|
|
351
351
|
const integrations = [];
|
|
352
352
|
const vitePlugins = [];
|
|
353
|
+
const vitePluginsLength = vitePlugins.length;
|
|
353
354
|
if (answers.styling === "tailwind") {
|
|
354
355
|
integrations.push(" react(),");
|
|
355
356
|
vitePlugins.push(" tailwindcss(),");
|
|
@@ -358,6 +359,31 @@ function generateAstroConfig(answers) {
|
|
|
358
359
|
adapter: node({
|
|
359
360
|
mode: 'standalone'
|
|
360
361
|
}),` : "";
|
|
362
|
+
const nativeExternals = `
|
|
363
|
+
ssr: {
|
|
364
|
+
external: [
|
|
365
|
+
'better-sqlite3',
|
|
366
|
+
'sharp',
|
|
367
|
+
'ssh2',
|
|
368
|
+
'cpu-features',
|
|
369
|
+
'ssh2-sftp-client',
|
|
370
|
+
'ioredis',
|
|
371
|
+
'nodemailer',
|
|
372
|
+
'jsonwebtoken',
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
optimizeDeps: {
|
|
376
|
+
exclude: [
|
|
377
|
+
'better-sqlite3',
|
|
378
|
+
'sharp',
|
|
379
|
+
'ssh2',
|
|
380
|
+
'cpu-features',
|
|
381
|
+
'ssh2-sftp-client',
|
|
382
|
+
'ioredis',
|
|
383
|
+
'nodemailer',
|
|
384
|
+
'jsonwebtoken',
|
|
385
|
+
],
|
|
386
|
+
},`;
|
|
361
387
|
const config = `import { defineConfig } from 'astro/config';
|
|
362
388
|
import node from '@astrojs/node';
|
|
363
389
|
${answers.styling === "tailwind" ? "import react from '@astrojs/react';\nimport tailwindcss from '@tailwindcss/vite';" : ""}
|
|
@@ -367,12 +393,14 @@ export default defineConfig({
|
|
|
367
393
|
|
|
368
394
|
integrations: [
|
|
369
395
|
${integrations.join("\n")}
|
|
370
|
-
],${
|
|
396
|
+
],${vitePluginsLength > 0 ? `
|
|
371
397
|
vite: {
|
|
372
398
|
plugins: [
|
|
373
399
|
${vitePlugins.join("\n")}
|
|
374
|
-
]
|
|
375
|
-
},` :
|
|
400
|
+
],${nativeExternals}
|
|
401
|
+
},` : `
|
|
402
|
+
vite: {${nativeExternals}
|
|
403
|
+
},`}
|
|
376
404
|
server: {
|
|
377
405
|
port: 4321,
|
|
378
406
|
host: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kyro",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Interactive scaffolding for Kyro CMS projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsup",
|
|
13
13
|
"dev": "tsup --watch",
|
|
14
|
-
"typecheck": "tsc --noEmit"
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"test:run": "vitest run"
|
|
15
17
|
},
|
|
16
18
|
"dependencies": {
|
|
17
19
|
"prompts": "^2.4.2",
|
|
@@ -20,7 +22,8 @@
|
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@types/node": "^22.13.0",
|
|
22
24
|
"tsup": "^8.4.0",
|
|
23
|
-
"typescript": "^5.7.3"
|
|
25
|
+
"typescript": "^5.7.3",
|
|
26
|
+
"vitest": "^3.0.0"
|
|
24
27
|
},
|
|
25
28
|
"engines": {
|
|
26
29
|
"node": ">=18.0.0"
|
|
@@ -34,6 +37,10 @@
|
|
|
34
37
|
"astro"
|
|
35
38
|
],
|
|
36
39
|
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/danielDozie/kyro-cms"
|
|
43
|
+
},
|
|
37
44
|
"publishConfig": {
|
|
38
45
|
"access": "public"
|
|
39
46
|
}
|
package/src/generators/astro.ts
CHANGED
|
@@ -17,6 +17,32 @@ export function generateAstroConfig(answers: Answers): string {
|
|
|
17
17
|
}),`
|
|
18
18
|
: "";
|
|
19
19
|
|
|
20
|
+
const nativeExternals = `
|
|
21
|
+
ssr: {
|
|
22
|
+
external: [
|
|
23
|
+
'better-sqlite3',
|
|
24
|
+
'sharp',
|
|
25
|
+
'ssh2',
|
|
26
|
+
'cpu-features',
|
|
27
|
+
'ssh2-sftp-client',
|
|
28
|
+
'ioredis',
|
|
29
|
+
'nodemailer',
|
|
30
|
+
'jsonwebtoken',
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
optimizeDeps: {
|
|
34
|
+
exclude: [
|
|
35
|
+
'better-sqlite3',
|
|
36
|
+
'sharp',
|
|
37
|
+
'ssh2',
|
|
38
|
+
'cpu-features',
|
|
39
|
+
'ssh2-sftp-client',
|
|
40
|
+
'ioredis',
|
|
41
|
+
'nodemailer',
|
|
42
|
+
'jsonwebtoken',
|
|
43
|
+
],
|
|
44
|
+
},`;
|
|
45
|
+
|
|
20
46
|
const config = `import { defineConfig } from 'astro/config';
|
|
21
47
|
import node from '@astrojs/node';
|
|
22
48
|
${answers.styling === "tailwind" ? "import react from '@astrojs/react';\nimport tailwindcss from '@tailwindcss/vite';" : ""}
|
|
@@ -32,9 +58,11 @@ ${integrations.join("\n")}
|
|
|
32
58
|
vite: {
|
|
33
59
|
plugins: [
|
|
34
60
|
${vitePlugins.join("\n")}
|
|
35
|
-
]
|
|
61
|
+
],${nativeExternals}
|
|
62
|
+
},`
|
|
63
|
+
: `
|
|
64
|
+
vite: {${nativeExternals}
|
|
36
65
|
},`
|
|
37
|
-
: ""
|
|
38
66
|
}
|
|
39
67
|
server: {
|
|
40
68
|
port: 4321,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { Answers } from "../src/prompts";
|
|
3
|
+
import { generateKyroConfig } from "../src/generators/config";
|
|
4
|
+
import { generateAstroConfig } from "../src/generators/astro";
|
|
5
|
+
import {
|
|
6
|
+
generatePackageJson,
|
|
7
|
+
formatPackageJson,
|
|
8
|
+
} from "../src/generators/packagejson";
|
|
9
|
+
|
|
10
|
+
const baseAnswers: Answers = {
|
|
11
|
+
projectName: "test-project",
|
|
12
|
+
database: "sqlite",
|
|
13
|
+
styling: "tailwind",
|
|
14
|
+
auth: true,
|
|
15
|
+
versioning: true,
|
|
16
|
+
admin: true,
|
|
17
|
+
template: "blog",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("generators", () => {
|
|
21
|
+
describe("generateKyroConfig", () => {
|
|
22
|
+
it("generates config with SQLite adapter", () => {
|
|
23
|
+
const config = generateKyroConfig(baseAnswers);
|
|
24
|
+
expect(config).toContain("createLocalAdapter");
|
|
25
|
+
expect(config).toContain("./data.db");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("generates config with PostgreSQL adapter", () => {
|
|
29
|
+
const answers = { ...baseAnswers, database: "postgres" as const };
|
|
30
|
+
const config = generateKyroConfig(answers);
|
|
31
|
+
expect(config).toContain("createDrizzleAdapter");
|
|
32
|
+
expect(config).toContain("DATABASE_URL");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("generates config with MySQL adapter", () => {
|
|
36
|
+
const answers = { ...baseAnswers, database: "mysql" as const };
|
|
37
|
+
const config = generateKyroConfig(answers);
|
|
38
|
+
expect(config).toContain("createDrizzleAdapter");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("generates config with MongoDB adapter", () => {
|
|
42
|
+
const answers = { ...baseAnswers, database: "mongodb" as const };
|
|
43
|
+
const config = generateKyroConfig(answers);
|
|
44
|
+
expect(config).toContain("createMongoDBAdapter");
|
|
45
|
+
expect(config).toContain("MONGODB_URI");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("includes auth when enabled", () => {
|
|
49
|
+
const config = generateKyroConfig({ ...baseAnswers, auth: true });
|
|
50
|
+
expect(config).toContain("auth: true");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("omits auth when disabled", () => {
|
|
54
|
+
const config = generateKyroConfig({ ...baseAnswers, auth: false });
|
|
55
|
+
expect(config).not.toContain("auth: true");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("includes versioning when enabled", () => {
|
|
59
|
+
const config = generateKyroConfig({ ...baseAnswers, versioning: true });
|
|
60
|
+
expect(config).toContain("versioning: true");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("imports correct template collections", () => {
|
|
64
|
+
const minimal = generateKyroConfig({
|
|
65
|
+
...baseAnswers,
|
|
66
|
+
template: "minimal",
|
|
67
|
+
});
|
|
68
|
+
expect(minimal).toContain("minimalCollections");
|
|
69
|
+
|
|
70
|
+
const blog = generateKyroConfig({ ...baseAnswers, template: "blog" });
|
|
71
|
+
expect(blog).toContain("blogCollections");
|
|
72
|
+
|
|
73
|
+
const ecommerce = generateKyroConfig({
|
|
74
|
+
...baseAnswers,
|
|
75
|
+
template: "ecommerce",
|
|
76
|
+
});
|
|
77
|
+
expect(ecommerce).toContain("ecommerceCollections");
|
|
78
|
+
|
|
79
|
+
const kitchen = generateKyroConfig({
|
|
80
|
+
...baseAnswers,
|
|
81
|
+
template: "kitchen-sink",
|
|
82
|
+
});
|
|
83
|
+
expect(kitchen).toContain("minimalCollections");
|
|
84
|
+
expect(kitchen).toContain("blogCollections");
|
|
85
|
+
expect(kitchen).toContain("ecommerceCollections");
|
|
86
|
+
expect(kitchen).toContain("kitchenSinkCollections");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("includes all API protocols", () => {
|
|
90
|
+
const config = generateKyroConfig(baseAnswers);
|
|
91
|
+
expect(config).toContain("rest: true");
|
|
92
|
+
expect(config).toContain("graphql: true");
|
|
93
|
+
expect(config).toContain("trpc: true");
|
|
94
|
+
expect(config).toContain("websocket: true");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("uses project name in config", () => {
|
|
98
|
+
const config = generateKyroConfig(baseAnswers);
|
|
99
|
+
expect(config).toContain("name: 'test-project'");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("generateAstroConfig", () => {
|
|
104
|
+
it("includes react integration for tailwind", () => {
|
|
105
|
+
const config = generateAstroConfig(baseAnswers);
|
|
106
|
+
expect(config).toContain("@astrojs/react");
|
|
107
|
+
expect(config).toContain("@tailwindcss/vite");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("includes node adapter when admin enabled", () => {
|
|
111
|
+
const config = generateAstroConfig(baseAnswers);
|
|
112
|
+
expect(config).toContain("@astrojs/node");
|
|
113
|
+
expect(config).toContain("standalone");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("includes SSR externals for native modules", () => {
|
|
117
|
+
const config = generateAstroConfig(baseAnswers);
|
|
118
|
+
expect(config).toContain("better-sqlite3");
|
|
119
|
+
expect(config).toContain("sharp");
|
|
120
|
+
expect(config).toContain("ssr");
|
|
121
|
+
expect(config).toContain("optimizeDeps");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("sets correct server port", () => {
|
|
125
|
+
const config = generateAstroConfig(baseAnswers);
|
|
126
|
+
expect(config).toContain("port: 4321");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("excludes react when not using tailwind", () => {
|
|
130
|
+
const answers = { ...baseAnswers, styling: "none" as const };
|
|
131
|
+
const config = generateAstroConfig(answers);
|
|
132
|
+
expect(config).not.toContain("@astrojs/react");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("generatePackageJson", () => {
|
|
137
|
+
it("includes core dependency", () => {
|
|
138
|
+
const pkg = generatePackageJson(baseAnswers, "/test");
|
|
139
|
+
expect(pkg.dependencies["@kyro-cms/core"]).toBeDefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("includes admin dependencies when admin enabled", () => {
|
|
143
|
+
const pkg = generatePackageJson(baseAnswers, "/test");
|
|
144
|
+
expect(pkg.dependencies["@kyro-cms/admin"]).toBeDefined();
|
|
145
|
+
expect(pkg.dependencies["@astrojs/node"]).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("omits admin dependencies when admin disabled", () => {
|
|
149
|
+
const pkg = generatePackageJson(
|
|
150
|
+
{ ...baseAnswers, admin: false },
|
|
151
|
+
"/test",
|
|
152
|
+
);
|
|
153
|
+
expect(pkg.dependencies["@kyro-cms/admin"]).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("includes database-specific dependencies", () => {
|
|
157
|
+
const pg = generatePackageJson(
|
|
158
|
+
{ ...baseAnswers, database: "postgres" },
|
|
159
|
+
"/test",
|
|
160
|
+
);
|
|
161
|
+
expect(pg.dependencies["pg"]).toBeDefined();
|
|
162
|
+
|
|
163
|
+
const mysql = generatePackageJson(
|
|
164
|
+
{ ...baseAnswers, database: "mysql" },
|
|
165
|
+
"/test",
|
|
166
|
+
);
|
|
167
|
+
expect(mysql.dependencies["mysql2"]).toBeDefined();
|
|
168
|
+
|
|
169
|
+
const mongo = generatePackageJson(
|
|
170
|
+
{ ...baseAnswers, database: "mongodb" },
|
|
171
|
+
"/test",
|
|
172
|
+
);
|
|
173
|
+
expect(mongo.dependencies["mongodb"]).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("includes auth bootstrap script when auth enabled", () => {
|
|
177
|
+
const pkg = generatePackageJson({ ...baseAnswers, auth: true }, "/test");
|
|
178
|
+
expect(pkg.scripts["db:bootstrap"]).toBeDefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("includes SQLite scripts for SQLite database", () => {
|
|
182
|
+
const pkg = generatePackageJson(
|
|
183
|
+
{ ...baseAnswers, database: "sqlite" },
|
|
184
|
+
"/test",
|
|
185
|
+
);
|
|
186
|
+
expect(pkg.scripts["db:generate"]).toBeDefined();
|
|
187
|
+
expect(pkg.scripts["db:push"]).toBeDefined();
|
|
188
|
+
expect(pkg.scripts["db:studio"]).toBeDefined();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("has correct project name", () => {
|
|
192
|
+
const pkg = generatePackageJson(baseAnswers, "/test");
|
|
193
|
+
expect(pkg.name).toBe("test-project");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("formats as valid JSON string", () => {
|
|
197
|
+
const pkg = generatePackageJson(baseAnswers, "/test");
|
|
198
|
+
const formatted = formatPackageJson(pkg);
|
|
199
|
+
expect(() => JSON.parse(formatted)).not.toThrow();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateProjectName, validateEmail } from "../src/validators";
|
|
3
|
+
|
|
4
|
+
describe("validators", () => {
|
|
5
|
+
describe("validateProjectName", () => {
|
|
6
|
+
it("accepts valid project names", () => {
|
|
7
|
+
expect(validateProjectName("my-app")).toBe(true);
|
|
8
|
+
expect(validateProjectName("kyro-cms-project")).toBe(true);
|
|
9
|
+
expect(validateProjectName("a1b2c3")).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("rejects empty names", () => {
|
|
13
|
+
expect(validateProjectName("")).toBe("Project name is required");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("rejects uppercase letters", () => {
|
|
17
|
+
expect(validateProjectName("My-App")).toBe(
|
|
18
|
+
"Use lowercase letters, numbers, and hyphens only",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("rejects special characters", () => {
|
|
23
|
+
expect(validateProjectName("my_app")).toBe(
|
|
24
|
+
"Use lowercase letters, numbers, and hyphens only",
|
|
25
|
+
);
|
|
26
|
+
expect(validateProjectName("my.app")).toBe(
|
|
27
|
+
"Use lowercase letters, numbers, and hyphens only",
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("rejects names that are too short", () => {
|
|
32
|
+
expect(validateProjectName("a")).toBe(
|
|
33
|
+
"Project name must be at least 2 characters",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("rejects names that are too long", () => {
|
|
38
|
+
expect(validateProjectName("a".repeat(51))).toBe(
|
|
39
|
+
"Project name must be less than 50 characters",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("rejects names starting with a number", () => {
|
|
44
|
+
expect(validateProjectName("1-app")).toBe(
|
|
45
|
+
"Project name cannot start with a number or hyphen",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects names starting with a hyphen", () => {
|
|
50
|
+
expect(validateProjectName("-app")).toBe(
|
|
51
|
+
"Project name cannot start with a number or hyphen",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("rejects reserved names", () => {
|
|
56
|
+
const reserved = [
|
|
57
|
+
"node_modules",
|
|
58
|
+
"dist",
|
|
59
|
+
"build",
|
|
60
|
+
"public",
|
|
61
|
+
"src",
|
|
62
|
+
"test",
|
|
63
|
+
"tests",
|
|
64
|
+
];
|
|
65
|
+
for (const name of reserved) {
|
|
66
|
+
expect(validateProjectName(name)).toBe(`"${name}" is a reserved name`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("validateEmail", () => {
|
|
72
|
+
it("accepts valid emails", () => {
|
|
73
|
+
expect(validateEmail("user@example.com")).toBe(true);
|
|
74
|
+
expect(validateEmail("test.user@domain.co.uk")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("rejects invalid emails", () => {
|
|
78
|
+
expect(validateEmail("not-an-email")).toBe("Invalid email address");
|
|
79
|
+
expect(validateEmail("@domain.com")).toBe("Invalid email address");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("allows empty string (optional field)", () => {
|
|
83
|
+
expect(validateEmail("")).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: "node",
|
|
7
|
+
include: ["test/**/*.test.ts"],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: "v8",
|
|
10
|
+
reporter: ["text", "json", "html"],
|
|
11
|
+
include: ["src/**/*.ts"],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|