create-sprint 0.0.46 → 0.0.52
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/generators.js +100 -87
- package/dist/index.js +12 -2
- package/package.json +1 -1
- package/src/generators.ts +103 -88
- package/src/index.ts +13 -3
package/dist/generators.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
2
|
export function generateJWTKeys() {
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const keys = crypto.generateKeyPairSync("rsa", {
|
|
4
|
+
modulusLength: 4096,
|
|
5
5
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
6
6
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
7
7
|
});
|
|
8
|
-
return
|
|
8
|
+
return keys;
|
|
9
9
|
}
|
|
10
10
|
export function getTypeScriptPackageJson(name, telemetry) {
|
|
11
11
|
const deps = {
|
|
@@ -130,22 +130,24 @@ export function getHomeRoute(language) {
|
|
|
130
130
|
if (language === "typescript") {
|
|
131
131
|
return `import { Router } from "sprint-es";
|
|
132
132
|
import { homeSchema } from "@/schemas/home";
|
|
133
|
-
import { homeController } from "@/controllers/home";
|
|
133
|
+
import { homeController, jwtValidateController } from "@/controllers/home";
|
|
134
134
|
|
|
135
135
|
const router = Router();
|
|
136
136
|
|
|
137
137
|
router.get("/", homeSchema, homeController);
|
|
138
|
+
router.post("/jwt/validate", jwtValidateController);
|
|
138
139
|
|
|
139
140
|
export default router;
|
|
140
141
|
`;
|
|
141
142
|
}
|
|
142
143
|
return `import { Router } from "sprint-es";
|
|
143
144
|
import { homeSchema } from "../schemas/home.js";
|
|
144
|
-
import { homeController } from "../controllers/home.js";
|
|
145
|
+
import { homeController, jwtValidateController } from "../controllers/home.js";
|
|
145
146
|
|
|
146
147
|
const router = Router();
|
|
147
148
|
|
|
148
149
|
router.get("/", homeSchema, homeController);
|
|
150
|
+
router.post("/jwt/validate", jwtValidateController);
|
|
149
151
|
|
|
150
152
|
export default router;
|
|
151
153
|
`;
|
|
@@ -153,68 +155,110 @@ export default router;
|
|
|
153
155
|
export function getAdminRoute(language) {
|
|
154
156
|
if (language === "typescript") {
|
|
155
157
|
return `import { Router } from "sprint-es";
|
|
156
|
-
import { adminSchema } from "@/schemas/admin";
|
|
157
|
-
import { adminController, adminUsersController, jwtGenerateController
|
|
158
|
+
import { adminSchema, jwtGenerateSchema } from "@/schemas/admin";
|
|
159
|
+
import { adminController, adminUsersController, jwtGenerateController } from "@/controllers/admin";
|
|
158
160
|
|
|
159
161
|
const router = Router();
|
|
160
162
|
|
|
161
163
|
router.get("/", adminSchema, adminController);
|
|
162
164
|
router.get("/users", adminSchema, adminUsersController);
|
|
163
|
-
router.post("/jwt/generate", jwtGenerateController);
|
|
164
|
-
router.post("/jwt/validate", jwtValidateController);
|
|
165
|
+
router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
|
|
165
166
|
|
|
166
167
|
export default router;
|
|
167
168
|
`;
|
|
168
169
|
}
|
|
169
170
|
return `import { Router } from "sprint-es";
|
|
170
|
-
import { adminSchema } from "../schemas/admin.js";
|
|
171
|
-
import { adminController, adminUsersController, jwtGenerateController
|
|
171
|
+
import { adminSchema, jwtGenerateSchema } from "../schemas/admin.js";
|
|
172
|
+
import { adminController, adminUsersController, jwtGenerateController } from "../controllers/admin.js";
|
|
172
173
|
|
|
173
174
|
const router = Router();
|
|
174
175
|
|
|
175
176
|
router.get("/", adminSchema, adminController);
|
|
176
177
|
router.get("/users", adminSchema, adminUsersController);
|
|
177
|
-
router.post("/jwt/generate", jwtGenerateController);
|
|
178
|
-
router.post("/jwt/validate", jwtValidateController);
|
|
178
|
+
router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
|
|
179
179
|
|
|
180
180
|
export default router;
|
|
181
181
|
`;
|
|
182
182
|
}
|
|
183
183
|
export function getHomeController(language) {
|
|
184
184
|
if (language === "typescript") {
|
|
185
|
-
return `import { Handler } from "sprint-es";
|
|
185
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
186
|
+
import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
186
187
|
|
|
187
|
-
export const homeController: Handler = (req, res) => {
|
|
188
|
+
export const homeController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
188
189
|
res.json({
|
|
189
190
|
message: "Hello World",
|
|
190
191
|
status: "ok"
|
|
191
192
|
});
|
|
192
193
|
};
|
|
194
|
+
|
|
195
|
+
export const jwtValidateController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
196
|
+
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
197
|
+
|
|
198
|
+
if (!token) {
|
|
199
|
+
return res.status(401).json({ error: "No token provided" });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const { publicKey } = getJwtFromEnv();
|
|
204
|
+
const decoded = verifyEncrypted(token, publicKey);
|
|
205
|
+
|
|
206
|
+
if (!decoded) {
|
|
207
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
res.json({ valid: true, payload: decoded });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return res.status(500).json({ error: "JWT not configured" });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
193
215
|
`;
|
|
194
216
|
}
|
|
195
|
-
return `import { Handler } from "sprint-es";
|
|
217
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
218
|
+
import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
196
219
|
|
|
197
|
-
export const homeController = (req, res) => {
|
|
220
|
+
export const homeController = (req: SprintRequest, res: SprintResponse) => {
|
|
198
221
|
res.json({
|
|
199
222
|
message: "Hello World",
|
|
200
223
|
status: "ok"
|
|
201
224
|
});
|
|
202
225
|
};
|
|
226
|
+
|
|
227
|
+
export const jwtValidateController = (req: SprintRequest, res: SprintResponse) => {
|
|
228
|
+
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
229
|
+
|
|
230
|
+
if (!token) {
|
|
231
|
+
return res.status(401).json({ error: "No token provided" });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const { publicKey } = getJwtFromEnv();
|
|
236
|
+
const decoded = verifyEncrypted(token, publicKey);
|
|
237
|
+
|
|
238
|
+
if (!decoded) {
|
|
239
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
res.json({ valid: true, payload: decoded });
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return res.status(500).json({ error: "JWT not configured" });
|
|
245
|
+
}
|
|
246
|
+
};
|
|
203
247
|
`;
|
|
204
248
|
}
|
|
205
249
|
export function getAdminController(language) {
|
|
206
250
|
if (language === "typescript") {
|
|
207
|
-
return `import { Handler } from "sprint-es";
|
|
208
|
-
import { signEncrypted,
|
|
251
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
252
|
+
import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
209
253
|
|
|
210
|
-
export const adminController: Handler = (req, res) => {
|
|
254
|
+
export const adminController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
211
255
|
res.json({
|
|
212
256
|
message: "Admin Dashboard",
|
|
213
257
|
status: "ok"
|
|
214
258
|
});
|
|
215
259
|
};
|
|
216
260
|
|
|
217
|
-
export const adminUsersController: Handler = (req, res) => {
|
|
261
|
+
export const adminUsersController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
218
262
|
res.json({
|
|
219
263
|
users: [
|
|
220
264
|
{ id: 1, name: "John Doe", role: "admin" },
|
|
@@ -223,13 +267,9 @@ export const adminUsersController: Handler = (req, res) => {
|
|
|
223
267
|
});
|
|
224
268
|
};
|
|
225
269
|
|
|
226
|
-
export const jwtGenerateController: Handler = (req, res) => {
|
|
270
|
+
export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
227
271
|
const { userId, role } = req.body || {};
|
|
228
272
|
|
|
229
|
-
if (!userId) {
|
|
230
|
-
return res.status(400).json({ error: "userId is required" });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
273
|
try {
|
|
234
274
|
const { privateKey } = getJwtFromEnv();
|
|
235
275
|
const payload = { userId, role: role || "user" };
|
|
@@ -239,40 +279,19 @@ export const jwtGenerateController: Handler = (req, res) => {
|
|
|
239
279
|
return res.status(500).json({ error: "JWT not configured" });
|
|
240
280
|
}
|
|
241
281
|
};
|
|
242
|
-
|
|
243
|
-
export const jwtValidateController: Handler = (req, res) => {
|
|
244
|
-
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
245
|
-
|
|
246
|
-
if (!token) {
|
|
247
|
-
return res.status(401).json({ error: "No token provided" });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const { publicKey } = getJwtFromEnv();
|
|
252
|
-
const decoded = verifyEncrypted(token, publicKey);
|
|
253
|
-
|
|
254
|
-
if (!decoded) {
|
|
255
|
-
return res.status(401).json({ error: "Invalid token" });
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
res.json({ valid: true, payload: decoded });
|
|
259
|
-
} catch (error) {
|
|
260
|
-
return res.status(500).json({ error: "JWT not configured" });
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
282
|
`;
|
|
264
283
|
}
|
|
265
|
-
return `import { Handler } from "sprint-es";
|
|
266
|
-
import { signEncrypted,
|
|
284
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
285
|
+
import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
267
286
|
|
|
268
|
-
export const adminController = (req, res) => {
|
|
287
|
+
export const adminController = (req: SprintRequest, res: SprintResponse) => {
|
|
269
288
|
res.json({
|
|
270
289
|
message: "Admin Dashboard",
|
|
271
290
|
status: "ok"
|
|
272
291
|
});
|
|
273
292
|
};
|
|
274
293
|
|
|
275
|
-
export const adminUsersController = (req, res) => {
|
|
294
|
+
export const adminUsersController = (req: SprintRequest, res: SprintResponse) => {
|
|
276
295
|
res.json({
|
|
277
296
|
users: [
|
|
278
297
|
{ id: 1, name: "John Doe", role: "admin" },
|
|
@@ -281,13 +300,9 @@ export const adminUsersController = (req, res) => {
|
|
|
281
300
|
});
|
|
282
301
|
};
|
|
283
302
|
|
|
284
|
-
export const jwtGenerateController = (req, res) => {
|
|
303
|
+
export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) => {
|
|
285
304
|
const { userId, role } = req.body || {};
|
|
286
305
|
|
|
287
|
-
if (!userId) {
|
|
288
|
-
return res.status(400).json({ error: "userId is required" });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
306
|
try {
|
|
292
307
|
const { privateKey } = getJwtFromEnv();
|
|
293
308
|
const payload = { userId, role: role || "user" };
|
|
@@ -297,27 +312,6 @@ export const jwtGenerateController = (req, res) => {
|
|
|
297
312
|
return res.status(500).json({ error: "JWT not configured" });
|
|
298
313
|
}
|
|
299
314
|
};
|
|
300
|
-
|
|
301
|
-
export const jwtValidateController = (req, res) => {
|
|
302
|
-
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
303
|
-
|
|
304
|
-
if (!token) {
|
|
305
|
-
return res.status(401).json({ error: "No token provided" });
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
const { publicKey } = getJwtFromEnv();
|
|
310
|
-
const decoded = verifyEncrypted(token, publicKey);
|
|
311
|
-
|
|
312
|
-
if (!decoded) {
|
|
313
|
-
return res.status(401).json({ error: "Invalid token" });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
res.json({ valid: true, payload: decoded });
|
|
317
|
-
} catch (error) {
|
|
318
|
-
return res.status(500).json({ error: "JWT not configured" });
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
315
|
`;
|
|
322
316
|
}
|
|
323
317
|
export function getHomeSchema(language) {
|
|
@@ -345,6 +339,13 @@ export const adminSchema = defineRouteSchema({
|
|
|
345
339
|
email: z.string().email().optional()
|
|
346
340
|
})
|
|
347
341
|
});
|
|
342
|
+
|
|
343
|
+
export const jwtGenerateSchema = defineRouteSchema({
|
|
344
|
+
body: z.object({
|
|
345
|
+
userId: z.string().min(1),
|
|
346
|
+
role: z.string().optional()
|
|
347
|
+
})
|
|
348
|
+
});
|
|
348
349
|
`;
|
|
349
350
|
}
|
|
350
351
|
return `import { z, defineRouteSchema } from "sprint-es/schemas";
|
|
@@ -358,6 +359,13 @@ export const adminSchema = defineRouteSchema({
|
|
|
358
359
|
email: z.string().email().optional()
|
|
359
360
|
})
|
|
360
361
|
});
|
|
362
|
+
|
|
363
|
+
export const jwtGenerateSchema = defineRouteSchema({
|
|
364
|
+
body: z.object({
|
|
365
|
+
userId: z.string().min(1),
|
|
366
|
+
role: z.string().optional()
|
|
367
|
+
})
|
|
368
|
+
});
|
|
361
369
|
`;
|
|
362
370
|
}
|
|
363
371
|
export function getAuthMiddleware(language) {
|
|
@@ -580,7 +588,7 @@ initTelemetry({
|
|
|
580
588
|
return config;
|
|
581
589
|
}
|
|
582
590
|
export function getEnvExample(telemetry) {
|
|
583
|
-
let env = `PORT=
|
|
591
|
+
let env = `PORT=5000
|
|
584
592
|
|
|
585
593
|
# Development: npm run dev (NODE_ENV=development)
|
|
586
594
|
# Production: npm start (NODE_ENV=production)
|
|
@@ -599,12 +607,16 @@ DISCORD_WEBHOOK_URL=
|
|
|
599
607
|
}
|
|
600
608
|
return env;
|
|
601
609
|
}
|
|
610
|
+
function envKey(key) {
|
|
611
|
+
return key;
|
|
612
|
+
}
|
|
602
613
|
export function getEnvDevelopment(telemetry) {
|
|
603
|
-
const
|
|
614
|
+
const keys = generateJWTKeys();
|
|
604
615
|
let env = `NODE_ENV=development
|
|
605
|
-
PORT=
|
|
606
|
-
JWT_PUBLIC_KEY
|
|
607
|
-
JWT_PRIVATE_KEY
|
|
616
|
+
PORT=5000
|
|
617
|
+
JWT_PUBLIC_KEY='${keys.publicKey}'
|
|
618
|
+
JWT_PRIVATE_KEY='${keys.privateKey}'
|
|
619
|
+
JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
|
|
608
620
|
`;
|
|
609
621
|
if (telemetry === "sentry" || telemetry === "glitchtip") {
|
|
610
622
|
env += `
|
|
@@ -621,11 +633,12 @@ DISCORD_WEBHOOK_URL=
|
|
|
621
633
|
return env;
|
|
622
634
|
}
|
|
623
635
|
export function getEnvProduction(telemetry) {
|
|
624
|
-
const
|
|
636
|
+
const keys = generateJWTKeys();
|
|
625
637
|
let env = `NODE_ENV=production
|
|
626
|
-
PORT=
|
|
627
|
-
JWT_PUBLIC_KEY
|
|
628
|
-
JWT_PRIVATE_KEY
|
|
638
|
+
PORT=5000
|
|
639
|
+
JWT_PUBLIC_KEY='${keys.publicKey}'
|
|
640
|
+
JWT_PRIVATE_KEY='${keys.privateKey}'
|
|
641
|
+
JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
|
|
629
642
|
`;
|
|
630
643
|
if (telemetry === "sentry" || telemetry === "glitchtip") {
|
|
631
644
|
env += `
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
3
|
+
import { mkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
import color from "picocolors";
|
|
5
6
|
import * as p from "@clack/prompts";
|
|
6
7
|
import { validateProjectName } from "./validators.js";
|
|
7
8
|
import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
|
|
9
|
+
export async function writeFile(path, content, options) {
|
|
10
|
+
if (typeof content === "string")
|
|
11
|
+
content = content.trimEnd();
|
|
12
|
+
await fsWriteFile(path, content, options);
|
|
13
|
+
}
|
|
14
|
+
;
|
|
8
15
|
export async function runCLI(args) {
|
|
9
16
|
const options = parseArgs(args);
|
|
10
17
|
p.intro("Sprint — Quickly API Framework");
|
|
18
|
+
p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
|
|
11
19
|
const config = await p.group({
|
|
12
20
|
projectName: () => p.text({
|
|
13
21
|
message: "Project name:",
|
|
@@ -43,12 +51,13 @@ export async function runCLI(args) {
|
|
|
43
51
|
await createProject(config.projectName, config.language, config.telemetry, config.docker);
|
|
44
52
|
s.stop("Project created");
|
|
45
53
|
const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
|
|
54
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
46
55
|
if (installDeps) {
|
|
47
56
|
const s2 = p.spinner();
|
|
48
57
|
s2.start("Installing dependencies");
|
|
49
58
|
try {
|
|
50
59
|
await new Promise((resolve, reject) => {
|
|
51
|
-
const child = spawn(
|
|
60
|
+
const child = spawn(npmCmd, ["install"], {
|
|
52
61
|
cwd: targetDir,
|
|
53
62
|
stdio: "pipe"
|
|
54
63
|
});
|
|
@@ -58,6 +67,7 @@ export async function runCLI(args) {
|
|
|
58
67
|
else
|
|
59
68
|
reject(new Error(`npm install exited with code ${code}`));
|
|
60
69
|
});
|
|
70
|
+
child.on("error", reject);
|
|
61
71
|
});
|
|
62
72
|
s2.stop("Dependencies installed");
|
|
63
73
|
}
|
package/package.json
CHANGED
package/src/generators.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
2
|
|
|
3
3
|
export interface JWTKeys {
|
|
4
4
|
publicKey: string;
|
|
@@ -6,12 +6,12 @@ export interface JWTKeys {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function generateJWTKeys(): JWTKeys {
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const keys = crypto.generateKeyPairSync("rsa", {
|
|
10
|
+
modulusLength: 4096,
|
|
11
11
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
12
12
|
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
13
|
-
});
|
|
14
|
-
return
|
|
13
|
+
}) as unknown as { publicKey: string; privateKey: string };
|
|
14
|
+
return keys;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function getTypeScriptPackageJson(name: string, telemetry: string) {
|
|
@@ -146,22 +146,24 @@ export function getHomeRoute(language: string) {
|
|
|
146
146
|
if (language === "typescript") {
|
|
147
147
|
return `import { Router } from "sprint-es";
|
|
148
148
|
import { homeSchema } from "@/schemas/home";
|
|
149
|
-
import { homeController } from "@/controllers/home";
|
|
149
|
+
import { homeController, jwtValidateController } from "@/controllers/home";
|
|
150
150
|
|
|
151
151
|
const router = Router();
|
|
152
152
|
|
|
153
153
|
router.get("/", homeSchema, homeController);
|
|
154
|
+
router.post("/jwt/validate", jwtValidateController);
|
|
154
155
|
|
|
155
156
|
export default router;
|
|
156
157
|
`;
|
|
157
158
|
}
|
|
158
159
|
return `import { Router } from "sprint-es";
|
|
159
160
|
import { homeSchema } from "../schemas/home.js";
|
|
160
|
-
import { homeController } from "../controllers/home.js";
|
|
161
|
+
import { homeController, jwtValidateController } from "../controllers/home.js";
|
|
161
162
|
|
|
162
163
|
const router = Router();
|
|
163
164
|
|
|
164
165
|
router.get("/", homeSchema, homeController);
|
|
166
|
+
router.post("/jwt/validate", jwtValidateController);
|
|
165
167
|
|
|
166
168
|
export default router;
|
|
167
169
|
`;
|
|
@@ -170,29 +172,27 @@ export default router;
|
|
|
170
172
|
export function getAdminRoute(language: string) {
|
|
171
173
|
if (language === "typescript") {
|
|
172
174
|
return `import { Router } from "sprint-es";
|
|
173
|
-
import { adminSchema } from "@/schemas/admin";
|
|
174
|
-
import { adminController, adminUsersController, jwtGenerateController
|
|
175
|
+
import { adminSchema, jwtGenerateSchema } from "@/schemas/admin";
|
|
176
|
+
import { adminController, adminUsersController, jwtGenerateController } from "@/controllers/admin";
|
|
175
177
|
|
|
176
178
|
const router = Router();
|
|
177
179
|
|
|
178
180
|
router.get("/", adminSchema, adminController);
|
|
179
181
|
router.get("/users", adminSchema, adminUsersController);
|
|
180
|
-
router.post("/jwt/generate", jwtGenerateController);
|
|
181
|
-
router.post("/jwt/validate", jwtValidateController);
|
|
182
|
+
router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
|
|
182
183
|
|
|
183
184
|
export default router;
|
|
184
185
|
`;
|
|
185
186
|
}
|
|
186
187
|
return `import { Router } from "sprint-es";
|
|
187
|
-
import { adminSchema } from "../schemas/admin.js";
|
|
188
|
-
import { adminController, adminUsersController, jwtGenerateController
|
|
188
|
+
import { adminSchema, jwtGenerateSchema } from "../schemas/admin.js";
|
|
189
|
+
import { adminController, adminUsersController, jwtGenerateController } from "../controllers/admin.js";
|
|
189
190
|
|
|
190
191
|
const router = Router();
|
|
191
192
|
|
|
192
193
|
router.get("/", adminSchema, adminController);
|
|
193
194
|
router.get("/users", adminSchema, adminUsersController);
|
|
194
|
-
router.post("/jwt/generate", jwtGenerateController);
|
|
195
|
-
router.post("/jwt/validate", jwtValidateController);
|
|
195
|
+
router.post("/jwt/generate", jwtGenerateSchema, jwtGenerateController);
|
|
196
196
|
|
|
197
197
|
export default router;
|
|
198
198
|
`;
|
|
@@ -200,40 +200,84 @@ export default router;
|
|
|
200
200
|
|
|
201
201
|
export function getHomeController(language: string) {
|
|
202
202
|
if (language === "typescript") {
|
|
203
|
-
return `import { Handler } from "sprint-es";
|
|
203
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
204
|
+
import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
204
205
|
|
|
205
|
-
export const homeController: Handler = (req, res) => {
|
|
206
|
+
export const homeController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
206
207
|
res.json({
|
|
207
208
|
message: "Hello World",
|
|
208
209
|
status: "ok"
|
|
209
210
|
});
|
|
210
211
|
};
|
|
212
|
+
|
|
213
|
+
export const jwtValidateController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
214
|
+
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
215
|
+
|
|
216
|
+
if (!token) {
|
|
217
|
+
return res.status(401).json({ error: "No token provided" });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const { publicKey } = getJwtFromEnv();
|
|
222
|
+
const decoded = verifyEncrypted(token, publicKey);
|
|
223
|
+
|
|
224
|
+
if (!decoded) {
|
|
225
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
res.json({ valid: true, payload: decoded });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return res.status(500).json({ error: "JWT not configured" });
|
|
231
|
+
}
|
|
232
|
+
};
|
|
211
233
|
`;
|
|
212
234
|
}
|
|
213
|
-
return `import { Handler } from "sprint-es";
|
|
235
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
236
|
+
import { verifyEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
214
237
|
|
|
215
|
-
export const homeController = (req, res) => {
|
|
238
|
+
export const homeController = (req: SprintRequest, res: SprintResponse) => {
|
|
216
239
|
res.json({
|
|
217
240
|
message: "Hello World",
|
|
218
241
|
status: "ok"
|
|
219
242
|
});
|
|
220
243
|
};
|
|
244
|
+
|
|
245
|
+
export const jwtValidateController = (req: SprintRequest, res: SprintResponse) => {
|
|
246
|
+
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
247
|
+
|
|
248
|
+
if (!token) {
|
|
249
|
+
return res.status(401).json({ error: "No token provided" });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const { publicKey } = getJwtFromEnv();
|
|
254
|
+
const decoded = verifyEncrypted(token, publicKey);
|
|
255
|
+
|
|
256
|
+
if (!decoded) {
|
|
257
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
res.json({ valid: true, payload: decoded });
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return res.status(500).json({ error: "JWT not configured" });
|
|
263
|
+
}
|
|
264
|
+
};
|
|
221
265
|
`;
|
|
222
266
|
}
|
|
223
267
|
|
|
224
268
|
export function getAdminController(language: string) {
|
|
225
269
|
if (language === "typescript") {
|
|
226
|
-
return `import { Handler } from "sprint-es";
|
|
227
|
-
import { signEncrypted,
|
|
270
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
271
|
+
import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
228
272
|
|
|
229
|
-
export const adminController: Handler = (req, res) => {
|
|
273
|
+
export const adminController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
230
274
|
res.json({
|
|
231
275
|
message: "Admin Dashboard",
|
|
232
276
|
status: "ok"
|
|
233
277
|
});
|
|
234
278
|
};
|
|
235
279
|
|
|
236
|
-
export const adminUsersController: Handler = (req, res) => {
|
|
280
|
+
export const adminUsersController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
237
281
|
res.json({
|
|
238
282
|
users: [
|
|
239
283
|
{ id: 1, name: "John Doe", role: "admin" },
|
|
@@ -242,13 +286,9 @@ export const adminUsersController: Handler = (req, res) => {
|
|
|
242
286
|
});
|
|
243
287
|
};
|
|
244
288
|
|
|
245
|
-
export const jwtGenerateController: Handler = (req, res) => {
|
|
289
|
+
export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintResponse) => {
|
|
246
290
|
const { userId, role } = req.body || {};
|
|
247
291
|
|
|
248
|
-
if (!userId) {
|
|
249
|
-
return res.status(400).json({ error: "userId is required" });
|
|
250
|
-
}
|
|
251
|
-
|
|
252
292
|
try {
|
|
253
293
|
const { privateKey } = getJwtFromEnv();
|
|
254
294
|
const payload = { userId, role: role || "user" };
|
|
@@ -258,40 +298,19 @@ export const jwtGenerateController: Handler = (req, res) => {
|
|
|
258
298
|
return res.status(500).json({ error: "JWT not configured" });
|
|
259
299
|
}
|
|
260
300
|
};
|
|
261
|
-
|
|
262
|
-
export const jwtValidateController: Handler = (req, res) => {
|
|
263
|
-
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
264
|
-
|
|
265
|
-
if (!token) {
|
|
266
|
-
return res.status(401).json({ error: "No token provided" });
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const { publicKey } = getJwtFromEnv();
|
|
271
|
-
const decoded = verifyEncrypted(token, publicKey);
|
|
272
|
-
|
|
273
|
-
if (!decoded) {
|
|
274
|
-
return res.status(401).json({ error: "Invalid token" });
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
res.json({ valid: true, payload: decoded });
|
|
278
|
-
} catch (error) {
|
|
279
|
-
return res.status(500).json({ error: "JWT not configured" });
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
301
|
`;
|
|
283
302
|
}
|
|
284
|
-
return `import { Handler } from "sprint-es";
|
|
285
|
-
import { signEncrypted,
|
|
303
|
+
return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
|
|
304
|
+
import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
|
|
286
305
|
|
|
287
|
-
export const adminController = (req, res) => {
|
|
306
|
+
export const adminController = (req: SprintRequest, res: SprintResponse) => {
|
|
288
307
|
res.json({
|
|
289
308
|
message: "Admin Dashboard",
|
|
290
309
|
status: "ok"
|
|
291
310
|
});
|
|
292
311
|
};
|
|
293
312
|
|
|
294
|
-
export const adminUsersController = (req, res) => {
|
|
313
|
+
export const adminUsersController = (req: SprintRequest, res: SprintResponse) => {
|
|
295
314
|
res.json({
|
|
296
315
|
users: [
|
|
297
316
|
{ id: 1, name: "John Doe", role: "admin" },
|
|
@@ -300,13 +319,9 @@ export const adminUsersController = (req, res) => {
|
|
|
300
319
|
});
|
|
301
320
|
};
|
|
302
321
|
|
|
303
|
-
export const jwtGenerateController = (req, res) => {
|
|
322
|
+
export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) => {
|
|
304
323
|
const { userId, role } = req.body || {};
|
|
305
324
|
|
|
306
|
-
if (!userId) {
|
|
307
|
-
return res.status(400).json({ error: "userId is required" });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
325
|
try {
|
|
311
326
|
const { privateKey } = getJwtFromEnv();
|
|
312
327
|
const payload = { userId, role: role || "user" };
|
|
@@ -316,28 +331,8 @@ export const jwtGenerateController = (req, res) => {
|
|
|
316
331
|
return res.status(500).json({ error: "JWT not configured" });
|
|
317
332
|
}
|
|
318
333
|
};
|
|
319
|
-
|
|
320
|
-
export const jwtValidateController = (req, res) => {
|
|
321
|
-
const token = req.sprint?.getAuthorization() || req.headers.authorization;
|
|
322
|
-
|
|
323
|
-
if (!token) {
|
|
324
|
-
return res.status(401).json({ error: "No token provided" });
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const { publicKey } = getJwtFromEnv();
|
|
329
|
-
const decoded = verifyEncrypted(token, publicKey);
|
|
330
|
-
|
|
331
|
-
if (!decoded) {
|
|
332
|
-
return res.status(401).json({ error: "Invalid token" });
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
res.json({ valid: true, payload: decoded });
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return res.status(500).json({ error: "JWT not configured" });
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
334
|
`;
|
|
335
|
+
|
|
341
336
|
}
|
|
342
337
|
|
|
343
338
|
export function getHomeSchema(language: string) {
|
|
@@ -366,6 +361,13 @@ export const adminSchema = defineRouteSchema({
|
|
|
366
361
|
email: z.string().email().optional()
|
|
367
362
|
})
|
|
368
363
|
});
|
|
364
|
+
|
|
365
|
+
export const jwtGenerateSchema = defineRouteSchema({
|
|
366
|
+
body: z.object({
|
|
367
|
+
userId: z.string().min(1),
|
|
368
|
+
role: z.string().optional()
|
|
369
|
+
})
|
|
370
|
+
});
|
|
369
371
|
`;
|
|
370
372
|
}
|
|
371
373
|
return `import { z, defineRouteSchema } from "sprint-es/schemas";
|
|
@@ -379,6 +381,13 @@ export const adminSchema = defineRouteSchema({
|
|
|
379
381
|
email: z.string().email().optional()
|
|
380
382
|
})
|
|
381
383
|
});
|
|
384
|
+
|
|
385
|
+
export const jwtGenerateSchema = defineRouteSchema({
|
|
386
|
+
body: z.object({
|
|
387
|
+
userId: z.string().min(1),
|
|
388
|
+
role: z.string().optional()
|
|
389
|
+
})
|
|
390
|
+
});
|
|
382
391
|
`;
|
|
383
392
|
}
|
|
384
393
|
|
|
@@ -611,7 +620,7 @@ initTelemetry({
|
|
|
611
620
|
}
|
|
612
621
|
|
|
613
622
|
export function getEnvExample(telemetry: string) {
|
|
614
|
-
let env = `PORT=
|
|
623
|
+
let env = `PORT=5000
|
|
615
624
|
|
|
616
625
|
# Development: npm run dev (NODE_ENV=development)
|
|
617
626
|
# Production: npm start (NODE_ENV=production)
|
|
@@ -632,12 +641,17 @@ DISCORD_WEBHOOK_URL=
|
|
|
632
641
|
return env;
|
|
633
642
|
}
|
|
634
643
|
|
|
644
|
+
function envKey(key: any): string {
|
|
645
|
+
return key;
|
|
646
|
+
}
|
|
647
|
+
|
|
635
648
|
export function getEnvDevelopment(telemetry: string) {
|
|
636
|
-
const
|
|
649
|
+
const keys = generateJWTKeys();
|
|
637
650
|
let env = `NODE_ENV=development
|
|
638
|
-
PORT=
|
|
639
|
-
JWT_PUBLIC_KEY
|
|
640
|
-
JWT_PRIVATE_KEY
|
|
651
|
+
PORT=5000
|
|
652
|
+
JWT_PUBLIC_KEY='${keys.publicKey}'
|
|
653
|
+
JWT_PRIVATE_KEY='${keys.privateKey}'
|
|
654
|
+
JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
|
|
641
655
|
`;
|
|
642
656
|
|
|
643
657
|
if (telemetry === "sentry" || telemetry === "glitchtip") {
|
|
@@ -656,11 +670,12 @@ DISCORD_WEBHOOK_URL=
|
|
|
656
670
|
}
|
|
657
671
|
|
|
658
672
|
export function getEnvProduction(telemetry: string) {
|
|
659
|
-
const
|
|
673
|
+
const keys = generateJWTKeys();
|
|
660
674
|
let env = `NODE_ENV=production
|
|
661
|
-
PORT=
|
|
662
|
-
JWT_PUBLIC_KEY
|
|
663
|
-
JWT_PRIVATE_KEY
|
|
675
|
+
PORT=5000
|
|
676
|
+
JWT_PUBLIC_KEY='${keys.publicKey}'
|
|
677
|
+
JWT_PRIVATE_KEY='${keys.privateKey}'
|
|
678
|
+
JWT_ENCRYPTION_SECRET='${crypto.randomBytes(32).toString("hex")}'
|
|
664
679
|
`;
|
|
665
680
|
|
|
666
681
|
if (telemetry === "sentry" || telemetry === "glitchtip") {
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
2
|
+
import { existsSync, writeFileSync } from "fs";
|
|
3
|
+
import { mkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
import color from "picocolors";
|
|
5
6
|
import * as p from "@clack/prompts";
|
|
6
7
|
import { validateProjectName } from "./validators.js";
|
|
7
8
|
import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob } from "./generators.js";
|
|
@@ -15,11 +16,18 @@ export interface CLIOptions {
|
|
|
15
16
|
skipPrompts?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export async function writeFile(path: string, content: string, options?: any) {
|
|
20
|
+
if (typeof content === "string") content = content.trimEnd();
|
|
21
|
+
await fsWriteFile(path, content, options);
|
|
22
|
+
};
|
|
23
|
+
|
|
18
24
|
export async function runCLI(args: string[]) {
|
|
19
25
|
const options = parseArgs(args);
|
|
20
26
|
|
|
21
27
|
p.intro("Sprint — Quickly API Framework");
|
|
22
28
|
|
|
29
|
+
p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
|
|
30
|
+
|
|
23
31
|
const config = await p.group(
|
|
24
32
|
{
|
|
25
33
|
projectName: () =>
|
|
@@ -73,13 +81,14 @@ export async function runCLI(args: string[]) {
|
|
|
73
81
|
s.stop("Project created");
|
|
74
82
|
|
|
75
83
|
const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
|
|
84
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
76
85
|
|
|
77
86
|
if (installDeps) {
|
|
78
87
|
const s2 = p.spinner();
|
|
79
88
|
s2.start("Installing dependencies");
|
|
80
89
|
try {
|
|
81
90
|
await new Promise<void>((resolve, reject) => {
|
|
82
|
-
const child = spawn(
|
|
91
|
+
const child = spawn(npmCmd, ["install"], {
|
|
83
92
|
cwd: targetDir,
|
|
84
93
|
stdio: "pipe"
|
|
85
94
|
});
|
|
@@ -87,6 +96,7 @@ export async function runCLI(args: string[]) {
|
|
|
87
96
|
if (code === 0) resolve();
|
|
88
97
|
else reject(new Error(`npm install exited with code ${code}`));
|
|
89
98
|
});
|
|
99
|
+
child.on("error", reject);
|
|
90
100
|
});
|
|
91
101
|
s2.stop("Dependencies installed");
|
|
92
102
|
} catch {
|