@soybeanjs/changelog 0.3.1 → 0.3.3

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.cjs CHANGED
@@ -1 +1,19 @@
1
- module.exports = require("/Users/soybean/Web/Projects/SoybeanJS/changelog/node_modules/.pnpm/jiti@1.19.3/node_modules/jiti/lib/index.js")(null, { interopDefault: true, esmResolve: true })("/Users/soybean/Web/Projects/SoybeanJS/changelog/src/index.ts")
1
+ "use strict";const cliProgress=require("cli-progress"),promises=require("fs/promises"),ofetch=require("ofetch"),w$1=require("dayjs"),fs=require("fs"),convertGitmoji=require("convert-gitmoji");function _interopDefaultCompat(t){return t&&typeof t=="object"&&"default"in t?t.default:t}const w__default=_interopDefaultCompat(w$1);async function execCommand(t,n,e){const{execa:o}=await import("execa");return(await o(t,n,e))?.stdout?.trim()||""}function notNullish(t){return t!=null}function partition(t,...n){const e=new Array(n.length+1).fill(null).map(()=>[]);return t.forEach((o,a,s)=>{let r=0;for(const i of n){if(i(o,a,s)){e[r].push(o);return}r+=1}e[r].push(o)}),e}function groupBy(t,n,e={}){for(const o of t){const a=o[n];e[a]=e[a]||[],e[a].push(o)}return e}function capitalize(t){return t.charAt(0).toUpperCase()+t.slice(1)}function join(t,n=", ",e=" and "){return!t||t.length===0?"":t.length===1?t[0]:t.length===2?t.join(e):`${t.slice(0,-1).join(n)}${e}${t.slice(-1)}`}const VERSION_REG=/v\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?/,VERSION_REG_OF_MARKDOWN=/## \[v\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?]/g,VERSION_WITH_RELEASE=/release\sv\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?/;async function getTotalGitTags(){return(await execCommand("git",["--no-pager","tag","-l","--sort=creatordate"])).split(`
2
+ `)}async function getTagDateMap(){const t=await execCommand("git",["--no-pager","log","--tags","--simplify-by-decoration","--pretty=format:%ci %d"]),n="tag: ",e=new Map;return t.split(`
3
+ `).filter(o=>o.includes(n)).forEach(o=>{const[a,s]=o.split(n),r=w__default(a).format("YYYY-MM-DD"),i=s.match(VERSION_REG)?.[0];i&&r&&e.set(i.trim(),r)}),e}function getFromToTags(t){const n=[];return t.forEach((e,o)=>{o<t.length-1&&n.push({from:e,to:t[o+1]})}),n}async function getLastGitTag(t=0){const n=await getTotalGitTags();return n[n.length+t-1]}async function b$1(){return await execCommand("git",["rev-parse","--abbrev-ref","HEAD"])}async function getCurrentGitBranch(){const t=await execCommand("git",["tag","--points-at","HEAD"]),n=b$1();return t||n}async function getGitHubRepo(){const t=await execCommand("git",["config","--get","remote.origin.url"]),n=t.match(/github\.com[/:]([\w\d._-]+?)\/([\w\d._-]+?)(\.git)?$/i);if(!n)throw new Error(`Can not parse GitHub repo from url ${t}`);return`${n[1]}/${n[2]}`}function isPrerelease(t){return!/^[^.]*[\d.]+$/.test(t)}function getFirstGitCommit(){return execCommand("git",["rev-list","--max-parents=0","HEAD"])}async function v(t,n="HEAD"){return(await execCommand("git",["--no-pager","log",`${t?`${t}...`:""}${n}`,'--pretty="----%n%s|%h|%an|%ae%n%b"',"--name-status"])).split(`----
4
+ `).splice(1).map(e=>{const[o,...a]=e.split(`
5
+ `),[s,r,i,p]=o.split("|");return{message:s,shortHash:r,author:{name:i,email:p},body:a.join(`
6
+ `)}})}function E(t){const n=/(?<type>[a-z]+)(\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i,e=/co-authored-by:\s*(?<name>.+)(<(?<email>.+)>)/gim,o=/\([a-z]*(#\d+)\s*\)/gm,a=/(#\d+)/gm,s=t.message.match(n);if(!s?.groups)return null;const r=s.groups.type,i=s.groups.scope||"",p=!!s.groups.breaking;let h=s.groups.description;const c=[];for(const g of h.matchAll(o))c.push({type:"pull-request",value:g[1]});for(const g of h.matchAll(a))c.some(m=>m.value===g[1])||c.push({type:"issue",value:g[1]});c.push({value:t.shortHash,type:"hash"}),h=h.replace(o,"").trim();const f=[t.author],l=t.body.matchAll(e);for(const g of l){const{name:m="",email:y=""}=g.groups||{},F={name:m.trim(),email:y.trim()};f.push(F)}return{...t,authors:f,resolvedAuthors:[],description:h,type:r,scope:i,references:c,isBreaking:p}}async function getGitCommits(t,n="HEAD"){return(await v(t,n)).map(e=>E(e)).filter(notNullish)}function G$1(t){return{accept:"application/vnd.github.v3+json",authorization:`token ${t}`}}async function x$1(t,n,e){let o="";try{o=(await ofetch.ofetch(`https://ungh.cc/users/find/${e}`))?.user?.username||""}catch(r){console.log("e: ",r)}if(o)return o;const{repo:a,token:s}=t;if(!s)return o;if(n.length)try{o=(await ofetch.ofetch(`https://api.github.com/repos/${a}/commits/${n[0]}`,{headers:G$1(s)}))?.author?.login||""}catch(r){console.log("e: ",r)}if(o)return o;try{o=(await ofetch.ofetch(`https://api.github.com/search/users?q=${encodeURIComponent(e)}`,{headers:G$1(s)})).items[0].login}catch(r){console.log("e: ",r)}return o}async function getGitCommitsAndResolvedAuthors(t,n,e){const o=[],a=new Map;for await(const s of t){const r=[];for await(const[p,h]of s.authors.entries()){const{email:c,name:f}=h;if(c&&f){const l=[];p===0&&l.push(s.shortHash);const g={name:f,email:c,commits:l,login:""};if(e?.has(c)){const m=e?.get(c)||"";g.login=m}else{const m=await x$1(n,l,c);g.login=m,e?.set(c,m)}r.push(g),a.has(c)||a.set(c,g)}}const i={...s,resolvedAuthors:r};o.push(i)}return{commits:o,contributors:Array.from(a.values())}}function u(){return{cwd:process.cwd(),types:{feat:"\u{1F680} Features",fix:"\u{1F41E} Bug Fixes",perf:"\u{1F525} Performance",refactor:"\u{1F485} Refactors",docs:"\u{1F4D6} Documentation",build:"\u{1F4E6} Build",types:"\u{1F30A} Types",chore:"\u{1F3E1} Chore",examples:"\u{1F3C0} Examples",test:"\u2705 Tests",style:"\u{1F3A8} Styles",ci:"\u{1F916} CI"},github:{repo:"",token:process.env.GITHUB_TOKEN||""},from:"",to:"",tags:[],tagDateMap:new Map,capitalize:!1,emoji:!0,titles:{breakingChanges:"\u{1F6A8} Breaking Changes"},output:"CHANGELOG.md",regenerate:!1,newVersion:""}}async function w(t){let n="";try{const e=await promises.readFile(`${t}/package.json`,"utf-8");n=JSON.parse(e)?.version||""}catch{}return{newVersion:n}}async function createOptions(t){var n;const e=u();Object.assign(e,t);const{newVersion:o}=await w(e.cwd);if((n=e.github).repo||(n.repo=await getGitHubRepo()),e.newVersion||(e.newVersion=`v${o}`),e.from||(e.from=await getLastGitTag()),e.to||(e.to=await getCurrentGitBranch()),e.to===e.from){const a=await getLastGitTag(-1),s=await getFirstGitCommit();e.from=a||s}return e.tags=await getTotalGitTags(),e.tagDateMap=await getTagDateMap(),e.prerelease=e.prerelease??isPrerelease(e.to),e}function $(t,n,e){const o=t.filter(s=>e==="issues"?s.type==="issue"||s.type==="pull-request":s.type==="hash").map(s=>n?s.type==="pull-request"||s.type==="issue"?`https://github.com/${n}/issues/${s.value.slice(1)}`:`[<samp>(${s.value.slice(0,5)})</samp>](https://github.com/${n}/commit/${s.value})`:s.value),a=join(o).trim();return e==="issues"?a&&`in ${a}`:a}function G(t,n){const e=$(t.references,n.github.repo,"issues"),o=$(t.references,n.github.repo,"hash");let a=join([...new Set(t.resolvedAuthors.map(r=>r.login?`@${r.login}`:`**${r.name}**`))]).trim();a&&(a=`by ${a}`);let s=[a,e,o].filter(r=>r?.trim()).join(" ");return s&&(s=`&nbsp;-&nbsp; ${s}`),[n.capitalize?capitalize(t.description):t.description,s].filter(r=>r?.trim()).join(" ")}function k(t,n){const e=/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g;let o=t.trim();return n.emoji||(o=t.replace(e,"").trim()),`### &nbsp;&nbsp;&nbsp;${o}`}function b(t,n,e){if(!t.length)return[];const o=["",k(n,e),""],a=groupBy(t,"scope");let s=!0;return Object.entries(a).some(([r,i])=>r&&i.length>1)||(s=!1),Object.keys(a).sort().forEach(r=>{let i="",p="";const h=`**${r}**`;r&&s?(o.push(`- ${h}:`),i=" "):r&&(p=`${h}: `),o.push(...a[r].reverse().map(c=>`${i}- ${p}${G(c,e)}`))}),o}function d(t){return`https://github.com/${t}`}function x(t){return`${d(t)}.png?size=48`}function M(t){let n="",e="";return t.forEach((o,a)=>{const{name:s,email:r,login:i}=o;if(i){const p=d(i),h=x(i);n+=`[![${i}](${h})](${p})&nbsp;&nbsp;`}else{let p=`[${s}](mailto:${r})`;a<t.length-1&&(p+=",&nbsp;"),e+=p}}),`${n}
7
+ ${e}`}function generateMarkdown(t){const{options:n,showTitle:e,contributors:o}=t,a=t.commits.filter(l=>l.description.match(VERSION_WITH_RELEASE)===null),s=[],r=!VERSION_REG.test(n.to),i=r?n.newVersion:n.to,p=`https://github.com/${n.github.repo}/compare/${n.from}...${i}`;if(e){const l=r?w__default().format("YY-MM-DD"):n.tagDateMap.get(n.to);let g=`## [${i}](${p})`;l&&(g+=` (${l})`),s.push(g)}const[h,c]=partition(a,l=>l.isBreaking),f=groupBy(c,"type");s.push(...b(h,n.titles.breakingChanges,n));for(const l of Object.keys(n.types)){const g=f[l]||[];s.push(...b(g,n.types[l],n))}if(s.length||s.push("*No significant changes*"),e||s.push("",`##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](${p})`),e){s.push("","### &nbsp;&nbsp;&nbsp;\u2764\uFE0F Contributors","");const l=M(o);s.push(l)}return convertGitmoji.convert(s.join(`
8
+ `).trim(),!0)}async function isVersionInMarkdown(t,n){let e=!1;const o=await promises.readFile(n,"utf8");if(o){const a=o.match(VERSION_REG_OF_MARKDOWN);if(a?.length){const s=`## [${t}]`;e=a.includes(s)}}return e}async function writeMarkdown(t,n,e=!1){let o;const a="# Changelog";!e&&fs.existsSync(n)?(o=await promises.readFile(n,"utf8"),o.startsWith(a)||(o=`${a}
9
+
10
+ ${o}`)):o=`${a}
11
+
12
+ `;const s=o.match(/^###?\s+.*$/m);s?o=`${o.slice(0,s.index)+t}
13
+
14
+ ${o.slice(s.index)}`:o+=`
15
+ ${t}
16
+
17
+ `,await promises.writeFile(n,o)}async function getChangelogMarkdown(t,n=!0){const e=await createOptions(t),o=await getGitCommits(e.from,e.to),{commits:a,contributors:s}=await getGitCommitsAndResolvedAuthors(o,e.github);return{markdown:generateMarkdown({commits:a,options:e,showTitle:n,contributors:s}),commits:a,options:e}}async function getTotalChangelogMarkdown(t,n=!0){const e=await createOptions(t);let o=null;n&&(o=new cliProgress.SingleBar({format:"generate total changelog: [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}"},cliProgress.Presets.shades_classic));const a=getFromToTags(e.tags);if(a.length===0){const{markdown:i}=await getChangelogMarkdown(e);return i}o?.start(a.length,0);let s="";const r=new Map;for await(const[i,p]of a.entries()){const{from:h,to:c}=p,f=await getGitCommits(h,c),{commits:l,contributors:g}=await getGitCommitsAndResolvedAuthors(f,e.github,r);s=`${generateMarkdown({commits:l,options:{...e,from:h,to:c},showTitle:!0,contributors:g})}
18
+
19
+ ${s}`,o?.update(i+1)}return o?.stop(),s}async function generateChangelog(t){const n=await createOptions(t),e=await isVersionInMarkdown(n.to,n.output);if(!n.regenerate&&e)return;const{markdown:o}=await getChangelogMarkdown(n);await writeMarkdown(o,n.output,n.regenerate)}async function generateTotalChangelog(t,n=!0){const e=await createOptions(t),o=await getTotalChangelogMarkdown(e,n);await writeMarkdown(o,e.output,!0)}exports.generateChangelog=generateChangelog,exports.generateTotalChangelog=generateTotalChangelog,exports.getChangelogMarkdown=getChangelogMarkdown,exports.getTotalChangelogMarkdown=getTotalChangelogMarkdown;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * the commit author
3
+ */
4
+ interface GitCommitAuthor {
5
+ /**
6
+ * the author name
7
+ */
8
+ name: string;
9
+ /**
10
+ * the author email
11
+ */
12
+ email: string;
13
+ }
14
+ /**
15
+ * the raw git commit
16
+ */
17
+ interface RawGitCommit {
18
+ /**
19
+ * the commit message
20
+ */
21
+ message: string;
22
+ /**
23
+ * the commit body
24
+ */
25
+ body: string;
26
+ /**
27
+ * the commit hash
28
+ */
29
+ shortHash: string;
30
+ /**
31
+ * the commit author
32
+ */
33
+ author: GitCommitAuthor;
34
+ }
35
+ /**
36
+ * the reference of the commit
37
+ */
38
+ interface Reference {
39
+ /**
40
+ * the reference type
41
+ */
42
+ type: 'hash' | 'issue' | 'pull-request';
43
+ /**
44
+ * the reference value
45
+ */
46
+ value: string;
47
+ }
48
+ /**
49
+ * the resolved github author
50
+ */
51
+ interface ResolvedAuthor extends GitCommitAuthor {
52
+ /**
53
+ * the git commit of the author
54
+ */
55
+ commits: string[];
56
+ /**
57
+ * the github logged username of the author
58
+ */
59
+ login: string;
60
+ }
61
+ /**
62
+ * git commit config
63
+ */
64
+ interface GitCommit extends RawGitCommit {
65
+ /**
66
+ * the commit description
67
+ */
68
+ description: string;
69
+ /**
70
+ * the commit scope type
71
+ */
72
+ type: string;
73
+ /**
74
+ * the commit scope
75
+ */
76
+ scope: string;
77
+ /**
78
+ * the commit references
79
+ */
80
+ references: Reference[];
81
+ /**
82
+ * the commit authors
83
+ */
84
+ authors: GitCommitAuthor[];
85
+ /**
86
+ * the resolved authors
87
+ */
88
+ resolvedAuthors: ResolvedAuthor[];
89
+ /**
90
+ * the commit breaking changes
91
+ */
92
+ isBreaking: boolean;
93
+ }
94
+ /**
95
+ * github config
96
+ */
97
+ interface GithubConfig {
98
+ /**
99
+ * the github repository name
100
+ * @example soybeanjs/changelog
101
+ */
102
+ repo: string;
103
+ /**
104
+ * the github token
105
+ */
106
+ token: string;
107
+ }
108
+ interface ChangelogOption {
109
+ /**
110
+ * the directory of the project
111
+ * @default process.cwd()
112
+ */
113
+ cwd: string;
114
+ /**
115
+ * the commit scope types
116
+ */
117
+ types: Record<string, string>;
118
+ /**
119
+ * github config
120
+ */
121
+ github: GithubConfig;
122
+ /**
123
+ * the commit hash or tag
124
+ */
125
+ from: string;
126
+ /**
127
+ * the commit hash or tag
128
+ */
129
+ to: string;
130
+ /**
131
+ * the whole commit tags
132
+ */
133
+ tags: string[];
134
+ /**
135
+ * the commit tag and date map
136
+ */
137
+ tagDateMap: Map<string, string>;
138
+ /**
139
+ * Whether to capitalize the first letter of the commit type
140
+ */
141
+ capitalize: boolean;
142
+ /**
143
+ * Use emojis in section titles
144
+ * @default true
145
+ */
146
+ emoji: boolean;
147
+ /**
148
+ * the section titles
149
+ */
150
+ titles: {
151
+ /**
152
+ * the title of breaking changes section
153
+ */
154
+ breakingChanges: string;
155
+ };
156
+ /**
157
+ * the output file path of the changelog
158
+ */
159
+ output: string;
160
+ /**
161
+ * Whether to regenerate the changelog if it already exists
162
+ * @example the changelog already exists the content of v0.0.1, but you want to regenerate it
163
+ */
164
+ regenerate: boolean;
165
+ /**
166
+ * version from package.json, with prefix "v"
167
+ * @description if the options "to" is not specified, the version will be used
168
+ */
169
+ newVersion: string;
170
+ /**
171
+ * Mark the release as prerelease
172
+ */
173
+ prerelease?: boolean;
174
+ }
175
+
176
+ /**
177
+ * get the changelog markdown by two git tags
178
+ * @param options the changelog options
179
+ * @param showTitle whither show the title
180
+ */
181
+ declare function getChangelogMarkdown(options?: Partial<ChangelogOption>, showTitle?: boolean): Promise<{
182
+ markdown: string;
183
+ commits: GitCommit[];
184
+ options: ChangelogOption;
185
+ }>;
186
+ /**
187
+ * get the changelog markdown by the total git tags
188
+ * @param options the changelog options
189
+ * @param showProgress whither show the progress bar
190
+ */
191
+ declare function getTotalChangelogMarkdown(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<string>;
192
+ /**
193
+ * generate the changelog markdown by two git tags
194
+ * @param options the changelog options
195
+ */
196
+ declare function generateChangelog(options?: Partial<ChangelogOption>): Promise<void>;
197
+ /**
198
+ * generate the changelog markdown by the total git tags
199
+ * @param options the changelog options
200
+ * @param showProgress whither show the progress bar
201
+ */
202
+ declare function generateTotalChangelog(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<void>;
203
+
204
+ export { type ChangelogOption, generateChangelog, generateTotalChangelog, getChangelogMarkdown, getTotalChangelogMarkdown };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * the commit author
3
+ */
4
+ interface GitCommitAuthor {
5
+ /**
6
+ * the author name
7
+ */
8
+ name: string;
9
+ /**
10
+ * the author email
11
+ */
12
+ email: string;
13
+ }
14
+ /**
15
+ * the raw git commit
16
+ */
17
+ interface RawGitCommit {
18
+ /**
19
+ * the commit message
20
+ */
21
+ message: string;
22
+ /**
23
+ * the commit body
24
+ */
25
+ body: string;
26
+ /**
27
+ * the commit hash
28
+ */
29
+ shortHash: string;
30
+ /**
31
+ * the commit author
32
+ */
33
+ author: GitCommitAuthor;
34
+ }
35
+ /**
36
+ * the reference of the commit
37
+ */
38
+ interface Reference {
39
+ /**
40
+ * the reference type
41
+ */
42
+ type: 'hash' | 'issue' | 'pull-request';
43
+ /**
44
+ * the reference value
45
+ */
46
+ value: string;
47
+ }
48
+ /**
49
+ * the resolved github author
50
+ */
51
+ interface ResolvedAuthor extends GitCommitAuthor {
52
+ /**
53
+ * the git commit of the author
54
+ */
55
+ commits: string[];
56
+ /**
57
+ * the github logged username of the author
58
+ */
59
+ login: string;
60
+ }
61
+ /**
62
+ * git commit config
63
+ */
64
+ interface GitCommit extends RawGitCommit {
65
+ /**
66
+ * the commit description
67
+ */
68
+ description: string;
69
+ /**
70
+ * the commit scope type
71
+ */
72
+ type: string;
73
+ /**
74
+ * the commit scope
75
+ */
76
+ scope: string;
77
+ /**
78
+ * the commit references
79
+ */
80
+ references: Reference[];
81
+ /**
82
+ * the commit authors
83
+ */
84
+ authors: GitCommitAuthor[];
85
+ /**
86
+ * the resolved authors
87
+ */
88
+ resolvedAuthors: ResolvedAuthor[];
89
+ /**
90
+ * the commit breaking changes
91
+ */
92
+ isBreaking: boolean;
93
+ }
94
+ /**
95
+ * github config
96
+ */
97
+ interface GithubConfig {
98
+ /**
99
+ * the github repository name
100
+ * @example soybeanjs/changelog
101
+ */
102
+ repo: string;
103
+ /**
104
+ * the github token
105
+ */
106
+ token: string;
107
+ }
108
+ interface ChangelogOption {
109
+ /**
110
+ * the directory of the project
111
+ * @default process.cwd()
112
+ */
113
+ cwd: string;
114
+ /**
115
+ * the commit scope types
116
+ */
117
+ types: Record<string, string>;
118
+ /**
119
+ * github config
120
+ */
121
+ github: GithubConfig;
122
+ /**
123
+ * the commit hash or tag
124
+ */
125
+ from: string;
126
+ /**
127
+ * the commit hash or tag
128
+ */
129
+ to: string;
130
+ /**
131
+ * the whole commit tags
132
+ */
133
+ tags: string[];
134
+ /**
135
+ * the commit tag and date map
136
+ */
137
+ tagDateMap: Map<string, string>;
138
+ /**
139
+ * Whether to capitalize the first letter of the commit type
140
+ */
141
+ capitalize: boolean;
142
+ /**
143
+ * Use emojis in section titles
144
+ * @default true
145
+ */
146
+ emoji: boolean;
147
+ /**
148
+ * the section titles
149
+ */
150
+ titles: {
151
+ /**
152
+ * the title of breaking changes section
153
+ */
154
+ breakingChanges: string;
155
+ };
156
+ /**
157
+ * the output file path of the changelog
158
+ */
159
+ output: string;
160
+ /**
161
+ * Whether to regenerate the changelog if it already exists
162
+ * @example the changelog already exists the content of v0.0.1, but you want to regenerate it
163
+ */
164
+ regenerate: boolean;
165
+ /**
166
+ * version from package.json, with prefix "v"
167
+ * @description if the options "to" is not specified, the version will be used
168
+ */
169
+ newVersion: string;
170
+ /**
171
+ * Mark the release as prerelease
172
+ */
173
+ prerelease?: boolean;
174
+ }
175
+
176
+ /**
177
+ * get the changelog markdown by two git tags
178
+ * @param options the changelog options
179
+ * @param showTitle whither show the title
180
+ */
181
+ declare function getChangelogMarkdown(options?: Partial<ChangelogOption>, showTitle?: boolean): Promise<{
182
+ markdown: string;
183
+ commits: GitCommit[];
184
+ options: ChangelogOption;
185
+ }>;
186
+ /**
187
+ * get the changelog markdown by the total git tags
188
+ * @param options the changelog options
189
+ * @param showProgress whither show the progress bar
190
+ */
191
+ declare function getTotalChangelogMarkdown(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<string>;
192
+ /**
193
+ * generate the changelog markdown by two git tags
194
+ * @param options the changelog options
195
+ */
196
+ declare function generateChangelog(options?: Partial<ChangelogOption>): Promise<void>;
197
+ /**
198
+ * generate the changelog markdown by the total git tags
199
+ * @param options the changelog options
200
+ * @param showProgress whither show the progress bar
201
+ */
202
+ declare function generateTotalChangelog(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<void>;
203
+
204
+ export { type ChangelogOption, generateChangelog, generateTotalChangelog, getChangelogMarkdown, getTotalChangelogMarkdown };
package/dist/index.d.ts CHANGED
@@ -1 +1,204 @@
1
- export * from "/Users/soybean/Web/Projects/SoybeanJS/changelog/src/index";
1
+ /**
2
+ * the commit author
3
+ */
4
+ interface GitCommitAuthor {
5
+ /**
6
+ * the author name
7
+ */
8
+ name: string;
9
+ /**
10
+ * the author email
11
+ */
12
+ email: string;
13
+ }
14
+ /**
15
+ * the raw git commit
16
+ */
17
+ interface RawGitCommit {
18
+ /**
19
+ * the commit message
20
+ */
21
+ message: string;
22
+ /**
23
+ * the commit body
24
+ */
25
+ body: string;
26
+ /**
27
+ * the commit hash
28
+ */
29
+ shortHash: string;
30
+ /**
31
+ * the commit author
32
+ */
33
+ author: GitCommitAuthor;
34
+ }
35
+ /**
36
+ * the reference of the commit
37
+ */
38
+ interface Reference {
39
+ /**
40
+ * the reference type
41
+ */
42
+ type: 'hash' | 'issue' | 'pull-request';
43
+ /**
44
+ * the reference value
45
+ */
46
+ value: string;
47
+ }
48
+ /**
49
+ * the resolved github author
50
+ */
51
+ interface ResolvedAuthor extends GitCommitAuthor {
52
+ /**
53
+ * the git commit of the author
54
+ */
55
+ commits: string[];
56
+ /**
57
+ * the github logged username of the author
58
+ */
59
+ login: string;
60
+ }
61
+ /**
62
+ * git commit config
63
+ */
64
+ interface GitCommit extends RawGitCommit {
65
+ /**
66
+ * the commit description
67
+ */
68
+ description: string;
69
+ /**
70
+ * the commit scope type
71
+ */
72
+ type: string;
73
+ /**
74
+ * the commit scope
75
+ */
76
+ scope: string;
77
+ /**
78
+ * the commit references
79
+ */
80
+ references: Reference[];
81
+ /**
82
+ * the commit authors
83
+ */
84
+ authors: GitCommitAuthor[];
85
+ /**
86
+ * the resolved authors
87
+ */
88
+ resolvedAuthors: ResolvedAuthor[];
89
+ /**
90
+ * the commit breaking changes
91
+ */
92
+ isBreaking: boolean;
93
+ }
94
+ /**
95
+ * github config
96
+ */
97
+ interface GithubConfig {
98
+ /**
99
+ * the github repository name
100
+ * @example soybeanjs/changelog
101
+ */
102
+ repo: string;
103
+ /**
104
+ * the github token
105
+ */
106
+ token: string;
107
+ }
108
+ interface ChangelogOption {
109
+ /**
110
+ * the directory of the project
111
+ * @default process.cwd()
112
+ */
113
+ cwd: string;
114
+ /**
115
+ * the commit scope types
116
+ */
117
+ types: Record<string, string>;
118
+ /**
119
+ * github config
120
+ */
121
+ github: GithubConfig;
122
+ /**
123
+ * the commit hash or tag
124
+ */
125
+ from: string;
126
+ /**
127
+ * the commit hash or tag
128
+ */
129
+ to: string;
130
+ /**
131
+ * the whole commit tags
132
+ */
133
+ tags: string[];
134
+ /**
135
+ * the commit tag and date map
136
+ */
137
+ tagDateMap: Map<string, string>;
138
+ /**
139
+ * Whether to capitalize the first letter of the commit type
140
+ */
141
+ capitalize: boolean;
142
+ /**
143
+ * Use emojis in section titles
144
+ * @default true
145
+ */
146
+ emoji: boolean;
147
+ /**
148
+ * the section titles
149
+ */
150
+ titles: {
151
+ /**
152
+ * the title of breaking changes section
153
+ */
154
+ breakingChanges: string;
155
+ };
156
+ /**
157
+ * the output file path of the changelog
158
+ */
159
+ output: string;
160
+ /**
161
+ * Whether to regenerate the changelog if it already exists
162
+ * @example the changelog already exists the content of v0.0.1, but you want to regenerate it
163
+ */
164
+ regenerate: boolean;
165
+ /**
166
+ * version from package.json, with prefix "v"
167
+ * @description if the options "to" is not specified, the version will be used
168
+ */
169
+ newVersion: string;
170
+ /**
171
+ * Mark the release as prerelease
172
+ */
173
+ prerelease?: boolean;
174
+ }
175
+
176
+ /**
177
+ * get the changelog markdown by two git tags
178
+ * @param options the changelog options
179
+ * @param showTitle whither show the title
180
+ */
181
+ declare function getChangelogMarkdown(options?: Partial<ChangelogOption>, showTitle?: boolean): Promise<{
182
+ markdown: string;
183
+ commits: GitCommit[];
184
+ options: ChangelogOption;
185
+ }>;
186
+ /**
187
+ * get the changelog markdown by the total git tags
188
+ * @param options the changelog options
189
+ * @param showProgress whither show the progress bar
190
+ */
191
+ declare function getTotalChangelogMarkdown(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<string>;
192
+ /**
193
+ * generate the changelog markdown by two git tags
194
+ * @param options the changelog options
195
+ */
196
+ declare function generateChangelog(options?: Partial<ChangelogOption>): Promise<void>;
197
+ /**
198
+ * generate the changelog markdown by the total git tags
199
+ * @param options the changelog options
200
+ * @param showProgress whither show the progress bar
201
+ */
202
+ declare function generateTotalChangelog(options?: Partial<ChangelogOption>, showProgress?: boolean): Promise<void>;
203
+
204
+ export { type ChangelogOption, generateChangelog, generateTotalChangelog, getChangelogMarkdown, getTotalChangelogMarkdown };
package/dist/index.mjs CHANGED
@@ -1,9 +1,19 @@
1
- import jiti from "file:///Users/soybean/Web/Projects/SoybeanJS/changelog/node_modules/.pnpm/jiti@1.19.3/node_modules/jiti/lib/index.js";
1
+ import{SingleBar as B,Presets as _}from"cli-progress";import{readFile as d,writeFile as N}from"fs/promises";import{ofetch as $}from"ofetch";import y from"dayjs";import{existsSync as S}from"fs";import{convert as I}from"convert-gitmoji";async function f(t,n,e){const{execa:o}=await import("execa");return(await o(t,n,e))?.stdout?.trim()||""}function z(t){return t!=null}function Y(t,...n){const e=new Array(n.length+1).fill(null).map(()=>[]);return t.forEach((o,a,s)=>{let r=0;for(const i of n){if(i(o,a,s)){e[r].push(o);return}r+=1}e[r].push(o)}),e}function F(t,n,e={}){for(const o of t){const a=o[n];e[a]=e[a]||[],e[a].push(o)}return e}function q(t){return t.charAt(0).toUpperCase()+t.slice(1)}function E(t,n=", ",e=" and "){return!t||t.length===0?"":t.length===1?t[0]:t.length===2?t.join(e):`${t.slice(0,-1).join(n)}${e}${t.slice(-1)}`}const v=/v\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?/,L=/## \[v\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?]/g,P=/release\sv\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?/;async function D(){return(await f("git",["--no-pager","tag","-l","--sort=creatordate"])).split(`
2
+ `)}async function U(){const t=await f("git",["--no-pager","log","--tags","--simplify-by-decoration","--pretty=format:%ci %d"]),n="tag: ",e=new Map;return t.split(`
3
+ `).filter(o=>o.includes(n)).forEach(o=>{const[a,s]=o.split(n),r=y(a).format("YYYY-MM-DD"),i=s.match(v)?.[0];i&&r&&e.set(i.trim(),r)}),e}function W(t){const n=[];return t.forEach((e,o)=>{o<t.length-1&&n.push({from:e,to:t[o+1]})}),n}async function C(t=0){const n=await D();return n[n.length+t-1]}async function K(){return await f("git",["rev-parse","--abbrev-ref","HEAD"])}async function J(){const t=await f("git",["tag","--points-at","HEAD"]),n=K();return t||n}async function Q(){const t=await f("git",["config","--get","remote.origin.url"]),n=t.match(/github\.com[/:]([\w\d._-]+?)\/([\w\d._-]+?)(\.git)?$/i);if(!n)throw new Error(`Can not parse GitHub repo from url ${t}`);return`${n[1]}/${n[2]}`}function X(t){return!/^[^.]*[\d.]+$/.test(t)}function Z(){return f("git",["rev-list","--max-parents=0","HEAD"])}async function tt(t,n="HEAD"){return(await f("git",["--no-pager","log",`${t?`${t}...`:""}${n}`,'--pretty="----%n%s|%h|%an|%ae%n%b"',"--name-status"])).split(`----
4
+ `).splice(1).map(e=>{const[o,...a]=e.split(`
5
+ `),[s,r,i,p]=o.split("|");return{message:s,shortHash:r,author:{name:i,email:p},body:a.join(`
6
+ `)}})}function et(t){const n=/(?<type>[a-z]+)(\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i,e=/co-authored-by:\s*(?<name>.+)(<(?<email>.+)>)/gim,o=/\([a-z]*(#\d+)\s*\)/gm,a=/(#\d+)/gm,s=t.message.match(n);if(!s?.groups)return null;const r=s.groups.type,i=s.groups.scope||"",p=!!s.groups.breaking;let g=s.groups.description;const c=[];for(const l of g.matchAll(o))c.push({type:"pull-request",value:l[1]});for(const l of g.matchAll(a))c.some(h=>h.value===l[1])||c.push({type:"issue",value:l[1]});c.push({value:t.shortHash,type:"hash"}),g=g.replace(o,"").trim();const m=[t.author],u=t.body.matchAll(e);for(const l of u){const{name:h="",email:R=""}=l.groups||{},V={name:h.trim(),email:R.trim()};m.push(V)}return{...t,authors:m,resolvedAuthors:[],description:g,type:r,scope:i,references:c,isBreaking:p}}async function A(t,n="HEAD"){return(await tt(t,n)).map(e=>et(e)).filter(z)}function k(t){return{accept:"application/vnd.github.v3+json",authorization:`token ${t}`}}async function nt(t,n,e){let o="";try{o=(await $(`https://ungh.cc/users/find/${e}`))?.user?.username||""}catch(r){console.log("e: ",r)}if(o)return o;const{repo:a,token:s}=t;if(!s)return o;if(n.length)try{o=(await $(`https://api.github.com/repos/${a}/commits/${n[0]}`,{headers:k(s)}))?.author?.login||""}catch(r){console.log("e: ",r)}if(o)return o;try{o=(await $(`https://api.github.com/search/users?q=${encodeURIComponent(e)}`,{headers:k(s)})).items[0].login}catch(r){console.log("e: ",r)}return o}async function M(t,n,e){const o=[],a=new Map;for await(const s of t){const r=[];for await(const[p,g]of s.authors.entries()){const{email:c,name:m}=g;if(c&&m){const u=[];p===0&&u.push(s.shortHash);const l={name:m,email:c,commits:u,login:""};if(e?.has(c)){const h=e?.get(c)||"";l.login=h}else{const h=await nt(n,u,c);l.login=h,e?.set(c,h)}r.push(l),a.has(c)||a.set(c,l)}}const i={...s,resolvedAuthors:r};o.push(i)}return{commits:o,contributors:Array.from(a.values())}}function ot(){return{cwd:process.cwd(),types:{feat:"\u{1F680} Features",fix:"\u{1F41E} Bug Fixes",perf:"\u{1F525} Performance",refactor:"\u{1F485} Refactors",docs:"\u{1F4D6} Documentation",build:"\u{1F4E6} Build",types:"\u{1F30A} Types",chore:"\u{1F3E1} Chore",examples:"\u{1F3C0} Examples",test:"\u2705 Tests",style:"\u{1F3A8} Styles",ci:"\u{1F916} CI"},github:{repo:"",token:process.env.GITHUB_TOKEN||""},from:"",to:"",tags:[],tagDateMap:new Map,capitalize:!1,emoji:!0,titles:{breakingChanges:"\u{1F6A8} Breaking Changes"},output:"CHANGELOG.md",regenerate:!1,newVersion:""}}async function st(t){let n="";try{const e=await d(`${t}/package.json`,"utf-8");n=JSON.parse(e)?.version||""}catch{}return{newVersion:n}}async function w(t){var n;const e=ot();Object.assign(e,t);const{newVersion:o}=await st(e.cwd);if((n=e.github).repo||(n.repo=await Q()),e.newVersion||(e.newVersion=`v${o}`),e.from||(e.from=await C()),e.to||(e.to=await J()),e.to===e.from){const a=await C(-1),s=await Z();e.from=a||s}return e.tags=await D(),e.tagDateMap=await U(),e.prerelease=e.prerelease??X(e.to),e}function T(t,n,e){const o=t.filter(s=>e==="issues"?s.type==="issue"||s.type==="pull-request":s.type==="hash").map(s=>n?s.type==="pull-request"||s.type==="issue"?`https://github.com/${n}/issues/${s.value.slice(1)}`:`[<samp>(${s.value.slice(0,5)})</samp>](https://github.com/${n}/commit/${s.value})`:s.value),a=E(o).trim();return e==="issues"?a&&`in ${a}`:a}function at(t,n){const e=T(t.references,n.github.repo,"issues"),o=T(t.references,n.github.repo,"hash");let a=E([...new Set(t.resolvedAuthors.map(r=>r.login?`@${r.login}`:`**${r.name}**`))]).trim();a&&(a=`by ${a}`);let s=[a,e,o].filter(r=>r?.trim()).join(" ");return s&&(s=`&nbsp;-&nbsp; ${s}`),[n.capitalize?q(t.description):t.description,s].filter(r=>r?.trim()).join(" ")}function rt(t,n){const e=/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g;let o=t.trim();return n.emoji||(o=t.replace(e,"").trim()),`### &nbsp;&nbsp;&nbsp;${o}`}function G(t,n,e){if(!t.length)return[];const o=["",rt(n,e),""],a=F(t,"scope");let s=!0;return Object.entries(a).some(([r,i])=>r&&i.length>1)||(s=!1),Object.keys(a).sort().forEach(r=>{let i="",p="";const g=`**${r}**`;r&&s?(o.push(`- ${g}:`),i=" "):r&&(p=`${g}: `),o.push(...a[r].reverse().map(c=>`${i}- ${p}${at(c,e)}`))}),o}function j(t){return`https://github.com/${t}`}function it(t){return`${j(t)}.png?size=48`}function ct(t){let n="",e="";return t.forEach((o,a)=>{const{name:s,email:r,login:i}=o;if(i){const p=j(i),g=it(i);n+=`[![${i}](${g})](${p})&nbsp;&nbsp;`}else{let p=`[${s}](mailto:${r})`;a<t.length-1&&(p+=",&nbsp;"),e+=p}}),`${n}
7
+ ${e}`}function H(t){const{options:n,showTitle:e,contributors:o}=t,a=t.commits.filter(u=>u.description.match(P)===null),s=[],r=!v.test(n.to),i=r?n.newVersion:n.to,p=`https://github.com/${n.github.repo}/compare/${n.from}...${i}`;if(e){const u=r?y().format("YY-MM-DD"):n.tagDateMap.get(n.to);let l=`## [${i}](${p})`;u&&(l+=` (${u})`),s.push(l)}const[g,c]=Y(a,u=>u.isBreaking),m=F(c,"type");s.push(...G(g,n.titles.breakingChanges,n));for(const u of Object.keys(n.types)){const l=m[u]||[];s.push(...G(l,n.types[u],n))}if(s.length||s.push("*No significant changes*"),e||s.push("",`##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](${p})`),e){s.push("","### &nbsp;&nbsp;&nbsp;\u2764\uFE0F Contributors","");const u=ct(o);s.push(u)}return I(s.join(`
8
+ `).trim(),!0)}async function ut(t,n){let e=!1;const o=await d(n,"utf8");if(o){const a=o.match(L);if(a?.length){const s=`## [${t}]`;e=a.includes(s)}}return e}async function x(t,n,e=!1){let o;const a="# Changelog";!e&&S(n)?(o=await d(n,"utf8"),o.startsWith(a)||(o=`${a}
2
9
 
3
- /** @type {import("/Users/soybean/Web/Projects/SoybeanJS/changelog/src/index")} */
4
- const _module = jiti(null, { interopDefault: true, esmResolve: true })("/Users/soybean/Web/Projects/SoybeanJS/changelog/src/index.ts");
10
+ ${o}`)):o=`${a}
5
11
 
6
- export const getChangelogMarkdown = _module.getChangelogMarkdown;
7
- export const getTotalChangelogMarkdown = _module.getTotalChangelogMarkdown;
8
- export const generateChangelog = _module.generateChangelog;
9
- export const generateTotalChangelog = _module.generateTotalChangelog;
12
+ `;const s=o.match(/^###?\s+.*$/m);s?o=`${o.slice(0,s.index)+t}
13
+
14
+ ${o.slice(s.index)}`:o+=`
15
+ ${t}
16
+
17
+ `,await N(n,o)}async function b(t,n=!0){const e=await w(t),o=await A(e.from,e.to),{commits:a,contributors:s}=await M(o,e.github);return{markdown:H({commits:a,options:e,showTitle:n,contributors:s}),commits:a,options:e}}async function O(t,n=!0){const e=await w(t);let o=null;n&&(o=new B({format:"generate total changelog: [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}"},_.shades_classic));const a=W(e.tags);if(a.length===0){const{markdown:i}=await b(e);return i}o?.start(a.length,0);let s="";const r=new Map;for await(const[i,p]of a.entries()){const{from:g,to:c}=p,m=await A(g,c),{commits:u,contributors:l}=await M(m,e.github,r);s=`${H({commits:u,options:{...e,from:g,to:c},showTitle:!0,contributors:l})}
18
+
19
+ ${s}`,o?.update(i+1)}return o?.stop(),s}async function pt(t){const n=await w(t),e=await ut(n.to,n.output);if(!n.regenerate&&e)return;const{markdown:o}=await b(n);await x(o,n.output,n.regenerate)}async function lt(t,n=!0){const e=await w(t),o=await O(e,n);await x(o,e.output,!0)}export{pt as generateChangelog,lt as generateTotalChangelog,b as getChangelogMarkdown,O as getTotalChangelogMarkdown};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soybeanjs/changelog",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "generate changelog form git tags and commits for github",
5
5
  "author": {
6
6
  "name": "Soybean",
@@ -36,18 +36,18 @@
36
36
  "convert-gitmoji": "0.1.3",
37
37
  "dayjs": "1.11.9",
38
38
  "execa": "8.0.1",
39
- "ofetch": "1.2.0"
39
+ "ofetch": "1.2.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@soybeanjs/cli": "0.6.7",
42
+ "@soybeanjs/cli": "0.7.1",
43
43
  "@types/cli-progress": "3.11.0",
44
- "@types/node": "20.5.2",
44
+ "@types/node": "20.5.3",
45
45
  "eslint": "8.47.0",
46
- "eslint-config-soybeanjs": "0.5.5",
46
+ "eslint-config-soybeanjs": "0.5.6",
47
47
  "simple-git-hooks": "2.9.0",
48
48
  "tsx": "3.12.7",
49
49
  "typescript": "5.1.6",
50
- "unbuild": "1.2.1"
50
+ "unbuild": "2.0.0"
51
51
  },
52
52
  "simple-git-hooks": {
53
53
  "commit-msg": "pnpm soy git-commit-verify",