create-szyy-app 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,12 +17,13 @@ npx create-szyy-app my-order-sys --yes --minimal
17
17
 
18
18
  ## 选项
19
19
 
20
- | 选项 | 说明 |
21
- | ------------------- | ---------------------------------------------------- |
22
- | `[project-name]` | 项目名称(kebab-case,小写字母开头) |
23
- | `-p, --port <port>` | 联调端口,写入 `.env` / `.env.micro` 等(默认 5690) |
24
- | `--minimal` | 删除 `templateSys` Demo,仅保留首页菜单 |
25
- | `-y, --yes` | 跳过交互,使用默认值 |
20
+ | 选项 | 说明 |
21
+ | ---------------------- | ---------------------------------------------------- |
22
+ | `[project-name]` | 项目名称(kebab-case,小写字母开头) |
23
+ | `-p, --port <port>` | 联调端口,写入 `.env` / `.env.micro` 等(默认 5690) |
24
+ | `--template-tag <tag>` | 覆盖模板 Git tag |
25
+ | `--minimal` | 删除 `templateSys` Demo,仅保留首页菜单 |
26
+ | `-y, --yes` | 跳过交互,使用默认值 |
26
27
 
27
28
  > **注意:** `pnpm dev` 独立模式端口固定 **3000**(`.env.development`),不受 `--port` 影响。`pnpm dev:micro` 使用 `--port` 配置的联调端口。
28
29
 
@@ -42,10 +43,15 @@ pnpm build:prod # 生产构建
42
43
  | -------------------- | --------------------------------------------------------------------------------- |
43
44
  | `SZYY_TEMPLATE_REPO` | 覆盖模板 Git 仓库 URL(默认 `http://172.1.1.65/RDKit/sunyard-szyy-app-template`) |
44
45
  | `SZYY_TEMPLATE_PATH` | 使用本地目录作为模板(开发/联调 CLI 时使用) |
46
+ | `SZYY_TEMPLATE_TAG` | 覆盖模板 Git tag |
47
+ | `SZYY_VERSIONS_URL` | 覆盖远程版本配置 URL(默认从模板仓 `create-app` 分支拉取 `versions.json`) |
48
+ | `SZYY_VERSIONS_PATH` | 使用本地 `versions.json`(开发/联调 CLI 时使用) |
45
49
 
46
50
  ## 版本锁定
47
51
 
48
- 依赖版本由 `templates/versions.json` 统一管理,生成项目的 `package.json` 会写死精确版本号(无 `^`)。
52
+ 版本配置默认从模板仓 **`create-app` 分支** 的 `versions.json` 远程拉取;拉取失败时使用 npm 包内置配置兜底。更新模板 tag 或依赖版本时,只需修改模板仓 `create-app` 分支上的 `versions.json`,**无需重新发布 CLI**。
53
+
54
+ 生成项目的 `package.json` 会写死精确版本号(无 `^`)。
49
55
 
50
56
  ## 开发 CLI
51
57
 
@@ -54,7 +60,7 @@ pnpm install
54
60
  pnpm build
55
61
 
56
62
  # 使用本地模板联调
57
- SZYY_TEMPLATE_PATH=../sunyard-szyy-app-template node dist/index.js test-app --yes
63
+ SZYY_TEMPLATE_PATH=../sunyard-szyy-app-template SZYY_VERSIONS_PATH=../sunyard-szyy-app-template/versions.json node dist/index.js test-app --yes
58
64
  ```
59
65
 
60
66
  ## 发布
@@ -77,11 +83,12 @@ pnpm publish:pkg --tag=beta
77
83
 
78
84
  ## 故障排查
79
85
 
80
- | 问题 | 处理 |
81
- | -------------- | ------------------------------------------------------------- |
82
- | degit 拉取失败 | 检查内网 Git 连通性,或设置 `SZYY_TEMPLATE_PATH` 使用本地模板 |
83
- | 占位符残留报错 | 确认模板 tag `versions.json` 中 `template` 版本一致 |
84
- | 联调端口冲突 | 创建时指定 `--port`,或修改生成项目 `.env.micro` |
86
+ | 问题 | 处理 |
87
+ | ---------------- | ----------------------------------------------------------------------------------- |
88
+ | degit 拉取失败 | 检查内网 Git 连通性,或设置 `SZYY_TEMPLATE_PATH` 使用本地模板 |
89
+ | 占位符残留报错 | 确认模板 tag 与远程/本地 `versions.json` 中 `template` 版本一致 |
90
+ | 版本配置拉取失败 | 检查内网 Git 连通性,CLI 会自动回退内置配置;可用 `SZYY_VERSIONS_PATH` 指定本地配置 |
91
+ | 联调端口冲突 | 创建时指定 `--port`,或修改生成项目 `.env.micro` |
85
92
 
86
93
  ## 延伸阅读
87
94
 
package/dist/index.js CHANGED
@@ -1,32 +1,133 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import fs3 from "fs";
5
- import path4 from "path";
4
+ import fs4 from "fs";
5
+ import path5 from "path";
6
6
  import { Command } from "commander";
7
7
  import * as p2 from "@clack/prompts";
8
8
  import pc2 from "picocolors";
9
9
 
10
10
  // src/prompts.ts
11
- import path2 from "path";
11
+ import path from "path";
12
12
  import * as p from "@clack/prompts";
13
13
  import pc from "picocolors";
14
14
 
15
+ // src/validate.ts
16
+ function validateName(name) {
17
+ if (!name) return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
18
+ if (!/^[a-z][a-z0-9-_]*$/.test(name)) {
19
+ return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26(-)\u6216\u4E0B\u5212\u7EBF(_)\uFF0C\u4E14\u4EE5\u5B57\u6BCD\u5F00\u5934";
20
+ }
21
+ return null;
22
+ }
23
+ function validatePort(port) {
24
+ const n = Number(port);
25
+ if (!port || isNaN(n) || !Number.isInteger(n) || n < 1024 || n > 65535) {
26
+ return "\u7AEF\u53E3\u53F7\u5FC5\u987B\u662F 1024 ~ 65535 \u4E4B\u95F4\u7684\u6574\u6570";
27
+ }
28
+ return null;
29
+ }
30
+ function toAppTitle(name) {
31
+ return name.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
32
+ }
33
+ function toAppNameUpper(name) {
34
+ return name.replace(/[-_]/g, "").toUpperCase();
35
+ }
36
+
37
+ // src/prompts.ts
38
+ async function collectOptions(projectNameArg, flags, versions) {
39
+ const cwd = process.cwd();
40
+ p.intro(`${pc.bgCyan(pc.black(" create-szyy-app "))} \u5FAE\u524D\u7AEF\u5B50\u5E94\u7528\u811A\u624B\u67B6`);
41
+ let appName = projectNameArg?.trim() || "";
42
+ if (!flags.yes || !appName) {
43
+ const nameResult = await p.text({
44
+ message: "\u9879\u76EE\u540D\u79F0",
45
+ placeholder: "my-order-sys",
46
+ initialValue: appName || void 0,
47
+ validate: (value) => validateName(value?.trim() || "") ?? void 0
48
+ });
49
+ if (p.isCancel(nameResult)) {
50
+ p.cancel("\u5DF2\u53D6\u6D88");
51
+ return null;
52
+ }
53
+ appName = nameResult.trim();
54
+ } else {
55
+ const nameError = validateName(appName);
56
+ if (nameError) {
57
+ p.log.error(nameError);
58
+ return null;
59
+ }
60
+ }
61
+ let appPort = flags.port ?? versions.defaultPort;
62
+ if (!flags.yes || flags.port === void 0) {
63
+ const portResult = await p.text({
64
+ message: "\u672C\u5730\u8054\u8C03\u7AEF\u53E3",
65
+ placeholder: String(versions.defaultPort),
66
+ initialValue: String(appPort),
67
+ validate: (value) => {
68
+ const port = value?.trim() || String(versions.defaultPort);
69
+ return validatePort(port) ?? void 0;
70
+ }
71
+ });
72
+ if (p.isCancel(portResult)) {
73
+ p.cancel("\u5DF2\u53D6\u6D88");
74
+ return null;
75
+ }
76
+ appPort = Number(portResult.trim() || versions.defaultPort);
77
+ } else {
78
+ const portError = validatePort(appPort);
79
+ if (portError) {
80
+ p.log.error(portError);
81
+ return null;
82
+ }
83
+ }
84
+ let keepDemo = !flags.minimal;
85
+ if (!flags.yes && flags.minimal === void 0) {
86
+ const demoResult = await p.confirm({
87
+ message: "\u662F\u5426\u4FDD\u7559 Demo \u9875",
88
+ initialValue: true
89
+ });
90
+ if (p.isCancel(demoResult)) {
91
+ p.cancel("\u5DF2\u53D6\u6D88");
92
+ return null;
93
+ }
94
+ keepDemo = demoResult;
95
+ }
96
+ const defaultTarget = path.resolve(cwd, appName);
97
+ let targetDir = defaultTarget;
98
+ if (!flags.yes) {
99
+ const dirResult = await p.text({
100
+ message: "\u76EE\u6807\u76EE\u5F55",
101
+ initialValue: defaultTarget,
102
+ validate: (value) => {
103
+ if (!value?.trim()) return "\u76EE\u6807\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A";
104
+ return void 0;
105
+ }
106
+ });
107
+ if (p.isCancel(dirResult)) {
108
+ p.cancel("\u5DF2\u53D6\u6D88");
109
+ return null;
110
+ }
111
+ targetDir = path.resolve(dirResult.trim());
112
+ }
113
+ return {
114
+ appName,
115
+ appNameUpper: toAppNameUpper(appName),
116
+ appTitle: toAppTitle(appName),
117
+ appPort,
118
+ targetDir,
119
+ keepDemo
120
+ };
121
+ }
122
+
15
123
  // src/replace.ts
16
124
  import fs from "fs";
17
- import path from "path";
18
- import { fileURLToPath } from "url";
19
- var __filename = fileURLToPath(import.meta.url);
20
- var __dirname = path.dirname(__filename);
21
- function loadVersions() {
22
- const versionsPath = path.resolve(__dirname, "../templates/versions.json");
23
- return JSON.parse(fs.readFileSync(versionsPath, "utf-8"));
24
- }
125
+ import path2 from "path";
25
126
  function stripRange(version) {
26
127
  return version.replace(/^[\^~>=<]+/, "");
27
128
  }
28
129
  function pinDependencies(targetDir, versions) {
29
- const pkgPath = path.join(targetDir, "package.json");
130
+ const pkgPath = path2.join(targetDir, "package.json");
30
131
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
31
132
  for (const section of ["dependencies", "devDependencies"]) {
32
133
  const deps = pkg[section];
@@ -65,13 +166,13 @@ var INCLUDE_EXTENSIONS = [
65
166
  var INCLUDE_BASENAMES = [".env", ".env.development", ".env.production", ".env.micro", ".env.sit", ".gitignore"];
66
167
  var PORT_SKIP_FILES = /* @__PURE__ */ new Set([".env.development"]);
67
168
  function shouldSkip(filePath, rootDir) {
68
- const rel = path.relative(rootDir, filePath);
69
- return SKIP_PATTERNS.some((p3) => rel === p3 || rel.startsWith(`${p3}${path.sep}`) || rel.startsWith(`${p3}/`));
169
+ const rel = path2.relative(rootDir, filePath);
170
+ return SKIP_PATTERNS.some((p3) => rel === p3 || rel.startsWith(`${p3}${path2.sep}`) || rel.startsWith(`${p3}/`));
70
171
  }
71
172
  function shouldProcess(filePath) {
72
- const base = path.basename(filePath);
173
+ const base = path2.basename(filePath);
73
174
  if (INCLUDE_BASENAMES.includes(base)) return true;
74
- const ext = path.extname(filePath);
175
+ const ext = path2.extname(filePath);
75
176
  return !!ext && INCLUDE_EXTENSIONS.includes(ext);
76
177
  }
77
178
  function replaceInFile(filePath, replacements) {
@@ -97,14 +198,14 @@ function getReplacements(options) {
97
198
  }
98
199
  function walkReplace(dir, rootDir, options, count) {
99
200
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
100
- const fullPath = path.join(dir, entry.name);
201
+ const fullPath = path2.join(dir, entry.name);
101
202
  if (shouldSkip(fullPath, rootDir)) continue;
102
203
  if (entry.isDirectory()) {
103
204
  walkReplace(fullPath, rootDir, options, count);
104
205
  continue;
105
206
  }
106
207
  if (!entry.isFile() || !shouldProcess(fullPath)) continue;
107
- const fileBase = path.basename(fullPath);
208
+ const fileBase = path2.basename(fullPath);
108
209
  const fileReplacements = getReplacements(options).filter(([from]) => {
109
210
  if (from === "__APP_PORT__" && PORT_SKIP_FILES.has(fileBase)) return false;
110
211
  return true;
@@ -118,14 +219,14 @@ function validateNoPlaceholders(targetDir) {
118
219
  const leftovers = [];
119
220
  function scan(dir) {
120
221
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
121
- const fullPath = path.join(dir, entry.name);
222
+ const fullPath = path2.join(dir, entry.name);
122
223
  if (shouldSkip(fullPath, targetDir)) continue;
123
224
  if (entry.isDirectory()) {
124
225
  scan(fullPath);
125
226
  } else if (entry.isFile() && shouldProcess(fullPath)) {
126
227
  const content = fs.readFileSync(fullPath, "utf-8");
127
228
  if (content.includes("__APP_NAME__") || content.includes("__APP_NAME_UPPER__") || content.includes("__APP_PORT__") || content.includes("__APP_TITLE__")) {
128
- leftovers.push(path.relative(targetDir, fullPath));
229
+ leftovers.push(path2.relative(targetDir, fullPath));
129
230
  }
130
231
  }
131
232
  }
@@ -137,7 +238,7 @@ ${leftovers.map((f) => ` - ${f}`).join("\n")}`);
137
238
  }
138
239
  }
139
240
  function validateDevelopmentPort(targetDir) {
140
- const devEnv = path.join(targetDir, ".env.development");
241
+ const devEnv = path2.join(targetDir, ".env.development");
141
242
  const content = fs.readFileSync(devEnv, "utf-8");
142
243
  const match = content.match(/^\s*VITE_PORT\s*=\s*(\S+)/m);
143
244
  if (!match || match[1] !== "3000") {
@@ -152,11 +253,11 @@ function applyReplacements(targetDir, options) {
152
253
  return count.files;
153
254
  }
154
255
  function applyMinimalMode(targetDir, appName) {
155
- const templateSysDir = path.join(targetDir, "src/views/templateSys");
256
+ const templateSysDir = path2.join(targetDir, "src/views/templateSys");
156
257
  if (fs.existsSync(templateSysDir)) {
157
258
  fs.rmSync(templateSysDir, { recursive: true, force: true });
158
259
  }
159
- const menuPath = path.join(targetDir, "mock/authMenuList.ts");
260
+ const menuPath = path2.join(targetDir, "mock/authMenuList.ts");
160
261
  fs.writeFileSync(
161
262
  menuPath,
162
263
  `export const authMenuList = [
@@ -181,7 +282,7 @@ function applyMinimalMode(targetDir, appName) {
181
282
  `,
182
283
  "utf-8"
183
284
  );
184
- const homePath = path.join(targetDir, "src/views/home/index.vue");
285
+ const homePath = path2.join(targetDir, "src/views/home/index.vue");
185
286
  fs.writeFileSync(
186
287
  homePath,
187
288
  `<template>
@@ -202,32 +303,26 @@ function applyMinimalMode(targetDir, appName) {
202
303
  "utf-8"
203
304
  );
204
305
  }
205
- function removeGitDir(targetDir) {
206
- const gitDir = path.join(targetDir, ".git");
207
- if (fs.existsSync(gitDir)) {
208
- fs.rmSync(gitDir, { recursive: true, force: true });
209
- }
210
- }
211
306
  var GENERATED_CHANGELOG = `# \u53D1\u5E03\u8BB0\u5F55
212
307
 
213
308
  > \u672C\u9879\u76EE\u6309\u7167\u81EA\u5B9A\u4E49\u63D0\u4EA4\u89C4\u8303\uFF08\u5982 fix: +\u63CF\u8FF0\uFF09\u81EA\u52A8\u751F\u6210\u4EE5\u4E0B\u53D8\u66F4\u8BB0\u5F55\u3002
214
309
  `;
215
310
  function cleanupTemplateArtifacts(targetDir) {
216
- const cursorDir = path.join(targetDir, ".cursor");
311
+ const cursorDir = path2.join(targetDir, ".cursor");
217
312
  if (fs.existsSync(cursorDir)) {
218
313
  fs.rmSync(cursorDir, { recursive: true, force: true });
219
314
  }
220
- const syncScript = path.join(targetDir, "build/sync-to-template.ts");
315
+ const syncScript = path2.join(targetDir, "build/sync-to-template.ts");
221
316
  if (fs.existsSync(syncScript)) {
222
317
  fs.rmSync(syncScript, { force: true });
223
318
  }
224
- fs.writeFileSync(path.join(targetDir, "CHANGELOG.md"), GENERATED_CHANGELOG, "utf-8");
319
+ fs.writeFileSync(path2.join(targetDir, "CHANGELOG.md"), GENERATED_CHANGELOG, "utf-8");
225
320
  cleanupGeneratedPackageJson(targetDir);
226
321
  cleanupGeneratedReadme(targetDir);
227
322
  cleanupGeneratedGitignore(targetDir);
228
323
  }
229
324
  function cleanupGeneratedPackageJson(targetDir) {
230
- const pkgPath = path.join(targetDir, "package.json");
325
+ const pkgPath = path2.join(targetDir, "package.json");
231
326
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
232
327
  if (pkg.scripts) {
233
328
  delete pkg.scripts["sync:to-template"];
@@ -237,7 +332,7 @@ function cleanupGeneratedPackageJson(targetDir) {
237
332
  `, "utf-8");
238
333
  }
239
334
  function cleanupGeneratedReadme(targetDir) {
240
- const readmePath = path.join(targetDir, "README.md");
335
+ const readmePath = path2.join(targetDir, "README.md");
241
336
  if (!fs.existsSync(readmePath)) return;
242
337
  let content = fs.readFileSync(readmePath, "utf-8");
243
338
  content = content.replace(/\n> \*\*说明:\*\*[^\n]*\n/, "\n");
@@ -245,7 +340,7 @@ function cleanupGeneratedReadme(targetDir) {
245
340
  fs.writeFileSync(readmePath, content, "utf-8");
246
341
  }
247
342
  function cleanupGeneratedGitignore(targetDir) {
248
- const gitignorePath = path.join(targetDir, ".gitignore");
343
+ const gitignorePath = path2.join(targetDir, ".gitignore");
249
344
  if (!fs.existsSync(gitignorePath)) return;
250
345
  let content = fs.readFileSync(gitignorePath, "utf-8");
251
346
  content = content.replace(/\n# Cursor project metadata[\s\S]*?(?=\n*$)/, "\n");
@@ -256,118 +351,10 @@ function cleanupGeneratedGitignore(targetDir) {
256
351
  fs.writeFileSync(gitignorePath, content.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n"), "utf-8");
257
352
  }
258
353
 
259
- // src/validate.ts
260
- function validateName(name) {
261
- if (!name) return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
262
- if (!/^[a-z][a-z0-9-_]*$/.test(name)) {
263
- return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26(-)\u6216\u4E0B\u5212\u7EBF(_)\uFF0C\u4E14\u4EE5\u5B57\u6BCD\u5F00\u5934";
264
- }
265
- return null;
266
- }
267
- function validatePort(port) {
268
- const n = Number(port);
269
- if (!port || isNaN(n) || !Number.isInteger(n) || n < 1024 || n > 65535) {
270
- return "\u7AEF\u53E3\u53F7\u5FC5\u987B\u662F 1024 ~ 65535 \u4E4B\u95F4\u7684\u6574\u6570";
271
- }
272
- return null;
273
- }
274
- function toAppTitle(name) {
275
- return name.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
276
- }
277
- function toAppNameUpper(name) {
278
- return name.replace(/[-_]/g, "").toUpperCase();
279
- }
280
-
281
- // src/prompts.ts
282
- async function collectOptions(projectNameArg, flags) {
283
- const versions = loadVersions();
284
- const cwd = process.cwd();
285
- p.intro(`${pc.bgCyan(pc.black(" create-szyy-app "))} \u5FAE\u524D\u7AEF\u5B50\u5E94\u7528\u811A\u624B\u67B6`);
286
- let appName = projectNameArg?.trim() || "";
287
- if (!flags.yes || !appName) {
288
- const nameResult = await p.text({
289
- message: "\u9879\u76EE\u540D\u79F0",
290
- placeholder: "my-order-sys",
291
- initialValue: appName || void 0,
292
- validate: (value) => validateName(value?.trim() || "") ?? void 0
293
- });
294
- if (p.isCancel(nameResult)) {
295
- p.cancel("\u5DF2\u53D6\u6D88");
296
- return null;
297
- }
298
- appName = nameResult.trim();
299
- } else {
300
- const nameError = validateName(appName);
301
- if (nameError) {
302
- p.log.error(nameError);
303
- return null;
304
- }
305
- }
306
- let appPort = flags.port ?? versions.defaultPort;
307
- if (!flags.yes || flags.port === void 0) {
308
- const portResult = await p.text({
309
- message: "\u672C\u5730\u8054\u8C03\u7AEF\u53E3",
310
- placeholder: String(versions.defaultPort),
311
- initialValue: String(appPort),
312
- validate: (value) => {
313
- const port = value?.trim() || String(versions.defaultPort);
314
- return validatePort(port) ?? void 0;
315
- }
316
- });
317
- if (p.isCancel(portResult)) {
318
- p.cancel("\u5DF2\u53D6\u6D88");
319
- return null;
320
- }
321
- appPort = Number(portResult.trim() || versions.defaultPort);
322
- } else {
323
- const portError = validatePort(appPort);
324
- if (portError) {
325
- p.log.error(portError);
326
- return null;
327
- }
328
- }
329
- let keepDemo = !flags.minimal;
330
- if (!flags.yes && flags.minimal === void 0) {
331
- const demoResult = await p.confirm({
332
- message: "\u662F\u5426\u4FDD\u7559 Demo \u9875",
333
- initialValue: true
334
- });
335
- if (p.isCancel(demoResult)) {
336
- p.cancel("\u5DF2\u53D6\u6D88");
337
- return null;
338
- }
339
- keepDemo = demoResult;
340
- }
341
- const defaultTarget = path2.resolve(cwd, appName);
342
- let targetDir = defaultTarget;
343
- if (!flags.yes) {
344
- const dirResult = await p.text({
345
- message: "\u76EE\u6807\u76EE\u5F55",
346
- initialValue: defaultTarget,
347
- validate: (value) => {
348
- if (!value?.trim()) return "\u76EE\u6807\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A";
349
- return void 0;
350
- }
351
- });
352
- if (p.isCancel(dirResult)) {
353
- p.cancel("\u5DF2\u53D6\u6D88");
354
- return null;
355
- }
356
- targetDir = path2.resolve(dirResult.trim());
357
- }
358
- return {
359
- appName,
360
- appNameUpper: toAppNameUpper(appName),
361
- appTitle: toAppTitle(appName),
362
- appPort,
363
- targetDir,
364
- keepDemo
365
- };
366
- }
367
-
368
354
  // src/template.ts
369
355
  import fs2 from "fs";
370
356
  import path3 from "path";
357
+ import os from "os";
371
358
  import { spawnSync } from "child_process";
372
359
  import degit from "degit";
373
360
  async function fetchTemplate(targetDir, versions) {
@@ -379,6 +366,10 @@ async function fetchTemplate(targetDir, versions) {
379
366
  const repoOverride = process.env.SZYY_TEMPLATE_REPO;
380
367
  const gitBase = repoOverride || `${versions.templateGitBase}/${versions.templateRepo}`;
381
368
  const degitSource = `${gitBase}#${versions.template}`;
369
+ if (shouldUseGitCloneDirectly(gitBase)) {
370
+ cloneWithGit(gitBase, versions.template, targetDir);
371
+ return;
372
+ }
382
373
  try {
383
374
  const emitter = degit(degitSource, {
384
375
  cache: false,
@@ -394,20 +385,37 @@ async function fetchTemplate(targetDir, versions) {
394
385
  cloneWithGit(gitBase, versions.template, targetDir);
395
386
  }
396
387
  }
388
+ var DEGIT_HOSTS = /* @__PURE__ */ new Set(["github.com", "gitlab.com", "bitbucket.org", "git.sr.ht"]);
389
+ function shouldUseGitCloneDirectly(source) {
390
+ if (source.startsWith("git@") || source.startsWith("file://")) return true;
391
+ if (source.startsWith(".") || source.startsWith("/") || /^[A-Za-z]:[\\/]/.test(source)) return true;
392
+ try {
393
+ const url = new URL(source);
394
+ return !DEGIT_HOSTS.has(url.hostname.toLowerCase());
395
+ } catch {
396
+ return true;
397
+ }
398
+ }
397
399
  function shouldFallbackToGitClone(error) {
398
400
  const message = error instanceof Error ? error.message : String(error);
399
401
  return message.includes("degit supports GitHub, GitLab, Sourcehut and BitBucket");
400
402
  }
401
403
  function cloneWithGit(repo, ref, targetDir) {
402
- const result = spawnSync("git", ["clone", "--depth", "1", "--branch", ref, repo, targetDir], {
403
- encoding: "utf8"
404
- });
405
- if (result.status === 0) {
406
- removeExcludedTemplateEntries(targetDir);
407
- return;
404
+ const tempDir = fs2.mkdtempSync(path3.join(os.tmpdir(), "create-szyy-app-"));
405
+ try {
406
+ const cloneDir = path3.join(tempDir, "template");
407
+ const result = spawnSync("git", ["clone", "--depth", "1", "--branch", ref, repo, cloneDir], {
408
+ encoding: "utf8"
409
+ });
410
+ if (result.status === 0) {
411
+ copyRecursive(cloneDir, targetDir);
412
+ return;
413
+ }
414
+ const errorOutput = result.stderr?.trim() || result.stdout?.trim() || "git clone \u6267\u884C\u5931\u8D25";
415
+ throw new Error(`\u6A21\u677F\u62C9\u53D6\u5931\u8D25: ${errorOutput}`);
416
+ } finally {
417
+ fs2.rmSync(tempDir, { recursive: true, force: true });
408
418
  }
409
- const errorOutput = result.stderr?.trim() || result.stdout?.trim() || "git clone \u6267\u884C\u5931\u8D25";
410
- throw new Error(`\u6A21\u677F\u62C9\u53D6\u5931\u8D25: ${errorOutput}`);
411
419
  }
412
420
  function copyLocalTemplate(source, target) {
413
421
  if (!fs2.existsSync(source)) {
@@ -443,9 +451,105 @@ function removeExcludedTemplateEntries(targetDir) {
443
451
  }
444
452
  }
445
453
 
454
+ // src/versions.ts
455
+ import fs3 from "fs";
456
+ import path4 from "path";
457
+ import { fileURLToPath } from "url";
458
+ var __filename2 = fileURLToPath(import.meta.url);
459
+ var __dirname2 = path4.dirname(__filename2);
460
+ var CONFIG_BRANCH = "create-app";
461
+ function loadBundledVersions() {
462
+ const versionsPath = path4.resolve(__dirname2, "../templates/versions.json");
463
+ return validateVersionsConfig(JSON.parse(fs3.readFileSync(versionsPath, "utf-8")));
464
+ }
465
+ function validateVersionsConfig(config) {
466
+ if (!config || typeof config !== "object") {
467
+ throw new Error("\u7248\u672C\u914D\u7F6E\u683C\u5F0F\u65E0\u6548");
468
+ }
469
+ const value = config;
470
+ if (typeof value.template !== "string" || !value.template.trim()) {
471
+ throw new Error("\u7248\u672C\u914D\u7F6E\u7F3A\u5C11 template");
472
+ }
473
+ if (typeof value.defaultPort !== "number" || !Number.isFinite(value.defaultPort)) {
474
+ throw new Error("\u7248\u672C\u914D\u7F6E\u7F3A\u5C11\u6709\u6548\u7684 defaultPort");
475
+ }
476
+ if (!value.dependencies || typeof value.dependencies !== "object" || Array.isArray(value.dependencies)) {
477
+ throw new Error("\u7248\u672C\u914D\u7F6E\u7F3A\u5C11 dependencies");
478
+ }
479
+ return config;
480
+ }
481
+ function mergeVersions(bundled, remote) {
482
+ return {
483
+ ...bundled,
484
+ ...remote,
485
+ dependencies: { ...bundled.dependencies, ...remote.dependencies },
486
+ compat: { ...bundled.compat, ...remote.compat }
487
+ };
488
+ }
489
+ function getRepoBase(bundled) {
490
+ const override = process.env.SZYY_TEMPLATE_REPO;
491
+ if (override) {
492
+ return override.replace(/\.git$/, "").replace(/\/$/, "");
493
+ }
494
+ return `${bundled.templateGitBase}/${bundled.templateRepo}`.replace(/\/$/, "");
495
+ }
496
+ function getDefaultRemoteVersionsUrl(bundled) {
497
+ return `${getRepoBase(bundled)}/-/raw/${CONFIG_BRANCH}/versions.json`;
498
+ }
499
+ async function fetchVersionsFromUrl(url) {
500
+ const response = await fetch(url, {
501
+ headers: { Accept: "application/json" },
502
+ signal: AbortSignal.timeout(15e3)
503
+ });
504
+ if (!response.ok) {
505
+ throw new Error(`\u62C9\u53D6\u7248\u672C\u914D\u7F6E\u5931\u8D25 (${response.status}): ${url}`);
506
+ }
507
+ return validateVersionsConfig(await response.json());
508
+ }
509
+ function readVersionsFromPath(filePath) {
510
+ const resolved = path4.resolve(filePath);
511
+ if (!fs3.existsSync(resolved)) {
512
+ throw new Error(`\u7248\u672C\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolved}`);
513
+ }
514
+ return validateVersionsConfig(JSON.parse(fs3.readFileSync(resolved, "utf-8")));
515
+ }
516
+ function applyOverrides(versions, options) {
517
+ const templateTag = options?.templateTag?.trim() || process.env.SZYY_TEMPLATE_TAG?.trim();
518
+ if (!templateTag) {
519
+ return versions;
520
+ }
521
+ return { ...versions, template: templateTag };
522
+ }
523
+ async function resolveVersions(options) {
524
+ const bundled = loadBundledVersions();
525
+ if (process.env.SZYY_VERSIONS_PATH) {
526
+ return {
527
+ config: applyOverrides(readVersionsFromPath(process.env.SZYY_VERSIONS_PATH), options),
528
+ source: "path"
529
+ };
530
+ }
531
+ const remoteUrl = process.env.SZYY_VERSIONS_URL || getDefaultRemoteVersionsUrl(bundled);
532
+ try {
533
+ const remote = await fetchVersionsFromUrl(remoteUrl);
534
+ return {
535
+ config: applyOverrides(mergeVersions(bundled, remote), options),
536
+ source: "remote",
537
+ remoteUrl
538
+ };
539
+ } catch (error) {
540
+ const fallbackReason = error instanceof Error ? error.message : String(error);
541
+ return {
542
+ config: applyOverrides(bundled, options),
543
+ source: "bundled",
544
+ remoteUrl,
545
+ fallbackReason
546
+ };
547
+ }
548
+ }
549
+
446
550
  // src/index.ts
447
551
  var program = new Command();
448
- program.name("create-szyy-app").description("\u521B\u5EFA\u5FAE\u524D\u7AEF\u5B50\u5E94\u7528").argument("[project-name]", "\u9879\u76EE\u540D\u79F0\uFF08kebab-case\uFF09").option("-p, --port <port>", "\u672C\u5730\u8054\u8C03\u7AEF\u53E3", (value) => Number(value)).option("--minimal", "\u4E0D\u4FDD\u7559 Demo \u9875").option("-y, --yes", "\u8DF3\u8FC7\u4EA4\u4E92\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C").action(async (projectName, options) => {
552
+ program.name("create-szyy-app").description("\u521B\u5EFA\u5FAE\u524D\u7AEF\u5B50\u5E94\u7528").argument("[project-name]", "\u9879\u76EE\u540D\u79F0\uFF08kebab-case\uFF09").option("-p, --port <port>", "\u672C\u5730\u8054\u8C03\u7AEF\u53E3", (value) => Number(value)).option("--template-tag <tag>", "\u8986\u76D6\u6A21\u677F Git tag").option("--minimal", "\u4E0D\u4FDD\u7559 Demo \u9875").option("-y, --yes", "\u8DF3\u8FC7\u4EA4\u4E92\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C").action(async (projectName, options) => {
449
553
  try {
450
554
  await run(projectName, options);
451
555
  } catch (error) {
@@ -454,12 +558,16 @@ program.name("create-szyy-app").description("\u521B\u5EFA\u5FAE\u524D\u7AEF\u5B5
454
558
  }
455
559
  });
456
560
  async function run(projectName, flags) {
561
+ const spinner2 = p2.spinner();
562
+ spinner2.start("\u52A0\u8F7D\u7248\u672C\u914D\u7F6E...");
563
+ const resolved = await resolveVersions({ templateTag: flags.templateTag });
564
+ const { config: versions } = resolved;
565
+ spinner2.stop(formatVersionsMessage(resolved));
457
566
  let createOptions;
458
567
  if (flags.yes && projectName) {
459
568
  const nameError = validateName(projectName);
460
569
  if (nameError) throw new Error(nameError);
461
- const versions2 = loadVersions();
462
- const port = flags.port ?? versions2.defaultPort;
570
+ const port = flags.port ?? versions.defaultPort;
463
571
  const portError = validatePort(port);
464
572
  if (portError) throw new Error(portError);
465
573
  createOptions = {
@@ -467,25 +575,23 @@ async function run(projectName, flags) {
467
575
  appNameUpper: toAppNameUpper(projectName),
468
576
  appTitle: toAppTitle(projectName),
469
577
  appPort: port,
470
- targetDir: path4.resolve(process.cwd(), projectName),
578
+ targetDir: path5.resolve(process.cwd(), projectName),
471
579
  keepDemo: !flags.minimal
472
580
  };
473
581
  } else {
474
- createOptions = await collectOptions(projectName, flags);
582
+ createOptions = await collectOptions(projectName, flags, versions);
475
583
  if (!createOptions) {
476
584
  process.exit(0);
477
585
  }
478
586
  }
479
- if (fs3.existsSync(createOptions.targetDir)) {
480
- const entries = fs3.readdirSync(createOptions.targetDir);
587
+ if (fs4.existsSync(createOptions.targetDir)) {
588
+ const entries = fs4.readdirSync(createOptions.targetDir);
481
589
  if (entries.length > 0) {
482
590
  throw new Error(`\u76EE\u6807\u76EE\u5F55\u975E\u7A7A: ${createOptions.targetDir}`);
483
591
  }
484
592
  } else {
485
- fs3.mkdirSync(createOptions.targetDir, { recursive: true });
593
+ fs4.mkdirSync(createOptions.targetDir, { recursive: true });
486
594
  }
487
- const versions = loadVersions();
488
- const spinner2 = p2.spinner();
489
595
  spinner2.start(`\u62C9\u53D6\u6A21\u677F ${versions.template}...`);
490
596
  await fetchTemplate(createOptions.targetDir, versions);
491
597
  spinner2.stop("\u6A21\u677F\u62C9\u53D6\u5B8C\u6210");
@@ -503,17 +609,29 @@ async function run(projectName, flags) {
503
609
  spinner2.start("\u6E05\u7406\u6A21\u677F\u7EF4\u62A4\u6587\u4EF6...");
504
610
  cleanupTemplateArtifacts(createOptions.targetDir);
505
611
  spinner2.stop("\u6A21\u677F\u7EF4\u62A4\u6587\u4EF6\u5DF2\u6E05\u7406");
506
- removeGitDir(createOptions.targetDir);
507
612
  p2.outro(
508
613
  [
509
614
  `${pc2.green("\u2713")} \u9879\u76EE ${pc2.cyan(createOptions.appName)} \u521B\u5EFA\u6210\u529F`,
510
615
  "",
511
616
  "\u4E0B\u4E00\u6B65:",
512
- ` cd ${path4.relative(process.cwd(), createOptions.targetDir) || "."}`,
617
+ ` cd ${path5.relative(process.cwd(), createOptions.targetDir) || "."}`,
513
618
  " pnpm install",
514
619
  " pnpm dev # \u72EC\u7ACB\u6A21\u5F0F\uFF0C\u7AEF\u53E3 3000",
515
620
  ` pnpm dev:micro # \u57FA\u5EA7\u8054\u8C03\uFF0C\u7AEF\u53E3 ${createOptions.appPort}`
516
621
  ].join("\n")
517
622
  );
518
623
  }
624
+ function formatVersionsMessage(resolved) {
625
+ const { config, source, fallbackReason } = resolved;
626
+ if (source === "remote") {
627
+ return `\u7248\u672C\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF08\u8FDC\u7A0B\uFF0C\u6A21\u677F ${config.template}\uFF09`;
628
+ }
629
+ if (source === "path") {
630
+ return `\u7248\u672C\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF08\u672C\u5730\u6587\u4EF6\uFF0C\u6A21\u677F ${config.template}\uFF09`;
631
+ }
632
+ if (fallbackReason) {
633
+ p2.log.warn(`\u8FDC\u7A0B\u7248\u672C\u914D\u7F6E\u4E0D\u53EF\u7528\uFF0C\u5DF2\u4F7F\u7528\u5185\u7F6E\u914D\u7F6E: ${fallbackReason}`);
634
+ }
635
+ return `\u7248\u672C\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF08\u5185\u7F6E\u515C\u5E95\uFF0C\u6A21\u677F ${config.template}\uFF09`;
636
+ }
519
637
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-szyy-app",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "脚手架:一键创建微前端子应用",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,6 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "tsup",
55
55
  "dev": "tsup --watch",
56
+ "smoke": "pnpm build && node scripts/smoke-create.mjs",
56
57
  "typecheck": "tsc --noEmit",
57
58
  "lint": "eslint .",
58
59
  "lint:fix": "eslint . --fix",
@@ -1,5 +1,5 @@
1
1
  {
2
- "template": "v1.0.2",
2
+ "template": "v1.0.3",
3
3
  "templateRepo": "sunyard-szyy-app-template",
4
4
  "templateGitBase": "http://172.1.1.65/RDKit",
5
5
  "defaultPort": 5690,