create-template-project 0.1.0 → 0.3.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/README.md +11 -11
- package/dist/config/dependencies.json +60 -44
- package/dist/index.js +1221 -0
- package/dist/templates/base/files/.github/workflows/node.js.yml +5 -3
- package/dist/templates/base/files/.husky/commit-msg +1 -0
- package/dist/templates/base/files/.husky/pre-commit +1 -0
- package/dist/templates/base/files/.prettierignore +53 -0
- package/dist/templates/base/files/.prettierrc.json +4 -2
- package/dist/templates/base/files/AGENTS.md +5 -2
- package/dist/templates/base/files/README.md +36 -13
- package/dist/templates/base/files/_oxlint.config.ts +17 -33
- package/dist/templates/base/files/package.json +1 -1
- package/dist/templates/base/files/tsconfig.json +32 -32
- package/dist/templates/base/files/vitest.config.ts +11 -2
- package/dist/templates/cli/files/package.json +4 -3
- package/dist/templates/cli/files/src/index.test.ts +10 -2
- package/dist/templates/cli/files/src/index.ts +4 -1
- package/dist/templates/cli/files/src/lib.ts +10 -0
- package/dist/templates/cli/files/vite.config.ts +22 -0
- package/dist/templates/web-app/files/index.html +11 -0
- package/dist/templates/web-app/files/playwright.config.ts +26 -0
- package/dist/templates/web-app/files/src/App.test.tsx +9 -0
- package/dist/templates/web-app/files/src/App.tsx +14 -0
- package/dist/templates/web-app/files/src/index.tsx +8 -0
- package/dist/templates/web-app/files/tests/e2e/basic.e2e-test.ts +13 -0
- package/dist/templates/web-app/files/vite.config.ts +34 -0
- package/dist/templates/web-fullstack/files/client/index.html +10 -0
- package/dist/templates/{fullstack → web-fullstack}/files/client/package.json +2 -2
- package/dist/templates/web-fullstack/files/client/src/App.test.tsx +11 -0
- package/dist/templates/{fullstack → web-fullstack}/files/client/src/contexts/AuthContext.tsx +1 -5
- package/dist/templates/{fullstack → web-fullstack}/files/client/src/pages/Login.tsx +4 -33
- package/dist/templates/web-fullstack/files/client/vite.config.ts +30 -0
- package/dist/templates/{fullstack → web-fullstack}/files/package.json +1 -2
- package/dist/templates/web-fullstack/files/playwright.config.ts +33 -0
- package/dist/templates/{fullstack → web-fullstack}/files/server/package.json +2 -2
- package/dist/templates/web-fullstack/files/server/src/index.test.ts +28 -0
- package/dist/templates/{fullstack → web-fullstack}/files/server/src/trpc.ts +1 -1
- package/dist/templates/web-fullstack/files/server/vite.config.ts +24 -0
- package/dist/templates/web-fullstack/files/tests/e2e/basic.e2e-test.ts +14 -0
- package/dist/templates/web-vanilla/files/index.html +11 -0
- package/dist/templates/web-vanilla/files/package.json +17 -0
- package/dist/templates/web-vanilla/files/playwright.config.ts +26 -0
- package/dist/templates/web-vanilla/files/src/index.test.ts +12 -0
- package/dist/templates/web-vanilla/files/src/index.ts +3 -0
- package/dist/templates/web-vanilla/files/src/lib.ts +9 -0
- package/dist/templates/web-vanilla/files/tests/e2e/basic.e2e-test.ts +11 -0
- package/dist/templates/web-vanilla/files/vite.config.ts +28 -0
- package/package.json +25 -27
- package/dist/cli.mjs +0 -272
- package/dist/generators/project.mjs +0 -354
- package/dist/index.d.mts +0 -4
- package/dist/index.mjs +0 -32
- package/dist/templates/base/index.mjs +0 -16
- package/dist/templates/cli/files/tsdown.config.ts +0 -3
- package/dist/templates/cli/index.mjs +0 -16
- package/dist/templates/fullstack/files/client/index.html +0 -8
- package/dist/templates/fullstack/files/client/src/App.test.tsx +0 -8
- package/dist/templates/fullstack/files/client/tsdown.config.ts +0 -3
- package/dist/templates/fullstack/files/server/src/index.test.ts +0 -7
- package/dist/templates/fullstack/files/server/tsdown.config.ts +0 -3
- package/dist/templates/fullstack/index.mjs +0 -42
- package/dist/templates/webapp/files/backend/src/index.ts +0 -17
- package/dist/templates/webapp/files/frontend/index.html +0 -9
- package/dist/templates/webapp/files/frontend/src/index.ts +0 -4
- package/dist/templates/webapp/files/package.json +0 -13
- package/dist/templates/webapp/files/src/index.test.ts +0 -5
- package/dist/templates/webapp/files/tsdown.config.ts +0 -10
- package/dist/templates/webapp/index.mjs +0 -16
- package/dist/templates/webpage/files/index.html +0 -8
- package/dist/templates/webpage/files/package.json +0 -8
- package/dist/templates/webpage/files/src/index.test.ts +0 -5
- package/dist/templates/webpage/files/src/index.ts +0 -1
- package/dist/templates/webpage/index.mjs +0 -16
- package/dist/types.mjs +0 -30
- package/dist/utils/file.mjs +0 -101
- /package/dist/templates/{fullstack → web-fullstack}/files/client/src/App.tsx +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/client/src/components/ProtectedRoute.tsx +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/client/src/main.tsx +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/client/src/pages/Dashboard.tsx +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/client/src/trpc.ts +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/server/src/context.ts +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/server/src/index.ts +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/server/src/routers/_app.ts +0 -0
- /package/dist/templates/{fullstack → web-fullstack}/files/server/src/routers/auth.ts +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { cancel, intro, outro } from "@clack/prompts";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import debugLib from "debug";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { execa } from "execa";
|
|
12
|
+
//#region src/types.ts
|
|
13
|
+
var TemplateTypeSchema = z.enum([
|
|
14
|
+
"cli",
|
|
15
|
+
"web-vanilla",
|
|
16
|
+
"web-app",
|
|
17
|
+
"web-fullstack"
|
|
18
|
+
]);
|
|
19
|
+
var PackageManagerSchema = z.enum([
|
|
20
|
+
"npm",
|
|
21
|
+
"pnpm",
|
|
22
|
+
"yarn"
|
|
23
|
+
]);
|
|
24
|
+
var ProjectOptionsSchema = z.object({
|
|
25
|
+
template: TemplateTypeSchema,
|
|
26
|
+
projectName: z.string().min(1, "Project name is required"),
|
|
27
|
+
packageManager: PackageManagerSchema.optional().default("npm"),
|
|
28
|
+
createGithubRepository: z.boolean().optional().default(false),
|
|
29
|
+
directory: z.string(),
|
|
30
|
+
overwrite: z.boolean().optional().default(false),
|
|
31
|
+
update: z.boolean().optional().default(false),
|
|
32
|
+
installDependencies: z.boolean().optional().default(false),
|
|
33
|
+
build: z.boolean().optional().default(false),
|
|
34
|
+
dev: z.boolean().optional().default(false),
|
|
35
|
+
open: z.boolean().optional().default(false),
|
|
36
|
+
progress: z.boolean().optional().default(true)
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils/file.ts
|
|
40
|
+
var debug$3 = debugLib("create-template-project:utils:file");
|
|
41
|
+
function getTemplateDir(dirname, templateName) {
|
|
42
|
+
const sourcePath = path.resolve(dirname, "files");
|
|
43
|
+
const distPath = path.resolve(dirname, "templates", templateName, "files");
|
|
44
|
+
return existsSync(distPath) ? distPath : sourcePath;
|
|
45
|
+
}
|
|
46
|
+
async function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
47
|
+
const files = await fs.readdir(dirPath);
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
if (file === ".DS_Store") continue;
|
|
50
|
+
if ((await fs.stat(path.join(dirPath, file))).isDirectory()) arrayOfFiles = await getAllFiles(path.join(dirPath, file), arrayOfFiles);
|
|
51
|
+
else arrayOfFiles.push(path.join(dirPath, file));
|
|
52
|
+
}
|
|
53
|
+
return arrayOfFiles;
|
|
54
|
+
}
|
|
55
|
+
function processContent(filePath, content, opts, addedDeps) {
|
|
56
|
+
const { projectName, template } = opts;
|
|
57
|
+
let description = "";
|
|
58
|
+
switch (template) {
|
|
59
|
+
case "cli":
|
|
60
|
+
description = "A modern Node.js CLI application with TypeScript and automated tooling.";
|
|
61
|
+
break;
|
|
62
|
+
case "web-vanilla":
|
|
63
|
+
description = "A standalone web page/application for modern browsers.";
|
|
64
|
+
break;
|
|
65
|
+
case "web-fullstack":
|
|
66
|
+
description = "A full-stack monorepo with an Express server and a React/MUI client.";
|
|
67
|
+
break;
|
|
68
|
+
case "web-app":
|
|
69
|
+
description = "A React application with MUI and TanStack Query.";
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
const pm = opts.packageManager || "npm";
|
|
73
|
+
let processed = content.replaceAll("{{projectName}}", projectName).replaceAll("{{description}}", description).replaceAll("{{packageManager}}", pm);
|
|
74
|
+
if (filePath.includes(".github/workflows/node.js.yml")) {
|
|
75
|
+
let installCommand = "npm ci";
|
|
76
|
+
let pmSetup = "";
|
|
77
|
+
if (pm === "pnpm") {
|
|
78
|
+
installCommand = "pnpm install --frozen-lockfile";
|
|
79
|
+
pmSetup = "- uses: pnpm/action-setup@v4\n with:\n version: 9";
|
|
80
|
+
} else if (pm === "yarn") installCommand = "yarn install --frozen-lockfile";
|
|
81
|
+
let playwrightSetup = "";
|
|
82
|
+
if (template === "web-fullstack" || template === "web-app" || template === "web-vanilla") playwrightSetup = "- name: Install Playwright Browsers & Deps\n run: npx playwright install --with-deps chromium";
|
|
83
|
+
processed = processed.replaceAll("{{installCommand}}", installCommand).replaceAll("# [PM_SETUP]", pmSetup).replaceAll("# [PLAYWRIGHT_SETUP]", playwrightSetup);
|
|
84
|
+
processed = processed.replace(/^\s*# \[PM_SETUP\]\s*\n/m, "");
|
|
85
|
+
processed = processed.replace(/^\s*# \[PLAYWRIGHT_SETUP\]\s*\n/m, "");
|
|
86
|
+
}
|
|
87
|
+
if (template === "web-vanilla" && filePath === "index.html") processed = processed.replace("{{scriptSrc}}", "/src/index.ts");
|
|
88
|
+
if (filePath === "CONTRIBUTING.md" && addedDeps.length > 0) {
|
|
89
|
+
processed += "\n## Dependencies\n\n";
|
|
90
|
+
const uniqueDeps = Array.from(new Set(addedDeps.map((d) => JSON.stringify(d)))).map((s) => JSON.parse(s));
|
|
91
|
+
for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
|
|
92
|
+
}
|
|
93
|
+
if ((template === "web-fullstack" || template === "web-vanilla" || template === "web-app") && filePath === "tsconfig.json") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, "/* Language and Environment */\n \"target\": \"ES2023\" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,\n \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,\n \"module\": \"ESNext\" /* Specify what module code is generated. */,\n \"moduleResolution\": \"bundler\" /* Specify how TypeScript looks up a file from a given module specifier. */,\n \"esModuleInterop\": true /* Emit additional JavaScript to ease support for importing CommonJS modules. */,\n \"resolveJsonModule\": true /* Enable importing .json files. */,\n \"allowImportingTsExtensions\": true /* Allow imports to include TypeScript file extensions. */,\n \"noEmit\": true /* Disable emitting files from a compilation. */,\n \"jsx\": \"react-jsx\" /* Specify what JSX code is generated. */,\n\n /* Strict Type-Checking Options */");
|
|
94
|
+
if (template === "web-fullstack" && filePath === "tsconfig.json") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
|
|
95
|
+
return processed;
|
|
96
|
+
}
|
|
97
|
+
function mergePackageJson(target, source) {
|
|
98
|
+
if (source.scripts) target.scripts = {
|
|
99
|
+
...target.scripts,
|
|
100
|
+
...source.scripts
|
|
101
|
+
};
|
|
102
|
+
if (source.dependencies) target.dependencies = {
|
|
103
|
+
...target.dependencies,
|
|
104
|
+
...source.dependencies
|
|
105
|
+
};
|
|
106
|
+
if (source.devDependencies) target.devDependencies = {
|
|
107
|
+
...target.devDependencies,
|
|
108
|
+
...source.devDependencies
|
|
109
|
+
};
|
|
110
|
+
if (source.workspaces) target.workspaces = source.workspaces;
|
|
111
|
+
if (source.bin) target.bin = source.bin;
|
|
112
|
+
}
|
|
113
|
+
function isSeedFile(filePath) {
|
|
114
|
+
return [
|
|
115
|
+
"src/",
|
|
116
|
+
"client/src/",
|
|
117
|
+
"server/src/",
|
|
118
|
+
"backend/src/",
|
|
119
|
+
"frontend/src/"
|
|
120
|
+
].some((dir) => filePath.startsWith(dir)) || [
|
|
121
|
+
"index.html",
|
|
122
|
+
"App.tsx",
|
|
123
|
+
"main.tsx",
|
|
124
|
+
"index.tsx"
|
|
125
|
+
].some((file) => filePath === file);
|
|
126
|
+
}
|
|
127
|
+
async function mergeFile(filePath, existing, template, log) {
|
|
128
|
+
debug$3("Merging file: %s", filePath);
|
|
129
|
+
const tempBase = filePath + ".base.tmp";
|
|
130
|
+
const tempNew = filePath + ".new.tmp";
|
|
131
|
+
try {
|
|
132
|
+
await fs.writeFile(tempNew, template);
|
|
133
|
+
await fs.writeFile(tempBase, "");
|
|
134
|
+
try {
|
|
135
|
+
const stdio = debug$3.enabled ? "inherit" : "pipe";
|
|
136
|
+
debug$3("Executing: git merge-file %s %s %s", filePath, tempBase, tempNew);
|
|
137
|
+
await execa("git", [
|
|
138
|
+
"merge-file",
|
|
139
|
+
filePath,
|
|
140
|
+
tempBase,
|
|
141
|
+
tempNew
|
|
142
|
+
], {
|
|
143
|
+
stdio,
|
|
144
|
+
preferLocal: true
|
|
145
|
+
});
|
|
146
|
+
return (await fs.readFile(filePath, "utf8")).trim() !== template.trim() ? "merged" : "updated";
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (e.exitCode === 1) return "conflict";
|
|
149
|
+
else {
|
|
150
|
+
debug$3("Git merge-file failed: %O", e);
|
|
151
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
152
|
+
log.error(`Failed to merge ${filePath}: ${e.message}${detail}`);
|
|
153
|
+
return "error";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
await fs.rm(tempBase, { force: true });
|
|
158
|
+
await fs.rm(tempNew, { force: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/templates/base/index.ts
|
|
163
|
+
var __dirname$5 = path.dirname(fileURLToPath(import.meta.url));
|
|
164
|
+
var getBaseTemplate = (_opts) => {
|
|
165
|
+
return {
|
|
166
|
+
name: "base",
|
|
167
|
+
description: "The foundation for all project templates, including common tooling and configuration.",
|
|
168
|
+
components: [
|
|
169
|
+
{
|
|
170
|
+
name: "TypeScript",
|
|
171
|
+
description: "Mandatory strict mode for type safety."
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "oxlint",
|
|
175
|
+
description: "Ultra-fast Rust-based linter."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "Prettier",
|
|
179
|
+
description: "Consistent code formatting."
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "Vitest",
|
|
183
|
+
description: "Modern, fast unit testing with coverage."
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "Husky",
|
|
187
|
+
description: "Git hooks for pre-commit linting."
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "commitlint",
|
|
191
|
+
description: "Standardized commit messages."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "conventional-changelog",
|
|
195
|
+
description: "Automated release notes."
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "debug",
|
|
199
|
+
description: "Structured logging for debugging."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "Zod",
|
|
203
|
+
description: "TypeScript-first schema validation for runtime type safety."
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
dependencies: { zod: "zod" },
|
|
207
|
+
devDependencies: { "eslint-plugin-regexp": "" },
|
|
208
|
+
scripts: {},
|
|
209
|
+
files: [],
|
|
210
|
+
templateDir: getTemplateDir(__dirname$5, "base")
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/templates/cli/index.ts
|
|
215
|
+
var __dirname$4 = path.dirname(fileURLToPath(import.meta.url));
|
|
216
|
+
var getCliTemplate = (_opts) => {
|
|
217
|
+
return {
|
|
218
|
+
name: "cli",
|
|
219
|
+
description: "A robust Node.js command-line application template.",
|
|
220
|
+
components: [
|
|
221
|
+
{
|
|
222
|
+
name: "commander",
|
|
223
|
+
description: "The complete solution for Node.js command-line interfaces."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "cli-progress",
|
|
227
|
+
description: "Easy to use progress-bar for terminal applications."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "Vite",
|
|
231
|
+
description: "Fast, modern frontend and backend build tool."
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
dependencies: {},
|
|
235
|
+
devDependencies: { vite: "vite" },
|
|
236
|
+
scripts: {},
|
|
237
|
+
files: [],
|
|
238
|
+
templateDir: getTemplateDir(__dirname$4, "cli")
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/templates/web-vanilla/index.ts
|
|
243
|
+
var __dirname$3 = path.dirname(fileURLToPath(import.meta.url));
|
|
244
|
+
var getWebVanillaTemplate = (_opts) => {
|
|
245
|
+
return {
|
|
246
|
+
name: "web-vanilla",
|
|
247
|
+
description: "A modern, standalone web page template with built-in development and testing tooling.",
|
|
248
|
+
components: [
|
|
249
|
+
{
|
|
250
|
+
name: "Vite",
|
|
251
|
+
description: "Fast frontend build tool and development server."
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "Vitest",
|
|
255
|
+
description: "Modern testing framework with browser support."
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "Playwright",
|
|
259
|
+
description: "Comprehensive end-to-end testing for modern web apps."
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
dependencies: {},
|
|
263
|
+
devDependencies: {
|
|
264
|
+
vite: "vite",
|
|
265
|
+
vitest: "vitest",
|
|
266
|
+
"@vitest/browser": "@vitest/browser",
|
|
267
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
268
|
+
playwright: "playwright",
|
|
269
|
+
"@playwright/test": "@playwright/test"
|
|
270
|
+
},
|
|
271
|
+
scripts: {
|
|
272
|
+
dev: "vite",
|
|
273
|
+
build: "vite build",
|
|
274
|
+
preview: "vite preview",
|
|
275
|
+
test: "vitest run",
|
|
276
|
+
"test:ui": "vitest",
|
|
277
|
+
"test:e2e": "playwright test"
|
|
278
|
+
},
|
|
279
|
+
files: [],
|
|
280
|
+
templateDir: getTemplateDir(__dirname$3, "web-vanilla")
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/templates/web-app/index.ts
|
|
285
|
+
var __dirname$2 = path.dirname(fileURLToPath(import.meta.url));
|
|
286
|
+
var getWebAppTemplate = (_opts) => {
|
|
287
|
+
return {
|
|
288
|
+
name: "web-app",
|
|
289
|
+
description: "A React application with MUI and TanStack Query, powered by Vite.",
|
|
290
|
+
components: [
|
|
291
|
+
{
|
|
292
|
+
name: "React",
|
|
293
|
+
description: "Powerful library for building component-based user interfaces."
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "MUI",
|
|
297
|
+
description: "Rich set of Material Design UI components for React."
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "TanStack React Query",
|
|
301
|
+
description: "Powerful asynchronous state management for React."
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "Vite",
|
|
305
|
+
description: "Next-generation frontend tooling."
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "Vitest",
|
|
309
|
+
description: "Testing framework with cross-browser support."
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "Playwright",
|
|
313
|
+
description: "End-to-end testing for modern web apps."
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
dependencies: {
|
|
317
|
+
react: "react",
|
|
318
|
+
"react-dom": "react-dom",
|
|
319
|
+
"@mui/material": "@mui/material",
|
|
320
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
321
|
+
"@emotion/react": "@emotion/react",
|
|
322
|
+
"@emotion/styled": "@emotion/styled",
|
|
323
|
+
"@tanstack/react-query": "@tanstack/react-query"
|
|
324
|
+
},
|
|
325
|
+
devDependencies: {
|
|
326
|
+
"@types/react": "@types/react",
|
|
327
|
+
"@types/react-dom": "@types/react-dom",
|
|
328
|
+
vite: "vite",
|
|
329
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
330
|
+
vitest: "vitest",
|
|
331
|
+
"@vitest/browser": "@vitest/browser",
|
|
332
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
333
|
+
playwright: "playwright",
|
|
334
|
+
"@playwright/test": "@playwright/test",
|
|
335
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
336
|
+
},
|
|
337
|
+
scripts: {
|
|
338
|
+
dev: "vite",
|
|
339
|
+
build: "vite build",
|
|
340
|
+
preview: "vite preview",
|
|
341
|
+
test: "vitest run",
|
|
342
|
+
"test:ui": "vitest",
|
|
343
|
+
"test:e2e": "playwright test",
|
|
344
|
+
start: "vite preview"
|
|
345
|
+
},
|
|
346
|
+
files: [],
|
|
347
|
+
templateDir: getTemplateDir(__dirname$2, "web-app")
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/templates/web-fullstack/index.ts
|
|
352
|
+
var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
353
|
+
var getWebFullstackTemplate = (_opts) => {
|
|
354
|
+
return {
|
|
355
|
+
name: "web-fullstack",
|
|
356
|
+
description: "A comprehensive full-stack monorepo featuring an Express backend with tRPC and a modern React client with MUI.",
|
|
357
|
+
components: [
|
|
358
|
+
{
|
|
359
|
+
name: "React",
|
|
360
|
+
description: "Powerful library for building component-based user interfaces."
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "MUI",
|
|
364
|
+
description: "Rich set of Material Design UI components for React."
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "tRPC",
|
|
368
|
+
description: "End-to-end typesafe APIs made easy."
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "React Query",
|
|
372
|
+
description: "Powerful asynchronous state management for React."
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: "Express",
|
|
376
|
+
description: "Fast, minimalist backend web framework."
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "React Router",
|
|
380
|
+
description: "Declarative routing for the frontend."
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "Vite",
|
|
384
|
+
description: "Fast, modern frontend build tool."
|
|
385
|
+
}
|
|
386
|
+
],
|
|
387
|
+
dependencies: {
|
|
388
|
+
react: "react",
|
|
389
|
+
"react-dom": "react-dom",
|
|
390
|
+
"@mui/material": "@mui/material",
|
|
391
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
392
|
+
"@emotion/react": "@emotion/react",
|
|
393
|
+
"@emotion/styled": "@emotion/styled",
|
|
394
|
+
express: "express",
|
|
395
|
+
"@trpc/server": "@trpc/server",
|
|
396
|
+
"@trpc/client": "@trpc/client",
|
|
397
|
+
"@trpc/react-query": "@trpc/react-query",
|
|
398
|
+
"@tanstack/react-query": "@tanstack/react-query",
|
|
399
|
+
"react-router-dom": "react-router-dom",
|
|
400
|
+
cors: "cors"
|
|
401
|
+
},
|
|
402
|
+
devDependencies: {
|
|
403
|
+
"@types/react": "@types/react",
|
|
404
|
+
"@types/react-dom": "@types/react-dom",
|
|
405
|
+
"@types/express": "@types/express",
|
|
406
|
+
"@types/cors": "@types/cors",
|
|
407
|
+
"@playwright/test": "@playwright/test",
|
|
408
|
+
vite: "vite",
|
|
409
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
410
|
+
vitest: "vitest",
|
|
411
|
+
"@vitest/browser": "@vitest/browser",
|
|
412
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
413
|
+
playwright: "playwright",
|
|
414
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
415
|
+
},
|
|
416
|
+
scripts: {
|
|
417
|
+
build: "npm run build --workspaces",
|
|
418
|
+
dev: "npm run dev --workspaces",
|
|
419
|
+
test: "npm run test --workspaces",
|
|
420
|
+
"test:e2e": "playwright test"
|
|
421
|
+
},
|
|
422
|
+
files: [],
|
|
423
|
+
templateDir: getTemplateDir(__dirname$1, "web-fullstack")
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/generators/info.ts
|
|
428
|
+
var MOCK_OPTS = {
|
|
429
|
+
template: "cli",
|
|
430
|
+
projectName: "mock",
|
|
431
|
+
directory: ".",
|
|
432
|
+
packageManager: "npm",
|
|
433
|
+
overwrite: false,
|
|
434
|
+
update: false,
|
|
435
|
+
installDependencies: false,
|
|
436
|
+
build: false,
|
|
437
|
+
dev: false,
|
|
438
|
+
open: false,
|
|
439
|
+
progress: true,
|
|
440
|
+
createGithubRepository: false
|
|
441
|
+
};
|
|
442
|
+
var getTemplateInfo = (type) => {
|
|
443
|
+
const opts = {
|
|
444
|
+
...MOCK_OPTS,
|
|
445
|
+
template: type
|
|
446
|
+
};
|
|
447
|
+
const base = getBaseTemplate(opts);
|
|
448
|
+
let template;
|
|
449
|
+
switch (type) {
|
|
450
|
+
case "cli":
|
|
451
|
+
template = getCliTemplate(opts);
|
|
452
|
+
break;
|
|
453
|
+
case "web-vanilla":
|
|
454
|
+
template = getWebVanillaTemplate(opts);
|
|
455
|
+
break;
|
|
456
|
+
case "web-app":
|
|
457
|
+
template = getWebAppTemplate(opts);
|
|
458
|
+
break;
|
|
459
|
+
case "web-fullstack":
|
|
460
|
+
template = getWebFullstackTemplate(opts);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
name: template.name,
|
|
465
|
+
description: template.description,
|
|
466
|
+
components: [...base.components, ...template.components]
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
var getAllTemplatesInfo = () => {
|
|
470
|
+
return [
|
|
471
|
+
"cli",
|
|
472
|
+
"web-vanilla",
|
|
473
|
+
"web-app",
|
|
474
|
+
"web-fullstack"
|
|
475
|
+
].map((type) => getTemplateInfo(type));
|
|
476
|
+
};
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/cli.ts
|
|
479
|
+
var pathExists$1 = (p) => fs.access(p).then(() => true).catch(() => false);
|
|
480
|
+
var debug$2 = debugLib("create-template-project:cli");
|
|
481
|
+
var parseArgs = async () => {
|
|
482
|
+
debug$2("Parsing CLI arguments: %O", process.argv);
|
|
483
|
+
const program = new Command();
|
|
484
|
+
if (process.env.NODE_ENV === "test") program.configureOutput({
|
|
485
|
+
writeOut: () => {},
|
|
486
|
+
writeErr: () => {}
|
|
487
|
+
});
|
|
488
|
+
program.name("create-template-project").exitOverride().description("Scaffold a new project template").version("0.1.0").option("--debug", "Enable debug output").on("option:debug", () => {
|
|
489
|
+
process.env["DEBUG"] = "create-template-project:*";
|
|
490
|
+
debugLib.enable("create-template-project:*");
|
|
491
|
+
}).addHelpText("after", `
|
|
492
|
+
Commands:
|
|
493
|
+
create - Create a new project from a template.
|
|
494
|
+
update - Update an existing project from its template.
|
|
495
|
+
interactive - Start interactive project configuration.
|
|
496
|
+
info - Show detailed information about available templates and components.
|
|
497
|
+
|
|
498
|
+
Templates:
|
|
499
|
+
cli - Node.js CLI application with commander and cli-progress.
|
|
500
|
+
web-vanilla - Standalone web page (modern HTML/JS).
|
|
501
|
+
web-app - React application with MUI and TanStack Query.
|
|
502
|
+
web-fullstack - Full-stack monorepo with Express server and React/MUI client.
|
|
503
|
+
`);
|
|
504
|
+
let commandResult;
|
|
505
|
+
program.command("info").description("Show detailed information about available templates and their components").option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").action((opts) => {
|
|
506
|
+
debug$2("Executing \"info\" command with options: %O", opts);
|
|
507
|
+
p.intro("Template Information");
|
|
508
|
+
if (opts.template) {
|
|
509
|
+
const typeResult = TemplateTypeSchema.safeParse(opts.template);
|
|
510
|
+
if (!typeResult.success) {
|
|
511
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
const info = getTemplateInfo(typeResult.data);
|
|
515
|
+
p.note([
|
|
516
|
+
`Description: ${info.description}`,
|
|
517
|
+
"",
|
|
518
|
+
"Components:",
|
|
519
|
+
...info.components.map((c) => ` ● ${c.name}: ${c.description}`)
|
|
520
|
+
].join("\n"), `Template: ${info.name}`);
|
|
521
|
+
} else {
|
|
522
|
+
const allInfo = getAllTemplatesInfo();
|
|
523
|
+
for (const info of allInfo) p.note([
|
|
524
|
+
`Description: ${info.description}`,
|
|
525
|
+
"",
|
|
526
|
+
"Components:",
|
|
527
|
+
...info.components.map((c) => ` ● ${c.name}: ${c.description}`)
|
|
528
|
+
].join("\n"), `Template: ${info.name}`);
|
|
529
|
+
}
|
|
530
|
+
p.outro("Use \"create\" to scaffold a new project.");
|
|
531
|
+
process.exit(0);
|
|
532
|
+
});
|
|
533
|
+
program.command("create").description("Create a new project from a template").option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").option("-n, --name <name>", "Project name").option("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "npm").option("--create-github-repository", "Create GitHub project").option("-d, --directory <path>", "Output directory", ".").option("--overwrite", "Overwrite existing directory by removing it first", false).option("--install-dependencies", "Install dependencies after scaffolding", false).option("--build", "Run the CI script (lint, build, test) after scaffolding", false).option("--dev", "Run the dev server after scaffolding", false).option("--open", "Open the browser after scaffolding", false).option("--no-progress", "Do not show progress indicators").action((opts) => {
|
|
534
|
+
debug$2("Executing \"create\" command with options: %O", opts);
|
|
535
|
+
commandResult = {
|
|
536
|
+
...opts,
|
|
537
|
+
update: false,
|
|
538
|
+
template: opts.template,
|
|
539
|
+
projectName: opts.name,
|
|
540
|
+
packageManager: opts.packageManager,
|
|
541
|
+
directory: path.resolve(opts.directory),
|
|
542
|
+
createGithubRepository: !!opts.createGithubRepository,
|
|
543
|
+
overwrite: !!opts.overwrite,
|
|
544
|
+
progress: !!opts.progress
|
|
545
|
+
};
|
|
546
|
+
debug$2("Processed \"create\" options: %O", commandResult);
|
|
547
|
+
});
|
|
548
|
+
program.command("update").description("Update an existing project from its template").addHelpText("after", `
|
|
549
|
+
Details:
|
|
550
|
+
The update command syncs your project with the latest template changes.
|
|
551
|
+
It intelligently merges changes into your existing files using 'git merge-file'.
|
|
552
|
+
|
|
553
|
+
Restrictions & Behavior:
|
|
554
|
+
- Seed Files: Files in 'src/', 'client/src/', etc., are considered "seed" files and are NEVER overwritten or modified during an update to protect your application logic.
|
|
555
|
+
- package.json: Dependencies and scripts are merged. Existing versions are preserved unless they are missing.
|
|
556
|
+
- Merging: For non-seed files, the tool attempts to merge template changes. If a conflict occurs, it will be marked with standard git conflict markers.
|
|
557
|
+
- Confirmation: The command will always show a summary of proposed changes (ADD, MODIFY) and ask for your confirmation before applying them.
|
|
558
|
+
`).option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").option("-n, --name <name>", "Project name").option("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "npm").option("--create-github-repository", "Create GitHub project").option("-d, --directory <path>", "Output directory", ".").option("--overwrite", "Overwrite existing directory by removing it first", false).option("--install-dependencies", "Install dependencies after scaffolding", false).option("--build", "Run the CI script (lint, build, test) after updating", false).option("--dev", "Run the dev server after scaffolding", false).option("--open", "Open the browser after scaffolding", false).option("--no-progress", "Do not show progress indicators").action((opts) => {
|
|
559
|
+
debug$2("Executing \"update\" command with options: %O", opts);
|
|
560
|
+
commandResult = {
|
|
561
|
+
...opts,
|
|
562
|
+
update: true,
|
|
563
|
+
template: opts.template,
|
|
564
|
+
projectName: opts.name,
|
|
565
|
+
packageManager: opts.packageManager,
|
|
566
|
+
directory: path.resolve(opts.directory),
|
|
567
|
+
createGithubRepository: !!opts.createGithubRepository,
|
|
568
|
+
overwrite: !!opts.overwrite,
|
|
569
|
+
progress: !!opts.progress
|
|
570
|
+
};
|
|
571
|
+
debug$2("Processed \"update\" options: %O", commandResult);
|
|
572
|
+
});
|
|
573
|
+
program.command("interactive").description("Start interactive project configuration").action(async () => {
|
|
574
|
+
debug$2("Starting interactive configuration");
|
|
575
|
+
const projectName = await p.text({
|
|
576
|
+
message: "Project name:",
|
|
577
|
+
placeholder: "my-app",
|
|
578
|
+
defaultValue: "my-app",
|
|
579
|
+
validate: (value) => value && value.length > 0 ? void 0 : "Project name is required"
|
|
580
|
+
});
|
|
581
|
+
if (p.isCancel(projectName)) {
|
|
582
|
+
p.cancel("Operation cancelled.");
|
|
583
|
+
process.exit(0);
|
|
584
|
+
}
|
|
585
|
+
const directory = await p.text({
|
|
586
|
+
message: "Target directory:",
|
|
587
|
+
initialValue: "."
|
|
588
|
+
});
|
|
589
|
+
if (p.isCancel(directory)) {
|
|
590
|
+
p.cancel("Operation cancelled.");
|
|
591
|
+
process.exit(0);
|
|
592
|
+
}
|
|
593
|
+
const projectDir = path.resolve(directory, projectName);
|
|
594
|
+
const exists = await pathExists$1(projectDir);
|
|
595
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
596
|
+
const pkgExists = await pathExists$1(pkgPath);
|
|
597
|
+
let existingConfig = {};
|
|
598
|
+
if (pkgExists) try {
|
|
599
|
+
existingConfig = JSON.parse(await fs.readFile(pkgPath, "utf8"))["create-template-project"] || {};
|
|
600
|
+
debug$2("Found existing project config: %O", existingConfig);
|
|
601
|
+
} catch (e) {
|
|
602
|
+
debug$2("Failed to read existing package.json: %O", e);
|
|
603
|
+
}
|
|
604
|
+
let update = false;
|
|
605
|
+
let overwrite = false;
|
|
606
|
+
if (exists) {
|
|
607
|
+
const action = await p.select({
|
|
608
|
+
message: `Directory "${projectDir}" already exists. What would you like to do?`,
|
|
609
|
+
options: [
|
|
610
|
+
{
|
|
611
|
+
label: "Run an update",
|
|
612
|
+
value: "update"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
label: "Overwrite existing directory by removing it first",
|
|
616
|
+
value: "overwrite"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
label: "Cancel",
|
|
620
|
+
value: "cancel"
|
|
621
|
+
}
|
|
622
|
+
]
|
|
623
|
+
});
|
|
624
|
+
if (p.isCancel(action) || action === "cancel") {
|
|
625
|
+
p.cancel("Operation cancelled.");
|
|
626
|
+
process.exit(0);
|
|
627
|
+
}
|
|
628
|
+
if (action === "update") update = true;
|
|
629
|
+
else if (action === "overwrite") overwrite = true;
|
|
630
|
+
}
|
|
631
|
+
let template = existingConfig.template;
|
|
632
|
+
if (!update || !template) {
|
|
633
|
+
template = await p.select({
|
|
634
|
+
message: "Select project template:",
|
|
635
|
+
initialValue: template || "cli",
|
|
636
|
+
options: [
|
|
637
|
+
{
|
|
638
|
+
label: "CLI Application (Node.js)",
|
|
639
|
+
value: "cli"
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
label: "Web-Vanilla (Standalone)",
|
|
643
|
+
value: "web-vanilla"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
label: "Web-App (React + MUI)",
|
|
647
|
+
value: "web-app"
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
label: "Web-Fullstack (Express + React Monorepo)",
|
|
651
|
+
value: "web-fullstack"
|
|
652
|
+
}
|
|
653
|
+
]
|
|
654
|
+
});
|
|
655
|
+
if (p.isCancel(template)) {
|
|
656
|
+
p.cancel("Operation cancelled.");
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
659
|
+
} else p.log.info(`Using existing template type: ${template}`);
|
|
660
|
+
let packageManager = "npm";
|
|
661
|
+
if (!update) {
|
|
662
|
+
packageManager = await p.select({
|
|
663
|
+
message: "Select package manager:",
|
|
664
|
+
initialValue: "npm",
|
|
665
|
+
options: [
|
|
666
|
+
{
|
|
667
|
+
label: "npm",
|
|
668
|
+
value: "npm"
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
label: "pnpm",
|
|
672
|
+
value: "pnpm"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
label: "yarn",
|
|
676
|
+
value: "yarn"
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
});
|
|
680
|
+
if (p.isCancel(packageManager)) {
|
|
681
|
+
p.cancel("Operation cancelled.");
|
|
682
|
+
process.exit(0);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const installDependenciesRes = await p.confirm({
|
|
686
|
+
message: "Should we install dependencies?",
|
|
687
|
+
initialValue: true
|
|
688
|
+
});
|
|
689
|
+
if (p.isCancel(installDependenciesRes)) {
|
|
690
|
+
p.cancel("Operation cancelled.");
|
|
691
|
+
process.exit(0);
|
|
692
|
+
}
|
|
693
|
+
const installDependencies = installDependenciesRes;
|
|
694
|
+
let build = false;
|
|
695
|
+
if (installDependencies) {
|
|
696
|
+
const res = await p.confirm({
|
|
697
|
+
message: "Should we run the CI script (lint, build, test)?",
|
|
698
|
+
initialValue: true
|
|
699
|
+
});
|
|
700
|
+
if (p.isCancel(res)) {
|
|
701
|
+
p.cancel("Operation cancelled.");
|
|
702
|
+
process.exit(0);
|
|
703
|
+
}
|
|
704
|
+
build = res;
|
|
705
|
+
}
|
|
706
|
+
const createGithubRepositoryRes = await p.confirm({
|
|
707
|
+
message: "Should we create a GitHub repository?",
|
|
708
|
+
initialValue: false
|
|
709
|
+
});
|
|
710
|
+
if (p.isCancel(createGithubRepositoryRes)) {
|
|
711
|
+
p.cancel("Operation cancelled.");
|
|
712
|
+
process.exit(0);
|
|
713
|
+
}
|
|
714
|
+
commandResult = {
|
|
715
|
+
template,
|
|
716
|
+
projectName,
|
|
717
|
+
packageManager,
|
|
718
|
+
createGithubRepository: createGithubRepositoryRes,
|
|
719
|
+
directory: path.resolve(directory),
|
|
720
|
+
update,
|
|
721
|
+
overwrite,
|
|
722
|
+
installDependencies,
|
|
723
|
+
build,
|
|
724
|
+
dev: false,
|
|
725
|
+
open: false,
|
|
726
|
+
progress: true
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
if (process.argv.length <= 2) {
|
|
730
|
+
program.outputHelp();
|
|
731
|
+
process.exit(0);
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
await program.parseAsync(process.argv);
|
|
735
|
+
} catch (e) {
|
|
736
|
+
if (e.code === "commander.helpDisplayed" || e.code === "commander.version" || e.code === "PROCESS_EXIT_0") process.exit(0);
|
|
737
|
+
if (e.code === "PROCESS_EXIT_1") process.exit(1);
|
|
738
|
+
p.cancel(e.message);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
if (!commandResult) {
|
|
742
|
+
debug$2("No command result found");
|
|
743
|
+
p.cancel("Unknown command or missing options.");
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
debug$2("Validating command result with Zod");
|
|
747
|
+
const validationResult = ProjectOptionsSchema.safeParse(commandResult);
|
|
748
|
+
if (!validationResult.success) {
|
|
749
|
+
const errors = validationResult.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
750
|
+
p.cancel(`Invalid options: ${errors}`);
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
commandResult = validationResult.data;
|
|
754
|
+
const projectDir = path.resolve(commandResult.directory, commandResult.projectName);
|
|
755
|
+
if (await pathExists$1(projectDir) && !commandResult.update && !commandResult.overwrite) {
|
|
756
|
+
p.cancel(`Directory "${projectDir}" already exists. Use --overwrite to overwrite or "update" command.`);
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
if (commandResult.open) {
|
|
760
|
+
commandResult.dev = true;
|
|
761
|
+
commandResult.installDependencies = true;
|
|
762
|
+
}
|
|
763
|
+
if (commandResult.dev || commandResult.build) commandResult.installDependencies = true;
|
|
764
|
+
return commandResult;
|
|
765
|
+
};
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/generators/project.ts
|
|
768
|
+
var debug$1 = debugLib("create-template-project:generator");
|
|
769
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
770
|
+
var getDependencyConfigPath = async () => {
|
|
771
|
+
const sourcePath = path.resolve(__dirname, "../config/dependencies.json");
|
|
772
|
+
const distPath = path.resolve(__dirname, "config/dependencies.json");
|
|
773
|
+
return await pathExists(distPath) ? distPath : sourcePath;
|
|
774
|
+
};
|
|
775
|
+
var pathExists = (p) => fs.access(p).then(() => true).catch(() => false);
|
|
776
|
+
var getLog = (progress) => ({
|
|
777
|
+
info: (msg) => progress ? p.log.info(msg) : void 0,
|
|
778
|
+
success: (msg) => progress ? p.log.success(msg) : void 0,
|
|
779
|
+
warn: (msg) => p.log.warn(msg),
|
|
780
|
+
error: (msg) => p.log.error(msg)
|
|
781
|
+
});
|
|
782
|
+
var getSpinner = (progress) => {
|
|
783
|
+
const s = p.spinner();
|
|
784
|
+
return {
|
|
785
|
+
start: (msg) => progress ? s.start(msg) : void 0,
|
|
786
|
+
stop: (msg) => progress ? s.stop(msg) : void 0,
|
|
787
|
+
message: (msg) => progress ? s.message(msg) : void 0
|
|
788
|
+
};
|
|
789
|
+
};
|
|
790
|
+
var showNote = (msg, title, progress) => {
|
|
791
|
+
if (progress !== void 0 && !progress) return;
|
|
792
|
+
if (title) p.log.success(title);
|
|
793
|
+
p.note(msg);
|
|
794
|
+
};
|
|
795
|
+
var generateProject = async (opts) => {
|
|
796
|
+
const { template: type, projectName, directory, update, overwrite, progress } = opts;
|
|
797
|
+
const isProgress = progress !== false;
|
|
798
|
+
const log = getLog(isProgress);
|
|
799
|
+
const spinner = () => getSpinner(isProgress);
|
|
800
|
+
const projectDir = path.join(directory, projectName);
|
|
801
|
+
debug$1("Project generation started for: %s", projectName);
|
|
802
|
+
debug$1("Options: %O", opts);
|
|
803
|
+
debug$1("Project directory: %s", projectDir);
|
|
804
|
+
let isUpdate = !!update;
|
|
805
|
+
if (await pathExists(projectDir)) {
|
|
806
|
+
if (overwrite) {
|
|
807
|
+
await fs.rm(projectDir, {
|
|
808
|
+
recursive: true,
|
|
809
|
+
force: true
|
|
810
|
+
});
|
|
811
|
+
isUpdate = false;
|
|
812
|
+
} else if (!isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use --overwrite to replace it or --update to update.`);
|
|
813
|
+
}
|
|
814
|
+
const templates = [getBaseTemplate(opts)];
|
|
815
|
+
debug$1("Applying template: base");
|
|
816
|
+
switch (type) {
|
|
817
|
+
case "cli":
|
|
818
|
+
debug$1("Applying template: cli");
|
|
819
|
+
templates.push(getCliTemplate(opts));
|
|
820
|
+
break;
|
|
821
|
+
case "web-vanilla":
|
|
822
|
+
debug$1("Applying template: web-vanilla");
|
|
823
|
+
templates.push(getWebVanillaTemplate(opts));
|
|
824
|
+
break;
|
|
825
|
+
case "web-app":
|
|
826
|
+
debug$1("Applying template: web-app");
|
|
827
|
+
templates.push(getWebAppTemplate(opts));
|
|
828
|
+
break;
|
|
829
|
+
case "web-fullstack":
|
|
830
|
+
debug$1("Applying template: web-fullstack");
|
|
831
|
+
templates.push(getWebFullstackTemplate(opts));
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
debug$1("Ensuring directory exists: %s", projectDir);
|
|
835
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
836
|
+
debug$1("Loading dependency configuration");
|
|
837
|
+
const depConfigPath = await getDependencyConfigPath();
|
|
838
|
+
const depConfig = JSON.parse(await fs.readFile(depConfigPath, "utf8"));
|
|
839
|
+
const addedDeps = [];
|
|
840
|
+
const resolveDeps = (deps = {}) => {
|
|
841
|
+
for (const dep of Object.keys(deps)) {
|
|
842
|
+
const config = depConfig.dependencies[dep];
|
|
843
|
+
if (config) {
|
|
844
|
+
deps[dep] = config.version;
|
|
845
|
+
addedDeps.push({
|
|
846
|
+
name: dep,
|
|
847
|
+
description: config.description
|
|
848
|
+
});
|
|
849
|
+
} else {
|
|
850
|
+
log.warn(`Dependency "${dep}" not found in master configuration. Using empty version.`);
|
|
851
|
+
debug$1(`Dependency "${dep}" missing in config`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
let finalPkg = {
|
|
856
|
+
name: projectName,
|
|
857
|
+
version: "0.1.0",
|
|
858
|
+
type: "module",
|
|
859
|
+
"create-template-project": { template: type },
|
|
860
|
+
scripts: {},
|
|
861
|
+
dependencies: {},
|
|
862
|
+
devDependencies: {}
|
|
863
|
+
};
|
|
864
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
865
|
+
if (isUpdate && await pathExists(pkgPath)) {
|
|
866
|
+
debug$1("Loading existing package.json for update");
|
|
867
|
+
const existingPkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
868
|
+
finalPkg = {
|
|
869
|
+
...finalPkg,
|
|
870
|
+
...existingPkg
|
|
871
|
+
};
|
|
872
|
+
finalPkg["create-template-project"] = {
|
|
873
|
+
...existingPkg["create-template-project"],
|
|
874
|
+
template: type
|
|
875
|
+
};
|
|
876
|
+
finalPkg.scripts = { ...existingPkg.scripts };
|
|
877
|
+
finalPkg.dependencies = { ...existingPkg.dependencies };
|
|
878
|
+
finalPkg.devDependencies = { ...existingPkg.devDependencies };
|
|
879
|
+
debug$1("Loaded existing package.json: %O", finalPkg);
|
|
880
|
+
}
|
|
881
|
+
for (const t of templates) {
|
|
882
|
+
debug$1("Collecting dependencies and scripts from template: %s", t.name);
|
|
883
|
+
const templateDeps = { ...t.dependencies };
|
|
884
|
+
const templateDevDeps = { ...t.devDependencies };
|
|
885
|
+
resolveDeps(templateDeps);
|
|
886
|
+
resolveDeps(templateDevDeps);
|
|
887
|
+
Object.assign(finalPkg.scripts, t.scripts);
|
|
888
|
+
Object.assign(finalPkg.dependencies, templateDeps);
|
|
889
|
+
Object.assign(finalPkg.devDependencies, templateDevDeps);
|
|
890
|
+
if (t.workspaces) finalPkg.workspaces = t.workspaces;
|
|
891
|
+
if (t.templateDir) {
|
|
892
|
+
const templatePkgPath = path.join(t.templateDir, "package.json");
|
|
893
|
+
if (await pathExists(templatePkgPath)) {
|
|
894
|
+
const pkgPart = JSON.parse(await fs.readFile(templatePkgPath, "utf8"));
|
|
895
|
+
resolveDeps(pkgPart.dependencies);
|
|
896
|
+
resolveDeps(pkgPart.devDependencies);
|
|
897
|
+
mergePackageJson(finalPkg, pkgPart);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const actions = [];
|
|
902
|
+
const pendingOperations = [];
|
|
903
|
+
for (const t of templates) {
|
|
904
|
+
debug$1("Collecting template files for: %s", t.name);
|
|
905
|
+
if (t.templateDir) {
|
|
906
|
+
debug$1("Reading physical files from: %s", t.templateDir);
|
|
907
|
+
const files = await getAllFiles(t.templateDir);
|
|
908
|
+
for (const file of files) {
|
|
909
|
+
let relativePath = path.relative(t.templateDir, file);
|
|
910
|
+
let targetPath = path.join(projectDir, relativePath);
|
|
911
|
+
if (isUpdate && isSeedFile(relativePath)) {
|
|
912
|
+
actions.push({
|
|
913
|
+
type: "SKIP",
|
|
914
|
+
path: relativePath
|
|
915
|
+
});
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (relativePath === "_oxlint.config.ts") {
|
|
919
|
+
relativePath = "oxlint.config.ts";
|
|
920
|
+
targetPath = path.join(projectDir, relativePath);
|
|
921
|
+
}
|
|
922
|
+
if (relativePath === "package.json") continue;
|
|
923
|
+
const finalTargetPath = targetPath;
|
|
924
|
+
const finalRelativePath = relativePath;
|
|
925
|
+
const content = processContent(relativePath, await fs.readFile(file, "utf8"), opts, addedDeps);
|
|
926
|
+
const exists = await pathExists(finalTargetPath);
|
|
927
|
+
if (isUpdate && exists) {
|
|
928
|
+
const existingContent = await fs.readFile(finalTargetPath, "utf8");
|
|
929
|
+
if (existingContent.trim() !== content.trim()) {
|
|
930
|
+
actions.push({
|
|
931
|
+
type: "MODIFY",
|
|
932
|
+
path: finalRelativePath
|
|
933
|
+
});
|
|
934
|
+
pendingOperations.push(async () => {
|
|
935
|
+
const result = await mergeFile(finalTargetPath, existingContent, content, log);
|
|
936
|
+
if (result === "merged") log.info(`ℹ Merged: ${finalRelativePath}`);
|
|
937
|
+
else if (result === "conflict") log.warn(`⚠ Conflict: ${finalRelativePath}`);
|
|
938
|
+
else if (result === "updated") log.info(`✔ Updated: ${finalRelativePath}`);
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
} else if (!exists) {
|
|
942
|
+
actions.push({
|
|
943
|
+
type: "ADD",
|
|
944
|
+
path: finalRelativePath
|
|
945
|
+
});
|
|
946
|
+
pendingOperations.push(async () => {
|
|
947
|
+
await fs.mkdir(path.dirname(finalTargetPath), { recursive: true });
|
|
948
|
+
await fs.writeFile(finalTargetPath, content);
|
|
949
|
+
const normalizedPath = finalRelativePath.split(path.sep).join("/");
|
|
950
|
+
if (normalizedPath.startsWith(".husky/") && !normalizedPath.endsWith("/")) await fs.chmod(finalTargetPath, 493);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (const file of t.files) {
|
|
956
|
+
const targetPath = path.join(projectDir, file.path);
|
|
957
|
+
if (isUpdate && isSeedFile(file.path)) {
|
|
958
|
+
actions.push({
|
|
959
|
+
type: "SKIP",
|
|
960
|
+
path: file.path
|
|
961
|
+
});
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
let content = typeof file.content === "function" ? file.content() : file.content;
|
|
965
|
+
content = processContent(file.path, content, opts, addedDeps);
|
|
966
|
+
const exists = await pathExists(targetPath);
|
|
967
|
+
if (isUpdate && exists) {
|
|
968
|
+
const existingContent = await fs.readFile(targetPath, "utf8");
|
|
969
|
+
if (existingContent.trim() !== content.trim()) {
|
|
970
|
+
actions.push({
|
|
971
|
+
type: "MODIFY",
|
|
972
|
+
path: file.path
|
|
973
|
+
});
|
|
974
|
+
pendingOperations.push(async () => {
|
|
975
|
+
const result = await mergeFile(targetPath, existingContent, content, log);
|
|
976
|
+
if (result === "merged") log.info(`ℹ Merged: ${file.path}`);
|
|
977
|
+
else if (result === "conflict") log.warn(`⚠ Conflict: ${file.path}`);
|
|
978
|
+
else if (result === "updated") log.info(`✔ Updated: ${file.path}`);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
} else if (!exists) {
|
|
982
|
+
actions.push({
|
|
983
|
+
type: "ADD",
|
|
984
|
+
path: file.path
|
|
985
|
+
});
|
|
986
|
+
pendingOperations.push(async () => {
|
|
987
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
988
|
+
await fs.writeFile(targetPath, content);
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const pm = opts.packageManager || "npm";
|
|
994
|
+
if (pm !== "npm") {
|
|
995
|
+
for (const [key, value] of Object.entries(finalPkg.scripts)) if (typeof value === "string") finalPkg.scripts[key] = value.replaceAll("npm run ", `${pm} run `);
|
|
996
|
+
}
|
|
997
|
+
if (pm === "pnpm" && finalPkg.workspaces) {
|
|
998
|
+
debug$1("Creating pnpm-workspace.yaml");
|
|
999
|
+
const workspaceYaml = `packages:\n${finalPkg.workspaces.map((w) => ` - '${w}'`).join("\n")}\n`;
|
|
1000
|
+
pendingOperations.push(async () => {
|
|
1001
|
+
await fs.writeFile(path.join(projectDir, "pnpm-workspace.yaml"), workspaceYaml);
|
|
1002
|
+
});
|
|
1003
|
+
delete finalPkg.workspaces;
|
|
1004
|
+
for (const key of Object.keys(finalPkg.scripts)) {
|
|
1005
|
+
const value = finalPkg.scripts[key];
|
|
1006
|
+
if (typeof value === "string" && value.includes("--workspaces")) finalPkg.scripts[key] = value.replace(" run ", " -r run ").replace(" --workspaces", "");
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (type === "cli" || type === "web-vanilla" || type === "web-app" || type === "web-fullstack") {
|
|
1010
|
+
const fullPath = path.join(projectDir, "vitest.config.ts");
|
|
1011
|
+
pendingOperations.push(async () => {
|
|
1012
|
+
await fs.rm(fullPath, { force: true });
|
|
1013
|
+
});
|
|
1014
|
+
if (isUpdate && await pathExists(fullPath)) actions.push({
|
|
1015
|
+
type: "DELETE",
|
|
1016
|
+
path: "vitest.config.ts"
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
if (isUpdate) actions.push({
|
|
1020
|
+
type: "MODIFY",
|
|
1021
|
+
path: "package.json"
|
|
1022
|
+
});
|
|
1023
|
+
else actions.push({
|
|
1024
|
+
type: "ADD",
|
|
1025
|
+
path: "package.json"
|
|
1026
|
+
});
|
|
1027
|
+
pendingOperations.push(async () => {
|
|
1028
|
+
debug$1("Writing final consolidated package.json to: %s", pkgPath);
|
|
1029
|
+
await fs.writeFile(pkgPath, JSON.stringify(finalPkg, null, " "));
|
|
1030
|
+
});
|
|
1031
|
+
if (isUpdate && actions.length > 0 && process.env.NODE_ENV !== "test") {
|
|
1032
|
+
const summary = actions.filter((a) => a.type !== "SKIP").map((a) => ` ${a.type.padEnd(8)} ${a.path}`).join("\n");
|
|
1033
|
+
if (summary) {
|
|
1034
|
+
p.note(summary, "Planned changes:");
|
|
1035
|
+
const confirm = await p.confirm({
|
|
1036
|
+
message: "Do you want to apply these changes?",
|
|
1037
|
+
initialValue: true
|
|
1038
|
+
});
|
|
1039
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
1040
|
+
p.cancel("Update cancelled.");
|
|
1041
|
+
process.exit(0);
|
|
1042
|
+
}
|
|
1043
|
+
} else log.info("No changes detected.");
|
|
1044
|
+
}
|
|
1045
|
+
for (const op of pendingOperations) await op();
|
|
1046
|
+
const stdio = debug$1.enabled ? "inherit" : "pipe";
|
|
1047
|
+
if (!await pathExists(path.join(projectDir, ".git"))) {
|
|
1048
|
+
debug$1("Initializing Git repository");
|
|
1049
|
+
try {
|
|
1050
|
+
debug$1("Executing: git init");
|
|
1051
|
+
await execa("git", ["init"], {
|
|
1052
|
+
cwd: projectDir,
|
|
1053
|
+
stdio,
|
|
1054
|
+
preferLocal: true
|
|
1055
|
+
});
|
|
1056
|
+
log.success("Initialized Git repository (git init).");
|
|
1057
|
+
} catch (e) {
|
|
1058
|
+
debug$1("Failed to initialize Git: %O", e);
|
|
1059
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1060
|
+
log.error(`Failed to initialize Git: ${e.message}${detail}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (opts.createGithubRepository && !isUpdate) {
|
|
1064
|
+
debug$1("Creating GitHub repository");
|
|
1065
|
+
try {
|
|
1066
|
+
debug$1("Executing: gh repo create %s --public --source=. --remote=origin", projectName);
|
|
1067
|
+
await execa("gh", [
|
|
1068
|
+
"repo",
|
|
1069
|
+
"create",
|
|
1070
|
+
projectName,
|
|
1071
|
+
"--public",
|
|
1072
|
+
"--source=.",
|
|
1073
|
+
"--remote=origin"
|
|
1074
|
+
], {
|
|
1075
|
+
cwd: projectDir,
|
|
1076
|
+
stdio,
|
|
1077
|
+
preferLocal: true
|
|
1078
|
+
});
|
|
1079
|
+
log.success("Created GitHub repository (gh repo create).");
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
debug$1("Failed to create GitHub repository: %O", e);
|
|
1082
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1083
|
+
log.warn(`Failed to create GitHub repository: ${e.message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (opts.installDependencies) {
|
|
1087
|
+
debug$1("Installing dependencies using %s", pm);
|
|
1088
|
+
const s = spinner();
|
|
1089
|
+
s.start(`Installing dependencies using ${pm}...`);
|
|
1090
|
+
try {
|
|
1091
|
+
debug$1("Executing: %s install", pm);
|
|
1092
|
+
await execa(pm, ["install"], {
|
|
1093
|
+
cwd: projectDir,
|
|
1094
|
+
stdio,
|
|
1095
|
+
preferLocal: true
|
|
1096
|
+
});
|
|
1097
|
+
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Dependencies installed (${pm} install).`);
|
|
1098
|
+
} catch (e) {
|
|
1099
|
+
debug$1("Failed to install dependencies: %O", e);
|
|
1100
|
+
s.stop("Failed to install dependencies.");
|
|
1101
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1102
|
+
log.error(`${e.message}${detail}`);
|
|
1103
|
+
throw new Error(`Failed to install dependencies: ${e.message}${detail}`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (opts.build && finalPkg.scripts.ci) {
|
|
1107
|
+
debug$1("Running CI script");
|
|
1108
|
+
const s = spinner();
|
|
1109
|
+
if (finalPkg.scripts["prettier-write"]) {
|
|
1110
|
+
s.start(`Formatting files with Prettier (${pm} run prettier-write)...`);
|
|
1111
|
+
try {
|
|
1112
|
+
debug$1("Executing: %s run prettier-write", pm);
|
|
1113
|
+
await execa(pm, ["run", "prettier-write"], {
|
|
1114
|
+
cwd: projectDir,
|
|
1115
|
+
stdio,
|
|
1116
|
+
preferLocal: true
|
|
1117
|
+
});
|
|
1118
|
+
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Files formatted (${pm} run prettier-write).`);
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
debug$1("Failed to format files: %O", e);
|
|
1121
|
+
s.stop("Failed to format files.");
|
|
1122
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1123
|
+
log.error(`${e.message}${detail}`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
s.start(`Running CI script (lint, build, test) (${pm} run ci)...`);
|
|
1127
|
+
try {
|
|
1128
|
+
debug$1("Executing: %s run ci", pm);
|
|
1129
|
+
await execa(pm, ["run", "ci"], {
|
|
1130
|
+
cwd: projectDir,
|
|
1131
|
+
stdio,
|
|
1132
|
+
preferLocal: true
|
|
1133
|
+
});
|
|
1134
|
+
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m CI script completed (${pm} run ci).`);
|
|
1135
|
+
} catch (e) {
|
|
1136
|
+
debug$1("Failed to run CI script: %O", e);
|
|
1137
|
+
s.stop("Failed to run CI script.");
|
|
1138
|
+
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1139
|
+
log.error(`${e.message}${detail}`);
|
|
1140
|
+
throw new Error(`Failed to run CI script: ${e.message}${detail}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
log.success(`Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}`);
|
|
1144
|
+
showSummary(opts, pm, isProgress);
|
|
1145
|
+
if (opts.dev && finalPkg.scripts.dev) {
|
|
1146
|
+
log.info("Starting dev server...");
|
|
1147
|
+
if (opts.open) try {
|
|
1148
|
+
debug$1("Executing: %s run dev -- --open", pm);
|
|
1149
|
+
await execa(pm, [
|
|
1150
|
+
"run",
|
|
1151
|
+
"dev",
|
|
1152
|
+
"--",
|
|
1153
|
+
"--open"
|
|
1154
|
+
], {
|
|
1155
|
+
cwd: projectDir,
|
|
1156
|
+
stdio: "inherit",
|
|
1157
|
+
preferLocal: true
|
|
1158
|
+
});
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
log.error("Dev server failed: " + e);
|
|
1161
|
+
}
|
|
1162
|
+
else try {
|
|
1163
|
+
debug$1("Executing: %s run dev", pm);
|
|
1164
|
+
await execa(pm, ["run", "dev"], {
|
|
1165
|
+
cwd: projectDir,
|
|
1166
|
+
stdio: "inherit",
|
|
1167
|
+
preferLocal: true
|
|
1168
|
+
});
|
|
1169
|
+
} catch (e) {
|
|
1170
|
+
log.error("Dev server failed: " + e);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
function showSummary(opts, pm, isProgress) {
|
|
1175
|
+
debug$1("Showing summary for options: %O", opts);
|
|
1176
|
+
const { projectName, template } = opts;
|
|
1177
|
+
const summary = [
|
|
1178
|
+
`Successfully created a new ${template} project named '${projectName}'.`,
|
|
1179
|
+
"",
|
|
1180
|
+
"Available Commands:"
|
|
1181
|
+
];
|
|
1182
|
+
const commands = [
|
|
1183
|
+
`${pm} run dev - Starts the development server`,
|
|
1184
|
+
`${pm} run build - Builds the project for production`,
|
|
1185
|
+
`${pm} run test - Runs the unit test suite (Vitest)`,
|
|
1186
|
+
`${pm} run lint - Lints and formats the codebase`,
|
|
1187
|
+
`${pm} run ci - Runs lint, build, and test (used by CI/CD)`
|
|
1188
|
+
];
|
|
1189
|
+
showNote([...summary, ...commands.map((c) => ` ${c}`)].join("\n"), "Project ready", isProgress);
|
|
1190
|
+
}
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/index.ts
|
|
1193
|
+
if (process.argv.includes("--debug")) {
|
|
1194
|
+
process.env["DEBUG"] = "create-template-project:*";
|
|
1195
|
+
debugLib.enable("create-template-project:*");
|
|
1196
|
+
}
|
|
1197
|
+
var debug = debugLib("create-template-project:main");
|
|
1198
|
+
var main = async () => {
|
|
1199
|
+
try {
|
|
1200
|
+
debug("Starting CLI execution");
|
|
1201
|
+
debug("Parsing arguments");
|
|
1202
|
+
const options = await parseArgs();
|
|
1203
|
+
if (!options) return;
|
|
1204
|
+
const isProgress = options.progress !== false;
|
|
1205
|
+
if (isProgress) intro("create-template-project");
|
|
1206
|
+
debug("Arguments parsed: %O", options);
|
|
1207
|
+
debug("Generating project");
|
|
1208
|
+
await generateProject(options);
|
|
1209
|
+
debug("Project generation complete");
|
|
1210
|
+
if (isProgress) outro("Done!");
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
debug("Execution failed: %O", error);
|
|
1213
|
+
if (error.code === "PROCESS_EXIT_0" || error.code === "commander.helpDisplayed" || error.code === "commander.version") process.exit(0);
|
|
1214
|
+
if (error.code === "PROCESS_EXIT_1") process.exit(1);
|
|
1215
|
+
cancel(error?.message || String(error));
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
if (import.meta.url.endsWith("src/index.ts") || import.meta.url.endsWith("dist/index.js")) await main();
|
|
1220
|
+
//#endregion
|
|
1221
|
+
export { main };
|