create-sprint 0.0.138 → 0.0.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import color from "picocolors";
6
6
  import * as p from "@clack/prompts";
7
7
  import { validateProjectName } from "./validators.js";
8
8
  import { getTypeScriptPackageJson, getJavaScriptPackageJson, getTsConfig, getMainFile, getHomeRoute, getAdminRoute, getUploadRoute, getHomeController, getAdminController, getUploadController, getEnvExample, getInternalAuthMiddleware, getUserAuthMiddleware, getHomeSchema, getAdminSchema, getUploadSchema, getDockerfile, getDockerCompose, getGitignore, getDockerIgnore, getSprintConfigFile, getEnvDevelopment, getEnvProduction, getExampleCronJob, getGraphQLFiles } from "./generators.js";
9
+ import { getQueueService, getCacheService, getTrpcRouter, getGrpcServer, getWebSocketScaffold } from "./templates/services.js";
9
10
  export async function writeFile(path, content, options) {
10
11
  if (typeof content === "string")
11
12
  content = content.trim();
@@ -14,7 +15,6 @@ export async function writeFile(path, content, options) {
14
15
  ;
15
16
  export async function runCLI(args) {
16
17
  const options = parseArgs(args);
17
- p.intro("Sprint — Quickly API Framework");
18
18
  p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
19
19
  let config;
20
20
  if (options.skipPrompts) {
@@ -24,11 +24,18 @@ export async function runCLI(args) {
24
24
  telemetry: options.telemetry ?? "none",
25
25
  swagger: options.swagger ?? true,
26
26
  graphql: options.graphql ?? false,
27
- docker: options.docker || false
27
+ docker: options.docker || false,
28
+ queue: options.queue ?? "none",
29
+ cache: options.cache ?? "none",
30
+ websocket: options.websocket ?? false,
31
+ trpc: options.trpc ?? false,
32
+ grpc: options.grpc ?? false,
33
+ csrf: options.csrf ?? false,
34
+ cors: options.cors ?? ""
28
35
  };
29
36
  }
30
37
  else {
31
- config = await p.group({
38
+ const result = await p.group({
32
39
  projectName: () => p.text({
33
40
  message: "Project name:",
34
41
  placeholder: "my-api",
@@ -41,6 +48,11 @@ export async function runCLI(args) {
41
48
  { value: "javascript", label: "JavaScript", hint: "not recommended" }
42
49
  ]
43
50
  }),
51
+ cors: () => p.text({
52
+ message: "CORS allowed origins (comma-separated, blank = deny all, '*' = wildcard):",
53
+ placeholder: "https://app.example.com,http://localhost:3000",
54
+ defaultValue: ""
55
+ }),
44
56
  telemetry: () => p.select({
45
57
  message: "Error tracking:",
46
58
  options: [
@@ -53,8 +65,28 @@ export async function runCLI(args) {
53
65
  { value: "nodemailer", label: "Nodemailer", hint: "sends emails", disabled: true }
54
66
  ]
55
67
  }),
56
- swagger: () => p.confirm({ message: "Add Swagger UI & OpenAPI?", initialValue: true }),
68
+ queue: () => p.select({
69
+ message: "Job queue:",
70
+ options: [
71
+ { value: "none", label: "None" },
72
+ { value: "memory", label: "In-memory", hint: "single process, retries + DLQ" },
73
+ { value: "bullmq", label: "BullMQ + Redis", hint: "distributed, production-grade" }
74
+ ]
75
+ }),
76
+ cache: () => p.select({
77
+ message: "Cache:",
78
+ options: [
79
+ { value: "none", label: "None" },
80
+ { value: "memory", label: "In-memory LRU", hint: "single process" },
81
+ { value: "redis", label: "Redis", hint: "shared across instances" }
82
+ ]
83
+ }),
84
+ websocket: () => p.confirm({ message: "Add WebSocket support?", initialValue: false }),
85
+ trpc: () => p.confirm({ message: "Add tRPC adapter?", initialValue: false }),
86
+ grpc: () => p.confirm({ message: "Add gRPC server?", initialValue: false }),
57
87
  graphql: () => p.confirm({ message: "Add GraphQL support?", initialValue: false }),
88
+ swagger: () => p.confirm({ message: "Add Swagger UI & OpenAPI?", initialValue: true }),
89
+ csrf: () => p.confirm({ message: "Add CSRF middleware (double-submit cookie)?", initialValue: false }),
58
90
  docker: () => p.confirm({ message: "Add Docker support?", initialValue: false })
59
91
  }, {
60
92
  onCancel: () => {
@@ -62,11 +94,12 @@ export async function runCLI(args) {
62
94
  process.exit(0);
63
95
  }
64
96
  });
97
+ config = result;
65
98
  }
66
99
  const targetDir = config.projectName === "." ? process.cwd() : join(process.cwd(), config.projectName);
67
100
  const s = p.spinner();
68
101
  s.start("Creating project");
69
- await createProject(config.projectName, config.language, config.telemetry, config.swagger, config.graphql, config.docker);
102
+ await createProject(config);
70
103
  s.stop("Project created");
71
104
  let installDeps = true;
72
105
  if (options.skipInstall)
@@ -112,6 +145,9 @@ function parseArgs(args) {
112
145
  const hasJs = args.includes("--js") || args.includes("--javascript");
113
146
  const hasName = args.indexOf("--name");
114
147
  const telemetryArg = args.includes("--telemetry") ? args[args.indexOf("--telemetry") + 1] : null;
148
+ const queueArg = args.includes("--queue") ? args[args.indexOf("--queue") + 1] : null;
149
+ const cacheArg = args.includes("--cache") ? args[args.indexOf("--cache") + 1] : null;
150
+ const corsArg = args.includes("--cors") ? args[args.indexOf("--cors") + 1] : null;
115
151
  if (args.includes("--yes") || args.includes("-y"))
116
152
  options.skipPrompts = true;
117
153
  if (!options.skipPrompts) {
@@ -139,36 +175,44 @@ function parseArgs(args) {
139
175
  options.graphql = true;
140
176
  else if (args.includes("--no-graphql"))
141
177
  options.graphql = false;
178
+ if (args.includes("--websocket") || args.includes("--ws"))
179
+ options.websocket = true;
180
+ if (args.includes("--trpc"))
181
+ options.trpc = true;
182
+ if (args.includes("--grpc"))
183
+ options.grpc = true;
184
+ if (args.includes("--csrf"))
185
+ options.csrf = true;
142
186
  if (args.includes("--no-install"))
143
187
  options.skipInstall = true;
144
- if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) {
145
- if (typeof telemetryArg === "string" && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg))
146
- options.telemetry = telemetryArg;
147
- }
188
+ if (queueArg && ["none", "memory", "bullmq"].includes(queueArg))
189
+ options.queue = queueArg;
190
+ if (cacheArg && ["none", "memory", "redis"].includes(cacheArg))
191
+ options.cache = cacheArg;
192
+ if (typeof corsArg === "string")
193
+ options.cors = corsArg;
194
+ if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg))
195
+ options.telemetry = telemetryArg;
148
196
  return options;
149
197
  }
150
198
  ;
151
- async function createProject(projectName, language, telemetry, swagger, graphql, useDocker) {
152
- const isCurrentDir = projectName === ".";
153
- const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), projectName);
199
+ async function createProject(features) {
200
+ const isCurrentDir = features.projectName === ".";
201
+ const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), features.projectName);
202
+ const ext = features.language === "typescript" ? "ts" : "js";
154
203
  if (!isCurrentDir && existsSync(targetDir)) {
155
- p.cancel(`Directory "${projectName}" already exists.`);
204
+ p.cancel(`Directory "${features.projectName}" already exists.`);
156
205
  process.exit(1);
157
206
  }
158
207
  if (!isCurrentDir)
159
208
  await mkdir(targetDir, { recursive: true });
160
- let pkgJson;
161
- if (language === "typescript")
162
- pkgJson = getTypeScriptPackageJson(projectName, telemetry, swagger, graphql);
163
- else
164
- pkgJson = getJavaScriptPackageJson(projectName, telemetry, swagger, graphql);
209
+ const pkgJson = features.language === "typescript"
210
+ ? getTypeScriptPackageJson(features.projectName, features.telemetry, features.swagger, features.graphql, features)
211
+ : getJavaScriptPackageJson(features.projectName, features.telemetry, features.swagger, features.graphql, features);
165
212
  await writeFile(join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2));
166
- if (language === "typescript") {
213
+ if (features.language === "typescript")
167
214
  await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
168
- await writeFile(join(targetDir, "sprint.config.ts"), getSprintConfigFile(language, telemetry, swagger, graphql));
169
- }
170
- else
171
- await writeFile(join(targetDir, "sprint.config.js"), getSprintConfigFile(language, telemetry, swagger, graphql));
215
+ await writeFile(join(targetDir, `sprint.config.${ext}`), getSprintConfigFile(features.language, features.telemetry, features.swagger, features.graphql, features));
172
216
  const srcDir = join(targetDir, "src");
173
217
  await mkdir(srcDir, { recursive: true });
174
218
  await mkdir(join(srcDir, "middlewares"), { recursive: true });
@@ -178,45 +222,57 @@ async function createProject(projectName, language, telemetry, swagger, graphql,
178
222
  await mkdir(join(srcDir, "cronjobs"), { recursive: true });
179
223
  await mkdir(join(srcDir, "config"), { recursive: true });
180
224
  await mkdir(join(srcDir, "services"), { recursive: true });
181
- if (graphql)
225
+ if (features.graphql)
182
226
  await mkdir(join(srcDir, "graphql"), { recursive: true });
183
- if (language === "typescript") {
184
- await writeFile(join(srcDir, "config", "index.ts"), "");
185
- await writeFile(join(srcDir, "config", "clients.ts"), "");
227
+ if (features.trpc)
228
+ await mkdir(join(srcDir, "trpc"), { recursive: true });
229
+ if (features.grpc)
230
+ await mkdir(join(srcDir, "grpc"), { recursive: true });
231
+ if (features.websocket)
232
+ await mkdir(join(srcDir, "ws"), { recursive: true });
233
+ await writeFile(join(srcDir, "config", `index.${ext}`), "");
234
+ await writeFile(join(srcDir, "config", `clients.${ext}`), "");
235
+ await writeFile(join(srcDir, "services", ".gitkeep"), "");
236
+ await writeFile(join(srcDir, `app.${ext}`), getMainFile(features.language, features.graphql, features));
237
+ await writeFile(join(srcDir, "routes", `home.${ext}`), getHomeRoute(features.language));
238
+ await writeFile(join(srcDir, "routes", `admin.${ext}`), getAdminRoute(features.language));
239
+ await writeFile(join(srcDir, "routes", `upload.${ext}`), getUploadRoute(features.language));
240
+ await writeFile(join(srcDir, "controllers", `home.${ext}`), getHomeController(features.language));
241
+ await writeFile(join(srcDir, "controllers", `admin.${ext}`), getAdminController(features.language));
242
+ await writeFile(join(srcDir, "controllers", `upload.${ext}`), getUploadController(features.language));
243
+ await writeFile(join(srcDir, "middlewares", `auth.internal.${ext}`), getInternalAuthMiddleware(features.language));
244
+ await writeFile(join(srcDir, "middlewares", `auth.user.${ext}`), getUserAuthMiddleware(features.language));
245
+ await writeFile(join(srcDir, "schemas", `home.${ext}`), getHomeSchema(features.language));
246
+ await writeFile(join(srcDir, "schemas", `admin.${ext}`), getAdminSchema(features.language));
247
+ await writeFile(join(srcDir, "schemas", `upload.${ext}`), getUploadSchema(features.language));
248
+ await writeFile(join(srcDir, "cronjobs", `example.${ext}`), getExampleCronJob(features.language));
249
+ if (features.queue !== "none")
250
+ await writeFile(join(srcDir, "services", `queue.${ext}`), getQueueService(features.language, features.queue));
251
+ if (features.cache !== "none")
252
+ await writeFile(join(srcDir, "services", `cache.${ext}`), getCacheService(features.language, features.cache));
253
+ if (features.trpc) {
254
+ await writeFile(join(srcDir, "trpc", `router.${ext}`), getTrpcRouter(features.language));
186
255
  }
187
- else {
188
- await writeFile(join(srcDir, "config", "index.js"), "");
189
- await writeFile(join(srcDir, "config", "clients.js"), "");
256
+ if (features.grpc) {
257
+ await writeFile(join(srcDir, "grpc", `server.${ext}`), getGrpcServer(features.language));
190
258
  }
191
- await writeFile(join(srcDir, "services", ".gitkeep"), "");
192
- await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getMainFile(language, graphql));
193
- await writeFile(join(srcDir, "routes", "home." + (language === "typescript" ? "ts" : "js")), getHomeRoute(language));
194
- await writeFile(join(srcDir, "routes", "admin." + (language === "typescript" ? "ts" : "js")), getAdminRoute(language));
195
- await writeFile(join(srcDir, "routes", "upload." + (language === "typescript" ? "ts" : "js")), getUploadRoute(language));
196
- await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
197
- await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
198
- await writeFile(join(srcDir, "controllers", "upload." + (language === "typescript" ? "ts" : "js")), getUploadController(language));
199
- await writeFile(join(srcDir, "middlewares", "auth.internal." + (language === "typescript" ? "ts" : "js")), getInternalAuthMiddleware(language));
200
- await writeFile(join(srcDir, "middlewares", "auth.user." + (language === "typescript" ? "ts" : "js")), getUserAuthMiddleware(language));
201
- await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
202
- await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));
203
- await writeFile(join(srcDir, "schemas", "upload." + (language === "typescript" ? "ts" : "js")), getUploadSchema(language));
204
- await writeFile(join(srcDir, "cronjobs", "example." + (language === "typescript" ? "ts" : "js")), getExampleCronJob(language));
205
- if (graphql) {
206
- const graphqlFiles = getGraphQLFiles(language);
207
- const ext = language === "typescript" ? "ts" : "js";
259
+ if (features.websocket) {
260
+ await writeFile(join(srcDir, "ws", `chat.${ext}`), getWebSocketScaffold(features.language));
261
+ }
262
+ if (features.graphql) {
263
+ const graphqlFiles = getGraphQLFiles(features.language);
208
264
  await writeFile(join(srcDir, "graphql", `types.${ext}`), graphqlFiles["types.ts"]);
209
265
  await writeFile(join(srcDir, "graphql", `resolvers.${ext}`), graphqlFiles["resolvers.ts"]);
210
266
  await writeFile(join(srcDir, "graphql", `schema.${ext}`), graphqlFiles["schema.ts"]);
211
267
  }
212
- await writeFile(join(targetDir, ".env.development.example"), getEnvExample(telemetry));
213
- await writeFile(join(targetDir, ".env.production.example"), getEnvExample(telemetry));
214
- await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(telemetry));
215
- await writeFile(join(targetDir, ".env.production"), getEnvProduction(telemetry));
268
+ await writeFile(join(targetDir, ".env.development.example"), getEnvExample(features.telemetry));
269
+ await writeFile(join(targetDir, ".env.production.example"), getEnvExample(features.telemetry));
270
+ await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(features.telemetry));
271
+ await writeFile(join(targetDir, ".env.production"), getEnvProduction(features.telemetry));
216
272
  await writeFile(join(targetDir, ".gitignore"), getGitignore());
217
- if (useDocker) {
218
- await writeFile(join(targetDir, "Dockerfile"), getDockerfile(language));
219
- await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(language));
273
+ if (features.docker) {
274
+ await writeFile(join(targetDir, "Dockerfile"), getDockerfile(features.language));
275
+ await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(features.language));
220
276
  await writeFile(join(targetDir, ".dockerignore"), getDockerIgnore());
221
277
  }
222
278
  }
@@ -28,56 +28,51 @@ export function getTsConfig() {
28
28
  `;
29
29
  }
30
30
  ;
31
- export function getSprintConfigFile(language, telemetry, swagger, graphql) {
31
+ function buildCorsBlock(corsInput) {
32
+ const trimmed = (corsInput ?? "").trim();
33
+ if (trimmed === "" || trimmed.toLowerCase() === "deny" || trimmed.toLowerCase() === "none") {
34
+ return ` cors: false,`;
35
+ }
36
+ if (trimmed === "*") {
37
+ return ` cors: { origin: "*" },`;
38
+ }
39
+ const origins = trimmed.split(",").map(o => o.trim()).filter(Boolean);
40
+ return ` cors: {
41
+ origin: ${JSON.stringify(origins)},
42
+ credentials: true,
43
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
44
+ allowedHeaders: ["Content-Type", "Authorization"]
45
+ },`;
46
+ }
47
+ export function getSprintConfigFile(language, telemetry, swagger, graphql, features) {
48
+ const corsInput = features?.cors ?? "";
32
49
  const swaggerEnabled = swagger ? "true" : "false";
33
50
  const swaggerUiEnabled = swagger ? '["development"]' : "false";
34
51
  const graphqlEnabled = graphql ? "true" : "false";
35
52
  const graphiqlEnabled = graphql ? '["development"]' : "false";
36
- if (language === "typescript") {
37
- let config = `import type { SprintOptions } from "sprint-es";
53
+ const corsBlock = buildCorsBlock(corsInput);
54
+ const isTs = language === "typescript";
55
+ const importLine = isTs ? `import type { SprintOptions } from "sprint-es";\n\n` : "";
56
+ const exportLine = isTs ? `export const config: SprintOptions = {` : `export const config = {`;
57
+ let config = `${importLine}${exportLine}
58
+ ${corsBlock}
38
59
 
39
- export const config: SprintOptions = {
40
- openapi: {
41
- enabled: ${swaggerEnabled},
42
- generateOnBuild: ${swaggerEnabled},
43
- swaggerUi: {
44
- enabled: ${swaggerUiEnabled}
45
- }
60
+ security: {
61
+ hsts: { maxAge: 63072000, includeSubDomains: true }
46
62
  },
47
- graphql: {
48
- enabled: ${graphqlEnabled},
49
- graphiql: {
50
- enabled: ${graphiqlEnabled}
51
- }
52
- }
53
- };
54
63
 
55
- // To use GraphQL, create a schema at src/graphql/schema.ts and import it here
56
- // import { GraphQLSchema } from "graphql";
57
- // export const graphqlSchema = new GraphQLSchema({ ... });
58
- `;
59
- if (telemetry === "sentry" || telemetry === "glitchtip") {
60
- config += `import { initTelemetry } from "sprint-es/telemetry";
64
+ context: {
65
+ trustIncomingRequestId: true
66
+ },
61
67
 
62
- initTelemetry({
63
- provider: "${telemetry}",
64
- dsn: process.env.SENTRY_DSN || "",
65
- environment: process.env.NODE_ENV || "development"
66
- });
67
- `;
68
- }
69
- else if (telemetry === "discord") {
70
- config += `import { initTelemetry } from "sprint-es/telemetry";
68
+ errorHandler: {
69
+ includeStack: process.env.NODE_ENV !== "production"
70
+ },
71
+
72
+ shutdown: {
73
+ timeoutMs: 30_000
74
+ },
71
75
 
72
- initTelemetry({
73
- provider: "discord",
74
- webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
75
- });
76
- `;
77
- }
78
- return config;
79
- }
80
- let config = `export const config = {
81
76
  openapi: {
82
77
  enabled: ${swaggerEnabled},
83
78
  generateOnBuild: ${swaggerEnabled},
@@ -85,6 +80,7 @@ initTelemetry({
85
80
  enabled: ${swaggerUiEnabled}
86
81
  }
87
82
  },
83
+
88
84
  graphql: {
89
85
  enabled: ${graphqlEnabled},
90
86
  graphiql: {
@@ -92,14 +88,9 @@ initTelemetry({
92
88
  }
93
89
  }
94
90
  };
95
-
96
- // To use GraphQL, create a schema at src/graphql/schema.js and import it here
97
- // import { GraphQLSchema } from "graphql";
98
- // export const graphqlSchema = new GraphQLSchema({ ... });
99
91
  `;
100
92
  if (telemetry === "sentry" || telemetry === "glitchtip") {
101
- config += `
102
- import { initTelemetry } from "sprint-es/telemetry";
93
+ config += `\nimport { initTelemetry } from "sprint-es/telemetry";
103
94
 
104
95
  initTelemetry({
105
96
  provider: "${telemetry}",
@@ -109,8 +100,7 @@ initTelemetry({
109
100
  `;
110
101
  }
111
102
  else if (telemetry === "discord") {
112
- config += `
113
- import { initTelemetry } from "sprint-es/telemetry";
103
+ config += `\nimport { initTelemetry } from "sprint-es/telemetry";
114
104
 
115
105
  initTelemetry({
116
106
  provider: "discord",
@@ -18,5 +18,7 @@ export { getExampleCronJob } from "./cronjobs.js";
18
18
  export { getDockerfile, getDockerCompose } from "./docker.js";
19
19
  // GraphQL
20
20
  export { getGraphQLFiles } from "./graphql.js";
21
+ // Services (queue, cache, trpc, grpc, websocket scaffolds)
22
+ export { getQueueService, getCacheService, getTrpcRouter, getGrpcServer, getWebSocketScaffold } from "./services.js";
21
23
  // Misc
22
24
  export { getGitignore, getDockerIgnore } from "./misc.js";
@@ -8,9 +8,23 @@ export function generateJWTKeys() {
8
8
  return keys;
9
9
  }
10
10
  ;
11
- export function getTypeScriptPackageJson(name, telemetry, swagger, graphql) {
11
+ function applyFeatureDeps(deps, features) {
12
+ if (!features)
13
+ return;
14
+ if (features.queue === "bullmq")
15
+ deps["bullmq"] = "^5.0.0";
16
+ if (features.queue === "bullmq" || features.cache === "redis")
17
+ deps["ioredis"] = "^5.0.0";
18
+ if (features.websocket)
19
+ deps["ws"] = "^8.18.0";
20
+ if (features.trpc)
21
+ deps["@trpc/server"] = "^11.0.0";
22
+ if (features.grpc)
23
+ deps["@grpc/grpc-js"] = "^1.12.0";
24
+ }
25
+ export function getTypeScriptPackageJson(name, telemetry, swagger, graphql, features) {
12
26
  const deps = {
13
- "sprint-es": "^0.0.168"
27
+ "sprint-es": "^1.0.0"
14
28
  };
15
29
  const devDeps = {
16
30
  "@types/node": "^22.0.0",
@@ -32,6 +46,9 @@ export function getTypeScriptPackageJson(name, telemetry, swagger, graphql) {
32
46
  deps["ruru"] = "^2.0.0-rc.6";
33
47
  devDeps["@types/swagger-ui-express"] = "^4.1.8";
34
48
  }
49
+ applyFeatureDeps(deps, features);
50
+ if (features?.websocket)
51
+ devDeps["@types/ws"] = "^8.5.10";
35
52
  return {
36
53
  name: name === "." ? "sprint-app" : name,
37
54
  version: "0.0.1",
@@ -65,9 +82,9 @@ export function getTypeScriptPackageJson(name, telemetry, swagger, graphql) {
65
82
  };
66
83
  }
67
84
  ;
68
- export function getJavaScriptPackageJson(name, telemetry, swagger, graphql) {
85
+ export function getJavaScriptPackageJson(name, telemetry, swagger, graphql, features) {
69
86
  const deps = {
70
- "sprint-es": "^0.0.168"
87
+ "sprint-es": "^1.0.0"
71
88
  };
72
89
  if (telemetry === "sentry" || telemetry === "glitchtip")
73
90
  deps["@sentry/node"] = "^8.0.0";
@@ -80,6 +97,7 @@ export function getJavaScriptPackageJson(name, telemetry, swagger, graphql) {
80
97
  deps["graphql-http"] = "^1.22.4";
81
98
  deps["ruru"] = "^2.0.0-rc.6";
82
99
  }
100
+ applyFeatureDeps(deps, features);
83
101
  return {
84
102
  name: name === "." ? "sprint-app" : name,
85
103
  version: "0.0.1",
@@ -1,31 +1,40 @@
1
- export function getMainFile(language, graphql = false) {
1
+ export function getMainFile(language, graphql = false, features) {
2
2
  const isTs = language === "typescript";
3
- if (isTs) {
4
- if (graphql) {
5
- return `import Sprint from "sprint-es";
6
- import { graphqlSchema } from "./graphql/schema";
7
-
8
- const app = new Sprint();
9
- app.setGraphQLSchema(graphqlSchema);
10
- `;
11
- }
12
- return `import Sprint from "sprint-es";
13
-
14
- const app = new Sprint();
15
- `;
16
- }
3
+ const ext = isTs ? "" : ".js";
4
+ const imports = [`import Sprint from "sprint-es";`];
5
+ const setup = [`const app = new Sprint();`];
17
6
  if (graphql) {
18
- return `import Sprint from "sprint-es";
19
- import { graphqlSchema } from "./graphql/schema.js";
20
-
21
- const app = new Sprint();
22
- app.setGraphQLSchema(graphqlSchema);
23
- `;
7
+ imports.push(`import { graphqlSchema } from "./graphql/schema${ext}";`);
8
+ setup.push(`app.setGraphQLSchema(graphqlSchema);`);
24
9
  }
25
- return `import Sprint from "sprint-es";
26
-
27
- const app = new Sprint();
28
- `;
10
+ if (features?.queue && features.queue !== "none") {
11
+ imports.push(`import "./services/queue${ext}";`);
12
+ }
13
+ if (features?.cache && features.cache !== "none") {
14
+ imports.push(`import "./services/cache${ext}";`);
15
+ }
16
+ if (features?.csrf) {
17
+ // CSRF is registered via defineMiddleware in src/middlewares/csrf.ts; just hint here
18
+ }
19
+ if (features?.trpc) {
20
+ imports.push(`import { attachTrpc } from "sprint-es/trpc";`);
21
+ imports.push(`import { appRouter } from "./trpc/router${ext}";`);
22
+ setup.push(`await app.ready;`);
23
+ setup.push(`await attachTrpc({ app: app.app, router: appRouter });`);
24
+ }
25
+ if (features?.websocket) {
26
+ imports.push(`import { attachWebSocket } from "sprint-es/ws";`);
27
+ imports.push(`import { chatHandler } from "./ws/chat${ext}";`);
28
+ setup.push(`{
29
+ const server = await app.onListen();
30
+ await attachWebSocket({ server, handlers: { "/ws/chat": chatHandler } });
31
+ }`);
32
+ }
33
+ if (features?.grpc) {
34
+ imports.push(`import { startGrpcServer } from "./grpc/server${ext}";`);
35
+ setup.push(`startGrpcServer().catch(err => console.error("[grpc] failed to start", err));`);
36
+ }
37
+ return imports.join("\n") + "\n\n" + setup.join("\n") + "\n";
29
38
  }
30
39
  ;
31
40
  export function getHomeRoute(language) {