create-template-project 1.1.2 → 1.2.0

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