@quiteer/scripts 0.0.6 → 0.0.7

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 +151 -23
  2. package/package.json +1 -1
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.6";
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);
@@ -435,7 +475,18 @@ const defaultOptions = {
435
475
  timelineOutput: "CHANGELOG_TIMELINE.md",
436
476
  formats: "both"
437
477
  },
438
- gitCommit: { add: true }
478
+ gitCommit: { add: true },
479
+ dirTree: {
480
+ md: false,
481
+ output: "DIRECTORY_TREE.md",
482
+ ignore: [
483
+ "node_modules",
484
+ ".git",
485
+ "dist",
486
+ "out",
487
+ "logs"
488
+ ]
489
+ }
439
490
  };
440
491
  async function loadCliOptions(overrides, cwd = process.cwd()) {
441
492
  const { config } = await loadConfig({
@@ -786,20 +837,16 @@ async function release(tagPrefix) {
786
837
  `chore(projects): release ${tagName}`,
787
838
  tagName
788
839
  ]);
840
+ const cli = await loadCliOptions();
841
+ const lang = cli.lang;
789
842
  await generateChangelogFiles({
790
- lang: "zh-cn",
791
- format: "both",
792
- groupOutput: "CHANGELOG.md",
793
- timelineOutput: "CHANGELOG_TIMELINE.md"
843
+ lang,
844
+ format: cli.changelog.formats,
845
+ groupOutput: cli.changelog.groupOutput,
846
+ timelineOutput: cli.changelog.timelineOutput
794
847
  });
795
848
  }
796
849
 
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
850
  //#endregion
804
851
  //#region src/commands/self-update.ts
805
852
  /**
@@ -847,6 +894,80 @@ async function selfUpdate() {
847
894
  }
848
895
  }
849
896
 
897
+ //#endregion
898
+ //#region src/commands/update-pkg.ts
899
+ async function updatePkg(args = ["--deep", "-u"]) {
900
+ execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
901
+ }
902
+
903
+ //#endregion
904
+ //#region src/commands/dir-tree.ts
905
+ /**
906
+ * 生成目录树结构字符串(ASCII Tree)
907
+ * - 默认忽略常见目录:node_modules、.git、dist、out、logs
908
+ * @param root 起始目录,默认为当前工作目录
909
+ * @returns 目录树字符串
910
+ */
911
+ async function buildDirTree(root = process.cwd(), ignoreList) {
912
+ const ignore = new Set(ignoreList ?? [
913
+ "node_modules",
914
+ ".git",
915
+ "dist",
916
+ "out",
917
+ "logs"
918
+ ]);
919
+ async function walk(dir, prefix = "") {
920
+ const items = (await readdir(dir, { withFileTypes: true })).filter((e) => !ignore.has(e.name)).map((e) => ({
921
+ name: e.name,
922
+ isDir: e.isDirectory()
923
+ })).sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1);
924
+ const lines = [];
925
+ const lastIdx = items.length - 1;
926
+ for (let i = 0; i < items.length; i++) {
927
+ const it = items[i];
928
+ const isLast = i === lastIdx;
929
+ const connector = isLast ? "└── " : "├── ";
930
+ const nextPrefix = prefix + (isLast ? " " : "│ ");
931
+ lines.push(`${prefix}${connector}${it.name}`);
932
+ if (it.isDir) {
933
+ const sub = await walk(path.join(dir, it.name), nextPrefix);
934
+ lines.push(...sub);
935
+ }
936
+ }
937
+ return lines;
938
+ }
939
+ const title = path.basename(root);
940
+ const content = await walk(root);
941
+ return [`|-- ${title}`, ...content].join("\n");
942
+ }
943
+ /**
944
+ * 根据目录树内容生成 Markdown 并写入文件
945
+ * - 使用代码块包裹,便于阅读
946
+ * @param tree 目录树字符串
947
+ * @param outfile 输出文件路径,默认 `DIRECTORY_TREE.md`
948
+ */
949
+ async function writeDirTreeMd(tree, outfile = "DIRECTORY_TREE.md") {
950
+ const md = `## 目录结构
951
+
952
+ \`\`\`text\n${tree}\n\`\`\`\n`;
953
+ await writeFile(path.join(process.cwd(), outfile), md, "utf8");
954
+ }
955
+ /**
956
+ * 生成并输出目录结构,支持可选生成 Markdown 文件
957
+ * - 默认只在控制台输出
958
+ * @param dir 目标目录,默认当前工作目录
959
+ * @param md 是否生成 Markdown 文件,默认 false
960
+ */
961
+ async function generateDirTree(dir, opts) {
962
+ const tree = await buildDirTree(dir ? path.resolve(process.cwd(), dir) : process.cwd(), opts?.ignore);
963
+ console.info("quiteer-script :>>", gray(`\n${tree}\n`));
964
+ if (opts?.md) {
965
+ const out = opts?.output || "DIRECTORY_TREE.md";
966
+ await writeDirTreeMd(tree, out);
967
+ console.info(lightCyan("quiteer-script :>>"), lightGreen(`已生成 Markdown: ${lightBlue(out)}`));
968
+ }
969
+ }
970
+
850
971
  //#endregion
851
972
  //#region src/index.ts
852
973
  async function setupCli() {
@@ -878,6 +999,13 @@ async function setupCli() {
878
999
  cli.command("self-update", `${bgGreen(white("便捷命令"))} ${lightCyan("qui su")} 自更新:检查并更新 @quiteer/scripts 到最新版本`).alias("su").action(async () => {
879
1000
  await selfUpdate();
880
1001
  });
1002
+ cli.command("tree [path]", `${bgGreen(white("便捷命令"))} ${lightBlue("qui t")} 生成当前目录结构(默认控制台输出),可选生成 Markdown`).alias("t").option("--md", "是否生成 Markdown 文件", { default: cliOptions.dirTree.md }).action(async (path$1, args) => {
1003
+ await generateDirTree(path$1, {
1004
+ md: !!args?.md,
1005
+ output: cliOptions.dirTree.output,
1006
+ ignore: cliOptions.dirTree.ignore
1007
+ });
1008
+ });
881
1009
  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
1010
  if (args?.add) await gitCommitAdd();
883
1011
  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.7",
5
5
  "homepage": "https://taiaiac.github.io/web/ci/scripts.html",
6
6
  "publishConfig": {
7
7
  "access": "public",