create-sprint 0.0.136 → 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.
@@ -1,3 +1,5 @@
1
+ import type { ProjectFeatures } from "../index.js";
2
+
1
3
  export function getTsConfig() {
2
4
  return `
3
5
  {
@@ -28,59 +30,61 @@ export function getTsConfig() {
28
30
  `;
29
31
  };
30
32
 
31
- export function getSprintConfigFile(language: string, telemetry: string, swagger: boolean, graphql: boolean) {
33
+ function buildCorsBlock(corsInput: string): string {
34
+ const trimmed = (corsInput ?? "").trim();
35
+ if (trimmed === "" || trimmed.toLowerCase() === "deny" || trimmed.toLowerCase() === "none") {
36
+ return ` cors: false,`;
37
+ }
38
+ if (trimmed === "*") {
39
+ return ` cors: { origin: "*" },`;
40
+ }
41
+ const origins = trimmed.split(",").map(o => o.trim()).filter(Boolean);
42
+ return ` cors: {
43
+ origin: ${JSON.stringify(origins)},
44
+ credentials: true,
45
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
46
+ allowedHeaders: ["Content-Type", "Authorization"]
47
+ },`;
48
+ }
49
+
50
+ export function getSprintConfigFile(
51
+ language: string,
52
+ telemetry: string,
53
+ swagger: boolean,
54
+ graphql: boolean,
55
+ features?: Partial<ProjectFeatures>
56
+ ): string {
57
+ const corsInput = features?.cors ?? "";
32
58
  const swaggerEnabled = swagger ? "true" : "false";
33
59
  const swaggerUiEnabled = swagger ? '["development"]' : "false";
34
60
  const graphqlEnabled = graphql ? "true" : "false";
35
61
  const graphiqlEnabled = graphql ? '["development"]' : "false";
36
-
37
- if (language === "typescript") {
38
- let config = `import type { SprintOptions } from "sprint-es";
39
62
 
40
- export const config: SprintOptions = {
41
- openapi: {
42
- enabled: ${swaggerEnabled},
43
- generateOnBuild: ${swaggerEnabled},
44
- swaggerUi: {
45
- enabled: ${swaggerUiEnabled}
46
- }
47
- },
48
- graphql: {
49
- enabled: ${graphqlEnabled},
50
- graphiql: {
51
- enabled: ${graphiqlEnabled}
52
- }
53
- }
54
- };
63
+ const corsBlock = buildCorsBlock(corsInput);
55
64
 
56
- // To use GraphQL, create a schema at src/graphql/schema.ts and import it here
57
- // import { GraphQLSchema } from "graphql";
58
- // export const graphqlSchema = new GraphQLSchema({ ... });
59
- `;
65
+ const isTs = language === "typescript";
66
+ const importLine = isTs ? `import type { SprintOptions } from "sprint-es";\n\n` : "";
67
+ const exportLine = isTs ? `export const config: SprintOptions = {` : `export const config = {`;
60
68
 
61
- if (telemetry === "sentry" || telemetry === "glitchtip") {
62
- config += `import { initTelemetry } from "sprint-es/telemetry";
69
+ let config = `${importLine}${exportLine}
70
+ ${corsBlock}
63
71
 
64
- initTelemetry({
65
- provider: "${telemetry}",
66
- dsn: process.env.SENTRY_DSN || "",
67
- environment: process.env.NODE_ENV || "development"
68
- });
69
- `;
70
- } else if (telemetry === "discord") {
71
- config += `import { initTelemetry } from "sprint-es/telemetry";
72
+ security: {
73
+ hsts: { maxAge: 63072000, includeSubDomains: true }
74
+ },
72
75
 
73
- initTelemetry({
74
- provider: "discord",
75
- webhookUrl: process.env.DISCORD_TELEMETRY_WEBHOOK_URL || ""
76
- });
77
- `;
78
- }
76
+ context: {
77
+ trustIncomingRequestId: true
78
+ },
79
79
 
80
- return config;
81
- }
80
+ errorHandler: {
81
+ includeStack: process.env.NODE_ENV !== "production"
82
+ },
83
+
84
+ shutdown: {
85
+ timeoutMs: 30_000
86
+ },
82
87
 
83
- let config = `export const config = {
84
88
  openapi: {
85
89
  enabled: ${swaggerEnabled},
86
90
  generateOnBuild: ${swaggerEnabled},
@@ -88,6 +92,7 @@ initTelemetry({
88
92
  enabled: ${swaggerUiEnabled}
89
93
  }
90
94
  },
95
+
91
96
  graphql: {
92
97
  enabled: ${graphqlEnabled},
93
98
  graphiql: {
@@ -95,15 +100,10 @@ initTelemetry({
95
100
  }
96
101
  }
97
102
  };
98
-
99
- // To use GraphQL, create a schema at src/graphql/schema.js and import it here
100
- // import { GraphQLSchema } from "graphql";
101
- // export const graphqlSchema = new GraphQLSchema({ ... });
102
103
  `;
103
104
 
104
105
  if (telemetry === "sentry" || telemetry === "glitchtip") {
105
- config += `
106
- import { initTelemetry } from "sprint-es/telemetry";
106
+ config += `\nimport { initTelemetry } from "sprint-es/telemetry";
107
107
 
108
108
  initTelemetry({
109
109
  provider: "${telemetry}",
@@ -112,8 +112,7 @@ initTelemetry({
112
112
  });
113
113
  `;
114
114
  } else if (telemetry === "discord") {
115
- config += `
116
- import { initTelemetry } from "sprint-es/telemetry";
115
+ config += `\nimport { initTelemetry } from "sprint-es/telemetry";
117
116
 
118
117
  initTelemetry({
119
118
  provider: "discord",
@@ -123,4 +122,4 @@ initTelemetry({
123
122
  }
124
123
 
125
124
  return config;
126
- };
125
+ };
@@ -105,7 +105,7 @@ export function getUploadController(language: string) {
105
105
  return `import { Handler, SprintRequest, SprintResponse } from "sprint-es";
106
106
 
107
107
  export const uploadPdfController: Handler = (req: SprintRequest, res: SprintResponse) => {
108
- const file = req.file;
108
+ const file = req.files?.document[0];
109
109
 
110
110
  if (!file) return res.status(400).json({ error: "No file uploaded" });
111
111
 
@@ -28,5 +28,8 @@ export { getDockerfile, getDockerCompose } from "./docker.js";
28
28
  // GraphQL
29
29
  export { getGraphQLFiles } from "./graphql.js";
30
30
 
31
+ // Services (queue, cache, trpc, grpc, websocket scaffolds)
32
+ export { getQueueService, getCacheService, getTrpcRouter, getGrpcServer, getWebSocketScaffold } from "./services.js";
33
+
31
34
  // Misc
32
35
  export { getGitignore, getDockerIgnore } from "./misc.js";
@@ -1,4 +1,5 @@
1
1
  import * as crypto from "node:crypto";
2
+ import type { ProjectFeatures } from "../index.js";
2
3
 
3
4
  export interface JWTKeys {
4
5
  publicKey: string;
@@ -14,9 +15,18 @@ export function generateJWTKeys(): JWTKeys {
14
15
  return keys;
15
16
  };
16
17
 
17
- export function getTypeScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean) {
18
+ function applyFeatureDeps(deps: Record<string, string>, features?: Partial<ProjectFeatures>): void {
19
+ if (!features) return;
20
+ if (features.queue === "bullmq") deps["bullmq"] = "^5.0.0";
21
+ if (features.queue === "bullmq" || features.cache === "redis") deps["ioredis"] = "^5.0.0";
22
+ if (features.websocket) deps["ws"] = "^8.18.0";
23
+ if (features.trpc) deps["@trpc/server"] = "^11.0.0";
24
+ if (features.grpc) deps["@grpc/grpc-js"] = "^1.12.0";
25
+ }
26
+
27
+ export function getTypeScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean, features?: Partial<ProjectFeatures>) {
18
28
  const deps: Record<string, string> = {
19
- "sprint-es": "^0.0.165"
29
+ "sprint-es": "^1.0.0"
20
30
  };
21
31
 
22
32
  const devDeps: Record<string, string> = {
@@ -41,6 +51,9 @@ export function getTypeScriptPackageJson(name: string, telemetry: string, swagge
41
51
  devDeps["@types/swagger-ui-express"] = "^4.1.8";
42
52
  }
43
53
 
54
+ applyFeatureDeps(deps, features);
55
+ if (features?.websocket) devDeps["@types/ws"] = "^8.5.10";
56
+
44
57
  return {
45
58
  name: name === "." ? "sprint-app" : name,
46
59
  version: "0.0.1",
@@ -74,14 +87,14 @@ export function getTypeScriptPackageJson(name: string, telemetry: string, swagge
74
87
  };
75
88
  };
76
89
 
77
- export function getJavaScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean) {
90
+ export function getJavaScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean, features?: Partial<ProjectFeatures>) {
78
91
  const deps: Record<string, string> = {
79
- "sprint-es": "^0.0.165"
92
+ "sprint-es": "^1.0.0"
80
93
  };
81
94
 
82
95
  if (telemetry === "sentry" || telemetry === "glitchtip") deps["@sentry/node"] = "^8.0.0";
83
96
  else if (telemetry === "discord") deps["axios"] = "^1.6.0";
84
-
97
+
85
98
  if (swagger) deps["swagger-ui-express"] = "^5.0.0";
86
99
 
87
100
  if (graphql) {
@@ -90,6 +103,8 @@ export function getJavaScriptPackageJson(name: string, telemetry: string, swagge
90
103
  deps["ruru"] = "^2.0.0-rc.6";
91
104
  }
92
105
 
106
+ applyFeatureDeps(deps, features);
107
+
93
108
  return {
94
109
  name: name === "." ? "sprint-app" : name,
95
110
  version: "0.0.1",
@@ -105,4 +120,4 @@ export function getJavaScriptPackageJson(name: string, telemetry: string, swagge
105
120
  },
106
121
  dependencies: deps
107
122
  };
108
- };
123
+ };
@@ -1,34 +1,57 @@
1
- export function getMainFile(language: string, graphql: boolean = false) {
1
+ export interface MainFileFeatures {
2
+ queue?: "none" | "memory" | "bullmq";
3
+ cache?: "none" | "memory" | "redis";
4
+ websocket?: boolean;
5
+ trpc?: boolean;
6
+ grpc?: boolean;
7
+ csrf?: boolean;
8
+ }
9
+
10
+ export function getMainFile(language: string, graphql: boolean = false, features?: MainFileFeatures): string {
2
11
  const isTs = language === "typescript";
3
-
4
- if (isTs) {
5
- if (graphql) {
6
- return `import Sprint from "sprint-es";
7
- import { graphqlSchema } from "./graphql/schema";
8
-
9
- const app = new Sprint();
10
- app.setGraphQLSchema(graphqlSchema);
11
- `;
12
- }
13
- return `import Sprint from "sprint-es";
12
+ const ext = isTs ? "" : ".js";
14
13
 
15
- const app = new Sprint();
16
- `;
17
- }
14
+ const imports: string[] = [`import Sprint from "sprint-es";`];
15
+ const setup: string[] = [`const app = new Sprint();`];
18
16
 
19
17
  if (graphql) {
20
- return `import Sprint from "sprint-es";
21
- import { graphqlSchema } from "./graphql/schema.js";
18
+ imports.push(`import { graphqlSchema } from "./graphql/schema${ext}";`);
19
+ setup.push(`app.setGraphQLSchema(graphqlSchema);`);
20
+ }
22
21
 
23
- const app = new Sprint();
24
- app.setGraphQLSchema(graphqlSchema);
25
- `;
22
+ if (features?.queue && features.queue !== "none") {
23
+ imports.push(`import "./services/queue${ext}";`);
24
+ }
25
+ if (features?.cache && features.cache !== "none") {
26
+ imports.push(`import "./services/cache${ext}";`);
27
+ }
28
+
29
+ if (features?.csrf) {
30
+ // CSRF is registered via defineMiddleware in src/middlewares/csrf.ts; just hint here
26
31
  }
27
32
 
28
- return `import Sprint from "sprint-es";
33
+ if (features?.trpc) {
34
+ imports.push(`import { attachTrpc } from "sprint-es/trpc";`);
35
+ imports.push(`import { appRouter } from "./trpc/router${ext}";`);
36
+ setup.push(`await app.ready;`);
37
+ setup.push(`await attachTrpc({ app: app.app, router: appRouter });`);
38
+ }
29
39
 
30
- const app = new Sprint();
31
- `;
40
+ if (features?.websocket) {
41
+ imports.push(`import { attachWebSocket } from "sprint-es/ws";`);
42
+ imports.push(`import { chatHandler } from "./ws/chat${ext}";`);
43
+ setup.push(`{
44
+ const server = await app.onListen();
45
+ await attachWebSocket({ server, handlers: { "/ws/chat": chatHandler } });
46
+ }`);
47
+ }
48
+
49
+ if (features?.grpc) {
50
+ imports.push(`import { startGrpcServer } from "./grpc/server${ext}";`);
51
+ setup.push(`startGrpcServer().catch(err => console.error("[grpc] failed to start", err));`);
52
+ }
53
+
54
+ return imports.join("\n") + "\n\n" + setup.join("\n") + "\n";
32
55
  };
33
56
 
34
57
  export function getHomeRoute(language: string) {
@@ -0,0 +1,270 @@
1
+ type Lang = "typescript" | "javascript";
2
+
3
+ export function getQueueService(language: Lang, choice: "memory" | "bullmq"): string {
4
+ if (choice === "bullmq") {
5
+ if (language === "typescript") {
6
+ return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
7
+ import { Queue, Worker } from "bullmq";
8
+ import IORedis from "ioredis";
9
+
10
+ const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
11
+ maxRetriesPerRequest: null
12
+ });
13
+
14
+ export interface EmailJob {
15
+ to: string;
16
+ subject: string;
17
+ body: string;
18
+ }
19
+
20
+ export const emails = defineQueue<EmailJob>({
21
+ name: "emails",
22
+ adapter: new BullMQQueue<EmailJob>({
23
+ queue: new Queue("emails", { connection }),
24
+ workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
25
+ defaultAttempts: 5
26
+ })
27
+ });
28
+
29
+ emails.process(async (job) => {
30
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
31
+ // TODO: integrate your SMTP / provider here.
32
+ });
33
+ `;
34
+ }
35
+ return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
36
+ import { Queue, Worker } from "bullmq";
37
+ import IORedis from "ioredis";
38
+
39
+ const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
40
+ maxRetriesPerRequest: null
41
+ });
42
+
43
+ export const emails = defineQueue({
44
+ name: "emails",
45
+ adapter: new BullMQQueue({
46
+ queue: new Queue("emails", { connection }),
47
+ workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
48
+ defaultAttempts: 5
49
+ })
50
+ });
51
+
52
+ emails.process(async (job) => {
53
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
54
+ // TODO: integrate your SMTP / provider here.
55
+ });
56
+ `;
57
+ }
58
+
59
+ // memory
60
+ if (language === "typescript") {
61
+ return `import { defineQueue } from "sprint-es/queue";
62
+
63
+ export interface EmailJob {
64
+ to: string;
65
+ subject: string;
66
+ body: string;
67
+ }
68
+
69
+ export const emails = defineQueue<EmailJob>({ name: "emails" });
70
+
71
+ emails.process(async (job) => {
72
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
73
+ // TODO: integrate your SMTP / provider here.
74
+ });
75
+ `;
76
+ }
77
+ return `import { defineQueue } from "sprint-es/queue";
78
+
79
+ export const emails = defineQueue({ name: "emails" });
80
+
81
+ emails.process(async (job) => {
82
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
83
+ // TODO: integrate your SMTP / provider here.
84
+ });
85
+ `;
86
+ }
87
+
88
+ export function getCacheService(language: Lang, choice: "memory" | "redis"): string {
89
+ if (choice === "redis") {
90
+ if (language === "typescript") {
91
+ return `import { defineCache, RedisCache } from "sprint-es/cache";
92
+ import IORedis from "ioredis";
93
+
94
+ const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
95
+
96
+ export const cache = defineCache({
97
+ name: "main",
98
+ adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
99
+ });
100
+ `;
101
+ }
102
+ return `import { defineCache, RedisCache } from "sprint-es/cache";
103
+ import IORedis from "ioredis";
104
+
105
+ const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
106
+
107
+ export const cache = defineCache({
108
+ name: "main",
109
+ adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
110
+ });
111
+ `;
112
+ }
113
+
114
+ // memory
115
+ if (language === "typescript") {
116
+ return `import { defineCache, MemoryCache } from "sprint-es/cache";
117
+
118
+ export const cache = defineCache({
119
+ name: "main",
120
+ adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
121
+ });
122
+ `;
123
+ }
124
+ return `import { defineCache, MemoryCache } from "sprint-es/cache";
125
+
126
+ export const cache = defineCache({
127
+ name: "main",
128
+ adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
129
+ });
130
+ `;
131
+ }
132
+
133
+ export function getTrpcRouter(language: Lang): string {
134
+ if (language === "typescript") {
135
+ return `import { initTRPC } from "@trpc/server";
136
+ import { z } from "sprint-es/schemas";
137
+
138
+ const t = initTRPC.create();
139
+
140
+ export const appRouter = t.router({
141
+ hello: t.procedure
142
+ .input(z.object({ name: z.string() }))
143
+ .query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
144
+
145
+ createUser: t.procedure
146
+ .input(z.object({ email: z.string().email(), name: z.string().min(1) }))
147
+ .mutation(async ({ input }) => {
148
+ // TODO: persist user
149
+ return { id: crypto.randomUUID(), ...input };
150
+ })
151
+ });
152
+
153
+ export type AppRouter = typeof appRouter;
154
+ `;
155
+ }
156
+ return `import { initTRPC } from "@trpc/server";
157
+ import { z } from "sprint-es/schemas";
158
+
159
+ const t = initTRPC.create();
160
+
161
+ export const appRouter = t.router({
162
+ hello: t.procedure
163
+ .input(z.object({ name: z.string() }))
164
+ .query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
165
+
166
+ createUser: t.procedure
167
+ .input(z.object({ email: z.string().email(), name: z.string().min(1) }))
168
+ .mutation(async ({ input }) => {
169
+ return { id: crypto.randomUUID(), ...input };
170
+ })
171
+ });
172
+ `;
173
+ }
174
+
175
+ export function getGrpcServer(language: Lang): string {
176
+ if (language === "typescript") {
177
+ return `import { createGrpcServer } from "sprint-es/grpc";
178
+ import * as grpc from "@grpc/grpc-js";
179
+
180
+ // Minimal greeter service using a service definition.
181
+ // Replace with proto-loaded service in production via @grpc/proto-loader.
182
+ const greeterServiceDefinition: grpc.ServiceDefinition = {
183
+ SayHello: {
184
+ path: "/Greeter/SayHello",
185
+ requestStream: false,
186
+ responseStream: false,
187
+ requestSerialize: (v: { name: string }) => Buffer.from(JSON.stringify(v)),
188
+ requestDeserialize: (b: Buffer) => JSON.parse(b.toString()),
189
+ responseSerialize: (v: { message: string }) => Buffer.from(JSON.stringify(v)),
190
+ responseDeserialize: (b: Buffer) => JSON.parse(b.toString())
191
+ }
192
+ };
193
+
194
+ export async function startGrpcServer() {
195
+ return await createGrpcServer({
196
+ address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
197
+ services: [
198
+ {
199
+ service: greeterServiceDefinition,
200
+ implementation: {
201
+ SayHello: (call: grpc.ServerUnaryCall<{ name: string }, { message: string }>, callback: grpc.sendUnaryData<{ message: string }>) => {
202
+ callback(null, { message: \`Hello, \${call.request.name}!\` });
203
+ }
204
+ }
205
+ }
206
+ ]
207
+ });
208
+ }
209
+ `;
210
+ }
211
+ return `import { createGrpcServer } from "sprint-es/grpc";
212
+
213
+ const greeterServiceDefinition = {
214
+ SayHello: {
215
+ path: "/Greeter/SayHello",
216
+ requestStream: false,
217
+ responseStream: false,
218
+ requestSerialize: (v) => Buffer.from(JSON.stringify(v)),
219
+ requestDeserialize: (b) => JSON.parse(b.toString()),
220
+ responseSerialize: (v) => Buffer.from(JSON.stringify(v)),
221
+ responseDeserialize: (b) => JSON.parse(b.toString())
222
+ }
223
+ };
224
+
225
+ export async function startGrpcServer() {
226
+ return await createGrpcServer({
227
+ address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
228
+ services: [
229
+ {
230
+ service: greeterServiceDefinition,
231
+ implementation: {
232
+ SayHello: (call, callback) => {
233
+ callback(null, { message: \`Hello, \${call.request.name}!\` });
234
+ }
235
+ }
236
+ }
237
+ ]
238
+ });
239
+ }
240
+ `;
241
+ }
242
+
243
+ export function getWebSocketScaffold(language: Lang): string {
244
+ if (language === "typescript") {
245
+ return `import type { WebSocketHandler, WebSocket } from "sprint-es/ws";
246
+
247
+ export const chatHandler: WebSocketHandler = {
248
+ onConnection: (socket: WebSocket, request) => {
249
+ console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
250
+ socket.on("message", (raw) => {
251
+ const text = raw.toString();
252
+ socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
253
+ });
254
+ socket.on("close", () => console.log("[ws] client disconnected"));
255
+ }
256
+ };
257
+ `;
258
+ }
259
+ return `export const chatHandler = {
260
+ onConnection: (socket, request) => {
261
+ console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
262
+ socket.on("message", (raw) => {
263
+ const text = raw.toString();
264
+ socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
265
+ });
266
+ socket.on("close", () => console.log("[ws] client disconnected"));
267
+ }
268
+ };
269
+ `;
270
+ }