create-template-project 1.1.1 → 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.
Files changed (28) hide show
  1. package/README.md +1 -0
  2. package/dist/config/dependencies.json +18 -18
  3. package/dist/index.js +669 -478
  4. package/dist/templates/base/files/_oxc.config.ts +205 -86
  5. package/dist/templates/cli/files/src/index.ts +1 -2
  6. package/dist/templates/cli/files/src/lib.ts +1 -3
  7. package/dist/templates/web-app/files/playwright.config.ts +7 -4
  8. package/dist/templates/web-app/files/src/App.tsx +11 -12
  9. package/dist/templates/web-app/files/src/index.tsx +1 -1
  10. package/dist/templates/web-app/files/tests/e2e/basic.e2e-test.ts +2 -2
  11. package/dist/templates/web-fullstack/files/client/src/App.tsx +4 -3
  12. package/dist/templates/web-fullstack/files/client/src/components/ProtectedRoute.tsx +4 -2
  13. package/dist/templates/web-fullstack/files/client/src/contexts/AuthContext.tsx +22 -18
  14. package/dist/templates/web-fullstack/files/client/src/main.tsx +8 -5
  15. package/dist/templates/web-fullstack/files/client/src/pages/Dashboard.tsx +9 -4
  16. package/dist/templates/web-fullstack/files/client/src/pages/Login.tsx +10 -6
  17. package/dist/templates/web-fullstack/files/client/src/trpc.ts +2 -1
  18. package/dist/templates/web-fullstack/files/playwright.config.ts +8 -5
  19. package/dist/templates/web-fullstack/files/server/src/context.ts +7 -5
  20. package/dist/templates/web-fullstack/files/server/src/index.ts +1 -1
  21. package/dist/templates/web-fullstack/files/server/src/routers/_app.ts +1 -0
  22. package/dist/templates/web-fullstack/files/server/src/routers/auth.ts +4 -4
  23. package/dist/templates/web-fullstack/files/server/src/trpc.ts +3 -4
  24. package/dist/templates/web-vanilla/files/playwright.config.ts +7 -4
  25. package/dist/templates/web-vanilla/files/src/index.test.ts +1 -1
  26. package/dist/templates/web-vanilla/files/src/index.ts +1 -1
  27. package/dist/templates/web-vanilla/files/src/lib.ts +1 -3
  28. package/package.json +21 -15
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 (!description) switch (template) {
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 || "npm";
58
- const lockfileRules = pm === "pnpm" ? "package-lock.json\nyarn.lock" : pm === "yarn" ? "package-lock.json\npnpm-lock.yaml" : "yarn.lock\npnpm-lock.yaml";
59
- 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);
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 = "npm" } = opts;
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 + "\n\n /* Strict Type-Checking Options */");
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 = Array.from(new Set(addedDeps.map((d) => JSON.stringify(d)))).map((s) => JSON.parse(s));
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
- function processContent$1(filePath, content, opts, addedDeps) {
149
+ var processContent$1 = (filePath, content, opts, addedDeps) => {
132
150
  const context = {
133
151
  filePath,
134
152
  opts,
135
153
  addedDeps
136
154
  };
137
- return processors.reduce((acc, processor) => processor(acc, context), content);
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/utils/file.ts
160
+ //#region src/shared/file.ts
141
161
  var debug$3 = debugLib("create-template-project:utils:file");
142
- function getTemplateDir(dirname, templateName) {
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 function getAllFiles(dirPath, arrayOfFiles = []) {
148
- const files = await fs.readdir(dirPath);
149
- for (const file of files) {
150
- if (file === ".DS_Store") continue;
151
- if ((await fs.stat(path.join(dirPath, file))).isDirectory()) arrayOfFiles = await getAllFiles(path.join(dirPath, file), arrayOfFiles);
152
- else arrayOfFiles.push(path.join(dirPath, file));
153
- }
154
- return arrayOfFiles;
155
- }
156
- function processContent(filePath, content, opts, addedDeps) {
157
- return processContent$1(filePath, content, opts, addedDeps);
158
- }
159
- function mergePackageJson(target, source) {
160
- if (source.scripts) target.scripts = {
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
- function isSeedFile(filePath) {
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 function mergeFile(filePath, existing, template, log) {
228
+ };
229
+ var mergeFile = async (filePath, existing, template, log) => {
191
230
  debug$3("Merging file: %s", filePath);
192
- const tempBase = filePath + ".base.tmp";
193
- const tempNew = filePath + ".new.tmp";
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 (e) {
211
- if (e.exitCode >= 1 && e.exitCode < 128) return "conflict";
212
- else {
213
- debug$3("Git merge-file failed: %O", e);
214
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
215
- log.error(`Failed to merge ${filePath}: ${e.message}${detail}`);
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
- return {
229
- name: "base",
230
- description: "The foundation for all project templates, including common tooling and configuration.",
231
- components: [
232
- {
233
- name: "TypeScript",
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
- scripts: {},
279
- files: [],
280
- templateDir: getTemplateDir(__dirname$5, "base")
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
- return {
316
- name: "web-vanilla",
317
- description: "A modern, standalone web page template with built-in development and testing tooling.",
318
- components: [
319
- {
320
- name: "Vite",
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
- scripts: {
342
- dev: "vite",
343
- build: "vite build",
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
- files: [],
350
- templateDir: getTemplateDir(__dirname$3, "web-vanilla")
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
- return {
358
- name: "web-app",
359
- description: "A React application with MUI and TanStack Query, powered by Vite.",
360
- components: [
361
- {
362
- name: "React",
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
- devDependencies: {
396
- "@types/react": "@types/react",
397
- "@types/react-dom": "@types/react-dom",
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
- scripts: {
408
- dev: "vite",
409
- build: "vite build",
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
- files: [],
417
- templateDir: getTemplateDir(__dirname$2, "web-app")
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
- return {
425
- name: "web-fullstack",
426
- description: "A comprehensive full-stack monorepo featuring an Express backend with tRPC and a modern React client with MUI.",
427
- components: [
428
- {
429
- name: "React",
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
- devDependencies: {
473
- "@types/react": "@types/react",
474
- "@types/react-dom": "@types/react-dom",
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
- scripts: {
487
- build: "npm run build --workspaces",
488
- dev: "npm run dev --workspaces",
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
- files: [],
493
- templateDir: getTemplateDir(__dirname$1, "web-fullstack")
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
- let template;
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 pathExists$1 = (p) => fs.access(p).then(() => true).catch(() => false);
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[0];
553
- const last = result[result.length - 1];
554
- if (first === "\"" && last === "\"" || first === "'" && last === "'") result = result.substring(1, result.length - 1).trim();
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 (e) {
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 (e) {
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["DEBUG"] = "create-template-project:*";
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 typeResult = TemplateTypeSchema.safeParse(opts.template);
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: opts.template,
696
+ template: templateResult.data,
634
697
  projectName: opts.name,
635
698
  description: opts.description,
636
699
  keywords: opts.keywords,
637
- author: opts.author || await getDefaultAuthor(),
638
- githubUsername: opts.githubUsername || await getDefaultGithubUsername(),
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: !!opts.createGithubRepository,
642
- progress: !!opts.progress
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 = JSON.parse(await fs.readFile(pkgPath, "utf8"));
667
- } catch (e) {
668
- p.log.error(`Failed to read or parse package.json at ${pkgPath}: ${e.message}`);
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 (!pkg.name) {
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
- if (!pkg["create-template-project"]?.template) {
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: opts.template || pkg["create-template-project"]?.template,
758
+ template,
683
759
  projectName: pkg.name,
684
- description: opts.description || pkg.description,
685
- keywords: opts.keywords || (pkg.keywords ? pkg.keywords.join(", ") : void 0),
686
- author: opts.author || pkg.author || await getDefaultAuthor(),
687
- githubUsername: opts.githubUsername || pkg["create-template-project"]?.githubUsername || await getDefaultGithubUsername(),
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: !!opts.createGithubRepository,
691
- progress: !!opts.progress
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 && value.length > 0 ? void 0 : "Project name is required"
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 = JSON.parse(await fs.readFile(pkgPath, "utf8"));
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 (e) {
733
- debug$2("Failed to read existing package.json: %O", e);
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 ? existingKeywords.join(", ") : ""
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: existingAuthor || existingConfig.author || defaultAuthor,
758
- validate: (value) => value && value.length > 0 ? void 0 : "Author name is required"
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: existingGithubUsername || defaultGithubUsername,
769
- validate: (value) => value && value.length > 0 ? void 0 : "GitHub username is required"
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 (action === "update") {
792
- if (!existingConfig.template) {
793
- p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
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
- let template = existingConfig.template;
876
+ const { template: existingTemplate } = existingConfig;
877
+ let template = existingTemplate;
800
878
  if (!update || !template) {
801
- template = await p.select({
879
+ const selectedTemplate = await p.select({
802
880
  message: "Select project template:",
803
- initialValue: template || "cli",
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(template)) {
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
- packageManager = await p.select({
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(packageManager)) {
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 (e) {
891
- if (e.code === "commander.helpDisplayed" || e.code === "commander.version" || e.code === "PROCESS_EXIT_0") process.exit(0);
892
- if (e.code === "PROCESS_EXIT_1") process.exit(1);
893
- p.cancel(e.message);
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
- if (commandResult.template) commandResult.template = stripQuotes(commandResult.template);
902
- if (commandResult.projectName) commandResult.projectName = stripQuotes(commandResult.projectName);
903
- if (commandResult.description) commandResult.description = stripQuotes(commandResult.description);
904
- if (commandResult.keywords) commandResult.keywords = stripQuotes(commandResult.keywords);
905
- if (commandResult.author) commandResult.author = stripQuotes(commandResult.author);
906
- if (commandResult.githubUsername) commandResult.githubUsername = stripQuotes(commandResult.githubUsername);
907
- if (commandResult.packageManager) commandResult.packageManager = stripQuotes(commandResult.packageManager);
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
- commandResult = validationResult.data;
916
- const projectDir = commandResult.directory;
917
- if (await pathExists$1(projectDir) && !commandResult.update) {
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 commandResult;
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) => progress ? p.log.info(msg) : void 0,
935
- success: (msg) => progress ? p.log.success(msg) : void 0,
936
- warn: (msg) => p.log.warn(msg),
937
- error: (msg) => p.log.error(msg)
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) => progress ? s.start(msg) : void 0,
943
- stop: (msg) => progress ? s.stop(msg) : void 0
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 !== false;
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
- let isUpdate = !!update;
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
- switch (type) {
971
- case "cli":
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 = JSON.parse(await fs.readFile(depConfigPath, "utf8"));
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
- if (config) {
998
- deps[dep] = config.version;
999
- addedDeps.push({
1000
- name: dep,
1001
- description: config.description
1002
- });
1003
- } else {
1004
- log.warn(`Dependency "${dep}" not found in master configuration. Using empty version.`);
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 || "TODO: Add project 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: 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 = JSON.parse(await fs.readFile(pkgPath, "utf8"));
1033
- if (!existingPkg["create-template-project"]?.template) throw new Error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
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
- finalPkg["create-template-project"] = {
1039
- ...existingPkg["create-template-project"],
1040
- template: type
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
- for (const t of templates) {
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
- if (t.templateDir) {
1058
- const templatePkgPath = path.join(t.templateDir, "package.json");
1059
- if (await pathExists(templatePkgPath)) {
1060
- const pkgPart = JSON.parse(await fs.readFile(templatePkgPath, "utf8"));
1061
- resolveDeps(pkgPart.dependencies);
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.substring(1);
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 || "npm";
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
- for (const op of pendingOperations) await op();
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,
@@ -1290,18 +1467,18 @@ var generateProject = async (opts) => {
1290
1467
  if (!await pathExists(path.join(projectDir, ".git"))) {
1291
1468
  debug$1("Initializing Git repository");
1292
1469
  try {
1293
- debug$1("Executing: git init");
1294
- await execa("git", ["init"], {
1470
+ debug$1("Executing: git init --initial-branch=main");
1471
+ await execa("git", ["init", "--initial-branch=main"], {
1295
1472
  cwd: projectDir,
1296
1473
  stdio,
1297
1474
  preferLocal: true
1298
1475
  });
1299
- log.success("Initialized Git repository (git init).");
1476
+ log.success("Initialized Git repository (main branch).");
1300
1477
  states.gitInitialized = true;
1301
- } catch (e) {
1302
- debug$1("Failed to initialize Git: %O", e);
1303
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1304
- log.error(`Failed to initialize Git: ${e.message}${detail}`);
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 (e) {
1321
- debug$1("Failed to install dependencies: %O", e);
1497
+ } catch (error) {
1498
+ debug$1("Failed to install dependencies: %O", error);
1322
1499
  s.stop("Failed to install dependencies.");
1323
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1324
- log.error(`${e.message}${detail}`);
1325
- throw new Error(`Failed to install dependencies: ${e.message}${detail}`);
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 (e) {
1342
- debug$1("Failed to format files: %O", e);
1518
+ } catch (error) {
1519
+ debug$1("Failed to format files: %O", error);
1343
1520
  s.stop("Failed to format files.");
1344
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1345
- log.error(`${e.message}${detail}`);
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 (e) {
1359
- debug$1("Failed to run CI script: %O", e);
1535
+ } catch (error) {
1536
+ debug$1("Failed to run CI script: %O", error);
1360
1537
  s.stop("Failed to run CI script.");
1361
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1362
- log.error(`${e.message}${detail}`);
1363
- throw new Error(`Failed to run CI script: ${e.message}${detail}`);
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 (e) {
1404
- debug$1("Failed to create/push GitHub repository: %O", e);
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 detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1407
- log.warn(`Failed to create/push GitHub repository: ${e.message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
1408
- states.githubError = e.message;
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 (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
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 function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status, actions) {
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}`,
@@ -1450,7 +1628,7 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1450
1628
  `- [x] Scaffold project files and directories`,
1451
1629
  `- [x] Configure \`package.json\` with appropriate dependencies`,
1452
1630
  `- [${states.depsInstalled ? "x" : " "}] Install dependencies using \`${pm}\`${states.depsSkipped ? " *(Skipped)*" : ""}`,
1453
- `- [${states.gitInitialized ? "x" : " "}] Initialize Git repository`,
1631
+ `- [${states.gitInitialized ? "x" : " "}] Initialize Git repository (main branch)`,
1454
1632
  `- [${states.githubCreated ? "x" : " "}] Create and push GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
1455
1633
  `- [${states.ciRun ? "x" : " "}] Run initial CI pipeline (lint, build, test)${states.ciSkipped ? " *(Skipped)*" : ""}`,
1456
1634
  "",
@@ -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] || a.type;
1472
- return `| \`${a.path}\` | ${actionIcon} | ${a.reason || "-"} | ${a.recommendedAction || (a.type === "CONFLICT" ? "**Resolve conflicts**" : "Review changes")} |`;
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
- function getTemplateArchitectureSection(template) {
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["DEBUG"] = "create-template-project:*";
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
- if (!options) return;
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
- if (error.code === "PROCESS_EXIT_0" || error.code === "commander.helpDisplayed" || error.code === "commander.version") process.exit(0);
1649
- if (error.code === "PROCESS_EXIT_1") process.exit(1);
1650
- cancel(error?.message || String(error));
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 };