create-template-project 0.3.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/LICENSE +1 -1
- package/README.md +56 -12
- package/dist/config/dependencies.json +13 -13
- package/dist/index.js +435 -147
- package/dist/templates/base/files/.github/workflows/node.js.yml +6 -4
- package/dist/templates/base/files/AGENTS.md +6 -5
- package/dist/templates/base/files/CONTRIBUTING.md +4 -4
- package/dist/templates/base/files/LICENSE +21 -0
- package/dist/templates/base/files/README.md +16 -14
- package/dist/templates/base/files/_oxc.config.ts +95 -0
- package/dist/templates/base/files/_oxfmt.config.ts +2 -0
- package/dist/templates/base/files/_oxlint.config.ts +2 -58
- package/dist/templates/base/files/package.json +30 -30
- package/package.json +93 -75
- package/dist/templates/base/files/.prettierignore +0 -53
- package/dist/templates/base/files/.prettierrc.json +0 -8
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
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
179
|
-
description: "
|
|
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("-
|
|
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.
|
|
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("-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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")
|
|
629
|
-
|
|
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 = "
|
|
751
|
+
let packageManager = "pnpm";
|
|
661
752
|
if (!update) {
|
|
662
753
|
packageManager = await p.select({
|
|
663
754
|
message: "Select package manager:",
|
|
664
|
-
initialValue: "
|
|
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:
|
|
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 =
|
|
755
|
-
if (await pathExists$1(projectDir) && !commandResult.update
|
|
756
|
-
p.cancel(`Directory "${projectDir}" already exists. Use
|
|
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.
|
|
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
|
|
@@ -783,33 +871,30 @@ var getSpinner = (progress) => {
|
|
|
783
871
|
const s = p.spinner();
|
|
784
872
|
return {
|
|
785
873
|
start: (msg) => progress ? s.start(msg) : void 0,
|
|
786
|
-
stop: (msg) => progress ? s.stop(msg) : void 0
|
|
787
|
-
message: (msg) => progress ? s.message(msg) : void 0
|
|
874
|
+
stop: (msg) => progress ? s.stop(msg) : void 0
|
|
788
875
|
};
|
|
789
876
|
};
|
|
790
|
-
var
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
877
|
+
var isFileRequired = (relativePath, type) => {
|
|
878
|
+
if (relativePath === "vitest.config.ts") return ![
|
|
879
|
+
"cli",
|
|
880
|
+
"web-vanilla",
|
|
881
|
+
"web-app",
|
|
882
|
+
"web-fullstack"
|
|
883
|
+
].includes(type);
|
|
884
|
+
return true;
|
|
794
885
|
};
|
|
795
886
|
var generateProject = async (opts) => {
|
|
796
|
-
const { template: type, projectName, directory, update,
|
|
887
|
+
const { template: type, projectName, author, githubUsername, directory, update, progress } = opts;
|
|
797
888
|
const isProgress = progress !== false;
|
|
798
889
|
const log = getLog(isProgress);
|
|
799
890
|
const spinner = () => getSpinner(isProgress);
|
|
800
|
-
const projectDir =
|
|
891
|
+
const projectDir = directory;
|
|
801
892
|
debug$1("Project generation started for: %s", projectName);
|
|
802
893
|
debug$1("Options: %O", opts);
|
|
803
894
|
debug$1("Project directory: %s", projectDir);
|
|
804
895
|
let isUpdate = !!update;
|
|
805
896
|
if (await pathExists(projectDir)) {
|
|
806
|
-
if (
|
|
807
|
-
await fs.rm(projectDir, {
|
|
808
|
-
recursive: true,
|
|
809
|
-
force: true
|
|
810
|
-
});
|
|
811
|
-
isUpdate = false;
|
|
812
|
-
} 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.`);
|
|
813
898
|
}
|
|
814
899
|
const templates = [getBaseTemplate(opts)];
|
|
815
900
|
debug$1("Applying template: base");
|
|
@@ -855,6 +940,16 @@ var generateProject = async (opts) => {
|
|
|
855
940
|
let finalPkg = {
|
|
856
941
|
name: projectName,
|
|
857
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
|
+
},
|
|
858
953
|
type: "module",
|
|
859
954
|
"create-template-project": { template: type },
|
|
860
955
|
scripts: {},
|
|
@@ -865,6 +960,7 @@ var generateProject = async (opts) => {
|
|
|
865
960
|
if (isUpdate && await pathExists(pkgPath)) {
|
|
866
961
|
debug$1("Loading existing package.json for update");
|
|
867
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.`);
|
|
868
964
|
finalPkg = {
|
|
869
965
|
...finalPkg,
|
|
870
966
|
...existingPkg
|
|
@@ -915,8 +1011,20 @@ var generateProject = async (opts) => {
|
|
|
915
1011
|
});
|
|
916
1012
|
continue;
|
|
917
1013
|
}
|
|
918
|
-
if (relativePath
|
|
919
|
-
|
|
1014
|
+
if (!isFileRequired(relativePath, type)) {
|
|
1015
|
+
if (isUpdate && await pathExists(targetPath)) {
|
|
1016
|
+
actions.push({
|
|
1017
|
+
type: "DELETE",
|
|
1018
|
+
path: relativePath
|
|
1019
|
+
});
|
|
1020
|
+
pendingOperations.push(async () => {
|
|
1021
|
+
await fs.rm(targetPath, { force: true });
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (relativePath.startsWith("_") && relativePath.endsWith(".config.ts")) {
|
|
1027
|
+
relativePath = relativePath.substring(1);
|
|
920
1028
|
targetPath = path.join(projectDir, relativePath);
|
|
921
1029
|
}
|
|
922
1030
|
if (relativePath === "package.json") continue;
|
|
@@ -961,6 +1069,18 @@ var generateProject = async (opts) => {
|
|
|
961
1069
|
});
|
|
962
1070
|
continue;
|
|
963
1071
|
}
|
|
1072
|
+
if (!isFileRequired(file.path, type)) {
|
|
1073
|
+
if (isUpdate && await pathExists(targetPath)) {
|
|
1074
|
+
actions.push({
|
|
1075
|
+
type: "DELETE",
|
|
1076
|
+
path: file.path
|
|
1077
|
+
});
|
|
1078
|
+
pendingOperations.push(async () => {
|
|
1079
|
+
await fs.rm(targetPath, { force: true });
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
964
1084
|
let content = typeof file.content === "function" ? file.content() : file.content;
|
|
965
1085
|
content = processContent(file.path, content, opts, addedDeps);
|
|
966
1086
|
const exists = await pathExists(targetPath);
|
|
@@ -997,41 +1117,47 @@ var generateProject = async (opts) => {
|
|
|
997
1117
|
if (pm === "pnpm" && finalPkg.workspaces) {
|
|
998
1118
|
debug$1("Creating pnpm-workspace.yaml");
|
|
999
1119
|
const workspaceYaml = `packages:\n${finalPkg.workspaces.map((w) => ` - '${w}'`).join("\n")}\n`;
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1120
|
+
const workspacePath = path.join(projectDir, "pnpm-workspace.yaml");
|
|
1121
|
+
const workspaceExists = await pathExists(workspacePath);
|
|
1122
|
+
let workspaceChanged = true;
|
|
1123
|
+
if (workspaceExists) workspaceChanged = (await fs.readFile(workspacePath, "utf8")).trim() !== workspaceYaml.trim();
|
|
1124
|
+
if (workspaceChanged) {
|
|
1125
|
+
actions.push({
|
|
1126
|
+
type: workspaceExists ? "MODIFY" : "ADD",
|
|
1127
|
+
path: "pnpm-workspace.yaml"
|
|
1128
|
+
});
|
|
1129
|
+
pendingOperations.push(async () => {
|
|
1130
|
+
await fs.writeFile(workspacePath, workspaceYaml);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1003
1133
|
delete finalPkg.workspaces;
|
|
1004
1134
|
for (const key of Object.keys(finalPkg.scripts)) {
|
|
1005
1135
|
const value = finalPkg.scripts[key];
|
|
1006
1136
|
if (typeof value === "string" && value.includes("--workspaces")) finalPkg.scripts[key] = value.replace(" run ", " -r run ").replace(" --workspaces", "");
|
|
1007
1137
|
}
|
|
1008
1138
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1139
|
+
const newPkgContent = JSON.stringify(finalPkg, null, " ");
|
|
1140
|
+
let pkgChanged = true;
|
|
1141
|
+
if (isUpdate && await pathExists(pkgPath)) pkgChanged = (await fs.readFile(pkgPath, "utf8")).trim() !== newPkgContent.trim();
|
|
1142
|
+
if (pkgChanged) {
|
|
1143
|
+
if (isUpdate) actions.push({
|
|
1144
|
+
type: "MODIFY",
|
|
1145
|
+
path: "package.json"
|
|
1013
1146
|
});
|
|
1014
|
-
|
|
1015
|
-
type: "
|
|
1016
|
-
path: "
|
|
1147
|
+
else actions.push({
|
|
1148
|
+
type: "ADD",
|
|
1149
|
+
path: "package.json"
|
|
1150
|
+
});
|
|
1151
|
+
pendingOperations.push(async () => {
|
|
1152
|
+
debug$1("Writing final consolidated package.json to: %s", pkgPath);
|
|
1153
|
+
await fs.writeFile(pkgPath, newPkgContent);
|
|
1017
1154
|
});
|
|
1018
1155
|
}
|
|
1019
|
-
if (isUpdate) actions.push({
|
|
1020
|
-
type: "MODIFY",
|
|
1021
|
-
path: "package.json"
|
|
1022
|
-
});
|
|
1023
|
-
else actions.push({
|
|
1024
|
-
type: "ADD",
|
|
1025
|
-
path: "package.json"
|
|
1026
|
-
});
|
|
1027
|
-
pendingOperations.push(async () => {
|
|
1028
|
-
debug$1("Writing final consolidated package.json to: %s", pkgPath);
|
|
1029
|
-
await fs.writeFile(pkgPath, JSON.stringify(finalPkg, null, " "));
|
|
1030
|
-
});
|
|
1031
1156
|
if (isUpdate && actions.length > 0 && process.env.NODE_ENV !== "test") {
|
|
1032
1157
|
const summary = actions.filter((a) => a.type !== "SKIP").map((a) => ` ${a.type.padEnd(8)} ${a.path}`).join("\n");
|
|
1033
1158
|
if (summary) {
|
|
1034
|
-
|
|
1159
|
+
const relativeProjectDir = path.relative(process.cwd(), projectDir) || ".";
|
|
1160
|
+
p.note(summary, `Planned changes in ${relativeProjectDir}:`);
|
|
1035
1161
|
const confirm = await p.confirm({
|
|
1036
1162
|
message: "Do you want to apply these changes?",
|
|
1037
1163
|
initialValue: true
|
|
@@ -1043,6 +1169,16 @@ var generateProject = async (opts) => {
|
|
|
1043
1169
|
} else log.info("No changes detected.");
|
|
1044
1170
|
}
|
|
1045
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
|
+
};
|
|
1046
1182
|
const stdio = debug$1.enabled ? "inherit" : "pipe";
|
|
1047
1183
|
if (!await pathExists(path.join(projectDir, ".git"))) {
|
|
1048
1184
|
debug$1("Initializing Git repository");
|
|
@@ -1054,12 +1190,13 @@ var generateProject = async (opts) => {
|
|
|
1054
1190
|
preferLocal: true
|
|
1055
1191
|
});
|
|
1056
1192
|
log.success("Initialized Git repository (git init).");
|
|
1193
|
+
states.gitInitialized = true;
|
|
1057
1194
|
} catch (e) {
|
|
1058
1195
|
debug$1("Failed to initialize Git: %O", e);
|
|
1059
1196
|
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1060
1197
|
log.error(`Failed to initialize Git: ${e.message}${detail}`);
|
|
1061
1198
|
}
|
|
1062
|
-
}
|
|
1199
|
+
} else states.gitInitialized = true;
|
|
1063
1200
|
if (opts.createGithubRepository && !isUpdate) {
|
|
1064
1201
|
debug$1("Creating GitHub repository");
|
|
1065
1202
|
try {
|
|
@@ -1077,10 +1214,12 @@ var generateProject = async (opts) => {
|
|
|
1077
1214
|
preferLocal: true
|
|
1078
1215
|
});
|
|
1079
1216
|
log.success("Created GitHub repository (gh repo create).");
|
|
1217
|
+
states.githubCreated = true;
|
|
1080
1218
|
} catch (e) {
|
|
1081
1219
|
debug$1("Failed to create GitHub repository: %O", e);
|
|
1082
1220
|
const detail = e.stdout || e.stderr ? `\n\nOutput:\n${e.stdout}\n${e.stderr}` : "";
|
|
1083
1221
|
log.warn(`Failed to create GitHub repository: ${e.message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
|
|
1222
|
+
states.githubError = e.message;
|
|
1084
1223
|
}
|
|
1085
1224
|
}
|
|
1086
1225
|
if (opts.installDependencies) {
|
|
@@ -1095,6 +1234,7 @@ var generateProject = async (opts) => {
|
|
|
1095
1234
|
preferLocal: true
|
|
1096
1235
|
});
|
|
1097
1236
|
s.stop(`\x1b[1G\x1b[2K\x1b[32mโ\x1b[39m Dependencies installed (${pm} install).`);
|
|
1237
|
+
states.depsInstalled = true;
|
|
1098
1238
|
} catch (e) {
|
|
1099
1239
|
debug$1("Failed to install dependencies: %O", e);
|
|
1100
1240
|
s.stop("Failed to install dependencies.");
|
|
@@ -1106,16 +1246,16 @@ var generateProject = async (opts) => {
|
|
|
1106
1246
|
if (opts.build && finalPkg.scripts.ci) {
|
|
1107
1247
|
debug$1("Running CI script");
|
|
1108
1248
|
const s = spinner();
|
|
1109
|
-
if (finalPkg.scripts
|
|
1110
|
-
s.start(`Formatting files with
|
|
1249
|
+
if (finalPkg.scripts.format) {
|
|
1250
|
+
s.start(`Formatting files with oxfmt (${pm} run format)...`);
|
|
1111
1251
|
try {
|
|
1112
|
-
debug$1("Executing: %s run
|
|
1113
|
-
await execa(pm, ["run", "
|
|
1252
|
+
debug$1("Executing: %s run format", pm);
|
|
1253
|
+
await execa(pm, ["run", "format"], {
|
|
1114
1254
|
cwd: projectDir,
|
|
1115
1255
|
stdio,
|
|
1116
1256
|
preferLocal: true
|
|
1117
1257
|
});
|
|
1118
|
-
s.stop(`\x1b[1G\x1b[2K\x1b[32mโ\x1b[39m Files formatted (${pm} run
|
|
1258
|
+
s.stop(`\x1b[1G\x1b[2K\x1b[32mโ\x1b[39m Files formatted (${pm} run format).`);
|
|
1119
1259
|
} catch (e) {
|
|
1120
1260
|
debug$1("Failed to format files: %O", e);
|
|
1121
1261
|
s.stop("Failed to format files.");
|
|
@@ -1132,6 +1272,7 @@ var generateProject = async (opts) => {
|
|
|
1132
1272
|
preferLocal: true
|
|
1133
1273
|
});
|
|
1134
1274
|
s.stop(`\x1b[1G\x1b[2K\x1b[32mโ\x1b[39m CI script completed (${pm} run ci).`);
|
|
1275
|
+
states.ciRun = true;
|
|
1135
1276
|
} catch (e) {
|
|
1136
1277
|
debug$1("Failed to run CI script: %O", e);
|
|
1137
1278
|
s.stop("Failed to run CI script.");
|
|
@@ -1140,53 +1281,200 @@ var generateProject = async (opts) => {
|
|
|
1140
1281
|
throw new Error(`Failed to run CI script: ${e.message}${detail}`);
|
|
1141
1282
|
}
|
|
1142
1283
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
await execa(pm, [
|
|
1150
|
-
"run",
|
|
1151
|
-
"dev",
|
|
1152
|
-
"--",
|
|
1153
|
-
"--open"
|
|
1154
|
-
], {
|
|
1155
|
-
cwd: projectDir,
|
|
1156
|
-
stdio: "inherit",
|
|
1157
|
-
preferLocal: true
|
|
1158
|
-
});
|
|
1159
|
-
} catch (e) {
|
|
1160
|
-
log.error("Dev server failed: " + e);
|
|
1161
|
-
}
|
|
1162
|
-
else try {
|
|
1163
|
-
debug$1("Executing: %s run dev", pm);
|
|
1164
|
-
await execa(pm, ["run", "dev"], {
|
|
1165
|
-
cwd: projectDir,
|
|
1166
|
-
stdio: "inherit",
|
|
1167
|
-
preferLocal: true
|
|
1168
|
-
});
|
|
1169
|
-
} catch (e) {
|
|
1170
|
-
log.error("Dev server failed: " + e);
|
|
1171
|
-
}
|
|
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}`);
|
|
1172
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);
|
|
1173
1299
|
};
|
|
1174
|
-
function
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
+
"---",
|
|
1331
|
+
"",
|
|
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.`,
|
|
1179
1362
|
"",
|
|
1180
|
-
"
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
+
}
|
|
1190
1478
|
}
|
|
1191
1479
|
//#endregion
|
|
1192
1480
|
//#region src/index.ts
|