create-morax 1.0.2 → 1.0.14
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 +1074 -144
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import pc18 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/core/runner.ts
|
|
7
|
-
import
|
|
7
|
+
import path15 from "path";
|
|
8
|
+
import { spinner as spinner11, multiselect } from "@clack/prompts";
|
|
9
|
+
import pc17 from "picocolors";
|
|
8
10
|
|
|
9
11
|
// src/core/startcli.ts
|
|
10
12
|
import gradient from "gradient-string";
|
|
@@ -53,21 +55,21 @@ function handleCancel(input) {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
// src/core/workspaceName.ts
|
|
56
|
-
async function promptWorkspaceName() {
|
|
58
|
+
async function promptWorkspaceName(isMonorepo = true) {
|
|
57
59
|
const nameInput = await text({
|
|
58
|
-
message: "What is the name of your new monorepo workspace?",
|
|
59
|
-
placeholder: "morax-workspace",
|
|
60
|
+
message: isMonorepo ? "What is the name of your new monorepo workspace?" : "What is the name of your new project?",
|
|
61
|
+
placeholder: isMonorepo ? "morax-workspace" : "my-app",
|
|
60
62
|
validate(value) {
|
|
61
63
|
if (value && value.includes(" "))
|
|
62
|
-
return "Workspace name cannot contain spaces!";
|
|
64
|
+
return isMonorepo ? "Workspace name cannot contain spaces!" : "Project name cannot contain spaces!";
|
|
63
65
|
}
|
|
64
66
|
});
|
|
65
67
|
handleCancel(nameInput);
|
|
66
|
-
return String(nameInput).trim() || "morax-workspace";
|
|
68
|
+
return String(nameInput).trim() || (isMonorepo ? "morax-workspace" : "my-app");
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
// src/tasks/directories.ts
|
|
70
|
-
import {
|
|
72
|
+
import { spinner } from "@clack/prompts";
|
|
71
73
|
import fsPromises2 from "fs/promises";
|
|
72
74
|
import path2 from "path";
|
|
73
75
|
import pc4 from "picocolors";
|
|
@@ -82,6 +84,7 @@ import { promisify } from "util";
|
|
|
82
84
|
import pc3 from "picocolors";
|
|
83
85
|
var execAsync = promisify(exec);
|
|
84
86
|
async function runCommand(command, options = {}) {
|
|
87
|
+
options.silent = true;
|
|
85
88
|
if (!options.silent) {
|
|
86
89
|
console.log(pc3.cyan(`> ${command}`));
|
|
87
90
|
}
|
|
@@ -159,36 +162,13 @@ async function createDirectories(directories, projectPath) {
|
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
async function makeDirectories(name, projectPath) {
|
|
162
|
-
const directories =
|
|
163
|
-
message: "Which directories do you want to include in your workspace?",
|
|
164
|
-
options: [
|
|
165
|
-
{
|
|
166
|
-
value: "apps",
|
|
167
|
-
label: "apps/*",
|
|
168
|
-
hint: "For frontend apps and backend services"
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
value: "packages",
|
|
172
|
-
label: "packages/*",
|
|
173
|
-
hint: "For shared components, configs, and utilities"
|
|
174
|
-
}
|
|
175
|
-
],
|
|
176
|
-
required: true
|
|
177
|
-
});
|
|
178
|
-
handleCancel(directories);
|
|
165
|
+
const directories = ["apps", "packages"];
|
|
179
166
|
const s = spinner();
|
|
180
167
|
console.log("\n");
|
|
181
168
|
s.start("Generating workspace configs...");
|
|
182
169
|
try {
|
|
183
|
-
await generateWorkspaceConfig(
|
|
184
|
-
|
|
185
|
-
directories,
|
|
186
|
-
projectPath
|
|
187
|
-
);
|
|
188
|
-
await createDirectories(
|
|
189
|
-
directories,
|
|
190
|
-
projectPath
|
|
191
|
-
);
|
|
170
|
+
await generateWorkspaceConfig(name, directories, projectPath);
|
|
171
|
+
await createDirectories(directories, projectPath);
|
|
192
172
|
s.stop(pc4.green("\u2714 Success: Generated Workspace Root & Folder Structures"));
|
|
193
173
|
} catch (error) {
|
|
194
174
|
s.stop(pc4.red("\u2716 Failed: Workspace generation failed"));
|
|
@@ -200,13 +180,607 @@ Error details: ${error.message || error}`));
|
|
|
200
180
|
return directories;
|
|
201
181
|
}
|
|
202
182
|
|
|
203
|
-
// src/
|
|
204
|
-
import
|
|
183
|
+
// src/core/react-runner.ts
|
|
184
|
+
import path6 from "path";
|
|
185
|
+
import { spinner as spinner4 } from "@clack/prompts";
|
|
186
|
+
import pc8 from "picocolors";
|
|
187
|
+
|
|
188
|
+
// src/tasks/react.ts
|
|
205
189
|
import pc5 from "picocolors";
|
|
206
190
|
import fsPromises3 from "fs/promises";
|
|
207
191
|
import path3 from "path";
|
|
192
|
+
import { spawn } from "child_process";
|
|
193
|
+
import { confirm, spinner as spinner2, text as text2 } from "@clack/prompts";
|
|
194
|
+
function runInteractiveCommand(command, args, cwd) {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
197
|
+
const child = spawn(fullCommand, [], {
|
|
198
|
+
cwd,
|
|
199
|
+
stdio: "inherit",
|
|
200
|
+
shell: true,
|
|
201
|
+
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
202
|
+
});
|
|
203
|
+
child.on("close", (code) => {
|
|
204
|
+
if (code === 0) {
|
|
205
|
+
resolve();
|
|
206
|
+
} else {
|
|
207
|
+
reject(
|
|
208
|
+
new Error(
|
|
209
|
+
`Command "${command} ${args.join(" ")}" exited with code ${code}`
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
child.on("error", (err) => {
|
|
215
|
+
reject(err);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async function setupReact(projectPath, isMonorepo = true) {
|
|
220
|
+
const appsDir = isMonorepo ? path3.join(projectPath, "apps") : path3.dirname(projectPath);
|
|
221
|
+
console.log(pc5.magentaBright("\n\u{1F537} IMPORTANT INSTRUCTIONS:"));
|
|
222
|
+
console.log(
|
|
223
|
+
pc5.magentaBright(
|
|
224
|
+
"During the Vite interactive configuration prompts, please select:"
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
console.log(
|
|
228
|
+
pc5.magentaBright(
|
|
229
|
+
` 1. Install & Start: Choose ${pc5.bold("No")} when asked "Install with npm/pnpm and start now?"`
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
console.log(
|
|
233
|
+
pc5.magentaBright(
|
|
234
|
+
"This allows Morax CLI to continue automatically configuring your workspace (ESLint, Prettier, Tailwind, packages, etc.)!"
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
console.log(
|
|
238
|
+
pc5.cyan("\nStarting interactive Vite React application setup...")
|
|
239
|
+
);
|
|
240
|
+
let beforeDirs = [];
|
|
241
|
+
try {
|
|
242
|
+
beforeDirs = await fsPromises3.readdir(appsDir);
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await runInteractiveCommand("pnpm", ["create", "vite@latest"], appsDir);
|
|
247
|
+
let afterDirs = [];
|
|
248
|
+
try {
|
|
249
|
+
afterDirs = await fsPromises3.readdir(appsDir);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
const newDirs = afterDirs.filter((d) => !beforeDirs.includes(d));
|
|
253
|
+
let createdDirName = "web";
|
|
254
|
+
if (newDirs.length > 0) {
|
|
255
|
+
createdDirName = newDirs[0];
|
|
256
|
+
}
|
|
257
|
+
const webDir = path3.join(appsDir, createdDirName);
|
|
258
|
+
try {
|
|
259
|
+
await fsPromises3.access(webDir);
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Vite React application directory "apps/${createdDirName}" was not found. Please ensure you complete the Vite installation.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const aliasSpinner = spinner2();
|
|
266
|
+
aliasSpinner.start("Configuring import aliases and Node types...");
|
|
267
|
+
try {
|
|
268
|
+
const pkgPath = path3.join(webDir, "package.json");
|
|
269
|
+
try {
|
|
270
|
+
const pkgRaw = await fsPromises3.readFile(pkgPath, "utf8");
|
|
271
|
+
const pkg = JSON.parse(pkgRaw);
|
|
272
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
273
|
+
pkg.devDependencies["@types/node"] = "^20.11.0";
|
|
274
|
+
await fsPromises3.writeFile(
|
|
275
|
+
pkgPath,
|
|
276
|
+
JSON.stringify(pkg, null, 2),
|
|
277
|
+
"utf8"
|
|
278
|
+
);
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
const tsconfigPaths = [
|
|
282
|
+
path3.join(webDir, "tsconfig.json"),
|
|
283
|
+
path3.join(webDir, "tsconfig.app.json")
|
|
284
|
+
];
|
|
285
|
+
for (const tsPath of tsconfigPaths) {
|
|
286
|
+
try {
|
|
287
|
+
let content = await fsPromises3.readFile(tsPath, "utf8");
|
|
288
|
+
if (content.includes('"compilerOptions"')) {
|
|
289
|
+
if (!content.includes('"paths"')) {
|
|
290
|
+
content = content.replace(
|
|
291
|
+
/("compilerOptions"\s*:\s*\{)/,
|
|
292
|
+
`$1
|
|
293
|
+
"baseUrl": ".",
|
|
294
|
+
"paths": {
|
|
295
|
+
"@/*": ["./src/*"]
|
|
296
|
+
},`
|
|
297
|
+
);
|
|
298
|
+
await fsPromises3.writeFile(tsPath, content, "utf8");
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
content = content.replace(
|
|
302
|
+
/^(\s*\{)/,
|
|
303
|
+
`$1
|
|
304
|
+
"compilerOptions": {
|
|
305
|
+
"baseUrl": ".",
|
|
306
|
+
"paths": {
|
|
307
|
+
"@/*": ["./src/*"]
|
|
308
|
+
}
|
|
309
|
+
},`
|
|
310
|
+
);
|
|
311
|
+
await fsPromises3.writeFile(tsPath, content, "utf8");
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const viteConfigPath = path3.join(webDir, "vite.config.ts");
|
|
317
|
+
try {
|
|
318
|
+
let content = await fsPromises3.readFile(viteConfigPath, "utf8");
|
|
319
|
+
if (!content.includes("import path from 'path'")) {
|
|
320
|
+
content = "import path from 'path';\n" + content;
|
|
321
|
+
}
|
|
322
|
+
if (!content.includes("resolve:")) {
|
|
323
|
+
content = content.replace(
|
|
324
|
+
/(plugins:\s*\[[^\]]*\]),?/,
|
|
325
|
+
`$1,
|
|
326
|
+
resolve: {
|
|
327
|
+
alias: {
|
|
328
|
+
"@": path.resolve(__dirname, "./src"),
|
|
329
|
+
},
|
|
330
|
+
}`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
await fsPromises3.writeFile(viteConfigPath, content, "utf8");
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
aliasSpinner.stop(
|
|
337
|
+
pc5.green("\u2714 Success: Import aliases and Node types configured")
|
|
338
|
+
);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
aliasSpinner.stop(pc5.red("\u2716 Failed: Import alias configuration failed"));
|
|
341
|
+
}
|
|
342
|
+
console.log(
|
|
343
|
+
pc5.green(
|
|
344
|
+
isMonorepo ? `
|
|
345
|
+
\u2714 Success: Vite React frontend configured in apps/${createdDirName}
|
|
346
|
+
` : `
|
|
347
|
+
\u2714 Success: Vite React frontend configured at root
|
|
348
|
+
`
|
|
349
|
+
)
|
|
350
|
+
);
|
|
351
|
+
const setupTailwindPrompt = await confirm({
|
|
352
|
+
message: "Do you want to install and configure Tailwind CSS v4?",
|
|
353
|
+
initialValue: true
|
|
354
|
+
});
|
|
355
|
+
handleCancel(setupTailwindPrompt);
|
|
356
|
+
const setupShadcnPrompt = await confirm({
|
|
357
|
+
message: `Do you want to setup shadcn UI in your Vite React website (apps/${createdDirName})?`,
|
|
358
|
+
initialValue: true
|
|
359
|
+
});
|
|
360
|
+
handleCancel(setupShadcnPrompt);
|
|
361
|
+
const needsTailwind = setupTailwindPrompt || setupShadcnPrompt;
|
|
362
|
+
if (needsTailwind) {
|
|
363
|
+
const s = spinner2();
|
|
364
|
+
s.start(
|
|
365
|
+
"Installing Tailwind CSS v4, Vite integration, and Node types..."
|
|
366
|
+
);
|
|
367
|
+
try {
|
|
368
|
+
const pkgPath = path3.join(webDir, "package.json");
|
|
369
|
+
try {
|
|
370
|
+
const pkgRaw = await fsPromises3.readFile(pkgPath, "utf8");
|
|
371
|
+
const pkg = JSON.parse(pkgRaw);
|
|
372
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
373
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
374
|
+
pkg.dependencies["tailwindcss"] = "^4.0.0";
|
|
375
|
+
pkg.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
376
|
+
pkg.devDependencies["@types/node"] = "^20.11.0";
|
|
377
|
+
await fsPromises3.writeFile(
|
|
378
|
+
pkgPath,
|
|
379
|
+
JSON.stringify(pkg, null, 2),
|
|
380
|
+
"utf8"
|
|
381
|
+
);
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
await runCommand("pnpm install", { cwd: webDir, silent: true });
|
|
385
|
+
s.message("Configuring Vite plugins and Tailwind CSS imports...");
|
|
386
|
+
const viteConfigPath = path3.join(webDir, "vite.config.ts");
|
|
387
|
+
try {
|
|
388
|
+
let content = await fsPromises3.readFile(viteConfigPath, "utf8");
|
|
389
|
+
if (!content.includes("@tailwindcss/vite")) {
|
|
390
|
+
content = "import tailwindcss from '@tailwindcss/vite';\n" + content;
|
|
391
|
+
if (content.includes("plugins: [react()]")) {
|
|
392
|
+
content = content.replace(
|
|
393
|
+
"plugins: [react()]",
|
|
394
|
+
"plugins: [react(), tailwindcss()]"
|
|
395
|
+
);
|
|
396
|
+
} else if (content.includes("plugins: [react(),]")) {
|
|
397
|
+
content = content.replace(
|
|
398
|
+
"plugins: [react(),]",
|
|
399
|
+
"plugins: [react(), tailwindcss()]"
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
content = content.replace(
|
|
403
|
+
/(plugins:\s*\[\s*react\(\),?\s*)(\])/g,
|
|
404
|
+
"$1\n tailwindcss(),\n $2"
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
await fsPromises3.writeFile(viteConfigPath, content, "utf8");
|
|
408
|
+
}
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
const indexCssPath = path3.join(webDir, "src", "index.css");
|
|
412
|
+
try {
|
|
413
|
+
await fsPromises3.writeFile(
|
|
414
|
+
indexCssPath,
|
|
415
|
+
'@import "tailwindcss";\n',
|
|
416
|
+
"utf8"
|
|
417
|
+
);
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
s.stop(
|
|
421
|
+
pc5.green("\u2714 Success: Tailwind CSS v4 configured successfully\n")
|
|
422
|
+
);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
s.stop(pc5.red("\u2716 Failed: Tailwind CSS v4 setup failed"));
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (setupShadcnPrompt) {
|
|
429
|
+
const hasPreset = await confirm({
|
|
430
|
+
message: "Do you have a custom shadcn UI preset code?",
|
|
431
|
+
initialValue: false
|
|
432
|
+
});
|
|
433
|
+
handleCancel(hasPreset);
|
|
434
|
+
let presetCode = "";
|
|
435
|
+
if (hasPreset) {
|
|
436
|
+
const enteredPreset = await text2({
|
|
437
|
+
message: "Enter your custom shadcn UI preset code:",
|
|
438
|
+
placeholder: "e.g. 123456",
|
|
439
|
+
validate: (val) => {
|
|
440
|
+
if (!val || !val.trim()) return "Preset code cannot be empty";
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
handleCancel(enteredPreset);
|
|
445
|
+
presetCode = enteredPreset.trim();
|
|
446
|
+
}
|
|
447
|
+
const args = ["dlx", "shadcn@latest", "init"];
|
|
448
|
+
if (presetCode) {
|
|
449
|
+
args.push("--preset", presetCode);
|
|
450
|
+
}
|
|
451
|
+
args.push("--template", "vite");
|
|
452
|
+
console.log(
|
|
453
|
+
pc5.cyan("\nStarting interactive shadcn UI initialization...")
|
|
454
|
+
);
|
|
455
|
+
await runInteractiveCommand("pnpm", args, webDir);
|
|
456
|
+
console.log(
|
|
457
|
+
pc5.green(
|
|
458
|
+
isMonorepo ? `
|
|
459
|
+
\u2714 Success: shadcn UI successfully initialized in apps/${createdDirName}
|
|
460
|
+
` : `
|
|
461
|
+
\u2714 Success: shadcn UI successfully initialized at root
|
|
462
|
+
`
|
|
463
|
+
)
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
return webDir;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error(pc5.red(`
|
|
469
|
+
\u2716 Failed: Vite React frontend setup failed`));
|
|
470
|
+
console.error(pc5.red(`Error details: ${error.message || error}
|
|
471
|
+
`));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/tasks/git.ts
|
|
477
|
+
import { confirm as confirm2, spinner as spinner3 } from "@clack/prompts";
|
|
478
|
+
import pc6 from "picocolors";
|
|
479
|
+
import fsPromises4 from "fs/promises";
|
|
480
|
+
import path4 from "path";
|
|
481
|
+
async function promptGit() {
|
|
482
|
+
return await confirm2({
|
|
483
|
+
message: "Do you want to initialize a local Git repository?",
|
|
484
|
+
initialValue: true
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
async function gitInit(projectPath) {
|
|
488
|
+
await runCommand("git init", { cwd: projectPath, silent: true });
|
|
489
|
+
const gitignore = [
|
|
490
|
+
"# Dependency directories",
|
|
491
|
+
"node_modules/",
|
|
492
|
+
"jspm_packages/",
|
|
493
|
+
"web_modules/",
|
|
494
|
+
"",
|
|
495
|
+
"# Build and output outputs",
|
|
496
|
+
"dist/",
|
|
497
|
+
"build/",
|
|
498
|
+
".next/",
|
|
499
|
+
"out/",
|
|
500
|
+
".turbo/",
|
|
501
|
+
"",
|
|
502
|
+
"# Environments",
|
|
503
|
+
".env",
|
|
504
|
+
".env.local",
|
|
505
|
+
".env.development.local",
|
|
506
|
+
".env.test.local",
|
|
507
|
+
".env.production.local",
|
|
508
|
+
"",
|
|
509
|
+
"# Logs",
|
|
510
|
+
"npm-debug.log*",
|
|
511
|
+
"yarn-debug.log*",
|
|
512
|
+
"yarn-error.log*",
|
|
513
|
+
"pnpm-debug.log*",
|
|
514
|
+
"*.log",
|
|
515
|
+
"",
|
|
516
|
+
"# OS Metadata",
|
|
517
|
+
".DS_Store",
|
|
518
|
+
"Thumbs.db",
|
|
519
|
+
""
|
|
520
|
+
].join("\n");
|
|
521
|
+
await fsPromises4.writeFile(
|
|
522
|
+
path4.join(projectPath, ".gitignore"),
|
|
523
|
+
gitignore,
|
|
524
|
+
"utf8"
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
async function runGitSetup(projectPath) {
|
|
528
|
+
const gitPrompt = await promptGit();
|
|
529
|
+
handleCancel(gitPrompt);
|
|
530
|
+
let gitInitialized = false;
|
|
531
|
+
if (gitPrompt) {
|
|
532
|
+
const s = spinner3();
|
|
533
|
+
console.log("\n");
|
|
534
|
+
s.start("Initializing Git repository...");
|
|
535
|
+
try {
|
|
536
|
+
await gitInit(projectPath);
|
|
537
|
+
s.stop(pc6.green("\u2714 Success: Git repository initialized"));
|
|
538
|
+
gitInitialized = true;
|
|
539
|
+
} catch (error) {
|
|
540
|
+
s.stop(pc6.red("\u2716 Failed: Git initialization failed"));
|
|
541
|
+
console.error(pc6.red(`
|
|
542
|
+
Error details: ${error.message || error}`));
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
console.log("\n");
|
|
546
|
+
}
|
|
547
|
+
return gitInitialized;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/tasks/addreadme.ts
|
|
551
|
+
import fsPromises5 from "fs/promises";
|
|
552
|
+
import path5 from "path";
|
|
553
|
+
async function setupReadme(projectPath, workspaceName) {
|
|
554
|
+
const readmePath = path5.join(projectPath, "README.md");
|
|
555
|
+
const readmeContent = [
|
|
556
|
+
`# Morax `,
|
|
557
|
+
"",
|
|
558
|
+
"Welcome to your next-generation, high-performance monorepo workspace generated by **Morax**.",
|
|
559
|
+
"",
|
|
560
|
+
"## Workspace Structure",
|
|
561
|
+
"",
|
|
562
|
+
"- **`apps/`** \u2014 Frontend applications, backend servers, and user-facing services.",
|
|
563
|
+
"- **`packages/`** \u2014 Shared modules, TypeScript configurations, ESLint rule sets, and common utility libraries.",
|
|
564
|
+
"",
|
|
565
|
+
"## Key Commands",
|
|
566
|
+
"",
|
|
567
|
+
"Run the following commands from the root directory of your workspace:",
|
|
568
|
+
"",
|
|
569
|
+
"### Development",
|
|
570
|
+
"Start all dev servers and hot-reloading configurations concurrently:",
|
|
571
|
+
"```bash",
|
|
572
|
+
"pnpm dev",
|
|
573
|
+
"```",
|
|
574
|
+
"",
|
|
575
|
+
"### Linting",
|
|
576
|
+
"Run static analysis across all workspaces using your shared ESLint rules:",
|
|
577
|
+
"```bash",
|
|
578
|
+
"pnpm lint",
|
|
579
|
+
"```",
|
|
580
|
+
"",
|
|
581
|
+
"### Formatting",
|
|
582
|
+
"Automatically format all codebase files using Prettier:",
|
|
583
|
+
"```bash",
|
|
584
|
+
"pnpm format",
|
|
585
|
+
"```",
|
|
586
|
+
"",
|
|
587
|
+
"---",
|
|
588
|
+
"",
|
|
589
|
+
"*Generated with love by [Morax Scaffolder CLI](https://github.com/Elitedv/morax).*",
|
|
590
|
+
""
|
|
591
|
+
].join("\n");
|
|
592
|
+
await fsPromises5.writeFile(readmePath, readmeContent, "utf8");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/core/endcli.ts
|
|
596
|
+
import pc7 from "picocolors";
|
|
597
|
+
import { outro } from "@clack/prompts";
|
|
598
|
+
function endCli(name, projectPath) {
|
|
599
|
+
outro(pc7.yellow("Morax scaffolding completed successfully!"));
|
|
600
|
+
console.log(
|
|
601
|
+
`\u26A1 ${pc7.bold("Workspace ready:")} ${pc7.yellow(name)}
|
|
602
|
+
${pc7.bold("To start developing:")}
|
|
603
|
+
1. ${pc7.cyan(`cd ${name}`)}
|
|
604
|
+
2. ${pc7.cyan("pnpm dev")}`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/core/react-runner.ts
|
|
609
|
+
async function runReactScaffolder(name, projectPath) {
|
|
610
|
+
const finalProjectPath = await setupReact(projectPath, false);
|
|
611
|
+
await setupReadme(finalProjectPath, path6.basename(finalProjectPath));
|
|
612
|
+
await runGitSetup(finalProjectPath);
|
|
613
|
+
const installSpinner = spinner4();
|
|
614
|
+
console.log("\n");
|
|
615
|
+
installSpinner.start("Running final package installation...");
|
|
616
|
+
try {
|
|
617
|
+
await runCommand("pnpm install", { cwd: finalProjectPath, silent: true });
|
|
618
|
+
installSpinner.stop(
|
|
619
|
+
pc8.green("\u2714 Success: Dependencies configured successfully")
|
|
620
|
+
);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
installSpinner.stop(pc8.red("\u2716 Warning: Final package installation failed"));
|
|
623
|
+
console.error(pc8.red(`
|
|
624
|
+
Error details: ${error.message || error}`));
|
|
625
|
+
}
|
|
626
|
+
console.log("\n");
|
|
627
|
+
endCli(path6.basename(finalProjectPath), finalProjectPath);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/core/next-runner.ts
|
|
631
|
+
import path9 from "path";
|
|
632
|
+
import { spinner as spinner5 } from "@clack/prompts";
|
|
633
|
+
import pc11 from "picocolors";
|
|
634
|
+
|
|
635
|
+
// src/tasks/nextjs.ts
|
|
636
|
+
import { confirm as confirm3 } from "@clack/prompts";
|
|
637
|
+
import pc9 from "picocolors";
|
|
638
|
+
import fsPromises6 from "fs/promises";
|
|
639
|
+
import path7 from "path";
|
|
640
|
+
import { spawn as spawn2 } from "child_process";
|
|
641
|
+
function runInteractiveCommand2(command, args, cwd) {
|
|
642
|
+
return new Promise((resolve, reject) => {
|
|
643
|
+
const child = spawn2(command, args, {
|
|
644
|
+
cwd,
|
|
645
|
+
stdio: "inherit",
|
|
646
|
+
shell: true,
|
|
647
|
+
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
648
|
+
});
|
|
649
|
+
child.on("close", (code) => {
|
|
650
|
+
if (code === 0) {
|
|
651
|
+
resolve();
|
|
652
|
+
} else {
|
|
653
|
+
reject(
|
|
654
|
+
new Error(
|
|
655
|
+
`Command "${command} ${args.join(" ")}" exited with code ${code}`
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
child.on("error", (err) => {
|
|
661
|
+
reject(err);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
async function setupNextjs(projectPath, isMonorepo = true) {
|
|
666
|
+
const appsDir = isMonorepo ? path7.join(projectPath, "apps") : path7.dirname(projectPath);
|
|
667
|
+
const targetName = isMonorepo ? "web" : path7.basename(projectPath);
|
|
668
|
+
const webDir = path7.join(appsDir, targetName);
|
|
669
|
+
try {
|
|
670
|
+
await fsPromises6.rm(webDir, { recursive: true, force: true });
|
|
671
|
+
} catch {
|
|
672
|
+
}
|
|
673
|
+
console.log(pc9.cyan("\nStarting interactive Next.js application setup..."));
|
|
674
|
+
await runInteractiveCommand2(
|
|
675
|
+
"npx",
|
|
676
|
+
["create-next-app@latest", targetName],
|
|
677
|
+
appsDir
|
|
678
|
+
);
|
|
679
|
+
return webDir;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/tasks/shadcn.ts
|
|
683
|
+
import { confirm as confirm4 } from "@clack/prompts";
|
|
684
|
+
import pc10 from "picocolors";
|
|
685
|
+
import fsPromises7 from "fs/promises";
|
|
686
|
+
import path8 from "path";
|
|
687
|
+
import { spawn as spawn3 } from "child_process";
|
|
688
|
+
async function promptShadcn() {
|
|
689
|
+
return await confirm4({
|
|
690
|
+
message: "Do you want to setup shadcn UI in your Next.js website (apps/web)?",
|
|
691
|
+
initialValue: true
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function runInteractiveCommand3(command, args, cwd) {
|
|
695
|
+
return new Promise((resolve, reject) => {
|
|
696
|
+
const child = spawn3(command, args, {
|
|
697
|
+
cwd,
|
|
698
|
+
stdio: "inherit",
|
|
699
|
+
shell: true,
|
|
700
|
+
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
701
|
+
});
|
|
702
|
+
child.on("close", (code) => {
|
|
703
|
+
if (code === 0) {
|
|
704
|
+
resolve();
|
|
705
|
+
} else {
|
|
706
|
+
reject(
|
|
707
|
+
new Error(
|
|
708
|
+
`Command "${command} ${args.join(" ")}" exited with code ${code}`
|
|
709
|
+
)
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
child.on("error", (err) => {
|
|
714
|
+
reject(err);
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
async function setupShadcn(projectPath, isMonorepo = true) {
|
|
719
|
+
const webDir = isMonorepo ? path8.join(projectPath, "apps", "web") : projectPath;
|
|
720
|
+
console.log(pc10.cyan("\nStarting interactive shadcn UI initialization..."));
|
|
721
|
+
await runInteractiveCommand3(
|
|
722
|
+
"pnpm",
|
|
723
|
+
["dlx", "shadcn@latest", "init", "--template", "next"],
|
|
724
|
+
webDir
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
async function runShadcnSetup(projectPath, isMonorepo = true) {
|
|
728
|
+
const webDir = isMonorepo ? path8.join(projectPath, "apps", "web") : projectPath;
|
|
729
|
+
try {
|
|
730
|
+
await fsPromises7.access(webDir);
|
|
731
|
+
} catch {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const shadcnPrompt = await promptShadcn();
|
|
735
|
+
handleCancel(shadcnPrompt);
|
|
736
|
+
if (shadcnPrompt) {
|
|
737
|
+
try {
|
|
738
|
+
await setupShadcn(projectPath, isMonorepo);
|
|
739
|
+
console.log(
|
|
740
|
+
pc10.green(
|
|
741
|
+
isMonorepo ? "\n\u2714 Success: shadcn UI successfully initialized in apps/web\n" : "\n\u2714 Success: shadcn UI successfully initialized at root\n"
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
console.error(pc10.red(`
|
|
746
|
+
\u2716 Failed: shadcn UI setup failed`));
|
|
747
|
+
console.error(pc10.red(`Error details: ${error.message || error}
|
|
748
|
+
`));
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/core/next-runner.ts
|
|
755
|
+
async function runNextScaffolder(name, projectPath) {
|
|
756
|
+
const finalProjectPath = await setupNextjs(projectPath, false);
|
|
757
|
+
await runShadcnSetup(finalProjectPath, false);
|
|
758
|
+
await setupReadme(finalProjectPath, path9.basename(finalProjectPath));
|
|
759
|
+
await runGitSetup(finalProjectPath);
|
|
760
|
+
const installSpinner = spinner5();
|
|
761
|
+
console.log("\n");
|
|
762
|
+
installSpinner.start("Running final package installation...");
|
|
763
|
+
try {
|
|
764
|
+
await runCommand("pnpm install", { cwd: finalProjectPath, silent: true });
|
|
765
|
+
installSpinner.stop(
|
|
766
|
+
pc11.green("\u2714 Success: Dependencies configured successfully")
|
|
767
|
+
);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
installSpinner.stop(pc11.red("\u2716 Warning: Final package installation failed"));
|
|
770
|
+
console.error(pc11.red(`
|
|
771
|
+
Error details: ${error.message || error}`));
|
|
772
|
+
}
|
|
773
|
+
console.log("\n");
|
|
774
|
+
endCli(path9.basename(finalProjectPath), finalProjectPath);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/tasks/prettier.ts
|
|
778
|
+
import { confirm as confirm5, spinner as spinner6 } from "@clack/prompts";
|
|
779
|
+
import pc12 from "picocolors";
|
|
780
|
+
import fsPromises8 from "fs/promises";
|
|
781
|
+
import path10 from "path";
|
|
208
782
|
async function promptPrettier() {
|
|
209
|
-
return await
|
|
783
|
+
return await confirm5({
|
|
210
784
|
message: "Do you want to setup Prettier code formatting?",
|
|
211
785
|
initialValue: true
|
|
212
786
|
});
|
|
@@ -220,8 +794,8 @@ async function setupPrettier(projectPath) {
|
|
|
220
794
|
trailingComma: "all",
|
|
221
795
|
printWidth: 100
|
|
222
796
|
};
|
|
223
|
-
await
|
|
224
|
-
|
|
797
|
+
await fsPromises8.writeFile(
|
|
798
|
+
path10.join(projectPath, ".prettierrc"),
|
|
225
799
|
JSON.stringify(prettierrc, null, 2),
|
|
226
800
|
"utf8"
|
|
227
801
|
);
|
|
@@ -246,19 +820,19 @@ async function setupPrettier(projectPath) {
|
|
|
246
820
|
".env.*",
|
|
247
821
|
""
|
|
248
822
|
].join("\n");
|
|
249
|
-
await
|
|
250
|
-
|
|
823
|
+
await fsPromises8.writeFile(
|
|
824
|
+
path10.join(projectPath, ".prettierignore"),
|
|
251
825
|
prettierignore,
|
|
252
826
|
"utf8"
|
|
253
827
|
);
|
|
254
|
-
const rootPackagePath =
|
|
255
|
-
const rootPackageContent = await
|
|
828
|
+
const rootPackagePath = path10.join(projectPath, "package.json");
|
|
829
|
+
const rootPackageContent = await fsPromises8.readFile(rootPackagePath, "utf8");
|
|
256
830
|
const pkg = JSON.parse(rootPackageContent);
|
|
257
831
|
pkg.scripts = {
|
|
258
832
|
...pkg.scripts || {},
|
|
259
833
|
format: "prettier --write ."
|
|
260
834
|
};
|
|
261
|
-
await
|
|
835
|
+
await fsPromises8.writeFile(
|
|
262
836
|
rootPackagePath,
|
|
263
837
|
JSON.stringify(pkg, null, 2),
|
|
264
838
|
"utf8"
|
|
@@ -269,15 +843,15 @@ async function runPrettierSetup(projectPath) {
|
|
|
269
843
|
const prettierPrompt = await promptPrettier();
|
|
270
844
|
handleCancel(prettierPrompt);
|
|
271
845
|
if (prettierPrompt) {
|
|
272
|
-
const s =
|
|
846
|
+
const s = spinner6();
|
|
273
847
|
console.log("\n");
|
|
274
848
|
s.start("Setting up Prettier auto-formatting...");
|
|
275
849
|
try {
|
|
276
850
|
await setupPrettier(projectPath);
|
|
277
|
-
s.stop(
|
|
851
|
+
s.stop(pc12.green("\u2714 Success: Prettier formatting configured"));
|
|
278
852
|
} catch (error) {
|
|
279
|
-
s.stop(
|
|
280
|
-
console.error(
|
|
853
|
+
s.stop(pc12.red("\u2716 Failed: Prettier setup failed"));
|
|
854
|
+
console.error(pc12.red(`
|
|
281
855
|
Error details: ${error.message || error}`));
|
|
282
856
|
process.exit(1);
|
|
283
857
|
}
|
|
@@ -285,87 +859,328 @@ Error details: ${error.message || error}`));
|
|
|
285
859
|
}
|
|
286
860
|
}
|
|
287
861
|
|
|
288
|
-
// src/tasks/
|
|
289
|
-
import {
|
|
290
|
-
import
|
|
291
|
-
import
|
|
292
|
-
import
|
|
293
|
-
async function
|
|
294
|
-
|
|
295
|
-
|
|
862
|
+
// src/tasks/eslint.ts
|
|
863
|
+
import { spinner as spinner7 } from "@clack/prompts";
|
|
864
|
+
import pc13 from "picocolors";
|
|
865
|
+
import fsPromises9 from "fs/promises";
|
|
866
|
+
import path11 from "path";
|
|
867
|
+
async function setupEslint(projectPath) {
|
|
868
|
+
const eslintDir = path11.join(projectPath, "packages", "eslint");
|
|
869
|
+
await fsPromises9.mkdir(eslintDir, { recursive: true });
|
|
870
|
+
await runCommand("pnpm init", { cwd: eslintDir, silent: true });
|
|
871
|
+
const eslintPackagePath = path11.join(eslintDir, "package.json");
|
|
872
|
+
const eslintPackageRaw = await fsPromises9.readFile(eslintPackagePath, "utf8");
|
|
873
|
+
const eslintPkg = JSON.parse(eslintPackageRaw);
|
|
874
|
+
eslintPkg.name = "@config/eslint";
|
|
875
|
+
eslintPkg.version = "1.0.0";
|
|
876
|
+
eslintPkg.private = true;
|
|
877
|
+
eslintPkg.type = "module";
|
|
878
|
+
eslintPkg.main = "eslint.config.ts";
|
|
879
|
+
eslintPkg.exports = {
|
|
880
|
+
".": "./eslint.config.ts"
|
|
881
|
+
};
|
|
882
|
+
eslintPkg.scripts = {
|
|
883
|
+
lint: "eslint ."
|
|
884
|
+
};
|
|
885
|
+
delete eslintPkg.keywords;
|
|
886
|
+
delete eslintPkg.author;
|
|
887
|
+
delete eslintPkg.license;
|
|
888
|
+
await fsPromises9.writeFile(
|
|
889
|
+
eslintPackagePath,
|
|
890
|
+
JSON.stringify(eslintPkg, null, 2),
|
|
891
|
+
"utf8"
|
|
892
|
+
);
|
|
893
|
+
await runCommand(
|
|
894
|
+
"pnpm add -D eslint @eslint/js @eslint/json globals typescript-eslint",
|
|
895
|
+
{ cwd: eslintDir }
|
|
896
|
+
);
|
|
897
|
+
const eslintConfigContent = [
|
|
898
|
+
'import js from "@eslint/js";',
|
|
899
|
+
'import globals from "globals";',
|
|
900
|
+
'import tseslint from "typescript-eslint";',
|
|
901
|
+
'import eslintJson from "@eslint/json";',
|
|
902
|
+
"",
|
|
903
|
+
"export default tseslint.config(",
|
|
904
|
+
" {",
|
|
905
|
+
' ignores: ["**/dist/**", "**/node_modules/**"],',
|
|
906
|
+
" },",
|
|
907
|
+
" js.configs.recommended,",
|
|
908
|
+
" ...tseslint.configs.recommended,",
|
|
909
|
+
" {",
|
|
910
|
+
" languageOptions: {",
|
|
911
|
+
" globals: {",
|
|
912
|
+
" ...globals.node,",
|
|
913
|
+
" },",
|
|
914
|
+
" },",
|
|
915
|
+
" rules: {",
|
|
916
|
+
' "no-unused-vars": "off",',
|
|
917
|
+
' "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],',
|
|
918
|
+
" },",
|
|
919
|
+
" },",
|
|
920
|
+
" {",
|
|
921
|
+
' files: ["**/*.json"],',
|
|
922
|
+
' language: "json/json",',
|
|
923
|
+
" ...eslintJson.configs.recommended,",
|
|
924
|
+
" }",
|
|
925
|
+
");",
|
|
926
|
+
""
|
|
927
|
+
].join("\n");
|
|
928
|
+
await fsPromises9.writeFile(
|
|
929
|
+
path11.join(eslintDir, "eslint.config.ts"),
|
|
930
|
+
eslintConfigContent,
|
|
931
|
+
"utf8"
|
|
932
|
+
);
|
|
933
|
+
const rootPackagePath = path11.join(projectPath, "package.json");
|
|
934
|
+
const rootPackageContent = await fsPromises9.readFile(rootPackagePath, "utf8");
|
|
935
|
+
const rootPkg = JSON.parse(rootPackageContent);
|
|
936
|
+
rootPkg.scripts = {
|
|
937
|
+
...rootPkg.scripts || {},
|
|
938
|
+
lint: "pnpm --filter @config/eslint lint"
|
|
939
|
+
};
|
|
940
|
+
await fsPromises9.writeFile(
|
|
941
|
+
rootPackagePath,
|
|
942
|
+
JSON.stringify(rootPkg, null, 2),
|
|
943
|
+
"utf8"
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
async function runEslintSetup(projectPath) {
|
|
947
|
+
const packagesDir = path11.join(projectPath, "packages");
|
|
948
|
+
try {
|
|
949
|
+
await fsPromises9.access(packagesDir);
|
|
950
|
+
} catch {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const s = spinner7();
|
|
954
|
+
console.log("\n");
|
|
955
|
+
s.start("Setting up modular ESLint in packages/eslint...");
|
|
956
|
+
try {
|
|
957
|
+
await setupEslint(projectPath);
|
|
958
|
+
s.stop(pc13.green("\u2714 Success: Modular ESLint configured in packages/eslint"));
|
|
959
|
+
} catch (error) {
|
|
960
|
+
s.stop(pc13.red("\u2716 Failed: ESLint setup failed"));
|
|
961
|
+
console.error(pc13.red(`
|
|
962
|
+
Error details: ${error.message || error}`));
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
console.log("\n");
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/tasks/typescript.ts
|
|
969
|
+
import { spinner as spinner8 } from "@clack/prompts";
|
|
970
|
+
import pc14 from "picocolors";
|
|
971
|
+
import fsPromises10 from "fs/promises";
|
|
972
|
+
import path12 from "path";
|
|
973
|
+
async function setupTypescript(projectPath) {
|
|
974
|
+
const tsDir = path12.join(projectPath, "packages", "typescript");
|
|
975
|
+
await fsPromises10.mkdir(tsDir, { recursive: true });
|
|
976
|
+
await runCommand("pnpm init", { cwd: tsDir, silent: true });
|
|
977
|
+
const tsPackagePath = path12.join(tsDir, "package.json");
|
|
978
|
+
const tsPackageRaw = await fsPromises10.readFile(tsPackagePath, "utf8");
|
|
979
|
+
const tsPkg = JSON.parse(tsPackageRaw);
|
|
980
|
+
tsPkg.name = "@config/typescript";
|
|
981
|
+
tsPkg.version = "1.0.0";
|
|
982
|
+
tsPkg.private = true;
|
|
983
|
+
tsPkg.type = "module";
|
|
984
|
+
tsPkg.exports = {
|
|
985
|
+
"./tsconfig.json": "./tsconfig.json"
|
|
986
|
+
};
|
|
987
|
+
delete tsPkg.keywords;
|
|
988
|
+
delete tsPkg.author;
|
|
989
|
+
delete tsPkg.license;
|
|
990
|
+
delete tsPkg.main;
|
|
991
|
+
delete tsPkg.scripts;
|
|
992
|
+
await fsPromises10.writeFile(
|
|
993
|
+
tsPackagePath,
|
|
994
|
+
JSON.stringify(tsPkg, null, 2),
|
|
995
|
+
"utf8"
|
|
996
|
+
);
|
|
997
|
+
await runCommand(
|
|
998
|
+
"cd ../.. && pnpm add -D typescript -w && cd packages/typescript",
|
|
999
|
+
{ cwd: tsDir, silent: true }
|
|
1000
|
+
);
|
|
1001
|
+
await runCommand("npx tsc --init", { cwd: tsDir });
|
|
1002
|
+
}
|
|
1003
|
+
async function runTypescriptSetup(projectPath) {
|
|
1004
|
+
const packagesDir = path12.join(projectPath, "packages");
|
|
1005
|
+
try {
|
|
1006
|
+
await fsPromises10.access(packagesDir);
|
|
1007
|
+
} catch {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const s = spinner8();
|
|
1011
|
+
console.log("\n");
|
|
1012
|
+
s.start("Setting up modular TypeScript config in packages/typescript...");
|
|
1013
|
+
try {
|
|
1014
|
+
await setupTypescript(projectPath);
|
|
1015
|
+
s.stop(
|
|
1016
|
+
pc14.green(
|
|
1017
|
+
"\u2714 Success: Modular TypeScript config configured in packages/typescript"
|
|
1018
|
+
)
|
|
1019
|
+
);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
s.stop(pc14.red("\u2716 Failed: TypeScript config setup failed"));
|
|
1022
|
+
console.error(pc14.red(`
|
|
1023
|
+
Error details: ${error.message || error}`));
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
console.log("\n");
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/tasks/server.ts
|
|
1030
|
+
import { confirm as confirm6, spinner as spinner9 } from "@clack/prompts";
|
|
1031
|
+
import pc15 from "picocolors";
|
|
1032
|
+
import fsPromises11 from "fs/promises";
|
|
1033
|
+
import path13 from "path";
|
|
1034
|
+
async function promptServer() {
|
|
1035
|
+
return await confirm6({
|
|
1036
|
+
message: "Do you want to setup a Express backend in apps/server?",
|
|
296
1037
|
initialValue: true
|
|
297
1038
|
});
|
|
298
1039
|
}
|
|
299
|
-
async function
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1040
|
+
async function setupServer(projectPath) {
|
|
1041
|
+
const serverDir = path13.join(projectPath, "apps", "server");
|
|
1042
|
+
const srcDir = path13.join(serverDir, "src");
|
|
1043
|
+
await fsPromises11.mkdir(srcDir, { recursive: true });
|
|
1044
|
+
await runCommand("pnpm init", { cwd: serverDir, silent: true });
|
|
1045
|
+
const serverPackagePath = path13.join(serverDir, "package.json");
|
|
1046
|
+
const serverPackageRaw = await fsPromises11.readFile(serverPackagePath, "utf8");
|
|
1047
|
+
const serverPkg = JSON.parse(serverPackageRaw);
|
|
1048
|
+
const eslintDir = path13.join(projectPath, "packages", "eslint");
|
|
1049
|
+
let hasEslint = false;
|
|
1050
|
+
try {
|
|
1051
|
+
await fsPromises11.access(eslintDir);
|
|
1052
|
+
hasEslint = true;
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
serverPkg.name = "server";
|
|
1056
|
+
serverPkg.version = "1.0.0";
|
|
1057
|
+
serverPkg.private = true;
|
|
1058
|
+
serverPkg.type = "module";
|
|
1059
|
+
serverPkg.main = "dist/index.js";
|
|
1060
|
+
serverPkg.scripts = {
|
|
1061
|
+
dev: "tsx watch src/index.ts",
|
|
1062
|
+
build: "tsc",
|
|
1063
|
+
start: "node dist/index.js"
|
|
1064
|
+
};
|
|
1065
|
+
serverPkg.devDependencies = {
|
|
1066
|
+
"@config/typescript": "workspace:*"
|
|
1067
|
+
};
|
|
1068
|
+
if (hasEslint) {
|
|
1069
|
+
serverPkg.devDependencies["@config/eslint"] = "workspace:*";
|
|
1070
|
+
serverPkg.scripts["lint"] = "eslint .";
|
|
1071
|
+
}
|
|
1072
|
+
delete serverPkg.keywords;
|
|
1073
|
+
delete serverPkg.author;
|
|
1074
|
+
delete serverPkg.license;
|
|
1075
|
+
await fsPromises11.writeFile(
|
|
1076
|
+
serverPackagePath,
|
|
1077
|
+
JSON.stringify(serverPkg, null, 2),
|
|
1078
|
+
"utf8"
|
|
1079
|
+
);
|
|
1080
|
+
const tsconfigContent = {
|
|
1081
|
+
extends: "@config/typescript/tsconfig.json",
|
|
1082
|
+
compilerOptions: {
|
|
1083
|
+
rootDir: "src",
|
|
1084
|
+
outDir: "dist"
|
|
1085
|
+
},
|
|
1086
|
+
include: ["src/**/*"]
|
|
1087
|
+
};
|
|
1088
|
+
await fsPromises11.writeFile(
|
|
1089
|
+
path13.join(serverDir, "tsconfig.json"),
|
|
1090
|
+
JSON.stringify(tsconfigContent, null, 2),
|
|
1091
|
+
"utf8"
|
|
1092
|
+
);
|
|
1093
|
+
if (hasEslint) {
|
|
1094
|
+
const eslintConfigContent = [
|
|
1095
|
+
"import baseConfig from '@config/eslint';",
|
|
1096
|
+
"",
|
|
1097
|
+
"export default [",
|
|
1098
|
+
" ...baseConfig,",
|
|
1099
|
+
" {",
|
|
1100
|
+
" // Add server-specific overrides if necessary",
|
|
1101
|
+
" },",
|
|
1102
|
+
"];",
|
|
1103
|
+
""
|
|
1104
|
+
].join("\n");
|
|
1105
|
+
await fsPromises11.writeFile(
|
|
1106
|
+
path13.join(serverDir, "eslint.config.ts"),
|
|
1107
|
+
eslintConfigContent,
|
|
1108
|
+
"utf8"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
await runCommand("pnpm add express cors", { cwd: serverDir, silent: false });
|
|
1112
|
+
await runCommand("pnpm add -D @types/express @types/cors tsx typescript", {
|
|
1113
|
+
cwd: serverDir,
|
|
1114
|
+
silent: false
|
|
1115
|
+
});
|
|
1116
|
+
const indexTsContent = [
|
|
1117
|
+
"import express from 'express';",
|
|
1118
|
+
"import cors from 'cors';",
|
|
306
1119
|
"",
|
|
307
|
-
"
|
|
308
|
-
"
|
|
309
|
-
"build/",
|
|
310
|
-
".next/",
|
|
311
|
-
"out/",
|
|
312
|
-
".turbo/",
|
|
1120
|
+
"const app = express();",
|
|
1121
|
+
"const port = process.env.PORT || 3001;",
|
|
313
1122
|
"",
|
|
314
|
-
"
|
|
315
|
-
".
|
|
316
|
-
".
|
|
317
|
-
".env.development.local",
|
|
318
|
-
".env.test.local",
|
|
319
|
-
".env.production.local",
|
|
1123
|
+
"// Enable CORS and parsing of JSON request bodies",
|
|
1124
|
+
"app.use(cors());",
|
|
1125
|
+
"app.use(express.json());",
|
|
320
1126
|
"",
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
1127
|
+
"// Root API health and welcome route",
|
|
1128
|
+
"app.get('/api', (req, res) => {",
|
|
1129
|
+
" res.json({",
|
|
1130
|
+
" message: 'Welcome to the Morax High-Performance Backend API!',",
|
|
1131
|
+
" timestamp: new Date().toISOString(),",
|
|
1132
|
+
" status: 'healthy',",
|
|
1133
|
+
" });",
|
|
1134
|
+
"});",
|
|
327
1135
|
"",
|
|
328
|
-
"
|
|
329
|
-
".
|
|
330
|
-
"
|
|
1136
|
+
"app.listen(port, () => {",
|
|
1137
|
+
" console.log(`\u{1F680} Server is running on http://localhost:${port}`);",
|
|
1138
|
+
"});",
|
|
331
1139
|
""
|
|
332
1140
|
].join("\n");
|
|
333
|
-
await
|
|
334
|
-
|
|
335
|
-
|
|
1141
|
+
await fsPromises11.writeFile(
|
|
1142
|
+
path13.join(srcDir, "index.ts"),
|
|
1143
|
+
indexTsContent,
|
|
336
1144
|
"utf8"
|
|
337
1145
|
);
|
|
338
1146
|
}
|
|
339
|
-
async function
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
1147
|
+
async function runServerSetup(projectPath) {
|
|
1148
|
+
const appsDir = path13.join(projectPath, "apps");
|
|
1149
|
+
try {
|
|
1150
|
+
await fsPromises11.access(appsDir);
|
|
1151
|
+
} catch {
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const serverPrompt = await promptServer();
|
|
1155
|
+
handleCancel(serverPrompt);
|
|
1156
|
+
if (serverPrompt) {
|
|
1157
|
+
const s = spinner9();
|
|
345
1158
|
console.log("\n");
|
|
346
|
-
s.start("
|
|
1159
|
+
s.start("Setting up modular Express backend in apps/server...");
|
|
347
1160
|
try {
|
|
348
|
-
await
|
|
349
|
-
s.stop(
|
|
350
|
-
|
|
1161
|
+
await setupServer(projectPath);
|
|
1162
|
+
s.stop(
|
|
1163
|
+
pc15.green(
|
|
1164
|
+
"\u2714 Success: Modular Express backend configured in apps/server"
|
|
1165
|
+
)
|
|
1166
|
+
);
|
|
351
1167
|
} catch (error) {
|
|
352
|
-
s.stop(
|
|
353
|
-
console.error(
|
|
1168
|
+
s.stop(pc15.red("\u2716 Failed: Express backend setup failed"));
|
|
1169
|
+
console.error(pc15.red(`
|
|
354
1170
|
Error details: ${error.message || error}`));
|
|
355
1171
|
process.exit(1);
|
|
356
1172
|
}
|
|
357
1173
|
console.log("\n");
|
|
358
1174
|
}
|
|
359
|
-
return gitInitialized;
|
|
360
1175
|
}
|
|
361
1176
|
|
|
362
1177
|
// src/tasks/husky.ts
|
|
363
|
-
import { confirm as
|
|
364
|
-
import
|
|
365
|
-
import
|
|
366
|
-
import
|
|
1178
|
+
import { confirm as confirm7, spinner as spinner10 } from "@clack/prompts";
|
|
1179
|
+
import pc16 from "picocolors";
|
|
1180
|
+
import fsPromises12 from "fs/promises";
|
|
1181
|
+
import path14 from "path";
|
|
367
1182
|
async function promptHusky() {
|
|
368
|
-
return await
|
|
1183
|
+
return await confirm7({
|
|
369
1184
|
message: "Do you want to setup Husky pre-commit hooks?",
|
|
370
1185
|
initialValue: true
|
|
371
1186
|
});
|
|
@@ -373,10 +1188,10 @@ async function promptHusky() {
|
|
|
373
1188
|
async function setupHusky(projectPath) {
|
|
374
1189
|
await runCommand("pnpm add -D -E husky -w", { cwd: projectPath });
|
|
375
1190
|
await runCommand("pnpm exec husky init", { cwd: projectPath });
|
|
376
|
-
const preCommitPath =
|
|
1191
|
+
const preCommitPath = path14.join(projectPath, ".husky", "pre-commit");
|
|
377
1192
|
let hookContent = "";
|
|
378
1193
|
try {
|
|
379
|
-
hookContent = await
|
|
1194
|
+
hookContent = await fsPromises12.readFile(preCommitPath, "utf8");
|
|
380
1195
|
} catch {
|
|
381
1196
|
hookContent = "pnpm test";
|
|
382
1197
|
}
|
|
@@ -384,9 +1199,9 @@ async function setupHusky(projectPath) {
|
|
|
384
1199
|
if (!hookContent.includes("git add .")) {
|
|
385
1200
|
hookContent = hookContent.trim() + "\ngit add .\n";
|
|
386
1201
|
}
|
|
387
|
-
await
|
|
1202
|
+
await fsPromises12.writeFile(preCommitPath, hookContent, "utf8");
|
|
388
1203
|
try {
|
|
389
|
-
await
|
|
1204
|
+
await fsPromises12.chmod(preCommitPath, 493);
|
|
390
1205
|
} catch {
|
|
391
1206
|
}
|
|
392
1207
|
}
|
|
@@ -395,15 +1210,15 @@ async function runHuskySetup(projectPath, gitInitialized) {
|
|
|
395
1210
|
const huskyPrompt = await promptHusky();
|
|
396
1211
|
handleCancel(huskyPrompt);
|
|
397
1212
|
if (huskyPrompt) {
|
|
398
|
-
const s =
|
|
1213
|
+
const s = spinner10();
|
|
399
1214
|
console.log("\n");
|
|
400
1215
|
s.start("Setting up Husky hooks...");
|
|
401
1216
|
try {
|
|
402
1217
|
await setupHusky(projectPath);
|
|
403
|
-
s.stop(
|
|
1218
|
+
s.stop(pc16.green("\u2714 Success: Husky pre-commit hooks configured"));
|
|
404
1219
|
} catch (error) {
|
|
405
|
-
s.stop(
|
|
406
|
-
console.error(
|
|
1220
|
+
s.stop(pc16.red("\u2716 Failed: Husky setup failed"));
|
|
1221
|
+
console.error(pc16.red(`
|
|
407
1222
|
Error details: ${error.message || error}`));
|
|
408
1223
|
process.exit(1);
|
|
409
1224
|
}
|
|
@@ -411,52 +1226,167 @@ Error details: ${error.message || error}`));
|
|
|
411
1226
|
}
|
|
412
1227
|
}
|
|
413
1228
|
|
|
414
|
-
// src/core/
|
|
415
|
-
import
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
console.log(
|
|
421
|
-
boxen2(
|
|
422
|
-
`\u26A1 ${pc8.bold("Workspace ready:")} ${pc8.yellow(name)}
|
|
423
|
-
Location: ${pc8.cyan(projectPath)}
|
|
424
|
-
|
|
425
|
-
${pc8.bold("To start developing:")}
|
|
426
|
-
1. ${pc8.cyan(`cd ${name}`)}
|
|
427
|
-
2. ${pc8.cyan("pnpm dev")}
|
|
428
|
-
|
|
429
|
-
\u{1F4A1} Code formatting & git orchestration is fully active.`,
|
|
1229
|
+
// src/core/web.ts
|
|
1230
|
+
import { select } from "@clack/prompts";
|
|
1231
|
+
async function addweb(projectPath) {
|
|
1232
|
+
const framework = await select({
|
|
1233
|
+
message: "Which frontend website setup would you like to include in apps/web?",
|
|
1234
|
+
options: [
|
|
430
1235
|
{
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1236
|
+
value: "nextjs",
|
|
1237
|
+
label: "Next.js",
|
|
1238
|
+
hint: "The React Framework for the Web"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
value: "react",
|
|
1242
|
+
label: "React (Vite)",
|
|
1243
|
+
hint: "Vite React Starter Template"
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
value: "skip",
|
|
1247
|
+
label: "Skip",
|
|
1248
|
+
hint: "Skip setting up a frontend website"
|
|
437
1249
|
}
|
|
438
|
-
|
|
439
|
-
|
|
1250
|
+
],
|
|
1251
|
+
initialValue: "nextjs"
|
|
1252
|
+
});
|
|
1253
|
+
handleCancel(framework);
|
|
1254
|
+
if (framework === "nextjs") {
|
|
1255
|
+
await setupNextjs(projectPath);
|
|
1256
|
+
await runShadcnSetup(projectPath);
|
|
1257
|
+
} else if (framework === "react") {
|
|
1258
|
+
await setupReact(projectPath);
|
|
1259
|
+
}
|
|
440
1260
|
}
|
|
441
1261
|
|
|
442
1262
|
// src/core/runner.ts
|
|
443
|
-
async function runWorkspaceScaffolder() {
|
|
1263
|
+
async function runWorkspaceScaffolder(options = {}) {
|
|
444
1264
|
startCli();
|
|
445
|
-
const
|
|
446
|
-
const
|
|
1265
|
+
const isMonorepo = !options.react && !options.next;
|
|
1266
|
+
const name = await promptWorkspaceName(isMonorepo);
|
|
1267
|
+
const projectPath = path15.join(process.cwd(), name);
|
|
1268
|
+
if (options.react) {
|
|
1269
|
+
await runReactScaffolder(name, projectPath);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
if (options.next) {
|
|
1273
|
+
await runNextScaffolder(name, projectPath);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
447
1276
|
await makeDirectories(name, projectPath);
|
|
1277
|
+
await setupReadme(projectPath, name);
|
|
448
1278
|
const gitInitialized = await runGitSetup(projectPath);
|
|
449
|
-
await
|
|
450
|
-
await
|
|
1279
|
+
const selectedApps = await askServerorFrontendInclue();
|
|
1280
|
+
const selectedTools = await askWhichToolToInclude(
|
|
1281
|
+
selectedApps.includes("server")
|
|
1282
|
+
);
|
|
1283
|
+
if (selectedTools.includes("prettier")) {
|
|
1284
|
+
await runPrettierSetup(projectPath);
|
|
1285
|
+
}
|
|
1286
|
+
if (selectedTools.includes("husky")) {
|
|
1287
|
+
await runHuskySetup(projectPath, gitInitialized);
|
|
1288
|
+
}
|
|
1289
|
+
if (selectedTools.includes("eslint")) {
|
|
1290
|
+
await runEslintSetup(projectPath);
|
|
1291
|
+
}
|
|
1292
|
+
if (selectedTools.includes("typescript")) {
|
|
1293
|
+
await runTypescriptSetup(projectPath);
|
|
1294
|
+
}
|
|
1295
|
+
if (selectedApps.includes("server")) {
|
|
1296
|
+
await runServerSetup(projectPath);
|
|
1297
|
+
}
|
|
1298
|
+
if (selectedApps.includes("web")) {
|
|
1299
|
+
await addweb(projectPath);
|
|
1300
|
+
}
|
|
1301
|
+
const s = spinner11();
|
|
1302
|
+
console.log("\n");
|
|
1303
|
+
s.start("Running final workspace package installation...");
|
|
1304
|
+
try {
|
|
1305
|
+
await runCommand("pnpm install", { cwd: projectPath, silent: true });
|
|
1306
|
+
s.stop(
|
|
1307
|
+
pc17.green(
|
|
1308
|
+
"\u2714 Success: Workspace dependencies and symlinks configured successfully"
|
|
1309
|
+
)
|
|
1310
|
+
);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
s.stop(pc17.red("\u2716 Warning: Final workspace installation failed"));
|
|
1313
|
+
console.error(pc17.red(`
|
|
1314
|
+
Error details: ${error.message || error}`));
|
|
1315
|
+
}
|
|
1316
|
+
console.log("\n");
|
|
451
1317
|
endCli(name, projectPath);
|
|
452
1318
|
}
|
|
1319
|
+
async function askWhichToolToInclude(hasServer) {
|
|
1320
|
+
const options = [
|
|
1321
|
+
{
|
|
1322
|
+
value: "prettier",
|
|
1323
|
+
label: "Prettier",
|
|
1324
|
+
hint: "Code formatter rules & auto-formatting script"
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
value: "husky",
|
|
1328
|
+
label: "Husky",
|
|
1329
|
+
hint: "Git pre-commit hooks setup (requires Git)"
|
|
1330
|
+
}
|
|
1331
|
+
];
|
|
1332
|
+
if (!hasServer) {
|
|
1333
|
+
options.splice(
|
|
1334
|
+
1,
|
|
1335
|
+
0,
|
|
1336
|
+
{
|
|
1337
|
+
value: "eslint",
|
|
1338
|
+
label: "ESLint",
|
|
1339
|
+
hint: "Modern Flat Config linter configurations"
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
value: "typescript",
|
|
1343
|
+
label: "TypeScript",
|
|
1344
|
+
hint: "Extendable shared TypeScript configurations"
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
const tools = await multiselect({
|
|
1349
|
+
message: hasServer ? "ESLint & TypeScript are required for the Express backend. Select additional configurations/tools:" : "Which configurations/linter tools would you like to set up?",
|
|
1350
|
+
options,
|
|
1351
|
+
required: false
|
|
1352
|
+
});
|
|
1353
|
+
handleCancel(tools);
|
|
1354
|
+
const selectedTools = tools;
|
|
1355
|
+
if (hasServer) {
|
|
1356
|
+
if (!selectedTools.includes("eslint")) selectedTools.push("eslint");
|
|
1357
|
+
if (!selectedTools.includes("typescript")) selectedTools.push("typescript");
|
|
1358
|
+
}
|
|
1359
|
+
return selectedTools;
|
|
1360
|
+
}
|
|
1361
|
+
async function askServerorFrontendInclue() {
|
|
1362
|
+
const apps = await multiselect({
|
|
1363
|
+
message: "Which backend/frontend applications would you like to set up?",
|
|
1364
|
+
options: [
|
|
1365
|
+
{
|
|
1366
|
+
value: "web",
|
|
1367
|
+
label: "Frontend Web App",
|
|
1368
|
+
hint: "Scaffold Next.js or React (Vite) frontend in apps/web"
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
value: "server",
|
|
1372
|
+
label: "Backend Server",
|
|
1373
|
+
hint: "Scaffold Express backend in apps/server"
|
|
1374
|
+
}
|
|
1375
|
+
],
|
|
1376
|
+
required: false
|
|
1377
|
+
});
|
|
1378
|
+
handleCancel(apps);
|
|
1379
|
+
return apps;
|
|
1380
|
+
}
|
|
453
1381
|
|
|
454
1382
|
// src/index.ts
|
|
455
1383
|
async function main() {
|
|
456
|
-
|
|
1384
|
+
const isReactOnly = process.argv.includes("--react") || process.argv.includes("-r");
|
|
1385
|
+
const isNextOnly = process.argv.includes("--nextjs") || process.argv.includes("--next") || process.argv.includes("-n");
|
|
1386
|
+
await runWorkspaceScaffolder({ react: isReactOnly, next: isNextOnly });
|
|
457
1387
|
}
|
|
458
1388
|
main().catch((err) => {
|
|
459
|
-
console.error(
|
|
1389
|
+
console.error(pc18.red("Fatal Error during execution:"), err);
|
|
460
1390
|
process.exit(1);
|
|
461
1391
|
});
|
|
462
1392
|
//# sourceMappingURL=index.js.map
|