create-template-project 1.1.2 → 1.2.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 +11 -0
- package/dist/config/dependencies.json +15 -15
- package/dist/index.js +677 -483
- package/dist/templates/base/files/.release-it.json +1 -1
- package/dist/templates/base/files/_oxc.config.ts +212 -87
- package/dist/templates/cli/files/src/index.ts +1 -2
- package/dist/templates/cli/files/src/lib.ts +1 -3
- package/dist/templates/web-app/files/playwright.config.ts +7 -4
- package/dist/templates/web-app/files/src/App.tsx +11 -12
- package/dist/templates/web-app/files/src/index.tsx +1 -1
- package/dist/templates/web-app/files/tests/e2e/basic.e2e-test.ts +2 -2
- package/dist/templates/web-fullstack/files/client/src/App.tsx +8 -5
- package/dist/templates/web-fullstack/files/client/src/components/ProtectedRoute.tsx +5 -3
- package/dist/templates/web-fullstack/files/client/src/contexts/AuthContext.tsx +22 -18
- package/dist/templates/web-fullstack/files/client/src/main.tsx +8 -5
- package/dist/templates/web-fullstack/files/client/src/pages/Dashboard.tsx +12 -7
- package/dist/templates/web-fullstack/files/client/src/pages/Login.tsx +11 -7
- package/dist/templates/web-fullstack/files/client/src/trpc.ts +2 -1
- package/dist/templates/web-fullstack/files/playwright.config.ts +8 -5
- package/dist/templates/web-fullstack/files/server/src/context.ts +7 -5
- package/dist/templates/web-fullstack/files/server/src/index.ts +2 -3
- package/dist/templates/web-fullstack/files/server/src/routers/_app.ts +1 -0
- package/dist/templates/web-fullstack/files/server/src/routers/auth.ts +4 -4
- package/dist/templates/web-fullstack/files/server/src/trpc.ts +3 -4
- package/dist/templates/web-vanilla/files/playwright.config.ts +7 -4
- package/dist/templates/web-vanilla/files/src/index.test.ts +1 -1
- package/dist/templates/web-vanilla/files/src/index.ts +1 -1
- package/dist/templates/web-vanilla/files/src/lib.ts +1 -3
- package/package.json +13 -7
package/dist/index.js
CHANGED
|
@@ -7,9 +7,8 @@ import { execa } from "execa";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
import debugLib from "debug";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
10
|
import { existsSync } from "node:fs";
|
|
12
|
-
//#region src/types.ts
|
|
11
|
+
//#region src/shared/types.ts
|
|
13
12
|
var TemplateTypeSchema = z.enum([
|
|
14
13
|
"cli",
|
|
15
14
|
"web-vanilla",
|
|
@@ -39,8 +38,8 @@ var ProjectOptionsSchema = z.object({
|
|
|
39
38
|
//#region src/utils/templating/generic.ts
|
|
40
39
|
var genericProcessor = (content, { opts }) => {
|
|
41
40
|
const { projectName, template, author, githubUsername } = opts;
|
|
42
|
-
let description = opts.description
|
|
43
|
-
if (
|
|
41
|
+
let description = opts.description ?? "";
|
|
42
|
+
if (description.length === 0) switch (template) {
|
|
44
43
|
case "cli":
|
|
45
44
|
description = "A modern Node.js CLI application with TypeScript and automated tooling.";
|
|
46
45
|
break;
|
|
@@ -53,10 +52,11 @@ var genericProcessor = (content, { opts }) => {
|
|
|
53
52
|
case "web-app":
|
|
54
53
|
description = "A React application with MUI and TanStack Query.";
|
|
55
54
|
break;
|
|
55
|
+
default: break;
|
|
56
56
|
}
|
|
57
|
-
const pm = opts.packageManager
|
|
57
|
+
const pm = opts.packageManager;
|
|
58
58
|
const lockfileRules = pm === "pnpm" ? "package-lock.json\nyarn.lock" : pm === "yarn" ? "package-lock.json\npnpm-lock.yaml" : "yarn.lock\npnpm-lock.yaml";
|
|
59
|
-
return content.replaceAll("{{projectName}}", projectName).replaceAll("{{description}}", description).replaceAll("{{packageManager}}", pm).replaceAll("{{author}}", author
|
|
59
|
+
return content.replaceAll("{{projectName}}", projectName).replaceAll("{{description}}", description).replaceAll("{{packageManager}}", pm).replaceAll("{{author}}", author).replaceAll("{{githubUsername}}", githubUsername).replaceAll("{{year}}", (/* @__PURE__ */ new Date()).getFullYear().toString()).replaceAll("{{lockfileRules}}", lockfileRules);
|
|
60
60
|
};
|
|
61
61
|
//#endregion
|
|
62
62
|
//#region src/utils/templating/github-workflow.ts
|
|
@@ -69,7 +69,7 @@ var WORKFLOW_PLAYWRIGHT_SETUP = ` - name: Install Playwright Browsers & Dep
|
|
|
69
69
|
run: npx playwright install --with-deps chromium`;
|
|
70
70
|
var githubWorkflowProcessor = (content, { filePath, opts }) => {
|
|
71
71
|
if (!filePath.includes(".github/workflows/node.js.yml")) return content;
|
|
72
|
-
const { template, packageManager: pm
|
|
72
|
+
const { template, packageManager: pm } = opts;
|
|
73
73
|
let installCommand = "npm ci";
|
|
74
74
|
let pmSetup = "";
|
|
75
75
|
if (pm === "pnpm") {
|
|
@@ -99,7 +99,7 @@ var tsconfigProcessor = (content, { filePath, opts }) => {
|
|
|
99
99
|
if (filePath !== "tsconfig.json") return content;
|
|
100
100
|
const { template } = opts;
|
|
101
101
|
let processed = content;
|
|
102
|
-
if (template === "web-fullstack" || template === "web-vanilla" || template === "web-app") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, WEB_ENV
|
|
102
|
+
if (template === "web-fullstack" || template === "web-vanilla" || template === "web-app") processed = processed.replace(/\/\* Language and Environment \*\/[\s\S]*?\/\* Strict Type-Checking Options \*\//, `${WEB_ENV}\n\n\t\t/* Strict Type-Checking Options */`);
|
|
103
103
|
if (template === "web-fullstack") processed = processed.replace(/"include":\s*\[\s*"src\/\*\*\/\*"\s*\]/, "\"include\": [\"client/src/**/*\", \"server/src/**/*\"]");
|
|
104
104
|
return processed;
|
|
105
105
|
};
|
|
@@ -109,7 +109,7 @@ var contributingProcessor = (content, { filePath, addedDeps }) => {
|
|
|
109
109
|
if (filePath !== "CONTRIBUTING.md" || addedDeps.length === 0) return content;
|
|
110
110
|
let processed = content;
|
|
111
111
|
processed += "\n## Dependencies\n\n";
|
|
112
|
-
const uniqueDeps =
|
|
112
|
+
const uniqueDeps = [...new Map(addedDeps.map((dep) => [dep.name, dep])).values()];
|
|
113
113
|
for (const dep of uniqueDeps) processed += `- **${dep.name}**: ${dep.description}\n`;
|
|
114
114
|
return processed;
|
|
115
115
|
};
|
|
@@ -120,59 +120,94 @@ var webVanillaHtmlProcessor = (content, { filePath, opts }) => {
|
|
|
120
120
|
return content;
|
|
121
121
|
};
|
|
122
122
|
//#endregion
|
|
123
|
+
//#region src/utils/templating/oxc-config.ts
|
|
124
|
+
var oxcConfigProcessor = (content, { filePath, opts }) => {
|
|
125
|
+
if (filePath !== "oxc.config.ts") return content;
|
|
126
|
+
const shouldAddNodeEnv = opts.template === "cli" || opts.template === "web-fullstack";
|
|
127
|
+
const shouldAddBrowserEnv = opts.template === "web-vanilla" || opts.template === "web-app" || opts.template === "web-fullstack";
|
|
128
|
+
const linesToAdd = [];
|
|
129
|
+
if (shouldAddNodeEnv && !content.includes("node: true")) linesToAdd.push(" node: true,");
|
|
130
|
+
if (shouldAddBrowserEnv && !content.includes("browser: true")) linesToAdd.push(" browser: true,");
|
|
131
|
+
if (linesToAdd.length === 0) return content;
|
|
132
|
+
for (const envBlock of [" env: {\n builtin: true,\n", "env: {\n builtin: true,\n"]) if (content.includes(envBlock)) return content.replace(envBlock, `${envBlock}${linesToAdd.join("\n")}\n`);
|
|
133
|
+
return content;
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
123
136
|
//#region src/utils/templating/index.ts
|
|
124
137
|
var processors = [
|
|
125
138
|
genericProcessor,
|
|
126
139
|
githubWorkflowProcessor,
|
|
127
140
|
tsconfigProcessor,
|
|
128
141
|
contributingProcessor,
|
|
129
|
-
webVanillaHtmlProcessor
|
|
142
|
+
webVanillaHtmlProcessor,
|
|
143
|
+
oxcConfigProcessor
|
|
130
144
|
];
|
|
131
|
-
|
|
145
|
+
var processContent$1 = (filePath, content, opts, addedDeps) => {
|
|
132
146
|
const context = {
|
|
133
147
|
filePath,
|
|
134
148
|
opts,
|
|
135
149
|
addedDeps
|
|
136
150
|
};
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
let processed = content;
|
|
152
|
+
for (const processor of processors) processed = processor(processed, context);
|
|
153
|
+
return processed;
|
|
154
|
+
};
|
|
139
155
|
//#endregion
|
|
140
|
-
//#region src/
|
|
156
|
+
//#region src/shared/file.ts
|
|
141
157
|
var debug$3 = debugLib("create-template-project:utils:file");
|
|
142
|
-
|
|
158
|
+
var toErrorDetail$1 = (error) => {
|
|
159
|
+
if (error instanceof Error) {
|
|
160
|
+
const value = error;
|
|
161
|
+
const stdout = value.stdout ?? "";
|
|
162
|
+
const stderr = value.stderr ?? "";
|
|
163
|
+
const detail = stdout.length > 0 || stderr.length > 0 ? `\n\nOutput:\n${stdout}\n${stderr}` : "";
|
|
164
|
+
return {
|
|
165
|
+
message: error.message,
|
|
166
|
+
detail,
|
|
167
|
+
exitCode: value.exitCode
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
message: String(error),
|
|
172
|
+
detail: ""
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
var getTemplateDir = (dirname, templateName) => {
|
|
143
176
|
const sourcePath = path.resolve(dirname, "files");
|
|
144
177
|
const distPath = path.resolve(dirname, "templates", templateName, "files");
|
|
145
178
|
return existsSync(distPath) ? distPath : sourcePath;
|
|
146
|
-
}
|
|
147
|
-
async
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
};
|
|
180
|
+
var getAllFiles = async (dirPath, arrayOfFiles = []) => {
|
|
181
|
+
const filteredEntries = (await fs.readdir(dirPath, { withFileTypes: true })).filter((entry) => entry.name !== ".DS_Store");
|
|
182
|
+
const files = filteredEntries.filter((entry) => entry.isFile()).map((entry) => path.join(dirPath, entry.name));
|
|
183
|
+
const directories = filteredEntries.filter((entry) => entry.isDirectory());
|
|
184
|
+
const nestedResults = await Promise.all(directories.map(async (entry) => {
|
|
185
|
+
return await getAllFiles(path.join(dirPath, entry.name));
|
|
186
|
+
}));
|
|
187
|
+
return [
|
|
188
|
+
...arrayOfFiles,
|
|
189
|
+
...files,
|
|
190
|
+
...nestedResults.flat()
|
|
191
|
+
];
|
|
192
|
+
};
|
|
193
|
+
var processContent = (filePath, content, opts, addedDeps) => processContent$1(filePath, content, opts, addedDeps);
|
|
194
|
+
var mergePackageJson = (target, source) => {
|
|
195
|
+
if (source.scripts !== void 0) target.scripts = {
|
|
161
196
|
...target.scripts,
|
|
162
197
|
...source.scripts
|
|
163
198
|
};
|
|
164
|
-
if (source.dependencies) target.dependencies = {
|
|
199
|
+
if (source.dependencies !== void 0) target.dependencies = {
|
|
165
200
|
...target.dependencies,
|
|
166
201
|
...source.dependencies
|
|
167
202
|
};
|
|
168
|
-
if (source.devDependencies) target.devDependencies = {
|
|
203
|
+
if (source.devDependencies !== void 0) target.devDependencies = {
|
|
169
204
|
...target.devDependencies,
|
|
170
205
|
...source.devDependencies
|
|
171
206
|
};
|
|
172
|
-
if (source.workspaces) target.workspaces = source.workspaces;
|
|
173
|
-
if (source.bin) target.bin = source.bin;
|
|
174
|
-
}
|
|
175
|
-
|
|
207
|
+
if (source.workspaces !== void 0) target.workspaces = source.workspaces;
|
|
208
|
+
if (source.bin !== void 0) target.bin = source.bin;
|
|
209
|
+
};
|
|
210
|
+
var isSeedFile = (filePath) => {
|
|
176
211
|
return [
|
|
177
212
|
"src/",
|
|
178
213
|
"client/src/",
|
|
@@ -186,14 +221,14 @@ function isSeedFile(filePath) {
|
|
|
186
221
|
"index.tsx",
|
|
187
222
|
"LICENSE"
|
|
188
223
|
].some((file) => filePath === file) || filePath.toLowerCase().endsWith(".md");
|
|
189
|
-
}
|
|
190
|
-
async
|
|
224
|
+
};
|
|
225
|
+
var mergeFile = async (filePath, existing, template, log) => {
|
|
191
226
|
debug$3("Merging file: %s", filePath);
|
|
192
|
-
const tempBase = filePath
|
|
193
|
-
const tempNew = filePath
|
|
227
|
+
const tempBase = `${filePath}.base.tmp`;
|
|
228
|
+
const tempNew = `${filePath}.new.tmp`;
|
|
194
229
|
try {
|
|
195
230
|
await fs.writeFile(tempNew, template);
|
|
196
|
-
await fs.writeFile(tempBase,
|
|
231
|
+
await fs.writeFile(tempBase, existing);
|
|
197
232
|
try {
|
|
198
233
|
const stdio = debug$3.enabled ? "inherit" : "pipe";
|
|
199
234
|
debug$3("Executing: git merge-file %s %s %s", filePath, tempBase, tempNew);
|
|
@@ -207,82 +242,76 @@ async function mergeFile(filePath, existing, template, log) {
|
|
|
207
242
|
preferLocal: true
|
|
208
243
|
});
|
|
209
244
|
return (await fs.readFile(filePath, "utf8")).trim() !== template.trim() ? "merged" : "updated";
|
|
210
|
-
} catch (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return "error";
|
|
217
|
-
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const detail = toErrorDetail$1(error);
|
|
247
|
+
if (detail.exitCode !== void 0 && detail.exitCode >= 1 && detail.exitCode < 128) return "conflict";
|
|
248
|
+
debug$3("Git merge-file failed: %O", error);
|
|
249
|
+
log.error(`Failed to merge ${filePath}: ${detail.message}${detail.detail}`);
|
|
250
|
+
return "error";
|
|
218
251
|
}
|
|
219
252
|
} finally {
|
|
220
253
|
await fs.rm(tempBase, { force: true });
|
|
221
254
|
await fs.rm(tempNew, { force: true });
|
|
222
255
|
}
|
|
223
|
-
}
|
|
256
|
+
};
|
|
224
257
|
//#endregion
|
|
225
258
|
//#region src/templates/base/index.ts
|
|
226
|
-
var
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
name: "TypeScript",
|
|
234
|
-
description: "Mandatory strict mode for type safety."
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
name: "oxlint",
|
|
238
|
-
description: "Ultra-fast Rust-based linter."
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
name: "oxfmt",
|
|
242
|
-
description: "High performance code formatting."
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: "Vitest",
|
|
246
|
-
description: "Modern, fast unit testing with coverage."
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: "Husky",
|
|
250
|
-
description: "Git hooks for pre-commit linting."
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: "commitlint",
|
|
254
|
-
description: "Standardized commit messages."
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: "conventional-changelog",
|
|
258
|
-
description: "Automated release notes."
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "release-it",
|
|
262
|
-
description: "Automated release workflow."
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "debug",
|
|
266
|
-
description: "Structured logging for debugging."
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "Zod",
|
|
270
|
-
description: "TypeScript-first schema validation for runtime type safety."
|
|
271
|
-
}
|
|
272
|
-
],
|
|
273
|
-
dependencies: { zod: "zod" },
|
|
274
|
-
devDependencies: {
|
|
275
|
-
"eslint-plugin-regexp": "",
|
|
276
|
-
"release-it": ""
|
|
259
|
+
var getBaseTemplate = (_opts) => ({
|
|
260
|
+
name: "base",
|
|
261
|
+
description: "The foundation for all project templates, including common tooling and configuration.",
|
|
262
|
+
components: [
|
|
263
|
+
{
|
|
264
|
+
name: "TypeScript",
|
|
265
|
+
description: "Mandatory strict mode for type safety."
|
|
277
266
|
},
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
{
|
|
268
|
+
name: "oxlint",
|
|
269
|
+
description: "Ultra-fast Rust-based linter."
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "oxfmt",
|
|
273
|
+
description: "High performance code formatting."
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "Vitest",
|
|
277
|
+
description: "Modern, fast unit testing with coverage."
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "Husky",
|
|
281
|
+
description: "Git hooks for pre-commit linting."
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "commitlint",
|
|
285
|
+
description: "Standardized commit messages."
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "conventional-changelog",
|
|
289
|
+
description: "Automated release notes."
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "release-it",
|
|
293
|
+
description: "Automated release workflow."
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "debug",
|
|
297
|
+
description: "Structured logging for debugging."
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "Zod",
|
|
301
|
+
description: "TypeScript-first schema validation for runtime type safety."
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
dependencies: { zod: "zod" },
|
|
305
|
+
devDependencies: {
|
|
306
|
+
"eslint-plugin-regexp": "",
|
|
307
|
+
"release-it": ""
|
|
308
|
+
},
|
|
309
|
+
scripts: {},
|
|
310
|
+
files: [],
|
|
311
|
+
templateDir: getTemplateDir(import.meta.dirname, "base")
|
|
312
|
+
});
|
|
283
313
|
//#endregion
|
|
284
314
|
//#region src/templates/cli/index.ts
|
|
285
|
-
var __dirname$4 = path.dirname(fileURLToPath(import.meta.url));
|
|
286
315
|
var getCliTemplate = (_opts) => {
|
|
287
316
|
return {
|
|
288
317
|
name: "cli",
|
|
@@ -305,194 +334,201 @@ var getCliTemplate = (_opts) => {
|
|
|
305
334
|
devDependencies: { vite: "vite" },
|
|
306
335
|
scripts: {},
|
|
307
336
|
files: [],
|
|
308
|
-
templateDir: getTemplateDir(
|
|
337
|
+
templateDir: getTemplateDir(import.meta.dirname, "cli")
|
|
309
338
|
};
|
|
310
339
|
};
|
|
311
340
|
//#endregion
|
|
312
341
|
//#region src/templates/web-vanilla/index.ts
|
|
313
|
-
var
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
name: "Vite",
|
|
321
|
-
description: "Fast frontend build tool and development server."
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
name: "Vitest",
|
|
325
|
-
description: "Modern testing framework with browser support."
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: "Playwright",
|
|
329
|
-
description: "Comprehensive end-to-end testing for modern web apps."
|
|
330
|
-
}
|
|
331
|
-
],
|
|
332
|
-
dependencies: {},
|
|
333
|
-
devDependencies: {
|
|
334
|
-
vite: "vite",
|
|
335
|
-
vitest: "vitest",
|
|
336
|
-
"@vitest/browser": "@vitest/browser",
|
|
337
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
338
|
-
playwright: "playwright",
|
|
339
|
-
"@playwright/test": "@playwright/test"
|
|
342
|
+
var getWebVanillaTemplate = (_opts) => ({
|
|
343
|
+
name: "web-vanilla",
|
|
344
|
+
description: "A modern, standalone web page template with built-in development and testing tooling.",
|
|
345
|
+
components: [
|
|
346
|
+
{
|
|
347
|
+
name: "Vite",
|
|
348
|
+
description: "Fast frontend build tool and development server."
|
|
340
349
|
},
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
preview: "vite preview",
|
|
345
|
-
test: "vitest run",
|
|
346
|
-
"test:ui": "vitest",
|
|
347
|
-
"integration-test": "playwright test"
|
|
350
|
+
{
|
|
351
|
+
name: "Vitest",
|
|
352
|
+
description: "Modern testing framework with browser support."
|
|
348
353
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
354
|
+
{
|
|
355
|
+
name: "Playwright",
|
|
356
|
+
description: "Comprehensive end-to-end testing for modern web apps."
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
dependencies: {},
|
|
360
|
+
devDependencies: {
|
|
361
|
+
vite: "vite",
|
|
362
|
+
vitest: "vitest",
|
|
363
|
+
"@vitest/browser": "@vitest/browser",
|
|
364
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
365
|
+
playwright: "playwright",
|
|
366
|
+
"@playwright/test": "@playwright/test"
|
|
367
|
+
},
|
|
368
|
+
scripts: {
|
|
369
|
+
dev: "vite",
|
|
370
|
+
build: "vite build",
|
|
371
|
+
preview: "vite preview",
|
|
372
|
+
test: "vitest run",
|
|
373
|
+
"test:ui": "vitest",
|
|
374
|
+
"integration-test": "playwright test"
|
|
375
|
+
},
|
|
376
|
+
files: [],
|
|
377
|
+
templateDir: getTemplateDir(import.meta.dirname, "web-vanilla")
|
|
378
|
+
});
|
|
353
379
|
//#endregion
|
|
354
380
|
//#region src/templates/web-app/index.ts
|
|
355
|
-
var
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
name: "React",
|
|
363
|
-
description: "Powerful library for building component-based user interfaces."
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
name: "MUI",
|
|
367
|
-
description: "Rich set of Material Design UI components for React."
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
name: "TanStack React Query",
|
|
371
|
-
description: "Powerful asynchronous state management for React."
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
name: "Vite",
|
|
375
|
-
description: "Next-generation frontend tooling."
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
name: "Vitest",
|
|
379
|
-
description: "Testing framework with cross-browser support."
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
name: "Playwright",
|
|
383
|
-
description: "End-to-end testing for modern web apps."
|
|
384
|
-
}
|
|
385
|
-
],
|
|
386
|
-
dependencies: {
|
|
387
|
-
react: "react",
|
|
388
|
-
"react-dom": "react-dom",
|
|
389
|
-
"@mui/material": "@mui/material",
|
|
390
|
-
"@mui/icons-material": "@mui/icons-material",
|
|
391
|
-
"@emotion/react": "@emotion/react",
|
|
392
|
-
"@emotion/styled": "@emotion/styled",
|
|
393
|
-
"@tanstack/react-query": "@tanstack/react-query"
|
|
381
|
+
var getWebAppTemplate = (_opts) => ({
|
|
382
|
+
name: "web-app",
|
|
383
|
+
description: "A React application with MUI and TanStack Query, powered by Vite.",
|
|
384
|
+
components: [
|
|
385
|
+
{
|
|
386
|
+
name: "React",
|
|
387
|
+
description: "Powerful library for building component-based user interfaces."
|
|
394
388
|
},
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
vite: "vite",
|
|
399
|
-
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
400
|
-
vitest: "vitest",
|
|
401
|
-
"@vitest/browser": "@vitest/browser",
|
|
402
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
403
|
-
playwright: "playwright",
|
|
404
|
-
"@playwright/test": "@playwright/test",
|
|
405
|
-
"vitest-browser-react": "vitest-browser-react"
|
|
389
|
+
{
|
|
390
|
+
name: "MUI",
|
|
391
|
+
description: "Rich set of Material Design UI components for React."
|
|
406
392
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
preview: "vite preview",
|
|
411
|
-
test: "vitest run",
|
|
412
|
-
"test:ui": "vitest",
|
|
413
|
-
"integration-test": "playwright test",
|
|
414
|
-
start: "vite preview"
|
|
393
|
+
{
|
|
394
|
+
name: "TanStack React Query",
|
|
395
|
+
description: "Powerful asynchronous state management for React."
|
|
415
396
|
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
397
|
+
{
|
|
398
|
+
name: "Vite",
|
|
399
|
+
description: "Next-generation frontend tooling."
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: "Vitest",
|
|
403
|
+
description: "Testing framework with cross-browser support."
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "Playwright",
|
|
407
|
+
description: "End-to-end testing for modern web apps."
|
|
408
|
+
}
|
|
409
|
+
],
|
|
410
|
+
dependencies: {
|
|
411
|
+
react: "react",
|
|
412
|
+
"react-dom": "react-dom",
|
|
413
|
+
"@mui/material": "@mui/material",
|
|
414
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
415
|
+
"@emotion/react": "@emotion/react",
|
|
416
|
+
"@emotion/styled": "@emotion/styled",
|
|
417
|
+
"@tanstack/react-query": "@tanstack/react-query"
|
|
418
|
+
},
|
|
419
|
+
devDependencies: {
|
|
420
|
+
"@types/react": "@types/react",
|
|
421
|
+
"@types/react-dom": "@types/react-dom",
|
|
422
|
+
vite: "vite",
|
|
423
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
424
|
+
vitest: "vitest",
|
|
425
|
+
"@vitest/browser": "@vitest/browser",
|
|
426
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
427
|
+
playwright: "playwright",
|
|
428
|
+
"@playwright/test": "@playwright/test",
|
|
429
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
430
|
+
},
|
|
431
|
+
scripts: {
|
|
432
|
+
dev: "vite",
|
|
433
|
+
build: "vite build",
|
|
434
|
+
preview: "vite preview",
|
|
435
|
+
test: "vitest run",
|
|
436
|
+
"test:ui": "vitest",
|
|
437
|
+
"integration-test": "playwright test",
|
|
438
|
+
start: "vite preview"
|
|
439
|
+
},
|
|
440
|
+
files: [],
|
|
441
|
+
templateDir: getTemplateDir(import.meta.dirname, "web-app")
|
|
442
|
+
});
|
|
420
443
|
//#endregion
|
|
421
444
|
//#region src/templates/web-fullstack/index.ts
|
|
422
|
-
var
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
name: "React",
|
|
430
|
-
description: "Powerful library for building component-based user interfaces."
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: "MUI",
|
|
434
|
-
description: "Rich set of Material Design UI components for React."
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
name: "tRPC",
|
|
438
|
-
description: "End-to-end typesafe APIs made easy."
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: "React Query",
|
|
442
|
-
description: "Powerful asynchronous state management for React."
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
name: "Express",
|
|
446
|
-
description: "Fast, minimalist backend web framework."
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: "React Router",
|
|
450
|
-
description: "Declarative routing for the frontend."
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
name: "Vite",
|
|
454
|
-
description: "Fast, modern frontend build tool."
|
|
455
|
-
}
|
|
456
|
-
],
|
|
457
|
-
dependencies: {
|
|
458
|
-
react: "react",
|
|
459
|
-
"react-dom": "react-dom",
|
|
460
|
-
"@mui/material": "@mui/material",
|
|
461
|
-
"@mui/icons-material": "@mui/icons-material",
|
|
462
|
-
"@emotion/react": "@emotion/react",
|
|
463
|
-
"@emotion/styled": "@emotion/styled",
|
|
464
|
-
express: "express",
|
|
465
|
-
"@trpc/server": "@trpc/server",
|
|
466
|
-
"@trpc/client": "@trpc/client",
|
|
467
|
-
"@trpc/react-query": "@trpc/react-query",
|
|
468
|
-
"@tanstack/react-query": "@tanstack/react-query",
|
|
469
|
-
"react-router-dom": "react-router-dom",
|
|
470
|
-
cors: "cors"
|
|
445
|
+
var getWebFullstackTemplate = (_opts) => ({
|
|
446
|
+
name: "web-fullstack",
|
|
447
|
+
description: "A comprehensive full-stack monorepo featuring an Express backend with tRPC and a modern React client with MUI.",
|
|
448
|
+
components: [
|
|
449
|
+
{
|
|
450
|
+
name: "React",
|
|
451
|
+
description: "Powerful library for building component-based user interfaces."
|
|
471
452
|
},
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
"@types/express": "@types/express",
|
|
476
|
-
"@types/cors": "@types/cors",
|
|
477
|
-
"@playwright/test": "@playwright/test",
|
|
478
|
-
vite: "vite",
|
|
479
|
-
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
480
|
-
vitest: "vitest",
|
|
481
|
-
"@vitest/browser": "@vitest/browser",
|
|
482
|
-
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
483
|
-
playwright: "playwright",
|
|
484
|
-
"vitest-browser-react": "vitest-browser-react"
|
|
453
|
+
{
|
|
454
|
+
name: "MUI",
|
|
455
|
+
description: "Rich set of Material Design UI components for React."
|
|
485
456
|
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
test: "npm run test --workspaces",
|
|
490
|
-
"integration-test": "playwright test"
|
|
457
|
+
{
|
|
458
|
+
name: "tRPC",
|
|
459
|
+
description: "End-to-end typesafe APIs made easy."
|
|
491
460
|
},
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
461
|
+
{
|
|
462
|
+
name: "React Query",
|
|
463
|
+
description: "Powerful asynchronous state management for React."
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "Express",
|
|
467
|
+
description: "Fast, minimalist backend web framework."
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: "React Router",
|
|
471
|
+
description: "Declarative routing for the frontend."
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "Vite",
|
|
475
|
+
description: "Fast, modern frontend build tool."
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
dependencies: {
|
|
479
|
+
react: "react",
|
|
480
|
+
"react-dom": "react-dom",
|
|
481
|
+
"@mui/material": "@mui/material",
|
|
482
|
+
"@mui/icons-material": "@mui/icons-material",
|
|
483
|
+
"@emotion/react": "@emotion/react",
|
|
484
|
+
"@emotion/styled": "@emotion/styled",
|
|
485
|
+
express: "express",
|
|
486
|
+
"@trpc/server": "@trpc/server",
|
|
487
|
+
"@trpc/client": "@trpc/client",
|
|
488
|
+
"@trpc/react-query": "@trpc/react-query",
|
|
489
|
+
"@tanstack/react-query": "@tanstack/react-query",
|
|
490
|
+
"react-router-dom": "react-router-dom",
|
|
491
|
+
cors: "cors"
|
|
492
|
+
},
|
|
493
|
+
devDependencies: {
|
|
494
|
+
"@types/react": "@types/react",
|
|
495
|
+
"@types/react-dom": "@types/react-dom",
|
|
496
|
+
"@types/express": "@types/express",
|
|
497
|
+
"@types/cors": "@types/cors",
|
|
498
|
+
"@playwright/test": "@playwright/test",
|
|
499
|
+
vite: "vite",
|
|
500
|
+
"@vitejs/plugin-react": "@vitejs/plugin-react",
|
|
501
|
+
vitest: "vitest",
|
|
502
|
+
"@vitest/browser": "@vitest/browser",
|
|
503
|
+
"@vitest/browser-playwright": "@vitest/browser-playwright",
|
|
504
|
+
playwright: "playwright",
|
|
505
|
+
"vitest-browser-react": "vitest-browser-react"
|
|
506
|
+
},
|
|
507
|
+
scripts: {
|
|
508
|
+
build: "npm run build --workspaces",
|
|
509
|
+
dev: "npm run dev --workspaces",
|
|
510
|
+
test: "npm run test --workspaces",
|
|
511
|
+
"integration-test": "playwright test"
|
|
512
|
+
},
|
|
513
|
+
files: [],
|
|
514
|
+
templateDir: getTemplateDir(import.meta.dirname, "web-fullstack")
|
|
515
|
+
});
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/templates/registry.ts
|
|
518
|
+
var templateFactories = {
|
|
519
|
+
cli: getCliTemplate,
|
|
520
|
+
"web-vanilla": getWebVanillaTemplate,
|
|
521
|
+
"web-app": getWebAppTemplate,
|
|
522
|
+
"web-fullstack": getWebFullstackTemplate
|
|
495
523
|
};
|
|
524
|
+
var getTemplateByType = (type, opts) => templateFactories[type](opts);
|
|
525
|
+
var getProjectTemplates = (opts) => [getBaseTemplate(opts), getTemplateByType(opts.template, opts)];
|
|
526
|
+
var getTemplateTypes = () => [
|
|
527
|
+
"cli",
|
|
528
|
+
"web-vanilla",
|
|
529
|
+
"web-app",
|
|
530
|
+
"web-fullstack"
|
|
531
|
+
];
|
|
496
532
|
//#endregion
|
|
497
533
|
//#region src/generators/info.ts
|
|
498
534
|
var MOCK_OPTS = {
|
|
@@ -513,45 +549,57 @@ var getTemplateInfo = (type) => {
|
|
|
513
549
|
template: type
|
|
514
550
|
};
|
|
515
551
|
const base = getBaseTemplate(opts);
|
|
516
|
-
|
|
517
|
-
switch (type) {
|
|
518
|
-
case "cli":
|
|
519
|
-
template = getCliTemplate(opts);
|
|
520
|
-
break;
|
|
521
|
-
case "web-vanilla":
|
|
522
|
-
template = getWebVanillaTemplate(opts);
|
|
523
|
-
break;
|
|
524
|
-
case "web-app":
|
|
525
|
-
template = getWebAppTemplate(opts);
|
|
526
|
-
break;
|
|
527
|
-
case "web-fullstack":
|
|
528
|
-
template = getWebFullstackTemplate(opts);
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
552
|
+
const template = getTemplateByType(type, opts);
|
|
531
553
|
return {
|
|
532
554
|
name: template.name,
|
|
533
555
|
description: template.description,
|
|
534
556
|
components: [...base.components, ...template.components]
|
|
535
557
|
};
|
|
536
558
|
};
|
|
537
|
-
var getAllTemplatesInfo = () =>
|
|
538
|
-
return [
|
|
539
|
-
"cli",
|
|
540
|
-
"web-vanilla",
|
|
541
|
-
"web-app",
|
|
542
|
-
"web-fullstack"
|
|
543
|
-
].map((type) => getTemplateInfo(type));
|
|
544
|
-
};
|
|
559
|
+
var getAllTemplatesInfo = () => getTemplateTypes().map((type) => getTemplateInfo(type));
|
|
545
560
|
//#endregion
|
|
546
561
|
//#region src/cli.ts
|
|
547
|
-
var
|
|
562
|
+
var isRecord$2 = (value) => typeof value === "object" && value !== null;
|
|
563
|
+
var isPackageManager = (value) => value === "npm" || value === "pnpm" || value === "yarn";
|
|
564
|
+
var pathExists$1 = async (filePath) => {
|
|
565
|
+
try {
|
|
566
|
+
await fs.access(filePath);
|
|
567
|
+
return true;
|
|
568
|
+
} catch {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var parseStoredPackageJson = (raw) => {
|
|
573
|
+
const parsed = JSON.parse(raw);
|
|
574
|
+
if (!isRecord$2(parsed)) return {};
|
|
575
|
+
const pkg = parsed;
|
|
576
|
+
const config = pkg["create-template-project"];
|
|
577
|
+
const configRecord = isRecord$2(config) ? config : void 0;
|
|
578
|
+
let template;
|
|
579
|
+
if (configRecord !== void 0 && typeof configRecord.template === "string") {
|
|
580
|
+
const result = TemplateTypeSchema.safeParse(configRecord.template);
|
|
581
|
+
if (result.success) template = result.data;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
name: typeof pkg.name === "string" ? pkg.name : void 0,
|
|
585
|
+
description: typeof pkg.description === "string" ? pkg.description : void 0,
|
|
586
|
+
keywords: Array.isArray(pkg.keywords) ? pkg.keywords.filter((keyword) => typeof keyword === "string") : void 0,
|
|
587
|
+
author: typeof pkg.author === "string" ? pkg.author : void 0,
|
|
588
|
+
"create-template-project": configRecord ? {
|
|
589
|
+
template,
|
|
590
|
+
githubUsername: typeof configRecord.githubUsername === "string" ? configRecord.githubUsername : void 0,
|
|
591
|
+
author: typeof configRecord.author === "string" ? configRecord.author : void 0
|
|
592
|
+
} : void 0
|
|
593
|
+
};
|
|
594
|
+
};
|
|
595
|
+
var noop = () => void 0;
|
|
548
596
|
var stripQuotes = (str) => {
|
|
549
597
|
if (typeof str !== "string") return str;
|
|
550
598
|
let result = str.trim();
|
|
551
599
|
while (result.length >= 2) {
|
|
552
|
-
const first = result
|
|
553
|
-
const last = result
|
|
554
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'") result = result.
|
|
600
|
+
const [first] = result;
|
|
601
|
+
const last = result.at(-1);
|
|
602
|
+
if (first === "\"" && last === "\"" || first === "'" && last === "'") result = result.slice(1, -1).trim();
|
|
555
603
|
else break;
|
|
556
604
|
}
|
|
557
605
|
return result;
|
|
@@ -560,7 +608,7 @@ var getDefaultAuthor = async () => {
|
|
|
560
608
|
try {
|
|
561
609
|
const { stdout } = await execa("git", ["config", "user.name"]);
|
|
562
610
|
return stdout.trim();
|
|
563
|
-
} catch
|
|
611
|
+
} catch {
|
|
564
612
|
return "";
|
|
565
613
|
}
|
|
566
614
|
};
|
|
@@ -568,7 +616,7 @@ var getDefaultGithubUsername = async () => {
|
|
|
568
616
|
try {
|
|
569
617
|
const { stdout } = await execa("git", ["config", "github.user"]);
|
|
570
618
|
return stdout.trim();
|
|
571
|
-
} catch
|
|
619
|
+
} catch {
|
|
572
620
|
return "";
|
|
573
621
|
}
|
|
574
622
|
};
|
|
@@ -577,11 +625,11 @@ var parseArgs = async () => {
|
|
|
577
625
|
debug$2("Parsing CLI arguments: %O", process.argv);
|
|
578
626
|
const program = new Command();
|
|
579
627
|
if (process.env.NODE_ENV === "test") program.configureOutput({
|
|
580
|
-
writeOut:
|
|
581
|
-
writeErr:
|
|
628
|
+
writeOut: noop,
|
|
629
|
+
writeErr: noop
|
|
582
630
|
});
|
|
583
631
|
program.name("create-template-project").exitOverride().description("Scaffold a new project template").version("0.1.0").option("--debug", "Enable debug output").on("option:debug", () => {
|
|
584
|
-
process.env
|
|
632
|
+
process.env.DEBUG = "create-template-project:*";
|
|
585
633
|
debugLib.enable("create-template-project:*");
|
|
586
634
|
}).addHelpText("after", `
|
|
587
635
|
Commands:
|
|
@@ -600,8 +648,9 @@ Templates:
|
|
|
600
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) => {
|
|
601
649
|
debug$2("Executing \"info\" command with options: %O", opts);
|
|
602
650
|
p.intro("Template Information");
|
|
603
|
-
if (opts.template) {
|
|
604
|
-
const
|
|
651
|
+
if (opts.template !== void 0 && opts.template.length > 0) {
|
|
652
|
+
const template = stripQuotes(opts.template);
|
|
653
|
+
const typeResult = TemplateTypeSchema.safeParse(template);
|
|
605
654
|
if (!typeResult.success) {
|
|
606
655
|
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
607
656
|
process.exit(1);
|
|
@@ -627,19 +676,25 @@ Templates:
|
|
|
627
676
|
});
|
|
628
677
|
program.command("create").description("Create a new project from a template").option("-t, --template <type>", "Template type (cli, web-vanilla, web-app, web-fullstack)").option("-n, --name <name>", "Project name").option("--description <description>", "Project description").option("-k, --keywords <keywords>", "Project keywords (comma separated)").option("-a, --author <author>", "Author name (defaults to 'git config user.name')").option("--github-username <username>", "GitHub username (defaults to 'git config github.user')").option("-p, --package-manager <pm>", "Package manager (npm, pnpm, yarn)", "pnpm").option("--create-github-repository", "Create GitHub repository and push initial commit").requiredOption("--path <path>", "Output directory").option("--build", "Run the CI script (lint, build, test) after scaffolding", false).option("--no-progress", "Do not show progress indicators").action(async (opts) => {
|
|
629
678
|
debug$2("Executing \"create\" command with options: %O", opts);
|
|
679
|
+
const templateInput = stripQuotes(opts.template);
|
|
680
|
+
const templateResult = TemplateTypeSchema.safeParse(templateInput);
|
|
681
|
+
if (!templateResult.success) {
|
|
682
|
+
p.log.error(`Invalid template type: ${opts.template}. Must be one of: cli, web-vanilla, web-app, web-fullstack`);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
630
685
|
commandResult = {
|
|
631
|
-
...opts,
|
|
632
686
|
update: false,
|
|
633
|
-
template:
|
|
687
|
+
template: templateResult.data,
|
|
634
688
|
projectName: opts.name,
|
|
635
689
|
description: opts.description,
|
|
636
690
|
keywords: opts.keywords,
|
|
637
|
-
author: opts.author
|
|
638
|
-
githubUsername: opts.githubUsername
|
|
691
|
+
author: opts.author ?? await getDefaultAuthor(),
|
|
692
|
+
githubUsername: opts.githubUsername ?? await getDefaultGithubUsername(),
|
|
639
693
|
packageManager: opts.packageManager,
|
|
640
694
|
directory: path.resolve(opts.path),
|
|
641
|
-
createGithubRepository:
|
|
642
|
-
|
|
695
|
+
createGithubRepository: Boolean(opts.createGithubRepository),
|
|
696
|
+
build: Boolean(opts.build),
|
|
697
|
+
progress: Boolean(opts.progress)
|
|
643
698
|
};
|
|
644
699
|
debug$2("Processed \"create\" options: %O", commandResult);
|
|
645
700
|
});
|
|
@@ -663,32 +718,45 @@ Restrictions & Behavior:
|
|
|
663
718
|
}
|
|
664
719
|
let pkg;
|
|
665
720
|
try {
|
|
666
|
-
pkg =
|
|
667
|
-
} catch (
|
|
668
|
-
|
|
721
|
+
pkg = parseStoredPackageJson(await fs.readFile(pkgPath, "utf8"));
|
|
722
|
+
} catch (error) {
|
|
723
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
724
|
+
p.log.error(`Failed to read or parse package.json at ${pkgPath}: ${message}`);
|
|
669
725
|
process.exit(1);
|
|
670
726
|
}
|
|
671
|
-
if (
|
|
727
|
+
if (pkg.name === void 0 || pkg.name.length === 0) {
|
|
672
728
|
p.log.error(`No name property found in ${pkgPath}.`);
|
|
673
729
|
process.exit(1);
|
|
674
730
|
}
|
|
675
|
-
|
|
731
|
+
const projectConfig = pkg["create-template-project"];
|
|
732
|
+
if (projectConfig === void 0 || projectConfig.template === void 0) {
|
|
676
733
|
p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
677
734
|
process.exit(1);
|
|
678
735
|
}
|
|
736
|
+
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
|
+
}
|
|
679
746
|
commandResult = {
|
|
680
747
|
...opts,
|
|
681
748
|
update: true,
|
|
682
|
-
template
|
|
749
|
+
template,
|
|
683
750
|
projectName: pkg.name,
|
|
684
|
-
description: opts.description
|
|
685
|
-
keywords: opts.keywords
|
|
686
|
-
author: opts.author
|
|
687
|
-
githubUsername: opts.githubUsername
|
|
751
|
+
description: opts.description ?? pkg.description,
|
|
752
|
+
keywords: opts.keywords ?? (pkg.keywords !== void 0 ? pkg.keywords.join(", ") : void 0),
|
|
753
|
+
author: opts.author ?? pkg.author ?? await getDefaultAuthor(),
|
|
754
|
+
githubUsername: opts.githubUsername ?? projectConfig.githubUsername ?? await getDefaultGithubUsername(),
|
|
688
755
|
packageManager: opts.packageManager,
|
|
689
756
|
directory,
|
|
690
|
-
createGithubRepository:
|
|
691
|
-
|
|
757
|
+
createGithubRepository: Boolean(opts.createGithubRepository),
|
|
758
|
+
build: Boolean(opts.build),
|
|
759
|
+
progress: Boolean(opts.progress)
|
|
692
760
|
};
|
|
693
761
|
debug$2("Processed \"update\" options: %O", commandResult);
|
|
694
762
|
});
|
|
@@ -698,7 +766,7 @@ Restrictions & Behavior:
|
|
|
698
766
|
message: "Project name:",
|
|
699
767
|
placeholder: "my-app",
|
|
700
768
|
defaultValue: "my-app",
|
|
701
|
-
validate: (value) => value
|
|
769
|
+
validate: (value = "") => value.length > 0 ? void 0 : "Project name is required"
|
|
702
770
|
});
|
|
703
771
|
if (p.isCancel(projectName)) {
|
|
704
772
|
p.cancel("Operation cancelled.");
|
|
@@ -722,20 +790,20 @@ Restrictions & Behavior:
|
|
|
722
790
|
let existingDescription = "";
|
|
723
791
|
let existingKeywords = [];
|
|
724
792
|
if (pkgExists) try {
|
|
725
|
-
const pkg =
|
|
726
|
-
existingConfig = pkg["create-template-project"]
|
|
727
|
-
existingAuthor = pkg.author;
|
|
728
|
-
existingGithubUsername = existingConfig.githubUsername;
|
|
729
|
-
existingDescription = pkg.description;
|
|
730
|
-
existingKeywords = pkg.keywords
|
|
793
|
+
const pkg = parseStoredPackageJson(await fs.readFile(pkgPath, "utf8"));
|
|
794
|
+
existingConfig = pkg["create-template-project"] ?? {};
|
|
795
|
+
existingAuthor = pkg.author ?? "";
|
|
796
|
+
existingGithubUsername = existingConfig.githubUsername ?? "";
|
|
797
|
+
existingDescription = pkg.description ?? "";
|
|
798
|
+
existingKeywords = pkg.keywords ?? [];
|
|
731
799
|
debug$2("Found existing project config: %O", existingConfig);
|
|
732
|
-
} catch (
|
|
733
|
-
debug$2("Failed to read existing package.json: %O",
|
|
800
|
+
} catch (error) {
|
|
801
|
+
debug$2("Failed to read existing package.json: %O", error);
|
|
734
802
|
}
|
|
735
803
|
const projectDescription = await p.text({
|
|
736
804
|
message: "Project description:",
|
|
737
805
|
placeholder: "A new project",
|
|
738
|
-
defaultValue: existingDescription
|
|
806
|
+
defaultValue: existingDescription
|
|
739
807
|
});
|
|
740
808
|
if (p.isCancel(projectDescription)) {
|
|
741
809
|
p.cancel("Operation cancelled.");
|
|
@@ -744,29 +812,31 @@ Restrictions & Behavior:
|
|
|
744
812
|
const projectKeywords = await p.text({
|
|
745
813
|
message: "Project keywords (comma separated):",
|
|
746
814
|
placeholder: "cli, nodejs, typescript",
|
|
747
|
-
defaultValue: existingKeywords
|
|
815
|
+
defaultValue: existingKeywords.join(", ")
|
|
748
816
|
});
|
|
749
817
|
if (p.isCancel(projectKeywords)) {
|
|
750
818
|
p.cancel("Operation cancelled.");
|
|
751
819
|
process.exit(0);
|
|
752
820
|
}
|
|
753
821
|
const defaultAuthor = await getDefaultAuthor();
|
|
822
|
+
const authorDefault = existingAuthor.length > 0 ? existingAuthor : existingConfig.author ?? defaultAuthor;
|
|
754
823
|
const author = await p.text({
|
|
755
824
|
message: "Author name:",
|
|
756
825
|
placeholder: "Your Name",
|
|
757
|
-
defaultValue:
|
|
758
|
-
validate: (value) => value
|
|
826
|
+
defaultValue: authorDefault,
|
|
827
|
+
validate: (value = "") => value.length > 0 ? void 0 : "Author name is required"
|
|
759
828
|
});
|
|
760
829
|
if (p.isCancel(author)) {
|
|
761
830
|
p.cancel("Operation cancelled.");
|
|
762
831
|
process.exit(0);
|
|
763
832
|
}
|
|
764
833
|
const defaultGithubUsername = await getDefaultGithubUsername();
|
|
834
|
+
const githubDefault = existingGithubUsername.length > 0 ? existingGithubUsername : defaultGithubUsername;
|
|
765
835
|
const githubUsername = await p.text({
|
|
766
836
|
message: "GitHub username:",
|
|
767
837
|
placeholder: "your-github-username",
|
|
768
|
-
defaultValue:
|
|
769
|
-
validate: (value) => value
|
|
838
|
+
defaultValue: githubDefault,
|
|
839
|
+
validate: (value = "") => value.length > 0 ? void 0 : "GitHub username is required"
|
|
770
840
|
});
|
|
771
841
|
if (p.isCancel(githubUsername)) {
|
|
772
842
|
p.cancel("Operation cancelled.");
|
|
@@ -788,19 +858,18 @@ Restrictions & Behavior:
|
|
|
788
858
|
p.cancel("Operation cancelled.");
|
|
789
859
|
process.exit(0);
|
|
790
860
|
}
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
process.exit(1);
|
|
795
|
-
}
|
|
796
|
-
update = true;
|
|
861
|
+
if (!existingConfig.template) {
|
|
862
|
+
p.log.error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
863
|
+
process.exit(1);
|
|
797
864
|
}
|
|
865
|
+
update = true;
|
|
798
866
|
}
|
|
799
|
-
|
|
867
|
+
const { template: existingTemplate } = existingConfig;
|
|
868
|
+
let template = existingTemplate;
|
|
800
869
|
if (!update || !template) {
|
|
801
|
-
|
|
870
|
+
const selectedTemplate = await p.select({
|
|
802
871
|
message: "Select project template:",
|
|
803
|
-
initialValue: template
|
|
872
|
+
initialValue: template ?? "cli",
|
|
804
873
|
options: [
|
|
805
874
|
{
|
|
806
875
|
label: "CLI Application (Node.js)",
|
|
@@ -820,14 +889,24 @@ Restrictions & Behavior:
|
|
|
820
889
|
}
|
|
821
890
|
]
|
|
822
891
|
});
|
|
823
|
-
if (p.isCancel(
|
|
892
|
+
if (p.isCancel(selectedTemplate)) {
|
|
824
893
|
p.cancel("Operation cancelled.");
|
|
825
894
|
process.exit(0);
|
|
826
895
|
}
|
|
896
|
+
if (typeof selectedTemplate !== "string") {
|
|
897
|
+
p.cancel("Invalid template selection.");
|
|
898
|
+
process.exit(1);
|
|
899
|
+
}
|
|
900
|
+
const templateResult = TemplateTypeSchema.safeParse(selectedTemplate);
|
|
901
|
+
if (!templateResult.success) {
|
|
902
|
+
p.cancel("Invalid template selection.");
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
template = templateResult.data;
|
|
827
906
|
} else p.log.info(`Using existing template type: ${template}`);
|
|
828
907
|
let packageManager = "pnpm";
|
|
829
908
|
if (!update) {
|
|
830
|
-
|
|
909
|
+
const selectedPackageManager = await p.select({
|
|
831
910
|
message: "Select package manager:",
|
|
832
911
|
initialValue: "pnpm",
|
|
833
912
|
options: [
|
|
@@ -845,10 +924,15 @@ Restrictions & Behavior:
|
|
|
845
924
|
}
|
|
846
925
|
]
|
|
847
926
|
});
|
|
848
|
-
if (p.isCancel(
|
|
927
|
+
if (p.isCancel(selectedPackageManager)) {
|
|
849
928
|
p.cancel("Operation cancelled.");
|
|
850
929
|
process.exit(0);
|
|
851
930
|
}
|
|
931
|
+
if (typeof selectedPackageManager !== "string" || !isPackageManager(selectedPackageManager)) {
|
|
932
|
+
p.cancel("Invalid package manager selection.");
|
|
933
|
+
process.exit(1);
|
|
934
|
+
}
|
|
935
|
+
packageManager = selectedPackageManager;
|
|
852
936
|
}
|
|
853
937
|
const build = await p.confirm({
|
|
854
938
|
message: "Should we run the CI script (lint, build, test)?",
|
|
@@ -887,10 +971,12 @@ Restrictions & Behavior:
|
|
|
887
971
|
}
|
|
888
972
|
try {
|
|
889
973
|
await program.parseAsync(process.argv);
|
|
890
|
-
} catch (
|
|
891
|
-
|
|
892
|
-
if (
|
|
893
|
-
|
|
974
|
+
} catch (error) {
|
|
975
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
976
|
+
if (code === "commander.helpDisplayed" || code === "commander.version" || code === "PROCESS_EXIT_0") process.exit(0);
|
|
977
|
+
if (code === "PROCESS_EXIT_1") process.exit(1);
|
|
978
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
979
|
+
p.cancel(message);
|
|
894
980
|
process.exit(1);
|
|
895
981
|
}
|
|
896
982
|
if (!commandResult) {
|
|
@@ -898,13 +984,29 @@ Restrictions & Behavior:
|
|
|
898
984
|
p.cancel("Unknown command or missing options.");
|
|
899
985
|
process.exit(1);
|
|
900
986
|
}
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (commandResult.
|
|
907
|
-
|
|
987
|
+
const template = stripQuotes(commandResult.template);
|
|
988
|
+
if (template !== void 0) {
|
|
989
|
+
const templateResult = TemplateTypeSchema.safeParse(template);
|
|
990
|
+
if (templateResult.success) commandResult.template = templateResult.data;
|
|
991
|
+
}
|
|
992
|
+
if (commandResult.projectName !== void 0) {
|
|
993
|
+
const projectName = stripQuotes(commandResult.projectName);
|
|
994
|
+
if (projectName !== void 0) commandResult.projectName = projectName;
|
|
995
|
+
}
|
|
996
|
+
if (commandResult.description !== void 0) commandResult.description = stripQuotes(commandResult.description);
|
|
997
|
+
if (commandResult.keywords !== void 0) commandResult.keywords = stripQuotes(commandResult.keywords);
|
|
998
|
+
if (commandResult.author !== void 0) {
|
|
999
|
+
const author = stripQuotes(commandResult.author);
|
|
1000
|
+
if (author !== void 0) commandResult.author = author;
|
|
1001
|
+
}
|
|
1002
|
+
if (commandResult.githubUsername !== void 0) {
|
|
1003
|
+
const githubUsername = stripQuotes(commandResult.githubUsername);
|
|
1004
|
+
if (githubUsername !== void 0) commandResult.githubUsername = githubUsername;
|
|
1005
|
+
}
|
|
1006
|
+
if (commandResult.packageManager !== void 0) {
|
|
1007
|
+
const packageManager = stripQuotes(commandResult.packageManager);
|
|
1008
|
+
if (packageManager !== void 0 && isPackageManager(packageManager)) commandResult.packageManager = packageManager;
|
|
1009
|
+
}
|
|
908
1010
|
debug$2("Validating command result with Zod");
|
|
909
1011
|
const validationResult = ProjectOptionsSchema.safeParse(commandResult);
|
|
910
1012
|
if (!validationResult.success) {
|
|
@@ -912,35 +1014,54 @@ Restrictions & Behavior:
|
|
|
912
1014
|
p.cancel(`Invalid options: ${errors}`);
|
|
913
1015
|
process.exit(1);
|
|
914
1016
|
}
|
|
915
|
-
|
|
916
|
-
const projectDir =
|
|
917
|
-
if (await pathExists$1(projectDir) && !
|
|
1017
|
+
const validated = validationResult.data;
|
|
1018
|
+
const projectDir = validated.directory;
|
|
1019
|
+
if (await pathExists$1(projectDir) && !validated.update) {
|
|
918
1020
|
p.cancel(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
919
1021
|
process.exit(1);
|
|
920
1022
|
}
|
|
921
|
-
return
|
|
1023
|
+
return validated;
|
|
922
1024
|
};
|
|
923
1025
|
//#endregion
|
|
924
1026
|
//#region src/generators/project.ts
|
|
925
1027
|
var debug$1 = debugLib("create-template-project:generator");
|
|
926
|
-
var
|
|
1028
|
+
var moduleDir = import.meta.dirname;
|
|
1029
|
+
var pathExists = async (filePath) => {
|
|
1030
|
+
try {
|
|
1031
|
+
await fs.access(filePath);
|
|
1032
|
+
return true;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
927
1037
|
var getDependencyConfigPath = async () => {
|
|
928
|
-
const sourcePath = path.resolve(
|
|
929
|
-
const distPath = path.resolve(
|
|
1038
|
+
const sourcePath = path.resolve(moduleDir, "../config/dependencies.json");
|
|
1039
|
+
const distPath = path.resolve(moduleDir, "config/dependencies.json");
|
|
930
1040
|
return await pathExists(distPath) ? distPath : sourcePath;
|
|
931
1041
|
};
|
|
932
|
-
var pathExists = (p) => fs.access(p).then(() => true).catch(() => false);
|
|
933
1042
|
var getLog = (progress) => ({
|
|
934
|
-
info: (msg) =>
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1043
|
+
info: (msg) => {
|
|
1044
|
+
if (progress) p.log.info(msg);
|
|
1045
|
+
},
|
|
1046
|
+
success: (msg) => {
|
|
1047
|
+
if (progress) p.log.success(msg);
|
|
1048
|
+
},
|
|
1049
|
+
warn: (msg) => {
|
|
1050
|
+
p.log.warn(msg);
|
|
1051
|
+
},
|
|
1052
|
+
error: (msg) => {
|
|
1053
|
+
p.log.error(msg);
|
|
1054
|
+
}
|
|
938
1055
|
});
|
|
939
1056
|
var getSpinner = (progress) => {
|
|
940
1057
|
const s = p.spinner();
|
|
941
1058
|
return {
|
|
942
|
-
start: (msg) =>
|
|
943
|
-
|
|
1059
|
+
start: (msg) => {
|
|
1060
|
+
if (progress) s.start(msg);
|
|
1061
|
+
},
|
|
1062
|
+
stop: (msg) => {
|
|
1063
|
+
if (progress) s.stop(msg);
|
|
1064
|
+
}
|
|
944
1065
|
};
|
|
945
1066
|
};
|
|
946
1067
|
var isFileRequired = (relativePath, type) => {
|
|
@@ -952,70 +1073,111 @@ var isFileRequired = (relativePath, type) => {
|
|
|
952
1073
|
].includes(type);
|
|
953
1074
|
return true;
|
|
954
1075
|
};
|
|
1076
|
+
var getTemplateArchitectureSection;
|
|
1077
|
+
var generateGeneratedMd;
|
|
1078
|
+
var isTemplateType = (value) => value === "cli" || value === "web-vanilla" || value === "web-app" || value === "web-fullstack";
|
|
1079
|
+
var isRecord$1 = (value) => typeof value === "object" && value !== null;
|
|
1080
|
+
var parseStringRecord = (value) => {
|
|
1081
|
+
if (!isRecord$1(value)) return {};
|
|
1082
|
+
const out = {};
|
|
1083
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") out[key] = entry;
|
|
1084
|
+
return out;
|
|
1085
|
+
};
|
|
1086
|
+
var parseDependencyConfig = (raw) => {
|
|
1087
|
+
const parsed = JSON.parse(raw);
|
|
1088
|
+
if (!isRecord$1(parsed) || !isRecord$1(parsed.dependencies)) throw new Error("Invalid dependency configuration file format.");
|
|
1089
|
+
const dependencies = {};
|
|
1090
|
+
for (const [name, value] of Object.entries(parsed.dependencies)) {
|
|
1091
|
+
if (!isRecord$1(value)) continue;
|
|
1092
|
+
const { version, description } = value;
|
|
1093
|
+
if (typeof version === "string" && typeof description === "string") dependencies[name] = {
|
|
1094
|
+
version,
|
|
1095
|
+
description
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
return { dependencies };
|
|
1099
|
+
};
|
|
1100
|
+
var parseExistingProjectPackage = (raw) => {
|
|
1101
|
+
const parsed = JSON.parse(raw);
|
|
1102
|
+
if (!isRecord$1(parsed)) return {};
|
|
1103
|
+
const createTemplateProject = isRecord$1(parsed["create-template-project"]) ? parsed["create-template-project"] : void 0;
|
|
1104
|
+
const template = createTemplateProject ? createTemplateProject.template : void 0;
|
|
1105
|
+
return {
|
|
1106
|
+
...parsed,
|
|
1107
|
+
scripts: parseStringRecord(parsed.scripts),
|
|
1108
|
+
dependencies: parseStringRecord(parsed.dependencies),
|
|
1109
|
+
devDependencies: parseStringRecord(parsed.devDependencies),
|
|
1110
|
+
"create-template-project": typeof template === "string" && isTemplateType(template) ? { template } : void 0
|
|
1111
|
+
};
|
|
1112
|
+
};
|
|
1113
|
+
var parseTemplatePackageJson = (raw) => {
|
|
1114
|
+
const parsed = JSON.parse(raw);
|
|
1115
|
+
if (!isRecord$1(parsed)) return {};
|
|
1116
|
+
return {
|
|
1117
|
+
...parsed,
|
|
1118
|
+
dependencies: parseStringRecord(parsed.dependencies),
|
|
1119
|
+
devDependencies: parseStringRecord(parsed.devDependencies)
|
|
1120
|
+
};
|
|
1121
|
+
};
|
|
1122
|
+
var toErrorDetail = (error) => {
|
|
1123
|
+
if (error instanceof Error) {
|
|
1124
|
+
const withOutput = error;
|
|
1125
|
+
const stdout = withOutput.stdout ?? "";
|
|
1126
|
+
const stderr = withOutput.stderr ?? "";
|
|
1127
|
+
const detail = stdout.length > 0 || stderr.length > 0 ? `\n\nOutput:\n${stdout}\n${stderr}` : "";
|
|
1128
|
+
return {
|
|
1129
|
+
message: error.message,
|
|
1130
|
+
detail
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
message: String(error),
|
|
1135
|
+
detail: ""
|
|
1136
|
+
};
|
|
1137
|
+
};
|
|
955
1138
|
var generateProject = async (opts) => {
|
|
956
1139
|
const { template: type, projectName, author, githubUsername, directory, update, progress } = opts;
|
|
957
|
-
const isProgress = progress
|
|
1140
|
+
const isProgress = progress;
|
|
958
1141
|
const log = getLog(isProgress);
|
|
959
1142
|
const spinner = () => getSpinner(isProgress);
|
|
960
1143
|
const projectDir = directory;
|
|
961
1144
|
debug$1("Project generation started for: %s", projectName);
|
|
962
1145
|
debug$1("Options: %O", opts);
|
|
963
1146
|
debug$1("Project directory: %s", projectDir);
|
|
964
|
-
|
|
965
|
-
if (await pathExists(projectDir)) {
|
|
966
|
-
if (!isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
967
|
-
}
|
|
968
|
-
const templates = [getBaseTemplate(opts)];
|
|
1147
|
+
const isUpdate = update;
|
|
1148
|
+
if (await pathExists(projectDir) && !isUpdate) throw new Error(`Directory "${projectDir}" already exists. Use the "update" command to update.`);
|
|
969
1149
|
debug$1("Applying template: base");
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
debug$1("Applying template: cli");
|
|
973
|
-
templates.push(getCliTemplate(opts));
|
|
974
|
-
break;
|
|
975
|
-
case "web-vanilla":
|
|
976
|
-
debug$1("Applying template: web-vanilla");
|
|
977
|
-
templates.push(getWebVanillaTemplate(opts));
|
|
978
|
-
break;
|
|
979
|
-
case "web-app":
|
|
980
|
-
debug$1("Applying template: web-app");
|
|
981
|
-
templates.push(getWebAppTemplate(opts));
|
|
982
|
-
break;
|
|
983
|
-
case "web-fullstack":
|
|
984
|
-
debug$1("Applying template: web-fullstack");
|
|
985
|
-
templates.push(getWebFullstackTemplate(opts));
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
1150
|
+
debug$1("Applying template: %s", type);
|
|
1151
|
+
const templates = getProjectTemplates(opts);
|
|
988
1152
|
debug$1("Ensuring directory exists: %s", projectDir);
|
|
989
1153
|
await fs.mkdir(projectDir, { recursive: true });
|
|
990
1154
|
debug$1("Loading dependency configuration");
|
|
991
1155
|
const depConfigPath = await getDependencyConfigPath();
|
|
992
|
-
const depConfig =
|
|
1156
|
+
const depConfig = parseDependencyConfig(await fs.readFile(depConfigPath, "utf8"));
|
|
993
1157
|
const addedDeps = [];
|
|
994
1158
|
const resolveDeps = (deps = {}) => {
|
|
995
|
-
for (const dep of Object.keys(deps)) {
|
|
1159
|
+
for (const dep of Object.keys(deps)) if (Object.hasOwn(depConfig.dependencies, dep)) {
|
|
996
1160
|
const config = depConfig.dependencies[dep];
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
debug$1(`Dependency "${dep}" missing in config`);
|
|
1006
|
-
}
|
|
1161
|
+
deps[dep] = config.version;
|
|
1162
|
+
addedDeps.push({
|
|
1163
|
+
name: dep,
|
|
1164
|
+
description: config.description
|
|
1165
|
+
});
|
|
1166
|
+
} else {
|
|
1167
|
+
log.warn(`Dependency "${dep}" not found in central configuration. Using empty version.`);
|
|
1168
|
+
debug$1(`Dependency "${dep}" missing in config`);
|
|
1007
1169
|
}
|
|
1008
1170
|
};
|
|
1009
1171
|
let finalPkg = {
|
|
1010
1172
|
name: projectName,
|
|
1011
1173
|
version: "0.1.0",
|
|
1012
1174
|
private: true,
|
|
1013
|
-
description: opts.description
|
|
1014
|
-
keywords: opts.keywords ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
|
|
1175
|
+
description: opts.description ?? "TODO: Add project description",
|
|
1176
|
+
keywords: opts.keywords !== void 0 ? opts.keywords.split(",").map((k) => k.trim()) : ["TODO: Add keywords"],
|
|
1015
1177
|
homepage: `https://github.com/${githubUsername}/${projectName}#readme`,
|
|
1016
1178
|
bugs: { url: `https://github.com/${githubUsername}/${projectName}/issues` },
|
|
1017
1179
|
license: "MIT",
|
|
1018
|
-
author
|
|
1180
|
+
author,
|
|
1019
1181
|
repository: {
|
|
1020
1182
|
type: "git",
|
|
1021
1183
|
url: `https://github.com/${githubUsername}/${projectName}.git`
|
|
@@ -1029,22 +1191,28 @@ var generateProject = async (opts) => {
|
|
|
1029
1191
|
const pkgPath = path.join(projectDir, "package.json");
|
|
1030
1192
|
if (isUpdate && await pathExists(pkgPath)) {
|
|
1031
1193
|
debug$1("Loading existing package.json for update");
|
|
1032
|
-
const existingPkg =
|
|
1033
|
-
if (!existingPkg["create-template-project"]
|
|
1194
|
+
const existingPkg = parseExistingProjectPackage(await fs.readFile(pkgPath, "utf8"));
|
|
1195
|
+
if (!existingPkg["create-template-project"]) throw new Error(`No "create-template-project" configuration found in ${pkgPath}. The update command can only be used on projects created with this tool.`);
|
|
1034
1196
|
finalPkg = {
|
|
1035
1197
|
...finalPkg,
|
|
1036
|
-
...existingPkg
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1198
|
+
...existingPkg,
|
|
1199
|
+
"create-template-project": {
|
|
1200
|
+
...existingPkg["create-template-project"],
|
|
1201
|
+
template: type
|
|
1202
|
+
},
|
|
1203
|
+
scripts: { ...existingPkg.scripts },
|
|
1204
|
+
dependencies: { ...existingPkg.dependencies },
|
|
1205
|
+
devDependencies: { ...existingPkg.devDependencies }
|
|
1041
1206
|
};
|
|
1042
|
-
finalPkg.scripts = { ...existingPkg.scripts };
|
|
1043
|
-
finalPkg.dependencies = { ...existingPkg.dependencies };
|
|
1044
|
-
finalPkg.devDependencies = { ...existingPkg.devDependencies };
|
|
1045
1207
|
debug$1("Loaded existing package.json: %O", finalPkg);
|
|
1046
1208
|
}
|
|
1047
|
-
|
|
1209
|
+
const templatePackageParts = await Promise.all(templates.map(async (template) => {
|
|
1210
|
+
if (template.templateDir === void 0) return;
|
|
1211
|
+
const templatePkgPath = path.join(template.templateDir, "package.json");
|
|
1212
|
+
if (!await pathExists(templatePkgPath)) return;
|
|
1213
|
+
return parseTemplatePackageJson(await fs.readFile(templatePkgPath, "utf8"));
|
|
1214
|
+
}));
|
|
1215
|
+
for (const [index, t] of templates.entries()) {
|
|
1048
1216
|
debug$1("Collecting dependencies and scripts from template: %s", t.name);
|
|
1049
1217
|
const templateDeps = { ...t.dependencies };
|
|
1050
1218
|
const templateDevDeps = { ...t.devDependencies };
|
|
@@ -1053,22 +1221,19 @@ var generateProject = async (opts) => {
|
|
|
1053
1221
|
Object.assign(finalPkg.scripts, t.scripts);
|
|
1054
1222
|
Object.assign(finalPkg.dependencies, templateDeps);
|
|
1055
1223
|
Object.assign(finalPkg.devDependencies, templateDevDeps);
|
|
1056
|
-
if (t.workspaces) finalPkg.workspaces = t.workspaces;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
resolveDeps(pkgPart.devDependencies);
|
|
1063
|
-
mergePackageJson(finalPkg, pkgPart);
|
|
1064
|
-
}
|
|
1224
|
+
if (t.workspaces !== void 0) finalPkg.workspaces = t.workspaces;
|
|
1225
|
+
const pkgPart = templatePackageParts[index];
|
|
1226
|
+
if (pkgPart !== void 0) {
|
|
1227
|
+
resolveDeps(pkgPart.dependencies);
|
|
1228
|
+
resolveDeps(pkgPart.devDependencies);
|
|
1229
|
+
mergePackageJson(finalPkg, pkgPart);
|
|
1065
1230
|
}
|
|
1066
1231
|
}
|
|
1067
1232
|
const actions = [];
|
|
1068
1233
|
const pendingOperations = [];
|
|
1069
1234
|
for (const t of templates) {
|
|
1070
1235
|
debug$1("Collecting template files for: %s", t.name);
|
|
1071
|
-
if (t.templateDir) {
|
|
1236
|
+
if (t.templateDir !== void 0) {
|
|
1072
1237
|
debug$1("Reading physical files from: %s", t.templateDir);
|
|
1073
1238
|
const files = await getAllFiles(t.templateDir);
|
|
1074
1239
|
for (const file of files) {
|
|
@@ -1096,7 +1261,7 @@ var generateProject = async (opts) => {
|
|
|
1096
1261
|
continue;
|
|
1097
1262
|
}
|
|
1098
1263
|
if (relativePath.startsWith("_") && relativePath.endsWith(".config.ts")) {
|
|
1099
|
-
relativePath = relativePath.
|
|
1264
|
+
relativePath = relativePath.slice(1);
|
|
1100
1265
|
targetPath = path.join(projectDir, relativePath);
|
|
1101
1266
|
}
|
|
1102
1267
|
if (relativePath === "package.json") continue;
|
|
@@ -1214,7 +1379,7 @@ var generateProject = async (opts) => {
|
|
|
1214
1379
|
}
|
|
1215
1380
|
}
|
|
1216
1381
|
}
|
|
1217
|
-
const pm = opts.packageManager
|
|
1382
|
+
const pm = opts.packageManager;
|
|
1218
1383
|
if (pm !== "npm") {
|
|
1219
1384
|
for (const [key, value] of Object.entries(finalPkg.scripts)) if (typeof value === "string") finalPkg.scripts[key] = value.replaceAll("npm run ", `${pm} run `);
|
|
1220
1385
|
}
|
|
@@ -1275,7 +1440,10 @@ var generateProject = async (opts) => {
|
|
|
1275
1440
|
}
|
|
1276
1441
|
} else log.info("No changes detected.");
|
|
1277
1442
|
}
|
|
1278
|
-
|
|
1443
|
+
await pendingOperations.reduce(async (previous, operation) => {
|
|
1444
|
+
await previous;
|
|
1445
|
+
await operation();
|
|
1446
|
+
}, Promise.resolve());
|
|
1279
1447
|
const states = {
|
|
1280
1448
|
gitInitialized: false,
|
|
1281
1449
|
githubCreated: false,
|
|
@@ -1298,10 +1466,10 @@ var generateProject = async (opts) => {
|
|
|
1298
1466
|
});
|
|
1299
1467
|
log.success("Initialized Git repository (main branch).");
|
|
1300
1468
|
states.gitInitialized = true;
|
|
1301
|
-
} catch (
|
|
1302
|
-
debug$1("Failed to initialize Git: %O",
|
|
1303
|
-
const
|
|
1304
|
-
log.error(`Failed to initialize Git: ${
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
debug$1("Failed to initialize Git: %O", error);
|
|
1471
|
+
const { message, detail } = toErrorDetail(error);
|
|
1472
|
+
log.error(`Failed to initialize Git: ${message}${detail}`);
|
|
1305
1473
|
}
|
|
1306
1474
|
} else states.gitInitialized = true;
|
|
1307
1475
|
if (opts.build) {
|
|
@@ -1317,12 +1485,12 @@ var generateProject = async (opts) => {
|
|
|
1317
1485
|
});
|
|
1318
1486
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Dependencies installed (${pm} install).`);
|
|
1319
1487
|
states.depsInstalled = true;
|
|
1320
|
-
} catch (
|
|
1321
|
-
debug$1("Failed to install dependencies: %O",
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
debug$1("Failed to install dependencies: %O", error);
|
|
1322
1490
|
s.stop("Failed to install dependencies.");
|
|
1323
|
-
const
|
|
1324
|
-
log.error(`${
|
|
1325
|
-
throw new Error(`Failed to install dependencies: ${
|
|
1491
|
+
const { message, detail } = toErrorDetail(error);
|
|
1492
|
+
log.error(`${message}${detail}`);
|
|
1493
|
+
throw new Error(`Failed to install dependencies: ${message}${detail}`, { cause: error });
|
|
1326
1494
|
}
|
|
1327
1495
|
}
|
|
1328
1496
|
if (opts.build && finalPkg.scripts.ci) {
|
|
@@ -1338,11 +1506,11 @@ var generateProject = async (opts) => {
|
|
|
1338
1506
|
preferLocal: true
|
|
1339
1507
|
});
|
|
1340
1508
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Files formatted (${pm} run format).`);
|
|
1341
|
-
} catch (
|
|
1342
|
-
debug$1("Failed to format files: %O",
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
debug$1("Failed to format files: %O", error);
|
|
1343
1511
|
s.stop("Failed to format files.");
|
|
1344
|
-
const
|
|
1345
|
-
log.error(`${
|
|
1512
|
+
const { message, detail } = toErrorDetail(error);
|
|
1513
|
+
log.error(`${message}${detail}`);
|
|
1346
1514
|
}
|
|
1347
1515
|
}
|
|
1348
1516
|
s.start(`Running CI script (lint, build, test) (${pm} run ci)...`);
|
|
@@ -1355,12 +1523,12 @@ var generateProject = async (opts) => {
|
|
|
1355
1523
|
});
|
|
1356
1524
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m CI script completed (${pm} run ci).`);
|
|
1357
1525
|
states.ciRun = true;
|
|
1358
|
-
} catch (
|
|
1359
|
-
debug$1("Failed to run CI script: %O",
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
debug$1("Failed to run CI script: %O", error);
|
|
1360
1528
|
s.stop("Failed to run CI script.");
|
|
1361
|
-
const
|
|
1362
|
-
log.error(`${
|
|
1363
|
-
throw new Error(`Failed to run CI script: ${
|
|
1529
|
+
const { message, detail } = toErrorDetail(error);
|
|
1530
|
+
log.error(`${message}${detail}`);
|
|
1531
|
+
throw new Error(`Failed to run CI script: ${message}${detail}`, { cause: error });
|
|
1364
1532
|
}
|
|
1365
1533
|
}
|
|
1366
1534
|
if (opts.createGithubRepository && !isUpdate) {
|
|
@@ -1400,32 +1568,38 @@ var generateProject = async (opts) => {
|
|
|
1400
1568
|
});
|
|
1401
1569
|
s.stop(`\x1b[1G\x1b[2K\x1b[32m◆\x1b[39m Created GitHub repository and pushed initial commit.`);
|
|
1402
1570
|
states.githubCreated = true;
|
|
1403
|
-
} catch (
|
|
1404
|
-
debug$1("Failed to create/push GitHub repository: %O",
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
debug$1("Failed to create/push GitHub repository: %O", error);
|
|
1405
1573
|
s.stop("Failed to create/push GitHub repository.");
|
|
1406
|
-
const
|
|
1407
|
-
log.warn(`Failed to create/push GitHub repository: ${
|
|
1408
|
-
states.githubError =
|
|
1574
|
+
const { message, detail } = toErrorDetail(error);
|
|
1575
|
+
log.warn(`Failed to create/push GitHub repository: ${message}${detail}\nEnsure "gh" CLI is installed and authenticated.`);
|
|
1576
|
+
states.githubError = message;
|
|
1409
1577
|
}
|
|
1410
1578
|
}
|
|
1411
|
-
let hasErrors = false;
|
|
1412
1579
|
let hasWarnings = false;
|
|
1413
1580
|
const errorMessages = [];
|
|
1414
1581
|
if (states.githubError) {
|
|
1415
1582
|
hasWarnings = true;
|
|
1416
1583
|
errorMessages.push(`GitHub repository creation failed: ${states.githubError}`);
|
|
1417
1584
|
}
|
|
1585
|
+
const hasErrors = errorMessages.some((message) => message.startsWith("ERROR:"));
|
|
1418
1586
|
await generateGeneratedMd(projectDir, opts, pm, states, isUpdate, {
|
|
1419
1587
|
hasErrors,
|
|
1420
1588
|
hasWarnings,
|
|
1421
1589
|
errorMessages
|
|
1422
1590
|
}, actions);
|
|
1423
1591
|
const successMsg = `Project "${projectName}" ${isUpdate ? "updated" : "scaffolded"} successfully in ${projectDir}. A detailed setup guide has been generated at GENERATED.md`;
|
|
1424
|
-
if (
|
|
1592
|
+
if (hasErrors) log.error(`${successMsg} (completed with errors)`);
|
|
1593
|
+
else if (hasWarnings) log.warn(`${successMsg} (completed with warnings)`);
|
|
1425
1594
|
else log.success(successMsg);
|
|
1426
1595
|
};
|
|
1427
|
-
async
|
|
1596
|
+
generateGeneratedMd = async (projectDir, opts, pm, states, isUpdate, status, actions) => {
|
|
1428
1597
|
const statusBadge = status.hasErrors ? "🔴 **Completed with Errors**" : status.hasWarnings ? "🟡 **Completed with Warnings**" : "🟢 **Successfully Completed**";
|
|
1598
|
+
const skippedStepInstructions = [
|
|
1599
|
+
...states.depsSkipped ? [`- [ ] **Install dependencies manually:** Run \`${pm} install\` from the project root.`] : [],
|
|
1600
|
+
...states.githubSkipped ? [`- [ ] **Create and push GitHub repository manually:** Verify GitHub CLI auth with \`gh auth status\`, then run \`gh repo create ${opts.projectName} --public --source=. --remote=origin --push\`.`] : [],
|
|
1601
|
+
...states.ciSkipped ? [`- [ ] **Run CI checks manually:** After dependencies are installed, run \`${pm} run ci\`.`] : []
|
|
1602
|
+
];
|
|
1429
1603
|
const md = [
|
|
1430
1604
|
`# 🚀 Project Setup Guide: ${opts.projectName}`,
|
|
1431
1605
|
"",
|
|
@@ -1454,6 +1628,12 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1454
1628
|
`- [${states.githubCreated ? "x" : " "}] Create and push GitHub repository${states.githubSkipped ? " *(Skipped)*" : states.githubError ? " *(Failed)*" : ""}`,
|
|
1455
1629
|
`- [${states.ciRun ? "x" : " "}] Run initial CI pipeline (lint, build, test)${states.ciSkipped ? " *(Skipped)*" : ""}`,
|
|
1456
1630
|
"",
|
|
1631
|
+
...skippedStepInstructions.length > 0 ? [
|
|
1632
|
+
"## ⏭️ Complete Skipped Steps Manually",
|
|
1633
|
+
"Some initialization steps were marked as *(Skipped)*. Use the guidance below to complete them yourself:",
|
|
1634
|
+
...skippedStepInstructions,
|
|
1635
|
+
""
|
|
1636
|
+
] : [],
|
|
1457
1637
|
...isUpdate ? [
|
|
1458
1638
|
"### 🛠️ Upgrade Details",
|
|
1459
1639
|
"The following files were affected by this update:",
|
|
@@ -1468,8 +1648,8 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1468
1648
|
CONFLICT: "🔥 CONFLICT",
|
|
1469
1649
|
DELETE: "🗑️ DELETE",
|
|
1470
1650
|
UPDATED: "✨ UPDATED"
|
|
1471
|
-
}[a.type]
|
|
1472
|
-
return `| \`${a.path}\` | ${actionIcon} | ${a.reason
|
|
1651
|
+
}[a.type] ?? a.type;
|
|
1652
|
+
return `| \`${a.path}\` | ${actionIcon} | ${a.reason ?? "-"} | ${a.recommendedAction ?? (a.type === "CONFLICT" ? "**Resolve conflicts**" : "Review changes")} |`;
|
|
1473
1653
|
}),
|
|
1474
1654
|
""
|
|
1475
1655
|
] : [],
|
|
@@ -1544,6 +1724,7 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1544
1724
|
"| Command | Description |",
|
|
1545
1725
|
"| :--- | :--- |",
|
|
1546
1726
|
"| `gh repo view --web` | Open the repository in your default web browser |",
|
|
1727
|
+
"| `gh repo create <name> --public --source=. --remote=origin --push` | Create and push a new GitHub repository |",
|
|
1547
1728
|
"| `gh pr create` | Create a new Pull Request |",
|
|
1548
1729
|
"| `gh pr checkout <pr-number>` | Checkout a Pull Request branch locally |",
|
|
1549
1730
|
"| `gh issue create` | Create a new Issue |",
|
|
@@ -1566,8 +1747,8 @@ async function generateGeneratedMd(projectDir, opts, pm, states, isUpdate, statu
|
|
|
1566
1747
|
"<p align=\"center\"><i>This file was auto-generated by <b>create-template-project</b>.</i></p>"
|
|
1567
1748
|
].join("\n");
|
|
1568
1749
|
await fs.writeFile(path.join(projectDir, "GENERATED.md"), md);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1750
|
+
};
|
|
1751
|
+
getTemplateArchitectureSection = (template) => {
|
|
1571
1752
|
switch (template) {
|
|
1572
1753
|
case "cli": return [
|
|
1573
1754
|
"## 🏗️ CLI Architecture",
|
|
@@ -1622,21 +1803,33 @@ function getTemplateArchitectureSection(template) {
|
|
|
1622
1803
|
];
|
|
1623
1804
|
default: return [];
|
|
1624
1805
|
}
|
|
1625
|
-
}
|
|
1806
|
+
};
|
|
1626
1807
|
//#endregion
|
|
1627
1808
|
//#region src/index.ts
|
|
1628
1809
|
if (process.argv.includes("--debug")) {
|
|
1629
|
-
process.env
|
|
1810
|
+
process.env.DEBUG = "create-template-project:*";
|
|
1630
1811
|
debugLib.enable("create-template-project:*");
|
|
1631
1812
|
}
|
|
1632
1813
|
var debug = debugLib("create-template-project:main");
|
|
1814
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
1815
|
+
var getErrorCode = (error) => {
|
|
1816
|
+
if (!isRecord(error)) return;
|
|
1817
|
+
const { code } = error;
|
|
1818
|
+
return typeof code === "string" ? code : void 0;
|
|
1819
|
+
};
|
|
1820
|
+
var getErrorMessage = (error) => {
|
|
1821
|
+
if (isRecord(error)) {
|
|
1822
|
+
const { message } = error;
|
|
1823
|
+
if (typeof message === "string") return message;
|
|
1824
|
+
}
|
|
1825
|
+
return String(error);
|
|
1826
|
+
};
|
|
1633
1827
|
var main = async () => {
|
|
1634
1828
|
try {
|
|
1635
1829
|
debug("Starting CLI execution");
|
|
1636
1830
|
debug("Parsing arguments");
|
|
1637
1831
|
const options = await parseArgs();
|
|
1638
|
-
|
|
1639
|
-
const isProgress = options.progress !== false;
|
|
1832
|
+
const isProgress = options.progress;
|
|
1640
1833
|
if (isProgress) intro("create-template-project");
|
|
1641
1834
|
debug("Arguments parsed: %O", options);
|
|
1642
1835
|
debug("Generating project");
|
|
@@ -1645,12 +1838,13 @@ var main = async () => {
|
|
|
1645
1838
|
if (isProgress) outro("Done!");
|
|
1646
1839
|
} catch (error) {
|
|
1647
1840
|
debug("Execution failed: %O", error);
|
|
1648
|
-
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
1841
|
+
const errorCode = getErrorCode(error);
|
|
1842
|
+
if (errorCode === "PROCESS_EXIT_0" || errorCode === "commander.helpDisplayed" || errorCode === "commander.version") process.exit(0);
|
|
1843
|
+
if (errorCode === "PROCESS_EXIT_1") process.exit(1);
|
|
1844
|
+
cancel(getErrorMessage(error));
|
|
1651
1845
|
process.exit(1);
|
|
1652
1846
|
}
|
|
1653
1847
|
};
|
|
1654
|
-
if (import.meta.url.endsWith("src/index.ts") || import.meta.url.endsWith("dist/index.js")) await main();
|
|
1848
|
+
if (process.env.NODE_ENV !== "test" && (import.meta.url.endsWith("src/index.ts") || import.meta.url.endsWith("dist/index.js"))) await main();
|
|
1655
1849
|
//#endregion
|
|
1656
1850
|
export { main };
|