@quiteer/scripts 0.0.6 → 0.0.9

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.
Files changed (2) hide show
  1. package/dist/index.mjs +177 -24
  2. package/package.json +3 -2
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import cac from "cac";
3
3
  import { bgGreen, bgRed, blue, gray, green, lightBlue, lightCyan, lightGreen, red, white, yellow } from "kolorist";
4
- import { access, readFile, writeFile } from "node:fs/promises";
4
+ import { access, readFile, readdir, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
7
  import { execa } from "execa";
@@ -12,7 +12,7 @@ import enquirer from "enquirer";
12
12
  import { versionBump } from "bumpp";
13
13
 
14
14
  //#region package.json
15
- var version = "0.0.5";
15
+ var version = "0.0.9";
16
16
 
17
17
  //#endregion
18
18
  //#region src/locales/changelog.ts
@@ -213,18 +213,40 @@ async function getCommitsInRange(range) {
213
213
  ])).split("\n").filter(Boolean).map(parseCommit).filter((x) => !!x);
214
214
  }
215
215
  /**
216
- * 读取 package homepage 以生成提交链接
217
- * @returns 仓库主页 URL
216
+ * 读取 package homepage(若为 GitHub Pages URL,转换为仓库地址)
217
+ * @returns 可能的仓库主页 URL
218
218
  */
219
219
  async function readHomepage() {
220
220
  try {
221
221
  const content = await readFile(path.join(process.cwd(), "scripts", "package.json"), "utf8");
222
222
  const json = JSON.parse(content);
223
- return typeof json.homepage === "string" ? json.homepage.replace(/\/$/, "") : void 0;
223
+ const home = typeof json.homepage === "string" ? json.homepage.replace(/\/$/, "") : void 0;
224
+ if (!home) return void 0;
225
+ const m = home.match(/^https:\/\/([\w-]+)\.github\.io\/([\w.-]+)\//);
226
+ if (m) return `https://github.com/${m[1]}/${m[2]}`;
227
+ return home;
224
228
  } catch {
225
229
  return;
226
230
  }
227
231
  }
232
+ /**
233
+ * 获取 Web 版仓库地址(优先使用 git remote,回退到 homepage 推断)
234
+ * @returns 仓库 Web URL(如 https://github.com/<owner>/<repo>)
235
+ */
236
+ async function getRepoWebUrl() {
237
+ try {
238
+ const remote = await execCommand("git", [
239
+ "remote",
240
+ "get-url",
241
+ "origin"
242
+ ]);
243
+ if (remote) {
244
+ if (remote.startsWith("git@github.com:")) return `https://github.com/${remote.replace("git@github.com:", "").replace(/\.git$/, "")}`;
245
+ if (remote.startsWith("https://github.com/")) return `https://github.com/${remote.replace("https://github.com/", "").replace(/\.git$/, "")}`;
246
+ }
247
+ } catch {}
248
+ return await readHomepage();
249
+ }
228
250
  async function prependFile(filePath, content) {
229
251
  try {
230
252
  await access(filePath);
@@ -313,9 +335,18 @@ function formatSection(title, date, items, repoUrl, lang) {
313
335
  lines.push(`- ${typeIcon} ${typeLabel} ${scopeFmt ? `${scopeFmt}: ` : ""}${it.description}`);
314
336
  lines.push(` > **🕒 ${time}** · \`➕${it.added}\` / \`➖${it.deleted}\``);
315
337
  lines.push(` > \`👤 ${it.author}\` ${email}${link}`);
316
- for (const f of it.filesAdded ?? []) lines.push(` - ➕ \`${f}\``);
317
- for (const f of it.filesModified ?? []) lines.push(` - ✏️ \`${f}\``);
318
- for (const f of it.filesDeleted ?? []) lines.push(` - 🗑️ ~~~~\`${f}\`~~~~`);
338
+ for (const f of it.filesAdded ?? []) {
339
+ const url = repoUrl ? `${repoUrl}/blob/${it.hash}/${f}` : void 0;
340
+ lines.push(url ? ` - [\`${f}\`](${url})` : ` - \`${f}\``);
341
+ }
342
+ for (const f of it.filesModified ?? []) {
343
+ const url = repoUrl ? `${repoUrl}/blob/${it.hash}/${f}` : void 0;
344
+ lines.push(url ? ` - ✏️ [\`${f}\`](${url})` : ` - ✏️ \`${f}\``);
345
+ }
346
+ for (const f of it.filesDeleted ?? []) {
347
+ const url = repoUrl ? `${repoUrl}/commit/${it.hash}` : void 0;
348
+ lines.push(url ? ` - 🗑️ [~~\`${f}\`~~](${url})` : ` - 🗑️ ~~\`${f}\`~~`);
349
+ }
319
350
  }
320
351
  lines.push("");
321
352
  }
@@ -342,9 +373,18 @@ function formatTimeline(items, repoUrl) {
342
373
  lines.push(`- ${typeIcon} ${typeLabel} ${scopeFmt ? `${scopeFmt}: ` : ""}${it.description}`);
343
374
  lines.push(` > **🕒 ${time}** · \`➕${it.added}\` / \`➖${it.deleted}\``);
344
375
  lines.push(` > \`👤 ${it.author}\` ${email}${link}`);
345
- for (const f of it.filesAdded ?? []) lines.push(` - ➕ \`${f}\``);
346
- for (const f of it.filesModified ?? []) lines.push(` - ✏️ \`${f}\``);
347
- for (const f of it.filesDeleted ?? []) lines.push(` - 🗑️ ~~~~\`${f}\`~~~~`);
376
+ for (const f of it.filesAdded ?? []) {
377
+ const url = repoUrl ? `${repoUrl}/blob/${it.hash}/${f}` : void 0;
378
+ lines.push(url ? ` - [\`${f}\`](${url})` : ` - \`${f}\``);
379
+ }
380
+ for (const f of it.filesModified ?? []) {
381
+ const url = repoUrl ? `${repoUrl}/blob/${it.hash}/${f}` : void 0;
382
+ lines.push(url ? ` - ✏️ [\`${f}\`](${url})` : ` - ✏️ \`${f}\``);
383
+ }
384
+ for (const f of it.filesDeleted ?? []) {
385
+ const url = repoUrl ? `${repoUrl}/commit/${it.hash}` : void 0;
386
+ lines.push(url ? ` - 🗑️ [~~\`${f}\`~~](${url})` : ` - 🗑️ ~~\`${f}\`~~`);
387
+ }
348
388
  }
349
389
  lines.push("");
350
390
  }
@@ -361,7 +401,7 @@ function formatTimeline(items, repoUrl) {
361
401
  */
362
402
  async function generateChangelogFiles(options) {
363
403
  const repoRoot = await execCommand("git", ["rev-parse", "--show-toplevel"]);
364
- const homepage = await readHomepage();
404
+ const homepage = await getRepoWebUrl();
365
405
  const root = await getRootCommit();
366
406
  const title = "全部历史";
367
407
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -405,6 +445,74 @@ async function cleanup(paths) {
405
445
  await rimraf(paths, { glob: true });
406
446
  }
407
447
 
448
+ //#endregion
449
+ //#region src/commands/dir-tree.ts
450
+ /**
451
+ * 生成目录树结构字符串(ASCII Tree)
452
+ * - 默认忽略常见目录:node_modules、.git、dist、out、logs
453
+ * @param root 起始目录,默认为当前工作目录
454
+ * @returns 目录树字符串
455
+ */
456
+ async function buildDirTree(root = process.cwd(), ignoreList) {
457
+ const ignore = new Set(ignoreList ?? [
458
+ "node_modules",
459
+ ".git",
460
+ "dist",
461
+ "out",
462
+ "logs"
463
+ ]);
464
+ async function walk(dir, prefix = "") {
465
+ const items = (await readdir(dir, { withFileTypes: true })).filter((e) => !ignore.has(e.name)).map((e) => ({
466
+ name: e.name,
467
+ isDir: e.isDirectory()
468
+ })).sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1);
469
+ const lines = [];
470
+ const lastIdx = items.length - 1;
471
+ for (let i = 0; i < items.length; i++) {
472
+ const it = items[i];
473
+ const isLast = i === lastIdx;
474
+ const connector = isLast ? "└── " : "├── ";
475
+ const nextPrefix = prefix + (isLast ? " " : "│ ");
476
+ lines.push(`${prefix}${connector}${it.name}`);
477
+ if (it.isDir) {
478
+ const sub = await walk(path.join(dir, it.name), nextPrefix);
479
+ lines.push(...sub);
480
+ }
481
+ }
482
+ return lines;
483
+ }
484
+ const title = path.basename(root);
485
+ const content = await walk(root);
486
+ return [`|-- ${title}`, ...content].join("\n");
487
+ }
488
+ /**
489
+ * 根据目录树内容生成 Markdown 并写入文件
490
+ * - 使用代码块包裹,便于阅读
491
+ * @param tree 目录树字符串
492
+ * @param outfile 输出文件路径,默认 `DIRECTORY_TREE.md`
493
+ */
494
+ async function writeDirTreeMd(tree, outfile = "DIRECTORY_TREE.md") {
495
+ const md = `## 目录结构
496
+
497
+ \`\`\`text\n${tree}\n\`\`\`\n`;
498
+ await writeFile(path.join(process.cwd(), outfile), md, "utf8");
499
+ }
500
+ /**
501
+ * 生成并输出目录结构,支持可选生成 Markdown 文件
502
+ * - 默认只在控制台输出
503
+ * @param dir 目标目录,默认当前工作目录
504
+ * @param md 是否生成 Markdown 文件,默认 false
505
+ */
506
+ async function generateDirTree(dir, opts) {
507
+ const tree = await buildDirTree(dir ? path.resolve(process.cwd(), dir) : process.cwd(), opts?.ignore);
508
+ console.info("quiteer-script :>>", gray(`\n${tree}\n`));
509
+ if (opts?.md) {
510
+ const out = opts?.output || "DIRECTORY_TREE.md";
511
+ await writeDirTreeMd(tree, out);
512
+ console.info(lightCyan("quiteer-script :>>"), lightGreen(`已生成 Markdown: ${lightBlue(out)}`));
513
+ }
514
+ }
515
+
408
516
  //#endregion
409
517
  //#region src/config/index.ts
410
518
  const defaultOptions = {
@@ -435,7 +543,18 @@ const defaultOptions = {
435
543
  timelineOutput: "CHANGELOG_TIMELINE.md",
436
544
  formats: "both"
437
545
  },
438
- gitCommit: { add: true }
546
+ gitCommit: { add: true },
547
+ dirTree: {
548
+ md: false,
549
+ output: "DIRECTORY_TREE.md",
550
+ ignore: [
551
+ "node_modules",
552
+ ".git",
553
+ "dist",
554
+ "out",
555
+ "logs"
556
+ ]
557
+ }
439
558
  };
440
559
  async function loadCliOptions(overrides, cwd = process.cwd()) {
441
560
  const { config } = await loadConfig({
@@ -786,23 +905,34 @@ async function release(tagPrefix) {
786
905
  `chore(projects): release ${tagName}`,
787
906
  tagName
788
907
  ]);
908
+ const cli = await loadCliOptions();
909
+ const lang = cli.lang;
789
910
  await generateChangelogFiles({
790
- lang: "zh-cn",
791
- format: "both",
792
- groupOutput: "CHANGELOG.md",
793
- timelineOutput: "CHANGELOG_TIMELINE.md"
911
+ lang,
912
+ format: cli.changelog.formats,
913
+ groupOutput: cli.changelog.groupOutput,
914
+ timelineOutput: cli.changelog.timelineOutput
794
915
  });
795
916
  }
796
917
 
797
- //#endregion
798
- //#region src/commands/update-pkg.ts
799
- async function updatePkg(args = ["--deep", "-u"]) {
800
- execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
801
- }
802
-
803
918
  //#endregion
804
919
  //#region src/commands/self-update.ts
805
920
  /**
921
+ * 获取当前命令来源路径(本地 node_modules/.bin 或全局)
922
+ * @returns 可执行文件路径(若无法获取返回空字符串)
923
+ */
924
+ async function getCurrentBinPath() {
925
+ try {
926
+ const p = await execCommand("command", ["-v", "qui"]);
927
+ if (p) return p;
928
+ } catch {}
929
+ try {
930
+ return await execCommand("which", ["qui"]);
931
+ } catch {
932
+ return "";
933
+ }
934
+ }
935
+ /**
806
936
  * 检查 @quiteer/scripts 是否有新版本并提示更新
807
937
  * - 启动任意命令时调用,仅提示不执行安装
808
938
  */
@@ -813,7 +943,12 @@ async function checkUpdateAndNotify() {
813
943
  "@quiteer/scripts",
814
944
  "version"
815
945
  ]);
816
- if (latest && latest !== version) console.info("quiteer-script :>> ", lightCyan(`检测到新版本 ${lightGreen(latest)},当前版本 ${lightBlue(version)},建议执行 ${bgGreen(white("qui su"))} 进行更新`));
946
+ const binPath = await getCurrentBinPath();
947
+ const isLocal = binPath.includes("node_modules/.bin");
948
+ if (latest && latest !== version) {
949
+ console.info("quiteer-script :>> ", lightCyan(`检测到新版本 ${lightGreen(latest)},当前版本 ${lightBlue(version)},建议执行 ${bgGreen(white("qui su"))} 进行更新`));
950
+ if (isLocal) console.info("quiteer-script :>> ", lightBlue(`当前正在使用本地工作区命令:${binPath}`));
951
+ }
817
952
  } catch {}
818
953
  }
819
954
  /**
@@ -842,11 +977,22 @@ async function selfUpdate() {
842
977
  `@quiteer/scripts@${latest}`
843
978
  ], { stdio: "inherit" });
844
979
  console.info("quiteer-script :>> ", lightGreen("更新完成,请重新运行命令"));
980
+ if ((await getCurrentBinPath()).includes("node_modules/.bin")) {
981
+ console.info("quiteer-script :>> ", lightBlue("当前命令来源于本地工作区,如需使用全局最新版本:"));
982
+ console.info("quiteer-script :>> ", lightBlue("1) 退出当前仓库目录后执行 `qui`"));
983
+ console.info("quiteer-script :>> ", lightBlue("2) 或使用临时执行:`pnpm dlx @quiteer/scripts <command>`"));
984
+ }
845
985
  } catch (e) {
846
986
  console.info("quiteer-script :>> ", lightBlue(`更新失败:${e?.message || "未知错误"}`));
847
987
  }
848
988
  }
849
989
 
990
+ //#endregion
991
+ //#region src/commands/update-pkg.ts
992
+ async function updatePkg(args = ["--deep", "-u"]) {
993
+ execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
994
+ }
995
+
850
996
  //#endregion
851
997
  //#region src/index.ts
852
998
  async function setupCli() {
@@ -878,6 +1024,13 @@ async function setupCli() {
878
1024
  cli.command("self-update", `${bgGreen(white("便捷命令"))} ${lightCyan("qui su")} 自更新:检查并更新 @quiteer/scripts 到最新版本`).alias("su").action(async () => {
879
1025
  await selfUpdate();
880
1026
  });
1027
+ cli.command("tree [path]", `${bgGreen(white("便捷命令"))} ${lightBlue("qui t")} 生成当前目录结构(默认控制台输出),可选生成 Markdown`).alias("t").option("--md", "是否生成 Markdown 文件", { default: cliOptions.dirTree.md }).action(async (path$1, args) => {
1028
+ await generateDirTree(path$1, {
1029
+ md: !!args?.md,
1030
+ output: cliOptions.dirTree.output,
1031
+ ignore: cliOptions.dirTree.ignore
1032
+ });
1033
+ });
881
1034
  cli.command("git-commit", `${bgGreen(white("便捷命令"))} ${lightBlue("qui gc")} git 提交前后的操作和规范等`).alias("gc").option("--add", "添加所有变更文件到暂存区", { default: cliOptions.gitCommit.add }).option("-l ,--lang", "校验提交信息的语言", { default: cliOptions.lang }).action(async (args) => {
882
1035
  if (args?.add) await gitCommitAdd();
883
1036
  await gitCommit(args?.lang);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quiteer/scripts",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.9",
5
5
  "homepage": "https://taiaiac.github.io/web/ci/scripts.html",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -49,6 +49,7 @@
49
49
  "scripts": {
50
50
  "dev": "tsdown -w",
51
51
  "build": "tsdown",
52
- "release": "qui r --tag-prefix scripts"
52
+ "release": "qui r --tag-prefix scripts",
53
+ "build:release": "tsdown && pnpm publish"
53
54
  }
54
55
  }