create-sprint 0.0.2 → 0.0.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.
Files changed (3) hide show
  1. package/bin/cli.js +489 -92
  2. package/bin/cli.ts +637 -219
  3. package/package.json +35 -35
package/bin/cli.js CHANGED
@@ -1,31 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { select, input } from "@inquirer/prompts";
2
+ import { select, input, confirm } from "@inquirer/prompts";
3
3
  import { mkdir, writeFile } from "fs/promises";
4
4
  import { existsSync } from "fs";
5
5
  import { join } from "path";
6
+ import { execSync } from "child_process";
6
7
  const args = process.argv.slice(2);
7
- const initIndex = args.indexOf("init");
8
- const isInitCommand = initIndex !== -1;
9
- if (!isInitCommand) {
10
- console.log("\nšŸš€ Sprint - Quickly API Framework\n");
11
- console.log("Usage: sprint init [options]");
12
- console.log("\nOptions:");
13
- console.log(" --ts, --typescript Create TypeScript project");
14
- console.log(" --js, --javascript Create JavaScript project");
15
- console.log(" --name <name> Project name");
16
- console.log(" --current Create in current directory");
17
- console.log("\nExamples:");
18
- console.log(" sprint init Interactive mode");
19
- console.log(" sprint init --ts Create TypeScript project");
20
- console.log(" sprint init --ts --name my-api");
21
- console.log(" sprint init --js --current\n");
22
- process.exit(0);
23
- }
8
+ const hasHelp = args.includes("--help") || args.includes("-h");
24
9
  const hasTs = args.includes("--ts") || args.includes("--typescript");
25
10
  const hasJs = args.includes("--js") || args.includes("--javascript");
26
11
  const hasName = args.indexOf("--name");
27
12
  const projectNameArg = hasName !== -1 ? args[hasName + 1] : null;
28
13
  const useCurrentDirArg = args.includes("--current");
14
+ const skipInstallArg = args.includes("--no-install");
15
+ const telemetryArg = args.includes("--telemetry") ? args[args.indexOf("--telemetry") + 1] : null;
16
+ const useDockerArg = args.includes("--docker");
17
+ if (hasHelp) {
18
+ console.log("\nšŸš€ Sprint - Quickly API Framework\n");
19
+ console.log("Usage: sprint [options]");
20
+ console.log("\nOptions:");
21
+ console.log(" --ts, --typescript Create TypeScript project");
22
+ console.log(" --js, --javascript Create JavaScript project");
23
+ console.log(" --name <name> Project name (use '.' for current directory)");
24
+ console.log(" --no-install Skip automatic dependency installation");
25
+ console.log(" --telemetry <type> Telemetry: none, sentry, glitchtip, discord");
26
+ console.log(" --docker Add Docker support");
27
+ console.log(" --help, -h Show this help message");
28
+ console.log("\nExamples:");
29
+ console.log(" sprint Interactive mode");
30
+ console.log(" sprint --ts Create TypeScript project");
31
+ console.log(" sprint --ts --name my-api");
32
+ console.log(" sprint --js --name . --telemetry sentry --docker\n");
33
+ process.exit(0);
34
+ }
29
35
  async function main() {
30
36
  console.log("\nšŸš€ Welcome to Sprint - Quickly API Framework\n");
31
37
  let projectName;
@@ -51,29 +57,33 @@ async function main() {
51
57
  console.log(`\nāœ… Creating Sprint project: ${projectName === "." ? "current directory" : projectName} with ${language === "typescript" ? "TypeScript" : "JavaScript"}\n`);
52
58
  await createProject(projectName, language);
53
59
  console.log("\nāœ… Project created successfully!");
60
+ let installDeps = true;
61
+ if (!skipInstallArg) {
62
+ installDeps = await confirm({
63
+ message: "Do you want to install dependencies now?",
64
+ default: true,
65
+ });
66
+ }
67
+ if (installDeps) {
68
+ console.log("\nšŸ“¦ Installing dependencies...\n");
69
+ const targetDir = projectName === "." ? process.cwd() : join(process.cwd(), projectName);
70
+ try {
71
+ execSync("npm install", { cwd: targetDir, stdio: "inherit" });
72
+ console.log("\nāœ… Dependencies installed successfully!");
73
+ }
74
+ catch (error) {
75
+ console.error("\nāŒ Error installing dependencies. Please run 'npm install' manually.");
76
+ }
77
+ }
54
78
  console.log("\nšŸ“¦ Next steps:");
55
79
  const cdCmd = projectName === "." ? "" : `cd ${projectName} && `;
56
- console.log(` ${cdCmd}npm install`);
80
+ if (!installDeps) {
81
+ console.log(` ${cdCmd}npm install`);
82
+ }
57
83
  console.log(` ${cdCmd}npm run dev`);
58
84
  console.log("\n");
59
85
  }
60
86
  async function getProjectName() {
61
- const useCurrentDir = await select({
62
- message: "Where would you like to create your project?",
63
- choices: [
64
- {
65
- name: "Current directory",
66
- value: "current",
67
- },
68
- {
69
- name: "New directory",
70
- value: "new",
71
- },
72
- ],
73
- });
74
- if (useCurrentDir === "current") {
75
- return ".";
76
- }
77
87
  const name = await input({
78
88
  message: "Enter project name:",
79
89
  validate: (value) => {
@@ -102,6 +112,34 @@ async function selectLanguage() {
102
112
  });
103
113
  return language;
104
114
  }
115
+ async function selectTelemetry() {
116
+ const telemetry = await select({
117
+ message: "Select error tracking/telemetry solution:",
118
+ choices: [
119
+ {
120
+ name: "None",
121
+ value: "none",
122
+ description: "No error tracking integration",
123
+ },
124
+ {
125
+ name: "Sentry",
126
+ value: "sentry",
127
+ description: "Full-featured error tracking (free tier available)",
128
+ },
129
+ {
130
+ name: "GlitchTip",
131
+ value: "glitchtip",
132
+ description: "Simple error tracking, can be self-hosted",
133
+ },
134
+ {
135
+ name: "Discord Webhook",
136
+ value: "discord",
137
+ description: "Send error notifications to Discord channel",
138
+ },
139
+ ],
140
+ });
141
+ return telemetry;
142
+ }
105
143
  async function createProject(projectName, language) {
106
144
  const isCurrentDir = projectName === ".";
107
145
  const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), projectName);
@@ -112,44 +150,104 @@ async function createProject(projectName, language) {
112
150
  if (!isCurrentDir) {
113
151
  await mkdir(targetDir, { recursive: true });
114
152
  }
115
- const pkgJson = language === "typescript"
116
- ? getTypeScriptPackageJson(projectName)
117
- : getJavaScriptPackageJson(projectName);
153
+ let telemetry = "none";
154
+ if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) {
155
+ telemetry = telemetryArg;
156
+ }
157
+ else if (!hasTs && !hasJs) {
158
+ telemetry = await selectTelemetry();
159
+ }
160
+ let useDocker = useDockerArg;
161
+ if (!useDocker && !hasTs && !hasJs) {
162
+ useDocker = await confirm({
163
+ message: "Do you want to add Docker support?",
164
+ default: false,
165
+ });
166
+ }
167
+ let pkgJson;
168
+ if (language === "typescript") {
169
+ pkgJson = getTypeScriptPackageJson(projectName, telemetry);
170
+ }
171
+ else {
172
+ pkgJson = getJavaScriptPackageJson(projectName, telemetry);
173
+ }
118
174
  await writeFile(join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2));
119
175
  if (language === "typescript") {
120
176
  await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
177
+ await writeFile(join(targetDir, "vite.config.ts"), getViteConfig());
121
178
  }
122
179
  const srcDir = join(targetDir, "src");
123
180
  await mkdir(srcDir, { recursive: true });
124
- await writeFile(join(srcDir, "index." + (language === "typescript" ? "ts" : "js")), getMainFile(language));
181
+ await mkdir(join(srcDir, "middlewares"), { recursive: true });
182
+ await mkdir(join(srcDir, "routes"), { recursive: true });
183
+ await mkdir(join(srcDir, "controllers"), { recursive: true });
184
+ await writeFile(join(srcDir, "middlewares", ".gitkeep"), "");
185
+ await writeFile(join(srcDir, "controllers", ".gitkeep"), "");
186
+ await writeFile(join(srcDir, "index." + (language === "typescript" ? "ts" : "js")), getMainFile(language, telemetry));
187
+ await writeFile(join(srcDir, "routes", "home." + (language === "typescript" ? "ts" : "js")), getHomeRoute(language));
125
188
  if (language === "typescript") {
126
- await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getAppFile(language));
189
+ await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getAppFile(language, telemetry));
190
+ }
191
+ await writeFile(join(targetDir, ".env.example"), getEnvExample(telemetry));
192
+ await writeFile(join(targetDir, ".env"), "");
193
+ if (telemetry !== "none") {
194
+ await writeFile(join(targetDir, "sprint.config.js"), getSprintConfig(telemetry));
195
+ }
196
+ await writeFile(join(targetDir, ".gitignore"), getGitignore(language));
197
+ if (useDocker) {
198
+ await writeFile(join(targetDir, "Dockerfile"), getDockerfile(language));
199
+ await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(language));
200
+ await writeFile(join(targetDir, ".dockerignore"), getDockerIgnore());
127
201
  }
128
- await writeFile(join(targetDir, ".env.example"), getEnvExample());
129
202
  }
130
- function getTypeScriptPackageJson(name) {
203
+ function getTypeScriptPackageJson(name, telemetry) {
204
+ const deps = {
205
+ "sprint-es": "^0.0.24",
206
+ dotenv: "^17.0.0",
207
+ };
208
+ const devDeps = {
209
+ "@types/node": "^22.0.0",
210
+ "tsx": "^4.19.0",
211
+ typescript: "^5.6.0",
212
+ vite: "^6.0.0",
213
+ };
214
+ if (telemetry === "sentry") {
215
+ deps["@sentry/node"] = "^8.0.0";
216
+ }
217
+ else if (telemetry === "glitchtip") {
218
+ deps["@sentry/node"] = "^8.0.0";
219
+ }
220
+ else if (telemetry === "discord") {
221
+ deps["axios"] = "^1.6.0";
222
+ }
131
223
  return {
132
224
  name: name === "." ? "sprint-app" : name,
133
225
  version: "0.0.1",
134
226
  description: "Sprint API",
135
227
  main: "dist/index.js",
136
228
  scripts: {
137
- build: "tsc",
138
- start: "node dist/index.js",
139
- dev: "tsx watch src/index.ts",
140
- },
141
- dependencies: {
142
- "sprint-es": "^0.0.24",
143
- dotenv: "^17.0.0",
144
- },
145
- devDependencies: {
146
- "@types/node": "^22.0.0",
147
- "tsx": "^4.19.0",
148
- typescript: "^5.6.0",
229
+ build: "vite build",
230
+ start: "node dist/index.js --prod",
231
+ dev: "vite --dev",
149
232
  },
233
+ dependencies: deps,
234
+ devDependencies: devDeps,
150
235
  };
151
236
  }
152
- function getJavaScriptPackageJson(name) {
237
+ function getJavaScriptPackageJson(name, telemetry) {
238
+ const deps = {
239
+ "sprint-es": "^0.0.24",
240
+ dotenv: "^17.0.0",
241
+ };
242
+ if (telemetry === "sentry") {
243
+ deps["@sentry/node"] = "^8.0.0";
244
+ }
245
+ else if (telemetry === "glitchtip") {
246
+ deps["@sentry/node"] = "^8.0.0";
247
+ }
248
+ else if (telemetry === "discord") {
249
+ deps["axios"] = "^1.6.0";
250
+ }
153
251
  return {
154
252
  name: name === "." ? "sprint-app" : name,
155
253
  version: "0.0.1",
@@ -157,13 +255,10 @@ function getJavaScriptPackageJson(name) {
157
255
  main: "src/index.js",
158
256
  type: "module",
159
257
  scripts: {
160
- start: "node src/index.js",
161
- dev: "node --watch src/index.js",
162
- },
163
- dependencies: {
164
- "sprint-es": "^0.0.24",
165
- dotenv: "^17.0.0",
258
+ start: "node src/index.js --prod",
259
+ dev: "node --watch src/index.js --dev",
166
260
  },
261
+ dependencies: deps,
167
262
  };
168
263
  }
169
264
  function getTsConfig() {
@@ -183,71 +278,373 @@ function getTsConfig() {
183
278
  declaration: true,
184
279
  declarationMap: true,
185
280
  sourceMap: true,
281
+ tabWidth: 4,
186
282
  },
187
283
  include: ["src/**/*"],
188
284
  exclude: ["node_modules", "dist"],
189
285
  }, null, 2);
190
286
  }
191
- function getMainFile(language) {
287
+ function getViteConfig() {
288
+ return `import { defineConfig } from "vite";
289
+ import { resolve } from "path";
290
+
291
+ export default defineConfig({
292
+ build: {
293
+ lib: {
294
+ entry: resolve(__dirname, "src/index.ts"),
295
+ formats: ["es"],
296
+ fileName: "index",
297
+ },
298
+ outDir: "dist",
299
+ rollupOptions: {
300
+ external: ["sprint-es", "express", "cors", "morgan", "serve-favicon", "dotenv"],
301
+ },
302
+ target: "ES2020",
303
+ },
304
+ resolve: {
305
+ alias: {
306
+ "@": resolve(__dirname, "src"),
307
+ },
308
+ },
309
+ });
310
+ `;
311
+ }
312
+ function getMainFile(language, telemetry) {
313
+ const hasTelemetry = telemetry !== "none";
192
314
  if (language === "typescript") {
193
- return `import Sprint from 'sprint-es';
194
- import dotenv from 'dotenv';
315
+ let code = `import Sprint from "sprint-es";
316
+ import dotenv from "dotenv";
317
+ import homeRouter from "./routes/home";
195
318
 
196
319
  dotenv.config();
197
320
 
198
- const app = new Sprint({
199
- port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
200
- favicon: process.env.FAVICON || undefined,
201
- bodyParser: {
202
- limit: '10mb'
203
- }
321
+ `;
322
+ if (hasTelemetry) {
323
+ code += getTelemetryImport(language, telemetry);
324
+ }
325
+ code += `const app = new Sprint({
326
+ port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
327
+ favicon: process.env.FAVICON || undefined,
328
+ bodyParser: {
329
+ limit: "10mb"
330
+ }
204
331
  });
205
332
 
206
- app.get('/', (req, res) => {
207
- res.send('Hello from Sprint!');
208
- });
333
+ app.use(homeRouter);
209
334
 
210
335
  app.listen();
211
336
  `;
337
+ return code;
212
338
  }
213
- return `import Sprint from 'sprint-es';
214
- import dotenv from 'dotenv';
339
+ let code = `import Sprint from "sprint-es";
340
+ import dotenv from "dotenv";
341
+ import homeRouter from "./routes/home";
215
342
 
216
343
  dotenv.config();
217
344
 
218
- const app = new Sprint({
219
- port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
220
- favicon: process.env.FAVICON || undefined,
221
- bodyParser: {
222
- limit: '10mb'
223
- }
345
+ `;
346
+ if (hasTelemetry) {
347
+ code += getTelemetryImport(language, telemetry);
348
+ }
349
+ code += `const app = new Sprint({
350
+ port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
351
+ favicon: process.env.FAVICON || undefined,
352
+ bodyParser: {
353
+ limit: "10mb"
354
+ }
224
355
  });
225
356
 
226
- app.get('/', (req, res) => {
227
- res.send('Hello from Sprint!');
228
- });
357
+ app.use(homeRouter);
229
358
 
230
359
  app.listen();
231
360
  `;
361
+ return code;
362
+ }
363
+ function getTelemetryImport(language, telemetry) {
364
+ if (telemetry === "sentry" || telemetry === "glitchtip") {
365
+ return `import * as Sentry from "@sentry/node";
366
+
367
+ Sentry.init({
368
+ dsn: process.env.SENTRY_DSN,
369
+ integrations: [
370
+ Sentry.httpIntegration(),
371
+ ],
372
+ });
373
+
374
+ `;
375
+ }
376
+ else if (telemetry === "discord") {
377
+ return `import axios from "axios";
378
+
379
+ const discordWebhook = process.env.DISCORD_WEBHOOK_URL;
380
+
381
+ async function sendDiscordError(error, req) {
382
+ if (!discordWebhook) return;
383
+
384
+ const embed = {
385
+ title: "🚨 Error in Sprint API",
386
+ description: \`\${error.message}\`,
387
+ color: 16711680,
388
+ fields: [
389
+ {
390
+ name: "Method",
391
+ value: req.method,
392
+ inline: true,
393
+ },
394
+ {
395
+ name: "URL",
396
+ value: req.url,
397
+ inline: true,
398
+ },
399
+ {
400
+ name: "Stack",
401
+ value: error.stack?.split("\\n").slice(0, 5).join("\\n") || "No stack trace",
402
+ },
403
+ ],
404
+ timestamp: new Date().toISOString(),
405
+ };
406
+
407
+ try {
408
+ await axios.post(discordWebhook, {
409
+ embeds: [embed],
410
+ });
411
+ } catch (e) {
412
+ console.error("Failed to send Discord webhook:", e.message);
413
+ }
414
+ }
415
+
416
+ `;
417
+ }
418
+ return "";
232
419
  }
233
- function getAppFile(language) {
420
+ function getAppFile(language, telemetry) {
234
421
  if (language === "typescript") {
235
- return `import type { SprintOptions } from 'sprint-es';
422
+ return `import type { SprintOptions } from "sprint-es";
236
423
 
237
424
  export const options: SprintOptions = {
238
- port: 3000,
239
- bodyParser: {
240
- limit: '10mb'
241
- }
425
+ port: 3000,
426
+ bodyParser: {
427
+ limit: "10mb"
428
+ }
242
429
  };
243
430
  `;
244
431
  }
245
432
  return "";
246
433
  }
247
- function getEnvExample() {
248
- return `PORT=3000
434
+ function getEnvExample(telemetry) {
435
+ let env = `PORT=3000
249
436
  FAVICON=./public/favicon.ico
250
437
  NODE_ENV=development
438
+ `;
439
+ if (telemetry === "sentry" || telemetry === "glitchtip") {
440
+ env += `
441
+ # Sentry / GlitchTip (use GlitchTip DSN for self-hosted)
442
+ SENTRY_DSN=
443
+ `;
444
+ }
445
+ else if (telemetry === "discord") {
446
+ env += `
447
+ # Discord Webhook URL for error notifications
448
+ DISCORD_WEBHOOK_URL=
449
+ `;
450
+ }
451
+ return env;
452
+ }
453
+ function getSprintConfig(telemetry) {
454
+ if (telemetry === "discord") {
455
+ return `export default {
456
+ errorHandler: async (error, req, res) => {
457
+ console.error(error);
458
+
459
+ if (process.env.DISCORD_WEBHOOK_URL && res.socket.server) {
460
+ const axios = await import("axios");
461
+ const embed = {
462
+ title: "🚨 Error in Sprint API",
463
+ description: error.message,
464
+ color: 16711680,
465
+ fields: [
466
+ {
467
+ name: "Method",
468
+ value: req.method,
469
+ inline: true,
470
+ },
471
+ {
472
+ name: "URL",
473
+ value: req.url,
474
+ inline: true,
475
+ },
476
+ {
477
+ name: "Stack",
478
+ value: error.stack?.split("\\n").slice(0, 5).join("\\n") || "No stack",
479
+ },
480
+ ],
481
+ timestamp: new Date().toISOString(),
482
+ };
483
+
484
+ try {
485
+ await axios.default.post(process.env.DISCORD_WEBHOOK_URL, {
486
+ embeds: [embed],
487
+ });
488
+ } catch (e) {
489
+ console.error("Discord webhook error:", e.message);
490
+ }
491
+ }
492
+
493
+ res.status(500).json({ error: "Internal server error" });
494
+ }
495
+ };
496
+ `;
497
+ }
498
+ return "";
499
+ }
500
+ function getHomeRoute(language) {
501
+ if (language === "typescript") {
502
+ return `import { Router } from "sprint-es";
503
+
504
+ const router = Router();
505
+
506
+ router.get("/", (req, res) => {
507
+ res.json({
508
+ message: "Hello World",
509
+ status: "ok"
510
+ });
511
+ });
512
+
513
+ export default router;
514
+ `;
515
+ }
516
+ return `import { Router } from "sprint-es";
517
+
518
+ const router = Router();
519
+
520
+ router.get("/", (req, res) => {
521
+ res.json({
522
+ message: "Hello World",
523
+ status: "ok"
524
+ });
525
+ });
526
+
527
+ export default router;
528
+ `;
529
+ }
530
+ function getDockerfile(language) {
531
+ if (language === "typescript") {
532
+ return `FROM node:20-alpine
533
+
534
+ WORKDIR /app
535
+
536
+ COPY package*.json ./
537
+
538
+ RUN npm install
539
+
540
+ COPY . .
541
+
542
+ RUN npm run build
543
+
544
+ EXPOSE 3000
545
+
546
+ CMD ["npm", "start"]
547
+ `;
548
+ }
549
+ return `FROM node:20-alpine
550
+
551
+ WORKDIR /app
552
+
553
+ COPY package*.json ./
554
+
555
+ RUN npm install
556
+
557
+ COPY . .
558
+
559
+ EXPOSE 3000
560
+
561
+ CMD ["npm", "start"]
562
+ `;
563
+ }
564
+ function getDockerCompose(language) {
565
+ if (language === "typescript") {
566
+ return `version: "3.8"
567
+
568
+ services:
569
+ app:
570
+ build: .
571
+ ports:
572
+ - "3000:3000"
573
+ environment:
574
+ - NODE_ENV=production
575
+ - PORT=3000
576
+ restart: unless-stopped
577
+ `;
578
+ }
579
+ return `version: "3.8"
580
+
581
+ services:
582
+ app:
583
+ build: .
584
+ ports:
585
+ - "3000:3000"
586
+ environment:
587
+ - NODE_ENV=production
588
+ - PORT=3000
589
+ restart: unless-stopped
590
+ `;
591
+ }
592
+ function getGitignore(language) {
593
+ return `# Dependencies
594
+ node_modules/
595
+ npm-debug.log*
596
+ yarn-debug.log*
597
+ yarn-error.log*
598
+
599
+ # Build
600
+ dist/
601
+ build/
602
+ *.tsbuildinfo
603
+
604
+ # Environment
605
+ .env
606
+ .env.local
607
+ .env.*.local
608
+
609
+ # IDE
610
+ .vscode/
611
+ .idea/
612
+ *.swp
613
+ *.swo
614
+ *~
615
+
616
+ # OS
617
+ .DS_Store
618
+ Thumbs.db
619
+
620
+ # Logs
621
+ logs/
622
+ *.log
623
+
624
+ # Test
625
+ coverage/
626
+
627
+ # Temporary
628
+ tmp/
629
+ temp/
630
+ `;
631
+ }
632
+ function getDockerIgnore() {
633
+ return `node_modules
634
+ npm-debug.log
635
+ .env
636
+ .env.local
637
+ .git
638
+ .gitignore
639
+ README.md
640
+ dist
641
+ build
642
+ coverage
643
+ .vscode
644
+ .idea
645
+ *.log
646
+ tmp
647
+ temp
251
648
  `;
252
649
  }
253
650
  main().catch(console.error);