create-template-project 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/config/dependencies.json +2 -2
- package/dist/index.js +665 -474
- package/dist/templates/base/files/_oxc.config.ts +203 -87
- package/dist/templates/cli/files/src/index.ts +1 -2
- package/dist/templates/cli/files/src/lib.ts +1 -3
- package/dist/templates/web-app/files/playwright.config.ts +7 -4
- package/dist/templates/web-app/files/src/App.tsx +11 -12
- package/dist/templates/web-app/files/src/index.tsx +1 -1
- package/dist/templates/web-app/files/tests/e2e/basic.e2e-test.ts +2 -2
- package/dist/templates/web-fullstack/files/client/src/App.tsx +4 -3
- package/dist/templates/web-fullstack/files/client/src/components/ProtectedRoute.tsx +4 -2
- package/dist/templates/web-fullstack/files/client/src/contexts/AuthContext.tsx +22 -18
- package/dist/templates/web-fullstack/files/client/src/main.tsx +8 -5
- package/dist/templates/web-fullstack/files/client/src/pages/Dashboard.tsx +9 -4
- package/dist/templates/web-fullstack/files/client/src/pages/Login.tsx +10 -6
- package/dist/templates/web-fullstack/files/client/src/trpc.ts +2 -1
- package/dist/templates/web-fullstack/files/playwright.config.ts +8 -5
- package/dist/templates/web-fullstack/files/server/src/context.ts +7 -5
- package/dist/templates/web-fullstack/files/server/src/index.ts +1 -1
- package/dist/templates/web-fullstack/files/server/src/routers/_app.ts +1 -0
- package/dist/templates/web-fullstack/files/server/src/routers/auth.ts +4 -4
- package/dist/templates/web-fullstack/files/server/src/trpc.ts +3 -4
- package/dist/templates/web-vanilla/files/playwright.config.ts +7 -4
- package/dist/templates/web-vanilla/files/src/index.test.ts +1 -1
- package/dist/templates/web-vanilla/files/src/index.ts +1 -1
- package/dist/templates/web-vanilla/files/src/lib.ts +1 -3
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import fs from "node:fs/promises";
|
|
|
9
9
|
import debugLib from "debug";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
|
-
//#region src/types.ts
|
|
12
|
+
//#region src/shared/types.ts
|
|
13
13
|
var TemplateTypeSchema = z.enum([
|
|
14
14
|
"cli",
|
|
15
15
|
"web-vanilla",
|
|
@@ -39,8 +39,8 @@ var ProjectOptionsSchema = z.object({
|
|
|
39
39
|
//#region src/utils/templating/generic.ts
|
|
40
40
|
var genericProcessor = (content, { opts }) => {
|
|
41
41
|
const { projectName, template, author, githubUsername } = opts;
|
|
42
|
-
let description = opts.description
|
|
43
|
-
if (
|
|
42
|
+
let description = opts.description ?? "";
|
|
43
|
+
if (description.length === 0) switch (template) {
|
|
44
44
|
case "cli":
|
|
45
45
|
description = "A modern Node.js CLI application with TypeScript and automated tooling.";
|
|
46
46
|
break;
|
|
@@ -53,10 +53,14 @@ var genericProcessor = (content, { opts }) => {
|
|
|
53
53
|
case "web-app":
|
|
54
54
|
description = "A React application with MUI and TanStack Query.";
|
|
55
55
|
break;
|
|
56
|
+
default: break;
|
|
56
57
|
}
|
|
57
|
-
const pm = opts.packageManager
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const pm = opts.packageManager;
|
|
59
|
+
let lockfileRules = "";
|
|
60
|
+
if (pm === "pnpm") lockfileRules = "package-lock.json\nyarn.lock";
|
|
61
|
+
else if (pm === "yarn") lockfileRules = "package-lock.json\npnpm-lock.yaml";
|
|
62
|
+
else lockfileRules = "yarn.lock\npnpm-lock.yaml";
|
|
63
|
+
return content.replaceAll("{{projectName}}", projectName).replaceAll("{{description}}", description).replaceAll("{{packageManager}}", pm).replaceAll("{{author}}", author).replaceAll("{{githubUsername}}", githubUsername).replaceAll("{{year}}", (/* @__PURE__ */ new Date()).getFullYear().toString()).replaceAll("{{lockfileRules}}", lockfileRules);
|
|
60
64
|
};
|
|
61
65
|
//#endregion
|
|
62
66
|
//#region src/utils/templating/github-workflow.ts
|
|
@@ -69,7 +73,7 @@ var WORKFLOW_PLAYWRIGHT_SETUP = ` - name: Install Playwright Browsers & Dep
|
|
|
69
73
|
run: npx playwright install --with-deps chromium`;
|
|
70
74
|
var githubWorkflowProcessor = (content, { filePath, opts }) => {
|
|
71
75
|
if (!filePath.includes(".github/workflows/node.js.yml")) return content;
|
|
72
|
-
const { template, packageManager: pm
|
|
76
|
+
const { template, packageManager: pm } = opts;
|
|
73
77
|
let installCommand = "npm ci";
|
|
74
78
|
let pmSetup = "";
|
|
75
79
|
if (pm === "pnpm") {
|
|
@@ -99,7 +103,7 @@ var tsconfigProcessor = (content, { filePath, opts }) => {
|
|
|
99
103
|
if (filePath !== "tsconfig.json") return content;
|
|
100
104
|
const { template } = opts;
|
|
101
105
|
let processed = content;
|
|
102
|
-
if (template === "web-fullstack" || template === "web-vanilla" || template === "web-app") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, WEB_ENV
|
|
106
|
+
if (template === "web-fullstack" || template === "web-vanilla" || template === "web-app") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, `${WEB_ENV}\n\n\t\t/* Strict Type-Checking Options */`);
|
|
103
107
|
if (template === "web-fullstack") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
|
|
104
108
|
return processed;
|
|
105
109
|
};
|
|
@@ -109,7 +113,7 @@ var contributingProcessor = (content, { filePath, addedDeps }) => {
|
|
|
109
113
|
if (filePath !== "CONTRIBUTING.md" || addedDeps.length === 0) return content;
|
|
110
114
|
let processed = content;
|
|
111
115
|
processed += "\n## Dependencies\n\n";
|
|
112
|
-
const uniqueDeps =
|
|
116
|
+
const uniqueDeps = [...new Map(addedDeps.map((dep) => [dep.name, dep])).values()];
|
|
113
117
|
for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
|
|
114
118
|
return processed;
|
|
115
119
|
};
|
|
@@ -120,59 +124,94 @@ var webVanillaHtmlProcessor = (content, { filePath, opts }) => {
|
|
|
120
124
|
return content;
|
|
121
125
|
};
|
|
122
126
|
//#endregion
|
|
127
|
+
//#region src/utils/templating/oxc-config.ts
|
|
128
|
+
var oxcConfigProcessor = (content, { filePath, opts }) => {
|
|
129
|
+
if (filePath !== "oxc.config.ts") return content;
|
|
130
|
+
const shouldAddNodeEnv = opts.template === "cli" || opts.template === "web-fullstack";
|
|
131
|
+
const shouldAddBrowserEnv = opts.template === "web-vanilla" || opts.template === "web-app" || opts.template === "web-fullstack";
|
|
132
|
+
const linesToAdd = [];
|
|
133
|
+
if (shouldAddNodeEnv && !content.includes("node: true")) linesToAdd.push(" node: true,");
|
|
134
|
+
if (shouldAddBrowserEnv && !content.includes("browser: true")) linesToAdd.push(" browser: true,");
|
|
135
|
+
if (linesToAdd.length === 0) return content;
|
|
136
|
+
for (const envBlock of [" env: {\n builtin: true,\n", "env: {\n builtin: true,\n"]) if (content.includes(envBlock)) return content.replace(envBlock, `${envBlock}${linesToAdd.join("\n")}\n`);
|
|
137
|
+
return content;
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
123
140
|
//#region src/utils/templating/index.ts
|
|
124
141
|
var processors = [
|
|
125
142
|
genericProcessor,
|
|
126
143
|
githubWorkflowProcessor,
|
|
127
144
|
tsconfigProcessor,
|
|
128
145
|
contributingProcessor,
|
|
129
|
-
webVanillaHtmlProcessor
|
|
146
|
+
webVanillaHtmlProcessor,
|
|
147
|
+
oxcConfigProcessor
|
|
130
148
|
];
|
|
131
|
-
|
|
149
|
+
var processContent$1 = (filePath, content, opts, addedDeps) => {
|
|
132
150
|
const context = {
|
|
133
151
|
filePath,
|
|
134
152
|
opts,
|
|
135
153
|
addedDeps
|
|
136
154
|
};
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
let processed = content;
|
|
156
|
+
for (const processor of processors) processed = processor(processed, context);
|
|
157
|
+
return processed;
|
|
158
|
+
};
|
|
139
159
|
//#endregion
|
|
140
|
-
//#region src/
|
|
160
|
+
//#region src/shared/file.ts
|
|
141
161
|
var debug$3 = debugLib("create-template-project:utils:file");
|
|
142
|
-
|
|
162
|
+
var toErrorDetail$1 = (error) => {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
const value = error;
|
|
165
|
+
const stdout = value.stdout ?? "";
|
|
166
|
+
const stderr = value.stderr ?? "";
|
|
167
|
+
const detail = stdout.length > 0 || stderr.length > 0 ? `\n\nOutput:\n${stdout}\n${stderr}` : "";
|
|
168
|
+
return {
|
|
169
|
+
message: error.message,
|
|
170
|
+
detail,
|
|
171
|
+
exitCode: value.exitCode
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
message: String(error),
|
|
176
|
+
detail: ""
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
var getTemplateDir = (dirname, templateName) => {
|
|
143
180
|
const sourcePath = path.resolve(dirname, "files");
|
|
144
181
|
const distPath = path.resolve(dirname, "templates", templateName, "files");
|
|
145
182
|
return existsSync(distPath) ? distPath : sourcePath;
|
|
146
|
-
}
|
|
147
|
-
async
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
};
|
|
184
|
+
var getAllFiles = async (dirPath, arrayOfFiles = []) => {
|
|
185
|
+
const filteredEntries = (await fs.readdir(dirPath, { withFileTypes: true })).filter((entry) => entry.name !== ".DS_Store");
|
|
186
|
+
const files = filteredEntries.filter((entry) => entry.isFile()).map((entry) => path.join(dirPath, entry.name));
|
|
187
|
+
const directories = filteredEntries.filter((entry) => entry.isDirectory());
|
|
188
|
+
const nestedResults = await Promise.all(directories.map(async (entry) => {
|
|
189
|
+
return await getAllFiles(path.join(dirPath, entry.name));
|
|
190
|
+
}));
|
|
191
|
+
return [
|
|
192
|
+
...arrayOfFiles,
|
|
193
|
+
...files,
|
|
194
|
+
...nestedResults.flat()
|
|
195
|
+
];
|
|
196
|
+
};
|
|
197
|
+
var processContent = (filePath, content, opts, addedDeps) => processContent$1(filePath, content, opts, addedDeps);
|
|
198
|
+
var mergePackageJson = (target, source) => {
|
|
199
|
+
if (source.scripts !== void 0) target.scripts = {
|
|
161
200
|
...target.scripts,
|
|
162
201
|
...source.scripts
|
|
163
202
|
};
|
|
164
|
-
if (source.dependencies) target.dependencies = {
|
|
203
|
+
if (source.dependencies !== void 0) target.dependencies = {
|
|
165
204
|
...target.dependencies,
|
|
166
205
|
...source.dependencies
|
|
167
206
|
};
|
|
168
|
-
if (source.devDependencies) target.devDependencies = {
|
|
207
|
+
if (source.devDependencies !== void 0) target.devDependencies = {
|
|
169
208
|
...target.devDependencies,
|
|
170
209
|
...source.devDependencies
|
|
171
210
|
};
|
|
172
|
-
if (source.workspaces) target.workspaces = source.workspaces;
|
|
173
|
-
if (source.bin) target.bin = source.bin;
|
|
174
|
-
}
|
|
175
|
-
|
|
211
|
+
if (source.workspaces !== void 0) target.workspaces = source.workspaces;
|
|
212
|
+
if (source.bin !== void 0) target.bin = source.bin;
|
|
213
|
+
};
|
|
214
|
+
var isSeedFile = (filePath) => {
|
|
176
215
|
return [
|
|
177
216
|
"src/",
|
|
178
217
|
"client/src/",
|
|
@@ -186,14 +225,14 @@ function isSeedFile(filePath) {
|
|
|
186
225
|
"index.tsx",
|
|
187
226
|
"LICENSE"
|
|
188
227
|
].some((file) => filePath === file) || filePath.toLowerCase().endsWith(".md");
|
|
189
|
-
}
|
|
190
|
-
async
|
|
228
|
+
};
|
|
229
|
+
var mergeFile = async (filePath, existing, template, log) => {
|
|
191
230
|
debug$3("Merging file: %s", filePath);
|
|
192
|
-
const tempBase = filePath
|
|
193
|
-
const tempNew = filePath
|
|
231
|
+
const tempBase = `${filePath}.base.tmp`;
|
|
232
|
+
const tempNew = `${filePath}.new.tmp`;
|
|
194
233
|
try {
|
|
195
234
|
await fs.writeFile(tempNew, template);
|
|
196
|
-
await fs.writeFile(tempBase,
|
|
235
|
+
await fs.writeFile(tempBase, existing);
|
|
197
236
|
try {
|
|
198
237
|
const stdio = debug$3.enabled ? "inherit" : "pipe";
|
|
199
238
|
debug$3("Executing: git merge-file %s %s %s", filePath, tempBase, tempNew);
|
|
@@ -207,79 +246,75 @@ async function mergeFile(filePath, existing, template, log) {
|
|
|
207
246
|
preferLocal: true
|
|
208
247
|
});
|
|
209
248
|
return (await fs.readFile(filePath, "utf8")).trim() !== template.trim() ? "merged" : "updated";
|
|
210
|
-
} catch (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return "error";
|
|
217
|
-
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const detail = toErrorDetail$1(error);
|
|
251
|
+
if (detail.exitCode !== void 0 && detail.exitCode >= 1 && detail.exitCode < 128) return "conflict";
|
|
252
|
+
debug$3("Git merge-file failed: %O", error);
|
|
253
|
+
log.error(`Failed to merge ${filePath}: ${detail.message}${detail.detail}`);
|
|
254
|
+
return "error";
|
|
218
255
|
}
|
|
219
256
|
} finally {
|
|
220
257
|
await fs.rm(tempBase, { force: true });
|
|
221
258
|
await fs.rm(tempNew, { force: true });
|
|
222
259
|
}
|
|
223
|
-
}
|
|
260
|
+
};
|
|
224
261
|
//#endregion
|
|
225
262
|
//#region src/templates/base/index.ts
|
|
226
263
|
var __dirname$5 = path.dirname(fileURLToPath(import.meta.url));
|
|
227
|
-
var getBaseTemplate = (_opts) => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
description: "Mandatory strict mode for type safety."
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
name: "oxlint",
|
|
238
|
-
description: "Ultra-fast Rust-based linter."
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
name: "oxfmt",
|
|
242
|
-
description: "High performance code formatting."
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: "Vitest",
|
|
246
|
-
description: "Modern, fast unit testing with coverage."
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: "Husky",
|
|
250
|
-
description: "Git hooks for pre-commit linting."
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: "commitlint",
|
|
254
|
-
description: "Standardized commit messages."
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: "conventional-changelog",
|
|
258
|
-
description: "Automated release notes."
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "release-it",
|
|
262
|
-
description: "Automated release workflow."
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "debug",
|
|
266
|
-
description: "Structured logging for debugging."
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "Zod",
|
|
270
|
-
description: "TypeScript-first schema validation for runtime type safety."
|
|
271
|
-
}
|
|
272
|
-
],
|
|
273
|
-
dependencies: { zod: "zod" },
|
|
274
|
-
devDependencies: {
|
|
275
|
-
"eslint-plugin-regexp": "",
|
|
276
|
-
"release-it": ""
|
|
264
|
+
var getBaseTemplate = (_opts) => ({
|
|
265
|
+
name: "base",
|
|
266
|
+
description: "The foundation for all project templates, including common tooling and configuration.",
|
|
267
|
+
components: [
|
|
268
|
+
{
|
|
269
|
+
name: "TypeScript",
|
|
270
|
+
description: "Mandatory strict mode for type safety."
|
|
277
271
|
},
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
272
|
+
{
|
|
273
|
+
name: "oxlint",
|
|
274
|
+
description: "Ultra-fast Rust-based linter."
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "oxfmt",
|
|
278
|
+
description: "High performance code formatting."
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "Vitest",
|
|
282
|
+
description: "Modern, fast unit testing with coverage."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "Husky",
|
|
286
|
+
description: "Git hooks for pre-commit linting."
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "commitlint",
|
|
290
|
+
description: "Standardized commit messages."
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "conventional-changelog",
|
|
294
|
+
description: "Automated release notes."
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "release-it",
|
|
298
|
+
description: "Automated release workflow."
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "debug",
|
|
302
|
+
description: "Structured logging for debugging."
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "Zod",
|
|
306
|
+
description: "TypeScript-first schema validation for runtime type safety."
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
dependencies: { zod: "zod" },
|
|
310
|
+
devDependencies: {
|
|
311
|
+
"eslint-plugin-regexp": "",
|
|
312
|
+
"release-it": ""
|
|
313
|
+
},
|
|
314
|
+
scripts: {},
|
|
315
|
+
files: [],
|
|
316
|
+
templateDir: getTemplateDir(__dirname$5, "base")
|
|
317
|
+
});
|
|
283
318
|
//#endregion
|
|
284
319
|
//#region src/templates/cli/index.ts
|
|
285
320
|
var __dirname$4 = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -311,188 +346,198 @@ var getCliTemplate = (_opts) => {
|
|
|
311
346
|
//#endregion
|
|
312
347
|
//#region src/templates/web-vanilla/index.ts
|
|
313
348
|
var __dirname$3 = path.dirname(fileURLToPath(import.meta.url));
|
|
314
|
-
var getWebVanillaTemplate = (_opts) => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
description: "Fast frontend build tool and development server."
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
name: "Vitest",
|
|
325
|
-
description: "Modern testing framework with browser support."
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: "Playwright",
|
|
329
|
-
description: "Comprehensive end-to-end testing for modern web apps."
|
|
330
|
-
}
|
|
331
|
-
],
|
|
332
|
-
dependencies: {},
|
|
333
|
-
devDependencies: {
|
|
334
|
-
vite: "vite",
|
|
335
|
-
vitest: "vitest",
|
|
336
|
-
"@vitest/browser": "@vitest/browser",
|
|
337
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
338
|
-
playwright: "playwright",
|
|
339
|
-
"@playwright/test": "@playwright/test"
|
|
349
|
+
var getWebVanillaTemplate = (_opts) => ({
|
|
350
|
+
name: "web-vanilla",
|
|
351
|
+
description: "A modern, standalone web page template with built-in development and testing tooling.",
|
|
352
|
+
components: [
|
|
353
|
+
{
|
|
354
|
+
name: "Vite",
|
|
355
|
+
description: "Fast frontend build tool and development server."
|
|
340
356
|
},
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
preview: "vite preview",
|
|
345
|
-
test: "vitest run",
|
|
346
|
-
"test:ui": "vitest",
|
|
347
|
-
"integration-test": "playwright test"
|
|
357
|
+
{
|
|
358
|
+
name: "Vitest",
|
|
359
|
+
description: "Modern testing framework with browser support."
|
|
348
360
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
361
|
+
{
|
|
362
|
+
name: "Playwright",
|
|
363
|
+
description: "Comprehensive end-to-end testing for modern web apps."
|
|
364
|
+
}
|
|
365
|
+
],
|
|
366
|
+
dependencies: {},
|
|
367
|
+
devDependencies: {
|
|
368
|
+
vite: "vite",
|
|
369
|
+
vitest: "vitest",
|
|
370
|
+
"@vitest/browser": "@vitest/browser",
|
|
371
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
372
|
+
playwright: "playwright",
|
|
373
|
+
"@playwright/test": "@playwright/test"
|
|
374
|
+
},
|
|
375
|
+
scripts: {
|
|
376
|
+
dev: "vite",
|
|
377
|
+
build: "vite build",
|
|
378
|
+
preview: "vite preview",
|
|
379
|
+
test: "vitest run",
|
|
380
|
+
"test:ui": "vitest",
|
|
381
|
+
"integration-test": "playwright test"
|
|
382
|
+
},
|
|
383
|
+
files: [],
|
|
384
|
+
templateDir: getTemplateDir(__dirname$3, "web-vanilla")
|
|
385
|
+
});
|
|
353
386
|
//#endregion
|
|
354
387
|
//#region src/templates/web-app/index.ts
|
|
355
388
|
var __dirname$2 = path.dirname(fileURLToPath(import.meta.url));
|
|
356
|
-
var getWebAppTemplate = (_opts) => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
description: "Powerful library for building component-based user interfaces."
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
name: "MUI",
|
|
367
|
-
description: "Rich set of Material Design UI components for React."
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
name: "TanStack React Query",
|
|
371
|
-
description: "Powerful asynchronous state management for React."
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
name: "Vite",
|
|
375
|
-
description: "Next-generation frontend tooling."
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
name: "Vitest",
|
|
379
|
-
description: "Testing framework with cross-browser support."
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
name: "Playwright",
|
|
383
|
-
description: "End-to-end testing for modern web apps."
|
|
384
|
-
}
|
|
385
|
-
],
|
|
386
|
-
dependencies: {
|
|
387
|
-
react: "react",
|
|
388
|
-
"react-dom": "react-dom",
|
|
389
|
-
"@mui/material": "@mui/material",
|
|
390
|
-
"@mui/icons-material": "@mui/icons-material",
|
|
391
|
-
"@emotion/react": "@emotion/react",
|
|
392
|
-
"@emotion/styled": "@emotion/styled",
|
|
393
|
-
"@tanstack/react-query": "@tanstack/react-query"
|
|
389
|
+
var getWebAppTemplate = (_opts) => ({
|
|
390
|
+
name: "web-app",
|
|
391
|
+
description: "A React application with MUI and TanStack Query, powered by Vite.",
|
|
392
|
+
components: [
|
|
393
|
+
{
|
|
394
|
+
name: "React",
|
|
395
|
+
description: "Powerful library for building component-based user interfaces."
|
|
394
396
|
},
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
vite: "vite",
|
|
399
|
-
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
400
|
-
vitest: "vitest",
|
|
401
|
-
"@vitest/browser": "@vitest/browser",
|
|
402
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
403
|
-
playwright: "playwright",
|
|
404
|
-
"@playwright/test": "@playwright/test",
|
|
405
|
-
"vitest-browser-react": "vitest-browser-react"
|
|
397
|
+
{
|
|
398
|
+
name: "MUI",
|
|
399
|
+
description: "Rich set of Material Design UI components for React."
|
|
406
400
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
preview: "vite preview",
|
|
411
|
-
test: "vitest run",
|
|
412
|
-
"test:ui": "vitest",
|
|
413
|
-
"integration-test": "playwright test",
|
|
414
|
-
start: "vite preview"
|
|
401
|
+
{
|
|
402
|
+
name: "TanStack React Query",
|
|
403
|
+
description: "Powerful asynchronous state management for React."
|
|
415
404
|
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
405
|
+
{
|
|
406
|
+
name: "Vite",
|
|
407
|
+
description: "Next-generation frontend tooling."
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: "Vitest",
|
|
411
|
+
description: "Testing framework with cross-browser support."
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "Playwright",
|
|
415
|
+
description: "End-to-end testing for modern web apps."
|
|
416
|
+
}
|
|
417
|
+
],
|
|
418
|
+
dependencies: {
|
|
419
|
+
react: "react",
|
|
420
|
+
"react-dom": "react-dom",
|
|
421
|
+
"@mui/material": "@mui/material",
|
|
422
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
423
|
+
"@emotion/react": "@emotion/react",
|
|
424
|
+
"@emotion/styled": "@emotion/styled",
|
|
425
|
+
"@tanstack/react-query": "@tanstack/react-query"
|
|
426
|
+
},
|
|
427
|
+
devDependencies: {
|
|
428
|
+
"@types/react": "@types/react",
|
|
429
|
+
"@types/react-dom": "@types/react-dom",
|
|
430
|
+
vite: "vite",
|
|
431
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
432
|
+
vitest: "vitest",
|
|
433
|
+
"@vitest/browser": "@vitest/browser",
|
|
434
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
435
|
+
playwright: "playwright",
|
|
436
|
+
"@playwright/test": "@playwright/test",
|
|
437
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
438
|
+
},
|
|
439
|
+
scripts: {
|
|
440
|
+
dev: "vite",
|
|
441
|
+
build: "vite build",
|
|
442
|
+
preview: "vite preview",
|
|
443
|
+
test: "vitest run",
|
|
444
|
+
"test:ui": "vitest",
|
|
445
|
+
"integration-test": "playwright test",
|
|
446
|
+
start: "vite preview"
|
|
447
|
+
},
|
|
448
|
+
files: [],
|
|
449
|
+
templateDir: getTemplateDir(__dirname$2, "web-app")
|
|
450
|
+
});
|
|
420
451
|
//#endregion
|
|
421
452
|
//#region src/templates/web-fullstack/index.ts
|
|
422
453
|
var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
423
|
-
var getWebFullstackTemplate = (_opts) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
description: "Powerful library for building component-based user interfaces."
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: "MUI",
|
|
434
|
-
description: "Rich set of Material Design UI components for React."
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
name: "tRPC",
|
|
438
|
-
description: "End-to-end typesafe APIs made easy."
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: "React Query",
|
|
442
|
-
description: "Powerful asynchronous state management for React."
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
name: "Express",
|
|
446
|
-
description: "Fast, minimalist backend web framework."
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: "React Router",
|
|
450
|
-
description: "Declarative routing for the frontend."
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
name: "Vite",
|
|
454
|
-
description: "Fast, modern frontend build tool."
|
|
455
|
-
}
|
|
456
|
-
],
|
|
457
|
-
dependencies: {
|
|
458
|
-
react: "react",
|
|
459
|
-
"react-dom": "react-dom",
|
|
460
|
-
"@mui/material": "@mui/material",
|
|
461
|
-
"@mui/icons-material": "@mui/icons-material",
|
|
462
|
-
"@emotion/react": "@emotion/react",
|
|
463
|
-
"@emotion/styled": "@emotion/styled",
|
|
464
|
-
express: "express",
|
|
465
|
-
"@trpc/server": "@trpc/server",
|
|
466
|
-
"@trpc/client": "@trpc/client",
|
|
467
|
-
"@trpc/react-query": "@trpc/react-query",
|
|
468
|
-
"@tanstack/react-query": "@tanstack/react-query",
|
|
469
|
-
"react-router-dom": "react-router-dom",
|
|
470
|
-
cors: "cors"
|
|
454
|
+
var getWebFullstackTemplate = (_opts) => ({
|
|
455
|
+
name: "web-fullstack",
|
|
456
|
+
description: "A comprehensive full-stack monorepo featuring an Express backend with tRPC and a modern React client with MUI.",
|
|
457
|
+
components: [
|
|
458
|
+
{
|
|
459
|
+
name: "React",
|
|
460
|
+
description: "Powerful library for building component-based user interfaces."
|
|
471
461
|
},
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
"@types/express": "@types/express",
|
|
476
|
-
"@types/cors": "@types/cors",
|
|
477
|
-
"@playwright/test": "@playwright/test",
|
|
478
|
-
vite: "vite",
|
|
479
|
-
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
480
|
-
vitest: "vitest",
|
|
481
|
-
"@vitest/browser": "@vitest/browser",
|
|
482
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
483
|
-
playwright: "playwright",
|
|
484
|
-
"vitest-browser-react": "vitest-browser-react"
|
|
462
|
+
{
|
|
463
|
+
name: "MUI",
|
|
464
|
+
description: "Rich set of Material Design UI components for React."
|
|
485
465
|
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
test: "npm run test --workspaces",
|
|
490
|
-
"integration-test": "playwright test"
|
|
466
|
+
{
|
|
467
|
+
name: "tRPC",
|
|
468
|
+
description: "End-to-end typesafe APIs made easy."
|
|
491
469
|
},
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
470
|
+
{
|
|
471
|
+
name: "React Query",
|
|
472
|
+
description: "Powerful asynchronous state management for React."
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: "Express",
|
|
476
|
+
description: "Fast, minimalist backend web framework."
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "React Router",
|
|
480
|
+
description: "Declarative routing for the frontend."
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "Vite",
|
|
484
|
+
description: "Fast, modern frontend build tool."
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
dependencies: {
|
|
488
|
+
react: "react",
|
|
489
|
+
"react-dom": "react-dom",
|
|
490
|
+
"@mui/material": "@mui/material",
|
|
491
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
492
|
+
"@emotion/react": "@emotion/react",
|
|
493
|
+
"@emotion/styled": "@emotion/styled",
|
|
494
|
+
express: "express",
|
|
495
|
+
"@trpc/server": "@trpc/server",
|
|
496
|
+
"@trpc/client": "@trpc/client",
|
|
497
|
+
"@trpc/react-query": "@trpc/react-query",
|
|
498
|
+
"@tanstack/react-query": "@tanstack/react-query",
|
|
499
|
+
"react-router-dom": "react-router-dom",
|
|
500
|
+
cors: "cors"
|
|
501
|
+
},
|
|
502
|
+
devDependencies: {
|
|
503
|
+
"@types/react": "@types/react",
|
|
504
|
+
"@types/react-dom": "@types/react-dom",
|
|
505
|
+
"@types/express": "@types/express",
|
|
506
|
+
"@types/cors": "@types/cors",
|
|
507
|
+
"@playwright/test": "@playwright/test",
|
|
508
|
+
vite: "vite",
|
|
509
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
510
|
+
vitest: "vitest",
|
|
511
|
+
"@vitest/browser": "@vitest/browser",
|
|
512
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
513
|
+
playwright: "playwright",
|
|
514
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
515
|
+
},
|
|
516
|
+
scripts: {
|
|
517
|
+
build: "npm run build --workspaces",
|
|
518
|
+
dev: "npm run dev --workspaces",
|
|
519
|
+
test: "npm run test --workspaces",
|
|
520
|
+
"integration-test": "playwright test"
|
|
521
|
+
},
|
|
522
|
+
files: [],
|
|
523
|
+
templateDir: getTemplateDir(__dirname$1, "web-fullstack")
|
|
524
|
+
});
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/templates/registry.ts
|
|
527
|
+
var templateFactories = {
|
|
528
|
+
cli: getCliTemplate,
|
|
529
|
+
"web-vanilla": getWebVanillaTemplate,
|
|
530
|
+
"web-app": getWebAppTemplate,
|
|
531
|
+
"web-fullstack": getWebFullstackTemplate
|
|
495
532
|
};
|
|
533
|
+
var getTemplateByType = (type, opts) => templateFactories[type](opts);
|
|
534
|
+
var getProjectTemplates = (opts) => [getBaseTemplate(opts), getTemplateByType(opts.template, opts)];
|
|
535
|
+
var getTemplateTypes = () => [
|
|
536
|
+
"cli",
|
|
537
|
+
"web-vanilla",
|
|
538
|
+
"web-app",
|
|
539
|
+
"web-fullstack"
|
|
540
|
+
];
|
|
496
541
|
//#endregion
|
|
497
542
|
//#region src/generators/info.ts
|
|
498
543
|
var MOCK_OPTS = {
|
|
@@ -513,45 +558,57 @@ var getTemplateInfo = (type) => {
|
|
|
513
558
|
template: type
|
|
514
559
|
};
|
|
515
560
|
const base = getBaseTemplate(opts);
|
|
516
|
-
|
|
517
|
-
switch (type) {
|
|
518
|
-
case "cli":
|
|
519
|
-
template = getCliTemplate(opts);
|
|
520
|
-
break;
|
|
521
|
-
case "web-vanilla":
|
|
522
|
-
template = getWebVanillaTemplate(opts);
|
|
523
|
-
break;
|
|
524
|
-
case "web-app":
|
|
525
|
-
template = getWebAppTemplate(opts);
|
|
526
|
-
break;
|
|
527
|
-
case "web-fullstack":
|
|
528
|
-
template = getWebFullstackTemplate(opts);
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
561
|
+
const template = getTemplateByType(type, opts);
|
|
531
562
|
return {
|
|
532
563
|
name: template.name,
|
|
533
564
|
description: template.description,
|
|
534
565
|
components: [...base.components, ...template.components]
|
|
535
566
|
};
|
|
536
567
|
};
|
|
537
|
-
var getAllTemplatesInfo = () =>
|
|
538
|
-
return [
|
|
539
|
-
"cli",
|
|
540
|
-
"web-vanilla",
|
|
541
|
-
"web-app",
|
|
542
|
-
"web-fullstack"
|
|
543
|
-
].map((type) => getTemplateInfo(type));
|
|
544
|
-
};
|
|
568
|
+
var getAllTemplatesInfo = () => getTemplateTypes().map((type) => getTemplateInfo(type));
|
|
545
569
|
//#endregion
|
|
546
570
|
//#region src/cli.ts
|
|
547
|
-
var
|
|
571
|
+
var isRecord$2 = (value) => typeof value === "object" && value !== null;
|
|
572
|
+
var isPackageManager = (value) => value === "npm" || value === "pnpm" || value === "yarn";
|
|
573
|
+
var pathExists$1 = async (filePath) => {
|
|
574
|
+
try {
|
|
575
|
+
await fs.access(filePath);
|
|
576
|
+
return true;
|
|
577
|
+
} catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var parseStoredPackageJson = (raw) => {
|
|
582
|
+
const parsed = JSON.parse(raw);
|
|
583
|
+
if (!isRecord$2(parsed)) return {};
|
|
584
|
+
const pkg = parsed;
|
|
585
|
+
const config = pkg["create-template-project"];
|
|
586
|
+
const configRecord = isRecord$2(config) ? config : void 0;
|
|
587
|
+
let template;
|
|
588
|
+
if (configRecord !== void 0 && typeof configRecord.template === "string") {
|
|
589
|
+
const result = TemplateTypeSchema.safeParse(configRecord.template);
|
|
590
|
+
if (result.success) template = result.data;
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
name: typeof pkg.name === "string" ? pkg.name : void 0,
|
|
594
|
+
description: typeof pkg.description === "string" ? pkg.description : void 0,
|
|
595
|
+
keywords: Array.isArray(pkg.keywords) ? pkg.keywords.filter((keyword) => typeof keyword === "string") : void 0,
|
|
596
|
+
author: typeof pkg.author === "string" ? pkg.author : void 0,
|
|
597
|
+
"create-template-project": configRecord ? {
|
|
598
|
+
template,
|
|
599
|
+
githubUsername: typeof configRecord.githubUsername === "string" ? configRecord.githubUsername : void 0,
|
|
600
|
+
author: typeof configRecord.author === "string" ? configRecord.author : void 0
|
|
601
|
+
} : void 0
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
var noop = () => void 0;
|
|
548
605
|
var stripQuotes = (str) => {
|
|
549
606
|
if (typeof str !== "string") return str;
|
|
550
607
|
let result = str.trim();
|
|
551
608
|
while (result.length >= 2) {
|
|
552
|
-
const first = result
|
|
553
|
-
const last = result
|
|
554
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'") result = result.
|
|
609
|
+
const [first] = result;
|
|
610
|
+
const last = result.at(-1);
|
|
611
|
+
if (first === "\"" && last === "\"" || first === "'" && last === "'") result = result.slice(1, -1).trim();
|
|
555
612
|
else break;
|
|
556
613
|
}
|
|
557
614
|
return result;
|
|
@@ -560,7 +617,7 @@ var getDefaultAuthor = async () => {
|
|
|
560
617
|
try {
|
|
561
618
|
const { stdout } = await execa("git", ["config", "user.name"]);
|
|
562
619
|
return stdout.trim();
|
|
563
|
-
} catch
|
|
620
|
+
} catch {
|
|
564
621
|
return "";
|
|
565
622
|
}
|
|
566
623
|
};
|
|
@@ -568,7 +625,7 @@ var getDefaultGithubUsername = async () => {
|
|
|
568
625
|
try {
|
|
569
626
|
const { stdout } = await execa("git", ["config", "github.user"]);
|
|
570
627
|
return stdout.trim();
|
|
571
|
-
} catch
|
|
628
|
+
} catch {
|
|
572
629
|
return "";
|
|
573
630
|
}
|
|
574
631
|
};
|
|
@@ -577,11 +634,11 @@ var parseArgs = async () => {
|
|
|
577
634
|
debug$2("Parsing CLI arguments: %O", process.argv);
|
|
578
635
|
const program = new Command();
|
|
579
636
|
if (process.env.NODE_ENV === "test") program.configureOutput({
|
|
580
|
-
writeOut:
|
|
581
|
-
writeErr:
|
|
637
|
+
writeOut: noop,
|
|
638
|
+
writeErr: noop
|
|
582
639
|
});
|
|
583
640
|
program.name("create-template-project").exitOverride().description("Scaffold a new project template").version("0.1.0").option("--debug", "Enable debug output").on("option:debug", () => {
|
|
584
|
-
process.env
|
|
641
|
+
process.env.DEBUG = "create-template-project:*";
|
|
585
642
|
debugLib.enable("create-template-project:*");
|
|
586
643
|
}).addHelpText("after", `
|
|
587
644
|
Commands:
|
|
@@ -600,8 +657,9 @@ Templates:
|
|
|
600
657
|
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) => {
|
|
601
658
|
debug$2("Executing \"info\" command with options: %O", opts);
|
|
602
659
|
p.intro("Template Information");
|
|
603
|
-
if (opts.template) {
|
|
604
|
-
const
|
|
660
|
+
if (opts.template !== void 0 && opts.template.length > 0) {
|
|
661
|
+
const template = stripQuotes(opts.template);
|
|
662
|
+
const typeResult = TemplateTypeSchema.safeParse(template);
|
|
605
663
|
if (!typeResult.success) {
|
|
606
664
|
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
607
665
|
process.exit(1);
|
|
@@ -627,19 +685,25 @@ Templates:
|
|
|
627
685
|
});
|
|
628
686
|
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("--description <description>", "Project description").option("-k, --keywords <keywords>", "Project keywords (comma separated)").option("-a, --author <author>", "Author name (defaults to 'git config user.name')").option("--github-username <username>", "GitHub username (defaults to 'git config github.user')").option("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "pnpm").option("--create-github-repository", "Create GitHub repository and push initial commit").requiredOption("--path <path>", "Output directory").option("--build", "Run the CI script (lint, build, test) after scaffolding", false).option("--no-progress", "Do not show progress indicators").action(async (opts) => {
|
|
629
687
|
debug$2("Executing \"create\" command with options: %O", opts);
|
|
688
|
+
const templateInput = stripQuotes(opts.template);
|
|
689
|
+
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
690
|
+
if (!templateResult.success) {
|
|
691
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
630
694
|
commandResult = {
|
|
631
|
-
...opts,
|
|
632
695
|
update: false,
|
|
633
|
-
template:
|
|
696
|
+
template: templateResult.data,
|
|
634
697
|
projectName: opts.name,
|
|
635
698
|
description: opts.description,
|
|
636
699
|
keywords: opts.keywords,
|
|
637
|
-
author: opts.author
|
|
638
|
-
githubUsername: opts.githubUsername
|
|
700
|
+
author: opts.author ?? await getDefaultAuthor(),
|
|
701
|
+
githubUsername: opts.githubUsername ?? await getDefaultGithubUsername(),
|
|
639
702
|
packageManager: opts.packageManager,
|
|
640
703
|
directory: path.resolve(opts.path),
|
|
641
|
-
createGithubRepository:
|
|
642
|
-
|
|
704
|
+
createGithubRepository: Boolean(opts.createGithubRepository),
|
|
705
|
+
build: Boolean(opts.build),
|
|
706
|
+
progress: Boolean(opts.progress)
|
|
643
707
|
};
|
|
644
708
|
debug$2("Processed \"create\" options: %O", commandResult);
|
|
645
709
|
});
|
|
@@ -663,32 +727,45 @@ Restrictions & Behavior:
|
|
|
663
727
|
}
|
|
664
728
|
let pkg;
|
|
665
729
|
try {
|
|
666
|
-
pkg =
|
|
667
|
-
} catch (
|
|
668
|
-
|
|
730
|
+
pkg = parseStoredPackageJson(await fs.readFile(pkgPath, "utf8"));
|
|
731
|
+
} catch (error) {
|
|
732
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
733
|
+
p.log.error(`Failed to read or parse package.json at ${pkgPath}: ${message}`);
|
|
669
734
|
process.exit(1);
|
|
670
735
|
}
|
|
671
|
-
if (
|
|
736
|
+
if (pkg.name === void 0 || pkg.name.length === 0) {
|
|
672
737
|
p.log.error(`No name property found in ${pkgPath}.`);
|
|
673
738
|
process.exit(1);
|
|
674
739
|
}
|
|
675
|
-
|
|
740
|
+
const projectConfig = pkg["create-template-project"];
|
|
741
|
+
if (projectConfig === void 0 || projectConfig.template === void 0) {
|
|
676
742
|
p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
677
743
|
process.exit(1);
|
|
678
744
|
}
|
|
745
|
+
let template = projectConfig.template;
|
|
746
|
+
if (opts.template !== void 0) {
|
|
747
|
+
const templateInput = stripQuotes(opts.template);
|
|
748
|
+
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
749
|
+
if (!templateResult.success) {
|
|
750
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
template = templateResult.data;
|
|
754
|
+
}
|
|
679
755
|
commandResult = {
|
|
680
756
|
...opts,
|
|
681
757
|
update: true,
|
|
682
|
-
template
|
|
758
|
+
template,
|
|
683
759
|
projectName: pkg.name,
|
|
684
|
-
description: opts.description
|
|
685
|
-
keywords: opts.keywords
|
|
686
|
-
author: opts.author
|
|
687
|
-
githubUsername: opts.githubUsername
|
|
760
|
+
description: opts.description ?? pkg.description,
|
|
761
|
+
keywords: opts.keywords ?? (pkg.keywords !== void 0 ? pkg.keywords.join(", ") : void 0),
|
|
762
|
+
author: opts.author ?? pkg.author ?? await getDefaultAuthor(),
|
|
763
|
+
githubUsername: opts.githubUsername ?? projectConfig.githubUsername ?? await getDefaultGithubUsername(),
|
|
688
764
|
packageManager: opts.packageManager,
|
|
689
765
|
directory,
|
|
690
|
-
createGithubRepository:
|
|
691
|
-
|
|
766
|
+
createGithubRepository: Boolean(opts.createGithubRepository),
|
|
767
|
+
build: Boolean(opts.build),
|
|
768
|
+
progress: Boolean(opts.progress)
|
|
692
769
|
};
|
|
693
770
|
debug$2("Processed \"update\" options: %O", commandResult);
|
|
694
771
|
});
|
|
@@ -698,7 +775,7 @@ Restrictions & Behavior:
|
|
|
698
775
|
message: "Project name:",
|
|
699
776
|
placeholder: "my-app",
|
|
700
777
|
defaultValue: "my-app",
|
|
701
|
-
validate: (value) => value
|
|
778
|
+
validate: (value = "") => value.length > 0 ? void 0 : "Project name is required"
|
|
702
779
|
});
|
|
703
780
|
if (p.isCancel(projectName)) {
|
|
704
781
|
p.cancel("Operation cancelled.");
|
|
@@ -722,20 +799,20 @@ Restrictions & Behavior:
|
|
|
722
799
|
let existingDescription = "";
|
|
723
800
|
let existingKeywords = [];
|
|
724
801
|
if (pkgExists) try {
|
|
725
|
-
const pkg =
|
|
726
|
-
existingConfig = pkg["create-template-project"]
|
|
727
|
-
existingAuthor = pkg.author;
|
|
728
|
-
existingGithubUsername = existingConfig.githubUsername;
|
|
729
|
-
existingDescription = pkg.description;
|
|
730
|
-
existingKeywords = pkg.keywords
|
|
802
|
+
const pkg = parseStoredPackageJson(await fs.readFile(pkgPath, "utf8"));
|
|
803
|
+
existingConfig = pkg["create-template-project"] ?? {};
|
|
804
|
+
existingAuthor = pkg.author ?? "";
|
|
805
|
+
existingGithubUsername = existingConfig.githubUsername ?? "";
|
|
806
|
+
existingDescription = pkg.description ?? "";
|
|
807
|
+
existingKeywords = pkg.keywords ?? [];
|
|
731
808
|
debug$2("Found existing project config: %O", existingConfig);
|
|
732
|
-
} catch (
|
|
733
|
-
debug$2("Failed to read existing package.json: %O",
|
|
809
|
+
} catch (error) {
|
|
810
|
+
debug$2("Failed to read existing package.json: %O", error);
|
|
734
811
|
}
|
|
735
812
|
const projectDescription = await p.text({
|
|
736
813
|
message: "Project description:",
|
|
737
814
|
placeholder: "A new project",
|
|
738
|
-
defaultValue: existingDescription
|
|
815
|
+
defaultValue: existingDescription
|
|
739
816
|
});
|
|
740
817
|
if (p.isCancel(projectDescription)) {
|
|
741
818
|
p.cancel("Operation cancelled.");
|
|
@@ -744,29 +821,31 @@ Restrictions & Behavior:
|
|
|
744
821
|
const projectKeywords = await p.text({
|
|
745
822
|
message: "Project keywords (comma separated):",
|
|
746
823
|
placeholder: "cli, nodejs, typescript",
|
|
747
|
-
defaultValue: existingKeywords
|
|
824
|
+
defaultValue: existingKeywords.join(", ")
|
|
748
825
|
});
|
|
749
826
|
if (p.isCancel(projectKeywords)) {
|
|
750
827
|
p.cancel("Operation cancelled.");
|
|
751
828
|
process.exit(0);
|
|
752
829
|
}
|
|
753
830
|
const defaultAuthor = await getDefaultAuthor();
|
|
831
|
+
const authorDefault = existingAuthor.length > 0 ? existingAuthor : existingConfig.author ?? defaultAuthor;
|
|
754
832
|
const author = await p.text({
|
|
755
833
|
message: "Author name:",
|
|
756
834
|
placeholder: "Your Name",
|
|
757
|
-
defaultValue:
|
|
758
|
-
validate: (value) => value
|
|
835
|
+
defaultValue: authorDefault,
|
|
836
|
+
validate: (value = "") => value.length > 0 ? void 0 : "Author name is required"
|
|
759
837
|
});
|
|
760
838
|
if (p.isCancel(author)) {
|
|
761
839
|
p.cancel("Operation cancelled.");
|
|
762
840
|
process.exit(0);
|
|
763
841
|
}
|
|
764
842
|
const defaultGithubUsername = await getDefaultGithubUsername();
|
|
843
|
+
const githubDefault = existingGithubUsername.length > 0 ? existingGithubUsername : defaultGithubUsername;
|
|
765
844
|
const githubUsername = await p.text({
|
|
766
845
|
message: "GitHub username:",
|
|
767
846
|
placeholder: "your-github-username",
|
|
768
|
-
defaultValue:
|
|
769
|
-
validate: (value) => value
|
|
847
|
+
defaultValue: githubDefault,
|
|
848
|
+
validate: (value = "") => value.length > 0 ? void 0 : "GitHub username is required"
|
|
770
849
|
});
|
|
771
850
|
if (p.isCancel(githubUsername)) {
|
|
772
851
|
p.cancel("Operation cancelled.");
|
|
@@ -788,19 +867,18 @@ Restrictions & Behavior:
|
|
|
788
867
|
p.cancel("Operation cancelled.");
|
|
789
868
|
process.exit(0);
|
|
790
869
|
}
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
process.exit(1);
|
|
795
|
-
}
|
|
796
|
-
update = true;
|
|
870
|
+
if (!existingConfig.template) {
|
|
871
|
+
p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
872
|
+
process.exit(1);
|
|
797
873
|
}
|
|
874
|
+
update = true;
|
|
798
875
|
}
|
|
799
|
-
|
|
876
|
+
const { template: existingTemplate } = existingConfig;
|
|
877
|
+
let template = existingTemplate;
|
|
800
878
|
if (!update || !template) {
|
|
801
|
-
|
|
879
|
+
const selectedTemplate = await p.select({
|
|
802
880
|
message: "Select project template:",
|
|
803
|
-
initialValue: template
|
|
881
|
+
initialValue: template ?? "cli",
|
|
804
882
|
options: [
|
|
805
883
|
{
|
|
806
884
|
label: "CLI Application (Node.js)",
|
|
@@ -820,14 +898,24 @@ Restrictions & Behavior:
|
|
|
820
898
|
}
|
|
821
899
|
]
|
|
822
900
|
});
|
|
823
|
-
if (p.isCancel(
|
|
901
|
+
if (p.isCancel(selectedTemplate)) {
|
|
824
902
|
p.cancel("Operation cancelled.");
|
|
825
903
|
process.exit(0);
|
|
826
904
|
}
|
|
905
|
+
if (typeof selectedTemplate !== "string") {
|
|
906
|
+
p.cancel("Invalid template selection.");
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
const templateResult = TemplateTypeSchema.safeParse(selectedTemplate);
|
|
910
|
+
if (!templateResult.success) {
|
|
911
|
+
p.cancel("Invalid template selection.");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
template = templateResult.data;
|
|
827
915
|
} else p.log.info(`Using existing template type: ${template}`);
|
|
828
916
|
let packageManager = "pnpm";
|
|
829
917
|
if (!update) {
|
|
830
|
-
|
|
918
|
+
const selectedPackageManager = await p.select({
|
|
831
919
|
message: "Select package manager:",
|
|
832
920
|
initialValue: "pnpm",
|
|
833
921
|
options: [
|
|
@@ -845,10 +933,15 @@ Restrictions & Behavior:
|
|
|
845
933
|
}
|
|
846
934
|
]
|
|
847
935
|
});
|
|
848
|
-
if (p.isCancel(
|
|
936
|
+
if (p.isCancel(selectedPackageManager)) {
|
|
849
937
|
p.cancel("Operation cancelled.");
|
|
850
938
|
process.exit(0);
|
|
851
939
|
}
|
|
940
|
+
if (typeof selectedPackageManager !== "string" || !isPackageManager(selectedPackageManager)) {
|
|
941
|
+
p.cancel("Invalid package manager selection.");
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
packageManager = selectedPackageManager;
|
|
852
945
|
}
|
|
853
946
|
const build = await p.confirm({
|
|
854
947
|
message: "Should we run the CI script (lint, build, test)?",
|
|
@@ -887,10 +980,12 @@ Restrictions & Behavior:
|
|
|
887
980
|
}
|
|
888
981
|
try {
|
|
889
982
|
await program.parseAsync(process.argv);
|
|
890
|
-
} catch (
|
|
891
|
-
|
|
892
|
-
if (
|
|
893
|
-
|
|
983
|
+
} catch (error) {
|
|
984
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
985
|
+
if (code === "commander.helpDisplayed" || code === "commander.version" || code === "PROCESS_EXIT_0") process.exit(0);
|
|
986
|
+
if (code === "PROCESS_EXIT_1") process.exit(1);
|
|
987
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
988
|
+
p.cancel(message);
|
|
894
989
|
process.exit(1);
|
|
895
990
|
}
|
|
896
991
|
if (!commandResult) {
|
|
@@ -898,13 +993,29 @@ Restrictions & Behavior:
|
|
|
898
993
|
p.cancel("Unknown command or missing options.");
|
|
899
994
|
process.exit(1);
|
|
900
995
|
}
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (commandResult.
|
|
907
|
-
|
|
996
|
+
const template = stripQuotes(commandResult.template);
|
|
997
|
+
if (template !== void 0) {
|
|
998
|
+
const templateResult = TemplateTypeSchema.safeParse(template);
|
|
999
|
+
if (templateResult.success) commandResult.template = templateResult.data;
|
|
1000
|
+
}
|
|
1001
|
+
if (commandResult.projectName !== void 0) {
|
|
1002
|
+
const projectName = stripQuotes(commandResult.projectName);
|
|
1003
|
+
if (projectName !== void 0) commandResult.projectName = projectName;
|
|
1004
|
+
}
|
|
1005
|
+
if (commandResult.description !== void 0) commandResult.description = stripQuotes(commandResult.description);
|
|
1006
|
+
if (commandResult.keywords !== void 0) commandResult.keywords = stripQuotes(commandResult.keywords);
|
|
1007
|
+
if (commandResult.author !== void 0) {
|
|
1008
|
+
const author = stripQuotes(commandResult.author);
|
|
1009
|
+
if (author !== void 0) commandResult.author = author;
|
|
1010
|
+
}
|
|
1011
|
+
if (commandResult.githubUsername !== void 0) {
|
|
1012
|
+
const githubUsername = stripQuotes(commandResult.githubUsername);
|
|
1013
|
+
if (githubUsername !== void 0) commandResult.githubUsername = githubUsername;
|
|
1014
|
+
}
|
|
1015
|
+
if (commandResult.packageManager !== void 0) {
|
|
1016
|
+
const packageManager = stripQuotes(commandResult.packageManager);
|
|
1017
|
+
if (packageManager !== void 0 && isPackageManager(packageManager)) commandResult.packageManager = packageManager;
|
|
1018
|
+
}
|
|
908
1019
|
debug$2("Validating command result with Zod");
|
|
909
1020
|
const validationResult = ProjectOptionsSchema.safeParse(commandResult);
|
|
910
1021
|
if (!validationResult.success) {
|
|
@@ -912,35 +1023,54 @@ Restrictions & Behavior:
|
|
|
912
1023
|
p.cancel(`Invalid options: ${errors}`);
|
|
913
1024
|
process.exit(1);
|
|
914
1025
|
}
|
|
915
|
-
|
|
916
|
-
const projectDir =
|
|
917
|
-
if (await pathExists$1(projectDir) && !
|
|
1026
|
+
const validated = validationResult.data;
|
|
1027
|
+
const projectDir = validated.directory;
|
|
1028
|
+
if (await pathExists$1(projectDir) && !validated.update) {
|
|
918
1029
|
p.cancel(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
919
1030
|
process.exit(1);
|
|
920
1031
|
}
|
|
921
|
-
return
|
|
1032
|
+
return validated;
|
|
922
1033
|
};
|
|
923
1034
|
//#endregion
|
|
924
1035
|
//#region src/generators/project.ts
|
|
925
1036
|
var debug$1 = debugLib("create-template-project:generator");
|
|
926
1037
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1038
|
+
var pathExists = async (filePath) => {
|
|
1039
|
+
try {
|
|
1040
|
+
await fs.access(filePath);
|
|
1041
|
+
return true;
|
|
1042
|
+
} catch {
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
927
1046
|
var getDependencyConfigPath = async () => {
|
|
928
1047
|
const sourcePath = path.resolve(__dirname, "../config/dependencies.json");
|
|
929
1048
|
const distPath = path.resolve(__dirname, "config/dependencies.json");
|
|
930
1049
|
return await pathExists(distPath) ? distPath : sourcePath;
|
|
931
1050
|
};
|
|
932
|
-
var pathExists = (p) => fs.access(p).then(() => true).catch(() => false);
|
|
933
1051
|
var getLog = (progress) => ({
|
|
934
|
-
info: (msg) =>
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1052
|
+
info: (msg) => {
|
|
1053
|
+
if (progress) p.log.info(msg);
|
|
1054
|
+
},
|
|
1055
|
+
success: (msg) => {
|
|
1056
|
+
if (progress) p.log.success(msg);
|
|
1057
|
+
},
|
|
1058
|
+
warn: (msg) => {
|
|
1059
|
+
p.log.warn(msg);
|
|
1060
|
+
},
|
|
1061
|
+
error: (msg) => {
|
|
1062
|
+
p.log.error(msg);
|
|
1063
|
+
}
|
|
938
1064
|
});
|
|
939
1065
|
var getSpinner = (progress) => {
|
|
940
1066
|
const s = p.spinner();
|
|
941
1067
|
return {
|
|
942
|
-
start: (msg) =>
|
|
943
|
-
|
|
1068
|
+
start: (msg) => {
|
|
1069
|
+
if (progress) s.start(msg);
|
|
1070
|
+
},
|
|
1071
|
+
stop: (msg) => {
|
|
1072
|
+
if (progress) s.stop(msg);
|
|
1073
|
+
}
|
|
944
1074
|
};
|
|
945
1075
|
};
|
|
946
1076
|
var isFileRequired = (relativePath, type) => {
|
|
@@ -952,70 +1082,111 @@ var isFileRequired = (relativePath, type) => {
|
|
|
952
1082
|
].includes(type);
|
|
953
1083
|
return true;
|
|
954
1084
|
};
|
|
1085
|
+
var getTemplateArchitectureSection;
|
|
1086
|
+
var generateGeneratedMd;
|
|
1087
|
+
var isTemplateType = (value) => value === "cli" || value === "web-vanilla" || value === "web-app" || value === "web-fullstack";
|
|
1088
|
+
var isRecord$1 = (value) => typeof value === "object" && value !== null;
|
|
1089
|
+
var parseStringRecord = (value) => {
|
|
1090
|
+
if (!isRecord$1(value)) return {};
|
|
1091
|
+
const out = {};
|
|
1092
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") out[key] = entry;
|
|
1093
|
+
return out;
|
|
1094
|
+
};
|
|
1095
|
+
var parseDependencyConfig = (raw) => {
|
|
1096
|
+
const parsed = JSON.parse(raw);
|
|
1097
|
+
if (!isRecord$1(parsed) || !isRecord$1(parsed.dependencies)) throw new Error("Invalid dependency configuration file format.");
|
|
1098
|
+
const dependencies = {};
|
|
1099
|
+
for (const [name, value] of Object.entries(parsed.dependencies)) {
|
|
1100
|
+
if (!isRecord$1(value)) continue;
|
|
1101
|
+
const { version, description } = value;
|
|
1102
|
+
if (typeof version === "string" && typeof description === "string") dependencies[name] = {
|
|
1103
|
+
version,
|
|
1104
|
+
description
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
return { dependencies };
|
|
1108
|
+
};
|
|
1109
|
+
var parseExistingProjectPackage = (raw) => {
|
|
1110
|
+
const parsed = JSON.parse(raw);
|
|
1111
|
+
if (!isRecord$1(parsed)) return {};
|
|
1112
|
+
const createTemplateProject = isRecord$1(parsed["create-template-project"]) ? parsed["create-template-project"] : void 0;
|
|
1113
|
+
const template = createTemplateProject ? createTemplateProject.template : void 0;
|
|
1114
|
+
return {
|
|
1115
|
+
...parsed,
|
|
1116
|
+
scripts: parseStringRecord(parsed.scripts),
|
|
1117
|
+
dependencies: parseStringRecord(parsed.dependencies),
|
|
1118
|
+
devDependencies: parseStringRecord(parsed.devDependencies),
|
|
1119
|
+
"create-template-project": typeof template === "string" && isTemplateType(template) ? { template } : void 0
|
|
1120
|
+
};
|
|
1121
|
+
};
|
|
1122
|
+
var parseTemplatePackageJson = (raw) => {
|
|
1123
|
+
const parsed = JSON.parse(raw);
|
|
1124
|
+
if (!isRecord$1(parsed)) return {};
|
|
1125
|
+
return {
|
|
1126
|
+
...parsed,
|
|
1127
|
+
dependencies: parseStringRecord(parsed.dependencies),
|
|
1128
|
+
devDependencies: parseStringRecord(parsed.devDependencies)
|
|
1129
|
+
};
|
|
1130
|
+
};
|
|
1131
|
+
var toErrorDetail = (error) => {
|
|
1132
|
+
if (error instanceof Error) {
|
|
1133
|
+
const withOutput = error;
|
|
1134
|
+
const stdout = withOutput.stdout ?? "";
|
|
1135
|
+
const stderr = withOutput.stderr ?? "";
|
|
1136
|
+
const detail = stdout.length > 0 || stderr.length > 0 ? `\n\nOutput:\n${stdout}\n${stderr}` : "";
|
|
1137
|
+
return {
|
|
1138
|
+
message: error.message,
|
|
1139
|
+
detail
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
message: String(error),
|
|
1144
|
+
detail: ""
|
|
1145
|
+
};
|
|
1146
|
+
};
|
|
955
1147
|
var generateProject = async (opts) => {
|
|
956
1148
|
const { template: type, projectName, author, githubUsername, directory, update, progress } = opts;
|
|
957
|
-
const isProgress = progress
|
|
1149
|
+
const isProgress = progress;
|
|
958
1150
|
const log = getLog(isProgress);
|
|
959
1151
|
const spinner = () => getSpinner(isProgress);
|
|
960
1152
|
const projectDir = directory;
|
|
961
1153
|
debug$1("Project generation started for: %s", projectName);
|
|
962
1154
|
debug$1("Options: %O", opts);
|
|
963
1155
|
debug$1("Project directory: %s", projectDir);
|
|
964
|
-
|
|
965
|
-
if (await pathExists(projectDir)) {
|
|
966
|
-
if (!isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
967
|
-
}
|
|
968
|
-
const templates = [getBaseTemplate(opts)];
|
|
1156
|
+
const isUpdate = update;
|
|
1157
|
+
if (await pathExists(projectDir) && !isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
969
1158
|
debug$1("Applying template: base");
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
debug$1("Applying template: cli");
|
|
973
|
-
templates.push(getCliTemplate(opts));
|
|
974
|
-
break;
|
|
975
|
-
case "web-vanilla":
|
|
976
|
-
debug$1("Applying template: web-vanilla");
|
|
977
|
-
templates.push(getWebVanillaTemplate(opts));
|
|
978
|
-
break;
|
|
979
|
-
case "web-app":
|
|
980
|
-
debug$1("Applying template: web-app");
|
|
981
|
-
templates.push(getWebAppTemplate(opts));
|
|
982
|
-
break;
|
|
983
|
-
case "web-fullstack":
|
|
984
|
-
debug$1("Applying template: web-fullstack");
|
|
985
|
-
templates.push(getWebFullstackTemplate(opts));
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
1159
|
+
debug$1("Applying template: %s", type);
|
|
1160
|
+
const templates = getProjectTemplates(opts);
|
|
988
1161
|
debug$1("Ensuring directory exists: %s", projectDir);
|
|
989
1162
|
await fs.mkdir(projectDir, { recursive: true });
|
|
990
1163
|
debug$1("Loading dependency configuration");
|
|
991
1164
|
const depConfigPath = await getDependencyConfigPath();
|
|
992
|
-
const depConfig =
|
|
1165
|
+
const depConfig = parseDependencyConfig(await fs.readFile(depConfigPath, "utf8"));
|
|
993
1166
|
const addedDeps = [];
|
|
994
1167
|
const resolveDeps = (deps = {}) => {
|
|
995
|
-
for (const dep of Object.keys(deps)) {
|
|
1168
|
+
for (const dep of Object.keys(deps)) if (Object.hasOwn(depConfig.dependencies, dep)) {
|
|
996
1169
|
const config = depConfig.dependencies[dep];
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
debug$1(`Dependency "${dep}" missing in config`);
|
|
1006
|
-
}
|
|
1170
|
+
deps[dep] = config.version;
|
|
1171
|
+
addedDeps.push({
|
|
1172
|
+
name: dep,
|
|
1173
|
+
description: config.description
|
|
1174
|
+
});
|
|
1175
|
+
} else {
|
|
1176
|
+
log.warn(`Dependency "${dep}" not found in central configuration. Using empty version.`);
|
|
1177
|
+
debug$1(`Dependency "${dep}" missing in config`);
|
|
1007
1178
|
}
|
|
1008
1179
|
};
|
|
1009
1180
|
let finalPkg = {
|
|
1010
1181
|
name: projectName,
|
|
1011
1182
|
version: "0.1.0",
|
|
1012
1183
|
private: true,
|
|
1013
|
-
description: opts.description
|
|
1014
|
-
keywords: opts.keywords ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
|
|
1184
|
+
description: opts.description ?? "TODO: Add project description",
|
|
1185
|
+
keywords: opts.keywords !== void 0 ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
|
|
1015
1186
|
homepage: `https://github.com/${githubUsername}/${projectName}#readme`,
|
|
1016
1187
|
bugs: { url: `https://github.com/${githubUsername}/${projectName}/issues` },
|
|
1017
1188
|
license: "MIT",
|
|
1018
|
-
author
|
|
1189
|
+
author,
|
|
1019
1190
|
repository: {
|
|
1020
1191
|
type: "git",
|
|
1021
1192
|
url: `https://github.com/${githubUsername}/${projectName}.git`
|
|
@@ -1029,22 +1200,28 @@ var generateProject = async (opts) => {
|
|
|
1029
1200
|
const pkgPath = path.join(projectDir, "package.json");
|
|
1030
1201
|
if (isUpdate && await pathExists(pkgPath)) {
|
|
1031
1202
|
debug$1("Loading existing package.json for update");
|
|
1032
|
-
const existingPkg =
|
|
1033
|
-
if (!existingPkg["create-template-project"]
|
|
1203
|
+
const existingPkg = parseExistingProjectPackage(await fs.readFile(pkgPath, "utf8"));
|
|
1204
|
+
if (!existingPkg["create-template-project"]) throw new Error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
1034
1205
|
finalPkg = {
|
|
1035
1206
|
...finalPkg,
|
|
1036
|
-
...existingPkg
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1207
|
+
...existingPkg,
|
|
1208
|
+
"create-template-project": {
|
|
1209
|
+
...existingPkg["create-template-project"],
|
|
1210
|
+
template: type
|
|
1211
|
+
},
|
|
1212
|
+
scripts: { ...existingPkg.scripts },
|
|
1213
|
+
dependencies: { ...existingPkg.dependencies },
|
|
1214
|
+
devDependencies: { ...existingPkg.devDependencies }
|
|
1041
1215
|
};
|
|
1042
|
-
finalPkg.scripts = { ...existingPkg.scripts };
|
|
1043
|
-
finalPkg.dependencies = { ...existingPkg.dependencies };
|
|
1044
|
-
finalPkg.devDependencies = { ...existingPkg.devDependencies };
|
|
1045
1216
|
debug$1("Loaded existing package.json: %O", finalPkg);
|
|
1046
1217
|
}
|
|
1047
|
-
|
|
1218
|
+
const templatePackageParts = await Promise.all(templates.map(async (template) => {
|
|
1219
|
+
if (template.templateDir === void 0) return;
|
|
1220
|
+
const templatePkgPath = path.join(template.templateDir, "package.json");
|
|
1221
|
+
if (!await pathExists(templatePkgPath)) return;
|
|
1222
|
+
return parseTemplatePackageJson(await fs.readFile(templatePkgPath, "utf8"));
|
|
1223
|
+
}));
|
|
1224
|
+
for (const [index, t] of templates.entries()) {
|
|
1048
1225
|
debug$1("Collecting dependencies and scripts from template: %s", t.name);
|
|
1049
1226
|
const templateDeps = { ...t.dependencies };
|
|
1050
1227
|
const templateDevDeps = { ...t.devDependencies };
|
|
@@ -1053,22 +1230,19 @@ var generateProject = async (opts) => {
|
|
|
1053
1230
|
Object.assign(finalPkg.scripts, t.scripts);
|
|
1054
1231
|
Object.assign(finalPkg.dependencies, templateDeps);
|
|
1055
1232
|
Object.assign(finalPkg.devDependencies, templateDevDeps);
|
|
1056
|
-
if (t.workspaces) finalPkg.workspaces = t.workspaces;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
resolveDeps(pkgPart.devDependencies);
|
|
1063
|
-
mergePackageJson(finalPkg, pkgPart);
|
|
1064
|
-
}
|
|
1233
|
+
if (t.workspaces !== void 0) finalPkg.workspaces = t.workspaces;
|
|
1234
|
+
const pkgPart = templatePackageParts[index];
|
|
1235
|
+
if (pkgPart !== void 0) {
|
|
1236
|
+
resolveDeps(pkgPart.dependencies);
|
|
1237
|
+
resolveDeps(pkgPart.devDependencies);
|
|
1238
|
+
mergePackageJson(finalPkg, pkgPart);
|
|
1065
1239
|
}
|
|
1066
1240
|
}
|
|
1067
1241
|
const actions = [];
|
|
1068
1242
|
const pendingOperations = [];
|
|
1069
1243
|
for (const t of templates) {
|
|
1070
1244
|
debug$1("Collecting template files for: %s", t.name);
|
|
1071
|
-
if (t.templateDir) {
|
|
1245
|
+
if (t.templateDir !== void 0) {
|
|
1072
1246
|
debug$1("Reading physical files from: %s", t.templateDir);
|
|
1073
1247
|
const files = await getAllFiles(t.templateDir);
|
|
1074
1248
|
for (const file of files) {
|
|
@@ -1096,7 +1270,7 @@ var generateProject = async (opts) => {
|
|
|
1096
1270
|
continue;
|
|
1097
1271
|
}
|
|
1098
1272
|
if (relativePath.startsWith("_") && relativePath.endsWith(".config.ts")) {
|
|
1099
|
-
relativePath = relativePath.
|
|
1273
|
+
relativePath = relativePath.slice(1);
|
|
1100
1274
|
targetPath = path.join(projectDir, relativePath);
|
|
1101
1275
|
}
|
|
1102
1276
|
if (relativePath === "package.json") continue;
|
|
@@ -1214,7 +1388,7 @@ var generateProject = async (opts) => {
|
|
|
1214
1388
|
}
|
|
1215
1389
|
}
|
|
1216
1390
|
}
|
|
1217
|
-
const pm = opts.packageManager
|
|
1391
|
+
const pm = opts.packageManager;
|
|
1218
1392
|
if (pm !== "npm") {
|
|
1219
1393
|
for (const [key, value] of Object.entries(finalPkg.scripts)) if (typeof value === "string") finalPkg.scripts[key] = value.replaceAll("npm run ", `${pm} run `);
|
|
1220
1394
|
}
|
|
@@ -1275,7 +1449,10 @@ var generateProject = async (opts) => {
|
|
|
1275
1449
|
}
|
|
1276
1450
|
} else log.info("No changes detected.");
|
|
1277
1451
|
}
|
|
1278
|
-
|
|
1452
|
+
await pendingOperations.reduce(async (previous, operation) => {
|
|
1453
|
+
await previous;
|
|
1454
|
+
await operation();
|
|
1455
|
+
}, Promise.resolve());
|
|
1279
1456
|
const states = {
|
|
1280
1457
|
gitInitialized: false,
|
|
1281
1458
|
githubCreated: false,
|
|
@@ -1298,10 +1475,10 @@ var generateProject = async (opts) => {
|
|
|
1298
1475
|
});
|
|
1299
1476
|
log.success("Initialized Git repository (main branch).");
|
|
1300
1477
|
states.gitInitialized = true;
|
|
1301
|
-
} catch (
|
|
1302
|
-
debug$1("Failed to initialize Git: %O",
|
|
1303
|
-
const
|
|
1304
|
-
log.error(`Failed to initialize Git: ${
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
debug$1("Failed to initialize Git: %O", error);
|
|
1480
|
+
const { message, detail } = toErrorDetail(error);
|
|
1481
|
+
log.error(`Failed to initialize Git: ${message}${detail}`);
|
|
1305
1482
|
}
|
|
1306
1483
|
} else states.gitInitialized = true;
|
|
1307
1484
|
if (opts.build) {
|
|
@@ -1317,12 +1494,12 @@ var generateProject = async (opts) => {
|
|
|
1317
1494
|
});
|
|
1318
1495
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Dependencies installed (${pm} install).`);
|
|
1319
1496
|
states.depsInstalled = true;
|
|
1320
|
-
} catch (
|
|
1321
|
-
debug$1("Failed to install dependencies: %O",
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
debug$1("Failed to install dependencies: %O", error);
|
|
1322
1499
|
s.stop("Failed to install dependencies.");
|
|
1323
|
-
const
|
|
1324
|
-
log.error(`${
|
|
1325
|
-
throw new Error(`Failed to install dependencies: ${
|
|
1500
|
+
const { message, detail } = toErrorDetail(error);
|
|
1501
|
+
log.error(`${message}${detail}`);
|
|
1502
|
+
throw new Error(`Failed to install dependencies: ${message}${detail}`, { cause: error });
|
|
1326
1503
|
}
|
|
1327
1504
|
}
|
|
1328
1505
|
if (opts.build && finalPkg.scripts.ci) {
|
|
@@ -1338,11 +1515,11 @@ var generateProject = async (opts) => {
|
|
|
1338
1515
|
preferLocal: true
|
|
1339
1516
|
});
|
|
1340
1517
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Files formatted (${pm} run format).`);
|
|
1341
|
-
} catch (
|
|
1342
|
-
debug$1("Failed to format files: %O",
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
debug$1("Failed to format files: %O", error);
|
|
1343
1520
|
s.stop("Failed to format files.");
|
|
1344
|
-
const
|
|
1345
|
-
log.error(`${
|
|
1521
|
+
const { message, detail } = toErrorDetail(error);
|
|
1522
|
+
log.error(`${message}${detail}`);
|
|
1346
1523
|
}
|
|
1347
1524
|
}
|
|
1348
1525
|
s.start(`Running CI script (lint, build, test) (${pm} run ci)...`);
|
|
@@ -1355,12 +1532,12 @@ var generateProject = async (opts) => {
|
|
|
1355
1532
|
});
|
|
1356
1533
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m CI script completed (${pm} run ci).`);
|
|
1357
1534
|
states.ciRun = true;
|
|
1358
|
-
} catch (
|
|
1359
|
-
debug$1("Failed to run CI script: %O",
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
debug$1("Failed to run CI script: %O", error);
|
|
1360
1537
|
s.stop("Failed to run CI script.");
|
|
1361
|
-
const
|
|
1362
|
-
log.error(`${
|
|
1363
|
-
throw new Error(`Failed to run CI script: ${
|
|
1538
|
+
const { message, detail } = toErrorDetail(error);
|
|
1539
|
+
log.error(`${message}${detail}`);
|
|
1540
|
+
throw new Error(`Failed to run CI script: ${message}${detail}`, { cause: error });
|
|
1364
1541
|
}
|
|
1365
1542
|
}
|
|
1366
1543
|
if (opts.createGithubRepository && !isUpdate) {
|
|
@@ -1400,31 +1577,32 @@ var generateProject = async (opts) => {
|
|
|
1400
1577
|
});
|
|
1401
1578
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Created GitHub repository and pushed initial commit.`);
|
|
1402
1579
|
states.githubCreated = true;
|
|
1403
|
-
} catch (
|
|
1404
|
-
debug$1("Failed to create/push GitHub repository: %O",
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
debug$1("Failed to create/push GitHub repository: %O", error);
|
|
1405
1582
|
s.stop("Failed to create/push GitHub repository.");
|
|
1406
|
-
const
|
|
1407
|
-
log.warn(`Failed to create/push GitHub repository: ${
|
|
1408
|
-
states.githubError =
|
|
1583
|
+
const { message, detail } = toErrorDetail(error);
|
|
1584
|
+
log.warn(`Failed to create/push GitHub repository: ${message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
|
|
1585
|
+
states.githubError = message;
|
|
1409
1586
|
}
|
|
1410
1587
|
}
|
|
1411
|
-
let hasErrors = false;
|
|
1412
1588
|
let hasWarnings = false;
|
|
1413
1589
|
const errorMessages = [];
|
|
1414
1590
|
if (states.githubError) {
|
|
1415
1591
|
hasWarnings = true;
|
|
1416
1592
|
errorMessages.push(`GitHub repository creation failed: ${states.githubError}`);
|
|
1417
1593
|
}
|
|
1594
|
+
const hasErrors = errorMessages.some((message) => message.startsWith("ERROR:"));
|
|
1418
1595
|
await generateGeneratedMd(projectDir, opts, pm, states, isUpdate, {
|
|
1419
1596
|
hasErrors,
|
|
1420
1597
|
hasWarnings,
|
|
1421
1598
|
errorMessages
|
|
1422
1599
|
}, actions);
|
|
1423
1600
|
const successMsg = `Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}. A detailed setup guide has been generated at GENERATED.md`;
|
|
1424
|
-
if (
|
|
1601
|
+
if (hasErrors) log.error(`${successMsg} (completed with errors)`);
|
|
1602
|
+
else if (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
|
|
1425
1603
|
else log.success(successMsg);
|
|
1426
1604
|
};
|
|
1427
|
-
async
|
|
1605
|
+
generateGeneratedMd = async (projectDir, opts, pm, states, isUpdate, status, actions) => {
|
|
1428
1606
|
const statusBadge = status.hasErrors ? "🔴 **Completed with Errors**" : status.hasWarnings ? "🟡 **Completed with Warnings**" : "🟢 **Successfully Completed**";
|
|
1429
1607
|
const md = [
|
|
1430
1608
|
`# 🚀 Project Setup Guide: ${opts.projectName}`,
|
|
@@ -1468,8 +1646,8 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1468
1646
|
CONFLICT: "🔥 CONFLICT",
|
|
1469
1647
|
DELETE: "🗑️ DELETE",
|
|
1470
1648
|
UPDATED: "✨ UPDATED"
|
|
1471
|
-
}[a.type]
|
|
1472
|
-
return `| \`${a.path}\` | ${actionIcon} | ${a.reason
|
|
1649
|
+
}[a.type] ?? a.type;
|
|
1650
|
+
return `| \`${a.path}\` | ${actionIcon} | ${a.reason ?? "-"} | ${a.recommendedAction ?? (a.type === "CONFLICT" ? "**Resolve conflicts**" : "Review changes")} |`;
|
|
1473
1651
|
}),
|
|
1474
1652
|
""
|
|
1475
1653
|
] : [],
|
|
@@ -1566,8 +1744,8 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1566
1744
|
"<p align=\"center\"><i>This file was auto-generated by <b>create-template-project</b>.</i></p>"
|
|
1567
1745
|
].join("\n");
|
|
1568
1746
|
await fs.writeFile(path.join(projectDir, "GENERATED.md"), md);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1747
|
+
};
|
|
1748
|
+
getTemplateArchitectureSection = (template) => {
|
|
1571
1749
|
switch (template) {
|
|
1572
1750
|
case "cli": return [
|
|
1573
1751
|
"## 🏗️ CLI Architecture",
|
|
@@ -1622,21 +1800,33 @@ function getTemplateArchitectureSection(template) {
|
|
|
1622
1800
|
];
|
|
1623
1801
|
default: return [];
|
|
1624
1802
|
}
|
|
1625
|
-
}
|
|
1803
|
+
};
|
|
1626
1804
|
//#endregion
|
|
1627
1805
|
//#region src/index.ts
|
|
1628
1806
|
if (process.argv.includes("--debug")) {
|
|
1629
|
-
process.env
|
|
1807
|
+
process.env.DEBUG = "create-template-project:*";
|
|
1630
1808
|
debugLib.enable("create-template-project:*");
|
|
1631
1809
|
}
|
|
1632
1810
|
var debug = debugLib("create-template-project:main");
|
|
1811
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
1812
|
+
var getErrorCode = (error) => {
|
|
1813
|
+
if (!isRecord(error)) return;
|
|
1814
|
+
const { code } = error;
|
|
1815
|
+
return typeof code === "string" ? code : void 0;
|
|
1816
|
+
};
|
|
1817
|
+
var getErrorMessage = (error) => {
|
|
1818
|
+
if (isRecord(error)) {
|
|
1819
|
+
const { message } = error;
|
|
1820
|
+
if (typeof message === "string") return message;
|
|
1821
|
+
}
|
|
1822
|
+
return String(error);
|
|
1823
|
+
};
|
|
1633
1824
|
var main = async () => {
|
|
1634
1825
|
try {
|
|
1635
1826
|
debug("Starting CLI execution");
|
|
1636
1827
|
debug("Parsing arguments");
|
|
1637
1828
|
const options = await parseArgs();
|
|
1638
|
-
|
|
1639
|
-
const isProgress = options.progress !== false;
|
|
1829
|
+
const isProgress = options.progress;
|
|
1640
1830
|
if (isProgress) intro("create-template-project");
|
|
1641
1831
|
debug("Arguments parsed: %O", options);
|
|
1642
1832
|
debug("Generating project");
|
|
@@ -1645,12 +1835,13 @@ var main = async () => {
|
|
|
1645
1835
|
if (isProgress) outro("Done!");
|
|
1646
1836
|
} catch (error) {
|
|
1647
1837
|
debug("Execution failed: %O", error);
|
|
1648
|
-
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
1838
|
+
const errorCode = getErrorCode(error);
|
|
1839
|
+
if (errorCode === "PROCESS_EXIT_0" || errorCode === "commander.helpDisplayed" || errorCode === "commander.version") process.exit(0);
|
|
1840
|
+
if (errorCode === "PROCESS_EXIT_1") process.exit(1);
|
|
1841
|
+
cancel(getErrorMessage(error));
|
|
1651
1842
|
process.exit(1);
|
|
1652
1843
|
}
|
|
1653
1844
|
};
|
|
1654
|
-
if (import.meta.url.endsWith("src/index.ts") || import.meta.url.endsWith("dist/index.js")) await main();
|
|
1845
|
+
if (process.env.NODE_ENV !== "test" && (import.meta.url.endsWith("src/index.ts") || import.meta.url.endsWith("dist/index.js"))) await main();
|
|
1655
1846
|
//#endregion
|
|
1656
1847
|
export { main };
|