create-init-mtv-app 1.0.0
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 +492 -0
- package/package.json +46 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
|
|
7
|
+
//#region src/enum/db_provider.ts
|
|
8
|
+
let DB_PROVIDERS = /* @__PURE__ */ function(DB_PROVIDERS$1) {
|
|
9
|
+
DB_PROVIDERS$1["SUPABASE"] = "Supabase";
|
|
10
|
+
DB_PROVIDERS$1["DOCKER"] = "Docker";
|
|
11
|
+
return DB_PROVIDERS$1;
|
|
12
|
+
}({});
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/enum/db_tools.ts
|
|
16
|
+
let DB_TOOLS = /* @__PURE__ */ function(DB_TOOLS$1) {
|
|
17
|
+
DB_TOOLS$1["DRIZZLE_ORM"] = "Drizzle ORM";
|
|
18
|
+
DB_TOOLS$1["SUPABASE_JS_SDK"] = "Supabase JS SDK";
|
|
19
|
+
return DB_TOOLS$1;
|
|
20
|
+
}({});
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/enum/framework.ts
|
|
24
|
+
let FRAMEWORKS = /* @__PURE__ */ function(FRAMEWORKS$1) {
|
|
25
|
+
FRAMEWORKS$1["NEXT"] = "next";
|
|
26
|
+
FRAMEWORKS$1["REACT"] = "react";
|
|
27
|
+
FRAMEWORKS$1["NEXT_EXPRESS"] = "next-express";
|
|
28
|
+
FRAMEWORKS$1["REACT_EXPRESS"] = "react-express";
|
|
29
|
+
FRAMEWORKS$1["NEXT_NEST"] = "next-nest";
|
|
30
|
+
FRAMEWORKS$1["REACT_NEST"] = "react-nest";
|
|
31
|
+
return FRAMEWORKS$1;
|
|
32
|
+
}({});
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/enum/ui_lib.ts
|
|
36
|
+
let UI_LIB = /* @__PURE__ */ function(UI_LIB$1) {
|
|
37
|
+
UI_LIB$1["SHADCN"] = "shadcn";
|
|
38
|
+
UI_LIB$1["HERO_UI_V3"] = "hero-ui-v3";
|
|
39
|
+
return UI_LIB$1;
|
|
40
|
+
}({});
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/installers/dependencies.ts
|
|
44
|
+
async function installDependencies(projectName, dependencies) {
|
|
45
|
+
await execa("npm", ["install", ...dependencies], {
|
|
46
|
+
cwd: projectName,
|
|
47
|
+
stdio: "inherit"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/installers/devDependencies.ts
|
|
53
|
+
async function installDevDependencies(projectName, devDependencies) {
|
|
54
|
+
await execa("npm", [
|
|
55
|
+
"install",
|
|
56
|
+
"-D",
|
|
57
|
+
...devDependencies
|
|
58
|
+
], {
|
|
59
|
+
cwd: projectName,
|
|
60
|
+
stdio: "inherit"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/installers/nexjs.ts
|
|
66
|
+
async function createNextApp(projectName) {
|
|
67
|
+
await execa("npx", [
|
|
68
|
+
"create-next-app@latest",
|
|
69
|
+
projectName,
|
|
70
|
+
"--ts",
|
|
71
|
+
"--tailwind",
|
|
72
|
+
"--react-compiler",
|
|
73
|
+
"--biome",
|
|
74
|
+
"--app",
|
|
75
|
+
"--src-dir",
|
|
76
|
+
"--import-alias",
|
|
77
|
+
"@/*"
|
|
78
|
+
], { stdio: "inherit" });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/installers/react.ts
|
|
83
|
+
async function createReactApp(projectName) {
|
|
84
|
+
await execa("npm", [
|
|
85
|
+
"create",
|
|
86
|
+
"vite@latest",
|
|
87
|
+
projectName,
|
|
88
|
+
"--",
|
|
89
|
+
"--template",
|
|
90
|
+
"react"
|
|
91
|
+
], { stdio: "inherit" });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/installers/expressjs.ts
|
|
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
|
|
116
|
+
async function setupHuskyLintStaged(projectName) {
|
|
117
|
+
const preCommitPath = path.join(projectName, ".husky", "pre-commit");
|
|
118
|
+
const prePushPath = path.join(projectName, ".husky", "pre-push");
|
|
119
|
+
await Promise.all([fs.ensureFile(preCommitPath), fs.ensureFile(prePushPath)]);
|
|
120
|
+
await Promise.all([fs.writeFile(preCommitPath, `npx lint-staged`), fs.writeFile(prePushPath, `npm run build`)]);
|
|
121
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
122
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
123
|
+
packageJson["lint-staged"] = { "*.{ts,tsx,js,jsx,json,css}": ["npm run format", "npm run lint"] };
|
|
124
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/setups/next_supabase_client.ts
|
|
129
|
+
async function setupNextSupabaseClient(projectDir) {
|
|
130
|
+
const supabaseDir = path.join(projectDir, "src", "lib", "supabase");
|
|
131
|
+
await fs.ensureDir(supabaseDir);
|
|
132
|
+
const supabaseClientPath = path.join(supabaseDir, "client.ts");
|
|
133
|
+
const supabaseServerPath = path.join(supabaseDir, "server.ts");
|
|
134
|
+
const supabaseAdminPath = path.join(supabaseDir, "admin.ts");
|
|
135
|
+
await Promise.all([
|
|
136
|
+
fs.writeFile(supabaseClientPath, supabaseClientTemplate),
|
|
137
|
+
fs.writeFile(supabaseServerPath, supabaseServerTemplate),
|
|
138
|
+
fs.writeFile(supabaseAdminPath, supabaseAdminTemplate)
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
const supabaseClientTemplate = `import { createBrowserClient } from "@supabase/ssr";
|
|
142
|
+
import { sharedEnv } from "../../../env.shared";
|
|
143
|
+
|
|
144
|
+
export function createClient() {
|
|
145
|
+
return createBrowserClient(sharedEnv.NEXT_PUBLIC_APP_URL, sharedEnv.NEXT_PUBLIC_SUPABASE_ANON_KEY);
|
|
146
|
+
}
|
|
147
|
+
`.trimStart();
|
|
148
|
+
const supabaseServerTemplate = `import "server-only";
|
|
149
|
+
import { createServerClient } from "@supabase/ssr";
|
|
150
|
+
import { cookies } from "next/headers";
|
|
151
|
+
import { sharedEnv } from "../../../env.shared";
|
|
152
|
+
|
|
153
|
+
export async function createClient() {
|
|
154
|
+
const cookieStore = await cookies();
|
|
155
|
+
|
|
156
|
+
return createServerClient(
|
|
157
|
+
sharedEnv.NEXT_PUBLIC_SUPABASE_URL,
|
|
158
|
+
sharedEnv.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
|
159
|
+
{
|
|
160
|
+
cookies: {
|
|
161
|
+
getAll() {
|
|
162
|
+
return cookieStore.getAll();
|
|
163
|
+
},
|
|
164
|
+
setAll(cookiesToSet) {
|
|
165
|
+
try {
|
|
166
|
+
cookiesToSet.forEach(({ name, value, options }) => {
|
|
167
|
+
cookieStore.set(name, value, options);
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
// The 'setAll' method was called from a Server Component.
|
|
171
|
+
// This can be ignored if you have middleware refreshing
|
|
172
|
+
// user sessions.
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
`.trimStart();
|
|
180
|
+
const supabaseAdminTemplate = `import "server-only";
|
|
181
|
+
import { createClient } from "@supabase/supabase-js";
|
|
182
|
+
import { serverEnv } from "@/backend/env.server";
|
|
183
|
+
import { sharedEnv } from "../../../env.shared";
|
|
184
|
+
export function createAdminClient() {
|
|
185
|
+
return createClient(
|
|
186
|
+
sharedEnv.NEXT_PUBLIC_SUPABASE_URL,
|
|
187
|
+
serverEnv.SUPABASE_SERVICE_ROLE_KEY,
|
|
188
|
+
{
|
|
189
|
+
auth: {
|
|
190
|
+
autoRefreshToken: false,
|
|
191
|
+
persistSession: false,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
`.trimStart();
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/setups/next_env.ts
|
|
200
|
+
async function setupNextEnv(projectName, isSupabase = false) {
|
|
201
|
+
const sharedEnvPath = path.join(projectName, "env.shared.ts");
|
|
202
|
+
const backendDir = path.join(projectName, "src", "backend");
|
|
203
|
+
const serverEnvPath = path.join(backendDir, "env.server.ts");
|
|
204
|
+
await fs.ensureDir(backendDir);
|
|
205
|
+
await Promise.all([fs.ensureFile(sharedEnvPath), fs.ensureFile(serverEnvPath)]);
|
|
206
|
+
await Promise.all([fs.writeFile(sharedEnvPath, sharedEnvTemplate(isSupabase)), fs.writeFile(serverEnvPath, serverEnvTemplate(isSupabase))]);
|
|
207
|
+
}
|
|
208
|
+
const serverEnvTemplate = (isSupabase) => `import "server-only";
|
|
209
|
+
import { z } from "zod";
|
|
210
|
+
|
|
211
|
+
const envSchema = z.object({
|
|
212
|
+
NODE_ENV: z
|
|
213
|
+
.enum(["development", "production", "test"])
|
|
214
|
+
.default("development"),
|
|
215
|
+
DATABASE_URL: z.string().min(1, "Database URL is required"),
|
|
216
|
+
${isSupabase ? "SUPABASE_SERVICE_ROLE_KEY: z.string().min(1, \"Supabase service role key is required\")," : ""}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const parsed = envSchema.safeParse(process.env);
|
|
220
|
+
|
|
221
|
+
if (!parsed.success) {
|
|
222
|
+
console.error("❌ Invalid environment variables:");
|
|
223
|
+
console.error(JSON.stringify(parsed.error.format(), null, 2));
|
|
224
|
+
throw new Error("Invalid environment variables");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const serverEnv = parsed.data;
|
|
228
|
+
`.trimStart();
|
|
229
|
+
const sharedEnvTemplate = (isSupabase) => `import { z } from "zod";
|
|
230
|
+
|
|
231
|
+
const envSchema = z.object({
|
|
232
|
+
NEXT_PUBLIC_APP_URL: z
|
|
233
|
+
.url("Invalid app URL")
|
|
234
|
+
.default("http://localhost:3000"),
|
|
235
|
+
${isSupabase ? "NEXT_PUBLIC_SUPABASE_URL: z.url(\"Invalid Supabase URL\")," : "".trim()}
|
|
236
|
+
${isSupabase ? "NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1, \"Supabase anon key is required\")," : ""}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const parsed = envSchema.safeParse({
|
|
240
|
+
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
241
|
+
${isSupabase ? "NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL," : ""}
|
|
242
|
+
${isSupabase ? "NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY," : ""}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!parsed.success) {
|
|
246
|
+
console.error("❌ Invalid environment variables:");
|
|
247
|
+
console.error(JSON.stringify(parsed.error.format(), null, 2));
|
|
248
|
+
throw new Error("Invalid environment variables");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const sharedEnv = parsed.data;
|
|
252
|
+
`.trimStart();
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/setups/shadcn.ts
|
|
256
|
+
async function setupShadcn(projectName) {
|
|
257
|
+
await execa("npx", [
|
|
258
|
+
"shadcn@latest",
|
|
259
|
+
"init",
|
|
260
|
+
"dialog",
|
|
261
|
+
"avatar",
|
|
262
|
+
"button-group",
|
|
263
|
+
"calendar",
|
|
264
|
+
"card",
|
|
265
|
+
"carousel",
|
|
266
|
+
"chart",
|
|
267
|
+
"checkbox",
|
|
268
|
+
"drawer",
|
|
269
|
+
"dropdown-menu",
|
|
270
|
+
"input-group",
|
|
271
|
+
"label",
|
|
272
|
+
"pagination",
|
|
273
|
+
"select",
|
|
274
|
+
"sheet",
|
|
275
|
+
"skeleton",
|
|
276
|
+
"sonner",
|
|
277
|
+
"spinner",
|
|
278
|
+
"table",
|
|
279
|
+
"textarea"
|
|
280
|
+
], {
|
|
281
|
+
cwd: projectName,
|
|
282
|
+
stdio: "inherit"
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/setups/biome.ts
|
|
288
|
+
async function setupBiome(projectName, isShadcn) {
|
|
289
|
+
const biomePath = path.join(projectName, "biome.json");
|
|
290
|
+
const biomeConfig = await fs.readJson(biomePath);
|
|
291
|
+
const ignoreFiles = [...biomeConfig.files.includes, "!**/app/globals.css"];
|
|
292
|
+
if (isShadcn) ignoreFiles.push("!**/components/ui");
|
|
293
|
+
biomeConfig.files.includes = ignoreFiles;
|
|
294
|
+
await fs.writeJson(biomePath, biomeConfig, { spaces: 2 });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/setups/next_supabase_local.ts
|
|
299
|
+
async function setupNextSupabaseLocal(projectDir) {
|
|
300
|
+
execa("npx", ["supabase", "init"], {
|
|
301
|
+
cwd: projectDir,
|
|
302
|
+
stdio: "inherit"
|
|
303
|
+
});
|
|
304
|
+
const migrationsDir = path.join(projectDir, "supabase", "migrations");
|
|
305
|
+
const schemaDir = path.join(projectDir, "supabase", "schemas");
|
|
306
|
+
const dbDir = path.join(schemaDir, "db");
|
|
307
|
+
const storageDir = path.join(schemaDir, "storage");
|
|
308
|
+
await Promise.all([fs.ensureDir(migrationsDir), fs.ensureDir(schemaDir)]);
|
|
309
|
+
await Promise.all([fs.ensureDir(dbDir), fs.ensureDir(storageDir)]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/setups/jest.ts
|
|
314
|
+
async function setupJest(projectName) {
|
|
315
|
+
const jestConfigPath = path.join(projectName, "jest.config.ts");
|
|
316
|
+
await fs.ensureFile(jestConfigPath);
|
|
317
|
+
await fs.writeFile(jestConfigPath, jestConfigTemplate);
|
|
318
|
+
const packageJsonPath = path.join(projectName, "package.json");
|
|
319
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
320
|
+
packageJson.scripts["test:unit"] = "jest --testPathPatterns=unit";
|
|
321
|
+
packageJson.scripts["test:integration"] = "jest --testPathPatterns=integration";
|
|
322
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
323
|
+
const testUnitDir = path.join(projectName, "src", "backend", "test", "unit");
|
|
324
|
+
const testIntegrationDir = path.join(projectName, "src", "backend", "test", "integration");
|
|
325
|
+
await Promise.all([fs.ensureDir(testUnitDir), fs.ensureDir(testIntegrationDir)]);
|
|
326
|
+
}
|
|
327
|
+
const jestConfigTemplate = `import type { Config } from "jest";
|
|
328
|
+
|
|
329
|
+
const config: Config = {
|
|
330
|
+
clearMocks: true,
|
|
331
|
+
coverageProvider: "v8",
|
|
332
|
+
moduleNameMapper: {
|
|
333
|
+
"^@/(.*)$": "<rootDir>/src/$1",
|
|
334
|
+
},
|
|
335
|
+
preset: "ts-jest",
|
|
336
|
+
testEnvironment: "node",
|
|
337
|
+
transform: {
|
|
338
|
+
"^.+\\.tsx?$": [
|
|
339
|
+
"ts-jest",
|
|
340
|
+
{
|
|
341
|
+
tsconfig: "tsconfig.json",
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export default config;
|
|
348
|
+
`.trimStart();
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/create.ts
|
|
352
|
+
async function createProject(answers) {
|
|
353
|
+
const { projectName, framework, dbProvider, dbTool, uiLibrary, useMonorepo } = answers;
|
|
354
|
+
switch (framework) {
|
|
355
|
+
case FRAMEWORKS.NEXT: {
|
|
356
|
+
await createNextApp(projectName);
|
|
357
|
+
const isSupabase = dbProvider === DB_PROVIDERS.SUPABASE;
|
|
358
|
+
const dependencies = [
|
|
359
|
+
"react-hook-form",
|
|
360
|
+
"@hookform/resolvers",
|
|
361
|
+
"zod",
|
|
362
|
+
"server-only"
|
|
363
|
+
];
|
|
364
|
+
const devDependencies = [
|
|
365
|
+
"husky",
|
|
366
|
+
"lint-staged",
|
|
367
|
+
"jest",
|
|
368
|
+
"@types/jest",
|
|
369
|
+
"ts-jest"
|
|
370
|
+
];
|
|
371
|
+
if (isSupabase) dependencies.push("@supabase/ssr", "@supabase/supabase-js");
|
|
372
|
+
if (dbTool === DB_TOOLS.DRIZZLE_ORM) {
|
|
373
|
+
dependencies.push("drizzle-orm", "postgres");
|
|
374
|
+
devDependencies.push("drizzle-kit");
|
|
375
|
+
}
|
|
376
|
+
await installDependencies(projectName, dependencies);
|
|
377
|
+
await installDevDependencies(projectName, devDependencies);
|
|
378
|
+
await setupHuskyLintStaged(projectName);
|
|
379
|
+
await setupNextEnv(projectName, isSupabase);
|
|
380
|
+
if (isSupabase) await setupNextSupabaseClient(projectName);
|
|
381
|
+
if (dbTool === DB_TOOLS.SUPABASE_JS_SDK) await setupNextSupabaseLocal(projectName);
|
|
382
|
+
if (uiLibrary === UI_LIB.SHADCN) await setupShadcn(projectName);
|
|
383
|
+
await setupBiome(projectName, uiLibrary === UI_LIB.SHADCN);
|
|
384
|
+
await setupJest(projectName);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case FRAMEWORKS.NEXT_EXPRESS:
|
|
388
|
+
createNextApp(projectName + "-fe");
|
|
389
|
+
createNextExpressApp(projectName + "-be");
|
|
390
|
+
break;
|
|
391
|
+
case FRAMEWORKS.NEXT_NEST:
|
|
392
|
+
createNextApp(projectName + "-fe");
|
|
393
|
+
createNestApp(projectName + "-be");
|
|
394
|
+
break;
|
|
395
|
+
case FRAMEWORKS.REACT_EXPRESS:
|
|
396
|
+
createReactApp(projectName + "-fe");
|
|
397
|
+
createNextExpressApp(projectName + "-be");
|
|
398
|
+
break;
|
|
399
|
+
case FRAMEWORKS.REACT_NEST:
|
|
400
|
+
createReactApp(projectName + "-fe");
|
|
401
|
+
createNestApp(projectName + "-be");
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/ask.ts
|
|
408
|
+
const DB_TOOLS_MAP = {
|
|
409
|
+
[DB_PROVIDERS.SUPABASE]: [DB_TOOLS.DRIZZLE_ORM, DB_TOOLS.SUPABASE_JS_SDK],
|
|
410
|
+
[DB_PROVIDERS.DOCKER]: [DB_TOOLS.DRIZZLE_ORM]
|
|
411
|
+
};
|
|
412
|
+
async function ask() {
|
|
413
|
+
return await inquirer.prompt([
|
|
414
|
+
{
|
|
415
|
+
type: "input",
|
|
416
|
+
name: "projectName",
|
|
417
|
+
message: "What is your project name?",
|
|
418
|
+
default: "my-app",
|
|
419
|
+
validate(input) {
|
|
420
|
+
if (!input.trim()) return "Project name cannot be empty";
|
|
421
|
+
if (input.includes(" ")) return "Project name cannot contain spaces";
|
|
422
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(input)) return "Project name can only contain letters, numbers, - and _";
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
type: "select",
|
|
428
|
+
name: "framework",
|
|
429
|
+
message: "What framework do you want to use?",
|
|
430
|
+
choices: [
|
|
431
|
+
{
|
|
432
|
+
name: "Next.js",
|
|
433
|
+
value: FRAMEWORKS.NEXT
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "Next.js + Express",
|
|
437
|
+
value: FRAMEWORKS.NEXT_EXPRESS
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "React.js + Express",
|
|
441
|
+
value: FRAMEWORKS.REACT_EXPRESS
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "Next.js + NestJS",
|
|
445
|
+
value: FRAMEWORKS.NEXT_NEST
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "React.js + NestJS",
|
|
449
|
+
value: FRAMEWORKS.REACT_NEST
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
type: "select",
|
|
455
|
+
name: "dbProvider",
|
|
456
|
+
message: "Which database provider do you want to use?",
|
|
457
|
+
choices: [DB_PROVIDERS.SUPABASE, DB_PROVIDERS.DOCKER]
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
type: "select",
|
|
461
|
+
name: "dbTool",
|
|
462
|
+
message: "Which tool do you want to use for database?",
|
|
463
|
+
choices: (answers) => DB_TOOLS_MAP[answers.dbProvider]
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
type: "select",
|
|
467
|
+
name: "uiLibrary",
|
|
468
|
+
message: "Which UI library do you want to use?",
|
|
469
|
+
choices: [UI_LIB.SHADCN, UI_LIB.HERO_UI_V3]
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
type: "confirm",
|
|
473
|
+
name: "useMonorepo",
|
|
474
|
+
message: "Do you want to use monorepo?",
|
|
475
|
+
default: true,
|
|
476
|
+
when: (answers) => ![FRAMEWORKS.NEXT, FRAMEWORKS.REACT].includes(answers.framework)
|
|
477
|
+
}
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/index.ts
|
|
483
|
+
async function main() {
|
|
484
|
+
await createProject(await ask());
|
|
485
|
+
}
|
|
486
|
+
main().catch((error) => {
|
|
487
|
+
console.error("error:", error);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-init-mtv-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for create init app",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"create-init-app"
|
|
7
|
+
],
|
|
8
|
+
"homepage": "https://github.com/ThangBui2703/create-init-app#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/ThangBui2703/create-init-app/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/ThangBui2703/create-init-app.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"author": "init",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/index.mjs",
|
|
20
|
+
"bin": {
|
|
21
|
+
"create-init-app": "dist/index.mjs"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
28
|
+
"build": "npx tsdown",
|
|
29
|
+
"start": "node dist/index.mjs",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@types/fs-extra": "^11.0.4",
|
|
34
|
+
"chalk": "^5.6.2",
|
|
35
|
+
"commander": "^14.0.2",
|
|
36
|
+
"execa": "^9.6.1",
|
|
37
|
+
"fs-extra": "^11.3.3",
|
|
38
|
+
"inquirer": "^13.1.0",
|
|
39
|
+
"ora": "^9.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^25.0.3",
|
|
43
|
+
"tsdown": "^0.18.2",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|