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.
@@ -0,0 +1,262 @@
1
+ export function getQueueService(language, choice) {
2
+ if (choice === "bullmq") {
3
+ if (language === "typescript") {
4
+ return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
5
+ import { Queue, Worker } from "bullmq";
6
+ import IORedis from "ioredis";
7
+
8
+ const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
9
+ maxRetriesPerRequest: null
10
+ });
11
+
12
+ export interface EmailJob {
13
+ to: string;
14
+ subject: string;
15
+ body: string;
16
+ }
17
+
18
+ export const emails = defineQueue<EmailJob>({
19
+ name: "emails",
20
+ adapter: new BullMQQueue<EmailJob>({
21
+ queue: new Queue("emails", { connection }),
22
+ workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
23
+ defaultAttempts: 5
24
+ })
25
+ });
26
+
27
+ emails.process(async (job) => {
28
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
29
+ // TODO: integrate your SMTP / provider here.
30
+ });
31
+ `;
32
+ }
33
+ return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
34
+ import { Queue, Worker } from "bullmq";
35
+ import IORedis from "ioredis";
36
+
37
+ const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
38
+ maxRetriesPerRequest: null
39
+ });
40
+
41
+ export const emails = defineQueue({
42
+ name: "emails",
43
+ adapter: new BullMQQueue({
44
+ queue: new Queue("emails", { connection }),
45
+ workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
46
+ defaultAttempts: 5
47
+ })
48
+ });
49
+
50
+ emails.process(async (job) => {
51
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
52
+ // TODO: integrate your SMTP / provider here.
53
+ });
54
+ `;
55
+ }
56
+ // memory
57
+ if (language === "typescript") {
58
+ return `import { defineQueue } from "sprint-es/queue";
59
+
60
+ export interface EmailJob {
61
+ to: string;
62
+ subject: string;
63
+ body: string;
64
+ }
65
+
66
+ export const emails = defineQueue<EmailJob>({ name: "emails" });
67
+
68
+ emails.process(async (job) => {
69
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
70
+ // TODO: integrate your SMTP / provider here.
71
+ });
72
+ `;
73
+ }
74
+ return `import { defineQueue } from "sprint-es/queue";
75
+
76
+ export const emails = defineQueue({ name: "emails" });
77
+
78
+ emails.process(async (job) => {
79
+ console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
80
+ // TODO: integrate your SMTP / provider here.
81
+ });
82
+ `;
83
+ }
84
+ export function getCacheService(language, choice) {
85
+ if (choice === "redis") {
86
+ if (language === "typescript") {
87
+ return `import { defineCache, RedisCache } from "sprint-es/cache";
88
+ import IORedis from "ioredis";
89
+
90
+ const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
91
+
92
+ export const cache = defineCache({
93
+ name: "main",
94
+ adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
95
+ });
96
+ `;
97
+ }
98
+ return `import { defineCache, RedisCache } from "sprint-es/cache";
99
+ import IORedis from "ioredis";
100
+
101
+ const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
102
+
103
+ export const cache = defineCache({
104
+ name: "main",
105
+ adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
106
+ });
107
+ `;
108
+ }
109
+ // memory
110
+ if (language === "typescript") {
111
+ return `import { defineCache, MemoryCache } from "sprint-es/cache";
112
+
113
+ export const cache = defineCache({
114
+ name: "main",
115
+ adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
116
+ });
117
+ `;
118
+ }
119
+ return `import { defineCache, MemoryCache } from "sprint-es/cache";
120
+
121
+ export const cache = defineCache({
122
+ name: "main",
123
+ adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
124
+ });
125
+ `;
126
+ }
127
+ export function getTrpcRouter(language) {
128
+ if (language === "typescript") {
129
+ return `import { initTRPC } from "@trpc/server";
130
+ import { z } from "sprint-es/schemas";
131
+
132
+ const t = initTRPC.create();
133
+
134
+ export const appRouter = t.router({
135
+ hello: t.procedure
136
+ .input(z.object({ name: z.string() }))
137
+ .query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
138
+
139
+ createUser: t.procedure
140
+ .input(z.object({ email: z.string().email(), name: z.string().min(1) }))
141
+ .mutation(async ({ input }) => {
142
+ // TODO: persist user
143
+ return { id: crypto.randomUUID(), ...input };
144
+ })
145
+ });
146
+
147
+ export type AppRouter = typeof appRouter;
148
+ `;
149
+ }
150
+ return `import { initTRPC } from "@trpc/server";
151
+ import { z } from "sprint-es/schemas";
152
+
153
+ const t = initTRPC.create();
154
+
155
+ export const appRouter = t.router({
156
+ hello: t.procedure
157
+ .input(z.object({ name: z.string() }))
158
+ .query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
159
+
160
+ createUser: t.procedure
161
+ .input(z.object({ email: z.string().email(), name: z.string().min(1) }))
162
+ .mutation(async ({ input }) => {
163
+ return { id: crypto.randomUUID(), ...input };
164
+ })
165
+ });
166
+ `;
167
+ }
168
+ export function getGrpcServer(language) {
169
+ if (language === "typescript") {
170
+ return `import { createGrpcServer } from "sprint-es/grpc";
171
+ import * as grpc from "@grpc/grpc-js";
172
+
173
+ // Minimal greeter service using a service definition.
174
+ // Replace with proto-loaded service in production via @grpc/proto-loader.
175
+ const greeterServiceDefinition: grpc.ServiceDefinition = {
176
+ SayHello: {
177
+ path: "/Greeter/SayHello",
178
+ requestStream: false,
179
+ responseStream: false,
180
+ requestSerialize: (v: { name: string }) => Buffer.from(JSON.stringify(v)),
181
+ requestDeserialize: (b: Buffer) => JSON.parse(b.toString()),
182
+ responseSerialize: (v: { message: string }) => Buffer.from(JSON.stringify(v)),
183
+ responseDeserialize: (b: Buffer) => JSON.parse(b.toString())
184
+ }
185
+ };
186
+
187
+ export async function startGrpcServer() {
188
+ return await createGrpcServer({
189
+ address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
190
+ services: [
191
+ {
192
+ service: greeterServiceDefinition,
193
+ implementation: {
194
+ SayHello: (call: grpc.ServerUnaryCall<{ name: string }, { message: string }>, callback: grpc.sendUnaryData<{ message: string }>) => {
195
+ callback(null, { message: \`Hello, \${call.request.name}!\` });
196
+ }
197
+ }
198
+ }
199
+ ]
200
+ });
201
+ }
202
+ `;
203
+ }
204
+ return `import { createGrpcServer } from "sprint-es/grpc";
205
+
206
+ const greeterServiceDefinition = {
207
+ SayHello: {
208
+ path: "/Greeter/SayHello",
209
+ requestStream: false,
210
+ responseStream: false,
211
+ requestSerialize: (v) => Buffer.from(JSON.stringify(v)),
212
+ requestDeserialize: (b) => JSON.parse(b.toString()),
213
+ responseSerialize: (v) => Buffer.from(JSON.stringify(v)),
214
+ responseDeserialize: (b) => JSON.parse(b.toString())
215
+ }
216
+ };
217
+
218
+ export async function startGrpcServer() {
219
+ return await createGrpcServer({
220
+ address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
221
+ services: [
222
+ {
223
+ service: greeterServiceDefinition,
224
+ implementation: {
225
+ SayHello: (call, callback) => {
226
+ callback(null, { message: \`Hello, \${call.request.name}!\` });
227
+ }
228
+ }
229
+ }
230
+ ]
231
+ });
232
+ }
233
+ `;
234
+ }
235
+ export function getWebSocketScaffold(language) {
236
+ if (language === "typescript") {
237
+ return `import type { WebSocketHandler, WebSocket } from "sprint-es/ws";
238
+
239
+ export const chatHandler: WebSocketHandler = {
240
+ onConnection: (socket: WebSocket, request) => {
241
+ console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
242
+ socket.on("message", (raw) => {
243
+ const text = raw.toString();
244
+ socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
245
+ });
246
+ socket.on("close", () => console.log("[ws] client disconnected"));
247
+ }
248
+ };
249
+ `;
250
+ }
251
+ return `export const chatHandler = {
252
+ onConnection: (socket, request) => {
253
+ console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
254
+ socket.on("message", (raw) => {
255
+ const text = raw.toString();
256
+ socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
257
+ });
258
+ socket.on("close", () => console.log("[ws] client disconnected"));
259
+ }
260
+ };
261
+ `;
262
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sprint",
3
- "version": "0.0.138",
3
+ "version": "0.0.139",
4
4
  "description": "Create a new Sprint API project",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -6,8 +6,11 @@ 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
 
10
- type TelemetryProviders = "none" | "sentry" | "glitchtip" | "discord" | "open-telemetry" | "telegram" | "nodemailer";
11
+ type TelemetryProviders = "none" | "sentry" | "glitchtip" | "discord" | "open-telemetry" | "telegram" | "nodemailer";
12
+ type QueueChoice = "none" | "memory" | "bullmq";
13
+ type CacheChoice = "none" | "memory" | "redis";
11
14
 
12
15
  export interface CLIOptions {
13
16
  projectName?: string;
@@ -16,10 +19,33 @@ export interface CLIOptions {
16
19
  swagger?: boolean;
17
20
  graphql?: boolean;
18
21
  docker?: boolean;
22
+ queue?: QueueChoice;
23
+ cache?: CacheChoice;
24
+ websocket?: boolean;
25
+ trpc?: boolean;
26
+ grpc?: boolean;
27
+ csrf?: boolean;
28
+ cors?: string;
19
29
  skipInstall?: boolean;
20
30
  skipPrompts?: boolean;
21
31
  }
22
32
 
33
+ export interface ProjectFeatures {
34
+ projectName: string;
35
+ language: "typescript" | "javascript";
36
+ telemetry: TelemetryProviders;
37
+ swagger: boolean;
38
+ graphql: boolean;
39
+ docker: boolean;
40
+ queue: QueueChoice;
41
+ cache: CacheChoice;
42
+ websocket: boolean;
43
+ trpc: boolean;
44
+ grpc: boolean;
45
+ csrf: boolean;
46
+ cors: string;
47
+ }
48
+
23
49
  export async function writeFile(path: string, content: string, options?: any) {
24
50
  if (typeof content === "string") content = content.trim();
25
51
  await fsWriteFile(path, content, options);
@@ -28,18 +54,9 @@ export async function writeFile(path: string, content: string, options?: any) {
28
54
  export async function runCLI(args: string[]) {
29
55
  const options = parseArgs(args);
30
56
 
31
- p.intro("Sprint — Quickly API Framework");
32
-
33
57
  p.intro(`${color.bgCyan(color.black(' create-sprint-app '))}`);
34
58
 
35
- let config: {
36
- projectName: string;
37
- language: "typescript" | "javascript";
38
- telemetry: TelemetryProviders;
39
- swagger: boolean;
40
- graphql: boolean;
41
- docker: boolean;
42
- };
59
+ let config: ProjectFeatures;
43
60
 
44
61
  if (options.skipPrompts) {
45
62
  config = {
@@ -48,10 +65,17 @@ export async function runCLI(args: string[]) {
48
65
  telemetry: options.telemetry ?? "none",
49
66
  swagger: options.swagger ?? true,
50
67
  graphql: options.graphql ?? false,
51
- docker: options.docker || false
68
+ docker: options.docker || false,
69
+ queue: options.queue ?? "none",
70
+ cache: options.cache ?? "none",
71
+ websocket: options.websocket ?? false,
72
+ trpc: options.trpc ?? false,
73
+ grpc: options.grpc ?? false,
74
+ csrf: options.csrf ?? false,
75
+ cors: options.cors ?? ""
52
76
  };
53
77
  } else {
54
- config = await p.group(
78
+ const result = await p.group(
55
79
  {
56
80
  projectName: () =>
57
81
  p.text({
@@ -69,6 +93,13 @@ export async function runCLI(args: string[]) {
69
93
  ]
70
94
  }),
71
95
 
96
+ cors: () =>
97
+ p.text({
98
+ message: "CORS allowed origins (comma-separated, blank = deny all, '*' = wildcard):",
99
+ placeholder: "https://app.example.com,http://localhost:3000",
100
+ defaultValue: ""
101
+ }),
102
+
72
103
  telemetry: () =>
73
104
  p.select({
74
105
  message: "Error tracking:",
@@ -83,10 +114,32 @@ export async function runCLI(args: string[]) {
83
114
  ]
84
115
  }),
85
116
 
86
- swagger: () => p.confirm({ message: "Add Swagger UI & OpenAPI?", initialValue: true }),
117
+ queue: () =>
118
+ p.select({
119
+ message: "Job queue:",
120
+ options: [
121
+ { value: "none", label: "None" },
122
+ { value: "memory", label: "In-memory", hint: "single process, retries + DLQ" },
123
+ { value: "bullmq", label: "BullMQ + Redis", hint: "distributed, production-grade" }
124
+ ]
125
+ }),
87
126
 
88
- graphql: () => p.confirm({ message: "Add GraphQL support?", initialValue: false }),
127
+ cache: () =>
128
+ p.select({
129
+ message: "Cache:",
130
+ options: [
131
+ { value: "none", label: "None" },
132
+ { value: "memory", label: "In-memory LRU", hint: "single process" },
133
+ { value: "redis", label: "Redis", hint: "shared across instances" }
134
+ ]
135
+ }),
89
136
 
137
+ websocket: () => p.confirm({ message: "Add WebSocket support?", initialValue: false }),
138
+ trpc: () => p.confirm({ message: "Add tRPC adapter?", initialValue: false }),
139
+ grpc: () => p.confirm({ message: "Add gRPC server?", initialValue: false }),
140
+ graphql: () => p.confirm({ message: "Add GraphQL support?", initialValue: false }),
141
+ swagger: () => p.confirm({ message: "Add Swagger UI & OpenAPI?", initialValue: true }),
142
+ csrf: () => p.confirm({ message: "Add CSRF middleware (double-submit cookie)?", initialValue: false }),
90
143
  docker: () => p.confirm({ message: "Add Docker support?", initialValue: false })
91
144
  },
92
145
  {
@@ -96,20 +149,14 @@ export async function runCLI(args: string[]) {
96
149
  }
97
150
  }
98
151
  );
152
+ config = result as unknown as ProjectFeatures;
99
153
  }
100
154
 
101
155
  const targetDir = config.projectName === "." ? process.cwd() : join(process.cwd(), config.projectName);
102
156
 
103
157
  const s = p.spinner();
104
158
  s.start("Creating project");
105
- await createProject(
106
- config.projectName,
107
- config.language,
108
- config.telemetry,
109
- config.swagger,
110
- config.graphql,
111
- config.docker
112
- );
159
+ await createProject(config);
113
160
  s.stop("Project created");
114
161
 
115
162
  let installDeps = true;
@@ -143,7 +190,7 @@ export async function runCLI(args: string[]) {
143
190
  }
144
191
 
145
192
  const cdCmd = config.projectName === "." ? "" : `cd ${config.projectName} && `;
146
- p.note([!installDeps ? `${cdCmd}npm install --include=dev` : "", `${cdCmd}npm run dev` ].filter(Boolean).join("\n"), "Next steps");
193
+ p.note([!installDeps ? `${cdCmd}npm install --include=dev` : "", `${cdCmd}npm run dev`].filter(Boolean).join("\n"), "Next steps");
147
194
 
148
195
  p.outro("Ready. Happy shipping.");
149
196
  };
@@ -155,6 +202,9 @@ function parseArgs(args: string[]): CLIOptions {
155
202
  const hasJs = args.includes("--js") || args.includes("--javascript");
156
203
  const hasName = args.indexOf("--name");
157
204
  const telemetryArg = args.includes("--telemetry") ? args[args.indexOf("--telemetry") + 1] : null;
205
+ const queueArg = args.includes("--queue") ? args[args.indexOf("--queue") + 1] : null;
206
+ const cacheArg = args.includes("--cache") ? args[args.indexOf("--cache") + 1] : null;
207
+ const corsArg = args.includes("--cors") ? args[args.indexOf("--cors") + 1] : null;
158
208
 
159
209
  if (args.includes("--yes") || args.includes("-y")) options.skipPrompts = true;
160
210
 
@@ -162,7 +212,7 @@ function parseArgs(args: string[]): CLIOptions {
162
212
  if (hasTs) options.language = "typescript";
163
213
  else if (hasJs) options.language = "javascript";
164
214
  } else options.language = "typescript";
165
-
215
+
166
216
  if (hasName !== -1) {
167
217
  const value = args[hasName + 1];
168
218
  if (typeof value === "string") options.projectName = value;
@@ -171,54 +221,52 @@ function parseArgs(args: string[]): CLIOptions {
171
221
  if (args.includes("--current")) options.projectName = ".";
172
222
 
173
223
  if (args.includes("--docker")) options.docker = true;
174
-
224
+
175
225
  if (args.includes("--swagger")) options.swagger = true;
176
226
  else if (args.includes("--no-swagger")) options.swagger = false;
177
-
227
+
178
228
  if (args.includes("--graphql")) options.graphql = true;
179
229
  else if (args.includes("--no-graphql")) options.graphql = false;
180
-
230
+
231
+ if (args.includes("--websocket") || args.includes("--ws")) options.websocket = true;
232
+ if (args.includes("--trpc")) options.trpc = true;
233
+ if (args.includes("--grpc")) options.grpc = true;
234
+ if (args.includes("--csrf")) options.csrf = true;
181
235
  if (args.includes("--no-install")) options.skipInstall = true;
182
236
 
183
- if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) {
184
- if (typeof telemetryArg === "string" && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) options.telemetry = telemetryArg as TelemetryProviders;
185
- }
186
-
237
+ if (queueArg && ["none", "memory", "bullmq"].includes(queueArg)) options.queue = queueArg as QueueChoice;
238
+ if (cacheArg && ["none", "memory", "redis"].includes(cacheArg)) options.cache = cacheArg as CacheChoice;
239
+ if (typeof corsArg === "string") options.cors = corsArg;
240
+
241
+ if (telemetryArg && ["sentry", "glitchtip", "discord", "none"].includes(telemetryArg)) options.telemetry = telemetryArg as TelemetryProviders;
242
+
187
243
  return options;
188
244
  };
189
245
 
190
- async function createProject(
191
- projectName: string,
192
- language: "typescript" | "javascript",
193
- telemetry: TelemetryProviders,
194
- swagger: boolean,
195
- graphql: boolean,
196
- useDocker: boolean
197
- ) {
198
- const isCurrentDir = projectName === ".";
199
- const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), projectName);
246
+ async function createProject(features: ProjectFeatures) {
247
+ const isCurrentDir = features.projectName === ".";
248
+ const targetDir = isCurrentDir ? process.cwd() : join(process.cwd(), features.projectName);
249
+ const ext = features.language === "typescript" ? "ts" : "js";
200
250
 
201
251
  if (!isCurrentDir && existsSync(targetDir)) {
202
- p.cancel(`Directory "${projectName}" already exists.`);
252
+ p.cancel(`Directory "${features.projectName}" already exists.`);
203
253
  process.exit(1);
204
254
  }
205
255
 
206
256
  if (!isCurrentDir) await mkdir(targetDir, { recursive: true });
207
257
 
208
- let pkgJson;
209
- if (language === "typescript") pkgJson = getTypeScriptPackageJson(projectName, telemetry, swagger, graphql);
210
- else pkgJson = getJavaScriptPackageJson(projectName, telemetry, swagger, graphql);
211
-
258
+ const pkgJson = features.language === "typescript"
259
+ ? getTypeScriptPackageJson(features.projectName, features.telemetry, features.swagger, features.graphql, features)
260
+ : getJavaScriptPackageJson(features.projectName, features.telemetry, features.swagger, features.graphql, features);
261
+
212
262
  await writeFile(join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2));
213
263
 
214
- if (language === "typescript") {
215
- await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
216
- await writeFile(join(targetDir, "sprint.config.ts"), getSprintConfigFile(language, telemetry, swagger, graphql));
217
- } else await writeFile(join(targetDir, "sprint.config.js"), getSprintConfigFile(language, telemetry, swagger, graphql));
218
-
264
+ if (features.language === "typescript") await writeFile(join(targetDir, "tsconfig.json"), getTsConfig());
265
+
266
+ await writeFile(join(targetDir, `sprint.config.${ext}`), getSprintConfigFile(features.language, features.telemetry, features.swagger, features.graphql, features));
267
+
219
268
  const srcDir = join(targetDir, "src");
220
269
  await mkdir(srcDir, { recursive: true });
221
-
222
270
  await mkdir(join(srcDir, "middlewares"), { recursive: true });
223
271
  await mkdir(join(srcDir, "routes"), { recursive: true });
224
272
  await mkdir(join(srcDir, "controllers"), { recursive: true });
@@ -226,58 +274,63 @@ async function createProject(
226
274
  await mkdir(join(srcDir, "cronjobs"), { recursive: true });
227
275
  await mkdir(join(srcDir, "config"), { recursive: true });
228
276
  await mkdir(join(srcDir, "services"), { recursive: true });
229
-
230
- if (graphql) await mkdir(join(srcDir, "graphql"), { recursive: true });
231
277
 
232
- if (language === "typescript") {
233
- await writeFile(join(srcDir, "config", "index.ts"), "");
234
- await writeFile(join(srcDir, "config", "clients.ts"), "");
235
- } else {
236
- await writeFile(join(srcDir, "config", "index.js"), "");
237
- await writeFile(join(srcDir, "config", "clients.js"), "");
238
- }
278
+ if (features.graphql) await mkdir(join(srcDir, "graphql"), { recursive: true });
279
+ if (features.trpc) await mkdir(join(srcDir, "trpc"), { recursive: true });
280
+ if (features.grpc) await mkdir(join(srcDir, "grpc"), { recursive: true });
281
+ if (features.websocket) await mkdir(join(srcDir, "ws"), { recursive: true });
239
282
 
283
+ await writeFile(join(srcDir, "config", `index.${ext}`), "");
284
+ await writeFile(join(srcDir, "config", `clients.${ext}`), "");
240
285
  await writeFile(join(srcDir, "services", ".gitkeep"), "");
241
286
 
242
- await writeFile(join(srcDir, "app." + (language === "typescript" ? "ts" : "js")), getMainFile(language, graphql));
287
+ await writeFile(join(srcDir, `app.${ext}`), getMainFile(features.language, features.graphql, features));
243
288
 
244
- await writeFile(join(srcDir, "routes", "home." + (language === "typescript" ? "ts" : "js")), getHomeRoute(language));
245
- await writeFile(join(srcDir, "routes", "admin." + (language === "typescript" ? "ts" : "js")), getAdminRoute(language));
246
- await writeFile(join(srcDir, "routes", "upload." + (language === "typescript" ? "ts" : "js")), getUploadRoute(language));
289
+ await writeFile(join(srcDir, "routes", `home.${ext}`), getHomeRoute(features.language));
290
+ await writeFile(join(srcDir, "routes", `admin.${ext}`), getAdminRoute(features.language));
291
+ await writeFile(join(srcDir, "routes", `upload.${ext}`), getUploadRoute(features.language));
247
292
 
248
- await writeFile(join(srcDir, "controllers", "home." + (language === "typescript" ? "ts" : "js")), getHomeController(language));
249
- await writeFile(join(srcDir, "controllers", "admin." + (language === "typescript" ? "ts" : "js")), getAdminController(language));
250
- await writeFile(join(srcDir, "controllers", "upload." + (language === "typescript" ? "ts" : "js")), getUploadController(language));
293
+ await writeFile(join(srcDir, "controllers", `home.${ext}`), getHomeController(features.language));
294
+ await writeFile(join(srcDir, "controllers", `admin.${ext}`), getAdminController(features.language));
295
+ await writeFile(join(srcDir, "controllers", `upload.${ext}`), getUploadController(features.language));
251
296
 
252
- await writeFile(join(srcDir, "middlewares", "auth.internal." + (language === "typescript" ? "ts" : "js")), getInternalAuthMiddleware(language));
253
- await writeFile(join(srcDir, "middlewares", "auth.user." + (language === "typescript" ? "ts" : "js")), getUserAuthMiddleware(language));
297
+ await writeFile(join(srcDir, "middlewares", `auth.internal.${ext}`), getInternalAuthMiddleware(features.language));
298
+ await writeFile(join(srcDir, "middlewares", `auth.user.${ext}`), getUserAuthMiddleware(features.language));
254
299
 
255
- await writeFile(join(srcDir, "schemas", "home." + (language === "typescript" ? "ts" : "js")), getHomeSchema(language));
256
- await writeFile(join(srcDir, "schemas", "admin." + (language === "typescript" ? "ts" : "js")), getAdminSchema(language));
257
- await writeFile(join(srcDir, "schemas", "upload." + (language === "typescript" ? "ts" : "js")), getUploadSchema(language));
300
+ await writeFile(join(srcDir, "schemas", `home.${ext}`), getHomeSchema(features.language));
301
+ await writeFile(join(srcDir, "schemas", `admin.${ext}`), getAdminSchema(features.language));
302
+ await writeFile(join(srcDir, "schemas", `upload.${ext}`), getUploadSchema(features.language));
258
303
 
259
- await writeFile(join(srcDir, "cronjobs", "example." + (language === "typescript" ? "ts" : "js")), getExampleCronJob(language));
304
+ await writeFile(join(srcDir, "cronjobs", `example.${ext}`), getExampleCronJob(features.language));
305
+
306
+ if (features.queue !== "none") await writeFile(join(srcDir, "services", `queue.${ext}`), getQueueService(features.language, features.queue));
307
+ if (features.cache !== "none") await writeFile(join(srcDir, "services", `cache.${ext}`), getCacheService(features.language, features.cache));
308
+ if (features.trpc) {
309
+ await writeFile(join(srcDir, "trpc", `router.${ext}`), getTrpcRouter(features.language));
310
+ }
311
+ if (features.grpc) {
312
+ await writeFile(join(srcDir, "grpc", `server.${ext}`), getGrpcServer(features.language));
313
+ }
314
+ if (features.websocket) {
315
+ await writeFile(join(srcDir, "ws", `chat.${ext}`), getWebSocketScaffold(features.language));
316
+ }
260
317
 
261
- if (graphql) {
262
- const graphqlFiles = getGraphQLFiles(language);
263
- const ext = language === "typescript" ? "ts" : "js";
264
-
318
+ if (features.graphql) {
319
+ const graphqlFiles = getGraphQLFiles(features.language);
265
320
  await writeFile(join(srcDir, "graphql", `types.${ext}`), graphqlFiles["types.ts"]);
266
321
  await writeFile(join(srcDir, "graphql", `resolvers.${ext}`), graphqlFiles["resolvers.ts"]);
267
322
  await writeFile(join(srcDir, "graphql", `schema.${ext}`), graphqlFiles["schema.ts"]);
268
323
  }
269
324
 
270
- await writeFile(join(targetDir, ".env.development.example"), getEnvExample(telemetry));
271
- await writeFile(join(targetDir, ".env.production.example"), getEnvExample(telemetry));
272
-
273
- await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(telemetry));
274
- await writeFile(join(targetDir, ".env.production"), getEnvProduction(telemetry));
275
-
325
+ await writeFile(join(targetDir, ".env.development.example"), getEnvExample(features.telemetry));
326
+ await writeFile(join(targetDir, ".env.production.example"), getEnvExample(features.telemetry));
327
+ await writeFile(join(targetDir, ".env.development"), getEnvDevelopment(features.telemetry));
328
+ await writeFile(join(targetDir, ".env.production"), getEnvProduction(features.telemetry));
276
329
  await writeFile(join(targetDir, ".gitignore"), getGitignore());
277
330
 
278
- if (useDocker) {
279
- await writeFile(join(targetDir, "Dockerfile"), getDockerfile(language));
280
- await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(language));
331
+ if (features.docker) {
332
+ await writeFile(join(targetDir, "Dockerfile"), getDockerfile(features.language));
333
+ await writeFile(join(targetDir, "docker-compose.yml"), getDockerCompose(features.language));
281
334
  await writeFile(join(targetDir, ".dockerignore"), getDockerIgnore());
282
335
  }
283
- };
336
+ };