create-template-project 0.5.0 → 1.1.1

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,11 +22,12 @@ 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
- - **GitHub Integration:** Automatically initializes a Git repository and can create a GitHub repository using the `gh` CLI.
30
+ - **GitHub Integration:** Automatically initializes a Git repository and can create a GitHub repository (including initial commit and push) using the `gh` CLI.
30
31
  - **CI Ready:** Generates GitHub Actions workflows for automated testing and linting.
31
32
 
32
33
  ## Installation
@@ -88,9 +89,8 @@ create-template-project update --template cli
88
89
  - `-a, --author <author>`: Author name (defaults to 'git config user.name')
89
90
  - `--github-username <username>`: GitHub username (defaults to 'git config github.user')
90
91
  - `-p, --package-manager <pm>`: Package manager (`npm`, `pnpm`, `yarn`) (defaults to `pnpm`)
91
- - `--create-github-repository`: Create GitHub project (requires `gh` CLI authenticated)
92
+ - `--create-github-repository`: Create GitHub repository and push initial commit (requires `gh` CLI authenticated)
92
93
  - `--path <path>`: Output directory (mandatory)
93
- - `--install-dependencies`: Install dependencies after scaffolding
94
94
  - `--build`: Run the CI script (lint, build, test) after scaffolding
95
95
  - `--no-progress`: Do not show progress indicators
96
96
 
@@ -102,9 +102,8 @@ create-template-project update --template cli
102
102
  - `-a, --author <author>`: Author name (defaults to 'git config user.name')
103
103
  - `--github-username <username>`: GitHub username (defaults to 'git config github.user')
104
104
  - `-p, --package-manager <pm>`: Package manager (`npm`, `pnpm`, `yarn`) (defaults to `pnpm`)
105
- - `--create-github-repository`: Create GitHub project (requires `gh` CLI authenticated)
105
+ - `--create-github-repository`: Create GitHub repository and push initial commit (requires `gh` CLI authenticated)
106
106
  - `-d, --directory <path>`: Output directory (defaults to `.`)
107
- - `--install-dependencies`: Install dependencies after updating
108
107
  - `--build`: Run the CI script (lint, build, test) after updating
109
108
  - `--dev`: Run the dev server after updating
110
109
  - `--open`: Open the browser after updating
@@ -152,6 +152,10 @@
152
152
  "version": "7.13.2",
153
153
  "description": "Declarative routing for React web applications."
154
154
  },
155
+ "release-it": {
156
+ "version": "19.2.4",
157
+ "description": "Interactive release-tool for Git repositories."
158
+ },
155
159
  "cors": {
156
160
  "version": "2.8.6",
157
161
  "description": "Node.js CORS middleware."
package/dist/index.js CHANGED
@@ -32,28 +32,12 @@ var ProjectOptionsSchema = z.object({
32
32
  createGithubRepository: z.boolean().optional().default(false),
33
33
  directory: z.string(),
34
34
  update: z.boolean().optional().default(false),
35
- installDependencies: z.boolean().optional().default(false),
36
35
  build: z.boolean().optional().default(false),
37
36
  progress: z.boolean().optional().default(true)
38
37
  });
39
38
  //#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) {
39
+ //#region src/utils/templating/generic.ts
40
+ var genericProcessor = (content, { opts }) => {
57
41
  const { projectName, template, author, githubUsername } = opts;
58
42
  let description = opts.description || "";
59
43
  if (!description) switch (template) {
@@ -72,29 +56,105 @@ function processContent(filePath, content, opts, addedDeps) {
72
56
  }
73
57
  const pm = opts.packageManager || "npm";
74
58
  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/**/*\"]");
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
+ };
61
+ //#endregion
62
+ //#region src/utils/templating/github-workflow.ts
63
+ var WORKFLOW_PNPM_SETUP = ` - name: Setup pnpm
64
+ uses: pnpm/action-setup@v4
65
+ with:
66
+ version: 9
67
+ run_install: false`;
68
+ var WORKFLOW_PLAYWRIGHT_SETUP = ` - name: Install Playwright Browsers & Deps
69
+ run: npx playwright install --with-deps chromium`;
70
+ var githubWorkflowProcessor = (content, { filePath, opts }) => {
71
+ if (!filePath.includes(".github/workflows/node.js.yml")) return content;
72
+ const { template, packageManager: pm = "npm" } = opts;
73
+ let installCommand = "npm ci";
74
+ let pmSetup = "";
75
+ if (pm === "pnpm") {
76
+ installCommand = "pnpm install --frozen-lockfile";
77
+ pmSetup = WORKFLOW_PNPM_SETUP;
78
+ } else if (pm === "yarn") installCommand = "yarn install --frozen-lockfile";
79
+ let playwrightSetup = "";
80
+ if (template === "web-fullstack" || template === "web-app" || template === "web-vanilla") playwrightSetup = WORKFLOW_PLAYWRIGHT_SETUP;
81
+ let processed = content.replaceAll("{{installCommand}}", installCommand).replaceAll("# [PM_SETUP]", pmSetup).replaceAll("# [PLAYWRIGHT_SETUP]", playwrightSetup);
82
+ processed = processed.replace(/^\s*# \[PM_SETUP\]\s*\n/m, "");
83
+ processed = processed.replace(/^\s*# \[PLAYWRIGHT_SETUP\]\s*\n/m, "");
84
+ return processed;
85
+ };
86
+ //#endregion
87
+ //#region src/utils/templating/tsconfig.ts
88
+ var WEB_ENV = `/* Language and Environment */
89
+ "target": "ES2023" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
90
+ "lib": ["ES2023", "DOM", "DOM.Iterable"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
91
+ "module": "ESNext" /* Specify what module code is generated. */,
92
+ "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
93
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. */,
94
+ "resolveJsonModule": true /* Enable importing .json files. */,
95
+ "allowImportingTsExtensions": true /* Allow imports to include TypeScript file extensions. */,
96
+ "noEmit": true /* Disable emitting files from a compilation. */,
97
+ "jsx": "react-jsx" /* Specify what JSX code is generated. */,`;
98
+ var tsconfigProcessor = (content, { filePath, opts }) => {
99
+ if (filePath !== "tsconfig.json") return content;
100
+ const { template } = opts;
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 */");
103
+ if (template === "web-fullstack") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
97
104
  return processed;
105
+ };
106
+ //#endregion
107
+ //#region src/utils/templating/contributing.ts
108
+ var contributingProcessor = (content, { filePath, addedDeps }) => {
109
+ if (filePath !== "CONTRIBUTING.md" || addedDeps.length === 0) return content;
110
+ let processed = content;
111
+ processed += "\n## Dependencies\n\n";
112
+ const uniqueDeps = Array.from(new Set(addedDeps.map((d) => JSON.stringify(d)))).map((s) => JSON.parse(s));
113
+ for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
114
+ return processed;
115
+ };
116
+ //#endregion
117
+ //#region src/utils/templating/web-vanilla-html.ts
118
+ var webVanillaHtmlProcessor = (content, { filePath, opts }) => {
119
+ if (opts.template === "web-vanilla" && filePath === "index.html") return content.replace("{{scriptSrc}}", "/src/index.ts");
120
+ return content;
121
+ };
122
+ //#endregion
123
+ //#region src/utils/templating/index.ts
124
+ var processors = [
125
+ genericProcessor,
126
+ githubWorkflowProcessor,
127
+ tsconfigProcessor,
128
+ contributingProcessor,
129
+ webVanillaHtmlProcessor
130
+ ];
131
+ function processContent$1(filePath, content, opts, addedDeps) {
132
+ const context = {
133
+ filePath,
134
+ opts,
135
+ addedDeps
136
+ };
137
+ return processors.reduce((acc, processor) => processor(acc, context), content);
138
+ }
139
+ //#endregion
140
+ //#region src/utils/file.ts
141
+ var debug$3 = debugLib("create-template-project:utils:file");
142
+ function getTemplateDir(dirname, templateName) {
143
+ const sourcePath = path.resolve(dirname, "files");
144
+ const distPath = path.resolve(dirname, "templates", templateName, "files");
145
+ 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);
98
158
  }
99
159
  function mergePackageJson(target, source) {
100
160
  if (source.scripts) target.scripts = {
@@ -197,6 +257,10 @@ var getBaseTemplate = (_opts) => {
197
257
  name: "conventional-changelog",
198
258
  description: "Automated release notes."
199
259
  },
260
+ {
261
+ name: "release-it",
262
+ description: "Automated release workflow."
263
+ },
200
264
  {
201
265
  name: "debug",
202
266
  description: "Structured logging for debugging."
@@ -207,7 +271,10 @@ var getBaseTemplate = (_opts) => {
207
271
  }
208
272
  ],
209
273
  dependencies: { zod: "zod" },
210
- devDependencies: { "eslint-plugin-regexp": "" },
274
+ devDependencies: {
275
+ "eslint-plugin-regexp": "",
276
+ "release-it": ""
277
+ },
211
278
  scripts: {},
212
279
  files: [],
213
280
  templateDir: getTemplateDir(__dirname$5, "base")
@@ -277,7 +344,7 @@ var getWebVanillaTemplate = (_opts) => {
277
344
  preview: "vite preview",
278
345
  test: "vitest run",
279
346
  "test:ui": "vitest",
280
- "test:e2e": "playwright test"
347
+ "integration-test": "playwright test"
281
348
  },
282
349
  files: [],
283
350
  templateDir: getTemplateDir(__dirname$3, "web-vanilla")
@@ -343,7 +410,7 @@ var getWebAppTemplate = (_opts) => {
343
410
  preview: "vite preview",
344
411
  test: "vitest run",
345
412
  "test:ui": "vitest",
346
- "test:e2e": "playwright test",
413
+ "integration-test": "playwright test",
347
414
  start: "vite preview"
348
415
  },
349
416
  files: [],
@@ -420,7 +487,7 @@ var getWebFullstackTemplate = (_opts) => {
420
487
  build: "npm run build --workspaces",
421
488
  dev: "npm run dev --workspaces",
422
489
  test: "npm run test --workspaces",
423
- "test:e2e": "playwright test"
490
+ "integration-test": "playwright test"
424
491
  },
425
492
  files: [],
426
493
  templateDir: getTemplateDir(__dirname$1, "web-fullstack")
@@ -436,7 +503,6 @@ var MOCK_OPTS = {
436
503
  directory: ".",
437
504
  packageManager: "npm",
438
505
  update: false,
439
- installDependencies: false,
440
506
  build: false,
441
507
  progress: true,
442
508
  createGithubRepository: false
@@ -479,6 +545,17 @@ var getAllTemplatesInfo = () => {
479
545
  //#endregion
480
546
  //#region src/cli.ts
481
547
  var pathExists$1 = (p) => fs.access(p).then(() => true).catch(() => false);
548
+ var stripQuotes = (str) => {
549
+ if (typeof str !== "string") return str;
550
+ let result = str.trim();
551
+ 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();
555
+ else break;
556
+ }
557
+ return result;
558
+ };
482
559
  var getDefaultAuthor = async () => {
483
560
  try {
484
561
  const { stdout } = await execa("git", ["config", "user.name"]);
@@ -548,7 +625,7 @@ Templates:
548
625
  p.outro("Use \"create\" to scaffold a new project.");
549
626
  process.exit(0);
550
627
  });
551
- 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 project").requiredOption("--path <path>", "Output directory").option("--install-dependencies", "Install dependencies after scaffolding", false).option("--build", "Run the CI script (lint, build, test) after scaffolding", false).option("--no-progress", "Do not show progress indicators").action(async (opts) => {
628
+ 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) => {
552
629
  debug$2("Executing \"create\" command with options: %O", opts);
553
630
  commandResult = {
554
631
  ...opts,
@@ -576,7 +653,7 @@ Restrictions & Behavior:
576
653
  - package.json: Dependencies and scripts are merged. Existing versions are preserved unless they are missing.
577
654
  - Merging: For non-seed files, the tool attempts to merge template changes. If a conflict occurs, it will be marked with standard git conflict markers.
578
655
  - Confirmation: The command will always show a summary of proposed changes (ADD, MODIFY) and ask for your confirmation before applying them.
579
- `).option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").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 project").option("-d, --directory <path>", "Output directory", ".").option("--install-dependencies", "Install dependencies after scaffolding", false).option("--build", "Run the CI script (lint, build, test) after updating", false).option("--dev", "Run the dev server after scaffolding", false).option("--open", "Open the browser after scaffolding", false).option("--no-progress", "Do not show progress indicators").action(async (opts) => {
656
+ `).option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").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").option("-d, --directory <path>", "Output directory", ".").option("--build", "Run the CI script (lint, build, test) after updating", false).option("--dev", "Run the dev server after scaffolding", false).option("--open", "Open the browser after scaffolding", false).option("--no-progress", "Do not show progress indicators").action(async (opts) => {
580
657
  debug$2("Executing \"update\" command with options: %O", opts);
581
658
  const directory = path.resolve(opts.directory);
582
659
  const pkgPath = path.join(directory, "package.json");
@@ -773,29 +850,16 @@ Restrictions & Behavior:
773
850
  process.exit(0);
774
851
  }
775
852
  }
776
- const installDependenciesRes = await p.confirm({
777
- message: "Should we install dependencies?",
853
+ const build = await p.confirm({
854
+ message: "Should we run the CI script (lint, build, test)?",
778
855
  initialValue: true
779
856
  });
780
- if (p.isCancel(installDependenciesRes)) {
857
+ if (p.isCancel(build)) {
781
858
  p.cancel("Operation cancelled.");
782
859
  process.exit(0);
783
860
  }
784
- const installDependencies = installDependenciesRes;
785
- let build = false;
786
- if (installDependencies) {
787
- const res = await p.confirm({
788
- message: "Should we run the CI script (lint, build, test)?",
789
- initialValue: true
790
- });
791
- if (p.isCancel(res)) {
792
- p.cancel("Operation cancelled.");
793
- process.exit(0);
794
- }
795
- build = res;
796
- }
797
861
  const createGithubRepositoryRes = await p.confirm({
798
- message: "Should we create a GitHub repository?",
862
+ message: "Create GitHub repository and push initial commit?",
799
863
  initialValue: false
800
864
  });
801
865
  if (p.isCancel(createGithubRepositoryRes)) {
@@ -813,7 +877,6 @@ Restrictions & Behavior:
813
877
  createGithubRepository: createGithubRepositoryRes,
814
878
  directory: projectDir,
815
879
  update,
816
- installDependencies,
817
880
  build,
818
881
  progress: true
819
882
  };
@@ -835,6 +898,13 @@ Restrictions & Behavior:
835
898
  p.cancel("Unknown command or missing options.");
836
899
  process.exit(1);
837
900
  }
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);
838
908
  debug$2("Validating command result with Zod");
839
909
  const validationResult = ProjectOptionsSchema.safeParse(commandResult);
840
910
  if (!validationResult.success) {
@@ -848,7 +918,6 @@ Restrictions & Behavior:
848
918
  p.cancel(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
849
919
  process.exit(1);
850
920
  }
851
- if (commandResult.build) commandResult.installDependencies = true;
852
921
  return commandResult;
853
922
  };
854
923
  //#endregion
@@ -940,6 +1009,7 @@ var generateProject = async (opts) => {
940
1009
  let finalPkg = {
941
1010
  name: projectName,
942
1011
  version: "0.1.0",
1012
+ private: true,
943
1013
  description: opts.description || "TODO: Add project description",
944
1014
  keywords: opts.keywords ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
945
1015
  homepage: `https://github.com/${githubUsername}/${projectName}#readme`,
@@ -1007,7 +1077,8 @@ var generateProject = async (opts) => {
1007
1077
  if (isUpdate && isSeedFile(relativePath)) {
1008
1078
  actions.push({
1009
1079
  type: "SKIP",
1010
- path: relativePath
1080
+ path: relativePath,
1081
+ reason: "Seed file - skipped during update to preserve manual changes"
1011
1082
  });
1012
1083
  continue;
1013
1084
  }
@@ -1015,7 +1086,8 @@ var generateProject = async (opts) => {
1015
1086
  if (isUpdate && await pathExists(targetPath)) {
1016
1087
  actions.push({
1017
1088
  type: "DELETE",
1018
- path: relativePath
1089
+ path: relativePath,
1090
+ reason: "File no longer required for this template type"
1019
1091
  });
1020
1092
  pendingOperations.push(async () => {
1021
1093
  await fs.rm(targetPath, { force: true });
@@ -1035,21 +1107,36 @@ var generateProject = async (opts) => {
1035
1107
  if (isUpdate && exists) {
1036
1108
  const existingContent = await fs.readFile(finalTargetPath, "utf8");
1037
1109
  if (existingContent.trim() !== content.trim()) {
1038
- actions.push({
1110
+ const action = {
1039
1111
  type: "MODIFY",
1040
- path: finalRelativePath
1041
- });
1112
+ path: finalRelativePath,
1113
+ reason: "Template tooling or configuration update"
1114
+ };
1115
+ actions.push(action);
1042
1116
  pendingOperations.push(async () => {
1043
1117
  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}`);
1118
+ if (result === "merged") {
1119
+ action.type = "MERGE";
1120
+ action.reason = "Merged template updates with your manual changes";
1121
+ action.recommendedAction = "Review changes for correct integration";
1122
+ log.info(`ℹ Merged: ${finalRelativePath}`);
1123
+ } else if (result === "conflict") {
1124
+ action.type = "CONFLICT";
1125
+ action.reason = "Conflicting changes between template and your code";
1126
+ action.recommendedAction = "Resolve git conflict markers in this file";
1127
+ log.warn(`⚠ Conflict: ${finalRelativePath}`);
1128
+ } else if (result === "updated") {
1129
+ action.type = "UPDATED";
1130
+ action.reason = "File was updated to the latest template version";
1131
+ log.info(`✔ Updated: ${finalRelativePath}`);
1132
+ }
1047
1133
  });
1048
1134
  }
1049
1135
  } else if (!exists) {
1050
1136
  actions.push({
1051
1137
  type: "ADD",
1052
- path: finalRelativePath
1138
+ path: finalRelativePath,
1139
+ reason: "New template file added"
1053
1140
  });
1054
1141
  pendingOperations.push(async () => {
1055
1142
  await fs.mkdir(path.dirname(finalTargetPath), { recursive: true });
@@ -1065,7 +1152,8 @@ var generateProject = async (opts) => {
1065
1152
  if (isUpdate && isSeedFile(file.path)) {
1066
1153
  actions.push({
1067
1154
  type: "SKIP",
1068
- path: file.path
1155
+ path: file.path,
1156
+ reason: "Seed file - skipped during update to preserve manual changes"
1069
1157
  });
1070
1158
  continue;
1071
1159
  }
@@ -1073,7 +1161,8 @@ var generateProject = async (opts) => {
1073
1161
  if (isUpdate && await pathExists(targetPath)) {
1074
1162
  actions.push({
1075
1163
  type: "DELETE",
1076
- path: file.path
1164
+ path: file.path,
1165
+ reason: "File no longer required for this template type"
1077
1166
  });
1078
1167
  pendingOperations.push(async () => {
1079
1168
  await fs.rm(targetPath, { force: true });
@@ -1087,21 +1176,36 @@ var generateProject = async (opts) => {
1087
1176
  if (isUpdate && exists) {
1088
1177
  const existingContent = await fs.readFile(targetPath, "utf8");
1089
1178
  if (existingContent.trim() !== content.trim()) {
1090
- actions.push({
1179
+ const action = {
1091
1180
  type: "MODIFY",
1092
- path: file.path
1093
- });
1181
+ path: file.path,
1182
+ reason: "Template configuration update"
1183
+ };
1184
+ actions.push(action);
1094
1185
  pendingOperations.push(async () => {
1095
1186
  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}`);
1187
+ if (result === "merged") {
1188
+ action.type = "MERGE";
1189
+ action.reason = "Merged template updates with your manual changes";
1190
+ action.recommendedAction = "Review changes for correct integration";
1191
+ log.info(`ℹ Merged: ${file.path}`);
1192
+ } else if (result === "conflict") {
1193
+ action.type = "CONFLICT";
1194
+ action.reason = "Conflicting changes between template and your code";
1195
+ action.recommendedAction = "Resolve git conflict markers in this file";
1196
+ log.warn(`⚠ Conflict: ${file.path}`);
1197
+ } else if (result === "updated") {
1198
+ action.type = "UPDATED";
1199
+ action.reason = "File was updated to the latest template version";
1200
+ log.info(`✔ Updated: ${file.path}`);
1201
+ }
1099
1202
  });
1100
1203
  }
1101
1204
  } else if (!exists) {
1102
1205
  actions.push({
1103
1206
  type: "ADD",
1104
- path: file.path
1207
+ path: file.path,
1208
+ reason: "New template file added"
1105
1209
  });
1106
1210
  pendingOperations.push(async () => {
1107
1211
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
@@ -1124,7 +1228,8 @@ var generateProject = async (opts) => {
1124
1228
  if (workspaceChanged) {
1125
1229
  actions.push({
1126
1230
  type: workspaceExists ? "MODIFY" : "ADD",
1127
- path: "pnpm-workspace.yaml"
1231
+ path: "pnpm-workspace.yaml",
1232
+ reason: "Updated workspace configuration for pnpm"
1128
1233
  });
1129
1234
  pendingOperations.push(async () => {
1130
1235
  await fs.writeFile(workspacePath, workspaceYaml);
@@ -1142,11 +1247,13 @@ var generateProject = async (opts) => {
1142
1247
  if (pkgChanged) {
1143
1248
  if (isUpdate) actions.push({
1144
1249
  type: "MODIFY",
1145
- path: "package.json"
1250
+ path: "package.json",
1251
+ reason: "Updated dependencies and scripts to match latest template"
1146
1252
  });
1147
1253
  else actions.push({
1148
1254
  type: "ADD",
1149
- path: "package.json"
1255
+ path: "package.json",
1256
+ reason: "Initial project configuration"
1150
1257
  });
1151
1258
  pendingOperations.push(async () => {
1152
1259
  debug$1("Writing final consolidated package.json to: %s", pkgPath);
@@ -1175,7 +1282,7 @@ var generateProject = async (opts) => {
1175
1282
  githubSkipped: !opts.createGithubRepository || isUpdate,
1176
1283
  githubError: "",
1177
1284
  depsInstalled: false,
1178
- depsSkipped: !opts.installDependencies,
1285
+ depsSkipped: !opts.build,
1179
1286
  ciRun: false,
1180
1287
  ciSkipped: !opts.build || !finalPkg.scripts.ci
1181
1288
  };
@@ -1197,32 +1304,7 @@ var generateProject = async (opts) => {
1197
1304
  log.error(`Failed to initialize Git: ${e.message}${detail}`);
1198
1305
  }
1199
1306
  } else states.gitInitialized = true;
1200
- if (opts.createGithubRepository && !isUpdate) {
1201
- debug$1("Creating GitHub repository");
1202
- try {
1203
- debug$1("Executing: gh repo create %s --public --source=. --remote=origin", projectName);
1204
- await execa("gh", [
1205
- "repo",
1206
- "create",
1207
- projectName,
1208
- "--public",
1209
- "--source=.",
1210
- "--remote=origin"
1211
- ], {
1212
- cwd: projectDir,
1213
- stdio,
1214
- preferLocal: true
1215
- });
1216
- log.success("Created GitHub repository (gh repo create).");
1217
- states.githubCreated = true;
1218
- } catch (e) {
1219
- debug$1("Failed to create GitHub repository: %O", e);
1220
- const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1221
- log.warn(`Failed to create GitHub repository: ${e.message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
1222
- states.githubError = e.message;
1223
- }
1224
- }
1225
- if (opts.installDependencies) {
1307
+ if (opts.build) {
1226
1308
  debug$1("Installing dependencies using %s", pm);
1227
1309
  const s = spinner();
1228
1310
  s.start(`Installing dependencies using ${pm}...`);
@@ -1281,6 +1363,51 @@ var generateProject = async (opts) => {
1281
1363
  throw new Error(`Failed to run CI script: ${e.message}${detail}`);
1282
1364
  }
1283
1365
  }
1366
+ if (opts.createGithubRepository && !isUpdate) {
1367
+ debug$1("Creating and pushing GitHub repository");
1368
+ const s = spinner();
1369
+ s.start("Creating and pushing GitHub repository...");
1370
+ try {
1371
+ debug$1("Executing: git add .");
1372
+ await execa("git", ["add", "."], {
1373
+ cwd: projectDir,
1374
+ stdio,
1375
+ preferLocal: true
1376
+ });
1377
+ debug$1("Executing: git commit -m \"chore: initial scaffold\"");
1378
+ await execa("git", [
1379
+ "commit",
1380
+ "-m",
1381
+ "chore: initial scaffold"
1382
+ ], {
1383
+ cwd: projectDir,
1384
+ stdio,
1385
+ preferLocal: true
1386
+ });
1387
+ debug$1("Executing: gh repo create %s --public --source=. --remote=origin --push", projectName);
1388
+ await execa("gh", [
1389
+ "repo",
1390
+ "create",
1391
+ projectName,
1392
+ "--public",
1393
+ "--source=.",
1394
+ "--remote=origin",
1395
+ "--push"
1396
+ ], {
1397
+ cwd: projectDir,
1398
+ stdio,
1399
+ preferLocal: true
1400
+ });
1401
+ s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Created GitHub repository and pushed initial commit.`);
1402
+ states.githubCreated = true;
1403
+ } catch (e) {
1404
+ debug$1("Failed to create/push GitHub repository: %O", e);
1405
+ 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;
1409
+ }
1410
+ }
1284
1411
  let hasErrors = false;
1285
1412
  let hasWarnings = false;
1286
1413
  const errorMessages = [];
@@ -1292,12 +1419,12 @@ var generateProject = async (opts) => {
1292
1419
  hasErrors,
1293
1420
  hasWarnings,
1294
1421
  errorMessages
1295
- });
1422
+ }, actions);
1296
1423
  const successMsg = `Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}. A detailed setup guide has been generated at GENERATED.md`;
1297
1424
  if (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
1298
1425
  else log.success(successMsg);
1299
1426
  };
1300
- async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status) {
1427
+ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status, actions) {
1301
1428
  const statusBadge = status.hasErrors ? "🔴 **Completed with Errors**" : status.hasWarnings ? "🟡 **Completed with Warnings**" : "🟢 **Successfully Completed**";
1302
1429
  const md = [
1303
1430
  `# 🚀 Project Setup Guide: ${opts.projectName}`,
@@ -1319,14 +1446,49 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1319
1446
  "---",
1320
1447
  "",
1321
1448
  "## 📋 Initialization Checklist",
1322
- "The following tasks were executed during the generation process:",
1449
+ isUpdate ? "The project was updated with the latest template changes:" : "The following tasks were executed during the generation process:",
1323
1450
  `- [x] Scaffold project files and directories`,
1324
1451
  `- [x] Configure \`package.json\` with appropriate dependencies`,
1325
1452
  `- [${states.depsInstalled ? "x" : " "}] Install dependencies using \`${pm}\`${states.depsSkipped ? " *(Skipped)*" : ""}`,
1326
1453
  `- [${states.gitInitialized ? "x" : " "}] Initialize Git repository`,
1327
- `- [${states.githubCreated ? "x" : " "}] Create GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
1454
+ `- [${states.githubCreated ? "x" : " "}] Create and push GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
1328
1455
  `- [${states.ciRun ? "x" : " "}] Run initial CI pipeline (lint, build, test)${states.ciSkipped ? " *(Skipped)*" : ""}`,
1329
1456
  "",
1457
+ ...isUpdate ? [
1458
+ "### 🛠️ Upgrade Details",
1459
+ "The following files were affected by this update:",
1460
+ "",
1461
+ "| File Path | Action | Reason | Next Steps |",
1462
+ "| :--- | :--- | :--- | :--- |",
1463
+ ...actions.filter((a) => a.type !== "SKIP").map((a) => {
1464
+ const actionIcon = {
1465
+ ADD: "➕ ADD",
1466
+ MODIFY: "📝 MODIFY",
1467
+ MERGE: "🔀 MERGE",
1468
+ CONFLICT: "🔥 CONFLICT",
1469
+ DELETE: "🗑️ DELETE",
1470
+ UPDATED: "✨ UPDATED"
1471
+ }[a.type] || a.type;
1472
+ return `| \`${a.path}\` | ${actionIcon} | ${a.reason || "-"} | ${a.recommendedAction || (a.type === "CONFLICT" ? "**Resolve conflicts**" : "Review changes")} |`;
1473
+ }),
1474
+ ""
1475
+ ] : [],
1476
+ "---",
1477
+ "",
1478
+ "## 🛠️ Manual Adjustments Needed",
1479
+ "To complete your project setup, please review and manually update the following:",
1480
+ "- [ ] **`LICENSE`**: Verify the copyright year and author name.",
1481
+ "- [ ] **`package.json`**: Review the description, keywords, author, and repository links.",
1482
+ "- [ ] **`README.md`**: Update with project-specific instructions, architecture details, and contribution guidelines.",
1483
+ "- [ ] **`.gitignore`**: Note that there is a **`# Custom`** section at the end of the file for your own ignores.",
1484
+ "",
1485
+ "---",
1486
+ "",
1487
+ "## 💡 Next Steps",
1488
+ "1. Review the generated codebase to familiarize yourself with the structure.",
1489
+ `2. Start the development server using \`${pm} run dev\`.`,
1490
+ "3. Make your first commit and push to your remote repository.",
1491
+ "",
1330
1492
  "---",
1331
1493
  "",
1332
1494
  "## 💻 Available Commands",
@@ -1386,35 +1548,20 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
1386
1548
  "| `gh pr checkout <pr-number>` | Checkout a Pull Request branch locally |",
1387
1549
  "| `gh issue create` | Create a new Issue |",
1388
1550
  "| `gh issue list` | List all open Issues |",
1389
- "| `gh repo delete <owner>/<repo> --confirm` | Dangerously delete a repository completely (use with caution!) |",
1551
+ "| `gh repo delete <owner>/<repo> --yes` | Dangerously delete a repository completely (use with caution!) |",
1390
1552
  "",
1391
1553
  "---",
1392
1554
  "",
1393
1555
  "## 🚀 Creating a Release",
1394
1556
  "This project uses Conventional Commits and automated changelogs. To create a new release:",
1395
- "1. **Verify build/tests:** `pnpm run ci`",
1396
- "2. **Bump version:** `pnpm version <patch|minor|major> --no-git-tag-version`",
1397
- "3. **Update changelog:** `pnpm run create-changelog`",
1398
- "4. **Commit changes:** `git add . && git commit -m \"chore(release): $(node -p 'require(\"./package.json\").version')\"`",
1399
- "5. **Tag & Push:** `git tag v$(node -p 'require(\"./package.json\").version') && git push && git push --tags`",
1400
- "6. **Create GitHub Release:** `gh release create v$(node -p 'require(\"./package.json\").version') --generate-notes`",
1401
- "7. **Publish (if applicable):** `pnpm publish`",
1557
+ "1. **Run the release command:** `pnpm release`",
1402
1558
  "",
1403
- "---",
1559
+ "This single command will automatically run your CI suite, bump the version, generate a changelog, create a Git tag, push to GitHub, and create a GitHub release.",
1404
1560
  "",
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.",
1561
+ "**Note:** NPM publishing is disabled by default. See `CONTRIBUTING.md` for details on how to enable it.",
1410
1562
  "",
1411
1563
  "---",
1412
1564
  "",
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
1565
  "<br>",
1419
1566
  "<p align=\"center\"><i>This file was auto-generated by <b>create-template-project</b>.</i></p>"
1420
1567
  ].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
@@ -0,0 +1,23 @@
1
+ {
2
+ "npm": {
3
+ "publish": false
4
+ },
5
+ "git": {
6
+ "requireCleanWorkingDir": true,
7
+ "commit": true,
8
+ "commitMessage": "chore(release): ${version}",
9
+ "tag": true,
10
+ "tagName": "v${version}",
11
+ "push": true,
12
+ "pushArgs": ["--follow-tags"]
13
+ },
14
+ "github": {
15
+ "release": true,
16
+ "autoGenerate": true
17
+ },
18
+ "hooks": {
19
+ "before:init": "pnpm run ci",
20
+ "after:bump": "pnpm run create-changelog",
21
+ "before:git:commit": "git add CHANGELOG.md"
22
+ }
23
+ }
@@ -1,9 +1,86 @@
1
1
  # Agent Guidelines: {{projectName}}
2
2
 
3
- Build/Lint/Test:
3
+ This document provides essential instructions for autonomous agents (like Cursor, GitHub Copilot, or CLI agents) working on the `{{projectName}}` repository.
4
+
5
+ ## Build/Lint/Test
6
+
4
7
  - `pnpm run ci`: Runs lint, build, and all tests.
5
8
  - `pnpm run dev`: Starts the development server.
6
- - `pnpm run test`: Runs unit tests (browser-based for web projects).
7
- - `pnpm run test:e2e`: Runs Playwright E2E tests.
9
+ - `pnpm run test`: Runs unit tests.
10
+ - `pnpm run integration-test`: Runs integration tests.
8
11
  - `pnpm exec vitest <file>`: Runs a specific test file.
9
12
  - `pnpm run lint`: Lints and formats the codebase (oxlint + oxfmt).
13
+
14
+ ## Project Overview
15
+
16
+ A modern project built with {{projectName}}, emphasizing type safety, performance, and best practices.
17
+
18
+ ## Build, Lint, and Test Commands
19
+
20
+ ### Core Commands
21
+
22
+ - **Install dependencies:** `pnpm install`
23
+ - **Build project:** `pnpm run build`
24
+ - **Lint code:** `pnpm run lint` (runs `tsc`, `oxlint`, and `oxfmt`)
25
+ - **Run all tests:** `pnpm run test` (uses `vitest` with coverage)
26
+ - **Run CI suite:** `pnpm run ci` (lint + build + test)
27
+ - **Run integration tests:** `pnpm run integration-test`
28
+
29
+ ### Targeted Testing
30
+
31
+ - **Run a single test file:** `pnpm exec vitest src/index.test.ts`
32
+ - **Run tests matching a pattern:** `pnpm exec vitest -t "feature"`
33
+ - **Watch mode:** `pnpm exec vitest`
34
+
35
+ ## Code Style & Conventions
36
+
37
+ ### Language & Runtime
38
+
39
+ - **TypeScript:** Strict mode is mandatory. Use explicit types for function boundaries.
40
+ - **Node.js:** Targets >= 22.0.0.
41
+ - **ESM:** The project uses ES Modules (`"type": "module"` in `package.json`).
42
+
43
+ ### Imports
44
+
45
+ - **Extensions:** Always use `.js` extensions in relative imports (e.g., `import { main } from './lib.js';`) to comply with ESM requirements in Node.js, even though source files are `.ts`.
46
+ - **Built-ins:** Use the `node:` prefix for built-in modules (e.g., `import path from 'node:path';`).
47
+
48
+ ### Formatting
49
+
50
+ - **Tooling:** oxfmt is enforced via `pnpm run lint`.
51
+ - **Indentation:** Tabs are used for indentation.
52
+ - **Quotes:** Single quotes for strings, except when double quotes prevent escaping.
53
+
54
+ ### Architecture & Types
55
+
56
+ - **Modularity:** Keep logic modularized.
57
+ - **Type Safety:** Use `zod` for runtime schema validation and `z.infer` for type definitions.
58
+ - **Immutability:** Prefer `readonly` and `const` where possible to ensure data integrity.
59
+
60
+ ### Naming Conventions
61
+
62
+ - **Variables/Functions:** `camelCase`.
63
+ - **Constants:** `UPPER_SNAKE_CASE`.
64
+ - **Files:** `kebab-case.ts`.
65
+
66
+ ### Error Handling
67
+
68
+ - **CLI Errors:** Throw descriptive errors.
69
+ - **Safety:** Always use `try...finally` or `using` (if applicable) to ensure resources like file handles or connections are closed.
70
+ - **Validation:** Validate all user inputs and CLI arguments using Zod schemas before processing.
71
+
72
+ ## Mandatory Completion Protocol (Definition of Done)
73
+
74
+ No task involving code changes is considered complete until the agent provides the following "Evidence of Done" in its final response:
75
+
76
+ 1. **CI Verification:** The agent MUST run `pnpm run ci` and include the full terminal output (showing `Found 0 warnings and 0 errors` and `Tests passed`).
77
+ 2. **Test Coverage:** The agent MUST ensure that all new code is covered by tests and all existing tests pass.
78
+
79
+ **Failure to provide these logs means the task is INCOMPLETE.** The user is encouraged to reject any response that lacks this section.
80
+
81
+ ## Git Workflow
82
+
83
+ - **Conventional Commits:** Adhere to the specification (e.g., `feat:`, `fix:`, `chore:`, `test:`).
84
+ - **Hooks:** Husky runs `pnpm run ci` on pre-commit. Do not bypass hooks.
85
+ - **Branches:** Use descriptive branch names like `feat/feature-name` or `fix/issue-description`.
86
+
@@ -21,11 +21,22 @@ We follow the **Conventional Commits** specification. This is **enforced** by `c
21
21
 
22
22
  ## Release Process
23
23
 
24
- 1. **Verify**: `pnpm run ci`
25
- 2. **Bump Version**: `pnpm version <patch|minor|major> --no-git-tag-version`
26
- 3. **Update Changelog**: `pnpm run create-changelog`
27
- 4. **Commit**: `git add . && git commit -m "chore(release): $(node -p 'require("./package.json").version')"`
28
- 5. **Tag & Push**: `git tag v$(node -p 'require("./package.json").version') && git push && git push --tags`
29
- 6. **Create GitHub Release**: `gh release create v$(node -p 'require("./package.json").version') --generate-notes`
30
- 7. **Publish**: `pnpm publish`
24
+ To release a new version:
25
+
26
+ ```sh
27
+ pnpm release -- patch # or minor / major
28
+ ```
29
+
30
+ This will automatically:
31
+ 1. Run the CI suite (`pnpm run ci`).
32
+ 2. Bump the version in `package.json`.
33
+ 3. Update the `CHANGELOG.md`.
34
+ 4. Commit, tag, and push the changes.
35
+ 5. Create a GitHub release with auto-generated notes.
36
+ 6. Publish to npm (if configured).
37
+
38
+ **Note:** NPM publishing is **disabled** by default for new projects. To enable it:
39
+ 1. Set `"private": false` in `package.json`.
40
+ 2. Set `"publish": true` in `.release-it.json`.
41
+ 3. Ensure you have the necessary `NPM_TOKEN` configured.
31
42
 
@@ -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
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
3
  "version": "0.1.0",
4
+ "private": true,
4
5
  "type": "module",
5
6
  "scripts": {
6
7
  "lint": "tsc && oxlint --disable-nested-config src client server tests *.config.ts && npm run format:check",
7
8
  "format": "oxfmt --write .",
8
9
  "format:check": "oxfmt --check .",
9
10
  "test": "vitest run --coverage",
11
+ "release": "release-it",
10
12
  "ci": "npm run lint && npm run build && npm run test",
11
13
  "create-changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
12
14
  "prepare": "husky"
@@ -25,6 +27,7 @@
25
27
  "oxlint": "",
26
28
  "oxlint-tsgolint": "",
27
29
  "oxfmt": "",
30
+ "release-it": "",
28
31
  "eslint-plugin-regexp": "",
29
32
  "typescript": "",
30
33
  "vitest": ""
@@ -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,7 @@
1
1
  {
2
2
  "name": "create-template-project",
3
- "version": "0.5.0",
3
+ "version": "1.1.1",
4
+ "private": false,
4
5
  "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
6
  "keywords": [
6
7
  "boilerplate",
@@ -58,10 +59,11 @@
58
59
  "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
60
  "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
61
  "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",
62
+ "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
63
  "ci": "pnpm run lint && pnpm run build && pnpm run test",
63
64
  "create-changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
64
- "prepare": "husky"
65
+ "prepare": "husky",
66
+ "release": "release-it"
65
67
  },
66
68
  "dependencies": {
67
69
  "@clack/prompts": "1.1.0",
@@ -80,10 +82,11 @@
80
82
  "conventional-changelog-angular": "8.3.0",
81
83
  "eslint-plugin-regexp": "3.1.0",
82
84
  "husky": "9.1.7",
83
- "oxfmt": "0.41.0",
84
- "oxlint": "1.56.0",
85
- "oxlint-tsgolint": "0.17.1",
85
+ "oxfmt": "0.42.0",
86
+ "oxlint": "1.57.0",
87
+ "oxlint-tsgolint": "0.17.3",
86
88
  "pnpm": "10.32.1",
89
+ "release-it": "19.2.4",
87
90
  "rimraf": "6.1.3",
88
91
  "typescript": "5.9.3",
89
92
  "vite": "8.0.2",