create-template-project 0.4.0 โ†’ 0.5.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/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import { Command } from "commander";
3
3
  import * as p from "@clack/prompts";
4
4
  import { cancel, intro, outro } from "@clack/prompts";
5
5
  import { z } from "zod";
6
+ import { execa } from "execa";
6
7
  import path from "node:path";
7
8
  import fs from "node:fs/promises";
8
9
  import debugLib from "debug";
9
10
  import { fileURLToPath } from "node:url";
10
11
  import { existsSync } from "node:fs";
11
- import { execa } from "execa";
12
12
  //#region src/types.ts
13
13
  var TemplateTypeSchema = z.enum([
14
14
  "cli",
@@ -24,15 +24,16 @@ var PackageManagerSchema = z.enum([
24
24
  var ProjectOptionsSchema = z.object({
25
25
  template: TemplateTypeSchema,
26
26
  projectName: z.string().min(1, "Project name is required"),
27
+ description: z.string().optional(),
28
+ keywords: z.string().optional(),
29
+ author: z.string().min(1, "Author name is required"),
30
+ githubUsername: z.string().min(1, "GitHub username is required"),
27
31
  packageManager: PackageManagerSchema.optional().default("npm"),
28
32
  createGithubRepository: z.boolean().optional().default(false),
29
33
  directory: z.string(),
30
- overwrite: z.boolean().optional().default(false),
31
34
  update: z.boolean().optional().default(false),
32
35
  installDependencies: z.boolean().optional().default(false),
33
36
  build: z.boolean().optional().default(false),
34
- dev: z.boolean().optional().default(false),
35
- open: z.boolean().optional().default(false),
36
37
  progress: z.boolean().optional().default(true)
37
38
  });
38
39
  //#endregion
@@ -53,9 +54,9 @@ async function getAllFiles(dirPath, arrayOfFiles = []) {
53
54
  return arrayOfFiles;
54
55
  }
55
56
  function processContent(filePath, content, opts, addedDeps) {
56
- const { projectName, template } = opts;
57
- let description = "";
58
- switch (template) {
57
+ const { projectName, template, author, githubUsername } = opts;
58
+ let description = opts.description || "";
59
+ if (!description) switch (template) {
59
60
  case "cli":
60
61
  description = "A modern Node.js CLI application with TypeScript and automated tooling.";
61
62
  break;
@@ -70,7 +71,8 @@ function processContent(filePath, content, opts, addedDeps) {
70
71
  break;
71
72
  }
72
73
  const pm = opts.packageManager || "npm";
73
- let processed = content.replaceAll("{{projectName}}", projectName).replaceAll("{{description}}", description).replaceAll("{{packageManager}}", pm);
74
+ 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);
74
76
  if (filePath.includes(".github/workflows/node.js.yml")) {
75
77
  let installCommand = "npm ci";
76
78
  let pmSetup = "";
@@ -121,8 +123,9 @@ function isSeedFile(filePath) {
121
123
  "index.html",
122
124
  "App.tsx",
123
125
  "main.tsx",
124
- "index.tsx"
125
- ].some((file) => filePath === file);
126
+ "index.tsx",
127
+ "LICENSE"
128
+ ].some((file) => filePath === file) || filePath.toLowerCase().endsWith(".md");
126
129
  }
127
130
  async function mergeFile(filePath, existing, template, log) {
128
131
  debug$3("Merging file: %s", filePath);
@@ -145,7 +148,7 @@ async function mergeFile(filePath, existing, template, log) {
145
148
  });
146
149
  return (await fs.readFile(filePath, "utf8")).trim() !== template.trim() ? "merged" : "updated";
147
150
  } catch (e) {
148
- if (e.exitCode === 1) return "conflict";
151
+ if (e.exitCode >= 1 && e.exitCode < 128) return "conflict";
149
152
  else {
150
153
  debug$3("Git merge-file failed: %O", e);
151
154
  const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
@@ -175,8 +178,8 @@ var getBaseTemplate = (_opts) => {
175
178
  description: "Ultra-fast Rust-based linter."
176
179
  },
177
180
  {
178
- name: "Prettier",
179
- description: "Consistent code formatting."
181
+ name: "oxfmt",
182
+ description: "High performance code formatting."
180
183
  },
181
184
  {
182
185
  name: "Vitest",
@@ -428,14 +431,13 @@ var getWebFullstackTemplate = (_opts) => {
428
431
  var MOCK_OPTS = {
429
432
  template: "cli",
430
433
  projectName: "mock",
434
+ author: "mock",
435
+ githubUsername: "mock",
431
436
  directory: ".",
432
437
  packageManager: "npm",
433
- overwrite: false,
434
438
  update: false,
435
439
  installDependencies: false,
436
440
  build: false,
437
- dev: false,
438
- open: false,
439
441
  progress: true,
440
442
  createGithubRepository: false
441
443
  };
@@ -477,6 +479,22 @@ var getAllTemplatesInfo = () => {
477
479
  //#endregion
478
480
  //#region src/cli.ts
479
481
  var pathExists$1 = (p) => fs.access(p).then(() => true).catch(() => false);
482
+ var getDefaultAuthor = async () => {
483
+ try {
484
+ const { stdout } = await execa("git", ["config", "user.name"]);
485
+ return stdout.trim();
486
+ } catch (e) {
487
+ return "";
488
+ }
489
+ };
490
+ var getDefaultGithubUsername = async () => {
491
+ try {
492
+ const { stdout } = await execa("git", ["config", "github.user"]);
493
+ return stdout.trim();
494
+ } catch (e) {
495
+ return "";
496
+ }
497
+ };
480
498
  var debug$2 = debugLib("create-template-project:cli");
481
499
  var parseArgs = async () => {
482
500
  debug$2("Parsing CLI arguments: %O", process.argv);
@@ -530,17 +548,20 @@ Templates:
530
548
  p.outro("Use \"create\" to scaffold a new project.");
531
549
  process.exit(0);
532
550
  });
533
- 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("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "npm").option("--create-github-repository", "Create GitHub project").option("-d, --directory <path>", "Output directory", ".").option("--overwrite", "Overwrite existing directory by removing it first", false).option("--install-dependencies", "Install dependencies after scaffolding", false).option("--build", "Run the CI script (lint, build, test) after scaffolding", 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((opts) => {
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) => {
534
552
  debug$2("Executing \"create\" command with options: %O", opts);
535
553
  commandResult = {
536
554
  ...opts,
537
555
  update: false,
538
556
  template: opts.template,
539
557
  projectName: opts.name,
558
+ description: opts.description,
559
+ keywords: opts.keywords,
560
+ author: opts.author || await getDefaultAuthor(),
561
+ githubUsername: opts.githubUsername || await getDefaultGithubUsername(),
540
562
  packageManager: opts.packageManager,
541
- directory: path.resolve(opts.directory),
563
+ directory: path.resolve(opts.path),
542
564
  createGithubRepository: !!opts.createGithubRepository,
543
- overwrite: !!opts.overwrite,
544
565
  progress: !!opts.progress
545
566
  };
546
567
  debug$2("Processed \"create\" options: %O", commandResult);
@@ -551,21 +572,45 @@ Details:
551
572
  It intelligently merges changes into your existing files using 'git merge-file'.
552
573
 
553
574
  Restrictions & Behavior:
554
- - Seed Files: Files in 'src/', 'client/src/', etc., are considered "seed" files and are NEVER overwritten or modified during an update to protect your application logic.
575
+ - Seed Files: Files in 'src/', 'client/src/', etc., and ALL markdown files (*.md) are considered "seed" files and are NEVER overwritten or modified during an update to protect your application logic and documentation.
555
576
  - package.json: Dependencies and scripts are merged. Existing versions are preserved unless they are missing.
556
577
  - 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.
557
578
  - Confirmation: The command will always show a summary of proposed changes (ADD, MODIFY) and ask for your confirmation before applying them.
558
- `).option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").option("-n, --name <name>", "Project name").option("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "npm").option("--create-github-repository", "Create GitHub project").option("-d, --directory <path>", "Output directory", ".").option("--overwrite", "Overwrite existing directory by removing it first", false).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((opts) => {
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) => {
559
580
  debug$2("Executing \"update\" command with options: %O", opts);
581
+ const directory = path.resolve(opts.directory);
582
+ const pkgPath = path.join(directory, "package.json");
583
+ if (!await pathExists$1(pkgPath)) {
584
+ p.log.error(`No package.json found in ${directory}. The update command must be run in a project directory.`);
585
+ process.exit(1);
586
+ }
587
+ let pkg;
588
+ try {
589
+ pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
590
+ } catch (e) {
591
+ p.log.error(`Failed to read or parse package.json at ${pkgPath}: ${e.message}`);
592
+ process.exit(1);
593
+ }
594
+ if (!pkg.name) {
595
+ p.log.error(`No name property found in ${pkgPath}.`);
596
+ process.exit(1);
597
+ }
598
+ if (!pkg["create-template-project"]?.template) {
599
+ p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
600
+ process.exit(1);
601
+ }
560
602
  commandResult = {
561
603
  ...opts,
562
604
  update: true,
563
- template: opts.template,
564
- projectName: opts.name,
605
+ template: opts.template || pkg["create-template-project"]?.template,
606
+ projectName: pkg.name,
607
+ description: opts.description || pkg.description,
608
+ keywords: opts.keywords || (pkg.keywords ? pkg.keywords.join(", ") : void 0),
609
+ author: opts.author || pkg.author || await getDefaultAuthor(),
610
+ githubUsername: opts.githubUsername || pkg["create-template-project"]?.githubUsername || await getDefaultGithubUsername(),
565
611
  packageManager: opts.packageManager,
566
- directory: path.resolve(opts.directory),
612
+ directory,
567
613
  createGithubRepository: !!opts.createGithubRepository,
568
- overwrite: !!opts.overwrite,
569
614
  progress: !!opts.progress
570
615
  };
571
616
  debug$2("Processed \"update\" options: %O", commandResult);
@@ -595,38 +640,84 @@ Restrictions & Behavior:
595
640
  const pkgPath = path.join(projectDir, "package.json");
596
641
  const pkgExists = await pathExists$1(pkgPath);
597
642
  let existingConfig = {};
643
+ let existingAuthor = "";
644
+ let existingGithubUsername = "";
645
+ let existingDescription = "";
646
+ let existingKeywords = [];
598
647
  if (pkgExists) try {
599
- existingConfig = JSON.parse(await fs.readFile(pkgPath, "utf8"))["create-template-project"] || {};
648
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
649
+ existingConfig = pkg["create-template-project"] || {};
650
+ existingAuthor = pkg.author;
651
+ existingGithubUsername = existingConfig.githubUsername;
652
+ existingDescription = pkg.description;
653
+ existingKeywords = pkg.keywords || [];
600
654
  debug$2("Found existing project config: %O", existingConfig);
601
655
  } catch (e) {
602
656
  debug$2("Failed to read existing package.json: %O", e);
603
657
  }
658
+ const projectDescription = await p.text({
659
+ message: "Project description:",
660
+ placeholder: "A new project",
661
+ defaultValue: existingDescription || ""
662
+ });
663
+ if (p.isCancel(projectDescription)) {
664
+ p.cancel("Operation cancelled.");
665
+ process.exit(0);
666
+ }
667
+ const projectKeywords = await p.text({
668
+ message: "Project keywords (comma separated):",
669
+ placeholder: "cli, nodejs, typescript",
670
+ defaultValue: existingKeywords ? existingKeywords.join(", ") : ""
671
+ });
672
+ if (p.isCancel(projectKeywords)) {
673
+ p.cancel("Operation cancelled.");
674
+ process.exit(0);
675
+ }
676
+ const defaultAuthor = await getDefaultAuthor();
677
+ const author = await p.text({
678
+ message: "Author name:",
679
+ placeholder: "Your Name",
680
+ defaultValue: existingAuthor || existingConfig.author || defaultAuthor,
681
+ validate: (value) => value && value.length > 0 ? void 0 : "Author name is required"
682
+ });
683
+ if (p.isCancel(author)) {
684
+ p.cancel("Operation cancelled.");
685
+ process.exit(0);
686
+ }
687
+ const defaultGithubUsername = await getDefaultGithubUsername();
688
+ const githubUsername = await p.text({
689
+ message: "GitHub username:",
690
+ placeholder: "your-github-username",
691
+ defaultValue: existingGithubUsername || defaultGithubUsername,
692
+ validate: (value) => value && value.length > 0 ? void 0 : "GitHub username is required"
693
+ });
694
+ if (p.isCancel(githubUsername)) {
695
+ p.cancel("Operation cancelled.");
696
+ process.exit(0);
697
+ }
604
698
  let update = false;
605
- let overwrite = false;
606
699
  if (exists) {
607
700
  const action = await p.select({
608
701
  message: `Directory "${projectDir}" already exists. What would you like to do?`,
609
- options: [
610
- {
611
- label: "Run an update",
612
- value: "update"
613
- },
614
- {
615
- label: "Overwrite existing directory by removing it first",
616
- value: "overwrite"
617
- },
618
- {
619
- label: "Cancel",
620
- value: "cancel"
621
- }
622
- ]
702
+ options: [{
703
+ label: "Run an update",
704
+ value: "update"
705
+ }, {
706
+ label: "Cancel",
707
+ value: "cancel"
708
+ }]
623
709
  });
624
710
  if (p.isCancel(action) || action === "cancel") {
625
711
  p.cancel("Operation cancelled.");
626
712
  process.exit(0);
627
713
  }
628
- if (action === "update") update = true;
629
- else if (action === "overwrite") overwrite = true;
714
+ if (action === "update") {
715
+ if (!existingConfig.template) {
716
+ p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
717
+ process.exit(1);
718
+ }
719
+ update = true;
720
+ }
630
721
  }
631
722
  let template = existingConfig.template;
632
723
  if (!update || !template) {
@@ -657,11 +748,11 @@ Restrictions & Behavior:
657
748
  process.exit(0);
658
749
  }
659
750
  } else p.log.info(`Using existing template type: ${template}`);
660
- let packageManager = "npm";
751
+ let packageManager = "pnpm";
661
752
  if (!update) {
662
753
  packageManager = await p.select({
663
754
  message: "Select package manager:",
664
- initialValue: "npm",
755
+ initialValue: "pnpm",
665
756
  options: [
666
757
  {
667
758
  label: "npm",
@@ -714,15 +805,16 @@ Restrictions & Behavior:
714
805
  commandResult = {
715
806
  template,
716
807
  projectName,
808
+ description: projectDescription,
809
+ keywords: projectKeywords,
810
+ author,
811
+ githubUsername,
717
812
  packageManager,
718
813
  createGithubRepository: createGithubRepositoryRes,
719
- directory: path.resolve(directory),
814
+ directory: projectDir,
720
815
  update,
721
- overwrite,
722
816
  installDependencies,
723
817
  build,
724
- dev: false,
725
- open: false,
726
818
  progress: true
727
819
  };
728
820
  });
@@ -751,16 +843,12 @@ Restrictions & Behavior:
751
843
  process.exit(1);
752
844
  }
753
845
  commandResult = validationResult.data;
754
- const projectDir = path.resolve(commandResult.directory, commandResult.projectName);
755
- if (await pathExists$1(projectDir) && !commandResult.update && !commandResult.overwrite) {
756
- p.cancel(`Directory "${projectDir}" already exists. Use --overwrite to overwrite or "update" command.`);
846
+ const projectDir = commandResult.directory;
847
+ if (await pathExists$1(projectDir) && !commandResult.update) {
848
+ p.cancel(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
757
849
  process.exit(1);
758
850
  }
759
- if (commandResult.open) {
760
- commandResult.dev = true;
761
- commandResult.installDependencies = true;
762
- }
763
- if (commandResult.dev || commandResult.build) commandResult.installDependencies = true;
851
+ if (commandResult.build) commandResult.installDependencies = true;
764
852
  return commandResult;
765
853
  };
766
854
  //#endregion
@@ -786,11 +874,6 @@ var getSpinner = (progress) => {
786
874
  stop: (msg) => progress ? s.stop(msg) : void 0
787
875
  };
788
876
  };
789
- var showNote = (msg, title, progress) => {
790
- if (progress !== void 0 && !progress) return;
791
- if (title) p.log.success(title);
792
- p.note(msg);
793
- };
794
877
  var isFileRequired = (relativePath, type) => {
795
878
  if (relativePath === "vitest.config.ts") return ![
796
879
  "cli",
@@ -801,23 +884,17 @@ var isFileRequired = (relativePath, type) => {
801
884
  return true;
802
885
  };
803
886
  var generateProject = async (opts) => {
804
- const { template: type, projectName, directory, update, overwrite, progress } = opts;
887
+ const { template: type, projectName, author, githubUsername, directory, update, progress } = opts;
805
888
  const isProgress = progress !== false;
806
889
  const log = getLog(isProgress);
807
890
  const spinner = () => getSpinner(isProgress);
808
- const projectDir = path.join(directory, projectName);
891
+ const projectDir = directory;
809
892
  debug$1("Project generation started for: %s", projectName);
810
893
  debug$1("Options: %O", opts);
811
894
  debug$1("Project directory: %s", projectDir);
812
895
  let isUpdate = !!update;
813
896
  if (await pathExists(projectDir)) {
814
- if (overwrite) {
815
- await fs.rm(projectDir, {
816
- recursive: true,
817
- force: true
818
- });
819
- isUpdate = false;
820
- } else if (!isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use --overwrite to replace it or --update to update.`);
897
+ if (!isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
821
898
  }
822
899
  const templates = [getBaseTemplate(opts)];
823
900
  debug$1("Applying template: base");
@@ -863,6 +940,16 @@ var generateProject = async (opts) => {
863
940
  let finalPkg = {
864
941
  name: projectName,
865
942
  version: "0.1.0",
943
+ description: opts.description || "TODO: Add project description",
944
+ keywords: opts.keywords ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
945
+ homepage: `https://github.com/${githubUsername}/${projectName}#readme`,
946
+ bugs: { url: `https://github.com/${githubUsername}/${projectName}/issues` },
947
+ license: "MIT",
948
+ author: author || "",
949
+ repository: {
950
+ type: "git",
951
+ url: `https://github.com/${githubUsername}/${projectName}.git`
952
+ },
866
953
  type: "module",
867
954
  "create-template-project": { template: type },
868
955
  scripts: {},
@@ -873,6 +960,7 @@ var generateProject = async (opts) => {
873
960
  if (isUpdate && await pathExists(pkgPath)) {
874
961
  debug$1("Loading existing package.json for update");
875
962
  const existingPkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
963
+ 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.`);
876
964
  finalPkg = {
877
965
  ...finalPkg,
878
966
  ...existingPkg
@@ -935,8 +1023,8 @@ var generateProject = async (opts) => {
935
1023
  }
936
1024
  continue;
937
1025
  }
938
- if (relativePath === "_oxlint.config.ts") {
939
- relativePath = "oxlint.config.ts";
1026
+ if (relativePath.startsWith("_") && relativePath.endsWith(".config.ts")) {
1027
+ relativePath = relativePath.substring(1);
940
1028
  targetPath = path.join(projectDir, relativePath);
941
1029
  }
942
1030
  if (relativePath === "package.json") continue;
@@ -1081,6 +1169,16 @@ var generateProject = async (opts) => {
1081
1169
  } else log.info("No changes detected.");
1082
1170
  }
1083
1171
  for (const op of pendingOperations) await op();
1172
+ const states = {
1173
+ gitInitialized: false,
1174
+ githubCreated: false,
1175
+ githubSkipped: !opts.createGithubRepository || isUpdate,
1176
+ githubError: "",
1177
+ depsInstalled: false,
1178
+ depsSkipped: !opts.installDependencies,
1179
+ ciRun: false,
1180
+ ciSkipped: !opts.build || !finalPkg.scripts.ci
1181
+ };
1084
1182
  const stdio = debug$1.enabled ? "inherit" : "pipe";
1085
1183
  if (!await pathExists(path.join(projectDir, ".git"))) {
1086
1184
  debug$1("Initializing Git repository");
@@ -1092,12 +1190,13 @@ var generateProject = async (opts) => {
1092
1190
  preferLocal: true
1093
1191
  });
1094
1192
  log.success("Initialized Git repository (git init).");
1193
+ states.gitInitialized = true;
1095
1194
  } catch (e) {
1096
1195
  debug$1("Failed to initialize Git: %O", e);
1097
1196
  const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1098
1197
  log.error(`Failed to initialize Git: ${e.message}${detail}`);
1099
1198
  }
1100
- }
1199
+ } else states.gitInitialized = true;
1101
1200
  if (opts.createGithubRepository && !isUpdate) {
1102
1201
  debug$1("Creating GitHub repository");
1103
1202
  try {
@@ -1115,10 +1214,12 @@ var generateProject = async (opts) => {
1115
1214
  preferLocal: true
1116
1215
  });
1117
1216
  log.success("Created GitHub repository (gh repo create).");
1217
+ states.githubCreated = true;
1118
1218
  } catch (e) {
1119
1219
  debug$1("Failed to create GitHub repository: %O", e);
1120
1220
  const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
1121
1221
  log.warn(`Failed to create GitHub repository: ${e.message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
1222
+ states.githubError = e.message;
1122
1223
  }
1123
1224
  }
1124
1225
  if (opts.installDependencies) {
@@ -1133,6 +1234,7 @@ var generateProject = async (opts) => {
1133
1234
  preferLocal: true
1134
1235
  });
1135
1236
  s.stop(`\x1b[1G\x1b[2K\x1b[32mโ—†\x1b[39m Dependencies installed (${pm} install).`);
1237
+ states.depsInstalled = true;
1136
1238
  } catch (e) {
1137
1239
  debug$1("Failed to install dependencies: %O", e);
1138
1240
  s.stop("Failed to install dependencies.");
@@ -1144,16 +1246,16 @@ var generateProject = async (opts) => {
1144
1246
  if (opts.build && finalPkg.scripts.ci) {
1145
1247
  debug$1("Running CI script");
1146
1248
  const s = spinner();
1147
- if (finalPkg.scripts["prettier-write"]) {
1148
- s.start(`Formatting files with Prettier (${pm} run prettier-write)...`);
1249
+ if (finalPkg.scripts.format) {
1250
+ s.start(`Formatting files with oxfmt (${pm} run format)...`);
1149
1251
  try {
1150
- debug$1("Executing: %s run prettier-write", pm);
1151
- await execa(pm, ["run", "prettier-write"], {
1252
+ debug$1("Executing: %s run format", pm);
1253
+ await execa(pm, ["run", "format"], {
1152
1254
  cwd: projectDir,
1153
1255
  stdio,
1154
1256
  preferLocal: true
1155
1257
  });
1156
- s.stop(`\x1b[1G\x1b[2K\x1b[32mโ—†\x1b[39m Files formatted (${pm} run prettier-write).`);
1258
+ s.stop(`\x1b[1G\x1b[2K\x1b[32mโ—†\x1b[39m Files formatted (${pm} run format).`);
1157
1259
  } catch (e) {
1158
1260
  debug$1("Failed to format files: %O", e);
1159
1261
  s.stop("Failed to format files.");
@@ -1170,6 +1272,7 @@ var generateProject = async (opts) => {
1170
1272
  preferLocal: true
1171
1273
  });
1172
1274
  s.stop(`\x1b[1G\x1b[2K\x1b[32mโ—†\x1b[39m CI script completed (${pm} run ci).`);
1275
+ states.ciRun = true;
1173
1276
  } catch (e) {
1174
1277
  debug$1("Failed to run CI script: %O", e);
1175
1278
  s.stop("Failed to run CI script.");
@@ -1178,53 +1281,200 @@ var generateProject = async (opts) => {
1178
1281
  throw new Error(`Failed to run CI script: ${e.message}${detail}`);
1179
1282
  }
1180
1283
  }
1181
- log.success(`Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}`);
1182
- showSummary(opts, pm, isProgress);
1183
- if (opts.dev && finalPkg.scripts.dev) {
1184
- log.info("Starting dev server...");
1185
- if (opts.open) try {
1186
- debug$1("Executing: %s run dev -- --open", pm);
1187
- await execa(pm, [
1188
- "run",
1189
- "dev",
1190
- "--",
1191
- "--open"
1192
- ], {
1193
- cwd: projectDir,
1194
- stdio: "inherit",
1195
- preferLocal: true
1196
- });
1197
- } catch (e) {
1198
- log.error("Dev server failed: " + e);
1199
- }
1200
- else try {
1201
- debug$1("Executing: %s run dev", pm);
1202
- await execa(pm, ["run", "dev"], {
1203
- cwd: projectDir,
1204
- stdio: "inherit",
1205
- preferLocal: true
1206
- });
1207
- } catch (e) {
1208
- log.error("Dev server failed: " + e);
1209
- }
1284
+ let hasErrors = false;
1285
+ let hasWarnings = false;
1286
+ const errorMessages = [];
1287
+ if (states.githubError) {
1288
+ hasWarnings = true;
1289
+ errorMessages.push(`GitHub repository creation failed: ${states.githubError}`);
1210
1290
  }
1291
+ await generateGeneratedMd(projectDir, opts, pm, states, isUpdate, {
1292
+ hasErrors,
1293
+ hasWarnings,
1294
+ errorMessages
1295
+ });
1296
+ const successMsg = `Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}. A detailed setup guide has been generated at GENERATED.md`;
1297
+ if (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
1298
+ else log.success(successMsg);
1211
1299
  };
1212
- function showSummary(opts, pm, isProgress) {
1213
- debug$1("Showing summary for options: %O", opts);
1214
- const { projectName, template } = opts;
1215
- const summary = [
1216
- `Successfully created a new ${template} project named '${projectName}'.`,
1300
+ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, status) {
1301
+ const statusBadge = status.hasErrors ? "๐Ÿ”ด **Completed with Errors**" : status.hasWarnings ? "๐ŸŸก **Completed with Warnings**" : "๐ŸŸข **Successfully Completed**";
1302
+ const md = [
1303
+ `# ๐Ÿš€ Project Setup Guide: ${opts.projectName}`,
1304
+ "",
1305
+ `Welcome to your newly ${isUpdate ? "updated" : "generated"} **${opts.template}** project! This document outlines what was scaffolded, the automated steps already completed, and the remaining manual adjustments required to finalize your setup.`,
1306
+ "",
1307
+ `**Status:** ${statusBadge}`,
1308
+ "",
1309
+ ...status.errorMessages.length > 0 ? [
1310
+ "### โš ๏ธ Issues Encountered",
1311
+ ...status.errorMessages.map((msg) => `- ${msg}`),
1312
+ ""
1313
+ ] : [],
1314
+ "## ๐Ÿ“ฆ What Was Generated",
1315
+ `* **Project Name:** \`${opts.projectName}\``,
1316
+ `* **Template Used:** \`${opts.template}\``,
1317
+ `* **Package Manager:** \`${pm}\``,
1318
+ "",
1319
+ "---",
1320
+ "",
1321
+ "## ๐Ÿ“‹ Initialization Checklist",
1322
+ "The following tasks were executed during the generation process:",
1323
+ `- [x] Scaffold project files and directories`,
1324
+ `- [x] Configure \`package.json\` with appropriate dependencies`,
1325
+ `- [${states.depsInstalled ? "x" : " "}] Install dependencies using \`${pm}\`${states.depsSkipped ? " *(Skipped)*" : ""}`,
1326
+ `- [${states.gitInitialized ? "x" : " "}] Initialize Git repository`,
1327
+ `- [${states.githubCreated ? "x" : " "}] Create GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
1328
+ `- [${states.ciRun ? "x" : " "}] Run initial CI pipeline (lint, build, test)${states.ciSkipped ? " *(Skipped)*" : ""}`,
1329
+ "",
1330
+ "---",
1217
1331
  "",
1218
- "Available Commands:"
1219
- ];
1220
- const commands = [
1221
- `${pm} run dev - Starts the development server`,
1222
- `${pm} run build - Builds the project for production`,
1223
- `${pm} run test - Runs the unit test suite (Vitest)`,
1224
- `${pm} run lint - Lints and formats the codebase`,
1225
- `${pm} run ci - Runs lint, build, and test (used by CI/CD)`
1226
- ];
1227
- showNote([...summary, ...commands.map((c) => ` ${c}`)].join("\n"), "Project ready", isProgress);
1332
+ "## ๐Ÿ’ป Available Commands",
1333
+ `You can run these commands from the project root using \`${pm} run <command>\`:`,
1334
+ "",
1335
+ "| Command | Description |",
1336
+ "| :--- | :--- |",
1337
+ "| `dev` | Starts the development server |",
1338
+ "| `build` | Builds the project for production |",
1339
+ "| `test` | Runs the unit test suite (Vitest) |",
1340
+ "| `lint` | Lints and formats the codebase |",
1341
+ "| `ci` | Runs lint, build, and test (used by CI/CD) |",
1342
+ "",
1343
+ "---",
1344
+ "",
1345
+ ...getTemplateArchitectureSection(opts.template),
1346
+ "",
1347
+ "---",
1348
+ "",
1349
+ "## ๐Ÿงช Testing Strategy",
1350
+ "",
1351
+ "### Unit Testing (Vitest)",
1352
+ "This project is pre-configured with **Vitest** for blazing-fast unit testing and coverage reporting.",
1353
+ `- **Where to put tests**: Create files with the \`.test.ts\` or \`.spec.ts\` extension next to your source files (e.g., \`src/main.test.ts\`).`,
1354
+ `- **How to run**: \`${pm} run test\``,
1355
+ `- **Watch mode**: \`${pm} exec vitest\` to automatically re-run tests on file changes.`,
1356
+ `- **Coverage**: Coverage is generated automatically during the test run. Aim for high coverage on core logic!`,
1357
+ "",
1358
+ "### End-to-End (E2E) Testing",
1359
+ "Currently, only unit tests are scaffolded by default. To enhance your project's reliability, we highly recommend adding E2E testing:",
1360
+ `- **For Web Apps**: Consider installing [Playwright](https://playwright.dev/) (\`${pm} create playwright\`) to simulate real user interactions in the browser.`,
1361
+ `- **For CLIs**: Consider using \`execa\` within your Vitest suite to invoke your compiled CLI binary and assert its \`stdout\`/\`stderr\` outputs.`,
1362
+ "",
1363
+ "---",
1364
+ "",
1365
+ "## ๐Ÿ™ Key Git Commands",
1366
+ "Here are the most common Git operations you will use to manage your codebase:",
1367
+ "",
1368
+ "| Command | Description |",
1369
+ "| :--- | :--- |",
1370
+ "| `git status` | Check the current state of your working directory |",
1371
+ "| `git add .` | Stage all your changes for the next commit |",
1372
+ "| `git commit -m \"feat: your feature\"` | Create a new commit (following Conventional Commits) |",
1373
+ "| `git push` | Push your committed changes to the remote repository |",
1374
+ "| `git pull` | Fetch and merge changes from the remote repository |",
1375
+ "| `git checkout -b <branch>` | Create and switch to a new branch |",
1376
+ "",
1377
+ "---",
1378
+ "",
1379
+ "## ๐Ÿˆ Key GitHub (`gh`) Commands",
1380
+ "The `gh` CLI provides powerful tools to interact with GitHub right from your terminal:",
1381
+ "",
1382
+ "| Command | Description |",
1383
+ "| :--- | :--- |",
1384
+ "| `gh repo view --web` | Open the repository in your default web browser |",
1385
+ "| `gh pr create` | Create a new Pull Request |",
1386
+ "| `gh pr checkout <pr-number>` | Checkout a Pull Request branch locally |",
1387
+ "| `gh issue create` | Create a new Issue |",
1388
+ "| `gh issue list` | List all open Issues |",
1389
+ "| `gh repo delete <owner>/<repo> --confirm` | Dangerously delete a repository completely (use with caution!) |",
1390
+ "",
1391
+ "---",
1392
+ "",
1393
+ "## ๐Ÿš€ Creating a Release",
1394
+ "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`",
1402
+ "",
1403
+ "---",
1404
+ "",
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
+ "<br>",
1419
+ "<p align=\"center\"><i>This file was auto-generated by <b>create-template-project</b>.</i></p>"
1420
+ ].join("\n");
1421
+ await fs.writeFile(path.join(projectDir, "GENERATED.md"), md);
1422
+ }
1423
+ function getTemplateArchitectureSection(template) {
1424
+ switch (template) {
1425
+ case "cli": return [
1426
+ "## ๐Ÿ—๏ธ CLI Architecture",
1427
+ "This project uses `commander` for argument parsing and `@clack/prompts` for interactive CLI interfaces.",
1428
+ "",
1429
+ "### Source Files Generated",
1430
+ "- **`src/index.ts`**: The main execution entry point. Handles top-level errors and bootstraps the CLI application.",
1431
+ "- **`src/cli.ts`**: Parses command-line arguments and orchestrates your user prompts.",
1432
+ "",
1433
+ "### How to Enhance",
1434
+ "- Add new sub-commands directly in `src/cli.ts`.",
1435
+ "- Extract logic into a new `src/commands/` directory as your application scales."
1436
+ ];
1437
+ case "web-vanilla": return [
1438
+ "## ๐Ÿ—๏ธ Web Vanilla Architecture",
1439
+ "A standalone, blazing fast web application scaffolded with Vite.",
1440
+ "",
1441
+ "### Source Files Generated",
1442
+ "- **`index.html`**: The main HTML entry point that loads your application scripts.",
1443
+ "- **`src/main.ts`**: The core TypeScript application logic where you can start adding DOM manipulation.",
1444
+ "",
1445
+ "### How to Enhance",
1446
+ "- Add new UI logic or Web Components inside the `src/` directory.",
1447
+ "- Create styling (`.css` or `.scss`) and import them directly into `main.ts`."
1448
+ ];
1449
+ case "web-app": return [
1450
+ "## ๐Ÿ—๏ธ Web App Architecture",
1451
+ "A robust React SPA configured with MUI components and TanStack Query.",
1452
+ "",
1453
+ "### Source Files Generated",
1454
+ "- **`index.html`**: The HTML entry point hosting the React root element.",
1455
+ "- **`src/main.tsx`**: Bootstraps the React application and mounts all necessary providers (QueryClient, Theme).",
1456
+ "- **`src/App.tsx`**: The root application component. Start building your UI here.",
1457
+ "",
1458
+ "### How to Enhance",
1459
+ "- Add new components to a `src/components/` directory.",
1460
+ "- Set up React Router for client-side routing.",
1461
+ "- Manage complex global state with a store like Zustand if needed."
1462
+ ];
1463
+ case "web-fullstack": return [
1464
+ "## ๐Ÿ—๏ธ Fullstack Monorepo Architecture",
1465
+ "A modern monorepo combining an Express server with a React client, seamlessly integrated using workspaces.",
1466
+ "",
1467
+ "### Source Files Generated",
1468
+ "- **`client/`**: The frontend React application (similar to the `web-app` template).",
1469
+ "- **`server/`**: The backend Express/Node application delivering API endpoints.",
1470
+ "",
1471
+ "### How to Enhance",
1472
+ "- Add new API routes in the `server` package.",
1473
+ "- Consume those routes in the `client` package via TanStack Query.",
1474
+ "- **Tip**: Create a `shared/` workspace package to share types across both frontend and backend for end-to-end type safety."
1475
+ ];
1476
+ default: return [];
1477
+ }
1228
1478
  }
1229
1479
  //#endregion
1230
1480
  //#region src/index.ts