create-template-project 1.5.24 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -5
- package/dist/config/dependencies.json +21 -1
- package/dist/index.js +733 -142
- package/dist/templates/base/files/README.md +1 -1
- package/dist/templates/base/files/_oxc.config.ts +17 -35
- package/dist/templates/web-widget/files/.github/workflows/pages.yml +36 -0
- package/dist/templates/web-widget/files/index.html +21 -0
- package/dist/templates/web-widget/files/package.json +53 -0
- package/dist/templates/web-widget/files/playwright.config.ts +29 -0
- package/dist/templates/web-widget/files/scripts/screenshot.mjs +35 -0
- package/dist/templates/web-widget/files/src/demo/app.ts +22 -0
- package/dist/templates/web-widget/files/src/demo/demo-shell.css +50 -0
- package/dist/templates/web-widget/files/src/lib/index.ts +2 -0
- package/dist/templates/web-widget/files/src/lib/react.tsx +54 -0
- package/dist/templates/web-widget/files/src/lib/test-setup.ts +7 -0
- package/dist/templates/web-widget/files/src/lib/widget.test.ts +33 -0
- package/dist/templates/web-widget/files/src/lib/widget.ts +83 -0
- package/dist/templates/web-widget/files/src/styles/index.css +42 -0
- package/dist/templates/web-widget/files/stylelint.base.config.js +16 -0
- package/dist/templates/web-widget/files/stylelint.config.js +15 -0
- package/dist/templates/web-widget/files/tests/e2e/widget.e2e-test.ts +16 -0
- package/dist/templates/web-widget/files/tsdown.config.ts +20 -0
- package/dist/templates/web-widget/files/typedoc.json +12 -0
- package/dist/templates/web-widget/files/vite.config.ts +37 -0
- package/package.json +6 -4
- /package/dist/templates/base/files/.github/workflows/{node.js.yml → ci.yml} +0 -0
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { existsSync } from "node:fs";
|
|
|
12
12
|
var TemplateTypeSchema = z.enum([
|
|
13
13
|
"cli",
|
|
14
14
|
"web-vanilla",
|
|
15
|
+
"web-widget",
|
|
15
16
|
"web-app",
|
|
16
17
|
"web-fullstack"
|
|
17
18
|
]);
|
|
@@ -46,6 +47,9 @@ var genericProcessor = (content, { opts }) => {
|
|
|
46
47
|
case "web-vanilla":
|
|
47
48
|
description = "A standalone web page/application for modern browsers.";
|
|
48
49
|
break;
|
|
50
|
+
case "web-widget":
|
|
51
|
+
description = "A publishable native TypeScript web widget with a Vite demo and npm-ready package build.";
|
|
52
|
+
break;
|
|
49
53
|
case "web-fullstack":
|
|
50
54
|
description = "A full-stack monorepo with an Express server and a React/MUI client.";
|
|
51
55
|
break;
|
|
@@ -68,7 +72,7 @@ var WORKFLOW_PNPM_SETUP = ` - name: Setup pnpm
|
|
|
68
72
|
var WORKFLOW_PLAYWRIGHT_SETUP = ` - name: Install Playwright Browsers & Deps
|
|
69
73
|
run: npx playwright install --with-deps chromium`;
|
|
70
74
|
var githubWorkflowProcessor = (content, { filePath, opts }) => {
|
|
71
|
-
if (!filePath.includes(".github/workflows/
|
|
75
|
+
if (!filePath.includes(".github/workflows/") || !filePath.endsWith(".yml")) return content;
|
|
72
76
|
const { template, packageManager: pm } = opts;
|
|
73
77
|
let installCommand = "npm ci";
|
|
74
78
|
let pmSetup = "";
|
|
@@ -77,7 +81,7 @@ var githubWorkflowProcessor = (content, { filePath, opts }) => {
|
|
|
77
81
|
pmSetup = WORKFLOW_PNPM_SETUP;
|
|
78
82
|
} else if (pm === "yarn") installCommand = "yarn install --frozen-lockfile";
|
|
79
83
|
let playwrightSetup = "";
|
|
80
|
-
if (template === "web-fullstack" || template === "web-app" || template === "web-vanilla") playwrightSetup = WORKFLOW_PLAYWRIGHT_SETUP;
|
|
84
|
+
if ((filePath.includes("ci.yml") || filePath.includes("node.js.yml")) && (template === "web-fullstack" || template === "web-app" || template === "web-vanilla" || template === "web-widget")) playwrightSetup = WORKFLOW_PLAYWRIGHT_SETUP;
|
|
81
85
|
let processed = content.replaceAll("{{installCommand}}", installCommand).replaceAll("# [PM_SETUP]", pmSetup).replaceAll("# [PLAYWRIGHT_SETUP]", playwrightSetup);
|
|
82
86
|
processed = processed.replace(/^\s*# \[PM_SETUP\]\s*\n/mu, "");
|
|
83
87
|
processed = processed.replace(/^\s*# \[PLAYWRIGHT_SETUP\]\s*\n/mu, "");
|
|
@@ -99,7 +103,11 @@ var tsconfigProcessor = (content, { filePath, opts }) => {
|
|
|
99
103
|
if (filePath !== "tsconfig.json") return content;
|
|
100
104
|
const { template } = opts;
|
|
101
105
|
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 \*\//u, `${WEB_ENV}\n\n\t\t/* Strict Type-Checking Options */`);
|
|
106
|
+
if (template === "web-fullstack" || template === "web-vanilla" || template === "web-widget" || template === "web-app") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//u, `${WEB_ENV}\n\n\t\t/* Strict Type-Checking Options */`);
|
|
107
|
+
if (template === "web-widget") processed = processed.replace("\"moduleResolution\": \"bundler\" /* Specify how TypeScript looks up a file from a given module specifier. */,", `"moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
|
108
|
+
"paths": {
|
|
109
|
+
"${opts.projectName}": ["./src/lib/index.ts"]
|
|
110
|
+
},`);
|
|
103
111
|
if (template === "web-fullstack") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/u, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
|
|
104
112
|
return processed;
|
|
105
113
|
};
|
|
@@ -124,7 +132,7 @@ var webVanillaHtmlProcessor = (content, { filePath, opts }) => {
|
|
|
124
132
|
var oxcConfigProcessor = (content, { filePath, opts }) => {
|
|
125
133
|
if (filePath !== "oxc.config.ts") return content;
|
|
126
134
|
const shouldAddNodeEnv = opts.template === "cli" || opts.template === "web-fullstack";
|
|
127
|
-
const shouldAddBrowserEnv = opts.template === "web-vanilla" || opts.template === "web-app" || opts.template === "web-fullstack";
|
|
135
|
+
const shouldAddBrowserEnv = opts.template === "web-vanilla" || opts.template === "web-widget" || opts.template === "web-app" || opts.template === "web-fullstack";
|
|
128
136
|
const linesToAdd = [];
|
|
129
137
|
if (shouldAddNodeEnv && !content.includes("node: true")) linesToAdd.push(" node: true,");
|
|
130
138
|
if (shouldAddBrowserEnv && !content.includes("browser: true")) linesToAdd.push(" browser: true,");
|
|
@@ -155,6 +163,34 @@ var processContent$1 = (filePath, content, opts, addedDeps) => {
|
|
|
155
163
|
//#endregion
|
|
156
164
|
//#region src/shared/file.ts
|
|
157
165
|
var debug$3 = debugLib("create-template-project:utils:file");
|
|
166
|
+
var parseExactVersion$1 = (version) => {
|
|
167
|
+
const match = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:[-+].*)?$/u.exec(version);
|
|
168
|
+
if (match?.groups === void 0) return;
|
|
169
|
+
return [
|
|
170
|
+
Number(match.groups.major),
|
|
171
|
+
Number(match.groups.minor),
|
|
172
|
+
Number(match.groups.patch)
|
|
173
|
+
];
|
|
174
|
+
};
|
|
175
|
+
var isGreaterOrEqualVersion$1 = (current, next) => {
|
|
176
|
+
const currentParts = parseExactVersion$1(current);
|
|
177
|
+
const nextParts = parseExactVersion$1(next);
|
|
178
|
+
if (currentParts === void 0 || nextParts === void 0) return false;
|
|
179
|
+
for (const [index, currentPart] of currentParts.entries()) {
|
|
180
|
+
const nextPart = nextParts[index];
|
|
181
|
+
if (currentPart > nextPart) return true;
|
|
182
|
+
if (currentPart < nextPart) return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
var mergeStringRecord = (target, source, preserveExistingVersions) => {
|
|
187
|
+
const merged = { ...target };
|
|
188
|
+
for (const [name, version] of Object.entries(source)) {
|
|
189
|
+
if (preserveExistingVersions && Object.hasOwn(merged, name) && isGreaterOrEqualVersion$1(merged[name], version)) continue;
|
|
190
|
+
merged[name] = version;
|
|
191
|
+
}
|
|
192
|
+
return merged;
|
|
193
|
+
};
|
|
158
194
|
var toErrorDetail$1 = (error) => {
|
|
159
195
|
if (error instanceof Error) {
|
|
160
196
|
const value = error;
|
|
@@ -191,21 +227,27 @@ var getAllFiles = async (dirPath, arrayOfFiles = []) => {
|
|
|
191
227
|
];
|
|
192
228
|
};
|
|
193
229
|
var processContent = (filePath, content, opts, addedDeps) => processContent$1(filePath, content, opts, addedDeps);
|
|
194
|
-
var mergePackageJson = (target, source) => {
|
|
230
|
+
var mergePackageJson = (target, source, options = {}) => {
|
|
231
|
+
const preservePackageFields = options.preserveExistingPackageFields === true;
|
|
232
|
+
const preserveDependencyVersions = options.preserveExistingDependencyVersions === true;
|
|
233
|
+
if (source.private !== void 0) target.private = source.private;
|
|
234
|
+
if (source.files !== void 0 && (!preservePackageFields || target.files === void 0)) target.files = source.files;
|
|
235
|
+
if (source.main !== void 0 && (!preservePackageFields || target.main === void 0)) target.main = source.main;
|
|
236
|
+
if (source.module !== void 0 && (!preservePackageFields || target.module === void 0)) target.module = source.module;
|
|
237
|
+
if (source.types !== void 0 && (!preservePackageFields || target.types === void 0)) target.types = source.types;
|
|
238
|
+
if (source.exports !== void 0 && (!preservePackageFields || target.exports === void 0)) target.exports = source.exports;
|
|
195
239
|
if (source.scripts !== void 0) target.scripts = {
|
|
196
240
|
...target.scripts,
|
|
197
241
|
...source.scripts
|
|
198
242
|
};
|
|
199
|
-
if (source.dependencies !== void 0) target.dependencies =
|
|
200
|
-
|
|
201
|
-
|
|
243
|
+
if (source.dependencies !== void 0) target.dependencies = mergeStringRecord(target.dependencies, source.dependencies, preserveDependencyVersions);
|
|
244
|
+
if (source.devDependencies !== void 0) target.devDependencies = mergeStringRecord(target.devDependencies, source.devDependencies, preserveDependencyVersions);
|
|
245
|
+
if (source.peerDependencies !== void 0) target.peerDependencies = {
|
|
246
|
+
...target.peerDependencies,
|
|
247
|
+
...source.peerDependencies
|
|
202
248
|
};
|
|
203
|
-
if (source.
|
|
204
|
-
|
|
205
|
-
...source.devDependencies
|
|
206
|
-
};
|
|
207
|
-
if (source.workspaces !== void 0) target.workspaces = source.workspaces;
|
|
208
|
-
if (source.bin !== void 0) target.bin = source.bin;
|
|
249
|
+
if (source.workspaces !== void 0 && (!preservePackageFields || target.workspaces === void 0)) target.workspaces = source.workspaces;
|
|
250
|
+
if (source.bin !== void 0 && (!preservePackageFields || target.bin === void 0)) target.bin = source.bin;
|
|
209
251
|
};
|
|
210
252
|
var isSeedFile = (filePath) => {
|
|
211
253
|
return [
|
|
@@ -377,6 +419,72 @@ var getWebVanillaTemplate = (_opts) => ({
|
|
|
377
419
|
templateDir: getTemplateDir(import.meta.dirname, "web-vanilla")
|
|
378
420
|
});
|
|
379
421
|
//#endregion
|
|
422
|
+
//#region src/templates/web-widget/index.ts
|
|
423
|
+
var getWebWidgetTemplate = (_opts) => ({
|
|
424
|
+
name: "web-widget",
|
|
425
|
+
description: "A publishable native TypeScript web widget with a Vite demo, package build, API docs, and screenshots.",
|
|
426
|
+
components: [
|
|
427
|
+
{
|
|
428
|
+
name: "Native TypeScript Widget",
|
|
429
|
+
description: "Dependency-free DOM component published as an ESM package."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "tsdown",
|
|
433
|
+
description: "Builds ESM library artifacts and declaration files for npm publishing."
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "Vite",
|
|
437
|
+
description: "Runs and builds the documentation/demo site."
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "TypeDoc",
|
|
441
|
+
description: "Generates API documentation for the public package surface."
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "Stylelint",
|
|
445
|
+
description: "Lints the widget and demo stylesheets."
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "Playwright",
|
|
449
|
+
description: "Runs e2e checks and captures demo screenshots."
|
|
450
|
+
}
|
|
451
|
+
],
|
|
452
|
+
dependencies: {},
|
|
453
|
+
devDependencies: {
|
|
454
|
+
vite: "vite",
|
|
455
|
+
vitest: "vitest",
|
|
456
|
+
"@vitest/browser": "@vitest/browser",
|
|
457
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
458
|
+
playwright: "playwright",
|
|
459
|
+
"@playwright/test": "@playwright/test",
|
|
460
|
+
tsdown: "tsdown",
|
|
461
|
+
typedoc: "typedoc",
|
|
462
|
+
stylelint: "stylelint",
|
|
463
|
+
"stylelint-config-standard": "stylelint-config-standard",
|
|
464
|
+
react: "react",
|
|
465
|
+
"react-dom": "react-dom",
|
|
466
|
+
"@types/react": "@types/react",
|
|
467
|
+
"@types/react-dom": "@types/react-dom"
|
|
468
|
+
},
|
|
469
|
+
scripts: {
|
|
470
|
+
dev: "vite",
|
|
471
|
+
build: "vite build",
|
|
472
|
+
"build:lib": "tsdown",
|
|
473
|
+
"build:demo": "vite build",
|
|
474
|
+
"docs:api": "typedoc",
|
|
475
|
+
preview: "vite preview",
|
|
476
|
+
start: "vite preview --port 4173 --strictPort",
|
|
477
|
+
test: "vitest run",
|
|
478
|
+
"test:ui": "vitest",
|
|
479
|
+
"integration-test": "playwright test",
|
|
480
|
+
"lint:css": "stylelint 'src/{demo,styles}/**/*.css'",
|
|
481
|
+
"lint:css:fix": "stylelint --fix 'src/{demo,styles}/**/*.css'",
|
|
482
|
+
screenshot: "node scripts/screenshot.mjs"
|
|
483
|
+
},
|
|
484
|
+
files: [],
|
|
485
|
+
templateDir: getTemplateDir(import.meta.dirname, "web-widget")
|
|
486
|
+
});
|
|
487
|
+
//#endregion
|
|
380
488
|
//#region src/templates/web-app/index.ts
|
|
381
489
|
var getWebAppTemplate = (_opts) => ({
|
|
382
490
|
name: "web-app",
|
|
@@ -518,6 +626,7 @@ var getWebFullstackTemplate = (opts) => ({
|
|
|
518
626
|
var templateFactories = {
|
|
519
627
|
cli: getCliTemplate,
|
|
520
628
|
"web-vanilla": getWebVanillaTemplate,
|
|
629
|
+
"web-widget": getWebWidgetTemplate,
|
|
521
630
|
"web-app": getWebAppTemplate,
|
|
522
631
|
"web-fullstack": getWebFullstackTemplate
|
|
523
632
|
};
|
|
@@ -526,6 +635,7 @@ var getProjectTemplates = (opts) => [getBaseTemplate(opts), getTemplateByType(op
|
|
|
526
635
|
var getTemplateTypes = () => [
|
|
527
636
|
"cli",
|
|
528
637
|
"web-vanilla",
|
|
638
|
+
"web-widget",
|
|
529
639
|
"web-app",
|
|
530
640
|
"web-fullstack"
|
|
531
641
|
];
|
|
@@ -558,6 +668,208 @@ var getTemplateInfo = (type) => {
|
|
|
558
668
|
};
|
|
559
669
|
var getAllTemplatesInfo = () => getTemplateTypes().map((type) => getTemplateInfo(type));
|
|
560
670
|
//#endregion
|
|
671
|
+
//#region src/generators/project-compatibility.ts
|
|
672
|
+
var PASS_THRESHOLD = 75;
|
|
673
|
+
var isRecord$3 = (value) => typeof value === "object" && value !== null;
|
|
674
|
+
var pathExists$2 = async (filePath) => {
|
|
675
|
+
try {
|
|
676
|
+
await fs.access(filePath);
|
|
677
|
+
return true;
|
|
678
|
+
} catch {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
var listFiles = async (directory) => {
|
|
683
|
+
try {
|
|
684
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
685
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
686
|
+
const entryPath = path.join(directory, entry.name);
|
|
687
|
+
if (entry.isDirectory()) return await listFiles(entryPath);
|
|
688
|
+
return entry.isFile() ? [entryPath] : [];
|
|
689
|
+
}))).flat();
|
|
690
|
+
} catch {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
var parseStringRecord$1 = (value) => {
|
|
695
|
+
if (!isRecord$3(value)) return;
|
|
696
|
+
const parsed = {};
|
|
697
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") parsed[key] = entry;
|
|
698
|
+
return parsed;
|
|
699
|
+
};
|
|
700
|
+
var parsePackageJson = async (pkgPath) => {
|
|
701
|
+
try {
|
|
702
|
+
const parsed = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
703
|
+
if (!isRecord$3(parsed)) return;
|
|
704
|
+
return {
|
|
705
|
+
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
706
|
+
type: typeof parsed.type === "string" ? parsed.type : void 0,
|
|
707
|
+
scripts: parseStringRecord$1(parsed.scripts),
|
|
708
|
+
dependencies: parseStringRecord$1(parsed.dependencies),
|
|
709
|
+
devDependencies: parseStringRecord$1(parsed.devDependencies),
|
|
710
|
+
workspaces: Array.isArray(parsed.workspaces) ? parsed.workspaces.filter((workspace) => typeof workspace === "string") : void 0
|
|
711
|
+
};
|
|
712
|
+
} catch {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var hasAnyDependency = (pkg, dependency) => pkg?.dependencies?.[dependency] !== void 0 || pkg?.devDependencies?.[dependency] !== void 0;
|
|
717
|
+
var requiredFilesByTemplate = {
|
|
718
|
+
cli: [
|
|
719
|
+
"src/index.ts",
|
|
720
|
+
"src/index.test.ts",
|
|
721
|
+
"vite.config.ts"
|
|
722
|
+
],
|
|
723
|
+
"web-vanilla": [
|
|
724
|
+
"index.html",
|
|
725
|
+
"src/index.ts",
|
|
726
|
+
"vite.config.ts",
|
|
727
|
+
"playwright.config.ts"
|
|
728
|
+
],
|
|
729
|
+
"web-widget": [
|
|
730
|
+
"index.html",
|
|
731
|
+
"src/lib/index.ts",
|
|
732
|
+
"tsdown.config.ts",
|
|
733
|
+
"typedoc.json",
|
|
734
|
+
"vite.config.ts",
|
|
735
|
+
"playwright.config.ts"
|
|
736
|
+
],
|
|
737
|
+
"web-app": [
|
|
738
|
+
"index.html",
|
|
739
|
+
"src/index.tsx",
|
|
740
|
+
"src/App.tsx",
|
|
741
|
+
"vite.config.ts",
|
|
742
|
+
"playwright.config.ts"
|
|
743
|
+
],
|
|
744
|
+
"web-fullstack": [
|
|
745
|
+
"client/package.json",
|
|
746
|
+
"server/package.json",
|
|
747
|
+
"client/src/main.tsx",
|
|
748
|
+
"server/src/index.ts"
|
|
749
|
+
]
|
|
750
|
+
};
|
|
751
|
+
var requiredDependenciesByTemplate = {
|
|
752
|
+
cli: ["vite"],
|
|
753
|
+
"web-vanilla": [
|
|
754
|
+
"vite",
|
|
755
|
+
"vitest",
|
|
756
|
+
"playwright"
|
|
757
|
+
],
|
|
758
|
+
"web-widget": [
|
|
759
|
+
"vite",
|
|
760
|
+
"vitest",
|
|
761
|
+
"playwright",
|
|
762
|
+
"tsdown",
|
|
763
|
+
"typedoc",
|
|
764
|
+
"stylelint"
|
|
765
|
+
],
|
|
766
|
+
"web-app": [
|
|
767
|
+
"react",
|
|
768
|
+
"react-dom",
|
|
769
|
+
"@mui/material",
|
|
770
|
+
"vite"
|
|
771
|
+
],
|
|
772
|
+
"web-fullstack": [
|
|
773
|
+
"express",
|
|
774
|
+
"@trpc/server",
|
|
775
|
+
"react",
|
|
776
|
+
"react-dom"
|
|
777
|
+
]
|
|
778
|
+
};
|
|
779
|
+
var validateProjectCompatibility = async (directory, template) => {
|
|
780
|
+
const pkg = await parsePackageJson(path.join(directory, "package.json"));
|
|
781
|
+
const checks = [];
|
|
782
|
+
const addCheck = (id, label, passed, required = false) => {
|
|
783
|
+
checks.push({
|
|
784
|
+
id,
|
|
785
|
+
label,
|
|
786
|
+
passed,
|
|
787
|
+
required
|
|
788
|
+
});
|
|
789
|
+
};
|
|
790
|
+
addCheck("package-json", "package.json exists and is valid JSON", pkg !== void 0, true);
|
|
791
|
+
addCheck("package-name", "package.json has a name", typeof pkg?.name === "string" && pkg.name.length > 0, true);
|
|
792
|
+
addCheck("esm", "package.json uses ESM type module", pkg?.type === "module");
|
|
793
|
+
const canonicalWorkflowPath = ".github/workflows/ci.yml";
|
|
794
|
+
const legacyWorkflowPath = ".github/workflows/node.js.yml";
|
|
795
|
+
const canonicalWorkflowExists = await pathExists$2(path.join(directory, canonicalWorkflowPath));
|
|
796
|
+
const legacyWorkflowExists = await pathExists$2(path.join(directory, legacyWorkflowPath));
|
|
797
|
+
const commonFileResults = await Promise.all([
|
|
798
|
+
"tsconfig.json",
|
|
799
|
+
"vitest.config.ts",
|
|
800
|
+
"commitlint.config.js",
|
|
801
|
+
".husky/pre-commit"
|
|
802
|
+
].map(async (filePath) => ({
|
|
803
|
+
filePath,
|
|
804
|
+
exists: await pathExists$2(path.join(directory, filePath))
|
|
805
|
+
})));
|
|
806
|
+
for (const { filePath, exists } of commonFileResults) addCheck(`file:${filePath}`, `${filePath} exists`, exists);
|
|
807
|
+
addCheck(`file:${canonicalWorkflowPath}`, canonicalWorkflowExists ? `${canonicalWorkflowPath} exists` : `${canonicalWorkflowPath} exists (canonical; legacy ${legacyWorkflowPath} is accepted for adoption)`, canonicalWorkflowExists || legacyWorkflowExists);
|
|
808
|
+
const configFileResults = await Promise.all([
|
|
809
|
+
"oxc.config.ts",
|
|
810
|
+
"oxlint.config.ts",
|
|
811
|
+
"oxfmt.config.ts"
|
|
812
|
+
].map(async (filePath) => ({
|
|
813
|
+
filePath,
|
|
814
|
+
exists: await pathExists$2(path.join(directory, filePath))
|
|
815
|
+
})));
|
|
816
|
+
for (const { filePath, exists } of configFileResults) addCheck(`file:${filePath}`, `${filePath} exists`, exists);
|
|
817
|
+
for (const script of [
|
|
818
|
+
"typecheck",
|
|
819
|
+
"lint",
|
|
820
|
+
"format:check",
|
|
821
|
+
"ci",
|
|
822
|
+
"test"
|
|
823
|
+
]) addCheck(`script:${script}`, `package.json has ${script} script`, pkg?.scripts?.[script] !== void 0);
|
|
824
|
+
for (const dependency of [
|
|
825
|
+
"typescript",
|
|
826
|
+
"vitest",
|
|
827
|
+
"oxlint",
|
|
828
|
+
"oxfmt",
|
|
829
|
+
"husky",
|
|
830
|
+
"@commitlint/cli"
|
|
831
|
+
]) addCheck(`dependency:${dependency}`, `${dependency} dependency is declared`, hasAnyDependency(pkg, dependency));
|
|
832
|
+
const templateFileResults = await Promise.all(requiredFilesByTemplate[template].map(async (filePath) => ({
|
|
833
|
+
filePath,
|
|
834
|
+
exists: await pathExists$2(path.join(directory, filePath))
|
|
835
|
+
})));
|
|
836
|
+
for (const { filePath, exists } of templateFileResults) addCheck(`template-file:${filePath}`, `${template} file ${filePath} exists`, exists);
|
|
837
|
+
if (template === "web-widget") {
|
|
838
|
+
const sourceFiles = await listFiles(path.join(directory, "src/lib"));
|
|
839
|
+
const styleFiles = await listFiles(path.join(directory, "src/styles"));
|
|
840
|
+
const implementationFiles = sourceFiles.map((filePath) => path.relative(directory, filePath).split(path.sep).join("/")).filter((filePath) => /\.tsx?$/u.test(filePath) && !filePath.endsWith("/index.ts") && !filePath.endsWith("/react.tsx") && !filePath.includes(".test."));
|
|
841
|
+
const widgetStyles = styleFiles.map((filePath) => path.relative(directory, filePath).split(path.sep).join("/")).filter((filePath) => filePath.endsWith(".css"));
|
|
842
|
+
addCheck("web-widget:implementation", implementationFiles.length > 0 ? `web-widget implementation found (${implementationFiles.join(", ")})` : "web-widget implementation exists in src/lib", implementationFiles.length > 0);
|
|
843
|
+
addCheck("web-widget:stylesheet", widgetStyles.length > 0 ? `web-widget stylesheet found (${widgetStyles.join(", ")})` : "web-widget stylesheet exists in src/styles", widgetStyles.length > 0);
|
|
844
|
+
}
|
|
845
|
+
for (const dependency of requiredDependenciesByTemplate[template]) addCheck(`template-dependency:${dependency}`, `${template} dependency ${dependency} is declared`, hasAnyDependency(pkg, dependency));
|
|
846
|
+
if (template === "web-fullstack") addCheck("workspaces", "package.json has workspaces", Array.isArray(pkg?.workspaces) && pkg.workspaces.length > 0);
|
|
847
|
+
const requiredPassed = checks.filter((check) => check.required).every((check) => check.passed);
|
|
848
|
+
const passedCount = checks.filter((check) => check.passed).length;
|
|
849
|
+
const score = Math.round(passedCount / checks.length * 100);
|
|
850
|
+
return {
|
|
851
|
+
directory,
|
|
852
|
+
template,
|
|
853
|
+
score,
|
|
854
|
+
passed: requiredPassed && score >= PASS_THRESHOLD,
|
|
855
|
+
checks
|
|
856
|
+
};
|
|
857
|
+
};
|
|
858
|
+
var formatCompatibilityReport = (report) => {
|
|
859
|
+
const failed = report.checks.filter((check) => !check.passed);
|
|
860
|
+
const lines = [
|
|
861
|
+
`Directory: ${report.directory}`,
|
|
862
|
+
`Template: ${report.template}`,
|
|
863
|
+
`Score: ${report.score}%`,
|
|
864
|
+
`Status: ${report.passed ? "PASS" : "FAIL"}`
|
|
865
|
+
];
|
|
866
|
+
if (failed.length > 0) {
|
|
867
|
+
lines.push("", "Missing or incompatible checks:");
|
|
868
|
+
for (const check of failed) lines.push(`- ${check.label}${check.required ? " (required)" : ""}`);
|
|
869
|
+
}
|
|
870
|
+
return lines.join("\n");
|
|
871
|
+
};
|
|
872
|
+
//#endregion
|
|
561
873
|
//#region src/cli.ts
|
|
562
874
|
var isRecord$2 = (value) => typeof value === "object" && value !== null;
|
|
563
875
|
var isPackageManager = (value) => value === "npm" || value === "pnpm" || value === "yarn";
|
|
@@ -620,6 +932,35 @@ var getDefaultGithubUsername = async () => {
|
|
|
620
932
|
return "";
|
|
621
933
|
}
|
|
622
934
|
};
|
|
935
|
+
var getMissingProjectConfigMessage$1 = (pkgPath) => [
|
|
936
|
+
`No "create-template-project" configuration found in ${pkgPath}.`,
|
|
937
|
+
"The update command only runs after a project has been adopted by this tool.",
|
|
938
|
+
"If this project matches a template, run \"create-template-project doctor --template <type> --directory <dir>\" to validate it, then \"create-template-project adopt --template <type> --directory <dir>\" to add the marker."
|
|
939
|
+
].join(" ");
|
|
940
|
+
var parseTemplateOption = (template) => {
|
|
941
|
+
const templateInput = stripQuotes(template);
|
|
942
|
+
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
943
|
+
if (!templateResult.success) {
|
|
944
|
+
p.log.error(`Invalid template type: ${template}. Must be one of: cli, web-vanilla, web-widget, web-app, web-fullstack`);
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
return templateResult.data;
|
|
948
|
+
};
|
|
949
|
+
var adoptProject = async (directory, template) => {
|
|
950
|
+
const pkgPath = path.join(directory, "package.json");
|
|
951
|
+
const parsed = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
952
|
+
if (!isRecord$2(parsed)) {
|
|
953
|
+
p.log.error(`Failed to adopt project: ${pkgPath} does not contain a JSON object.`);
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
await fs.writeFile(pkgPath, `${JSON.stringify({
|
|
957
|
+
...parsed,
|
|
958
|
+
"create-template-project": {
|
|
959
|
+
...isRecord$2(parsed["create-template-project"]) ? parsed["create-template-project"] : {},
|
|
960
|
+
template
|
|
961
|
+
}
|
|
962
|
+
}, null, " ")}\n`);
|
|
963
|
+
};
|
|
623
964
|
var debug$2 = debugLib("create-template-project:cli");
|
|
624
965
|
var parseArgs = async () => {
|
|
625
966
|
debug$2("Parsing CLI arguments: %O", process.argv);
|
|
@@ -632,27 +973,22 @@ var parseArgs = async () => {
|
|
|
632
973
|
process.env.DEBUG = "create-template-project:*";
|
|
633
974
|
debugLib.enable("create-template-project:*");
|
|
634
975
|
}).addHelpText("after", `
|
|
635
|
-
Commands:
|
|
636
|
-
create - Create a new project from a template.
|
|
637
|
-
update - Update an existing project from its template.
|
|
638
|
-
interactive - Start interactive project configuration.
|
|
639
|
-
info - Show detailed information about available templates and components.
|
|
640
|
-
|
|
641
976
|
Templates:
|
|
642
977
|
cli - Node.js CLI application with commander and cli-progress.
|
|
643
978
|
web-vanilla - Standalone web page (modern HTML/JS).
|
|
979
|
+
web-widget - Publishable native TypeScript widget with demo, docs, and npm build.
|
|
644
980
|
web-app - React application with MUI and TanStack Query.
|
|
645
981
|
web-fullstack - Full-stack monorepo with Express server and React/MUI client.
|
|
646
982
|
`);
|
|
647
983
|
let commandResult;
|
|
648
|
-
program.command("info").description("Show detailed information about available templates and their components").option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").action((opts) => {
|
|
984
|
+
program.command("info").description("Show detailed information about available templates and their components").option("-t, --template <type>", "Template type (cli, web-vanilla, web-widget, web-app, web-fullstack)").action((opts) => {
|
|
649
985
|
debug$2("Executing \"info\" command with options: %O", opts);
|
|
650
986
|
p.intro("Template Information");
|
|
651
987
|
if (opts.template !== void 0 && opts.template.length > 0) {
|
|
652
988
|
const template = stripQuotes(opts.template);
|
|
653
989
|
const typeResult = TemplateTypeSchema.safeParse(template);
|
|
654
990
|
if (!typeResult.success) {
|
|
655
|
-
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
991
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-widget, web-app, web-fullstack`);
|
|
656
992
|
process.exit(1);
|
|
657
993
|
}
|
|
658
994
|
const info = getTemplateInfo(typeResult.data);
|
|
@@ -674,12 +1010,49 @@ Templates:
|
|
|
674
1010
|
p.outro("Use \"create\" to scaffold a new project.");
|
|
675
1011
|
process.exit(0);
|
|
676
1012
|
});
|
|
677
|
-
program.command("
|
|
1013
|
+
program.command("doctor").description("Check whether an existing project can be adopted for template updates").requiredOption("-t, --template <type>", "Template type (cli, web-vanilla, web-widget, web-app, web-fullstack)").option("-d, --directory <path>", "Project directory", ".").action(async (opts) => {
|
|
1014
|
+
debug$2("Executing \"doctor\" command with options: %O", opts);
|
|
1015
|
+
const template = parseTemplateOption(opts.template);
|
|
1016
|
+
const directory = path.resolve(opts.directory);
|
|
1017
|
+
const report = await validateProjectCompatibility(directory, template);
|
|
1018
|
+
p.note(formatCompatibilityReport(report), "Compatibility Check");
|
|
1019
|
+
if (!report.passed) {
|
|
1020
|
+
p.log.error("Project is not compatible enough to adopt safely. Fix the failed checks and run doctor again.");
|
|
1021
|
+
process.exit(1);
|
|
1022
|
+
}
|
|
1023
|
+
p.log.success(`Project is compatible. Run "create-template-project adopt --template ${template} --directory ${directory}" to add the create-template-project marker.`);
|
|
1024
|
+
process.exit(0);
|
|
1025
|
+
});
|
|
1026
|
+
program.command("adopt").description("Validate an existing project and add the create-template-project update marker").requiredOption("-t, --template <type>", "Template type (cli, web-vanilla, web-widget, web-app, web-fullstack)").option("-d, --directory <path>", "Project directory", ".").option("--yes", "Adopt without confirmation", false).action(async (opts) => {
|
|
1027
|
+
debug$2("Executing \"adopt\" command with options: %O", opts);
|
|
1028
|
+
const template = parseTemplateOption(opts.template);
|
|
1029
|
+
const directory = path.resolve(opts.directory);
|
|
1030
|
+
const report = await validateProjectCompatibility(directory, template);
|
|
1031
|
+
p.note(formatCompatibilityReport(report), "Compatibility Check");
|
|
1032
|
+
if (!report.passed) {
|
|
1033
|
+
p.log.error("Project is not compatible enough to adopt safely. Fix the failed checks and run doctor again.");
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
}
|
|
1036
|
+
if (opts.yes !== true) {
|
|
1037
|
+
const confirmed = await p.confirm({
|
|
1038
|
+
message: `Add create-template-project marker for template "${template}" to ${path.join(directory, "package.json")}?`,
|
|
1039
|
+
initialValue: false
|
|
1040
|
+
});
|
|
1041
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
1042
|
+
p.cancel("Adoption cancelled.");
|
|
1043
|
+
process.exit(0);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
await adoptProject(directory, template);
|
|
1047
|
+
p.log.success(`Project adopted as ${template}. You can now run "create-template-project update --directory ${directory}".`);
|
|
1048
|
+
process.exit(0);
|
|
1049
|
+
});
|
|
1050
|
+
program.command("create").description("Create a new project from a template").option("-t, --template <type>", "Template type (cli, web-vanilla, web-widget, 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) => {
|
|
678
1051
|
debug$2("Executing \"create\" command with options: %O", opts);
|
|
679
1052
|
const templateInput = stripQuotes(opts.template);
|
|
680
1053
|
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
681
1054
|
if (!templateResult.success) {
|
|
682
|
-
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
1055
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-widget, web-app, web-fullstack`);
|
|
683
1056
|
process.exit(1);
|
|
684
1057
|
}
|
|
685
1058
|
commandResult = {
|
|
@@ -708,7 +1081,7 @@ Restrictions & Behavior:
|
|
|
708
1081
|
- package.json: Dependencies and scripts are merged. Existing versions are preserved unless they are missing.
|
|
709
1082
|
- 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.
|
|
710
1083
|
- Confirmation: The command will show a summary of proposed changes and lets you preview diffs before applying.
|
|
711
|
-
`).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) => {
|
|
1084
|
+
`).option("-t, --template <type>", "Template type (cli, web-vanilla, web-widget, 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) => {
|
|
712
1085
|
debug$2("Executing \"update\" command with options: %O", opts);
|
|
713
1086
|
const directory = path.resolve(opts.directory);
|
|
714
1087
|
const pkgPath = path.join(directory, "package.json");
|
|
@@ -730,19 +1103,11 @@ Restrictions & Behavior:
|
|
|
730
1103
|
}
|
|
731
1104
|
const projectConfig = pkg["create-template-project"];
|
|
732
1105
|
if (projectConfig === void 0 || projectConfig.template === void 0) {
|
|
733
|
-
p.log.error(
|
|
1106
|
+
p.log.error(getMissingProjectConfigMessage$1(pkgPath));
|
|
734
1107
|
process.exit(1);
|
|
735
1108
|
}
|
|
736
1109
|
let template = projectConfig.template;
|
|
737
|
-
if (opts.template !== void 0)
|
|
738
|
-
const templateInput = stripQuotes(opts.template);
|
|
739
|
-
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
740
|
-
if (!templateResult.success) {
|
|
741
|
-
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
742
|
-
process.exit(1);
|
|
743
|
-
}
|
|
744
|
-
template = templateResult.data;
|
|
745
|
-
}
|
|
1110
|
+
if (opts.template !== void 0) template = parseTemplateOption(opts.template);
|
|
746
1111
|
commandResult = {
|
|
747
1112
|
...opts,
|
|
748
1113
|
update: true,
|
|
@@ -859,7 +1224,7 @@ Restrictions & Behavior:
|
|
|
859
1224
|
process.exit(0);
|
|
860
1225
|
}
|
|
861
1226
|
if (!existingConfig.template) {
|
|
862
|
-
p.log.error(
|
|
1227
|
+
p.log.error(getMissingProjectConfigMessage$1(pkgPath));
|
|
863
1228
|
process.exit(1);
|
|
864
1229
|
}
|
|
865
1230
|
update = true;
|
|
@@ -879,6 +1244,10 @@ Restrictions & Behavior:
|
|
|
879
1244
|
label: "Web-Vanilla (Standalone)",
|
|
880
1245
|
value: "web-vanilla"
|
|
881
1246
|
},
|
|
1247
|
+
{
|
|
1248
|
+
label: "Web-Widget (Publishable)",
|
|
1249
|
+
value: "web-widget"
|
|
1250
|
+
},
|
|
882
1251
|
{
|
|
883
1252
|
label: "Web-App (React + MUI)",
|
|
884
1253
|
value: "web-app"
|
|
@@ -1070,12 +1439,62 @@ var isFileRequired = (relativePath, type) => {
|
|
|
1070
1439
|
if (relativePath === "vitest.config.ts") return ![
|
|
1071
1440
|
"cli",
|
|
1072
1441
|
"web-vanilla",
|
|
1442
|
+
"web-widget",
|
|
1073
1443
|
"web-app",
|
|
1074
1444
|
"web-fullstack"
|
|
1075
1445
|
].includes(type);
|
|
1076
1446
|
return true;
|
|
1077
1447
|
};
|
|
1078
|
-
var isCustomConfigFile = (relativePath) => relativePath === "oxlint.config.ts" || relativePath === "oxfmt.config.ts";
|
|
1448
|
+
var isCustomConfigFile = (relativePath) => relativePath === "oxlint.config.ts" || relativePath === "oxfmt.config.ts" || relativePath === "stylelint.config.js";
|
|
1449
|
+
var toPosixPath = (filePath) => filePath.split(path.sep).join("/");
|
|
1450
|
+
var isOptionalCapabilityFile = (filePath) => {
|
|
1451
|
+
const normalizedPath = toPosixPath(filePath);
|
|
1452
|
+
return normalizedPath.startsWith(".github/workflows/") && normalizedPath !== ".github/workflows/ci.yml" && normalizedPath !== ".github/workflows/node.js.yml";
|
|
1453
|
+
};
|
|
1454
|
+
var isStarterFile = (filePath) => {
|
|
1455
|
+
const normalizedPath = toPosixPath(filePath);
|
|
1456
|
+
return isSeedFile(normalizedPath) || normalizedPath.startsWith("tests/e2e/") || normalizedPath.startsWith("src/demo/") || normalizedPath.endsWith(".test.ts") || normalizedPath.endsWith(".test.tsx") || normalizedPath.endsWith(".spec.ts") || normalizedPath.endsWith(".spec.tsx");
|
|
1457
|
+
};
|
|
1458
|
+
var getAddReason = (filePath) => {
|
|
1459
|
+
const normalizedPath = toPosixPath(filePath);
|
|
1460
|
+
if (normalizedPath === "package.json") return "Initial project metadata, dependencies, and scripts";
|
|
1461
|
+
if (normalizedPath === ".npmrc") return "Package manager configuration required by the template";
|
|
1462
|
+
if (normalizedPath === "pnpm-workspace.yaml") return "Workspace configuration required by pnpm";
|
|
1463
|
+
if (normalizedPath === ".github/workflows/ci.yml") return "Continuous integration workflow required by the base template";
|
|
1464
|
+
if (normalizedPath.startsWith(".github/workflows/")) return "GitHub workflow provided by the template";
|
|
1465
|
+
if (normalizedPath.includes("lint") || normalizedPath.includes("oxc") || normalizedPath.includes("oxfmt") || normalizedPath.includes("stylelint")) return "Linting and formatting configuration required by the template";
|
|
1466
|
+
if (normalizedPath.includes("tsconfig") || normalizedPath.includes("vite") || normalizedPath.includes("tsdown") || normalizedPath.includes("playwright") || normalizedPath.includes("typedoc")) return "Build, test, or documentation tooling configuration required by the template";
|
|
1467
|
+
return "Template tooling file required for this project type";
|
|
1468
|
+
};
|
|
1469
|
+
var getModifyReason = (filePath) => {
|
|
1470
|
+
const normalizedPath = toPosixPath(filePath);
|
|
1471
|
+
if (normalizedPath === "package.json") return "Update dependencies, scripts, and template metadata";
|
|
1472
|
+
if (normalizedPath === "pnpm-workspace.yaml") return "Update workspace configuration for pnpm";
|
|
1473
|
+
if (normalizedPath === ".github/workflows/ci.yml" || normalizedPath === ".github/workflows/node.js.yml") return "Update continuous integration workflow to match the base template";
|
|
1474
|
+
if (normalizedPath.startsWith(".github/workflows/")) return "Update GitHub workflow to match the template";
|
|
1475
|
+
if (normalizedPath.includes("lint") || normalizedPath.includes("oxc") || normalizedPath.includes("oxfmt") || normalizedPath.includes("stylelint")) return "Update linting and formatting configuration";
|
|
1476
|
+
if (normalizedPath.includes("tsconfig") || normalizedPath.includes("vite") || normalizedPath.includes("tsdown") || normalizedPath.includes("playwright") || normalizedPath.includes("typedoc")) return "Update build, test, or documentation tooling configuration";
|
|
1477
|
+
return "Update template-managed tooling or configuration";
|
|
1478
|
+
};
|
|
1479
|
+
var getDeleteReason = (filePath) => {
|
|
1480
|
+
if (toPosixPath(filePath) === "vitest.config.ts") return "File no longer required because this template uses Vite/Vitest configuration";
|
|
1481
|
+
return "File no longer required for this template type";
|
|
1482
|
+
};
|
|
1483
|
+
var getUpdateFilePolicy = (filePath) => {
|
|
1484
|
+
if (isOptionalCapabilityFile(filePath)) return {
|
|
1485
|
+
shouldAddDuringUpdate: false,
|
|
1486
|
+
reason: "Optional capability file - available but skipped during update"
|
|
1487
|
+
};
|
|
1488
|
+
if (isStarterFile(filePath)) return {
|
|
1489
|
+
shouldAddDuringUpdate: false,
|
|
1490
|
+
reason: "Starter/example project file - available but skipped during update"
|
|
1491
|
+
};
|
|
1492
|
+
return {
|
|
1493
|
+
shouldAddDuringUpdate: true,
|
|
1494
|
+
reason: getAddReason(filePath)
|
|
1495
|
+
};
|
|
1496
|
+
};
|
|
1497
|
+
var formatActionSummary = (actions) => actions.map((a) => ` ${a.type.padEnd(8)} ${a.path}${a.reason === void 0 ? "" : ` - ${a.reason}`}`).join("\n");
|
|
1079
1498
|
var buildSimpleUnifiedDiff = (filePath, before, after) => {
|
|
1080
1499
|
const beforeLines = before.split("\n");
|
|
1081
1500
|
const afterLines = after.split("\n");
|
|
@@ -1099,7 +1518,12 @@ var buildSimpleUnifiedDiff = (filePath, before, after) => {
|
|
|
1099
1518
|
};
|
|
1100
1519
|
var getTemplateArchitectureSection;
|
|
1101
1520
|
var generateGeneratedMd;
|
|
1102
|
-
var isTemplateType = (value) => value === "cli" || value === "web-vanilla" || value === "web-app" || value === "web-fullstack";
|
|
1521
|
+
var isTemplateType = (value) => value === "cli" || value === "web-vanilla" || value === "web-widget" || value === "web-app" || value === "web-fullstack";
|
|
1522
|
+
var getMissingProjectConfigMessage = (pkgPath) => [
|
|
1523
|
+
`No "create-template-project" configuration found in ${pkgPath}.`,
|
|
1524
|
+
"The update command only runs after a project has been adopted by this tool.",
|
|
1525
|
+
"Run \"create-template-project doctor --template <type> --directory <dir>\" to validate the structure, then \"create-template-project adopt --template <type> --directory <dir>\" to add the marker."
|
|
1526
|
+
].join(" ");
|
|
1103
1527
|
var isRecord$1 = (value) => typeof value === "object" && value !== null;
|
|
1104
1528
|
var parseStringRecord = (value) => {
|
|
1105
1529
|
if (!isRecord$1(value)) return {};
|
|
@@ -1131,9 +1555,38 @@ var parseExistingProjectPackage = (raw) => {
|
|
|
1131
1555
|
scripts: parseStringRecord(parsed.scripts),
|
|
1132
1556
|
dependencies: parseStringRecord(parsed.dependencies),
|
|
1133
1557
|
devDependencies: parseStringRecord(parsed.devDependencies),
|
|
1558
|
+
peerDependencies: parseStringRecord(parsed.peerDependencies),
|
|
1134
1559
|
"create-template-project": typeof template === "string" && isTemplateType(template) ? { template } : void 0
|
|
1135
1560
|
};
|
|
1136
1561
|
};
|
|
1562
|
+
var parseExactVersion = (version) => {
|
|
1563
|
+
const match = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:[-+].*)?$/u.exec(version);
|
|
1564
|
+
if (match?.groups === void 0) return;
|
|
1565
|
+
return [
|
|
1566
|
+
Number(match.groups.major),
|
|
1567
|
+
Number(match.groups.minor),
|
|
1568
|
+
Number(match.groups.patch)
|
|
1569
|
+
];
|
|
1570
|
+
};
|
|
1571
|
+
var isGreaterOrEqualVersion = (current, next) => {
|
|
1572
|
+
const currentParts = parseExactVersion(current);
|
|
1573
|
+
const nextParts = parseExactVersion(next);
|
|
1574
|
+
if (currentParts === void 0 || nextParts === void 0) return false;
|
|
1575
|
+
for (const [index, currentPart] of currentParts.entries()) {
|
|
1576
|
+
const nextPart = nextParts[index];
|
|
1577
|
+
if (currentPart > nextPart) return true;
|
|
1578
|
+
if (currentPart < nextPart) return false;
|
|
1579
|
+
}
|
|
1580
|
+
return true;
|
|
1581
|
+
};
|
|
1582
|
+
var mergePackageRecord = (target, source, preserveExistingVersions) => {
|
|
1583
|
+
const merged = { ...target };
|
|
1584
|
+
for (const [name, version] of Object.entries(source)) {
|
|
1585
|
+
if (preserveExistingVersions && Object.hasOwn(merged, name) && isGreaterOrEqualVersion(merged[name], version)) continue;
|
|
1586
|
+
merged[name] = version;
|
|
1587
|
+
}
|
|
1588
|
+
return merged;
|
|
1589
|
+
};
|
|
1137
1590
|
var parseTemplatePackageJson = (raw) => {
|
|
1138
1591
|
const parsed = JSON.parse(raw);
|
|
1139
1592
|
if (!isRecord$1(parsed)) return {};
|
|
@@ -1180,16 +1633,19 @@ var generateProject = async (opts) => {
|
|
|
1180
1633
|
const depConfig = parseDependencyConfig(await fs.readFile(depConfigPath, "utf8"));
|
|
1181
1634
|
const addedDeps = [];
|
|
1182
1635
|
const resolveDeps = (deps = {}) => {
|
|
1183
|
-
for (const dep of Object.
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1636
|
+
for (const [dep, version] of Object.entries(deps)) {
|
|
1637
|
+
if (version.length > 0 && version !== dep) continue;
|
|
1638
|
+
if (Object.hasOwn(depConfig.dependencies, dep)) {
|
|
1639
|
+
const config = depConfig.dependencies[dep];
|
|
1640
|
+
deps[dep] = config.version;
|
|
1641
|
+
addedDeps.push({
|
|
1642
|
+
name: dep,
|
|
1643
|
+
description: config.description
|
|
1644
|
+
});
|
|
1645
|
+
} else {
|
|
1646
|
+
log.warn(`Dependency "${dep}" not found in central configuration. Using empty version.`);
|
|
1647
|
+
debug$1(`Dependency "${dep}" missing in config`);
|
|
1648
|
+
}
|
|
1193
1649
|
}
|
|
1194
1650
|
};
|
|
1195
1651
|
let finalPkg = {
|
|
@@ -1215,18 +1671,30 @@ var generateProject = async (opts) => {
|
|
|
1215
1671
|
const pkgPath = path.join(projectDir, "package.json");
|
|
1216
1672
|
if (isUpdate && await pathExists(pkgPath)) {
|
|
1217
1673
|
debug$1("Loading existing package.json for update");
|
|
1674
|
+
const defaultPkg = finalPkg;
|
|
1218
1675
|
const existingPkg = parseExistingProjectPackage(await fs.readFile(pkgPath, "utf8"));
|
|
1219
|
-
if (!existingPkg["create-template-project"]) throw new Error(
|
|
1676
|
+
if (!existingPkg["create-template-project"]) throw new Error(getMissingProjectConfigMessage(pkgPath));
|
|
1220
1677
|
finalPkg = {
|
|
1221
|
-
...finalPkg,
|
|
1222
1678
|
...existingPkg,
|
|
1679
|
+
name: existingPkg.name ?? defaultPkg.name,
|
|
1680
|
+
version: existingPkg.version ?? defaultPkg.version,
|
|
1681
|
+
private: existingPkg.private,
|
|
1682
|
+
description: existingPkg.description ?? defaultPkg.description,
|
|
1683
|
+
keywords: existingPkg.keywords ?? defaultPkg.keywords,
|
|
1684
|
+
homepage: existingPkg.homepage ?? defaultPkg.homepage,
|
|
1685
|
+
bugs: existingPkg.bugs ?? defaultPkg.bugs,
|
|
1686
|
+
license: existingPkg.license ?? defaultPkg.license,
|
|
1687
|
+
author: existingPkg.author ?? defaultPkg.author,
|
|
1688
|
+
repository: existingPkg.repository ?? defaultPkg.repository,
|
|
1689
|
+
type: existingPkg.type ?? defaultPkg.type,
|
|
1223
1690
|
"create-template-project": {
|
|
1224
1691
|
...existingPkg["create-template-project"],
|
|
1225
1692
|
template: type
|
|
1226
1693
|
},
|
|
1227
1694
|
scripts: { ...existingPkg.scripts },
|
|
1228
1695
|
dependencies: { ...existingPkg.dependencies },
|
|
1229
|
-
devDependencies: { ...existingPkg.devDependencies }
|
|
1696
|
+
devDependencies: { ...existingPkg.devDependencies },
|
|
1697
|
+
peerDependencies: { ...existingPkg.peerDependencies }
|
|
1230
1698
|
};
|
|
1231
1699
|
debug$1("Loaded existing package.json: %O", finalPkg);
|
|
1232
1700
|
}
|
|
@@ -1243,14 +1711,17 @@ var generateProject = async (opts) => {
|
|
|
1243
1711
|
resolveDeps(templateDeps);
|
|
1244
1712
|
resolveDeps(templateDevDeps);
|
|
1245
1713
|
Object.assign(finalPkg.scripts, t.scripts);
|
|
1246
|
-
|
|
1247
|
-
|
|
1714
|
+
finalPkg.dependencies = mergePackageRecord(finalPkg.dependencies, templateDeps, isUpdate);
|
|
1715
|
+
finalPkg.devDependencies = mergePackageRecord(finalPkg.devDependencies, templateDevDeps, isUpdate);
|
|
1248
1716
|
if (t.workspaces !== void 0) finalPkg.workspaces = t.workspaces;
|
|
1249
1717
|
const pkgPart = templatePackageParts[index];
|
|
1250
1718
|
if (pkgPart !== void 0) {
|
|
1251
1719
|
resolveDeps(pkgPart.dependencies);
|
|
1252
1720
|
resolveDeps(pkgPart.devDependencies);
|
|
1253
|
-
mergePackageJson(finalPkg, pkgPart
|
|
1721
|
+
mergePackageJson(finalPkg, pkgPart, {
|
|
1722
|
+
preserveExistingPackageFields: isUpdate,
|
|
1723
|
+
preserveExistingDependencyVersions: isUpdate
|
|
1724
|
+
});
|
|
1254
1725
|
}
|
|
1255
1726
|
}
|
|
1256
1727
|
const actions = [];
|
|
@@ -1274,13 +1745,22 @@ var generateProject = async (opts) => {
|
|
|
1274
1745
|
}
|
|
1275
1746
|
if (!isFileRequired(relativePath, type)) {
|
|
1276
1747
|
if (isUpdate && await pathExists(targetPath)) {
|
|
1748
|
+
const existingContent = await fs.readFile(targetPath, "utf8");
|
|
1749
|
+
plannedDiffs.push({
|
|
1750
|
+
path: relativePath,
|
|
1751
|
+
before: existingContent,
|
|
1752
|
+
after: ""
|
|
1753
|
+
});
|
|
1277
1754
|
actions.push({
|
|
1278
1755
|
type: "DELETE",
|
|
1279
1756
|
path: relativePath,
|
|
1280
|
-
reason:
|
|
1757
|
+
reason: getDeleteReason(relativePath)
|
|
1281
1758
|
});
|
|
1282
|
-
pendingOperations.push(
|
|
1283
|
-
|
|
1759
|
+
pendingOperations.push({
|
|
1760
|
+
path: relativePath,
|
|
1761
|
+
run: async () => {
|
|
1762
|
+
await fs.rm(targetPath, { force: true });
|
|
1763
|
+
}
|
|
1284
1764
|
});
|
|
1285
1765
|
}
|
|
1286
1766
|
continue;
|
|
@@ -1289,7 +1769,7 @@ var generateProject = async (opts) => {
|
|
|
1289
1769
|
relativePath = relativePath.slice(1);
|
|
1290
1770
|
targetPath = path.join(projectDir, relativePath);
|
|
1291
1771
|
}
|
|
1292
|
-
if (isUpdate && (relativePath
|
|
1772
|
+
if (isUpdate && isCustomConfigFile(relativePath)) {
|
|
1293
1773
|
actions.push({
|
|
1294
1774
|
type: "SKIP",
|
|
1295
1775
|
path: relativePath,
|
|
@@ -1313,36 +1793,50 @@ var generateProject = async (opts) => {
|
|
|
1313
1793
|
const action = {
|
|
1314
1794
|
type: "MODIFY",
|
|
1315
1795
|
path: finalRelativePath,
|
|
1316
|
-
reason:
|
|
1796
|
+
reason: getModifyReason(finalRelativePath)
|
|
1317
1797
|
};
|
|
1318
1798
|
actions.push(action);
|
|
1319
|
-
pendingOperations.push(
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
log.info(`✔ Updated: ${finalRelativePath}`);
|
|
1325
|
-
} else {
|
|
1326
|
-
const result = await mergeFile(finalTargetPath, existingContent, content, log);
|
|
1327
|
-
if (result === "merged") {
|
|
1328
|
-
action.type = "MERGE";
|
|
1329
|
-
action.reason = "Merged template updates with your manual changes";
|
|
1330
|
-
action.recommendedAction = "Review changes for correct integration";
|
|
1331
|
-
log.info(`ℹ Merged: ${finalRelativePath}`);
|
|
1332
|
-
} else if (result === "conflict") {
|
|
1333
|
-
action.type = "CONFLICT";
|
|
1334
|
-
action.reason = "Conflicting changes between template and your code";
|
|
1335
|
-
action.recommendedAction = "Resolve git conflict markers in this file";
|
|
1336
|
-
log.warn(`⚠ Conflict: ${finalRelativePath}`);
|
|
1337
|
-
} else if (result === "updated") {
|
|
1799
|
+
pendingOperations.push({
|
|
1800
|
+
path: finalRelativePath,
|
|
1801
|
+
run: async () => {
|
|
1802
|
+
if (finalRelativePath === "oxc.config.ts") {
|
|
1803
|
+
await fs.writeFile(finalTargetPath, content);
|
|
1338
1804
|
action.type = "UPDATED";
|
|
1339
|
-
action.reason = "
|
|
1805
|
+
action.reason = "Default config was updated to the latest template version";
|
|
1340
1806
|
log.info(`✔ Updated: ${finalRelativePath}`);
|
|
1807
|
+
} else {
|
|
1808
|
+
const result = await mergeFile(finalTargetPath, existingContent, content, log);
|
|
1809
|
+
if (result === "merged") {
|
|
1810
|
+
action.type = "MERGE";
|
|
1811
|
+
action.reason = "Merged template updates with your manual changes";
|
|
1812
|
+
action.recommendedAction = "Review changes for correct integration";
|
|
1813
|
+
log.info(`ℹ Merged: ${finalRelativePath}`);
|
|
1814
|
+
} else if (result === "conflict") {
|
|
1815
|
+
action.type = "CONFLICT";
|
|
1816
|
+
action.reason = "Conflicting changes between template and your code";
|
|
1817
|
+
action.recommendedAction = "Resolve git conflict markers in this file";
|
|
1818
|
+
log.warn(`⚠ Conflict: ${finalRelativePath}`);
|
|
1819
|
+
} else if (result === "updated") {
|
|
1820
|
+
action.type = "UPDATED";
|
|
1821
|
+
action.reason = "File was updated to the latest template version";
|
|
1822
|
+
log.info(`✔ Updated: ${finalRelativePath}`);
|
|
1823
|
+
}
|
|
1341
1824
|
}
|
|
1342
1825
|
}
|
|
1343
1826
|
});
|
|
1344
1827
|
}
|
|
1345
1828
|
} else if (!exists) {
|
|
1829
|
+
if (isUpdate) {
|
|
1830
|
+
const policy = getUpdateFilePolicy(finalRelativePath);
|
|
1831
|
+
if (!policy.shouldAddDuringUpdate) {
|
|
1832
|
+
actions.push({
|
|
1833
|
+
type: "SKIP",
|
|
1834
|
+
path: finalRelativePath,
|
|
1835
|
+
reason: policy.reason
|
|
1836
|
+
});
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1346
1840
|
plannedDiffs.push({
|
|
1347
1841
|
path: finalRelativePath,
|
|
1348
1842
|
before: "",
|
|
@@ -1351,13 +1845,16 @@ var generateProject = async (opts) => {
|
|
|
1351
1845
|
actions.push({
|
|
1352
1846
|
type: "ADD",
|
|
1353
1847
|
path: finalRelativePath,
|
|
1354
|
-
reason:
|
|
1848
|
+
reason: getAddReason(finalRelativePath)
|
|
1355
1849
|
});
|
|
1356
|
-
pendingOperations.push(
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1850
|
+
pendingOperations.push({
|
|
1851
|
+
path: finalRelativePath,
|
|
1852
|
+
run: async () => {
|
|
1853
|
+
await fs.mkdir(path.dirname(finalTargetPath), { recursive: true });
|
|
1854
|
+
await fs.writeFile(finalTargetPath, content);
|
|
1855
|
+
const normalizedPath = finalRelativePath.split(path.sep).join("/");
|
|
1856
|
+
if (normalizedPath.startsWith(".husky/") && !normalizedPath.endsWith("/")) await fs.chmod(finalTargetPath, 493);
|
|
1857
|
+
}
|
|
1361
1858
|
});
|
|
1362
1859
|
}
|
|
1363
1860
|
}
|
|
@@ -1382,13 +1879,22 @@ var generateProject = async (opts) => {
|
|
|
1382
1879
|
}
|
|
1383
1880
|
if (!isFileRequired(file.path, type)) {
|
|
1384
1881
|
if (isUpdate && await pathExists(targetPath)) {
|
|
1882
|
+
const existingContent = await fs.readFile(targetPath, "utf8");
|
|
1883
|
+
plannedDiffs.push({
|
|
1884
|
+
path: file.path,
|
|
1885
|
+
before: existingContent,
|
|
1886
|
+
after: ""
|
|
1887
|
+
});
|
|
1385
1888
|
actions.push({
|
|
1386
1889
|
type: "DELETE",
|
|
1387
1890
|
path: file.path,
|
|
1388
|
-
reason:
|
|
1891
|
+
reason: getDeleteReason(file.path)
|
|
1389
1892
|
});
|
|
1390
|
-
pendingOperations.push(
|
|
1391
|
-
|
|
1893
|
+
pendingOperations.push({
|
|
1894
|
+
path: file.path,
|
|
1895
|
+
run: async () => {
|
|
1896
|
+
await fs.rm(targetPath, { force: true });
|
|
1897
|
+
}
|
|
1392
1898
|
});
|
|
1393
1899
|
}
|
|
1394
1900
|
continue;
|
|
@@ -1407,36 +1913,50 @@ var generateProject = async (opts) => {
|
|
|
1407
1913
|
const action = {
|
|
1408
1914
|
type: "MODIFY",
|
|
1409
1915
|
path: file.path,
|
|
1410
|
-
reason:
|
|
1916
|
+
reason: getModifyReason(file.path)
|
|
1411
1917
|
};
|
|
1412
1918
|
actions.push(action);
|
|
1413
|
-
pendingOperations.push(
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
log.info(`✔ Updated: ${file.path}`);
|
|
1419
|
-
} else {
|
|
1420
|
-
const result = await mergeFile(targetPath, existingContent, content, log);
|
|
1421
|
-
if (result === "merged") {
|
|
1422
|
-
action.type = "MERGE";
|
|
1423
|
-
action.reason = "Merged template updates with your manual changes";
|
|
1424
|
-
action.recommendedAction = "Review changes for correct integration";
|
|
1425
|
-
log.info(`ℹ Merged: ${file.path}`);
|
|
1426
|
-
} else if (result === "conflict") {
|
|
1427
|
-
action.type = "CONFLICT";
|
|
1428
|
-
action.reason = "Conflicting changes between template and your code";
|
|
1429
|
-
action.recommendedAction = "Resolve git conflict markers in this file";
|
|
1430
|
-
log.warn(`⚠ Conflict: ${file.path}`);
|
|
1431
|
-
} else if (result === "updated") {
|
|
1919
|
+
pendingOperations.push({
|
|
1920
|
+
path: file.path,
|
|
1921
|
+
run: async () => {
|
|
1922
|
+
if (file.path === "oxc.config.ts") {
|
|
1923
|
+
await fs.writeFile(targetPath, content);
|
|
1432
1924
|
action.type = "UPDATED";
|
|
1433
|
-
action.reason = "
|
|
1925
|
+
action.reason = "Default config was updated to the latest template version";
|
|
1434
1926
|
log.info(`✔ Updated: ${file.path}`);
|
|
1927
|
+
} else {
|
|
1928
|
+
const result = await mergeFile(targetPath, existingContent, content, log);
|
|
1929
|
+
if (result === "merged") {
|
|
1930
|
+
action.type = "MERGE";
|
|
1931
|
+
action.reason = "Merged template updates with your manual changes";
|
|
1932
|
+
action.recommendedAction = "Review changes for correct integration";
|
|
1933
|
+
log.info(`ℹ Merged: ${file.path}`);
|
|
1934
|
+
} else if (result === "conflict") {
|
|
1935
|
+
action.type = "CONFLICT";
|
|
1936
|
+
action.reason = "Conflicting changes between template and your code";
|
|
1937
|
+
action.recommendedAction = "Resolve git conflict markers in this file";
|
|
1938
|
+
log.warn(`⚠ Conflict: ${file.path}`);
|
|
1939
|
+
} else if (result === "updated") {
|
|
1940
|
+
action.type = "UPDATED";
|
|
1941
|
+
action.reason = "File was updated to the latest template version";
|
|
1942
|
+
log.info(`✔ Updated: ${file.path}`);
|
|
1943
|
+
}
|
|
1435
1944
|
}
|
|
1436
1945
|
}
|
|
1437
1946
|
});
|
|
1438
1947
|
}
|
|
1439
1948
|
} else if (!exists) {
|
|
1949
|
+
if (isUpdate) {
|
|
1950
|
+
const policy = getUpdateFilePolicy(file.path);
|
|
1951
|
+
if (!policy.shouldAddDuringUpdate) {
|
|
1952
|
+
actions.push({
|
|
1953
|
+
type: "SKIP",
|
|
1954
|
+
path: file.path,
|
|
1955
|
+
reason: policy.reason
|
|
1956
|
+
});
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1440
1960
|
plannedDiffs.push({
|
|
1441
1961
|
path: file.path,
|
|
1442
1962
|
before: "",
|
|
@@ -1445,11 +1965,14 @@ var generateProject = async (opts) => {
|
|
|
1445
1965
|
actions.push({
|
|
1446
1966
|
type: "ADD",
|
|
1447
1967
|
path: file.path,
|
|
1448
|
-
reason:
|
|
1968
|
+
reason: getAddReason(file.path)
|
|
1449
1969
|
});
|
|
1450
|
-
pendingOperations.push(
|
|
1451
|
-
|
|
1452
|
-
|
|
1970
|
+
pendingOperations.push({
|
|
1971
|
+
path: file.path,
|
|
1972
|
+
run: async () => {
|
|
1973
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
1974
|
+
await fs.writeFile(targetPath, content);
|
|
1975
|
+
}
|
|
1453
1976
|
});
|
|
1454
1977
|
}
|
|
1455
1978
|
}
|
|
@@ -1473,10 +1996,13 @@ var generateProject = async (opts) => {
|
|
|
1473
1996
|
actions.push({
|
|
1474
1997
|
type: "ADD",
|
|
1475
1998
|
path: ".npmrc",
|
|
1476
|
-
reason: "
|
|
1999
|
+
reason: getAddReason(".npmrc")
|
|
1477
2000
|
});
|
|
1478
|
-
pendingOperations.push(
|
|
1479
|
-
|
|
2001
|
+
pendingOperations.push({
|
|
2002
|
+
path: ".npmrc",
|
|
2003
|
+
run: async () => {
|
|
2004
|
+
await fs.writeFile(npmrcPath, npmrcContent);
|
|
2005
|
+
}
|
|
1480
2006
|
});
|
|
1481
2007
|
}
|
|
1482
2008
|
if (pm === "pnpm" && finalPkg.workspaces) {
|
|
@@ -1496,10 +2022,13 @@ var generateProject = async (opts) => {
|
|
|
1496
2022
|
actions.push({
|
|
1497
2023
|
type: workspaceExists ? "MODIFY" : "ADD",
|
|
1498
2024
|
path: "pnpm-workspace.yaml",
|
|
1499
|
-
reason: "
|
|
2025
|
+
reason: workspaceExists ? getModifyReason("pnpm-workspace.yaml") : getAddReason("pnpm-workspace.yaml")
|
|
1500
2026
|
});
|
|
1501
|
-
pendingOperations.push(
|
|
1502
|
-
|
|
2027
|
+
pendingOperations.push({
|
|
2028
|
+
path: "pnpm-workspace.yaml",
|
|
2029
|
+
run: async () => {
|
|
2030
|
+
await fs.writeFile(workspacePath, workspaceYaml);
|
|
2031
|
+
}
|
|
1503
2032
|
});
|
|
1504
2033
|
}
|
|
1505
2034
|
delete finalPkg.workspaces;
|
|
@@ -1521,35 +2050,50 @@ var generateProject = async (opts) => {
|
|
|
1521
2050
|
if (isUpdate) actions.push({
|
|
1522
2051
|
type: "MODIFY",
|
|
1523
2052
|
path: "package.json",
|
|
1524
|
-
reason: "
|
|
2053
|
+
reason: getModifyReason("package.json")
|
|
1525
2054
|
});
|
|
1526
2055
|
else actions.push({
|
|
1527
2056
|
type: "ADD",
|
|
1528
2057
|
path: "package.json",
|
|
1529
|
-
reason: "
|
|
2058
|
+
reason: getAddReason("package.json")
|
|
1530
2059
|
});
|
|
1531
|
-
pendingOperations.push(
|
|
1532
|
-
|
|
1533
|
-
|
|
2060
|
+
pendingOperations.push({
|
|
2061
|
+
path: "package.json",
|
|
2062
|
+
run: async () => {
|
|
2063
|
+
debug$1("Writing final consolidated package.json to: %s", pkgPath);
|
|
2064
|
+
await fs.writeFile(pkgPath, newPkgContent);
|
|
2065
|
+
}
|
|
1534
2066
|
});
|
|
1535
2067
|
}
|
|
1536
2068
|
if (isUpdate && actions.length > 0 && process.env.NODE_ENV !== "test") {
|
|
1537
|
-
const summary = actions
|
|
2069
|
+
const summary = formatActionSummary(actions);
|
|
1538
2070
|
if (summary) {
|
|
1539
2071
|
const relativeProjectDir = path.relative(process.cwd(), projectDir) || ".";
|
|
1540
2072
|
p.note(summary, `Planned changes in ${relativeProjectDir}:`);
|
|
2073
|
+
const syncedPaths = /* @__PURE__ */ new Set();
|
|
2074
|
+
const syncFile = async (filePath) => {
|
|
2075
|
+
const operation = pendingOperations.find((candidate) => candidate.path === filePath && !syncedPaths.has(candidate.path));
|
|
2076
|
+
if (operation === void 0) {
|
|
2077
|
+
p.note("This file has no pending sync operation.", `Sync: ${filePath}`);
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
await operation.run();
|
|
2081
|
+
syncedPaths.add(filePath);
|
|
2082
|
+
p.log.success(`Synced ${filePath}`);
|
|
2083
|
+
};
|
|
1541
2084
|
let confirmed = false;
|
|
1542
2085
|
while (!confirmed) {
|
|
1543
|
-
const
|
|
1544
|
-
message: "Choose
|
|
2086
|
+
const selectedFile = await p.select({
|
|
2087
|
+
message: "Choose file to inspect or sync:",
|
|
1545
2088
|
options: [
|
|
2089
|
+
...plannedDiffs.map((entry) => ({
|
|
2090
|
+
label: entry.path,
|
|
2091
|
+
value: entry.path,
|
|
2092
|
+
hint: `${syncedPaths.has(entry.path) ? "synced; " : ""}${actions.find((candidate) => candidate.path === entry.path)?.reason ?? "Planned file change"}`
|
|
2093
|
+
})),
|
|
1546
2094
|
{
|
|
1547
|
-
label: "
|
|
1548
|
-
value: "
|
|
1549
|
-
},
|
|
1550
|
-
{
|
|
1551
|
-
label: "Apply changes",
|
|
1552
|
-
value: "apply"
|
|
2095
|
+
label: "Continue update",
|
|
2096
|
+
value: "continue-update"
|
|
1553
2097
|
},
|
|
1554
2098
|
{
|
|
1555
2099
|
label: "Cancel update",
|
|
@@ -1557,24 +2101,57 @@ var generateProject = async (opts) => {
|
|
|
1557
2101
|
}
|
|
1558
2102
|
]
|
|
1559
2103
|
});
|
|
1560
|
-
if (p.isCancel(
|
|
2104
|
+
if (p.isCancel(selectedFile) || selectedFile === "cancel") {
|
|
1561
2105
|
p.cancel("Update cancelled.");
|
|
1562
2106
|
process.exit(0);
|
|
1563
2107
|
}
|
|
1564
|
-
if (
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
2108
|
+
if (selectedFile === "continue-update") {
|
|
2109
|
+
confirmed = true;
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
const fileActionOptions = syncedPaths.has(selectedFile) ? [{
|
|
2113
|
+
label: "Show diff",
|
|
2114
|
+
value: "show-diff"
|
|
2115
|
+
}, {
|
|
2116
|
+
label: "Back",
|
|
2117
|
+
value: "back"
|
|
2118
|
+
}] : [
|
|
2119
|
+
{
|
|
2120
|
+
label: "Show diff",
|
|
2121
|
+
value: "show-diff"
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
label: "Sync file",
|
|
2125
|
+
value: "sync-file"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
label: "Back",
|
|
2129
|
+
value: "back"
|
|
2130
|
+
}
|
|
2131
|
+
];
|
|
2132
|
+
const fileAction = await p.select({
|
|
2133
|
+
message: `Choose action for ${selectedFile}:`,
|
|
2134
|
+
options: fileActionOptions
|
|
2135
|
+
});
|
|
2136
|
+
if (p.isCancel(fileAction) || fileAction === "back") continue;
|
|
2137
|
+
if (fileAction === "show-diff") {
|
|
2138
|
+
const selectedDiff = plannedDiffs.find((entry) => entry.path === selectedFile);
|
|
2139
|
+
if (selectedDiff !== void 0) {
|
|
2140
|
+
const diff = buildSimpleUnifiedDiff(selectedDiff.path, selectedDiff.before, selectedDiff.after);
|
|
2141
|
+
p.note(diff, `Diff preview: ${selectedDiff.path}`);
|
|
1568
2142
|
}
|
|
1569
2143
|
continue;
|
|
1570
2144
|
}
|
|
1571
|
-
|
|
2145
|
+
if (fileAction === "sync-file") await syncFile(selectedFile);
|
|
1572
2146
|
}
|
|
2147
|
+
await pendingOperations.filter((operation) => !syncedPaths.has(operation.path)).reduce(async (previous, operation) => {
|
|
2148
|
+
await previous;
|
|
2149
|
+
await operation.run();
|
|
2150
|
+
}, Promise.resolve());
|
|
1573
2151
|
} else log.info("No changes detected.");
|
|
1574
|
-
}
|
|
1575
|
-
await pendingOperations.reduce(async (previous, operation) => {
|
|
2152
|
+
} else await pendingOperations.reduce(async (previous, operation) => {
|
|
1576
2153
|
await previous;
|
|
1577
|
-
await operation();
|
|
2154
|
+
await operation.run();
|
|
1578
2155
|
}, Promise.resolve());
|
|
1579
2156
|
const states = {
|
|
1580
2157
|
gitInitialized: false,
|
|
@@ -1911,6 +2488,20 @@ getTemplateArchitectureSection = (template) => {
|
|
|
1911
2488
|
"- Add new UI logic or Web Components inside the `src/` directory.",
|
|
1912
2489
|
"- Create styling (`.css` or `.scss`) and import them directly into `main.ts`."
|
|
1913
2490
|
];
|
|
2491
|
+
case "web-widget": return [
|
|
2492
|
+
"## 🏗️ Web Widget Architecture",
|
|
2493
|
+
"A publishable native TypeScript widget package with a Vite-powered demo site.",
|
|
2494
|
+
"",
|
|
2495
|
+
"### Source Files Generated",
|
|
2496
|
+
"- **`src/lib/`**: The public widget implementation, exports, and optional React adapter subpath.",
|
|
2497
|
+
"- **`src/styles/index.css`**: The exported widget stylesheet available through the package `./styles.css` subpath.",
|
|
2498
|
+
"- **`src/demo/`**: The demo application used by Vite, Playwright, and screenshot generation.",
|
|
2499
|
+
"",
|
|
2500
|
+
"### How to Enhance",
|
|
2501
|
+
"- Keep reusable package logic in `src/lib/` and demo-only behavior in `src/demo/`.",
|
|
2502
|
+
"- Update `tsdown.config.ts` and `package.json` exports together when adding public entry points.",
|
|
2503
|
+
"- Run `pnpm run docs:api` and `pnpm run screenshot` before publishing visible API or demo changes."
|
|
2504
|
+
];
|
|
1914
2505
|
case "web-app": return [
|
|
1915
2506
|
"## 🏗️ Web App Architecture",
|
|
1916
2507
|
"A robust React SPA configured with MUI components and TanStack Query.",
|