@quiteer/scripts 0.0.3 → 0.0.5
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 +68 -0
- package/dist/index.mjs +139 -29
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @quiteer/scripts
|
|
2
|
+
|
|
3
|
+
简化多包仓库的版本管理与变更日志生成,提供便捷的 CLI 命令。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
- 全局安装(推荐)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm i -g @quiteer/scripts
|
|
11
|
+
# 或者
|
|
12
|
+
npm i -g @quiteer/scripts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- 项目内安装(作为开发依赖)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add -D @quiteer/scripts
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> 运行环境:Node.js ≥ 22.14.0,建议使用 pnpm 管理依赖。
|
|
22
|
+
|
|
23
|
+
## 快速使用
|
|
24
|
+
|
|
25
|
+
- 查看版本
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
qui -v
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- 版本管理与打标签(支持多包选择与自定义前缀标签,同时生成全量 changelog)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 交互选择需要提升版本的包
|
|
35
|
+
qui r --tag-prefix scripts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- 生成全量变更日志(中文,生成 CHANGELOG.md 与 CHANGELOG_TIMELINE.md)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
qui cl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- 规范化提交(交互式生成符合 Conventional Commits 的提交信息)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
qui gc
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 常用工作流示例
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1) 更新版本并创建标签(例如为 scripts 包增加前缀)
|
|
54
|
+
qui r --tag-prefix scripts
|
|
55
|
+
|
|
56
|
+
# 2) 生成 changelog(默认生成全部历史)
|
|
57
|
+
qui cl
|
|
58
|
+
|
|
59
|
+
# 3) 提交并推送(如需发布)
|
|
60
|
+
git add -A && git commit -m "docs(changelog): update"
|
|
61
|
+
# 可选:git push && git push --tags
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 说明
|
|
65
|
+
|
|
66
|
+
- 多包仓库:发布时可交互选择要更新的包,未选中的包不受影响。
|
|
67
|
+
- 标签前缀:标签命名为 `<prefix>-vX.Y.Z`,可避免多包仓库中的全局标签冲突。
|
|
68
|
+
- changelog:默认基于仓库最初提交到当前 HEAD 的全量历史生成。
|
package/dist/index.mjs
CHANGED
|
@@ -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.4";
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/locales/changelog.ts
|
|
@@ -196,13 +196,6 @@ function getTypeIcon(type) {
|
|
|
196
196
|
"typo": "✏️ "
|
|
197
197
|
}[type] || "";
|
|
198
198
|
}
|
|
199
|
-
async function getTags() {
|
|
200
|
-
return (await execCommand("git", [
|
|
201
|
-
"tag",
|
|
202
|
-
"--list",
|
|
203
|
-
"--sort=-version:refname"
|
|
204
|
-
])).split("\n").filter(Boolean);
|
|
205
|
-
}
|
|
206
199
|
async function getRootCommit() {
|
|
207
200
|
return await execCommand("git", [
|
|
208
201
|
"rev-list",
|
|
@@ -210,15 +203,6 @@ async function getRootCommit() {
|
|
|
210
203
|
"HEAD"
|
|
211
204
|
]);
|
|
212
205
|
}
|
|
213
|
-
async function getDateForRef(ref) {
|
|
214
|
-
return await execCommand("git", [
|
|
215
|
-
"log",
|
|
216
|
-
"-1",
|
|
217
|
-
"--date=short",
|
|
218
|
-
"--format=%ad",
|
|
219
|
-
ref
|
|
220
|
-
]) || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
221
|
-
}
|
|
222
206
|
async function getCommitsInRange(range) {
|
|
223
207
|
return (await execCommand("git", [
|
|
224
208
|
"log",
|
|
@@ -378,18 +362,10 @@ function formatTimeline(items, repoUrl) {
|
|
|
378
362
|
async function generateChangelogFiles(options) {
|
|
379
363
|
const repoRoot = await execCommand("git", ["rev-parse", "--show-toplevel"]);
|
|
380
364
|
const homepage = await readHomepage();
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
let
|
|
385
|
-
if (tags.length >= 1) {
|
|
386
|
-
const latest = tags[0];
|
|
387
|
-
const prev = tags[1];
|
|
388
|
-
title = latest;
|
|
389
|
-
date = await getDateForRef(latest);
|
|
390
|
-
range = prev ? `${prev}..${latest}` : `${await getRootCommit()}..${latest}`;
|
|
391
|
-
} else range = "HEAD";
|
|
392
|
-
let items = await getCommitsInRange(range);
|
|
365
|
+
const root = await getRootCommit();
|
|
366
|
+
const title = "全部历史";
|
|
367
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
368
|
+
let items = await getCommitsInRange(`${root}..HEAD`);
|
|
393
369
|
items = await enrichCommit(items);
|
|
394
370
|
const fallback = [
|
|
395
371
|
`## ${title} - ${date}`,
|
|
@@ -693,6 +669,137 @@ async function updatePkg(args = ["--deep", "-u"]) {
|
|
|
693
669
|
execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
|
|
694
670
|
}
|
|
695
671
|
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/commands/git-branches.ts
|
|
674
|
+
/**
|
|
675
|
+
* 列出远程仓库所有分支(含最新提交时间),并按时间倒序打印
|
|
676
|
+
* - 会先执行 `git fetch --prune --tags` 同步远程引用
|
|
677
|
+
* - 通过 `git for-each-ref` 获取 `refs/remotes/<remote>` 下的分支与提交时间
|
|
678
|
+
* @param remote 远程仓库名,默认 `origin`
|
|
679
|
+
* @returns Promise<void>
|
|
680
|
+
*/
|
|
681
|
+
async function gitRemoteBranches(urlOrRemote) {
|
|
682
|
+
const temp = "gb-temp";
|
|
683
|
+
const target = (urlOrRemote || "").trim();
|
|
684
|
+
let refBase = "";
|
|
685
|
+
try {
|
|
686
|
+
if (target) {
|
|
687
|
+
await execCommand("git", [
|
|
688
|
+
"fetch",
|
|
689
|
+
target,
|
|
690
|
+
`+refs/heads/*:refs/remotes/${temp}/*`,
|
|
691
|
+
"--prune",
|
|
692
|
+
"--no-tags"
|
|
693
|
+
]);
|
|
694
|
+
refBase = `refs/remotes/${temp}`;
|
|
695
|
+
} else {
|
|
696
|
+
const r = "origin";
|
|
697
|
+
await execCommand("git", [
|
|
698
|
+
"fetch",
|
|
699
|
+
r,
|
|
700
|
+
"--prune",
|
|
701
|
+
"--tags"
|
|
702
|
+
]);
|
|
703
|
+
refBase = `refs/remotes/${r}`;
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
console.log("拉取远程分支失败,请检查 URL 或网络连接");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const lines = (await execCommand("git", [
|
|
710
|
+
"for-each-ref",
|
|
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("未找到远程分支,请确认已配置远程并有追踪分支");
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* 英文相对时间转中文
|
|
721
|
+
* @param rel 英文相对时间字符串(如:2 hours ago)
|
|
722
|
+
* @returns 中文相对时间(如:2小时前)
|
|
723
|
+
*/
|
|
724
|
+
function zhRelative(rel) {
|
|
725
|
+
const s = rel.toLowerCase().trim();
|
|
726
|
+
let m;
|
|
727
|
+
if (m = s.match(/^(\d+)\s+seconds?\s+ago$/)) return `${m[1]}秒前`;
|
|
728
|
+
if (m = s.match(/^(\d+)\s+minutes?\s+ago$/)) return `${m[1]}分钟前`;
|
|
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;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 计算字符串在等宽终端中的显示宽度(简单处理全角字符为宽度 2)
|
|
745
|
+
* @param s 输入字符串(不含 ANSI 颜色码)
|
|
746
|
+
* @returns 可视宽度
|
|
747
|
+
*/
|
|
748
|
+
function displayWidth(s) {
|
|
749
|
+
let w = 0;
|
|
750
|
+
for (const ch of s) {
|
|
751
|
+
const code = ch.codePointAt(0) || 0;
|
|
752
|
+
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;
|
|
753
|
+
}
|
|
754
|
+
return w;
|
|
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);
|
|
766
|
+
}
|
|
767
|
+
const rows = lines.map((line) => {
|
|
768
|
+
const [nameRaw, relRaw, shaRaw, subjectRaw] = line.split(" ");
|
|
769
|
+
return {
|
|
770
|
+
name: nameRaw.replace(/^.+\//, ""),
|
|
771
|
+
relZh: zhRelative(relRaw || ""),
|
|
772
|
+
sha: (shaRaw || "").slice(0, 7),
|
|
773
|
+
subject: subjectRaw || ""
|
|
774
|
+
};
|
|
775
|
+
}).filter((item) => item.name && item.name !== "HEAD");
|
|
776
|
+
const nameWidth = Math.max(...rows.map((r) => displayWidth(r.name)));
|
|
777
|
+
const relValues = rows.map((r) => `(${r.relZh})`);
|
|
778
|
+
const relWidth = Math.max(...relValues.map((v) => displayWidth(v)), 0);
|
|
779
|
+
const label = " 最新 ";
|
|
780
|
+
const labelWidth = displayWidth(label);
|
|
781
|
+
const tag = bgGreen(white(label));
|
|
782
|
+
const pad = " ".repeat(labelWidth);
|
|
783
|
+
rows.forEach((row, i) => {
|
|
784
|
+
const relText = padEndDisplay(`(${row.relZh})`, relWidth);
|
|
785
|
+
const line = `${padEndDisplay(row.name, nameWidth)} ${relText} ${row.sha} ${row.subject}`;
|
|
786
|
+
if (i === 0) console.log(`${tag} ${lightGreen(line)}`);
|
|
787
|
+
else console.log(`${pad} ${white(line)}`);
|
|
788
|
+
});
|
|
789
|
+
if (target) {
|
|
790
|
+
const refnames = await execCommand("git", [
|
|
791
|
+
"for-each-ref",
|
|
792
|
+
"--format=%(refname)",
|
|
793
|
+
refBase
|
|
794
|
+
]);
|
|
795
|
+
for (const ref of refnames.split("\n").filter(Boolean)) await execCommand("git", [
|
|
796
|
+
"update-ref",
|
|
797
|
+
"-d",
|
|
798
|
+
ref
|
|
799
|
+
]);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
696
803
|
//#endregion
|
|
697
804
|
//#region src/index.ts
|
|
698
805
|
async function setupCli() {
|
|
@@ -727,6 +834,9 @@ async function setupCli() {
|
|
|
727
834
|
cli.command("git-commit-verify", `${bgGreen(white("便捷命令"))} ${lightBlue("qui gv")} 校验提交信息是否符合 Conventional Commits 标准`).alias("gv").option("-l --lang", "校验提交信息的语言", { default: cliOptions.lang }).action(async (args) => {
|
|
728
835
|
await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores);
|
|
729
836
|
});
|
|
837
|
+
cli.command("git-branches [url]", `${bgGreen(white("便捷命令"))} ${lightBlue("qui gb")} 查看远程仓库所有分支(分支名 + 最新时间),倒序排序`).alias("gb").action(async (url) => {
|
|
838
|
+
await gitRemoteBranches(url);
|
|
839
|
+
});
|
|
730
840
|
cli.command("release", `${bgGreen(white("便捷命令"))} ${lightBlue("qui r")} 版本管理:选择包并提升版本,创建自定义前缀标签并生成 changelog`).alias("r").option("--tag-prefix <prefix>", "标签前缀(可选,留空则交互输入)").action(async (args) => {
|
|
731
841
|
await release(args?.tagPrefix);
|
|
732
842
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiteer/scripts",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
5
|
-
"homepage": "https://github.
|
|
4
|
+
"version": "0.0.5",
|
|
5
|
+
"homepage": "https://taiaiac.github.io/web/ci/scripts.html",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
8
8
|
"registry": "https://registry.npmjs.org/"
|