@quiteer/scripts 0.0.5 → 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.
- package/dist/index.mjs +319 -140
- 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.
|
|
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
|
|
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
|
-
|
|
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 ?? [])
|
|
317
|
-
|
|
318
|
-
|
|
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 ?? [])
|
|
346
|
-
|
|
347
|
-
|
|
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
|
|
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({
|
|
@@ -493,6 +544,137 @@ async function generateConfig() {
|
|
|
493
544
|
].join("\n"), "utf8");
|
|
494
545
|
}
|
|
495
546
|
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/commands/git-branches.ts
|
|
549
|
+
/**
|
|
550
|
+
* 列出远程仓库所有分支(含最新提交时间),并按时间倒序打印
|
|
551
|
+
* - 会先执行 `git fetch --prune --tags` 同步远程引用
|
|
552
|
+
* - 通过 `git for-each-ref` 获取 `refs/remotes/<remote>` 下的分支与提交时间
|
|
553
|
+
* @param remote 远程仓库名,默认 `origin`
|
|
554
|
+
* @returns Promise<void>
|
|
555
|
+
*/
|
|
556
|
+
async function gitRemoteBranches(urlOrRemote) {
|
|
557
|
+
const temp = "gb-temp";
|
|
558
|
+
const target = (urlOrRemote || "").trim();
|
|
559
|
+
let refBase = "";
|
|
560
|
+
try {
|
|
561
|
+
if (target) {
|
|
562
|
+
await execCommand("git", [
|
|
563
|
+
"fetch",
|
|
564
|
+
target,
|
|
565
|
+
`+refs/heads/*:refs/remotes/${temp}/*`,
|
|
566
|
+
"--prune",
|
|
567
|
+
"--no-tags"
|
|
568
|
+
]);
|
|
569
|
+
refBase = `refs/remotes/${temp}`;
|
|
570
|
+
} else {
|
|
571
|
+
const r = "origin";
|
|
572
|
+
await execCommand("git", [
|
|
573
|
+
"fetch",
|
|
574
|
+
r,
|
|
575
|
+
"--prune",
|
|
576
|
+
"--tags"
|
|
577
|
+
]);
|
|
578
|
+
refBase = `refs/remotes/${r}`;
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
console.log("拉取远程分支失败,请检查 URL 或网络连接");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const lines = (await execCommand("git", [
|
|
585
|
+
"for-each-ref",
|
|
586
|
+
"--format=%(refname:short) %(committerdate:relative) %(objectname:short) %(contents:subject)",
|
|
587
|
+
"--sort=-committerdate",
|
|
588
|
+
refBase
|
|
589
|
+
])).split("\n").filter(Boolean);
|
|
590
|
+
if (lines.length === 0) {
|
|
591
|
+
console.log("未找到远程分支,请确认已配置远程并有追踪分支");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* 英文相对时间转中文
|
|
596
|
+
* @param rel 英文相对时间字符串(如:2 hours ago)
|
|
597
|
+
* @returns 中文相对时间(如:2小时前)
|
|
598
|
+
*/
|
|
599
|
+
function zhRelative(rel) {
|
|
600
|
+
const s = rel.toLowerCase().trim();
|
|
601
|
+
let m;
|
|
602
|
+
if (m = s.match(/^(\d+)\s+seconds?\s+ago$/)) return `${m[1]}秒前`;
|
|
603
|
+
if (m = s.match(/^(\d+)\s+minutes?\s+ago$/)) return `${m[1]}分钟前`;
|
|
604
|
+
if (m = s.match(/^(\d+)\s+hours?\s+ago$/)) return `${m[1]}小时前`;
|
|
605
|
+
if (m = s.match(/^(\d+)\s+days?\s+ago$/)) return `${m[1]}天前`;
|
|
606
|
+
if (m = s.match(/^(\d+)\s+weeks?\s+ago$/)) return `${m[1]}周前`;
|
|
607
|
+
if (m = s.match(/^(\d+)\s+months?\s+ago$/)) return `${m[1]}个月前`;
|
|
608
|
+
if (m = s.match(/^(\d+)\s+years?\s+ago$/)) return `${m[1]}年前`;
|
|
609
|
+
if (s === "yesterday") return "昨天";
|
|
610
|
+
if (s === "today") return "今天";
|
|
611
|
+
if (m = s.match(/^about\s+(\d+)\s+hours?\s+ago$/)) return `${m[1]}小时前`;
|
|
612
|
+
if (m = s.match(/^about\s+(\d+)\s+minutes?\s+ago$/)) return `${m[1]}分钟前`;
|
|
613
|
+
if (m = s.match(/^about\s+(\d+)\s+days?\s+ago$/)) return `${m[1]}天前`;
|
|
614
|
+
if (m = s.match(/^about\s+(\d+)\s+months?\s+ago$/)) return `${m[1]}个月前`;
|
|
615
|
+
if (m = s.match(/^about\s+(\d+)\s+years?\s+ago$/)) return `${m[1]}年前`;
|
|
616
|
+
return rel;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 计算字符串在等宽终端中的显示宽度(简单处理全角字符为宽度 2)
|
|
620
|
+
* @param s 输入字符串(不含 ANSI 颜色码)
|
|
621
|
+
* @returns 可视宽度
|
|
622
|
+
*/
|
|
623
|
+
function displayWidth(s) {
|
|
624
|
+
let w = 0;
|
|
625
|
+
for (const ch of s) {
|
|
626
|
+
const code = ch.codePointAt(0) || 0;
|
|
627
|
+
w += code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 ? 2 : 1;
|
|
628
|
+
}
|
|
629
|
+
return w;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* 基于显示宽度的左对齐填充
|
|
633
|
+
* @param s 源字符串
|
|
634
|
+
* @param width 目标显示宽度
|
|
635
|
+
* @returns 左对齐并填充空格的字符串
|
|
636
|
+
*/
|
|
637
|
+
function padEndDisplay(s, width) {
|
|
638
|
+
const w = displayWidth(s);
|
|
639
|
+
if (w >= width) return s;
|
|
640
|
+
return s + " ".repeat(width - w);
|
|
641
|
+
}
|
|
642
|
+
const rows = lines.map((line) => {
|
|
643
|
+
const [nameRaw, relRaw, shaRaw, subjectRaw] = line.split(" ");
|
|
644
|
+
return {
|
|
645
|
+
name: nameRaw.replace(/^.+\//, ""),
|
|
646
|
+
relZh: zhRelative(relRaw || ""),
|
|
647
|
+
sha: (shaRaw || "").slice(0, 7),
|
|
648
|
+
subject: subjectRaw || ""
|
|
649
|
+
};
|
|
650
|
+
}).filter((item) => item.name && item.name !== "HEAD");
|
|
651
|
+
const nameWidth = Math.max(...rows.map((r) => displayWidth(r.name)));
|
|
652
|
+
const relValues = rows.map((r) => `(${r.relZh})`);
|
|
653
|
+
const relWidth = Math.max(...relValues.map((v) => displayWidth(v)), 0);
|
|
654
|
+
const label = " 最新 ";
|
|
655
|
+
const labelWidth = displayWidth(label);
|
|
656
|
+
const tag = bgGreen(white(label));
|
|
657
|
+
const pad = " ".repeat(labelWidth);
|
|
658
|
+
rows.forEach((row, i) => {
|
|
659
|
+
const relText = padEndDisplay(`(${row.relZh})`, relWidth);
|
|
660
|
+
const line = `${padEndDisplay(row.name, nameWidth)} ${relText} ${row.sha} ${row.subject}`;
|
|
661
|
+
if (i === 0) console.log(`${tag} ${lightGreen(line)}`);
|
|
662
|
+
else console.log(`${pad} ${white(line)}`);
|
|
663
|
+
});
|
|
664
|
+
if (target) {
|
|
665
|
+
const refnames = await execCommand("git", [
|
|
666
|
+
"for-each-ref",
|
|
667
|
+
"--format=%(refname)",
|
|
668
|
+
refBase
|
|
669
|
+
]);
|
|
670
|
+
for (const ref of refnames.split("\n").filter(Boolean)) await execCommand("git", [
|
|
671
|
+
"update-ref",
|
|
672
|
+
"-d",
|
|
673
|
+
ref
|
|
674
|
+
]);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
496
678
|
//#endregion
|
|
497
679
|
//#region src/commands/git-commit.ts
|
|
498
680
|
/**
|
|
@@ -655,148 +837,134 @@ async function release(tagPrefix) {
|
|
|
655
837
|
`chore(projects): release ${tagName}`,
|
|
656
838
|
tagName
|
|
657
839
|
]);
|
|
840
|
+
const cli = await loadCliOptions();
|
|
841
|
+
const lang = cli.lang;
|
|
658
842
|
await generateChangelogFiles({
|
|
659
|
-
lang
|
|
660
|
-
format:
|
|
661
|
-
groupOutput:
|
|
662
|
-
timelineOutput:
|
|
843
|
+
lang,
|
|
844
|
+
format: cli.changelog.formats,
|
|
845
|
+
groupOutput: cli.changelog.groupOutput,
|
|
846
|
+
timelineOutput: cli.changelog.timelineOutput
|
|
663
847
|
});
|
|
664
848
|
}
|
|
665
849
|
|
|
666
850
|
//#endregion
|
|
667
|
-
//#region src/commands/update
|
|
668
|
-
async function updatePkg(args = ["--deep", "-u"]) {
|
|
669
|
-
execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
//#endregion
|
|
673
|
-
//#region src/commands/git-branches.ts
|
|
851
|
+
//#region src/commands/self-update.ts
|
|
674
852
|
/**
|
|
675
|
-
*
|
|
676
|
-
* -
|
|
677
|
-
* - 通过 `git for-each-ref` 获取 `refs/remotes/<remote>` 下的分支与提交时间
|
|
678
|
-
* @param remote 远程仓库名,默认 `origin`
|
|
679
|
-
* @returns Promise<void>
|
|
853
|
+
* 检查 @quiteer/scripts 是否有新版本并提示更新
|
|
854
|
+
* - 启动任意命令时调用,仅提示不执行安装
|
|
680
855
|
*/
|
|
681
|
-
async function
|
|
682
|
-
const temp = "gb-temp";
|
|
683
|
-
const target = (urlOrRemote || "").trim();
|
|
684
|
-
let refBase = "";
|
|
856
|
+
async function checkUpdateAndNotify() {
|
|
685
857
|
try {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
console.log("拉取远程分支失败,请检查 URL 或网络连接");
|
|
858
|
+
const latest = await execCommand("pnpm", [
|
|
859
|
+
"view",
|
|
860
|
+
"@quiteer/scripts",
|
|
861
|
+
"version"
|
|
862
|
+
]);
|
|
863
|
+
if (latest && latest !== version) console.info("quiteer-script :>> ", lightCyan(`检测到新版本 ${lightGreen(latest)},当前版本 ${lightBlue(version)},建议执行 ${bgGreen(white("qui su"))} 进行更新`));
|
|
864
|
+
} catch {}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* 自更新到最新版本
|
|
868
|
+
* - 对比当前版本与远端版本,不一致则使用 pnpm 全局更新
|
|
869
|
+
*/
|
|
870
|
+
async function selfUpdate() {
|
|
871
|
+
const latest = await execCommand("pnpm", [
|
|
872
|
+
"view",
|
|
873
|
+
"@quiteer/scripts",
|
|
874
|
+
"version"
|
|
875
|
+
]);
|
|
876
|
+
if (!latest) {
|
|
877
|
+
console.info("quiteer-script :>> ", lightBlue("无法获取远端版本,请检查网络后重试"));
|
|
707
878
|
return;
|
|
708
879
|
}
|
|
709
|
-
|
|
710
|
-
"
|
|
711
|
-
"--format=%(refname:short) %(committerdate:relative) %(objectname:short) %(contents:subject)",
|
|
712
|
-
"--sort=-committerdate",
|
|
713
|
-
refBase
|
|
714
|
-
])).split("\n").filter(Boolean);
|
|
715
|
-
if (lines.length === 0) {
|
|
716
|
-
console.log("未找到远程分支,请确认已配置远程并有追踪分支");
|
|
880
|
+
if (latest === version) {
|
|
881
|
+
console.info("quiteer-script :>> ", lightGreen(`已是最新版本 ${latest}`));
|
|
717
882
|
return;
|
|
718
883
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (m = s.match(/^(\d+)\s+hours?\s+ago$/)) return `${m[1]}小时前`;
|
|
730
|
-
if (m = s.match(/^(\d+)\s+days?\s+ago$/)) return `${m[1]}天前`;
|
|
731
|
-
if (m = s.match(/^(\d+)\s+weeks?\s+ago$/)) return `${m[1]}周前`;
|
|
732
|
-
if (m = s.match(/^(\d+)\s+months?\s+ago$/)) return `${m[1]}个月前`;
|
|
733
|
-
if (m = s.match(/^(\d+)\s+years?\s+ago$/)) return `${m[1]}年前`;
|
|
734
|
-
if (s === "yesterday") return "昨天";
|
|
735
|
-
if (s === "today") return "今天";
|
|
736
|
-
if (m = s.match(/^about\s+(\d+)\s+hours?\s+ago$/)) return `${m[1]}小时前`;
|
|
737
|
-
if (m = s.match(/^about\s+(\d+)\s+minutes?\s+ago$/)) return `${m[1]}分钟前`;
|
|
738
|
-
if (m = s.match(/^about\s+(\d+)\s+days?\s+ago$/)) return `${m[1]}天前`;
|
|
739
|
-
if (m = s.match(/^about\s+(\d+)\s+months?\s+ago$/)) return `${m[1]}个月前`;
|
|
740
|
-
if (m = s.match(/^about\s+(\d+)\s+years?\s+ago$/)) return `${m[1]}年前`;
|
|
741
|
-
return rel;
|
|
884
|
+
console.info("quiteer-script :>> ", lightCyan(`开始更新到最新版本 ${latest}(当前 ${version})`));
|
|
885
|
+
try {
|
|
886
|
+
await execa("pnpm", [
|
|
887
|
+
"add",
|
|
888
|
+
"-g",
|
|
889
|
+
`@quiteer/scripts@${latest}`
|
|
890
|
+
], { stdio: "inherit" });
|
|
891
|
+
console.info("quiteer-script :>> ", lightGreen("更新完成,请重新运行命令"));
|
|
892
|
+
} catch (e) {
|
|
893
|
+
console.info("quiteer-script :>> ", lightBlue(`更新失败:${e?.message || "未知错误"}`));
|
|
742
894
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
895
|
+
}
|
|
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
|
+
}
|
|
753
936
|
}
|
|
754
|
-
return
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* 基于显示宽度的左对齐填充
|
|
758
|
-
* @param s 源字符串
|
|
759
|
-
* @param width 目标显示宽度
|
|
760
|
-
* @returns 左对齐并填充空格的字符串
|
|
761
|
-
*/
|
|
762
|
-
function padEndDisplay(s, width) {
|
|
763
|
-
const w = displayWidth(s);
|
|
764
|
-
if (w >= width) return s;
|
|
765
|
-
return s + " ".repeat(width - w);
|
|
937
|
+
return lines;
|
|
766
938
|
}
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
"update-ref",
|
|
797
|
-
"-d",
|
|
798
|
-
ref
|
|
799
|
-
]);
|
|
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)}`));
|
|
800
968
|
}
|
|
801
969
|
}
|
|
802
970
|
|
|
@@ -811,6 +979,7 @@ async function setupCli() {
|
|
|
811
979
|
*/
|
|
812
980
|
const cliOptions = await loadCliOptions();
|
|
813
981
|
const cli = cac(blue("quiteer"));
|
|
982
|
+
await checkUpdateAndNotify();
|
|
814
983
|
cli.command("generate-config", `${bgGreen(white("便捷命令"))} ${lightCyan("qui g")} 在项目根目录下生成配置文件`).alias("g").action(async () => {
|
|
815
984
|
await generateConfig();
|
|
816
985
|
});
|
|
@@ -827,6 +996,16 @@ async function setupCli() {
|
|
|
827
996
|
cli.command("update-pkg", `${bgGreen(white("便捷命令"))} ${lightBlue("qui u")} 更新 package.json 依赖版本`).alias("u").action(async () => {
|
|
828
997
|
await updatePkg(cliOptions.ncuCommandArgs);
|
|
829
998
|
});
|
|
999
|
+
cli.command("self-update", `${bgGreen(white("便捷命令"))} ${lightCyan("qui su")} 自更新:检查并更新 @quiteer/scripts 到最新版本`).alias("su").action(async () => {
|
|
1000
|
+
await selfUpdate();
|
|
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
|
+
});
|
|
830
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) => {
|
|
831
1010
|
if (args?.add) await gitCommitAdd();
|
|
832
1011
|
await gitCommit(args?.lang);
|