create-sprint 0.0.7 → 0.0.32

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.
@@ -1,13 +1,12 @@
1
1
  export function getTypeScriptPackageJson(name, telemetry) {
2
2
  const deps = {
3
- "sprint-es": "^0.0.24",
3
+ "sprint-es": "^0.0.29",
4
4
  dotenv: "^17.0.0",
5
5
  };
6
6
  const devDeps = {
7
7
  "@types/node": "^22.0.0",
8
8
  "tsx": "^4.19.0",
9
9
  typescript: "^5.6.0",
10
- vite: "^6.0.0",
11
10
  };
12
11
  if (telemetry === "sentry" || telemetry === "glitchtip") {
13
12
  deps["@sentry/node"] = "^8.0.0";
@@ -21,9 +20,9 @@ export function getTypeScriptPackageJson(name, telemetry) {
21
20
  description: "Sprint API",
22
21
  main: "dist/index.js",
23
22
  scripts: {
24
- build: "vite build",
25
- start: "NODE_ENV=production node dist/index.js",
26
- dev: "NODE_ENV=development tsx src/index.ts",
23
+ build: "sprint-es build",
24
+ start: "sprint-es start",
25
+ dev: "sprint-es dev",
27
26
  },
28
27
  dependencies: deps,
29
28
  devDependencies: devDeps,
@@ -31,7 +30,7 @@ export function getTypeScriptPackageJson(name, telemetry) {
31
30
  }
32
31
  export function getJavaScriptPackageJson(name, telemetry) {
33
32
  const deps = {
34
- "sprint-es": "^0.0.24",
33
+ "sprint-es": "^0.0.29",
35
34
  dotenv: "^17.0.0",
36
35
  };
37
36
  if (telemetry === "sentry" || telemetry === "glitchtip") {
@@ -47,8 +46,9 @@ export function getJavaScriptPackageJson(name, telemetry) {
47
46
  main: "src/index.js",
48
47
  type: "module",
49
48
  scripts: {
50
- start: "NODE_ENV=production node src/index.js",
51
- dev: "NODE_ENV=development node --watch src/index.js",
49
+ build: "sprint-es build",
50
+ start: "sprint-es start",
51
+ dev: "sprint-es dev",
52
52
  },
53
53
  dependencies: deps,
54
54
  };
@@ -71,6 +71,10 @@ export function getTsConfig() {
71
71
  declarationMap: true,
72
72
  sourceMap: true,
73
73
  tabWidth: 4,
74
+ baseUrl: ".",
75
+ paths: {
76
+ "@/*": ["./src/*"]
77
+ }
74
78
  },
75
79
  include: ["src/**/*"],
76
80
  exclude: ["node_modules", "dist"],
@@ -104,43 +108,35 @@ export default defineConfig({
104
108
  export function getMainFile(language) {
105
109
  if (language === "typescript") {
106
110
  return `import Sprint from "sprint-es";
107
- import { config } from "./sprint.config";
108
111
 
109
- const app = new Sprint(config);
112
+ const app = new Sprint();
110
113
  `;
111
114
  }
112
115
  return `import Sprint from "sprint-es";
113
- import { config } from "./sprint.config.js";
114
116
 
115
- const app = new Sprint(config);
117
+ const app = new Sprint();
116
118
  `;
117
119
  }
118
120
  export function getHomeRoute(language) {
119
121
  if (language === "typescript") {
120
122
  return `import { Router } from "sprint-es";
123
+ import { homeSchema } from "@/schemas/home";
124
+ import { homeController } from "@/controllers/home";
121
125
 
122
126
  const router = Router();
123
127
 
124
- router.get("/", (req, res) => {
125
- res.json({
126
- message: "Hello World",
127
- status: "ok"
128
- });
129
- });
128
+ router.get("/", homeSchema, homeController);
130
129
 
131
130
  export default router;
132
131
  `;
133
132
  }
134
133
  return `import { Router } from "sprint-es";
134
+ import { homeSchema } from "../schemas/home.js";
135
+ import { homeController } from "../controllers/home.js";
135
136
 
136
137
  const router = Router();
137
138
 
138
- router.get("/", (req, res) => {
139
- res.json({
140
- message: "Hello World",
141
- status: "ok"
142
- });
143
- });
139
+ router.get("/", homeSchema, homeController);
144
140
 
145
141
  export default router;
146
142
  `;
@@ -148,49 +144,129 @@ export default router;
148
144
  export function getAdminRoute(language) {
149
145
  if (language === "typescript") {
150
146
  return `import { Router } from "sprint-es";
147
+ import { adminSchema } from "@/schemas/admin";
148
+ import { adminController, adminUsersController } from "@/controllers/admin";
149
+
150
+ const router = Router();
151
+
152
+ router.get("/", adminSchema, adminController);
153
+ router.get("/users", adminSchema, adminUsersController);
154
+
155
+ export default router;
156
+ `;
157
+ }
158
+ return `import { Router } from "sprint-es";
159
+ import { adminSchema } from "../schemas/admin.js";
160
+ import { adminController, adminUsersController } from "../controllers/admin.js";
151
161
 
152
162
  const router = Router();
153
163
 
154
- router.get("/", (req, res) => {
164
+ router.get("/", adminSchema, adminController);
165
+ router.get("/users", adminSchema, adminUsersController);
166
+
167
+ export default router;
168
+ `;
169
+ }
170
+ export function getHomeController(language) {
171
+ if (language === "typescript") {
172
+ return `import { Handler } from "sprint-es";
173
+
174
+ export const homeController: Handler = (req, res) => {
175
+ res.json({
176
+ message: "Hello World",
177
+ status: "ok"
178
+ });
179
+ };
180
+ `;
181
+ }
182
+ return `import { Handler } from "sprint-es";
183
+
184
+ export const homeController = (req, res) => {
185
+ res.json({
186
+ message: "Hello World",
187
+ status: "ok"
188
+ });
189
+ };
190
+ `;
191
+ }
192
+ export function getAdminController(language) {
193
+ if (language === "typescript") {
194
+ return `import { Handler } from "sprint-es";
195
+
196
+ export const adminController: Handler = (req, res) => {
155
197
  res.json({
156
198
  message: "Admin Dashboard",
157
199
  status: "ok"
158
200
  });
159
- });
201
+ };
160
202
 
161
- router.get("/users", (req, res) => {
203
+ export const adminUsersController: Handler = (req, res) => {
162
204
  res.json({
163
205
  users: [
164
206
  { id: 1, name: "John Doe", role: "admin" },
165
207
  { id: 2, name: "Jane Smith", role: "user" }
166
208
  ]
167
209
  });
168
- });
169
-
170
- export default router;
210
+ };
171
211
  `;
172
212
  }
173
- return `import { Router } from "sprint-es";
213
+ return `import { Handler } from "sprint-es";
174
214
 
175
- const router = Router();
176
-
177
- router.get("/", (req, res) => {
215
+ export const adminController = (req, res) => {
178
216
  res.json({
179
217
  message: "Admin Dashboard",
180
218
  status: "ok"
181
219
  });
182
- });
220
+ };
183
221
 
184
- router.get("/users", (req, res) => {
222
+ export const adminUsersController = (req, res) => {
185
223
  res.json({
186
224
  users: [
187
225
  { id: 1, name: "John Doe", role: "admin" },
188
226
  { id: 2, name: "Jane Smith", role: "user" }
189
227
  ]
190
228
  });
191
- });
229
+ };
230
+ `;
231
+ }
232
+ export function getHomeSchema(language) {
233
+ if (language === "typescript") {
234
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
192
235
 
193
- export default router;
236
+ export const homeSchema = defineRouteSchema({});
237
+ `;
238
+ }
239
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
240
+
241
+ export const homeSchema = defineRouteSchema({});
242
+ `;
243
+ }
244
+ export function getAdminSchema(language) {
245
+ if (language === "typescript") {
246
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
247
+
248
+ export const adminSchema = defineRouteSchema({
249
+ queryParams: z.object({
250
+ id: z.string().uuid()
251
+ }),
252
+ body: z.object({
253
+ name: z.string().min(1),
254
+ email: z.email().optional()
255
+ })
256
+ });
257
+ `;
258
+ }
259
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
260
+
261
+ export const adminSchema = defineRouteSchema({
262
+ queryParams: z.object({
263
+ id: z.string().uuid()
264
+ }),
265
+ body: z.object({
266
+ name: z.string().min(1),
267
+ email: z.email().optional()
268
+ })
269
+ });
194
270
  `;
195
271
  }
196
272
  export function getAuthMiddleware(language) {
@@ -358,6 +434,11 @@ export const config: SprintOptions = {
358
434
  port: process.env.PORT ? parseInt(process.env.PORT) : 3000
359
435
  };
360
436
 
437
+ // Add Vite config here if needed
438
+ // export const vite = {
439
+ // build: { ... }
440
+ // };
441
+
361
442
  `;
362
443
  if (telemetry === "sentry" || telemetry === "glitchtip") {
363
444
  config += `import { initTelemetry } from "sprint-es/telemetry";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { mkdir, writeFile } from "fs/promises";
4
4
  import { join } from "path";
5
5
  import { input, select, confirm } from "@inquirer/prompts";
6
6
  import { validateProjectName } from "./validators.js";
7
- import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getAuthMiddleware, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvExample, getEnvDevelopment, getEnvProduction } from "./generators.js";
7
+ import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction } from "./generators.js";
8
8
  export async function runCLI(args) {
9
9
  const options = parseArgs(args);
10
10
  console.log("\n🚀 Welcome to Sprint - Quickly API Framework\n");
@@ -30,7 +30,7 @@ export async function runCLI(args) {
30
30
  if (options.skipInstall) {
31
31
  installDeps = false;
32
32
  }
33
- else if (!options.skipPrompts) {
33
+ else {
34
34
  installDeps = await confirm({
35
35
  message: "Do you want to install dependencies now?",
36
36
  default: true,
@@ -61,41 +61,39 @@ function parseArgs(args) {
61
61
  const hasJs = args.includes("--js") || args.includes("--javascript");
62
62
  const hasName = args.indexOf("--name");
63
63
  const telemetryArg = args.includes("--telemetry") ? args[args.indexOf("--telemetry") + 1] : null;
64
- if (args.includes("--yes") || args.includes("-y")) {
64
+ if (args.includes("--yes") || args.includes("-y"))
65
65
  options.skipPrompts = true;
66
+ if (!options.skipPrompts) {
67
+ if (hasTs)
68
+ options.language = "typescript";
69
+ else if (hasJs)
70
+ options.language = "javascript";
66
71
  }
67
- if (hasTs) {
72
+ else
68
73
  options.language = "typescript";
69
- }
70
- else if (hasJs) {
71
- options.language = "javascript";
72
- }
73
- if (hasName !== -1 && args[hasName + 1]) {
74
+ if (hasName !== -1 && args[hasName + 1])
74
75
  options.projectName = args[hasName + 1];
75
- }
76
- if (args.includes("--current")) {
76
+ if (args.includes("--current"))
77
77
  options.projectName = ".";
78
- }
79
- if (args.includes("--docker")) {
78
+ if (args.includes("--docker"))
80
79
  options.docker = true;
81
- }
82
- if (args.includes("--no-install")) {
80
+ if (args.includes("--no-install"))
83
81
  options.skipInstall = true;
84
- }
85
- if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) {
82
+ if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg))
86
83
  options.telemetry = telemetryArg;
87
- }
88
84
  return options;
89
85
  }
86
+ ;
90
87
  async function getProjectName() {
91
88
  const name = await input({
92
89
  message: "Enter project name:",
93
90
  validate: (value) => {
94
91
  return validateProjectName(value) || true;
95
- },
92
+ }
96
93
  });
97
94
  return name;
98
95
  }
96
+ ;
99
97
  async function selectLanguage() {
100
98
  const language = await select({
101
99
  message: "Select your preferred language:",
@@ -109,11 +107,12 @@ async function selectLanguage() {
109
107
  name: "JavaScript",
110
108
  value: "javascript",
111
109
  description: "Vanilla JavaScript for simpler projects",
112
- },
113
- ],
110
+ }
111
+ ]
114
112
  });
115
113
  return language;
116
114
  }
115
+ ;
117
116
  async function selectTelemetry() {
118
117
  const telemetry = await select({
119
118
  message: "Select error tracking/telemetry solution:",
@@ -137,11 +136,12 @@ async function selectTelemetry() {
137
136
  name: "Discord Webhook",
138
137
  value: "discord",
139
138
  description: "Send error notifications to Discord channel",
140
- },
141
- ],
139
+ }
140
+ ]
142
141
  });
143
142
  return telemetry;
144
143
  }
144
+ ;
145
145
  async function createProject(projectName, language, telemetryArg, useDockerArg) {
146
146
  const isCurrentDir = projectName === ".";
147
147
  const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), projectName);
@@ -149,13 +149,11 @@ async function createProject(projectName, language, telemetryArg, useDockerArg)
149
149
  console.error(`Error: Directory ${projectName} already exists`);
150
150
  process.exit(1);
151
151
  }
152
- if (!isCurrentDir) {
152
+ if (!isCurrentDir)
153
153
  await mkdir(targetDir, { recursive: true });
154
- }
155
154
  let telemetry = telemetryArg || "none";
156
- if (!telemetryArg) {
155
+ if (!telemetryArg)
157
156
  telemetry = await selectTelemetry();
158
- }
159
157
  let useDocker = useDockerArg || false;
160
158
  if (!useDockerArg) {
161
159
  useDocker = await confirm({
@@ -164,35 +162,37 @@ async function createProject(projectName, language, telemetryArg, useDockerArg)
164
162
  });
165
163
  }
166
164
  let pkgJson;
167
- if (language === "typescript") {
165
+ if (language === "typescript")
168
166
  pkgJson = getTypeScriptPackageJson(projectName, telemetry);
169
- }
170
- else {
167
+ else
171
168
  pkgJson = getJavaScriptPackageJson(projectName, telemetry);
172
- }
173
169
  await writeFile(join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2));
174
170
  if (language === "typescript") {
175
171
  await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
176
172
  await writeFile(join(targetDir, "vite.config.ts"), getViteConfig());
177
173
  await writeFile(join(targetDir, "sprint.config.ts"), getSprintConfigFile(language, telemetry));
178
174
  }
179
- else {
175
+ else
180
176
  await writeFile(join(targetDir, "sprint.config.js"), getSprintConfigFile(language, telemetry));
181
- }
182
177
  const srcDir = join(targetDir, "src");
183
178
  await mkdir(srcDir, { recursive: true });
184
179
  await mkdir(join(srcDir, "middlewares"), { recursive: true });
185
180
  await mkdir(join(srcDir, "routes"), { recursive: true });
186
181
  await mkdir(join(srcDir, "controllers"), { recursive: true });
182
+ await mkdir(join(srcDir, "schemas"), { recursive: true });
187
183
  await writeFile(join(srcDir, "middlewares", ".gitkeep"), "");
188
- await writeFile(join(srcDir, "controllers", ".gitkeep"), "");
189
184
  await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getMainFile(language));
190
185
  await writeFile(join(srcDir, "routes", "home." + (language === "typescript" ? "ts" : "js")), getHomeRoute(language));
191
186
  await writeFile(join(srcDir, "routes", "admin." + (language === "typescript" ? "ts" : "js")), getAdminRoute(language));
187
+ await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
188
+ await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
192
189
  await writeFile(join(srcDir, "middlewares", "auth." + (language === "typescript" ? "ts" : "js")), getAuthMiddleware(language));
193
- await writeFile(join(targetDir, ".env.example"), getEnvExample(telemetry));
194
- await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(telemetry));
195
- await writeFile(join(targetDir, ".env.production"), getEnvProduction(telemetry));
190
+ await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
191
+ await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));
192
+ await writeFile(join(targetDir, ".env.development.example"), getEnvDevelopment(telemetry));
193
+ await writeFile(join(targetDir, ".env.production.example"), getEnvProduction(telemetry));
194
+ await writeFile(join(targetDir, ".env.development"), "");
195
+ await writeFile(join(targetDir, ".env.production"), "");
196
196
  await writeFile(join(targetDir, ".gitignore"), getGitignore());
197
197
  if (useDocker) {
198
198
  await writeFile(join(targetDir, "Dockerfile"), getDockerfile(language));
@@ -200,3 +200,4 @@ async function createProject(projectName, language, telemetryArg, useDockerArg)
200
200
  await writeFile(join(targetDir, ".dockerignore"), getDockerIgnore());
201
201
  }
202
202
  }
203
+ ;
@@ -2,24 +2,19 @@ export function validateProjectName(name) {
2
2
  if (!name.trim())
3
3
  return "Please enter a project name";
4
4
  const n = name.trim();
5
- if (n !== n.toLowerCase()) {
5
+ if (n !== n.toLowerCase())
6
6
  return "Project name must be lowercase";
7
- }
8
- if (n.length > 214) {
7
+ if (n.length > 214)
9
8
  return "Project name must be less than 214 characters";
10
- }
11
- if (n.startsWith("-") || n.startsWith(".")) {
9
+ if (n.startsWith("-") || n.startsWith("."))
12
10
  return "Project name cannot start with - or .";
13
- }
14
- if (/[~!@#$%^&*(){}[\]<>?:]/.test(n)) {
11
+ if (/[~!@#$%^&*(){}[\]<>?:]/.test(n))
15
12
  return "Project name cannot contain special characters (only letters, numbers, - and _)";
16
- }
17
- if (n !== encodeURIComponent(n)) {
13
+ if (n !== encodeURIComponent(n))
18
14
  return "Project name must be URL-safe";
19
- }
20
15
  const reserved = ["node_modules", "favicon.ico"];
21
- if (reserved.includes(n.toLowerCase())) {
16
+ if (reserved.includes(n.toLowerCase()))
22
17
  return `Cannot use "${n}" as project name`;
23
- }
24
18
  return null;
25
19
  }
20
+ ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sprint",
3
- "version": "0.0.7",
3
+ "version": "0.0.32",
4
4
  "description": "Create a new Sprint API project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "build": "tsc && node -e \"const fs=require('fs');const p='dist/cli.js';let c=fs.readFileSync(p,'utf8');if(!c.startsWith('#!')){fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c)}\"",
16
- "prepublishOnly": "npm run build",
16
+ "prepublishOnly": "npm version patch && npm run build",
17
17
  "start": "node dist/cli.js"
18
18
  },
19
19
  "keywords": [
@@ -29,10 +29,11 @@
29
29
  "@inquirer/prompts": "^7.10.1"
30
30
  },
31
31
  "devDependencies": {
32
+ "@types/node": "^25.3.3",
32
33
  "tsx": "^4.19.0",
33
34
  "typescript": "^5.9.3"
34
35
  },
35
36
  "engines": {
36
37
  "node": ">=18.0.0"
37
38
  }
38
- }
39
+ }
package/src/cli.ts CHANGED
@@ -23,4 +23,4 @@ if (args.includes("--help") || args.includes("-h")) {
23
23
  process.exit(0);
24
24
  }
25
25
 
26
- runCLI(args).catch(console.error);
26
+ runCLI(args).catch(console.error);
package/src/generators.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export function getTypeScriptPackageJson(name: string, telemetry: string) {
2
2
  const deps: Record<string, string> = {
3
- "sprint-es": "^0.0.24",
3
+ "sprint-es": "^0.0.29",
4
4
  dotenv: "^17.0.0",
5
5
  };
6
6
 
@@ -8,7 +8,6 @@ export function getTypeScriptPackageJson(name: string, telemetry: string) {
8
8
  "@types/node": "^22.0.0",
9
9
  "tsx": "^4.19.0",
10
10
  typescript: "^5.6.0",
11
- vite: "^6.0.0",
12
11
  };
13
12
 
14
13
  if (telemetry === "sentry" || telemetry === "glitchtip") {
@@ -23,9 +22,9 @@ export function getTypeScriptPackageJson(name: string, telemetry: string) {
23
22
  description: "Sprint API",
24
23
  main: "dist/index.js",
25
24
  scripts: {
26
- build: "vite build",
27
- start: "NODE_ENV=production node dist/index.js",
28
- dev: "NODE_ENV=development tsx src/index.ts",
25
+ build: "sprint-es build",
26
+ start: "sprint-es start",
27
+ dev: "sprint-es dev",
29
28
  },
30
29
  dependencies: deps,
31
30
  devDependencies: devDeps,
@@ -34,7 +33,7 @@ export function getTypeScriptPackageJson(name: string, telemetry: string) {
34
33
 
35
34
  export function getJavaScriptPackageJson(name: string, telemetry: string) {
36
35
  const deps: Record<string, string> = {
37
- "sprint-es": "^0.0.24",
36
+ "sprint-es": "^0.0.29",
38
37
  dotenv: "^17.0.0",
39
38
  };
40
39
 
@@ -51,8 +50,9 @@ export function getJavaScriptPackageJson(name: string, telemetry: string) {
51
50
  main: "src/index.js",
52
51
  type: "module",
53
52
  scripts: {
54
- start: "NODE_ENV=production node src/index.js",
55
- dev: "NODE_ENV=development node --watch src/index.js",
53
+ build: "sprint-es build",
54
+ start: "sprint-es start",
55
+ dev: "sprint-es dev",
56
56
  },
57
57
  dependencies: deps,
58
58
  };
@@ -76,6 +76,10 @@ export function getTsConfig() {
76
76
  declarationMap: true,
77
77
  sourceMap: true,
78
78
  tabWidth: 4,
79
+ baseUrl: ".",
80
+ paths: {
81
+ "@/*": ["./src/*"]
82
+ }
79
83
  },
80
84
  include: ["src/**/*"],
81
85
  exclude: ["node_modules", "dist"],
@@ -111,45 +115,37 @@ export default defineConfig({
111
115
  export function getMainFile(language: string) {
112
116
  if (language === "typescript") {
113
117
  return `import Sprint from "sprint-es";
114
- import { config } from "./sprint.config";
115
118
 
116
- const app = new Sprint(config);
119
+ const app = new Sprint();
117
120
  `;
118
121
  }
119
122
 
120
123
  return `import Sprint from "sprint-es";
121
- import { config } from "./sprint.config.js";
122
124
 
123
- const app = new Sprint(config);
125
+ const app = new Sprint();
124
126
  `;
125
127
  }
126
128
 
127
129
  export function getHomeRoute(language: string) {
128
130
  if (language === "typescript") {
129
131
  return `import { Router } from "sprint-es";
132
+ import { homeSchema } from "@/schemas/home";
133
+ import { homeController } from "@/controllers/home";
130
134
 
131
135
  const router = Router();
132
136
 
133
- router.get("/", (req, res) => {
134
- res.json({
135
- message: "Hello World",
136
- status: "ok"
137
- });
138
- });
137
+ router.get("/", homeSchema, homeController);
139
138
 
140
139
  export default router;
141
140
  `;
142
141
  }
143
142
  return `import { Router } from "sprint-es";
143
+ import { homeSchema } from "../schemas/home.js";
144
+ import { homeController } from "../controllers/home.js";
144
145
 
145
146
  const router = Router();
146
147
 
147
- router.get("/", (req, res) => {
148
- res.json({
149
- message: "Hello World",
150
- status: "ok"
151
- });
152
- });
148
+ router.get("/", homeSchema, homeController);
153
149
 
154
150
  export default router;
155
151
  `;
@@ -158,49 +154,133 @@ export default router;
158
154
  export function getAdminRoute(language: string) {
159
155
  if (language === "typescript") {
160
156
  return `import { Router } from "sprint-es";
157
+ import { adminSchema } from "@/schemas/admin";
158
+ import { adminController, adminUsersController } from "@/controllers/admin";
161
159
 
162
160
  const router = Router();
163
161
 
164
- router.get("/", (req, res) => {
162
+ router.get("/", adminSchema, adminController);
163
+ router.get("/users", adminSchema, adminUsersController);
164
+
165
+ export default router;
166
+ `;
167
+ }
168
+ return `import { Router } from "sprint-es";
169
+ import { adminSchema } from "../schemas/admin.js";
170
+ import { adminController, adminUsersController } from "../controllers/admin.js";
171
+
172
+ const router = Router();
173
+
174
+ router.get("/", adminSchema, adminController);
175
+ router.get("/users", adminSchema, adminUsersController);
176
+
177
+ export default router;
178
+ `;
179
+ }
180
+
181
+ export function getHomeController(language: string) {
182
+ if (language === "typescript") {
183
+ return `import { Handler } from "sprint-es";
184
+
185
+ export const homeController: Handler = (req, res) => {
186
+ res.json({
187
+ message: "Hello World",
188
+ status: "ok"
189
+ });
190
+ };
191
+ `;
192
+ }
193
+ return `import { Handler } from "sprint-es";
194
+
195
+ export const homeController = (req, res) => {
196
+ res.json({
197
+ message: "Hello World",
198
+ status: "ok"
199
+ });
200
+ };
201
+ `;
202
+ }
203
+
204
+ export function getAdminController(language: string) {
205
+ if (language === "typescript") {
206
+ return `import { Handler } from "sprint-es";
207
+
208
+ export const adminController: Handler = (req, res) => {
165
209
  res.json({
166
210
  message: "Admin Dashboard",
167
211
  status: "ok"
168
212
  });
169
- });
213
+ };
170
214
 
171
- router.get("/users", (req, res) => {
215
+ export const adminUsersController: Handler = (req, res) => {
172
216
  res.json({
173
217
  users: [
174
218
  { id: 1, name: "John Doe", role: "admin" },
175
219
  { id: 2, name: "Jane Smith", role: "user" }
176
220
  ]
177
221
  });
178
- });
179
-
180
- export default router;
222
+ };
181
223
  `;
182
224
  }
183
- return `import { Router } from "sprint-es";
225
+ return `import { Handler } from "sprint-es";
184
226
 
185
- const router = Router();
186
-
187
- router.get("/", (req, res) => {
227
+ export const adminController = (req, res) => {
188
228
  res.json({
189
229
  message: "Admin Dashboard",
190
230
  status: "ok"
191
231
  });
192
- });
232
+ };
193
233
 
194
- router.get("/users", (req, res) => {
234
+ export const adminUsersController = (req, res) => {
195
235
  res.json({
196
236
  users: [
197
237
  { id: 1, name: "John Doe", role: "admin" },
198
238
  { id: 2, name: "Jane Smith", role: "user" }
199
239
  ]
200
240
  });
201
- });
241
+ };
242
+ `;
243
+ }
202
244
 
203
- export default router;
245
+ export function getHomeSchema(language: string) {
246
+ if (language === "typescript") {
247
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
248
+
249
+ export const homeSchema = defineRouteSchema({});
250
+ `;
251
+ }
252
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
253
+
254
+ export const homeSchema = defineRouteSchema({});
255
+ `;
256
+ }
257
+
258
+ export function getAdminSchema(language: string) {
259
+ if (language === "typescript") {
260
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
261
+
262
+ export const adminSchema = defineRouteSchema({
263
+ queryParams: z.object({
264
+ id: z.string().uuid()
265
+ }),
266
+ body: z.object({
267
+ name: z.string().min(1),
268
+ email: z.email().optional()
269
+ })
270
+ });
271
+ `;
272
+ }
273
+ return `import { z, defineRouteSchema } from "sprint-es/schemas";
274
+
275
+ export const adminSchema = defineRouteSchema({
276
+ queryParams: z.object({
277
+ id: z.string().uuid()
278
+ }),
279
+ body: z.object({
280
+ name: z.string().min(1),
281
+ email: z.email().optional()
282
+ })
283
+ });
204
284
  `;
205
285
  }
206
286
 
@@ -374,6 +454,11 @@ export const config: SprintOptions = {
374
454
  port: process.env.PORT ? parseInt(process.env.PORT) : 3000
375
455
  };
376
456
 
457
+ // Add Vite config here if needed
458
+ // export const vite = {
459
+ // build: { ... }
460
+ // };
461
+
377
462
  `;
378
463
 
379
464
  if (telemetry === "sentry" || telemetry === "glitchtip") {
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ import { mkdir, writeFile } from "fs/promises";
4
4
  import { join } from "path";
5
5
  import { input, select, confirm } from "@inquirer/prompts";
6
6
  import { validateProjectName } from "./validators.js";
7
- import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getAuthMiddleware, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvExample, getEnvDevelopment, getEnvProduction } from "./generators.js";
7
+ import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getViteConfig, getMainFile, getHomeRoute, getAdminRoute, getHomeController, getAdminController, getAuthMiddleware, getHomeSchema, getAdminSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction } from "./generators.js";
8
8
 
9
9
  export interface CLIOptions {
10
10
  projectName?: string;
@@ -48,7 +48,7 @@ export async function runCLI(args: string[]) {
48
48
  let installDeps = true;
49
49
  if (options.skipInstall) {
50
50
  installDeps = false;
51
- } else if (!options.skipPrompts) {
51
+ } else {
52
52
  installDeps = await confirm({
53
53
  message: "Do you want to install dependencies now?",
54
54
  default: true,
@@ -83,49 +83,36 @@ function parseArgs(args: string[]): CLIOptions {
83
83
  const hasName = args.indexOf("--name");
84
84
  const telemetryArg = args.includes("--telemetry") ? args[args.indexOf("--telemetry") + 1] : null;
85
85
 
86
- if (args.includes("--yes") || args.includes("-y")) {
87
- options.skipPrompts = true;
88
- }
89
-
90
- if (hasTs) {
91
- options.language = "typescript";
92
- } else if (hasJs) {
93
- options.language = "javascript";
94
- }
95
-
96
- if (hasName !== -1 && args[hasName + 1]) {
97
- options.projectName = args[hasName + 1];
98
- }
99
-
100
- if (args.includes("--current")) {
101
- options.projectName = ".";
102
- }
86
+ if (args.includes("--yes") || args.includes("-y")) options.skipPrompts = true;
103
87
 
104
- if (args.includes("--docker")) {
105
- options.docker = true;
106
- }
88
+ if (!options.skipPrompts) {
89
+ if (hasTs) options.language = "typescript";
90
+ else if (hasJs) options.language = "javascript";
91
+ } else options.language = "typescript";
92
+
93
+ if (hasName !== -1 && args[hasName + 1]) options.projectName = args[hasName + 1];
107
94
 
108
- if (args.includes("--no-install")) {
109
- options.skipInstall = true;
110
- }
95
+ if (args.includes("--current")) options.projectName = ".";
111
96
 
112
- if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) {
113
- options.telemetry = telemetryArg as CLIOptions["telemetry"];
114
- }
97
+ if (args.includes("--docker")) options.docker = true;
98
+
99
+ if (args.includes("--no-install")) options.skipInstall = true;
115
100
 
101
+ if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) options.telemetry = telemetryArg as CLIOptions["telemetry"];
102
+
116
103
  return options;
117
- }
104
+ };
118
105
 
119
106
  async function getProjectName(): Promise<string> {
120
107
  const name = await input({
121
108
  message: "Enter project name:",
122
109
  validate: (value) => {
123
110
  return validateProjectName(value) || true;
124
- },
111
+ }
125
112
  });
126
113
 
127
114
  return name;
128
- }
115
+ };
129
116
 
130
117
  async function selectLanguage(): Promise<"typescript" | "javascript"> {
131
118
  const language = await select({
@@ -140,12 +127,12 @@ async function selectLanguage(): Promise<"typescript" | "javascript"> {
140
127
  name: "JavaScript",
141
128
  value: "javascript",
142
129
  description: "Vanilla JavaScript for simpler projects",
143
- },
144
- ],
130
+ }
131
+ ]
145
132
  });
146
133
 
147
134
  return language as "typescript" | "javascript";
148
- }
135
+ };
149
136
 
150
137
  async function selectTelemetry(): Promise<"none" | "sentry" | "glitchtip" | "discord"> {
151
138
  const telemetry = await select({
@@ -170,12 +157,12 @@ async function selectTelemetry(): Promise<"none" | "sentry" | "glitchtip" | "dis
170
157
  name: "Discord Webhook",
171
158
  value: "discord",
172
159
  description: "Send error notifications to Discord channel",
173
- },
174
- ],
160
+ }
161
+ ]
175
162
  });
176
163
 
177
164
  return telemetry as "none" | "sentry" | "glitchtip" | "discord";
178
- }
165
+ };
179
166
 
180
167
  async function createProject(
181
168
  projectName: string,
@@ -191,14 +178,10 @@ async function createProject(
191
178
  process.exit(1);
192
179
  }
193
180
 
194
- if (!isCurrentDir) {
195
- await mkdir(targetDir, { recursive: true });
196
- }
181
+ if (!isCurrentDir) await mkdir(targetDir, { recursive: true });
197
182
 
198
183
  let telemetry = telemetryArg || "none";
199
- if (!telemetryArg) {
200
- telemetry = await selectTelemetry();
201
- }
184
+ if (!telemetryArg) telemetry = await selectTelemetry();
202
185
 
203
186
  let useDocker = useDockerArg || false;
204
187
  if (!useDockerArg) {
@@ -209,42 +192,45 @@ async function createProject(
209
192
  }
210
193
 
211
194
  let pkgJson;
212
- if (language === "typescript") {
213
- pkgJson = getTypeScriptPackageJson(projectName, telemetry);
214
- } else {
215
- pkgJson = getJavaScriptPackageJson(projectName, telemetry);
216
- }
217
-
195
+ if (language === "typescript") pkgJson = getTypeScriptPackageJson(projectName, telemetry);
196
+ else pkgJson = getJavaScriptPackageJson(projectName, telemetry);
197
+
218
198
  await writeFile(join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2));
219
199
 
220
200
  if (language === "typescript") {
221
201
  await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
222
202
  await writeFile(join(targetDir, "vite.config.ts"), getViteConfig());
223
203
  await writeFile(join(targetDir, "sprint.config.ts"), getSprintConfigFile(language, telemetry));
224
- } else {
225
- await writeFile(join(targetDir, "sprint.config.js"), getSprintConfigFile(language, telemetry));
226
- }
227
-
204
+ } else await writeFile(join(targetDir, "sprint.config.js"), getSprintConfigFile(language, telemetry));
205
+
228
206
  const srcDir = join(targetDir, "src");
229
207
  await mkdir(srcDir, { recursive: true });
230
208
 
231
209
  await mkdir(join(srcDir, "middlewares"), { recursive: true });
232
210
  await mkdir(join(srcDir, "routes"), { recursive: true });
233
211
  await mkdir(join(srcDir, "controllers"), { recursive: true });
212
+ await mkdir(join(srcDir, "schemas"), { recursive: true });
234
213
 
235
214
  await writeFile(join(srcDir, "middlewares", ".gitkeep"), "");
236
- await writeFile(join(srcDir, "controllers", ".gitkeep"), "");
237
215
 
238
216
  await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getMainFile(language));
239
217
 
240
218
  await writeFile(join(srcDir, "routes", "home." + (language === "typescript" ? "ts" : "js")), getHomeRoute(language));
241
219
  await writeFile(join(srcDir, "routes", "admin." + (language === "typescript" ? "ts" : "js")), getAdminRoute(language));
242
220
 
221
+ await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
222
+ await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
223
+
243
224
  await writeFile(join(srcDir, "middlewares", "auth." + (language === "typescript" ? "ts" : "js")), getAuthMiddleware(language));
244
225
 
245
- await writeFile(join(targetDir, ".env.example"), getEnvExample(telemetry));
246
- await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(telemetry));
247
- await writeFile(join(targetDir, ".env.production"), getEnvProduction(telemetry));
226
+ await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
227
+ await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));
228
+
229
+ await writeFile(join(targetDir, ".env.development.example"), getEnvDevelopment(telemetry));
230
+ await writeFile(join(targetDir, ".env.production.example"), getEnvProduction(telemetry));
231
+
232
+ await writeFile(join(targetDir, ".env.development"), "");
233
+ await writeFile(join(targetDir, ".env.production"), "");
248
234
 
249
235
  await writeFile(join(targetDir, ".gitignore"), getGitignore());
250
236
 
@@ -253,4 +239,4 @@ async function createProject(
253
239
  await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(language));
254
240
  await writeFile(join(targetDir, ".dockerignore"), getDockerIgnore());
255
241
  }
256
- }
242
+ };
package/src/validators.ts CHANGED
@@ -3,30 +3,19 @@ export function validateProjectName(name: string): string | null {
3
3
 
4
4
  const n = name.trim();
5
5
 
6
- if (n !== n.toLowerCase()) {
7
- return "Project name must be lowercase";
8
- }
6
+ if (n !== n.toLowerCase()) return "Project name must be lowercase";
9
7
 
10
- if (n.length > 214) {
11
- return "Project name must be less than 214 characters";
12
- }
8
+ if (n.length > 214) return "Project name must be less than 214 characters";
13
9
 
14
- if (n.startsWith("-") || n.startsWith(".")) {
15
- return "Project name cannot start with - or .";
16
- }
10
+ if (n.startsWith("-") || n.startsWith(".")) return "Project name cannot start with - or .";
17
11
 
18
- if (/[~!@#$%^&*(){}[\]<>?:]/.test(n)) {
19
- return "Project name cannot contain special characters (only letters, numbers, - and _)";
20
- }
21
-
22
- if (n !== encodeURIComponent(n)) {
23
- return "Project name must be URL-safe";
24
- }
12
+ if (/[~!@#$%^&*(){}[\]<>?:]/.test(n)) return "Project name cannot contain special characters (only letters, numbers, - and _)";
13
+
14
+ if (n !== encodeURIComponent(n)) return "Project name must be URL-safe";
15
+
25
16
 
26
17
  const reserved = ["node_modules", "favicon.ico"];
27
- if (reserved.includes(n.toLowerCase())) {
28
- return `Cannot use "${n}" as project name`;
29
- }
30
-
18
+ if (reserved.includes(n.toLowerCase())) return `Cannot use "${n}" as project name`;
19
+
31
20
  return null;
32
- }
21
+ };