@ci-kit/changelog 0.0.0-stage → 0.0.1
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/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +30 -0
- package/dist/index.d.mts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.mjs +112 -0
- package/package.json +39 -3
- package/README.md +0 -3
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { cac } from 'cac';
|
|
5
|
+
import { collectCommits, renderChangelog } from './index.mjs';
|
|
6
|
+
import 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const version = "0.0.1";
|
|
9
|
+
const pkg = {
|
|
10
|
+
version: version};
|
|
11
|
+
|
|
12
|
+
const cli = cac("ci-kit-changelog");
|
|
13
|
+
cli.command("[out]", "Generate grouped release notes from git history").option("--from <tag>", "Previous tag (exclusive)").option("--to <tag>", "Target tag (inclusive, defaults to HEAD)").option("--max <n>", "Max items per section", { default: 10 }).action((out, options) => {
|
|
14
|
+
const commits = collectCommits({ from: options.from, to: options.to });
|
|
15
|
+
const markdown = renderChangelog(commits, {
|
|
16
|
+
maxPerSection: Number(options.max ?? 10)
|
|
17
|
+
});
|
|
18
|
+
if (out) {
|
|
19
|
+
writeFileSync(out, `${markdown}
|
|
20
|
+
`, "utf8");
|
|
21
|
+
process.stdout.write(`Wrote ${out} (${commits.length} commits scanned)
|
|
22
|
+
`);
|
|
23
|
+
} else {
|
|
24
|
+
process.stdout.write(`${markdown}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
cli.help();
|
|
29
|
+
cli.version(pkg.version);
|
|
30
|
+
cli.parse();
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface Commit {
|
|
2
|
+
hash: string;
|
|
3
|
+
subject: string;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
interface ChangelogSection {
|
|
7
|
+
type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
items: string[];
|
|
10
|
+
}
|
|
11
|
+
interface ChangelogOptions {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
from?: string;
|
|
14
|
+
to?: string;
|
|
15
|
+
maxPerSection?: number;
|
|
16
|
+
keep?: Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
title: string;
|
|
19
|
+
}>;
|
|
20
|
+
drop?: string[];
|
|
21
|
+
}
|
|
22
|
+
declare const DEFAULT_KEEP: {
|
|
23
|
+
type: string;
|
|
24
|
+
title: string;
|
|
25
|
+
}[];
|
|
26
|
+
declare const DEFAULT_DROP: string[];
|
|
27
|
+
declare function collectCommits(options?: ChangelogOptions): Commit[];
|
|
28
|
+
interface ParsedSubject {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
breaking: boolean;
|
|
32
|
+
references: string;
|
|
33
|
+
}
|
|
34
|
+
declare function parseSubject(subject: string): ParsedSubject | null;
|
|
35
|
+
declare function renderChangelog(commits: Commit[], options?: ChangelogOptions): string;
|
|
36
|
+
declare function isBetaTag(tag: string): boolean;
|
|
37
|
+
declare function previousTagMatcher(channel: 'beta' | 'production'): string;
|
|
38
|
+
|
|
39
|
+
export { DEFAULT_DROP, DEFAULT_KEEP, collectCommits, isBetaTag, parseSubject, previousTagMatcher, renderChangelog };
|
|
40
|
+
export type { ChangelogOptions, ChangelogSection, Commit };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface Commit {
|
|
2
|
+
hash: string;
|
|
3
|
+
subject: string;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
interface ChangelogSection {
|
|
7
|
+
type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
items: string[];
|
|
10
|
+
}
|
|
11
|
+
interface ChangelogOptions {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
from?: string;
|
|
14
|
+
to?: string;
|
|
15
|
+
maxPerSection?: number;
|
|
16
|
+
keep?: Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
title: string;
|
|
19
|
+
}>;
|
|
20
|
+
drop?: string[];
|
|
21
|
+
}
|
|
22
|
+
declare const DEFAULT_KEEP: {
|
|
23
|
+
type: string;
|
|
24
|
+
title: string;
|
|
25
|
+
}[];
|
|
26
|
+
declare const DEFAULT_DROP: string[];
|
|
27
|
+
declare function collectCommits(options?: ChangelogOptions): Commit[];
|
|
28
|
+
interface ParsedSubject {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
breaking: boolean;
|
|
32
|
+
references: string;
|
|
33
|
+
}
|
|
34
|
+
declare function parseSubject(subject: string): ParsedSubject | null;
|
|
35
|
+
declare function renderChangelog(commits: Commit[], options?: ChangelogOptions): string;
|
|
36
|
+
declare function isBetaTag(tag: string): boolean;
|
|
37
|
+
declare function previousTagMatcher(channel: 'beta' | 'production'): string;
|
|
38
|
+
|
|
39
|
+
export { DEFAULT_DROP, DEFAULT_KEEP, collectCommits, isBetaTag, parseSubject, previousTagMatcher, renderChangelog };
|
|
40
|
+
export type { ChangelogOptions, ChangelogSection, Commit };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
|
|
4
|
+
const HEADER = "## \u66F4\u65B0\u5185\u5BB9";
|
|
5
|
+
const EMPTY_NOTICE = "\u672C\u6B21\u7248\u672C\u4E3B\u8981\u4E3A\u5185\u90E8\u7EF4\u62A4\u4E0E\u7A33\u5B9A\u6027\u8C03\u6574\uFF0C\u6CA1\u6709\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\u53D8\u66F4\u3002";
|
|
6
|
+
const FIRST_RELEASE_NOTICE = "\u9996\u4E2A\u7248\u672C\u3002";
|
|
7
|
+
const DEFAULT_KEEP = [
|
|
8
|
+
{ type: "feat", title: "\u65B0\u529F\u80FD" },
|
|
9
|
+
{ type: "fix", title: "\u4FEE\u590D" },
|
|
10
|
+
{ type: "perf", title: "\u6027\u80FD" },
|
|
11
|
+
{ type: "refactor", title: "\u91CD\u6784" }
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_DROP = [
|
|
14
|
+
"ci",
|
|
15
|
+
"docs",
|
|
16
|
+
"chore",
|
|
17
|
+
"test",
|
|
18
|
+
"build",
|
|
19
|
+
"style"
|
|
20
|
+
];
|
|
21
|
+
function collectCommits(options = {}) {
|
|
22
|
+
const { cwd = process.cwd(), from, to } = options;
|
|
23
|
+
const range = from ? `${from}..${to ?? "HEAD"}` : to ?? "HEAD";
|
|
24
|
+
const output = execFileSync(
|
|
25
|
+
"git",
|
|
26
|
+
[
|
|
27
|
+
"log",
|
|
28
|
+
range,
|
|
29
|
+
"--no-merges",
|
|
30
|
+
"--pretty=format:%H%x1f%s%x1f%b%x1e"
|
|
31
|
+
],
|
|
32
|
+
{ cwd, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }
|
|
33
|
+
);
|
|
34
|
+
return output.split("").map((chunk) => chunk.trim()).filter(Boolean).map((chunk) => {
|
|
35
|
+
const [hash = "", subject = "", body = ""] = chunk.split("");
|
|
36
|
+
return {
|
|
37
|
+
hash,
|
|
38
|
+
subject: subject.trim(),
|
|
39
|
+
body: body.trim()
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const PREFIX_RE = /^(feat|fix|perf|refactor|ci|docs|chore|test|build|style)(?:\([^)]+\))?(!)?:\s+/;
|
|
44
|
+
function parseSubject(subject) {
|
|
45
|
+
const match = subject.match(PREFIX_RE);
|
|
46
|
+
if (!match) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const [, type, breaking] = match;
|
|
50
|
+
const description = subject.slice(match[0].length).trim();
|
|
51
|
+
const references = description.match(/#\d+/g)?.join(" ") ?? "";
|
|
52
|
+
return {
|
|
53
|
+
type,
|
|
54
|
+
description: description.trim(),
|
|
55
|
+
breaking: breaking === "!",
|
|
56
|
+
references
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function renderChangelog(commits, options = {}) {
|
|
60
|
+
const {
|
|
61
|
+
keep = DEFAULT_KEEP,
|
|
62
|
+
drop = DEFAULT_DROP,
|
|
63
|
+
maxPerSection = 10
|
|
64
|
+
} = options;
|
|
65
|
+
if (commits.length === 0) {
|
|
66
|
+
return `${HEADER}
|
|
67
|
+
|
|
68
|
+
${FIRST_RELEASE_NOTICE}`;
|
|
69
|
+
}
|
|
70
|
+
const sections = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const commit of commits) {
|
|
72
|
+
const parsed = parseSubject(commit.subject);
|
|
73
|
+
if (!parsed || drop.includes(parsed.type)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!keep.some((entry) => entry.type === parsed.type)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const items = sections.get(parsed.type) ?? [];
|
|
80
|
+
items.push(parsed.breaking ? `${parsed.description}\uFF08\u4E0D\u517C\u5BB9\uFF09` : parsed.description);
|
|
81
|
+
sections.set(parsed.type, items);
|
|
82
|
+
}
|
|
83
|
+
const lines = [HEADER, ""];
|
|
84
|
+
let rendered = false;
|
|
85
|
+
for (const { type, title } of keep) {
|
|
86
|
+
const items = sections.get(type);
|
|
87
|
+
if (!items || items.length === 0) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
rendered = true;
|
|
91
|
+
lines.push(`### ${title}`, "");
|
|
92
|
+
for (const item of items.slice(0, maxPerSection)) {
|
|
93
|
+
lines.push(`- ${item}`);
|
|
94
|
+
}
|
|
95
|
+
if (items.length > maxPerSection) {
|
|
96
|
+
lines.push(`- \u4EE5\u53CA\u53E6\u5916 ${items.length - maxPerSection} \u6761`);
|
|
97
|
+
}
|
|
98
|
+
lines.push("");
|
|
99
|
+
}
|
|
100
|
+
if (!rendered) {
|
|
101
|
+
lines.push(EMPTY_NOTICE, "");
|
|
102
|
+
}
|
|
103
|
+
return lines.join("\n").trimEnd();
|
|
104
|
+
}
|
|
105
|
+
function isBetaTag(tag) {
|
|
106
|
+
return /\d+\.\d+\.\d+-beta\.\d+$/.test(tag);
|
|
107
|
+
}
|
|
108
|
+
function previousTagMatcher(channel) {
|
|
109
|
+
return channel === "beta" ? "v*-beta.*" : "v[0-9]*";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { DEFAULT_DROP, DEFAULT_KEEP, collectCommits, isBetaTag, parseSubject, previousTagMatcher, renderChangelog };
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ci-kit/changelog",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "Turn git commits into grouped release notes",
|
|
6
|
+
"author": "imba97",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/cikit-js/ci-kit#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/cikit-js/ci-kit.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/cikit-js/ci-kit/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.mjs",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"bin": {
|
|
26
|
+
"ci-kit-changelog": "dist/cli.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"cac": "^7.1.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "unbuild",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"stub": "unbuild --stub"
|
|
41
|
+
}
|
|
6
42
|
}
|
package/README.md
DELETED