create-init-mtv-app 1.0.1 → 1.0.3
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.mjs +484 -97
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import inquirer from "inquirer";
|
|
|
8
8
|
let DB_PROVIDERS = /* @__PURE__ */ function(DB_PROVIDERS$1) {
|
|
9
9
|
DB_PROVIDERS$1["SUPABASE"] = "Supabase";
|
|
10
10
|
DB_PROVIDERS$1["DOCKER"] = "Docker";
|
|
11
|
+
DB_PROVIDERS$1["NONE"] = "None";
|
|
11
12
|
return DB_PROVIDERS$1;
|
|
12
13
|
}({});
|
|
13
14
|
|
|
@@ -16,6 +17,7 @@ let DB_PROVIDERS = /* @__PURE__ */ function(DB_PROVIDERS$1) {
|
|
|
16
17
|
let DB_TOOLS = /* @__PURE__ */ function(DB_TOOLS$1) {
|
|
17
18
|
DB_TOOLS$1["DRIZZLE_ORM"] = "Drizzle ORM";
|
|
18
19
|
DB_TOOLS$1["SUPABASE_JS_SDK"] = "Supabase JS SDK";
|
|
20
|
+
DB_TOOLS$1["NONE"] = "None";
|
|
19
21
|
return DB_TOOLS$1;
|
|
20
22
|
}({});
|
|
21
23
|
|
|
@@ -40,8 +42,91 @@ let UI_LIB = /* @__PURE__ */ function(UI_LIB$1) {
|
|
|
40
42
|
}({});
|
|
41
43
|
|
|
42
44
|
//#endregion
|
|
43
|
-
//#region src/installers/
|
|
44
|
-
async function
|
|
45
|
+
//#region src/installers/expressjs/expressjs.ts
|
|
46
|
+
async function createExpressApp(projectName) {
|
|
47
|
+
await fs.ensureDir(projectName);
|
|
48
|
+
await execa("npm", ["init", "-y"], {
|
|
49
|
+
cwd: projectName,
|
|
50
|
+
stdio: "inherit"
|
|
51
|
+
});
|
|
52
|
+
const dtosDir = path.join(projectName, "src", "dtos");
|
|
53
|
+
const handlersDir = path.join(projectName, "src", "handlers");
|
|
54
|
+
const repositoriesDir = path.join(projectName, "src", "repositories");
|
|
55
|
+
const servicesDir = path.join(projectName, "src", "services");
|
|
56
|
+
const routesDir = path.join(projectName, "src", "routes");
|
|
57
|
+
const middlewareDir = path.join(projectName, "src", "middleware");
|
|
58
|
+
await Promise.all([
|
|
59
|
+
fs.ensureDir(dtosDir),
|
|
60
|
+
fs.ensureDir(handlersDir),
|
|
61
|
+
fs.ensureDir(repositoriesDir),
|
|
62
|
+
fs.ensureDir(servicesDir),
|
|
63
|
+
fs.ensureDir(routesDir),
|
|
64
|
+
fs.ensureDir(middlewareDir)
|
|
65
|
+
]);
|
|
66
|
+
const idParamDtoPath = path.join(dtosDir, "idParam.dto.ts");
|
|
67
|
+
const paginationDtoPath = path.join(dtosDir, "pagination.dto.ts");
|
|
68
|
+
const responseDtoPath = path.join(dtosDir, "response.dto.ts");
|
|
69
|
+
await Promise.all([
|
|
70
|
+
fs.ensureFile(idParamDtoPath),
|
|
71
|
+
fs.ensureFile(paginationDtoPath),
|
|
72
|
+
fs.ensureFile(responseDtoPath)
|
|
73
|
+
]);
|
|
74
|
+
await Promise.all([
|
|
75
|
+
fs.writeFile(idParamDtoPath, idParamDtoTemplate),
|
|
76
|
+
fs.writeFile(paginationDtoPath, paginationDtoTemplate),
|
|
77
|
+
fs.writeFile(responseDtoPath, responseDtoTemplate)
|
|
78
|
+
]);
|
|
79
|
+
const appFilePath = path.join(projectName, "src", "app.ts");
|
|
80
|
+
const indexFilePath = path.join(projectName, "index.ts");
|
|
81
|
+
await Promise.all([fs.ensureFile(appFilePath), fs.ensureFile(indexFilePath)]);
|
|
82
|
+
await Promise.all([fs.writeFile(appFilePath, appTemplate), fs.writeFile(indexFilePath, indexTemplate)]);
|
|
83
|
+
}
|
|
84
|
+
const appTemplate = `import express from "express";
|
|
85
|
+
import helmet from "helmet";
|
|
86
|
+
import cors from "cors";
|
|
87
|
+
import cookieParser from "cookie-parser";
|
|
88
|
+
import "dotenv/config";
|
|
89
|
+
import swaggerUi from "swagger-ui-express";
|
|
90
|
+
import swaggerDocument from "./swagger.json";
|
|
91
|
+
import morgan from "morgan";
|
|
92
|
+
|
|
93
|
+
const app = express();
|
|
94
|
+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // mabe AI will help to generate the json file
|
|
95
|
+
app.use(helmet());
|
|
96
|
+
app.use(cors());
|
|
97
|
+
app.use(express.json());
|
|
98
|
+
app.use(express.urlencoded({ extended: true }));
|
|
99
|
+
app.use(cookieParser());
|
|
100
|
+
app.use(morgan("dev"));
|
|
101
|
+
|
|
102
|
+
app.get("/health", (_req, res) => {
|
|
103
|
+
res.json({ status: "ok" });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export default app;`;
|
|
107
|
+
const indexTemplate = `import app from "./src/app";
|
|
108
|
+
|
|
109
|
+
const port = process.env.PORT || 8080;
|
|
110
|
+
app.listen(port, () => {
|
|
111
|
+
console.log(\`Server is running on port \${ port }\`);
|
|
112
|
+
});`;
|
|
113
|
+
const idParamDtoTemplate = `export type IdParam = {
|
|
114
|
+
id: string;
|
|
115
|
+
};`;
|
|
116
|
+
const paginationDtoTemplate = `export type Pagination = {
|
|
117
|
+
page: number;
|
|
118
|
+
limit: number;
|
|
119
|
+
};`;
|
|
120
|
+
const responseDtoTemplate = `export type Response<T> = {
|
|
121
|
+
data?: T;
|
|
122
|
+
message: string;
|
|
123
|
+
statusCode: number;
|
|
124
|
+
success: boolean;
|
|
125
|
+
};`;
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/installers/expressjs/dependencies.ts
|
|
129
|
+
async function installDependencies$1(projectName, dependencies) {
|
|
45
130
|
await execa("npm", ["install", ...dependencies], {
|
|
46
131
|
cwd: projectName,
|
|
47
132
|
stdio: "inherit"
|
|
@@ -49,8 +134,8 @@ async function installDependencies(projectName, dependencies) {
|
|
|
49
134
|
}
|
|
50
135
|
|
|
51
136
|
//#endregion
|
|
52
|
-
//#region src/installers/devDependencies.ts
|
|
53
|
-
async function installDevDependencies(projectName, devDependencies) {
|
|
137
|
+
//#region src/installers/expressjs/devDependencies.ts
|
|
138
|
+
async function installDevDependencies$1(projectName, devDependencies) {
|
|
54
139
|
await execa("npm", [
|
|
55
140
|
"install",
|
|
56
141
|
"-D",
|
|
@@ -62,19 +147,12 @@ async function installDevDependencies(projectName, devDependencies) {
|
|
|
62
147
|
}
|
|
63
148
|
|
|
64
149
|
//#endregion
|
|
65
|
-
//#region src/installers/
|
|
66
|
-
async function
|
|
150
|
+
//#region src/installers/nestjs.ts
|
|
151
|
+
async function createNestApp(projectName) {
|
|
67
152
|
await execa("npx", [
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"--tailwind",
|
|
72
|
-
"--react-compiler",
|
|
73
|
-
"--biome",
|
|
74
|
-
"--app",
|
|
75
|
-
"--src-dir",
|
|
76
|
-
"--import-alias",
|
|
77
|
-
"@/*"
|
|
153
|
+
"@nestjs/cli",
|
|
154
|
+
"new",
|
|
155
|
+
projectName
|
|
78
156
|
], { stdio: "inherit" });
|
|
79
157
|
}
|
|
80
158
|
|
|
@@ -92,27 +170,7 @@ async function createReactApp(projectName) {
|
|
|
92
170
|
}
|
|
93
171
|
|
|
94
172
|
//#endregion
|
|
95
|
-
//#region src/
|
|
96
|
-
async function createNextExpressApp(projectName) {
|
|
97
|
-
await execa("npx", [
|
|
98
|
-
"create-next-app@latest",
|
|
99
|
-
projectName,
|
|
100
|
-
"--use-npm"
|
|
101
|
-
], { stdio: "inherit" });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
//#endregion
|
|
105
|
-
//#region src/installers/nestjs.ts
|
|
106
|
-
async function createNestApp(projectName) {
|
|
107
|
-
await execa("npx", [
|
|
108
|
-
"@nestjs/cli",
|
|
109
|
-
"new",
|
|
110
|
-
projectName
|
|
111
|
-
], { stdio: "inherit" });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region src/setups/husky_lint-staged.ts
|
|
173
|
+
//#region src/configs/husky_lint-staged.ts
|
|
116
174
|
async function setupHuskyLintStaged(projectName) {
|
|
117
175
|
const preCommitPath = path.join(projectName, ".husky", "pre-commit");
|
|
118
176
|
const prePushPath = path.join(projectName, ".husky", "pre-push");
|
|
@@ -121,24 +179,27 @@ async function setupHuskyLintStaged(projectName) {
|
|
|
121
179
|
const packageJsonPath = path.join(projectName, "package.json");
|
|
122
180
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
123
181
|
packageJson["lint-staged"] = { "*.{ts,tsx,js,jsx,json,css}": ["npm run format", "npm run lint"] };
|
|
182
|
+
packageJson.scripts["prepare"] = "husky";
|
|
183
|
+
packageJson.scripts["lint"] = "biome check";
|
|
184
|
+
packageJson.scripts["format"] = "biome format --write";
|
|
124
185
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
125
186
|
}
|
|
126
187
|
|
|
127
188
|
//#endregion
|
|
128
|
-
//#region src/
|
|
129
|
-
async function
|
|
189
|
+
//#region src/configs/nextjs/supabase_client.ts
|
|
190
|
+
async function setupSupabaseClient(projectDir) {
|
|
130
191
|
const supabaseDir = path.join(projectDir, "src", "lib", "supabase");
|
|
131
192
|
await fs.ensureDir(supabaseDir);
|
|
132
193
|
const supabaseClientPath = path.join(supabaseDir, "client.ts");
|
|
133
194
|
const supabaseServerPath = path.join(supabaseDir, "server.ts");
|
|
134
195
|
const supabaseAdminPath = path.join(supabaseDir, "admin.ts");
|
|
135
196
|
await Promise.all([
|
|
136
|
-
fs.writeFile(supabaseClientPath, supabaseClientTemplate),
|
|
197
|
+
fs.writeFile(supabaseClientPath, supabaseClientTemplate$1),
|
|
137
198
|
fs.writeFile(supabaseServerPath, supabaseServerTemplate),
|
|
138
|
-
fs.writeFile(supabaseAdminPath, supabaseAdminTemplate)
|
|
199
|
+
fs.writeFile(supabaseAdminPath, supabaseAdminTemplate$1)
|
|
139
200
|
]);
|
|
140
201
|
}
|
|
141
|
-
const supabaseClientTemplate = `import { createBrowserClient } from "@supabase/ssr";
|
|
202
|
+
const supabaseClientTemplate$1 = `import { createBrowserClient } from "@supabase/ssr";
|
|
142
203
|
import { sharedEnv } from "../../../env.shared";
|
|
143
204
|
|
|
144
205
|
export function createClient() {
|
|
@@ -177,7 +238,7 @@ export async function createClient() {
|
|
|
177
238
|
);
|
|
178
239
|
}
|
|
179
240
|
`.trimStart();
|
|
180
|
-
const supabaseAdminTemplate = `import "server-only";
|
|
241
|
+
const supabaseAdminTemplate$1 = `import "server-only";
|
|
181
242
|
import { createClient } from "@supabase/supabase-js";
|
|
182
243
|
import { serverEnv } from "@/backend/env.server";
|
|
183
244
|
import { sharedEnv } from "../../../env.shared";
|
|
@@ -196,8 +257,8 @@ export function createAdminClient() {
|
|
|
196
257
|
`.trimStart();
|
|
197
258
|
|
|
198
259
|
//#endregion
|
|
199
|
-
//#region src/
|
|
200
|
-
async function
|
|
260
|
+
//#region src/configs/nextjs/env.ts
|
|
261
|
+
async function setupEnv(projectName, isSupabase = false) {
|
|
201
262
|
const sharedEnvPath = path.join(projectName, "env.shared.ts");
|
|
202
263
|
const backendDir = path.join(projectName, "src", "backend");
|
|
203
264
|
const serverEnvPath = path.join(backendDir, "env.server.ts");
|
|
@@ -252,7 +313,7 @@ export const sharedEnv = parsed.data;
|
|
|
252
313
|
`.trimStart();
|
|
253
314
|
|
|
254
315
|
//#endregion
|
|
255
|
-
//#region src/
|
|
316
|
+
//#region src/configs/nextjs/drizzle.ts
|
|
256
317
|
async function setupDrizzle(projectName) {
|
|
257
318
|
const schemasDir = path.join(projectName, "src", "backend", "db", "schemas");
|
|
258
319
|
const migrationsDir = path.join(projectName, "src", "backend", "db", "migrations");
|
|
@@ -262,7 +323,7 @@ async function setupDrizzle(projectName) {
|
|
|
262
323
|
fs.ensureDir(migrationsDir),
|
|
263
324
|
fs.ensureFile(drizzleConfigPath)
|
|
264
325
|
]);
|
|
265
|
-
await fs.writeFile(drizzleConfigPath, drizzleConfigTemplate);
|
|
326
|
+
await fs.writeFile(drizzleConfigPath, drizzleConfigTemplate$1);
|
|
266
327
|
const packageJsonPath = path.join(projectName, "package.json");
|
|
267
328
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
268
329
|
packageJson.scripts["db:generate"] = "npx drizzle-kit generate --config=./src/backend/db/drizzle.config.ts";
|
|
@@ -272,7 +333,7 @@ async function setupDrizzle(projectName) {
|
|
|
272
333
|
packageJson.scripts["db:check"] = "npx drizzle-kit check --config=./src/backend/db/drizzle.config.ts";
|
|
273
334
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
274
335
|
}
|
|
275
|
-
const drizzleConfigTemplate = `import "server-only";
|
|
336
|
+
const drizzleConfigTemplate$1 = `import "server-only";
|
|
276
337
|
import { defineConfig } from "drizzle-kit";
|
|
277
338
|
import { serverEnv } from "../env.server";
|
|
278
339
|
|
|
@@ -283,15 +344,19 @@ export default defineConfig({
|
|
|
283
344
|
dbCredentials: {
|
|
284
345
|
url: serverEnv.DATABASE_URL,
|
|
285
346
|
},
|
|
347
|
+
verbose: true,
|
|
348
|
+
strict: true
|
|
286
349
|
});
|
|
287
350
|
`.trimStart();
|
|
288
351
|
|
|
289
352
|
//#endregion
|
|
290
|
-
//#region src/
|
|
353
|
+
//#region src/configs/nextjs/shadcn.ts
|
|
291
354
|
async function setupShadcn(projectName) {
|
|
292
355
|
await execa("npx", [
|
|
293
356
|
"shadcn@latest",
|
|
294
357
|
"init",
|
|
358
|
+
"-b",
|
|
359
|
+
"neutral",
|
|
295
360
|
"dialog",
|
|
296
361
|
"avatar",
|
|
297
362
|
"button-group",
|
|
@@ -319,7 +384,7 @@ async function setupShadcn(projectName) {
|
|
|
319
384
|
}
|
|
320
385
|
|
|
321
386
|
//#endregion
|
|
322
|
-
//#region src/
|
|
387
|
+
//#region src/configs/nextjs/biome.ts
|
|
323
388
|
async function setupBiome(projectName, isShadcn) {
|
|
324
389
|
const biomePath = path.join(projectName, "biome.json");
|
|
325
390
|
const biomeConfig = await fs.readJson(biomePath);
|
|
@@ -330,9 +395,9 @@ async function setupBiome(projectName, isShadcn) {
|
|
|
330
395
|
}
|
|
331
396
|
|
|
332
397
|
//#endregion
|
|
333
|
-
//#region src/
|
|
334
|
-
async function
|
|
335
|
-
execa("npx", ["supabase", "init"], {
|
|
398
|
+
//#region src/configs/nextjs/supabase_local.ts
|
|
399
|
+
async function setupSupabaseLocal(projectDir) {
|
|
400
|
+
await execa("npx", ["supabase", "init"], {
|
|
336
401
|
cwd: projectDir,
|
|
337
402
|
stdio: "inherit"
|
|
338
403
|
});
|
|
@@ -345,21 +410,22 @@ async function setupNextSupabaseLocal(projectDir) {
|
|
|
345
410
|
}
|
|
346
411
|
|
|
347
412
|
//#endregion
|
|
348
|
-
//#region src/
|
|
413
|
+
//#region src/configs/nextjs/jest.ts
|
|
349
414
|
async function setupJest(projectName) {
|
|
350
415
|
const jestConfigPath = path.join(projectName, "jest.config.ts");
|
|
351
416
|
await fs.ensureFile(jestConfigPath);
|
|
352
|
-
await fs.writeFile(jestConfigPath, jestConfigTemplate);
|
|
417
|
+
await fs.writeFile(jestConfigPath, jestConfigTemplate$1);
|
|
353
418
|
const packageJsonPath = path.join(projectName, "package.json");
|
|
354
419
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
355
420
|
packageJson.scripts["test:unit"] = "jest --testPathPatterns=unit";
|
|
356
421
|
packageJson.scripts["test:integration"] = "jest --testPathPatterns=integration";
|
|
422
|
+
packageJson.scripts["test"] = "jest";
|
|
357
423
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
358
424
|
const testUnitDir = path.join(projectName, "src", "backend", "test", "unit");
|
|
359
425
|
const testIntegrationDir = path.join(projectName, "src", "backend", "test", "integration");
|
|
360
426
|
await Promise.all([fs.ensureDir(testUnitDir), fs.ensureDir(testIntegrationDir)]);
|
|
361
427
|
}
|
|
362
|
-
const jestConfigTemplate = `import type { Config } from "jest";
|
|
428
|
+
const jestConfigTemplate$1 = `import type { Config } from "jest";
|
|
363
429
|
|
|
364
430
|
const config: Config = {
|
|
365
431
|
clearMocks: true,
|
|
@@ -382,55 +448,377 @@ const config: Config = {
|
|
|
382
448
|
export default config;
|
|
383
449
|
`.trimStart();
|
|
384
450
|
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/configs/docker_postgres.ts
|
|
453
|
+
async function setupDockerPostgres(projectName) {
|
|
454
|
+
const dockerComposePath = path.join(projectName, "docker-compose.yml");
|
|
455
|
+
await fs.ensureFile(dockerComposePath);
|
|
456
|
+
await fs.writeFile(dockerComposePath, dockerComposeTemplate);
|
|
457
|
+
}
|
|
458
|
+
const dockerComposeTemplate = `
|
|
459
|
+
version: '3'
|
|
460
|
+
services:
|
|
461
|
+
postgres:
|
|
462
|
+
image: postgres:17
|
|
463
|
+
ports:
|
|
464
|
+
- 5432:5432
|
|
465
|
+
environment:
|
|
466
|
+
- POSTGRES_USER=postgres
|
|
467
|
+
- POSTGRES_PASSWORD=postgres
|
|
468
|
+
- POSTGRES_DB=postgres
|
|
469
|
+
volumes:
|
|
470
|
+
- postgres_data:/var/lib/postgresql/data
|
|
471
|
+
volumes:
|
|
472
|
+
postgres_data:
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/configs/expressjs/env.ts
|
|
477
|
+
async function configEnv(projectName, isSupabase = false) {
|
|
478
|
+
const envPath = path.join(projectName, "src", "env.ts");
|
|
479
|
+
await fs.ensureFile(envPath);
|
|
480
|
+
await fs.writeFile(envPath, envTemplate(isSupabase));
|
|
481
|
+
}
|
|
482
|
+
const envTemplate = (isSupabase) => `import { z } from "zod";
|
|
483
|
+
|
|
484
|
+
const envSchema = z.object({
|
|
485
|
+
NODE_ENV: z
|
|
486
|
+
.enum(["development", "production", "test"])
|
|
487
|
+
.default("development"),
|
|
488
|
+
DATABASE_URL: z.string().min(1, "Database URL is required"),
|
|
489
|
+
${isSupabase ? "SUPABASE_SERVICE_ROLE_KEY: z.string().min(1, \"Supabase service role key is required\")," : ""}
|
|
490
|
+
${isSupabase ? "SUPABASE_URL: z.string().min(1, \"Supabase URL is required\")," : ""}
|
|
491
|
+
${isSupabase ? "SUPABASE_ANON_KEY: z.string().min(1, \"Supabase anon key is required\")," : ""}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const parsed = envSchema.safeParse(process.env);
|
|
495
|
+
|
|
496
|
+
if (!parsed.success) {
|
|
497
|
+
console.error("❌ Invalid environment variables:");
|
|
498
|
+
console.error(JSON.stringify(parsed.error.format(), null, 2));
|
|
499
|
+
throw new Error("Invalid environment variables");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export const env = parsed.data;
|
|
503
|
+
`.trimStart();
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/configs/expressjs/supabase_client.ts
|
|
507
|
+
async function configSupabaseClient(projectName) {
|
|
508
|
+
const supabaseDir = path.join(projectName, "src", "db", "supabase");
|
|
509
|
+
await fs.ensureDir(supabaseDir);
|
|
510
|
+
const supabaseClientPath = path.join(supabaseDir, "client.ts");
|
|
511
|
+
const supabaseAdminPath = path.join(supabaseDir, "admin.ts");
|
|
512
|
+
await Promise.all([fs.ensureFile(supabaseClientPath), fs.ensureFile(supabaseAdminPath)]);
|
|
513
|
+
await Promise.all([fs.writeFile(supabaseClientPath, supabaseClientTemplate), fs.writeFile(supabaseAdminPath, supabaseAdminTemplate)]);
|
|
514
|
+
}
|
|
515
|
+
const supabaseClientTemplate = `import { createClient } from "@supabase/supabase-js";
|
|
516
|
+
import { env } from "../../env";
|
|
517
|
+
|
|
518
|
+
const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY);
|
|
519
|
+
export default supabase;
|
|
520
|
+
`;
|
|
521
|
+
const supabaseAdminTemplate = `import { createClient } from "@supabase/supabase-js";
|
|
522
|
+
import { env } from "../../env";
|
|
523
|
+
|
|
524
|
+
const supabaseAdmin = createClient(
|
|
525
|
+
env.SUPABASE_URL,
|
|
526
|
+
env.SUPABASE_SERVICE_ROLE_KEY,
|
|
527
|
+
{
|
|
528
|
+
auth: {
|
|
529
|
+
autoRefreshToken: false,
|
|
530
|
+
persistSession: false,
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
export default supabaseAdmin;
|
|
535
|
+
`;
|
|
536
|
+
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/configs/expressjs/supabase_local.ts
|
|
539
|
+
async function configSupabaseLocal(projectDir) {
|
|
540
|
+
execa("npx", ["supabase", "init"], {
|
|
541
|
+
cwd: projectDir,
|
|
542
|
+
stdio: "inherit"
|
|
543
|
+
});
|
|
544
|
+
const migrationsDir = path.join(projectDir, "supabase", "migrations");
|
|
545
|
+
const schemaDir = path.join(projectDir, "supabase", "schemas");
|
|
546
|
+
const dbDir = path.join(schemaDir, "db");
|
|
547
|
+
const storageDir = path.join(schemaDir, "storage");
|
|
548
|
+
await Promise.all([fs.ensureDir(migrationsDir), fs.ensureDir(schemaDir)]);
|
|
549
|
+
await Promise.all([fs.ensureDir(dbDir), fs.ensureDir(storageDir)]);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/configs/expressjs/drizzle.ts
|
|
554
|
+
async function configDrizzle(projectName) {
|
|
555
|
+
const schemasDir = path.join(projectName, "src", "db", "schemas");
|
|
556
|
+
const migrationsDir = path.join(projectName, "src", "db", "migrations");
|
|
557
|
+
const drizzleConfigPath = path.join(projectName, "drizzle.config.ts");
|
|
558
|
+
await Promise.all([
|
|
559
|
+
fs.ensureDir(schemasDir),
|
|
560
|
+
fs.ensureDir(migrationsDir),
|
|
561
|
+
fs.ensureFile(drizzleConfigPath)
|
|
562
|
+
]);
|
|
563
|
+
await fs.writeFile(drizzleConfigPath, drizzleConfigTemplate);
|
|
564
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
565
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
566
|
+
packageJson.scripts["db:generate"] = "npx drizzle-kit generate --config=./drizzle.config.ts";
|
|
567
|
+
packageJson.scripts["db:migrate"] = "npx drizzle-kit push --config=./drizzle.config.ts";
|
|
568
|
+
packageJson.scripts["db:studio"] = "npx drizzle-kit studio --verbose --config=./drizzle.config.ts";
|
|
569
|
+
packageJson.scripts["db:pull"] = "npx drizzle-kit pull --config=./drizzle.config.ts";
|
|
570
|
+
packageJson.scripts["db:check"] = "npx drizzle-kit check --config=./drizzle.config.ts";
|
|
571
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
572
|
+
}
|
|
573
|
+
const drizzleConfigTemplate = `import { defineConfig } from "drizzle-kit";
|
|
574
|
+
import { env } from "./src/env";
|
|
575
|
+
|
|
576
|
+
export default defineConfig({
|
|
577
|
+
dialect: "postgresql",
|
|
578
|
+
schema: "./src/db/schemas/*",
|
|
579
|
+
out: "./src/db/migrations",
|
|
580
|
+
dbCredentials: {
|
|
581
|
+
url: env.DATABASE_URL,
|
|
582
|
+
},
|
|
583
|
+
verbose: true,
|
|
584
|
+
strict: true,
|
|
585
|
+
});
|
|
586
|
+
`.trimStart();
|
|
587
|
+
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/configs/expressjs/biome.ts
|
|
590
|
+
async function configBiome(projectName) {
|
|
591
|
+
execa("npx", ["biome", "init"], {
|
|
592
|
+
cwd: projectName,
|
|
593
|
+
stdio: "inherit"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/configs/expressjs/jest.ts
|
|
599
|
+
async function configJest(projectName) {
|
|
600
|
+
const jestConfigPath = path.join(projectName, "jest.config.mjs");
|
|
601
|
+
await fs.ensureFile(jestConfigPath);
|
|
602
|
+
await fs.writeFile(jestConfigPath, jestConfigTemplate);
|
|
603
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
604
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
605
|
+
packageJson.scripts["test:unit"] = "jest --testPathPatterns=unit";
|
|
606
|
+
packageJson.scripts["test:integration"] = "jest --testPathPatterns=integration";
|
|
607
|
+
packageJson.scripts["test"] = "jest";
|
|
608
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
609
|
+
const testUnitDir = path.join(projectName, "test", "unit");
|
|
610
|
+
const testIntegrationDir = path.join(projectName, "test", "integration");
|
|
611
|
+
await Promise.all([fs.ensureDir(testUnitDir), fs.ensureDir(testIntegrationDir)]);
|
|
612
|
+
}
|
|
613
|
+
const jestConfigTemplate = `/** @type {import('jest').Config} */
|
|
614
|
+
|
|
615
|
+
export default {
|
|
616
|
+
clearMocks: true,
|
|
617
|
+
coverageProvider: "v8",
|
|
618
|
+
moduleNameMapper: {
|
|
619
|
+
"^@/(.*)$": "<rootDir>/src/$1",
|
|
620
|
+
},
|
|
621
|
+
preset: "ts-jest/presets/default-esm",
|
|
622
|
+
testEnvironment: "node",
|
|
623
|
+
};
|
|
624
|
+
`.trimStart();
|
|
625
|
+
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/configs/expressjs/typescript.ts
|
|
628
|
+
async function configTypescript(projectName) {
|
|
629
|
+
await execa("npx", ["tsc", "--init"], {
|
|
630
|
+
cwd: projectName,
|
|
631
|
+
stdio: "inherit"
|
|
632
|
+
});
|
|
633
|
+
const tsconfigPath = path.join(projectName, "tsconfig.json");
|
|
634
|
+
await fs.writeFile(tsconfigPath, tsconfigTemplate);
|
|
635
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
636
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
637
|
+
packageJson.scripts["start"] = "node ./dist/index.js";
|
|
638
|
+
packageJson.scripts["build"] = "tsc --build";
|
|
639
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
640
|
+
}
|
|
641
|
+
const tsconfigTemplate = `{
|
|
642
|
+
"compilerOptions": {
|
|
643
|
+
"rootDir": ".",
|
|
644
|
+
"outDir": "./dist",
|
|
645
|
+
|
|
646
|
+
"module": "CommonJS",
|
|
647
|
+
"target": "ES2022",
|
|
648
|
+
"esModuleInterop": true,
|
|
649
|
+
"resolveJsonModule": true,
|
|
650
|
+
"types": ["jest"],
|
|
651
|
+
|
|
652
|
+
"sourceMap": true,
|
|
653
|
+
"declaration": true,
|
|
654
|
+
"declarationMap": true,
|
|
655
|
+
|
|
656
|
+
"noUncheckedIndexedAccess": true,
|
|
657
|
+
"exactOptionalPropertyTypes": true,
|
|
658
|
+
|
|
659
|
+
"strict": true,
|
|
660
|
+
"isolatedModules": true,
|
|
661
|
+
"noUncheckedSideEffectImports": true,
|
|
662
|
+
"moduleDetection": "force",
|
|
663
|
+
"skipLibCheck": true
|
|
664
|
+
}
|
|
665
|
+
}`.trimStart();
|
|
666
|
+
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/configs/expressjs/nodemon.ts
|
|
669
|
+
async function configNodemon(projectName) {
|
|
670
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
671
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
672
|
+
packageJson.scripts["dev"] = "nodemon index.ts";
|
|
673
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/setup/expressjs.ts
|
|
678
|
+
async function setupExpressjs(projectName, dbProvider, dbTool, isNeverThrow) {
|
|
679
|
+
await createExpressApp(projectName);
|
|
680
|
+
const dependencies = [
|
|
681
|
+
"cookie-parser",
|
|
682
|
+
"cors",
|
|
683
|
+
"dotenv",
|
|
684
|
+
"drizzle-orm",
|
|
685
|
+
"express",
|
|
686
|
+
"helmet",
|
|
687
|
+
"morgan",
|
|
688
|
+
"swagger-ui-express",
|
|
689
|
+
"zod"
|
|
690
|
+
];
|
|
691
|
+
const devDependencies = [
|
|
692
|
+
"@biomejs/biome",
|
|
693
|
+
"@types/cookie-parser",
|
|
694
|
+
"@types/cors",
|
|
695
|
+
"@types/dotenv",
|
|
696
|
+
"@types/express",
|
|
697
|
+
"@types/helmet",
|
|
698
|
+
"@types/jest",
|
|
699
|
+
"@types/morgan",
|
|
700
|
+
"@types/swagger-ui-express",
|
|
701
|
+
"husky",
|
|
702
|
+
"jest",
|
|
703
|
+
"lint-staged",
|
|
704
|
+
"nodemon",
|
|
705
|
+
"ts-jest",
|
|
706
|
+
"ts-node",
|
|
707
|
+
"typescript"
|
|
708
|
+
];
|
|
709
|
+
if (dbProvider === DB_PROVIDERS.SUPABASE) dependencies.push("@supabase/supabase-js");
|
|
710
|
+
if (dbTool === DB_TOOLS.DRIZZLE_ORM) {
|
|
711
|
+
dependencies.push("drizzle-orm", "postgres");
|
|
712
|
+
devDependencies.push("drizzle-kit");
|
|
713
|
+
}
|
|
714
|
+
if (isNeverThrow) dependencies.push("neverthrow");
|
|
715
|
+
await installDependencies$1(projectName, dependencies);
|
|
716
|
+
await installDevDependencies$1(projectName, devDependencies);
|
|
717
|
+
await setupHuskyLintStaged(projectName);
|
|
718
|
+
await configEnv(projectName, dbProvider === DB_PROVIDERS.SUPABASE);
|
|
719
|
+
if (dbProvider === DB_PROVIDERS.DOCKER) await setupDockerPostgres(projectName);
|
|
720
|
+
if (dbProvider === DB_PROVIDERS.SUPABASE) await configSupabaseClient(projectName);
|
|
721
|
+
if (dbTool === DB_TOOLS.SUPABASE_JS_SDK) await configSupabaseLocal(projectName);
|
|
722
|
+
if (dbTool === DB_TOOLS.DRIZZLE_ORM) await configDrizzle(projectName);
|
|
723
|
+
await configBiome(projectName);
|
|
724
|
+
await configJest(projectName);
|
|
725
|
+
await configTypescript(projectName);
|
|
726
|
+
await configNodemon(projectName);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/installers/nextjs/dependencies.ts
|
|
731
|
+
async function installDependencies(projectName, dependencies) {
|
|
732
|
+
await execa("npm", ["install", ...dependencies], {
|
|
733
|
+
cwd: projectName,
|
|
734
|
+
stdio: "inherit"
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/installers/nextjs/devDependencies.ts
|
|
740
|
+
async function installDevDependencies(projectName, devDependencies) {
|
|
741
|
+
await execa("npm", [
|
|
742
|
+
"install",
|
|
743
|
+
"-D",
|
|
744
|
+
...devDependencies
|
|
745
|
+
], {
|
|
746
|
+
cwd: projectName,
|
|
747
|
+
stdio: "inherit"
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
//#endregion
|
|
752
|
+
//#region src/installers/nextjs/nexjs.ts
|
|
753
|
+
async function createNextApp(projectName) {
|
|
754
|
+
await execa("npx", [
|
|
755
|
+
"create-next-app@latest",
|
|
756
|
+
projectName,
|
|
757
|
+
"--ts",
|
|
758
|
+
"--tailwind",
|
|
759
|
+
"--react-compiler",
|
|
760
|
+
"--biome",
|
|
761
|
+
"--app",
|
|
762
|
+
"--src-dir",
|
|
763
|
+
"--import-alias",
|
|
764
|
+
"@/*"
|
|
765
|
+
], { stdio: "inherit" });
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/setup/nextjs.ts
|
|
770
|
+
async function setupNextjs(projectName, dbProvider, dbTool, uiLibrary, isNeverThrow) {
|
|
771
|
+
await createNextApp(projectName);
|
|
772
|
+
const dependencies = [
|
|
773
|
+
"react-hook-form",
|
|
774
|
+
"@hookform/resolvers",
|
|
775
|
+
"zod",
|
|
776
|
+
"server-only"
|
|
777
|
+
];
|
|
778
|
+
const devDependencies = [
|
|
779
|
+
"husky",
|
|
780
|
+
"lint-staged",
|
|
781
|
+
"jest",
|
|
782
|
+
"@types/jest",
|
|
783
|
+
"ts-jest"
|
|
784
|
+
];
|
|
785
|
+
if (dbProvider === DB_PROVIDERS.SUPABASE) dependencies.push("@supabase/ssr", "@supabase/supabase-js");
|
|
786
|
+
if (dbTool === DB_TOOLS.DRIZZLE_ORM) {
|
|
787
|
+
dependencies.push("drizzle-orm", "postgres");
|
|
788
|
+
devDependencies.push("drizzle-kit");
|
|
789
|
+
}
|
|
790
|
+
if (isNeverThrow) dependencies.push("neverthrow");
|
|
791
|
+
await installDependencies(projectName, dependencies);
|
|
792
|
+
await installDevDependencies(projectName, devDependencies);
|
|
793
|
+
await setupHuskyLintStaged(projectName);
|
|
794
|
+
await setupEnv(projectName, dbProvider === DB_PROVIDERS.SUPABASE);
|
|
795
|
+
if (dbProvider === DB_PROVIDERS.SUPABASE) await setupSupabaseClient(projectName);
|
|
796
|
+
if (dbProvider === DB_PROVIDERS.DOCKER) await setupDockerPostgres(projectName);
|
|
797
|
+
if (dbTool === DB_TOOLS.SUPABASE_JS_SDK) await setupSupabaseLocal(projectName);
|
|
798
|
+
if (dbTool === DB_TOOLS.DRIZZLE_ORM) await setupDrizzle(projectName);
|
|
799
|
+
if (uiLibrary === UI_LIB.SHADCN) await setupShadcn(projectName);
|
|
800
|
+
await setupBiome(projectName, uiLibrary === UI_LIB.SHADCN);
|
|
801
|
+
await setupJest(projectName);
|
|
802
|
+
}
|
|
803
|
+
|
|
385
804
|
//#endregion
|
|
386
805
|
//#region src/create.ts
|
|
387
806
|
async function createProject(answers) {
|
|
388
|
-
const { projectName, framework, dbProvider, dbTool, uiLibrary,
|
|
807
|
+
const { projectName, framework, dbProvider, dbTool, uiLibrary, isNeverThrow } = answers;
|
|
389
808
|
switch (framework) {
|
|
390
|
-
case FRAMEWORKS.NEXT:
|
|
391
|
-
await
|
|
392
|
-
const isSupabase = dbProvider === DB_PROVIDERS.SUPABASE;
|
|
393
|
-
const dependencies = [
|
|
394
|
-
"react-hook-form",
|
|
395
|
-
"@hookform/resolvers",
|
|
396
|
-
"zod",
|
|
397
|
-
"server-only"
|
|
398
|
-
];
|
|
399
|
-
const devDependencies = [
|
|
400
|
-
"husky",
|
|
401
|
-
"lint-staged",
|
|
402
|
-
"jest",
|
|
403
|
-
"@types/jest",
|
|
404
|
-
"ts-jest"
|
|
405
|
-
];
|
|
406
|
-
if (isSupabase) dependencies.push("@supabase/ssr", "@supabase/supabase-js");
|
|
407
|
-
if (dbTool === DB_TOOLS.DRIZZLE_ORM) {
|
|
408
|
-
dependencies.push("drizzle-orm", "postgres");
|
|
409
|
-
devDependencies.push("drizzle-kit");
|
|
410
|
-
}
|
|
411
|
-
await installDependencies(projectName, dependencies);
|
|
412
|
-
await installDevDependencies(projectName, devDependencies);
|
|
413
|
-
await setupHuskyLintStaged(projectName);
|
|
414
|
-
await setupNextEnv(projectName, isSupabase);
|
|
415
|
-
if (isSupabase) await setupNextSupabaseClient(projectName);
|
|
416
|
-
if (dbTool === DB_TOOLS.SUPABASE_JS_SDK) await setupNextSupabaseLocal(projectName);
|
|
417
|
-
if (dbTool === DB_TOOLS.DRIZZLE_ORM) await setupDrizzle(projectName);
|
|
418
|
-
if (uiLibrary === UI_LIB.SHADCN) await setupShadcn(projectName);
|
|
419
|
-
await setupBiome(projectName, uiLibrary === UI_LIB.SHADCN);
|
|
420
|
-
await setupJest(projectName);
|
|
809
|
+
case FRAMEWORKS.NEXT:
|
|
810
|
+
await setupNextjs(projectName, dbProvider, dbTool, uiLibrary, isNeverThrow);
|
|
421
811
|
break;
|
|
422
|
-
}
|
|
423
812
|
case FRAMEWORKS.NEXT_EXPRESS:
|
|
424
|
-
|
|
425
|
-
createNextExpressApp(projectName + "-be");
|
|
813
|
+
await Promise.all([setupNextjs(projectName + "-fe", DB_PROVIDERS.NONE, DB_TOOLS.NONE, uiLibrary, isNeverThrow), setupExpressjs(projectName + "-be", dbProvider, dbTool, isNeverThrow)]);
|
|
426
814
|
break;
|
|
427
815
|
case FRAMEWORKS.NEXT_NEST:
|
|
428
|
-
|
|
816
|
+
await setupNextjs(projectName + "-fe", DB_PROVIDERS.NONE, DB_TOOLS.NONE, uiLibrary, isNeverThrow);
|
|
429
817
|
createNestApp(projectName + "-be");
|
|
430
818
|
break;
|
|
431
819
|
case FRAMEWORKS.REACT_EXPRESS:
|
|
432
820
|
createReactApp(projectName + "-fe");
|
|
433
|
-
|
|
821
|
+
createExpressApp(projectName + "-be");
|
|
434
822
|
break;
|
|
435
823
|
case FRAMEWORKS.REACT_NEST:
|
|
436
824
|
createReactApp(projectName + "-fe");
|
|
@@ -506,10 +894,9 @@ async function ask() {
|
|
|
506
894
|
},
|
|
507
895
|
{
|
|
508
896
|
type: "confirm",
|
|
509
|
-
name: "
|
|
510
|
-
message: "Do you want to use
|
|
511
|
-
default:
|
|
512
|
-
when: (answers) => ![FRAMEWORKS.NEXT, FRAMEWORKS.REACT].includes(answers.framework)
|
|
897
|
+
name: "isNeverThrow",
|
|
898
|
+
message: "Do you want to use nerverthrow?",
|
|
899
|
+
default: false
|
|
513
900
|
}
|
|
514
901
|
]);
|
|
515
902
|
}
|