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 +110 -54
- package/dist/templates/configFiles.js +39 -49
- package/dist/templates/index.js +2 -0
- package/dist/templates/packageJson.js +22 -4
- package/dist/templates/routes.js +34 -25
- package/dist/templates/services.js +262 -0
- package/package.json +1 -1
- package/src/index.ts +142 -89
- package/src/templates/configFiles.ts +50 -51
- package/src/templates/index.ts +3 -0
- package/src/templates/packageJson.ts +21 -6
- package/src/templates/routes.ts +46 -23
- package/src/templates/services.ts +270 -0
|
@@ -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
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
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 (
|
|
184
|
-
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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,
|
|
287
|
+
await writeFile(join(srcDir, `app.${ext}`), getMainFile(features.language, features.graphql, features));
|
|
243
288
|
|
|
244
|
-
await writeFile(join(srcDir, "routes",
|
|
245
|
-
await writeFile(join(srcDir, "routes",
|
|
246
|
-
await writeFile(join(srcDir, "routes",
|
|
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",
|
|
249
|
-
await writeFile(join(srcDir, "controllers",
|
|
250
|
-
await writeFile(join(srcDir, "controllers",
|
|
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",
|
|
253
|
-
await writeFile(join(srcDir, "middlewares",
|
|
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",
|
|
256
|
-
await writeFile(join(srcDir, "schemas",
|
|
257
|
-
await writeFile(join(srcDir, "schemas",
|
|
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",
|
|
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.
|
|
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 (
|
|
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
|
+
};
|