create-kyro 0.2.4 → 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 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
- ],${vitePlugins.length > 0 ? `
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.4",
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
  }
@@ -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
+ });
@@ -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
+ });