create-sprint 0.0.54 → 0.0.56

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.
@@ -9,7 +9,7 @@ export function generateJWTKeys() {
9
9
  }
10
10
  export function getTypeScriptPackageJson(name, telemetry) {
11
11
  const deps = {
12
- "sprint-es": "^0.0.38"
12
+ "sprint-es": "^0.0.48"
13
13
  };
14
14
  const devDeps = {
15
15
  "@types/node": "^22.0.0",
@@ -39,7 +39,7 @@ export function getTypeScriptPackageJson(name, telemetry) {
39
39
  }
40
40
  export function getJavaScriptPackageJson(name, telemetry) {
41
41
  const deps = {
42
- "sprint-es": "^0.0.38"
42
+ "sprint-es": "^0.0.48"
43
43
  };
44
44
  if (telemetry === "sentry" || telemetry === "glitchtip") {
45
45
  deps["@sentry/node"] = "^8.0.0";
@@ -117,13 +117,35 @@ export default defineConfig({
117
117
  export function getMainFile(language) {
118
118
  if (language === "typescript") {
119
119
  return `import Sprint from "sprint-es";
120
+ import homeRouter from "./routes/home";
121
+ import adminRouter from "./routes/admin";
122
+ import authInternalMiddleware from "./middlewares/auth.internal";
123
+ import authUserMiddleware from "./middlewares/auth.user";
120
124
 
121
125
  const app = new Sprint();
126
+
127
+ app.use(authUserMiddleware);
128
+ app.use(authInternalMiddleware);
129
+ app.route("/", homeRouter);
130
+ app.route("/admin", adminRouter);
131
+
132
+ app.listen();
122
133
  `;
123
134
  }
124
135
  return `import Sprint from "sprint-es";
136
+ import homeRouter from "./routes/home.js";
137
+ import adminRouter from "./routes/admin.js";
138
+ import authInternalMiddleware from "./middlewares/auth.internal.js";
139
+ import authUserMiddleware from "./middlewares/auth.user.js";
125
140
 
126
141
  const app = new Sprint();
142
+
143
+ app.use(authUserMiddleware);
144
+ app.use(authInternalMiddleware);
145
+ app.route("/", homeRouter);
146
+ app.route("/admin", adminRouter);
147
+
148
+ app.listen();
127
149
  `;
128
150
  }
129
151
  export function getHomeRoute(language) {
@@ -135,7 +157,7 @@ import { homeController, jwtValidateController } from "@/controllers/home";
135
157
  const router = Router();
136
158
 
137
159
  router.get("/", homeSchema, homeController);
138
- router.post("/jwt/validate", jwtValidateController);
160
+ router.post("/me", jwtValidateController);
139
161
 
140
162
  export default router;
141
163
  `;
@@ -147,7 +169,7 @@ import { homeController, jwtValidateController } from "../controllers/home.js";
147
169
  const router = Router();
148
170
 
149
171
  router.get("/", homeSchema, homeController);
150
- router.post("/jwt/validate", jwtValidateController);
172
+ router.post("/me", jwtValidateController);
151
173
 
152
174
  export default router;
153
175
  `;
@@ -193,24 +215,7 @@ export const homeController: Handler = (req: SprintRequest, res: SprintResponse)
193
215
  };
194
216
 
195
217
  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, encryptionSecret } = getJwtFromEnv();
204
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
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
- }
218
+ return res.json(req.custom.user);
214
219
  };
215
220
  `;
216
221
  }
@@ -225,24 +230,7 @@ export const homeController = (req: SprintRequest, res: SprintResponse) => {
225
230
  };
226
231
 
227
232
  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, encryptionSecret } = getJwtFromEnv();
236
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
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
- }
233
+ return res.json(req.custom.user);
246
234
  };
247
235
  `;
248
236
  }
@@ -271,7 +259,7 @@ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintRe
271
259
  const { userId, role } = req.body || {};
272
260
 
273
261
  try {
274
- const { privateKey } = getJwtFromEnv();
262
+ const { privateKey, encryptionSecret } = getJwtFromEnv();
275
263
  const payload = { userId, role: role || "user" };
276
264
  const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
277
265
  res.json({ token });
@@ -281,7 +269,8 @@ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintRe
281
269
  };
282
270
  `;
283
271
  }
284
- return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
272
+ else {
273
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
285
274
  import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
286
275
 
287
276
  export const adminController = (req: SprintRequest, res: SprintResponse) => {
@@ -313,6 +302,7 @@ export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) =
313
302
  }
314
303
  };
315
304
  `;
305
+ }
316
306
  }
317
307
  export function getHomeSchema(language) {
318
308
  if (language === "typescript") {
@@ -426,7 +416,7 @@ export default defineMiddleware({
426
416
 
427
417
  const token = auth.replace("Bearer ", "");
428
418
 
429
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);cd .
419
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
430
420
 
431
421
  if (!decoded) return res.status(403).json({ error: "Invalid token" });
432
422
 
@@ -499,8 +489,7 @@ CMD ["npm", "start"]
499
489
  `;
500
490
  }
501
491
  export function getDockerCompose(language) {
502
- return `version: "3.8"
503
-
492
+ return `
504
493
  services:
505
494
  app:
506
495
  build: .
@@ -508,7 +497,7 @@ services:
508
497
  - "3000:3000"
509
498
  environment:
510
499
  - NODE_ENV=production
511
- - PORT=3000
500
+ - PORT=5000
512
501
  restart: unless-stopped
513
502
  `;
514
503
  }
@@ -600,7 +589,7 @@ initTelemetry({
600
589
 
601
590
  initTelemetry({
602
591
  provider: "discord",
603
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || ""
592
+ webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
604
593
  });
605
594
  `;
606
595
  }
@@ -627,7 +616,7 @@ import { initTelemetry } from "sprint-es/telemetry";
627
616
 
628
617
  initTelemetry({
629
618
  provider: "discord",
630
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || ""
619
+ webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
631
620
  });
632
621
  `;
633
622
  }
@@ -648,7 +637,7 @@ SENTRY_DSN=
648
637
  else if (telemetry === "discord") {
649
638
  env += `
650
639
  # Discord Webhook URL for error notifications
651
- DISCORD_WEBHOOK_URL=
640
+ DISCORD_TELEMETRY_WEBHOOK_URL=
652
641
  `;
653
642
  }
654
643
  return env;
@@ -673,7 +662,7 @@ SENTRY_DSN=
673
662
  else if (telemetry === "discord") {
674
663
  env += `
675
664
  # Discord Webhook URL
676
- DISCORD_WEBHOOK_URL=
665
+ DISCORD_TELEMETRY_WEBHOOK_URL=
677
666
  `;
678
667
  }
679
668
  return env;
@@ -695,7 +684,7 @@ SENTRY_DSN=
695
684
  else if (telemetry === "discord") {
696
685
  env += `
697
686
  # Discord Webhook URL
698
- DISCORD_WEBHOOK_URL=
687
+ DISCORD_TELEMETRY_WEBHOOK_URL=
699
688
  `;
700
689
  }
701
690
  return env;
package/dist/index.js CHANGED
@@ -16,41 +16,58 @@ export async function runCLI(args) {
16
16
  const options = parseArgs(args);
17
17
  p.intro("Sprint — Quickly API Framework");
18
18
  p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
19
- const config = await p.group({
20
- projectName: () => p.text({
21
- message: "Project name:",
22
- placeholder: "my-api",
23
- validate: (v) => validateProjectName(v) || undefined,
24
- }),
25
- language: () => p.select({
26
- message: "Language:",
27
- options: [
28
- { value: "typescript", label: "TypeScript", hint: "recommended" },
29
- { value: "javascript", label: "JavaScript" },
30
- ],
31
- }),
32
- telemetry: () => p.select({
33
- message: "Error tracking:",
34
- options: [
35
- { value: "none", label: "None" },
36
- { value: "sentry", label: "Sentry", hint: "free tier available" },
37
- { value: "glitchtip", label: "GlitchTip", hint: "self-hostable" },
38
- { value: "discord", label: "Discord Webhook", hint: "sends to a channel" },
39
- ],
40
- }),
41
- docker: () => p.confirm({ message: "Add Docker support?", initialValue: false }),
42
- }, {
43
- onCancel: () => {
44
- p.cancel("Cancelled.");
45
- process.exit(0);
46
- },
47
- });
19
+ let config;
20
+ if (options.skipPrompts) {
21
+ config = {
22
+ projectName: options.projectName || "sprint-app",
23
+ language: options.language || "typescript",
24
+ telemetry: options.telemetry || "none",
25
+ docker: options.docker || false,
26
+ };
27
+ }
28
+ else {
29
+ config = await p.group({
30
+ projectName: () => p.text({
31
+ message: "Project name:",
32
+ placeholder: "my-api",
33
+ validate: (v) => validateProjectName(v) || undefined,
34
+ }),
35
+ language: () => p.select({
36
+ message: "Language:",
37
+ options: [
38
+ { value: "typescript", label: "TypeScript", hint: "recommended" },
39
+ { value: "javascript", label: "JavaScript" },
40
+ ],
41
+ }),
42
+ telemetry: () => p.select({
43
+ message: "Error tracking:",
44
+ options: [
45
+ { value: "none", label: "None" },
46
+ { value: "sentry", label: "Sentry", hint: "free tier available" },
47
+ { value: "glitchtip", label: "GlitchTip", hint: "self-hostable" },
48
+ { value: "discord", label: "Discord Webhook", hint: "sends to a channel" },
49
+ ],
50
+ }),
51
+ docker: () => p.confirm({ message: "Add Docker support?", initialValue: false }),
52
+ }, {
53
+ onCancel: () => {
54
+ p.cancel("Cancelled.");
55
+ process.exit(0);
56
+ },
57
+ });
58
+ }
48
59
  const targetDir = config.projectName === "." ? process.cwd() : join(process.cwd(), config.projectName);
49
60
  const s = p.spinner();
50
61
  s.start("Creating project");
51
62
  await createProject(config.projectName, config.language, config.telemetry, config.docker);
52
63
  s.stop("Project created");
53
- const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
64
+ let installDeps = true;
65
+ if (options.skipInstall) {
66
+ installDeps = false;
67
+ }
68
+ else if (!options.skipPrompts) {
69
+ installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
70
+ }
54
71
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
55
72
  if (installDeps) {
56
73
  const s2 = p.spinner();
@@ -59,7 +76,7 @@ export async function runCLI(args) {
59
76
  await new Promise((resolve, reject) => {
60
77
  const child = spawn(npmCmd, ["install"], {
61
78
  cwd: targetDir,
62
- stdio: "pipe"
79
+ stdio: "inherit"
63
80
  });
64
81
  child.on("close", (code) => {
65
82
  if (code === 0)
@@ -67,12 +84,16 @@ export async function runCLI(args) {
67
84
  else
68
85
  reject(new Error(`npm install exited with code ${code}`));
69
86
  });
70
- child.on("error", reject);
87
+ child.on("error", (err) => {
88
+ p.cancel(`Failed to run npm install: ${err.message}`);
89
+ reject(err);
90
+ });
71
91
  });
72
92
  s2.stop("Dependencies installed");
73
93
  }
74
- catch {
94
+ catch (err) {
75
95
  s2.stop("Install failed — run npm install manually");
96
+ console.error(err);
76
97
  }
77
98
  }
78
99
  const cdCmd = config.projectName === "." ? "" : `cd ${config.projectName} && `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sprint",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Create a new Sprint API project",
5
5
  "type": "module",
6
6
  "bin": {
package/src/generators.ts CHANGED
@@ -16,7 +16,7 @@ export function generateJWTKeys(): JWTKeys {
16
16
 
17
17
  export function getTypeScriptPackageJson(name: string, telemetry: string) {
18
18
  const deps: Record<string, string> = {
19
- "sprint-es": "^0.0.38"
19
+ "sprint-es": "^0.0.48"
20
20
  };
21
21
 
22
22
  const devDeps: Record<string, string> = {
@@ -49,7 +49,7 @@ export function getTypeScriptPackageJson(name: string, telemetry: string) {
49
49
 
50
50
  export function getJavaScriptPackageJson(name: string, telemetry: string) {
51
51
  const deps: Record<string, string> = {
52
- "sprint-es": "^0.0.38"
52
+ "sprint-es": "^0.0.48"
53
53
  };
54
54
 
55
55
  if (telemetry === "sentry" || telemetry === "glitchtip") {
@@ -131,14 +131,36 @@ export default defineConfig({
131
131
  export function getMainFile(language: string) {
132
132
  if (language === "typescript") {
133
133
  return `import Sprint from "sprint-es";
134
+ import homeRouter from "./routes/home";
135
+ import adminRouter from "./routes/admin";
136
+ import authInternalMiddleware from "./middlewares/auth.internal";
137
+ import authUserMiddleware from "./middlewares/auth.user";
134
138
 
135
139
  const app = new Sprint();
140
+
141
+ app.use(authUserMiddleware);
142
+ app.use(authInternalMiddleware);
143
+ app.route("/", homeRouter);
144
+ app.route("/admin", adminRouter);
145
+
146
+ app.listen();
136
147
  `;
137
148
  }
138
149
 
139
150
  return `import Sprint from "sprint-es";
151
+ import homeRouter from "./routes/home.js";
152
+ import adminRouter from "./routes/admin.js";
153
+ import authInternalMiddleware from "./middlewares/auth.internal.js";
154
+ import authUserMiddleware from "./middlewares/auth.user.js";
140
155
 
141
156
  const app = new Sprint();
157
+
158
+ app.use(authUserMiddleware);
159
+ app.use(authInternalMiddleware);
160
+ app.route("/", homeRouter);
161
+ app.route("/admin", adminRouter);
162
+
163
+ app.listen();
142
164
  `;
143
165
  }
144
166
 
@@ -151,7 +173,7 @@ import { homeController, jwtValidateController } from "@/controllers/home";
151
173
  const router = Router();
152
174
 
153
175
  router.get("/", homeSchema, homeController);
154
- router.post("/jwt/validate", jwtValidateController);
176
+ router.post("/me", jwtValidateController);
155
177
 
156
178
  export default router;
157
179
  `;
@@ -163,7 +185,7 @@ import { homeController, jwtValidateController } from "../controllers/home.js";
163
185
  const router = Router();
164
186
 
165
187
  router.get("/", homeSchema, homeController);
166
- router.post("/jwt/validate", jwtValidateController);
188
+ router.post("/me", jwtValidateController);
167
189
 
168
190
  export default router;
169
191
  `;
@@ -211,24 +233,7 @@ export const homeController: Handler = (req: SprintRequest, res: SprintResponse)
211
233
  };
212
234
 
213
235
  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, encryptionSecret } = getJwtFromEnv();
222
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
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
- }
236
+ return res.json(req.custom.user);
232
237
  };
233
238
  `;
234
239
  }
@@ -243,24 +248,7 @@ export const homeController = (req: SprintRequest, res: SprintResponse) => {
243
248
  };
244
249
 
245
250
  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, encryptionSecret } = getJwtFromEnv();
254
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
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
- }
251
+ return res.json(req.custom.user);
264
252
  };
265
253
  `;
266
254
  }
@@ -290,7 +278,7 @@ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintRe
290
278
  const { userId, role } = req.body || {};
291
279
 
292
280
  try {
293
- const { privateKey } = getJwtFromEnv();
281
+ const { privateKey, encryptionSecret } = getJwtFromEnv();
294
282
  const payload = { userId, role: role || "user" };
295
283
  const token = signEncrypted(payload, privateKey, encryptionSecret, { expiresIn: "1h" });
296
284
  res.json({ token });
@@ -299,8 +287,8 @@ export const jwtGenerateController: Handler = (req: SprintRequest, res: SprintRe
299
287
  }
300
288
  };
301
289
  `;
302
- }
303
- return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
290
+ } else {
291
+ return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
304
292
  import { signEncrypted, getJwtFromEnv } from "sprint-es/jwt";
305
293
 
306
294
  export const adminController = (req: SprintRequest, res: SprintResponse) => {
@@ -332,7 +320,7 @@ export const jwtGenerateController = (req: SprintRequest, res: SprintResponse) =
332
320
  }
333
321
  };
334
322
  `;
335
-
323
+ }
336
324
  }
337
325
 
338
326
  export function getHomeSchema(language: string) {
@@ -450,7 +438,7 @@ export default defineMiddleware({
450
438
 
451
439
  const token = auth.replace("Bearer ", "");
452
440
 
453
- const decoded = verifyEncrypted(token, publicKey, encryptionSecret);cd .
441
+ const decoded = verifyEncrypted(token, publicKey, encryptionSecret);
454
442
 
455
443
  if (!decoded) return res.status(403).json({ error: "Invalid token" });
456
444
 
@@ -525,8 +513,7 @@ CMD ["npm", "start"]
525
513
  }
526
514
 
527
515
  export function getDockerCompose(language: string) {
528
- return `version: "3.8"
529
-
516
+ return `
530
517
  services:
531
518
  app:
532
519
  build: .
@@ -534,7 +521,7 @@ services:
534
521
  - "3000:3000"
535
522
  environment:
536
523
  - NODE_ENV=production
537
- - PORT=3000
524
+ - PORT=5000
538
525
  restart: unless-stopped
539
526
  `;
540
527
  }
@@ -629,7 +616,7 @@ initTelemetry({
629
616
 
630
617
  initTelemetry({
631
618
  provider: "discord",
632
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || ""
619
+ webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
633
620
  });
634
621
  `;
635
622
  }
@@ -658,7 +645,7 @@ import { initTelemetry } from "sprint-es/telemetry";
658
645
 
659
646
  initTelemetry({
660
647
  provider: "discord",
661
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || ""
648
+ webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
662
649
  });
663
650
  `;
664
651
  }
@@ -681,7 +668,7 @@ SENTRY_DSN=
681
668
  } else if (telemetry === "discord") {
682
669
  env += `
683
670
  # Discord Webhook URL for error notifications
684
- DISCORD_WEBHOOK_URL=
671
+ DISCORD_TELEMETRY_WEBHOOK_URL=
685
672
  `;
686
673
  }
687
674
 
@@ -709,7 +696,7 @@ SENTRY_DSN=
709
696
  } else if (telemetry === "discord") {
710
697
  env += `
711
698
  # Discord Webhook URL
712
- DISCORD_WEBHOOK_URL=
699
+ DISCORD_TELEMETRY_WEBHOOK_URL=
713
700
  `;
714
701
  }
715
702
 
@@ -733,7 +720,7 @@ SENTRY_DSN=
733
720
  } else if (telemetry === "discord") {
734
721
  env += `
735
722
  # Discord Webhook URL
736
- DISCORD_WEBHOOK_URL=
723
+ DISCORD_TELEMETRY_WEBHOOK_URL=
737
724
  `;
738
725
  }
739
726
 
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "child_process";
2
- import { existsSync, writeFileSync } from "fs";
2
+ import { existsSync } from "fs";
3
3
  import { mkdir, writeFile as fsWriteFile } from "fs/promises";
4
4
  import { join } from "path";
5
5
  import color from "picocolors";
@@ -28,59 +28,81 @@ export async function runCLI(args: string[]) {
28
28
 
29
29
  p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
30
30
 
31
- const config = await p.group(
32
- {
33
- projectName: () =>
34
- p.text({
35
- message: "Project name:",
36
- placeholder: "my-api",
37
- validate: (v) => validateProjectName(v) || undefined,
38
- }),
39
-
40
- language: () =>
41
- p.select({
42
- message: "Language:",
43
- options: [
44
- { value: "typescript", label: "TypeScript", hint: "recommended" },
45
- { value: "javascript", label: "JavaScript" },
46
- ],
47
- }),
48
-
49
- telemetry: () =>
50
- p.select({
51
- message: "Error tracking:",
52
- options: [
53
- { value: "none", label: "None" },
54
- { value: "sentry", label: "Sentry", hint: "free tier available" },
55
- { value: "glitchtip", label: "GlitchTip", hint: "self-hostable" },
56
- { value: "discord", label: "Discord Webhook", hint: "sends to a channel" },
57
- ],
58
- }),
59
-
60
- docker: () =>
61
- p.confirm({ message: "Add Docker support?", initialValue: false }),
62
- },
63
- {
64
- onCancel: () => {
65
- p.cancel("Cancelled.");
66
- process.exit(0);
31
+ let config: {
32
+ projectName: string;
33
+ language: "typescript" | "javascript";
34
+ telemetry: string;
35
+ docker: boolean;
36
+ };
37
+
38
+ if (options.skipPrompts) {
39
+ config = {
40
+ projectName: options.projectName || "sprint-app",
41
+ language: options.language || "typescript",
42
+ telemetry: options.telemetry || "none",
43
+ docker: options.docker || false,
44
+ };
45
+ } else {
46
+ config = await p.group(
47
+ {
48
+ projectName: () =>
49
+ p.text({
50
+ message: "Project name:",
51
+ placeholder: "my-api",
52
+ validate: (v) => validateProjectName(v) || undefined,
53
+ }),
54
+
55
+ language: () =>
56
+ p.select({
57
+ message: "Language:",
58
+ options: [
59
+ { value: "typescript", label: "TypeScript", hint: "recommended" },
60
+ { value: "javascript", label: "JavaScript" },
61
+ ],
62
+ }),
63
+
64
+ telemetry: () =>
65
+ p.select({
66
+ message: "Error tracking:",
67
+ options: [
68
+ { value: "none", label: "None" },
69
+ { value: "sentry", label: "Sentry", hint: "free tier available" },
70
+ { value: "glitchtip", label: "GlitchTip", hint: "self-hostable" },
71
+ { value: "discord", label: "Discord Webhook", hint: "sends to a channel" },
72
+ ],
73
+ }),
74
+
75
+ docker: () =>
76
+ p.confirm({ message: "Add Docker support?", initialValue: false }),
67
77
  },
68
- }
69
- );
78
+ {
79
+ onCancel: () => {
80
+ p.cancel("Cancelled.");
81
+ process.exit(0);
82
+ },
83
+ }
84
+ );
85
+ }
70
86
 
71
87
  const targetDir = config.projectName === "." ? process.cwd() : join(process.cwd(), config.projectName);
72
88
 
73
89
  const s = p.spinner();
74
90
  s.start("Creating project");
75
91
  await createProject(
76
- config.projectName as string,
77
- config.language as "typescript" | "javascript",
78
- config.telemetry as string,
79
- config.docker as boolean,
92
+ config.projectName,
93
+ config.language,
94
+ config.telemetry,
95
+ config.docker,
80
96
  );
81
97
  s.stop("Project created");
82
98
 
83
- const installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true });
99
+ let installDeps = true;
100
+ if (options.skipInstall) {
101
+ installDeps = false;
102
+ } else if (!options.skipPrompts) {
103
+ installDeps = await p.confirm({ message: "Install dependencies now?", initialValue: true }) as boolean;
104
+ }
105
+
84
106
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
85
107
 
86
108
  if (installDeps) {
@@ -90,17 +112,21 @@ export async function runCLI(args: string[]) {
90
112
  await new Promise<void>((resolve, reject) => {
91
113
  const child = spawn(npmCmd, ["install"], {
92
114
  cwd: targetDir,
93
- stdio: "pipe"
115
+ stdio: "inherit"
94
116
  });
95
117
  child.on("close", (code) => {
96
118
  if (code === 0) resolve();
97
119
  else reject(new Error(`npm install exited with code ${code}`));
98
120
  });
99
- child.on("error", reject);
121
+ child.on("error", (err) => {
122
+ p.cancel(`Failed to run npm install: ${err.message}`);
123
+ reject(err);
124
+ });
100
125
  });
101
126
  s2.stop("Dependencies installed");
102
- } catch {
127
+ } catch (err) {
103
128
  s2.stop("Install failed — run npm install manually");
129
+ console.error(err);
104
130
  }
105
131
  }
106
132