create-template-project 0.5.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,9 +22,10 @@ pnpm dlx create-template-project interactive
22
22
 
23
23
  - **Modern Tech Stack:** All templates come with `commitlint`, `husky`, `vitest`, `oxlint`, `oxfmt`, and `typescript` (strict mode).
24
24
  - **Interactive CLI:** Prompts you for project details if CLI arguments are missing, using `@clack/prompts`.
25
- - **🔄 Update Mode:** Detects existing projects and offers a safe update path using `git merge-file`.
26
- - Files in `src/` and all `*.md` files are skipped to protect your application logic and documentation.
27
- - Ideal for keeping your project's boilerplate (linting, CI, configs) up-to-date.
25
+ - **🔄 Update Mode:** Detects existing projects and offers a safe update path.
26
+ - **Intelligent Tracking:** Automatically generates a detailed `GENERATED.md` with an "Upgrade Details" table showing exactly what changed, why, and what actions (like conflict resolution) are needed.
27
+ - **Seed File Protection:** Files in `src/`, all `*.md` files, and other core files are skipped to protect your application logic and custom documentation.
28
+ - **Tooling Sync:** Keeps your project's boilerplate (linting, CI, configs, scripts) up-to-date with the latest template versions.
28
29
  - **No-Build Option:** Supports creating simple projects without a build step (strips Vite).
29
30
  - **GitHub Integration:** Automatically initializes a Git repository and can create a GitHub repository using the `gh` CLI.
30
31
  - **CI Ready:** Generates GitHub Actions workflows for automated testing and linting.
package/dist/index.js CHANGED
@@ -37,23 +37,8 @@ var ProjectOptionsSchema = z.object({
37
37
  progress: z.boolean().optional().default(true)
38
38
  });
39
39
  //#endregion
40
- //#region src/utils/file.ts
41
- var debug$3 = debugLib("create-template-project:utils:file");
42
- function getTemplateDir(dirname, templateName) {
43
- const sourcePath = path.resolve(dirname, "files");
44
- const distPath = path.resolve(dirname, "templates", templateName, "files");
45
- return existsSync(distPath) ? distPath : sourcePath;
46
- }
47
- async function getAllFiles(dirPath, arrayOfFiles = []) {
48
- const files = await fs.readdir(dirPath);
49
- for (const file of files) {
50
- if (file === ".DS_Store") continue;
51
- if ((await fs.stat(path.join(dirPath, file))).isDirectory()) arrayOfFiles = await getAllFiles(path.join(dirPath, file), arrayOfFiles);
52
- else arrayOfFiles.push(path.join(dirPath, file));
53
- }
54
- return arrayOfFiles;
55
- }
56
- function processContent(filePath, content, opts, addedDeps) {
40
+ //#region src/utils/templating/generic.ts
41
+ var genericProcessor = (content, { opts }) => {
57
42
  const { projectName, template, author, githubUsername } = opts;
58
43
  let description = opts.description || "";
59
44
  if (!description) switch (template) {
@@ -72,29 +57,105 @@ function processContent(filePath, content, opts, addedDeps) {
72
57
  }
73
58
  const pm = opts.packageManager || "npm";
74
59
  const lockfileRules = pm === "pnpm" ? "package-lock.json\nyarn.lock" : pm === "yarn" ? "package-lock.json\npnpm-lock.yaml" : "yarn.lock\npnpm-lock.yaml";
75
- let processed = 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);
76
- if (filePath.includes(".github/workflows/node.js.yml")) {
77
- let installCommand = "npm ci";
78
- let pmSetup = "";
79
- if (pm === "pnpm") {
80
- installCommand = "pnpm install --frozen-lockfile";
81
- pmSetup = "- uses: pnpm/action-setup@v4\n with:\n version: 9";
82
- } else if (pm === "yarn") installCommand = "yarn install --frozen-lockfile";
83
- let playwrightSetup = "";
84
- if (template === "web-fullstack" || template === "web-app" || template === "web-vanilla") playwrightSetup = "- name: Install Playwright Browsers & Deps\n run: npx playwright install --with-deps chromium";
85
- processed = processed.replaceAll("{{installCommand}}", installCommand).replaceAll("# [PM_SETUP]", pmSetup).replaceAll("# [PLAYWRIGHT_SETUP]", playwrightSetup);
86
- processed = processed.replace(/^\s*# \[PM_SETUP\]\s*\n/m, "");
87
- processed = processed.replace(/^\s*# \[PLAYWRIGHT_SETUP\]\s*\n/m, "");
88
- }
89
- if (template === "web-vanilla" && filePath === "index.html") processed = processed.replace("{{scriptSrc}}", "/src/index.ts");
90
- if (filePath === "CONTRIBUTING.md" && addedDeps.length > 0) {
91
- processed += "\n## Dependencies\n\n";
92
- const uniqueDeps = Array.from(new Set(addedDeps.map((d) => JSON.stringify(d)))).map((s) => JSON.parse(s));
93
- for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
94
- }
95
- if ((template === "web-fullstack" || template === "web-vanilla" || template === "web-app") && filePath === "tsconfig.json") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, "/* Language and Environment */\n \"target\": \"ES2023\" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,\n \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,\n \"module\": \"ESNext\" /* Specify what module code is generated. */,\n \"moduleResolution\": \"bundler\" /* Specify how TypeScript looks up a file from a given module specifier. */,\n \"esModuleInterop\": true /* Emit additional JavaScript to ease support for importing CommonJS modules. */,\n \"resolveJsonModule\": true /* Enable importing .json files. */,\n \"allowImportingTsExtensions\": true /* Allow imports to include TypeScript file extensions. */,\n \"noEmit\": true /* Disable emitting files from a compilation. */,\n \"jsx\": \"react-jsx\" /* Specify what JSX code is generated. */,\n\n /* Strict Type-Checking Options */");
96
- if (template === "web-fullstack" && filePath === "tsconfig.json") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
60
+ 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);
61
+ };
62
+ //#endregion
63
+ //#region src/utils/templating/github-workflow.ts
64
+ var WORKFLOW_PNPM_SETUP = ` - name: Setup pnpm
65
+ uses: pnpm/action-setup@v4
66
+ with:
67
+ version: 9
68
+ run_install: false`;
69
+ var WORKFLOW_PLAYWRIGHT_SETUP = ` - name: Install Playwright Browsers & Deps
70
+ run: npx playwright install --with-deps chromium`;
71
+ var githubWorkflowProcessor = (content, { filePath, opts }) => {
72
+ if (!filePath.includes(".github/workflows/node.js.yml")) return content;
73
+ const { template, packageManager: pm = "npm" } = opts;
74
+ let installCommand = "npm ci";
75
+ let pmSetup = "";
76
+ if (pm === "pnpm") {
77
+ installCommand = "pnpm install --frozen-lockfile";
78
+ pmSetup = WORKFLOW_PNPM_SETUP;
79
+ } else if (pm === "yarn") installCommand = "yarn install --frozen-lockfile";
80
+ let playwrightSetup = "";
81
+ if (template === "web-fullstack" || template === "web-app" || template === "web-vanilla") playwrightSetup = WORKFLOW_PLAYWRIGHT_SETUP;
82
+ let processed = content.replaceAll("{{installCommand}}", installCommand).replaceAll("# [PM_SETUP]", pmSetup).replaceAll("# [PLAYWRIGHT_SETUP]", playwrightSetup);
83
+ processed = processed.replace(/^\s*# \[PM_SETUP\]\s*\n/m, "");
84
+ processed = processed.replace(/^\s*# \[PLAYWRIGHT_SETUP\]\s*\n/m, "");
85
+ return processed;
86
+ };
87
+ //#endregion
88
+ //#region src/utils/templating/tsconfig.ts
89
+ var WEB_ENV = `/* Language and Environment */
90
+ "target": "ES2023" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
91
+ "lib": ["ES2023", "DOM", "DOM.Iterable"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
92
+ "module": "ESNext" /* Specify what module code is generated. */,
93
+ "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
94
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. */,
95
+ "resolveJsonModule": true /* Enable importing .json files. */,
96
+ "allowImportingTsExtensions": true /* Allow imports to include TypeScript file extensions. */,
97
+ "noEmit": true /* Disable emitting files from a compilation. */,
98
+ "jsx": "react-jsx" /* Specify what JSX code is generated. */,`;
99
+ var tsconfigProcessor = (content, { filePath, opts }) => {
100
+ if (filePath !== "tsconfig.json") return content;
101
+ const { template } = opts;
102
+ let processed = content;
103
+ 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 */");
104
+ if (template === "web-fullstack") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
105
+ return processed;
106
+ };
107
+ //#endregion
108
+ //#region src/utils/templating/contributing.ts
109
+ var contributingProcessor = (content, { filePath, addedDeps }) => {
110
+ if (filePath !== "CONTRIBUTING.md" || addedDeps.length === 0) return content;
111
+ let processed = content;
112
+ processed += "\n## Dependencies\n\n";
113
+ const uniqueDeps = Array.from(new Set(addedDeps.map((d) => JSON.stringify(d)))).map((s) => JSON.parse(s));
114
+ for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
97
115
  return processed;
116
+ };
117
+ //#endregion
118
+ //#region src/utils/templating/web-vanilla-html.ts
119
+ var webVanillaHtmlProcessor = (content, { filePath, opts }) => {
120
+ if (opts.template === "web-vanilla" && filePath === "index.html") return content.replace("{{scriptSrc}}", "/src/index.ts");
121
+ return content;
122
+ };
123
+ //#endregion
124
+ //#region src/utils/templating/index.ts
125
+ var processors = [
126
+ genericProcessor,
127
+ githubWorkflowProcessor,
128
+ tsconfigProcessor,
129
+ contributingProcessor,
130
+ webVanillaHtmlProcessor
131
+ ];
132
+ function processContent$1(filePath, content, opts, addedDeps) {
133
+ const context = {
134
+ filePath,
135
+ opts,
136
+ addedDeps
137
+ };
138
+ return processors.reduce((acc, processor) => processor(acc, context), content);
139
+ }
140
+ //#endregion
141
+ //#region src/utils/file.ts
142
+ var debug$3 = debugLib("create-template-project:utils:file");
143
+ function getTemplateDir(dirname, templateName) {
144
+ const sourcePath = path.resolve(dirname, "files");
145
+ const distPath = path.resolve(dirname, "templates", templateName, "files");
146
+ return existsSync(distPath) ? distPath : sourcePath;
147
+ }
148
+ async function getAllFiles(dirPath, arrayOfFiles = []) {
149
+ const files = await fs.readdir(dirPath);
150
+ for (const file of files) {
151
+ if (file === ".DS_Store") continue;
152
+ if ((await fs.stat(path.join(dirPath, file))).isDirectory()) arrayOfFiles = await getAllFiles(path.join(dirPath, file), arrayOfFiles);
153
+ else arrayOfFiles.push(path.join(dirPath, file));
154
+ }
155
+ return arrayOfFiles;
156
+ }
157
+ function processContent(filePath, content, opts, addedDeps) {
158
+ return processContent$1(filePath, content, opts, addedDeps);
98
159
  }
99
160
  function mergePackageJson(target, source) {
100
161
  if (source.scripts) target.scripts = {
@@ -277,7 +338,7 @@ var getWebVanillaTemplate = (_opts) => {
277
338
  preview: "vite preview",
278
339
  test: "vitest run",
279
340
  "test:ui": "vitest",
280
- "test:e2e": "playwright test"
341
+ "integration-test": "playwright test"
281
342
  },
282
343
  files: [],
283
344
  templateDir: getTemplateDir(__dirname$3, "web-vanilla")
@@ -343,7 +404,7 @@ var getWebAppTemplate = (_opts) => {
343
404
  preview: "vite preview",
344
405
  test: "vitest run",
345
406
  "test:ui": "vitest",
346
- "test:e2e": "playwright test",
407
+ "integration-test": "playwright test",
347
408
  start: "vite preview"
348
409
  },
349
410
  files: [],
@@ -420,7 +481,7 @@ var getWebFullstackTemplate = (_opts) => {
420
481
  build: "npm run build --workspaces",
421
482
  dev: "npm run dev --workspaces",
422
483
  test: "npm run test --workspaces",
423
- "test:e2e": "playwright test"
484
+ "integration-test": "playwright test"
424
485
  },
425
486
  files: [],
426
487
  templateDir: getTemplateDir(__dirname$1, "web-fullstack")
@@ -1007,7 +1068,8 @@ var generateProject = async (opts) => {
1007
1068
  if (isUpdate && isSeedFile(relativePath)) {
1008
1069
  actions.push({
1009
1070
  type: "SKIP",
1010
- path: relativePath
1071
+ path: relativePath,
1072
+ reason: "Seed file - skipped during update to preserve manual changes"
1011
1073
  });
1012
1074
  continue;
1013
1075
  }
@@ -1015,7 +1077,8 @@ var generateProject = async (opts) => {
1015
1077
  if (isUpdate && await pathExists(targetPath)) {
1016
1078
  actions.push({
1017
1079
  type: "DELETE",
1018
- path: relativePath
1080
+ path: relativePath,
1081
+ reason: "File no longer required for this template type"
1019
1082
  });
1020
1083
  pendingOperations.push(async () => {
1021
1084
  await fs.rm(targetPath, { force: true });
@@ -1035,21 +1098,36 @@ var generateProject = async (opts) => {
1035
1098
  if (isUpdate && exists) {
1036
1099
  const existingContent = await fs.readFile(finalTargetPath, "utf8");
1037
1100
  if (existingContent.trim() !== content.trim()) {
1038
- actions.push({
1101
+ const action = {
1039
1102
  type: "MODIFY",
1040
- path: finalRelativePath
1041
- });
1103
+ path: finalRelativePath,
1104
+ reason: "Template tooling or configuration update"
1105
+ };
1106
+ actions.push(action);
1042
1107
  pendingOperations.push(async () => {
1043
1108
  const result = await mergeFile(finalTargetPath, existingContent, content, log);
1044
- if (result === "merged") log.info(`ℹ Merged: ${finalRelativePath}`);
1045
- else if (result === "conflict") log.warn(`⚠ Conflict: ${finalRelativePath}`);
1046
- else if (result === "updated") log.info(`✔ Updated: ${finalRelativePath}`);
1109
+ if (result === "merged") {
1110
+ action.type = "MERGE";
1111
+ action.reason = "Merged template updates with your manual changes";
1112
+ action.recommendedAction = "Review changes for correct integration";
1113
+ log.info(`ℹ Merged: ${finalRelativePath}`);
1114
+ } else if (result === "conflict") {
1115
+ action.type = "CONFLICT";
1116
+ action.reason = "Conflicting changes between template and your code";
1117
+ action.recommendedAction = "Resolve git conflict markers in this file";
1118
+ log.warn(`⚠ Conflict: ${finalRelativePath}`);
1119
+ } else if (result === "updated") {
1120
+ action.type = "UPDATED";
1121
+ action.reason = "File was updated to the latest template version";
1122
+ log.info(`✔ Updated: ${finalRelativePath}`);
1123
+ }
1047
1124
  });
1048
1125
  }
1049
1126
  } else if (!exists) {
1050
1127
  actions.push({
1051
1128
  type: "ADD",
1052
- path: finalRelativePath
1129
+ path: finalRelativePath,
1130
+ reason: "New template file added"
1053
1131
  });
1054
1132
  pendingOperations.push(async () => {
1055
1133
  await fs.mkdir(path.dirname(finalTargetPath), { recursive: true });
@@ -1065,7 +1143,8 @@ var generateProject = async (opts) => {
1065
1143
  if (isUpdate && isSeedFile(file.path)) {
1066
1144
  actions.push({
1067
1145
  type: "SKIP",
1068
- path: file.path
1146
+ path: file.path,
1147
+ reason: "Seed file - skipped during update to preserve manual changes"
1069
1148
  });
1070
1149
  continue;
1071
1150
  }
@@ -1073,7 +1152,8 @@ var generateProject = async (opts) => {
1073
1152
  if (isUpdate && await pathExists(targetPath)) {
1074
1153
  actions.push({
1075
1154
  type: "DELETE",
1076
- path: file.path
1155
+ path: file.path,
1156
+ reason: "File no longer required for this template type"
1077
1157
  });
1078
1158
  pendingOperations.push(async () => {
1079
1159
  await fs.rm(targetPath, { force: true });
@@ -1087,21 +1167,36 @@ var generateProject = async (opts) => {
1087
1167
  if (isUpdate && exists) {
1088
1168
  const existingContent = await fs.readFile(targetPath, "utf8");
1089
1169
  if (existingContent.trim() !== content.trim()) {
1090
- actions.push({
1170
+ const action = {
1091
1171
  type: "MODIFY",
1092
- path: file.path
1093
- });
1172
+ path: file.path,
1173
+ reason: "Template configuration update"
1174
+ };
1175
+ actions.push(action);
1094
1176
  pendingOperations.push(async () => {
1095
1177
  const result = await mergeFile(targetPath, existingContent, content, log);
1096
- if (result === "merged") log.info(`ℹ Merged: ${file.path}`);
1097
- else if (result === "conflict") log.warn(`⚠ Conflict: ${file.path}`);
1098
- else if (result === "updated") log.info(`✔ Updated: ${file.path}`);
1178
+ if (result === "merged") {
1179
+ action.type = "MERGE";
1180
+ action.reason = "Merged template updates with your manual changes";
1181
+ action.recommendedAction = "Review changes for correct integration";
1182
+ log.info(`ℹ Merged: ${file.path}`);
1183
+ } else if (result === "conflict") {
1184
+ action.type = "CONFLICT";
1185
+ action.reason = "Conflicting changes between template and your code";
1186
+ action.recommendedAction = "Resolve git conflict markers in this file";
1187
+ log.warn(`⚠ Conflict: ${file.path}`);
1188
+ } else if (result === "updated") {
1189
+ action.type = "UPDATED";
1190
+ action.reason = "File was updated to the latest template version";
1191
+ log.info(`✔ Updated: ${file.path}`);
1192
+ }
1099
1193
  });
1100
1194
  }
1101
1195
  } else if (!exists) {
1102
1196
  actions.push({
1103
1197
  type: "ADD",
1104
- path: file.path
1198
+ path: file.path,
1199
+ reason: "New template file added"
1105
1200
  });
1106
1201
  pendingOperations.push(async () => {
1107
1202
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
@@ -1124,7 +1219,8 @@ var generateProject = async (opts) => {
1124
1219
  if (workspaceChanged) {
1125
1220
  actions.push({
1126
1221
  type: workspaceExists ? "MODIFY" : "ADD",
1127
- path: "pnpm-workspace.yaml"
1222
+ path: "pnpm-workspace.yaml",
1223
+ reason: "Updated workspace configuration for pnpm"
1128
1224
  });
1129
1225
  pendingOperations.push(async () => {
1130
1226
  await fs.writeFile(workspacePath, workspaceYaml);
@@ -1142,11 +1238,13 @@ var generateProject = async (opts) => {
1142
1238
  if (pkgChanged) {
1143
1239
  if (isUpdate) actions.push({
1144
1240
  type: "MODIFY",
1145
- path: "package.json"
1241
+ path: "package.json",
1242
+ reason: "Updated dependencies and scripts to match latest template"
1146
1243
  });
1147
1244
  else actions.push({
1148
1245
  type: "ADD",
1149
- path: "package.json"
1246
+ path: "package.json",
1247
+ reason: "Initial project configuration"
1150
1248
  });
1151
1249
  pendingOperations.push(async () => {
1152
1250
  debug$1("Writing final consolidated package.json to: %s", pkgPath);
@@ -1292,12 +1390,12 @@ var generateProject = async (opts) => {
1292
1390
  hasErrors,
1293
1391
  hasWarnings,
1294
1392
  errorMessages
1295
- });
1393
+ }, actions);
1296
1394
  const successMsg = `Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}. A detailed setup guide has been generated at GENERATED.md`;
1297
1395
  if (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
1298
1396
  else log.success(successMsg);
1299
1397
  };
1300
- async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status) {
1398
+ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status, actions) {
1301
1399
  const statusBadge = status.hasErrors ? "🔴 **Completed with Errors**" : status.hasWarnings ? "🟡 **Completed with Warnings**" : "🟢 **Successfully Completed**";
1302
1400
  const md = [
1303
1401
  `# 🚀 Project Setup Guide: ${opts.projectName}`,
@@ -1319,7 +1417,7 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1319
1417
  "---",
1320
1418
  "",
1321
1419
  "## 📋 Initialization Checklist",
1322
- "The following tasks were executed during the generation process:",
1420
+ isUpdate ? "The project was updated with the latest template changes:" : "The following tasks were executed during the generation process:",
1323
1421
  `- [x] Scaffold project files and directories`,
1324
1422
  `- [x] Configure \`package.json\` with appropriate dependencies`,
1325
1423
  `- [${states.depsInstalled ? "x" : " "}] Install dependencies using \`${pm}\`${states.depsSkipped ? " *(Skipped)*" : ""}`,
@@ -1327,6 +1425,41 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1327
1425
  `- [${states.githubCreated ? "x" : " "}] Create GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
1328
1426
  `- [${states.ciRun ? "x" : " "}] Run initial CI pipeline (lint, build, test)${states.ciSkipped ? " *(Skipped)*" : ""}`,
1329
1427
  "",
1428
+ ...isUpdate ? [
1429
+ "### 🛠️ Upgrade Details",
1430
+ "The following files were affected by this update:",
1431
+ "",
1432
+ "| File Path | Action | Reason | Next Steps |",
1433
+ "| :--- | :--- | :--- | :--- |",
1434
+ ...actions.filter((a) => a.type !== "SKIP").map((a) => {
1435
+ const actionIcon = {
1436
+ ADD: "➕ ADD",
1437
+ MODIFY: "📝 MODIFY",
1438
+ MERGE: "🔀 MERGE",
1439
+ CONFLICT: "🔥 CONFLICT",
1440
+ DELETE: "🗑️ DELETE",
1441
+ UPDATED: "✨ UPDATED"
1442
+ }[a.type] || a.type;
1443
+ return `| \`${a.path}\` | ${actionIcon} | ${a.reason || "-"} | ${a.recommendedAction || (a.type === "CONFLICT" ? "**Resolve conflicts**" : "Review changes")} |`;
1444
+ }),
1445
+ ""
1446
+ ] : [],
1447
+ "---",
1448
+ "",
1449
+ "## 🛠️ Manual Adjustments Needed",
1450
+ "To complete your project setup, please review and manually update the following:",
1451
+ "- [ ] **`LICENSE`**: Verify the copyright year and author name.",
1452
+ "- [ ] **`package.json`**: Review the description, keywords, author, and repository links.",
1453
+ "- [ ] **`README.md`**: Update with project-specific instructions, architecture details, and contribution guidelines.",
1454
+ "- [ ] **`.gitignore`**: Note that there is a **`# Custom`** section at the end of the file for your own ignores.",
1455
+ "",
1456
+ "---",
1457
+ "",
1458
+ "## 💡 Next Steps",
1459
+ "1. Review the generated codebase to familiarize yourself with the structure.",
1460
+ `2. Start the development server using \`${pm} run dev\`.`,
1461
+ "3. Make your first commit and push to your remote repository.",
1462
+ "",
1330
1463
  "---",
1331
1464
  "",
1332
1465
  "## 💻 Available Commands",
@@ -1402,19 +1535,6 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1402
1535
  "",
1403
1536
  "---",
1404
1537
  "",
1405
- "## 🛠️ Manual Adjustments Needed",
1406
- "To complete your project setup, please review and manually update the following:",
1407
- "- [ ] **`LICENSE`**: Verify the copyright year and author name.",
1408
- "- [ ] **`package.json`**: Review the description, keywords, author, and repository links.",
1409
- "- [ ] **`README.md`**: Update with project-specific instructions, architecture details, and contribution guidelines.",
1410
- "",
1411
- "---",
1412
- "",
1413
- "## 💡 Next Steps",
1414
- "1. Review the generated codebase to familiarize yourself with the structure.",
1415
- `2. Start the development server using \`${pm} run dev\`.`,
1416
- "3. Make your first commit and push to your remote repository.",
1417
- "",
1418
1538
  "<br>",
1419
1539
  "<p align=\"center\"><i>This file was auto-generated by <b>create-template-project</b>.</i></p>"
1420
1540
  ].join("\n");
@@ -2,23 +2,49 @@ name: Node.js CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [main, master]
5
+ branches: [main]
6
6
  pull_request:
7
- branches: [main, master]
7
+ branches: [main]
8
8
 
9
9
  jobs:
10
10
  build:
11
11
  runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [22.x, 24.x]
16
+
12
17
  steps:
13
- - uses: actions/checkout@v4
14
- # [PM_SETUP]
15
- - name: Use Node.js
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+
21
+ # [PM_SETUP]
22
+
23
+ - name: Setup Node.js
16
24
  uses: actions/setup-node@v4
17
25
  with:
18
- node-version: "22.x"
26
+ node-version: ${{ matrix.node-version }}
19
27
  cache: "{{packageManager}}"
20
- - run: "{{installCommand}}"
21
- # [PLAYWRIGHT_SETUP]
22
- - run: "{{packageManager}} run ci"
28
+
29
+ - name: Verify {{packageManager}}
30
+ run: {{packageManager}} --version
31
+
32
+ - name: Install dependencies
33
+ run: {{installCommand}}
34
+
35
+ # [PLAYWRIGHT_SETUP]
36
+
37
+ - name: Lint
38
+ run: {{packageManager}} run lint
39
+
40
+ - name: Build
41
+ run: {{packageManager}} run build
42
+
43
+ - name: Unit test
44
+ run: {{packageManager}} run test
45
+
23
46
  - name: Coveralls GitHub Action
24
47
  uses: coverallsapp/github-action@v2
48
+
49
+ - name: Integration test
50
+ run: {{packageManager}} run integration-test
@@ -4,6 +4,6 @@ Build/Lint/Test:
4
4
  - `pnpm run ci`: Runs lint, build, and all tests.
5
5
  - `pnpm run dev`: Starts the development server.
6
6
  - `pnpm run test`: Runs unit tests (browser-based for web projects).
7
- - `pnpm run test:e2e`: Runs Playwright E2E tests.
7
+ - `pnpm run integration-test`: Runs integration tests.
8
8
  - `pnpm exec vitest <file>`: Runs a specific test file.
9
9
  - `pnpm run lint`: Lints and formats the codebase (oxlint + oxfmt).
@@ -17,7 +17,7 @@ This project is built using **Vite 8** for high-performance development and bund
17
17
  - `pnpm run build`: Builds the project for production.
18
18
  - `pnpm run preview`: Previews the production build.
19
19
  - `pnpm run test`: Runs the unit test suite (browser-based for web targets using **Vitest** and **Playwright**).
20
- - `pnpm run test:e2e`: Runs E2E tests using **Playwright**.
20
+ - `pnpm run integration-test`: Runs integration tests using **Playwright**.
21
21
  - `pnpm run lint`: Lints and formats the codebase using **oxlint** and **oxfmt**.
22
22
  - `pnpm run ci`: Full CI pipeline (lint, build, test).
23
23
 
@@ -34,7 +34,7 @@ This project is built using **Vite 8** for high-performance development and bund
34
34
  3. **Run tests**:
35
35
  ```bash
36
36
  pnpm run test
37
- pnpm run test:e2e
37
+ pnpm run integration-test
38
38
  ```
39
39
 
40
40
  ## Tooling
@@ -4,7 +4,7 @@ export default defineConfig({
4
4
  test: {
5
5
  coverage: {
6
6
  provider: 'v8',
7
- reporter: ['text', 'json', 'html'],
7
+ reporter: ['text', 'json', 'html', 'lcov'],
8
8
  include: ['src/**/*.ts', 'src/**/*.tsx'],
9
9
  exclude: ['src/**/*.test.ts', 'src/**/*.test.tsx', 'src/**/*.d.ts'],
10
10
  },
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "dev": "vite build --watch",
13
- "build": "vite build"
13
+ "build": "vite build",
14
+ "integration-test": "node -e \"\""
14
15
  }
15
16
  }
@@ -16,7 +16,7 @@ export default defineConfig({
16
16
  test: {
17
17
  coverage: {
18
18
  provider: 'v8',
19
- reporter: ['text', 'json', 'html'],
19
+ reporter: ['text', 'json', 'html', 'lcov'],
20
20
  },
21
21
  },
22
22
  });
@@ -13,7 +13,7 @@ export default defineConfig({
13
13
  include: ['src/**/*.test.{ts,tsx}'],
14
14
  coverage: {
15
15
  provider: 'v8',
16
- reporter: ['text', 'json', 'html'],
16
+ reporter: ['text', 'json', 'html', 'lcov'],
17
17
  },
18
18
  browser: {
19
19
  enabled: true,
@@ -9,7 +9,7 @@ export default defineConfig({
9
9
  include: ['src/**/*.test.{ts,tsx}'],
10
10
  coverage: {
11
11
  provider: 'v8',
12
- reporter: ['text', 'json', 'html'],
12
+ reporter: ['text', 'json', 'html', 'lcov'],
13
13
  },
14
14
  browser: {
15
15
  enabled: true,
@@ -29,6 +29,6 @@
29
29
  "scripts": {
30
30
  "build": "npm run build --workspaces",
31
31
  "dev": "npm run dev --workspaces",
32
- "test:e2e": "playwright test"
32
+ "integration-test": "playwright test"
33
33
  }
34
34
  }
@@ -18,7 +18,7 @@ export default defineConfig({
18
18
  environment: 'node',
19
19
  coverage: {
20
20
  provider: 'v8',
21
- reporter: ['text', 'json', 'html'],
21
+ reporter: ['text', 'json', 'html', 'lcov'],
22
22
  },
23
23
  },
24
24
  });
@@ -12,6 +12,6 @@
12
12
  "preview": "vite preview",
13
13
  "test": "vitest run",
14
14
  "test:ui": "vitest",
15
- "test:e2e": "playwright test"
15
+ "integration-test": "playwright test"
16
16
  }
17
17
  }
@@ -7,7 +7,7 @@ export default defineConfig({
7
7
  include: ['src/**/*.test.ts'],
8
8
  coverage: {
9
9
  provider: 'v8',
10
- reporter: ['text', 'json', 'html'],
10
+ reporter: ['text', 'json', 'html', 'lcov'],
11
11
  },
12
12
  browser: {
13
13
  enabled: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-template-project",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "An ultra-modular, type-safe Node.js CLI tool used to scaffold new project templates (CLI, Webpage, Webapp, Fullstack) with best-practice configurations pre-installed.",
5
5
  "keywords": [
6
6
  "boilerplate",
@@ -58,7 +58,7 @@
58
58
  "integration-test:web-vanilla": "rimraf ./temp/web-vanilla && node ./dist/index.js create --package-manager=pnpm --build --debug --no-progress --template=web-vanilla --name=web-vanilla --author=\"Jon Doe\" --github-username=\"jon-doe\" --path=./temp/web-vanilla",
59
59
  "integration-test:web-app": "rimraf ./temp/web-app && node ./dist/index.js create --package-manager=pnpm --build --debug --no-progress --template=web-app --name=web-app --author=\"Jon Doe\" --github-username=\"jon-doe\" --path=./temp/web-app",
60
60
  "integration-test:web-fullstack": "rimraf ./temp/web-fullstack && node ./dist/index.js create --package-manager=pnpm --build --debug --no-progress --template=web-fullstack --name=web-fullstack --author=\"Jon Doe\" --github-username=\"jon-doe\" --path=./temp/web-fullstack",
61
- "integration-test:all": "pnpm run integration-test:cli && pnpm run integration-test:web-vanilla && pnpm run integration-test:web-app && pnpm run integration-test:web-fullstack",
61
+ "integration-test": "pnpm run integration-test:cli && pnpm run integration-test:web-vanilla && pnpm run integration-test:web-app && pnpm run integration-test:web-fullstack",
62
62
  "ci": "pnpm run lint && pnpm run build && pnpm run test",
63
63
  "create-changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
64
64
  "prepare": "husky"