create-kyro 0.1.1 → 0.1.3
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 +701 -183
- package/my-kyro-app/README.md +14 -0
- package/my-kyro-app/SPEC.md +21 -0
- package/my-kyro-app/astro.config.mjs +20 -0
- package/my-kyro-app/kyro.config.ts +15 -0
- package/my-kyro-app/package-lock.json +6919 -0
- package/my-kyro-app/package.json +31 -0
- package/package.json +6 -12
- package/templates/blog/collections.ts +7 -6
- package/src/generators/astro.ts +0 -38
- package/src/generators/config.ts +0 -80
- package/src/generators/files.ts +0 -159
- package/src/generators/packagejson.ts +0 -72
- package/src/index.ts +0 -97
- package/src/prompts.ts +0 -164
- package/src/utils/logger.ts +0 -60
- package/src/validators.ts +0 -36
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -27,168 +27,172 @@ function validateProjectName(name) {
|
|
|
27
27
|
|
|
28
28
|
// src/prompts.ts
|
|
29
29
|
async function promptUser() {
|
|
30
|
-
const response = await prompts(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
30
|
+
const response = await prompts(
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
type: "text",
|
|
34
|
+
name: "projectName",
|
|
35
|
+
message: "Project name:",
|
|
36
|
+
initial: "my-kyro-app",
|
|
37
|
+
validate: validateProjectName
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "select",
|
|
41
|
+
name: "database",
|
|
42
|
+
message: "Database:",
|
|
43
|
+
hint: " ",
|
|
44
|
+
choices: [
|
|
45
|
+
{
|
|
46
|
+
title: "SQLite (local-first, zero config)",
|
|
47
|
+
description: "Best for development and small projects. No setup required.",
|
|
48
|
+
value: "sqlite"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: "PostgreSQL",
|
|
52
|
+
description: "Recommended for production. Robust and scalable.",
|
|
53
|
+
value: "postgres"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: "MySQL",
|
|
57
|
+
description: "Popular choice for web applications.",
|
|
58
|
+
value: "mysql"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: "MongoDB",
|
|
62
|
+
description: "Best for flexible, document-based schemas.",
|
|
63
|
+
value: "mongodb"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "multiselect",
|
|
69
|
+
name: "apis",
|
|
70
|
+
message: "API protocols (select multiple):",
|
|
71
|
+
hint: "Space to select, Enter to confirm",
|
|
72
|
+
instructions: false,
|
|
73
|
+
choices: [
|
|
74
|
+
{
|
|
75
|
+
title: "REST",
|
|
76
|
+
description: "Simple HTTP API, great for any client",
|
|
77
|
+
value: "rest",
|
|
78
|
+
selected: true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
title: "GraphQL",
|
|
82
|
+
description: "Flexible query language, great for complex data",
|
|
83
|
+
value: "graphql",
|
|
84
|
+
selected: true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
title: "tRPC",
|
|
88
|
+
description: "End-to-end typesafe APIs, great for TypeScript",
|
|
89
|
+
value: "trpc"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "WebSocket",
|
|
93
|
+
description: "Real-time bidirectional communication",
|
|
94
|
+
value: "websocket"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: "select",
|
|
100
|
+
name: "styling",
|
|
101
|
+
message: "Styling:",
|
|
102
|
+
hint: " ",
|
|
103
|
+
choices: [
|
|
104
|
+
{
|
|
105
|
+
title: "Tailwind CSS",
|
|
106
|
+
description: "Utility-first CSS framework, excellent DX",
|
|
107
|
+
value: "tailwind"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
title: "CSS Modules",
|
|
111
|
+
description: "Scoped CSS, no extra dependencies",
|
|
112
|
+
value: "cssmodules"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: "Styled Components",
|
|
116
|
+
description: "CSS-in-JS with tagged template literals",
|
|
117
|
+
value: "styled"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
title: "None",
|
|
121
|
+
description: "Bring your own styling solution",
|
|
122
|
+
value: "none"
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: "toggle",
|
|
128
|
+
name: "auth",
|
|
129
|
+
message: "Add authentication (JWT)?",
|
|
130
|
+
initial: true,
|
|
131
|
+
active: "Yes",
|
|
132
|
+
inactive: "No"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: "toggle",
|
|
136
|
+
name: "versioning",
|
|
137
|
+
message: "Add versioning/drafts?",
|
|
138
|
+
initial: true,
|
|
139
|
+
active: "Yes",
|
|
140
|
+
inactive: "No"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: "toggle",
|
|
144
|
+
name: "admin",
|
|
145
|
+
message: "Include admin dashboard?",
|
|
146
|
+
initial: true,
|
|
147
|
+
active: "Yes",
|
|
148
|
+
inactive: "No"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: "select",
|
|
152
|
+
name: "template",
|
|
153
|
+
message: "Starting template:",
|
|
154
|
+
hint: " ",
|
|
155
|
+
initial: 1,
|
|
156
|
+
choices: [
|
|
157
|
+
{
|
|
158
|
+
title: "Minimal",
|
|
159
|
+
description: "Basic configuration with one example collection + core settings",
|
|
160
|
+
value: "minimal"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: "Blog",
|
|
164
|
+
description: "Posts, categories, media library + core settings",
|
|
165
|
+
value: "blog"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
title: "E-commerce",
|
|
169
|
+
description: "Products, orders, customers, coupons + core + store/payment settings",
|
|
170
|
+
value: "ecommerce"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
title: "Kitchen Sink",
|
|
174
|
+
description: "Everything: pages, navigation, blog, e-commerce + all settings",
|
|
175
|
+
value: "kitchen-sink"
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
],
|
|
147
180
|
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
choices: [
|
|
153
|
-
{
|
|
154
|
-
title: "Minimal",
|
|
155
|
-
description: "Basic configuration with one example collection",
|
|
156
|
-
value: "minimal"
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
title: "Blog",
|
|
160
|
-
description: "Posts, categories, tags, and media library",
|
|
161
|
-
value: "blog"
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
title: "E-commerce",
|
|
165
|
-
description: "Products, orders, customers, inventory, and coupons",
|
|
166
|
-
value: "ecommerce"
|
|
167
|
-
}
|
|
168
|
-
]
|
|
181
|
+
onCancel: () => {
|
|
182
|
+
console.log("\nCancelled.");
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
|
-
|
|
171
|
-
onCancel: () => {
|
|
172
|
-
console.log("\nCancelled.");
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
186
|
+
);
|
|
176
187
|
return response;
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
// src/utils/logger.ts
|
|
180
|
-
import
|
|
181
|
-
var { cyan, green, yellow, red, bold, dim } = kolorist;
|
|
191
|
+
import { cyan, green, yellow, red, bold, dim } from "kolorist";
|
|
182
192
|
var logger = {
|
|
183
193
|
intro: (name, version) => {
|
|
184
194
|
console.log(`
|
|
185
|
-
${bold(cyan(
|
|
186
|
-
${bold(cyan(`\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D`))}
|
|
187
|
-
${bold(cyan(`\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557`))}
|
|
188
|
-
${bold(cyan(`\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551`))}
|
|
189
|
-
${bold(cyan(`\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551`))}
|
|
190
|
-
${bold(cyan(`\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`))}
|
|
191
|
-
${dim(`v${version}`)}
|
|
195
|
+
${bold(cyan(`Kyro CMS`))} ${dim(`v${version}`)}
|
|
192
196
|
${dim("Astro-native headless CMS")}
|
|
193
197
|
`);
|
|
194
198
|
},
|
|
@@ -235,10 +239,10 @@ ${cyan("?")} ${bold(msg)}`);
|
|
|
235
239
|
function generatePackageJson(answers, projectDir) {
|
|
236
240
|
const deps = {
|
|
237
241
|
"@kyro-cms/core": "latest",
|
|
238
|
-
|
|
242
|
+
astro: "^5.4.0"
|
|
239
243
|
};
|
|
240
244
|
const devDeps = {
|
|
241
|
-
|
|
245
|
+
typescript: "^5.7.3"
|
|
242
246
|
};
|
|
243
247
|
if (answers.styling === "tailwind") {
|
|
244
248
|
deps["@astrojs/react"] = "^4.2.0";
|
|
@@ -259,13 +263,17 @@ function generatePackageJson(answers, projectDir) {
|
|
|
259
263
|
}
|
|
260
264
|
if (answers.admin) {
|
|
261
265
|
deps["@kyro-cms/admin"] = "latest";
|
|
266
|
+
deps["@astrojs/node"] = "^9.5.5";
|
|
262
267
|
deps["lucide-react"] = "^0.475.0";
|
|
263
268
|
}
|
|
264
269
|
const scripts = {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
dev: "astro dev",
|
|
271
|
+
build: "astro build",
|
|
272
|
+
preview: "astro preview"
|
|
268
273
|
};
|
|
274
|
+
if (answers.auth) {
|
|
275
|
+
scripts["db:bootstrap"] = "kyro auth bootstrap";
|
|
276
|
+
}
|
|
269
277
|
if (answers.database === "sqlite") {
|
|
270
278
|
scripts["db:generate"] = "kyro generate";
|
|
271
279
|
scripts["db:push"] = "kyro push";
|
|
@@ -290,21 +298,21 @@ function generateKyroConfig(answers) {
|
|
|
290
298
|
const imports = ["import { defineConfig } from '@kyro-cms/core';"];
|
|
291
299
|
const adapterLines = [];
|
|
292
300
|
if (answers.database === "sqlite") {
|
|
293
|
-
imports.push("import {
|
|
294
|
-
adapterLines.push(` adapter:
|
|
301
|
+
imports.push("import { createLocalAdapter } from '@kyro-cms/core';");
|
|
302
|
+
adapterLines.push(` adapter: createLocalAdapter({ path: './data.db' }),`);
|
|
295
303
|
} else if (answers.database === "postgres") {
|
|
296
|
-
imports.push("import {
|
|
297
|
-
adapterLines.push(` adapter:
|
|
304
|
+
imports.push("import { createDrizzleAdapter } from '@kyro-cms/core';");
|
|
305
|
+
adapterLines.push(` adapter: createDrizzleAdapter({`);
|
|
298
306
|
adapterLines.push(` connectionString: process.env.DATABASE_URL,`);
|
|
299
307
|
adapterLines.push(` }),`);
|
|
300
308
|
} else if (answers.database === "mysql") {
|
|
301
|
-
imports.push("import {
|
|
302
|
-
adapterLines.push(` adapter:
|
|
309
|
+
imports.push("import { createDrizzleAdapter } from '@kyro-cms/core';");
|
|
310
|
+
adapterLines.push(` adapter: createDrizzleAdapter({`);
|
|
303
311
|
adapterLines.push(` connectionString: process.env.DATABASE_URL,`);
|
|
304
312
|
adapterLines.push(` }),`);
|
|
305
313
|
} else if (answers.database === "mongodb") {
|
|
306
|
-
imports.push("import {
|
|
307
|
-
adapterLines.push(` adapter:
|
|
314
|
+
imports.push("import { createMongoDBAdapter } from '@kyro-cms/core';");
|
|
315
|
+
adapterLines.push(` adapter: createMongoDBAdapter({`);
|
|
308
316
|
adapterLines.push(` connectionString: process.env.MONGODB_URI,`);
|
|
309
317
|
adapterLines.push(` }),`);
|
|
310
318
|
}
|
|
@@ -328,28 +336,46 @@ function generateKyroConfig(answers) {
|
|
|
328
336
|
if (answers.versioning) {
|
|
329
337
|
features.push(" versioning: true,");
|
|
330
338
|
}
|
|
331
|
-
let
|
|
332
|
-
let
|
|
339
|
+
let templateCollections = "";
|
|
340
|
+
let templateGlobals = "";
|
|
341
|
+
switch (answers.template) {
|
|
342
|
+
case "minimal":
|
|
343
|
+
templateCollections = "import { minimalCollections } from '@kyro-cms/core';";
|
|
344
|
+
break;
|
|
345
|
+
case "blog":
|
|
346
|
+
templateCollections = "import { blogCollections } from '@kyro-cms/core';";
|
|
347
|
+
break;
|
|
348
|
+
case "ecommerce":
|
|
349
|
+
templateCollections = "import { ecommerceCollections } from '@kyro-cms/core';";
|
|
350
|
+
break;
|
|
351
|
+
case "kitchen-sink":
|
|
352
|
+
templateCollections = `import { minimalCollections, blogCollections, ecommerceCollections, kitchenSinkCollections } from '@kyro-cms/core';`;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
if (templateCollections) {
|
|
356
|
+
imports.push(templateCollections);
|
|
357
|
+
}
|
|
358
|
+
let collectionsConfig = "";
|
|
333
359
|
if (answers.template === "minimal") {
|
|
334
|
-
|
|
335
|
-
collectionsLine = " ...minimalCollections,";
|
|
360
|
+
collectionsConfig = ` collections: Object.values(minimalCollections),`;
|
|
336
361
|
} else if (answers.template === "blog") {
|
|
337
|
-
|
|
338
|
-
collectionsLine = " ...blogCollections,";
|
|
362
|
+
collectionsConfig = ` collections: Object.values(blogCollections),`;
|
|
339
363
|
} else if (answers.template === "ecommerce") {
|
|
340
|
-
|
|
341
|
-
|
|
364
|
+
collectionsConfig = ` collections: Object.values(ecommerceCollections),`;
|
|
365
|
+
} else if (answers.template === "kitchen-sink") {
|
|
366
|
+
collectionsConfig = ` collections: [
|
|
367
|
+
...Object.values(minimalCollections),
|
|
368
|
+
...Object.values(blogCollections),
|
|
369
|
+
...Object.values(ecommerceCollections),
|
|
370
|
+
...Object.values(kitchenSinkCollections),
|
|
371
|
+
],`;
|
|
342
372
|
}
|
|
343
373
|
const config = `${imports.join("\n")}
|
|
344
|
-
${templateImports}
|
|
345
374
|
|
|
346
375
|
export default defineConfig({
|
|
347
376
|
name: '${answers.projectName}',
|
|
348
377
|
prefix: '/api',${adapterLines.length > 0 ? "\n" + adapterLines.join("\n") : ""}
|
|
349
|
-
|
|
350
|
-
collections: {
|
|
351
|
-
${collectionsLine}
|
|
352
|
-
},${features.length > 0 ? "\n" + features.join("\n") : ""}
|
|
378
|
+
${collectionsConfig ? collectionsConfig : ""}${features.length > 0 ? "\n" + features.join("\n") : ""}
|
|
353
379
|
|
|
354
380
|
api: {
|
|
355
381
|
${apiConfig.join("\n")}
|
|
@@ -371,6 +397,7 @@ function generateAstroConfig(answers) {
|
|
|
371
397
|
mode: 'standalone'
|
|
372
398
|
}),` : "";
|
|
373
399
|
const config = `import { defineConfig } from 'astro/config';
|
|
400
|
+
import node from '@astrojs/node';
|
|
374
401
|
${answers.styling === "tailwind" ? "import react from '@astrojs/react';\nimport tailwindcss from '@tailwindcss/vite';" : ""}
|
|
375
402
|
|
|
376
403
|
export default defineConfig({
|
|
@@ -428,18 +455,67 @@ data/
|
|
|
428
455
|
|
|
429
456
|
A Kyro CMS project.
|
|
430
457
|
|
|
431
|
-
##
|
|
458
|
+
## Quick Start
|
|
432
459
|
|
|
433
460
|
\`\`\`bash
|
|
434
461
|
npm install
|
|
435
462
|
npm run dev
|
|
436
463
|
\`\`\`
|
|
437
464
|
|
|
465
|
+
## Admin Dashboard
|
|
466
|
+
|
|
467
|
+
Visit [http://localhost:4321/admin](http://localhost:4321/admin) to access the admin.
|
|
468
|
+
|
|
469
|
+
${answers.auth ? `## Creating Your Admin User
|
|
470
|
+
|
|
471
|
+
Before logging into the admin, you need to create an admin user. Run:
|
|
472
|
+
|
|
473
|
+
\`\`\`bash
|
|
474
|
+
npm run db:bootstrap
|
|
475
|
+
\`\`\`
|
|
476
|
+
|
|
477
|
+
Or set environment variables to auto-bootstrap on startup:
|
|
478
|
+
|
|
479
|
+
\`\`\`bash
|
|
480
|
+
# .env
|
|
481
|
+
KYRO_ADMIN_EMAIL=admin@example.com
|
|
482
|
+
KYRO_ADMIN_PASSWORD=SecurePass123!
|
|
483
|
+
\`\`\`
|
|
484
|
+
|
|
485
|
+
Then restart the dev server.
|
|
486
|
+
` : ""}
|
|
487
|
+
|
|
438
488
|
## Documentation
|
|
439
489
|
|
|
440
490
|
Visit [https://kyro.cms](https://kyro.cms) for full documentation.
|
|
441
491
|
`;
|
|
442
492
|
writeFileSync(join(projectDir, "README.md"), readme);
|
|
493
|
+
const envExample = `# Kyro CMS Configuration
|
|
494
|
+
|
|
495
|
+
${answers.database === "sqlite" ? "# SQLite (local) - no additional config needed" : answers.database === "postgres" || answers.database === "mysql" ? "# Database connection (PostgreSQL/MySQL)\nDATABASE_URL=postgresql://user:password@localhost:5432/kyro_cms\nDATABASE_SSL=false" : "# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/kyro_cms"}
|
|
496
|
+
|
|
497
|
+
${answers.auth ? `# Authentication (required for auth)
|
|
498
|
+
JWT_SECRET=change-this-to-a-random-32-character-string
|
|
499
|
+
JWT_EXPIRES_IN=24h
|
|
500
|
+
|
|
501
|
+
# Bootstrap admin user (creates on first run)
|
|
502
|
+
KYRO_ADMIN_EMAIL=admin@example.com
|
|
503
|
+
KYRO_ADMIN_PASSWORD=SecurePass123!
|
|
504
|
+
KYRO_ADMIN_ROLE=super_admin
|
|
505
|
+
|
|
506
|
+
# Optional: Redis for sessions (recommended for production)
|
|
507
|
+
# REDIS_URL=redis://localhost:6379
|
|
508
|
+
# REDIS_TLS=false
|
|
509
|
+
|
|
510
|
+
# Optional: SMTP for emails
|
|
511
|
+
# SMTP_HOST=smtp.example.com
|
|
512
|
+
# SMTP_PORT=587
|
|
513
|
+
# SMTP_SECURE=false
|
|
514
|
+
# SMTP_USER=your-email@example.com
|
|
515
|
+
# SMTP_PASS=your-password
|
|
516
|
+
# SMTP_FROM=noreply@example.com` : ""}
|
|
517
|
+
`;
|
|
518
|
+
writeFileSync(join(projectDir, ".env.example"), envExample);
|
|
443
519
|
const spec = `# ${answers.projectName}
|
|
444
520
|
|
|
445
521
|
## Overview
|
|
@@ -506,11 +582,11 @@ const title = "${answers.projectName}";
|
|
|
506
582
|
`;
|
|
507
583
|
writeFileSync(join(pagesDir, "index.astro"), indexPage);
|
|
508
584
|
if (answers.admin) {
|
|
509
|
-
const adminDir = join(
|
|
585
|
+
const adminDir = join(pagesDir, "admin");
|
|
510
586
|
mkdirSync(adminDir, { recursive: true });
|
|
511
587
|
const adminIndex = `---
|
|
512
588
|
import { Admin } from '@kyro-cms/admin';
|
|
513
|
-
import config from '
|
|
589
|
+
import config from '../../../kyro.config';
|
|
514
590
|
---
|
|
515
591
|
<!DOCTYPE html>
|
|
516
592
|
<html lang="en">
|
|
@@ -526,13 +602,455 @@ import config from '../kyro.config';
|
|
|
526
602
|
`;
|
|
527
603
|
writeFileSync(join(adminDir, "index.astro"), adminIndex);
|
|
528
604
|
}
|
|
605
|
+
if (answers.auth) {
|
|
606
|
+
const authApiDir = join(pagesDir, "api", "auth");
|
|
607
|
+
mkdirSync(authApiDir, { recursive: true });
|
|
608
|
+
writeFileSync(
|
|
609
|
+
join(authApiDir, "login.ts"),
|
|
610
|
+
generateLoginEndpoint(answers.database)
|
|
611
|
+
);
|
|
612
|
+
writeFileSync(
|
|
613
|
+
join(authApiDir, "register.ts"),
|
|
614
|
+
generateRegisterEndpoint(answers.database)
|
|
615
|
+
);
|
|
616
|
+
writeFileSync(
|
|
617
|
+
join(authApiDir, "logout.ts"),
|
|
618
|
+
generateLogoutEndpoint(answers.database)
|
|
619
|
+
);
|
|
620
|
+
writeFileSync(join(authApiDir, "me.ts"), generateMeEndpoint());
|
|
621
|
+
writeFileSync(
|
|
622
|
+
join(authApiDir, "users.ts"),
|
|
623
|
+
generateUsersEndpoint(answers.database)
|
|
624
|
+
);
|
|
625
|
+
writeFileSync(join(srcDir, "middleware.ts"), generateMiddleware());
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function generateLoginEndpoint(database) {
|
|
629
|
+
const adapterImport = database === "sqlite" ? `import { SQLiteAuthAdapter } from "@kyro-cms/core";` : `import { RedisAuthAdapter } from "@kyro-cms/core";`;
|
|
630
|
+
const adapterInit = database === "sqlite" ? ` return new SQLiteAuthAdapter({ path: "./data.db" });` : ` return new RedisAuthAdapter({
|
|
631
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
632
|
+
tls: process.env.REDIS_TLS === "true",
|
|
633
|
+
});`;
|
|
634
|
+
return `import type { APIRoute } from "astro";
|
|
635
|
+
${adapterImport}
|
|
636
|
+
import jwt from "jsonwebtoken";
|
|
637
|
+
|
|
638
|
+
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
|
|
639
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "24h";
|
|
640
|
+
|
|
641
|
+
async function getAuthApi() {
|
|
642
|
+
${adapterInit}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
646
|
+
try {
|
|
647
|
+
const body = (await request.json()) as {
|
|
648
|
+
email?: string;
|
|
649
|
+
password?: string;
|
|
650
|
+
};
|
|
651
|
+
const { email, password } = body;
|
|
652
|
+
|
|
653
|
+
if (!email || !password) {
|
|
654
|
+
return new Response(
|
|
655
|
+
JSON.stringify({ error: "Email and password required" }),
|
|
656
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const adapter = await getAuthApi();
|
|
661
|
+
await adapter.connect();
|
|
662
|
+
|
|
663
|
+
const user = await adapter.findUserByEmail(email);
|
|
664
|
+
if (!user || !user.passwordHash) {
|
|
665
|
+
await adapter.disconnect();
|
|
666
|
+
return new Response(JSON.stringify({ error: "Invalid credentials" }), {
|
|
667
|
+
status: 401,
|
|
668
|
+
headers: { "Content-Type": "application/json" },
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const valid = await adapter.verifyPassword(password, user.passwordHash);
|
|
673
|
+
if (!valid) {
|
|
674
|
+
await adapter.disconnect();
|
|
675
|
+
return new Response(JSON.stringify({ error: "Invalid credentials" }), {
|
|
676
|
+
status: 401,
|
|
677
|
+
headers: { "Content-Type": "application/json" },
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const session = await adapter.createSession(user.id, {
|
|
682
|
+
ipAddress: request.headers.get("x-forwarded-for") || "unknown",
|
|
683
|
+
userAgent: request.headers.get("user-agent") || "",
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const token = jwt.sign(
|
|
687
|
+
{
|
|
688
|
+
sub: user.id,
|
|
689
|
+
email: user.email,
|
|
690
|
+
role: user.role,
|
|
691
|
+
tenantId: user.tenantId,
|
|
692
|
+
},
|
|
693
|
+
JWT_SECRET,
|
|
694
|
+
{ expiresIn: JWT_EXPIRES_IN as jwt.SignOptions["expiresIn"] },
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
await adapter.disconnect();
|
|
698
|
+
|
|
699
|
+
const { passwordHash, ...safeUser } = user;
|
|
700
|
+
|
|
701
|
+
return new Response(
|
|
702
|
+
JSON.stringify({
|
|
703
|
+
success: true,
|
|
704
|
+
user: safeUser,
|
|
705
|
+
token,
|
|
706
|
+
refreshToken: session.refreshToken,
|
|
707
|
+
}),
|
|
708
|
+
{
|
|
709
|
+
status: 200,
|
|
710
|
+
headers: { "Content-Type": "application/json" },
|
|
711
|
+
},
|
|
712
|
+
);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error("Login error:", error);
|
|
715
|
+
return new Response(JSON.stringify({ error: "Login failed" }), {
|
|
716
|
+
status: 500,
|
|
717
|
+
headers: { "Content-Type": "application/json" },
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
`;
|
|
722
|
+
}
|
|
723
|
+
function generateRegisterEndpoint(database) {
|
|
724
|
+
const adapterImport = database === "sqlite" ? `import { SQLiteAuthAdapter } from "@kyro-cms/core";` : `import { RedisAuthAdapter } from "@kyro-cms/core";`;
|
|
725
|
+
const adapterInit = database === "sqlite" ? ` return new SQLiteAuthAdapter({ path: "./data.db" });` : ` return new RedisAuthAdapter({
|
|
726
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
727
|
+
tls: process.env.REDIS_TLS === "true",
|
|
728
|
+
});`;
|
|
729
|
+
const isFirstUserCheck = database === "sqlite" ? ` const isFirstUser = !(await adapter.hasAnyUsers());` : ` const isFirstUser = await checkIsFirstUser(adapter);`;
|
|
730
|
+
const isFirstUserFn = database === "sqlite" ? "" : `
|
|
731
|
+
|
|
732
|
+
async function checkIsFirstUser(adapter: RedisAuthAdapter): Promise<boolean> {
|
|
733
|
+
try {
|
|
734
|
+
const redis = (adapter as any).redis;
|
|
735
|
+
if (!redis) return true;
|
|
736
|
+
const pattern = "kyro:auth:users:email:*";
|
|
737
|
+
const result = await redis.scan("0", "MATCH", pattern, "COUNT", "1");
|
|
738
|
+
const keys = result[1];
|
|
739
|
+
return keys.length === 0;
|
|
740
|
+
} catch {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
}`;
|
|
744
|
+
return `import type { APIRoute } from "astro";
|
|
745
|
+
${adapterImport}
|
|
746
|
+
import jwt from "jsonwebtoken";
|
|
747
|
+
|
|
748
|
+
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
|
|
749
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "24h";
|
|
750
|
+
const ALLOW_REGISTRATION = process.env.KYRO_ALLOW_REGISTRATION !== "false";
|
|
751
|
+
|
|
752
|
+
async function getAuthApi() {
|
|
753
|
+
${adapterInit}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
757
|
+
try {
|
|
758
|
+
const body = (await request.json()) as {
|
|
759
|
+
email?: string;
|
|
760
|
+
password?: string;
|
|
761
|
+
confirmPassword?: string;
|
|
762
|
+
};
|
|
763
|
+
const { email, password, confirmPassword } = body;
|
|
764
|
+
|
|
765
|
+
if (!email || !password) {
|
|
766
|
+
return new Response(
|
|
767
|
+
JSON.stringify({ error: "Email and password required" }),
|
|
768
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (password !== confirmPassword) {
|
|
773
|
+
return new Response(
|
|
774
|
+
JSON.stringify({ error: "Passwords do not match" }),
|
|
775
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (password.length < 8) {
|
|
780
|
+
return new Response(
|
|
781
|
+
JSON.stringify({ error: "Password must be at least 8 characters" }),
|
|
782
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const adapter = await getAuthApi();
|
|
787
|
+
try {
|
|
788
|
+
await adapter.connect();
|
|
789
|
+
} catch {
|
|
790
|
+
return new Response(
|
|
791
|
+
JSON.stringify({ error: "Unable to connect to auth storage." }),
|
|
792
|
+
{ status: 500, headers: { "Content-Type": "application/json" } },
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const existingUser = await adapter.findUserByEmail(email);
|
|
797
|
+
if (existingUser) {
|
|
798
|
+
await adapter.disconnect();
|
|
799
|
+
return new Response(
|
|
800
|
+
JSON.stringify({ error: "Email already registered" }),
|
|
801
|
+
{ status: 409, headers: { "Content-Type": "application/json" } },
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
${isFirstUserCheck}
|
|
806
|
+
|
|
807
|
+
if (!isFirstUser && !ALLOW_REGISTRATION) {
|
|
808
|
+
await adapter.disconnect();
|
|
809
|
+
return new Response(
|
|
810
|
+
JSON.stringify({ error: "Registration is disabled" }),
|
|
811
|
+
{ status: 403, headers: { "Content-Type": "application/json" } },
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const passwordHash = await adapter.hashPassword(password);
|
|
816
|
+
const user = await adapter.createUser({
|
|
817
|
+
email,
|
|
818
|
+
passwordHash,
|
|
819
|
+
role: isFirstUser ? "super_admin" : "editor",
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
if (isFirstUser) {
|
|
823
|
+
await adapter.updateUser(user.id, { emailVerified: true });
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const session = await adapter.createSession(user.id, {
|
|
827
|
+
ipAddress: request.headers.get("x-forwarded-for") || "unknown",
|
|
828
|
+
userAgent: request.headers.get("user-agent") || "",
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const token = jwt.sign(
|
|
832
|
+
{
|
|
833
|
+
sub: user.id,
|
|
834
|
+
email: user.email,
|
|
835
|
+
role: user.role,
|
|
836
|
+
tenantId: user.tenantId,
|
|
837
|
+
},
|
|
838
|
+
JWT_SECRET,
|
|
839
|
+
{ expiresIn: JWT_EXPIRES_IN as jwt.SignOptions["expiresIn"] },
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
await adapter.disconnect();
|
|
843
|
+
|
|
844
|
+
const { passwordHash: _, ...safeUser } = user;
|
|
845
|
+
|
|
846
|
+
return new Response(
|
|
847
|
+
JSON.stringify({
|
|
848
|
+
success: true,
|
|
849
|
+
isFirstUser,
|
|
850
|
+
user: safeUser,
|
|
851
|
+
token,
|
|
852
|
+
refreshToken: session.refreshToken,
|
|
853
|
+
}),
|
|
854
|
+
{
|
|
855
|
+
status: 201,
|
|
856
|
+
headers: { "Content-Type": "application/json" },
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
console.error("Registration error:", error);
|
|
861
|
+
return new Response(JSON.stringify({ error: "Registration failed" }), {
|
|
862
|
+
status: 500,
|
|
863
|
+
headers: { "Content-Type": "application/json" },
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
};${isFirstUserFn}
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
function generateLogoutEndpoint(database) {
|
|
870
|
+
const adapterImport = database === "sqlite" ? `import { SQLiteAuthAdapter } from "@kyro-cms/core";` : `import { RedisAuthAdapter } from "@kyro-cms/core";`;
|
|
871
|
+
const adapterInit = database === "sqlite" ? ` return new SQLiteAuthAdapter({ path: "./data.db" });` : ` return new RedisAuthAdapter({
|
|
872
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
873
|
+
tls: process.env.REDIS_TLS === "true",
|
|
874
|
+
});`;
|
|
875
|
+
return `import type { APIRoute } from "astro";
|
|
876
|
+
${adapterImport}
|
|
877
|
+
|
|
878
|
+
async function getAuthApi() {
|
|
879
|
+
${adapterInit}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
883
|
+
try {
|
|
884
|
+
const authHeader = request.headers.get("authorization");
|
|
885
|
+
const token = authHeader?.startsWith("Bearer ")
|
|
886
|
+
? authHeader.slice(7)
|
|
887
|
+
: null;
|
|
888
|
+
|
|
889
|
+
if (token) {
|
|
890
|
+
const adapter = await getAuthApi();
|
|
891
|
+
await adapter.connect();
|
|
892
|
+
await adapter.deleteSession(token);
|
|
893
|
+
await adapter.disconnect();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return new Response(
|
|
897
|
+
JSON.stringify({ success: true }),
|
|
898
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
899
|
+
);
|
|
900
|
+
} catch {
|
|
901
|
+
return new Response(
|
|
902
|
+
JSON.stringify({ success: true }),
|
|
903
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
`;
|
|
908
|
+
}
|
|
909
|
+
function generateMeEndpoint() {
|
|
910
|
+
return `import type { APIRoute } from "astro";
|
|
911
|
+
import jwt from "jsonwebtoken";
|
|
912
|
+
|
|
913
|
+
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
|
|
914
|
+
|
|
915
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
916
|
+
const authHeader = request.headers.get("authorization");
|
|
917
|
+
const token = authHeader?.startsWith("Bearer ")
|
|
918
|
+
? authHeader.slice(7)
|
|
919
|
+
: null;
|
|
920
|
+
|
|
921
|
+
if (!token) {
|
|
922
|
+
return new Response(
|
|
923
|
+
JSON.stringify({ error: "Not authenticated" }),
|
|
924
|
+
{ status: 401, headers: { "Content-Type": "application/json" } },
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
try {
|
|
929
|
+
const payload = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
|
|
930
|
+
return new Response(
|
|
931
|
+
JSON.stringify({
|
|
932
|
+
id: payload.sub,
|
|
933
|
+
email: payload.email,
|
|
934
|
+
role: payload.role,
|
|
935
|
+
}),
|
|
936
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
937
|
+
);
|
|
938
|
+
} catch {
|
|
939
|
+
return new Response(
|
|
940
|
+
JSON.stringify({ error: "Invalid token" }),
|
|
941
|
+
{ status: 401, headers: { "Content-Type": "application/json" } },
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
function generateUsersEndpoint(database) {
|
|
948
|
+
const adapterImport = database === "sqlite" ? `import { SQLiteAuthAdapter } from "@kyro-cms/core";` : `import { RedisAuthAdapter } from "@kyro-cms/core";`;
|
|
949
|
+
const adapterInit = database === "sqlite" ? ` return new SQLiteAuthAdapter({ path: "./data.db" });` : ` return new RedisAuthAdapter({
|
|
950
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
951
|
+
tls: process.env.REDIS_TLS === "true",
|
|
952
|
+
});`;
|
|
953
|
+
const hasUsersCheck = database === "sqlite" ? ` const hasUsers = await adapter.hasAnyUsers();` : ` const redis = (adapter as any).redis;
|
|
954
|
+
if (!redis) {
|
|
955
|
+
await adapter.disconnect();
|
|
956
|
+
return new Response(JSON.stringify({ hasUsers: false }), {
|
|
957
|
+
status: 200,
|
|
958
|
+
headers: { "Content-Type": "application/json" },
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const pattern = "kyro:auth:users:email:*";
|
|
963
|
+
const result = await redis.scan("0", "MATCH", pattern, "COUNT", "1");
|
|
964
|
+
const keys = result[1];
|
|
965
|
+
const hasUsers = keys.length > 0;`;
|
|
966
|
+
return `import type { APIRoute } from "astro";
|
|
967
|
+
${adapterImport}
|
|
968
|
+
|
|
969
|
+
async function getAuthApi() {
|
|
970
|
+
${adapterInit}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
export const GET: APIRoute = async () => {
|
|
974
|
+
try {
|
|
975
|
+
const adapter = await getAuthApi();
|
|
976
|
+
await adapter.connect();
|
|
977
|
+
|
|
978
|
+
${hasUsersCheck}
|
|
979
|
+
|
|
980
|
+
await adapter.disconnect();
|
|
981
|
+
|
|
982
|
+
return new Response(JSON.stringify({ hasUsers }), {
|
|
983
|
+
status: 200,
|
|
984
|
+
headers: { "Content-Type": "application/json" },
|
|
985
|
+
});
|
|
986
|
+
} catch {
|
|
987
|
+
return new Response(JSON.stringify({ hasUsers: false }), {
|
|
988
|
+
status: 200,
|
|
989
|
+
headers: { "Content-Type": "application/json" },
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
`;
|
|
994
|
+
}
|
|
995
|
+
function generateMiddleware() {
|
|
996
|
+
return `import type { MiddlewareHandler } from "astro";
|
|
997
|
+
import jwt from "jsonwebtoken";
|
|
998
|
+
|
|
999
|
+
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
|
|
1000
|
+
|
|
1001
|
+
const PUBLIC_PATHS = [
|
|
1002
|
+
"/api/auth/login",
|
|
1003
|
+
"/api/auth/logout",
|
|
1004
|
+
"/api/auth/register",
|
|
1005
|
+
"/api/auth/me",
|
|
1006
|
+
"/api/auth/users",
|
|
1007
|
+
"/api/health",
|
|
1008
|
+
"/favicon.svg",
|
|
1009
|
+
];
|
|
1010
|
+
|
|
1011
|
+
const PUBLIC_PREFIXES = ["/api/auth/", "/admin"];
|
|
1012
|
+
|
|
1013
|
+
export const onRequest: MiddlewareHandler = async ({ request, url }, next) => {
|
|
1014
|
+
const pathname = new URL(url).pathname;
|
|
1015
|
+
|
|
1016
|
+
if (PUBLIC_PATHS.includes(pathname) || PUBLIC_PATHS.includes(pathname.replace(/\\/$/, ""))) {
|
|
1017
|
+
return next();
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
for (const prefix of PUBLIC_PREFIXES) {
|
|
1021
|
+
if (pathname.startsWith(prefix)) {
|
|
1022
|
+
return next();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const authHeader = request.headers.get("authorization");
|
|
1027
|
+
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
1028
|
+
|
|
1029
|
+
if (!token) {
|
|
1030
|
+
return new Response(
|
|
1031
|
+
JSON.stringify({ error: "Authentication required" }),
|
|
1032
|
+
{ status: 401, headers: { "Content-Type": "application/json" } },
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
|
|
1038
|
+
return next();
|
|
1039
|
+
} catch {
|
|
1040
|
+
return new Response(
|
|
1041
|
+
JSON.stringify({ error: "Invalid or expired token" }),
|
|
1042
|
+
{ status: 401, headers: { "Content-Type": "application/json" } },
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
`;
|
|
529
1047
|
}
|
|
530
1048
|
|
|
531
1049
|
// src/index.ts
|
|
532
1050
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
533
1051
|
import { join as join2 } from "path";
|
|
534
1052
|
import { execSync } from "child_process";
|
|
535
|
-
var VERSION = "0.1.
|
|
1053
|
+
var VERSION = "0.1.1";
|
|
536
1054
|
async function main() {
|
|
537
1055
|
logger.intro("create-kyro", VERSION);
|
|
538
1056
|
const answers = await promptUser();
|