@furystack/yarn-plugin-changelog 1.0.0 → 1.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/CHANGELOG.md +30 -0
- package/bundles/@yarnpkg/plugin-changelog.js +19 -19
- package/package.json +8 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.1] - 2026-01-26
|
|
4
|
+
|
|
5
|
+
### 🔧 Chores
|
|
6
|
+
|
|
7
|
+
- Standardized author format, improved keywords, removed obsolete `gitHead`, added `engines` (Node 22+) and `sideEffects: false`
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-01-22
|
|
10
|
+
|
|
11
|
+
### 💥 Breaking Changes
|
|
12
|
+
|
|
13
|
+
### Initial release - Yarn plugin for automated changelog management
|
|
14
|
+
|
|
15
|
+
This new package provides a Yarn plugin that automates changelog generation and management in monorepos. It introduces three commands:
|
|
16
|
+
|
|
17
|
+
### ✨ Features
|
|
18
|
+
|
|
19
|
+
- **`yarn changelog create`** - Generates changelog drafts from `.yarn/versions/*.yml` version manifests. Supports `--dependabot` flag for auto-filling dependency update entries and `--force` to regenerate mismatched changelogs.
|
|
20
|
+
|
|
21
|
+
- **`yarn changelog check`** - Validates existing changelog drafts for correctness and completeness.
|
|
22
|
+
|
|
23
|
+
- **`yarn changelog apply`** - Applies changelog drafts to each package's `CHANGELOG.md` file, merging multiple entries and deduplicating content.
|
|
24
|
+
|
|
25
|
+
The plugin supports:
|
|
26
|
+
|
|
27
|
+
- Automatic template generation based on version bump type (patch/minor/major)
|
|
28
|
+
- Conventional commit style sections (Features, Bug Fixes, Documentation, etc.)
|
|
29
|
+
- Merging of multiple changelog chunks from different PRs
|
|
30
|
+
- Validation to ensure changelogs match version manifests
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
name: "@yarnpkg/plugin-changelog",
|
|
5
5
|
factory: function (require) {
|
|
6
|
-
"use strict";var plugin=(()=>{var z=Object.defineProperty;var
|
|
6
|
+
"use strict";var plugin=(()=>{var z=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ge=Object.prototype.hasOwnProperty;var N=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,o)=>(typeof require<"u"?require:t)[o]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var pe=(e,t)=>{for(var o in t)z(e,o,{get:t[o],enumerable:!0})},he=(e,t,o,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of le(t))!ge.call(e,i)&&i!==o&&z(e,i,{get:()=>t[i],enumerable:!(s=ce(t,i))||s.enumerable});return e};var de=e=>he(z({},"__esModule",{value:!0}),e);var $e={};pe($e,{default:()=>Oe});var se=N("@yarnpkg/cli"),M=N("@yarnpkg/core"),f=N("@yarnpkg/fslib"),$=N("clipanion");function W(e,t,o){let s=`## [${t}] - ${o}
|
|
7
7
|
|
|
8
8
|
`;for(let i of e.sections)i.isEmpty||(s+=`### ${i.name}
|
|
9
9
|
`,s+=`${i.content.trim()}
|
|
10
10
|
|
|
11
|
-
`);return s}var
|
|
12
|
-
`)[0].trim();return/^#{2,}/.test(o)?"heading":/^[-*+]/.test(o)||/^\d+\./.test(o)?"list":"other"}function fe(e){let t=e.trim();return/^[-*+]/.test(t)||/^\d+\./.test(t)}function
|
|
11
|
+
`);return s}var Z={heading:1,other:2,list:3};function me(e){let t=e.trim();if(!t)return"other";let o=t.split(`
|
|
12
|
+
`)[0].trim();return/^#{2,}/.test(o)?"heading":/^[-*+]/.test(o)||/^\d+\./.test(o)?"list":"other"}function fe(e){let t=e.trim();return/^[-*+]/.test(t)||/^\d+\./.test(t)}function ee(e){if(e.length===0)return"";let t=e.map(l=>({content:l.trim(),type:me(l)}));t.sort((l,h)=>Z[l.type]-Z[h.type]);let o=t.filter(l=>l.type!=="list"),s=t.filter(l=>l.type==="list"),i=[];for(let l of o)i.push(l.content);if(s.length>0){let l=[];for(let h of s){let a=h.content.split(`
|
|
13
13
|
`);for(let r of a)r.trim()&&(fe(r)||/^\s+/.test(r))&&l.push(r)}l.length>0&&i.push(l.join(`
|
|
14
14
|
`))}return i.join(`
|
|
15
15
|
|
|
16
|
-
`)}var
|
|
17
|
-
`:"",isEmpty:!c}});return{packageName:t,versionType:s,sections:h,hasPlaceholders:o}}var n={BREAKING_CHANGES:"\u{1F4A5} Breaking Changes",DEPRECATED:"\u{1F5D1}\uFE0F Deprecated",FEATURES:"\u2728 Features",BUG_FIXES:"\u{1F41B} Bug Fixes",DOCUMENTATION:"\u{1F4DA} Documentation",PERFORMANCE:"\u26A1 Performance",REFACTORING:"\u267B\uFE0F Refactoring",TESTS:"\u{1F9EA} Tests",BUILD:"\u{1F4E6} Build",CI:"\u{1F477} CI",DEPENDENCIES:"\u2B06\uFE0F Dependencies",CHORES:"\u{1F527} Chores"};function G(e,t={}){let o=[];return t.expectedVersionType&&e.versionType!==t.expectedVersionType&&o.push(`Version type mismatch: changelog has "${e.versionType}" but manifest expects "${t.expectedVersionType}". Run 'yarn changelog create --force' to regenerate.`),e.versionType==="major"&&!e.sections.some(i=>i.name===n.BREAKING_CHANGES&&!i.isEmpty)&&o.push(`Major release requires filled "${n.BREAKING_CHANGES}" section`),e.sections.filter(i=>!i.isEmpty).length===0&&o.push("At least one section must have content"),o}function
|
|
16
|
+
`)}var te={major:3,minor:2,patch:1};function Y(e){if(e.length===0)return{packageName:"",versionType:"patch",sections:[],hasPlaceholders:!1};if(e.length===1)return e[0];let{packageName:t}=e[0],o=e.some(a=>a.hasPlaceholders),s=e.reduce((a,r)=>{let c=te[r.versionType]??0,m=te[a]??0;return c>m?r.versionType:a},"patch"),i=new Map,l=[];for(let a of e)for(let r of a.sections){i.has(r.name)||(i.set(r.name,[]),l.push(r.name));let c=r.content.trim();if(!c)continue;let m=i.get(r.name);m.some(p=>p.trim().toLowerCase()===c.toLowerCase())||m.push(c)}let h=l.map(a=>{let r=i.get(a)??[],c=ee(r);return{name:a,content:c?`${c}
|
|
17
|
+
`:"",isEmpty:!c}});return{packageName:t,versionType:s,sections:h,hasPlaceholders:o}}var n={BREAKING_CHANGES:"\u{1F4A5} Breaking Changes",DEPRECATED:"\u{1F5D1}\uFE0F Deprecated",FEATURES:"\u2728 Features",BUG_FIXES:"\u{1F41B} Bug Fixes",DOCUMENTATION:"\u{1F4DA} Documentation",PERFORMANCE:"\u26A1 Performance",REFACTORING:"\u267B\uFE0F Refactoring",TESTS:"\u{1F9EA} Tests",BUILD:"\u{1F4E6} Build",CI:"\u{1F477} CI",DEPENDENCIES:"\u2B06\uFE0F Dependencies",CHORES:"\u{1F527} Chores"};function G(e,t={}){let o=[];return t.expectedVersionType&&e.versionType!==t.expectedVersionType&&o.push(`Version type mismatch: changelog has "${e.versionType}" but manifest expects "${t.expectedVersionType}". Run 'yarn changelog create --force' to regenerate.`),e.versionType==="major"&&!e.sections.some(i=>i.name===n.BREAKING_CHANGES&&!i.isEmpty)&&o.push(`Major release requires filled "${n.BREAKING_CHANGES}" section`),e.sections.filter(i=>!i.isEmpty).length===0&&o.push("At least one section must have content"),o}function ne(e,t){let o=[];return e||o.push(`${t}: Missing package name heading. Expected a heading like "# @furystack/package-name" at the start of the file.`),{isValid:o.length===0,errors:o}}function oe(e,t){let o=e.versionType!==t,i=G(e,{expectedVersionType:t}).filter(l=>!l.includes("Version type mismatch"));return{shouldRegenerate:o||i.length>0,hasVersionMismatch:o,contentErrors:i}}var ue="patch",Ee="<!-- PLACEHOLDER:",ye=/<!-- version-type: (\w+) -->/,Ce=/^# (.+)$/m,Pe=/^## (.+)$/;function A(e){let t=e.split(`
|
|
18
18
|
`),s=e.match(ye)?.[1]??ue,l=e.match(Ce)?.[1]??"",h=e.includes(Ee),a=[],r=null;for(let c of t){let m=c.match(Pe);m?(r&&a.push(r),r={name:m[1],content:"",isEmpty:!0}):r&&!c.trim().startsWith("<!--")&&(r.content+=`${c}
|
|
19
|
-
`,c.trim()&&(r.isEmpty=!1))}return r&&a.push(r),{packageName:l,versionType:s,sections:a,hasPlaceholders:h}}var I=".yarn/changelogs",F=".yarn/versions";var
|
|
19
|
+
`,c.trim()&&(r.isEmpty=!1))}return r&&a.push(r),{packageName:l,versionType:s,sections:a,hasPlaceholders:h}}var I=".yarn/changelogs",F=".yarn/versions";var X="0.0.0",J="# Changelog",_=class extends se.BaseCommand{static paths=[["changelog","apply"]];static usage=$.Command.Usage({description:"Apply changelog entries to package CHANGELOG.md files",details:`
|
|
20
20
|
This command:
|
|
21
21
|
- Reads all changelog drafts from \`.yarn/changelogs/\`
|
|
22
22
|
- Groups entries by package name
|
|
@@ -26,16 +26,16 @@ factory: function (require) {
|
|
|
26
26
|
|
|
27
27
|
`),!await f.xfs.existsPromise(s))return this.context.stdout.write(`No .yarn/changelogs directory found. Nothing to apply.
|
|
28
28
|
`),0;let l=(await f.xfs.readdirPromise(s)).filter(p=>p.endsWith(".md"));if(l.length===0)return this.context.stdout.write(`No changelog drafts found. Nothing to apply.
|
|
29
|
-
`),0;let h=[],a=[];for(let p of l){let d=f.ppath.join(s,p),C=await f.xfs.readFilePromise(d,"utf8"),g=A(C),E=
|
|
29
|
+
`),0;let h=[],a=[];for(let p of l){let d=f.ppath.join(s,p),C=await f.xfs.readFilePromise(d,"utf8"),g=A(C),E=ne(g.packageName,p);if(!E.isValid){a.push(...E.errors);continue}h.push({path:d,filename:p,packageName:g.packageName,content:C})}if(a.length>0){this.context.stderr.write(`Validation errors found:
|
|
30
30
|
`);for(let p of a)this.context.stderr.write(` \u2717 ${p}
|
|
31
31
|
`);this.context.stderr.write(`
|
|
32
32
|
Invalid drafts were skipped and not deleted.
|
|
33
33
|
|
|
34
|
-
`)}let r=new Map;for(let p of h){let d=r.get(p.packageName)??[];d.push(p),r.set(p.packageName,d)}let c=new Date().toISOString().split("T")[0],m=0;for(let[p,d]of r){let C=o.workspaces.find(y=>y.manifest.raw.name===p),g,E;if(C)g=C.cwd,E=C.manifest.version??
|
|
34
|
+
`)}let r=new Map;for(let p of h){let d=r.get(p.packageName)??[];d.push(p),r.set(p.packageName,d)}let c=new Date().toISOString().split("T")[0],m=0;for(let[p,d]of r){let C=o.workspaces.find(y=>y.manifest.raw.name===p),g,E;if(C)g=C.cwd,E=C.manifest.version??X;else{let y=p.replace(/^@[^/]+\//,"");g=f.ppath.join(o.cwd,`packages/${y}`);let O=f.ppath.join(g,"package.json");await f.xfs.existsPromise(O)?E=JSON.parse(await f.xfs.readFilePromise(O,"utf8")).version??X:E=X}if(!await f.xfs.existsPromise(g))throw new Error(`Package directory not found: ${g}
|
|
35
35
|
Package '${p}' has changelog entries but no workspace directory exists.
|
|
36
|
-
This may indicate the package was deleted or uses a non-standard directory structure.`);let R=f.ppath.join(g,"CHANGELOG.md"),T="";await f.xfs.existsPromise(R)&&(T=await f.xfs.readFilePromise(R,"utf8"));let b=d.map(y=>A(y.content)),k=
|
|
36
|
+
This may indicate the package was deleted or uses a non-standard directory structure.`);let R=f.ppath.join(g,"CHANGELOG.md"),T="";await f.xfs.existsPromise(R)&&(T=await f.xfs.readFilePromise(R,"utf8"));let b=d.map(y=>A(y.content)),k=Y(b),P=W(k,E,c),x,S=new RegExp(`^${J}(?:\\r?\\n)+`);if(T){let y=T.match(S);if(y){let O=y[0].length;x=T.slice(0,O)+P+T.slice(O)}else x=`${J}
|
|
37
37
|
|
|
38
|
-
${P}${T}`}else x=`${
|
|
38
|
+
${P}${T}`}else x=`${J}
|
|
39
39
|
|
|
40
40
|
${P}`;if(this.context.stdout.write(`Applying ${d.length} entry(ies) to ${p}
|
|
41
41
|
`),this.dryRun){if(this.verbose){this.context.stdout.write(` Would write to: ${R}
|
|
@@ -43,8 +43,8 @@ ${P}`;if(this.context.stdout.write(`Applying ${d.length} entry(ies) to ${p}
|
|
|
43
43
|
`)}}else{await f.xfs.writeFilePromise(R,x);for(let y of d)await f.xfs.unlinkPromise(y.path),this.verbose&&this.context.stdout.write(` Deleted: ${y.filename}
|
|
44
44
|
`)}m+=d.length}let D=this.dryRun?"Would apply":"Applied";return this.context.stdout.write(`
|
|
45
45
|
${D} ${m} changelog entry(ies) to ${r.size} package(s).
|
|
46
|
-
`),a.length>0?1:0}};var
|
|
47
|
-
`),i=!1;for(let l of s){let h=l.trim();if(h==="releases:"){i=!0;continue}if(i&&h){let a=h.match(/^["']?([^"':]+)["']?\s*:\s*(patch|minor|major)\s*$/);if(a){let r=a[1],c=a[2];Ne(c)&&o.push({packageName:r,versionType:c})}}}return{id:Re(t),path:t,releases:o}}function
|
|
46
|
+
`),a.length>0?1:0}};var ie=N("@yarnpkg/cli"),H=N("@yarnpkg/core"),w=N("@yarnpkg/fslib"),U=N("clipanion");function Ne(e){return e==="patch"||e==="minor"||e==="major"}function V(e,t){let o=[],s=e.split(`
|
|
47
|
+
`),i=!1;for(let l of s){let h=l.trim();if(h==="releases:"){i=!0;continue}if(i&&h){let a=h.match(/^["']?([^"':]+)["']?\s*:\s*(patch|minor|major)\s*$/);if(a){let r=a[1],c=a[2];Ne(c)&&o.push({packageName:r,versionType:c})}}}return{id:Re(t),path:t,releases:o}}function re(e){return e.replace(/\//g,"-")}function Re(e){return(e.split("/").pop()??"").replace(".yml","")}var Te="Updated dependencies",we=`<!--
|
|
48
48
|
FORMATTING GUIDE:
|
|
49
49
|
|
|
50
50
|
### Detailed Entry (appears first when merging)
|
|
@@ -62,13 +62,13 @@ appear before simple list items within each section.
|
|
|
62
62
|
<!-- PLACEHOLDER: ${o} -->`;return t&&(s+=`
|
|
63
63
|
${De}`),s}function be(e){return(e==="major"?Se:e==="minor"?Ae:ve).map(o=>{let s=o===n.BREAKING_CHANGES;return Ie(o,s)}).join(`
|
|
64
64
|
|
|
65
|
-
`)}function
|
|
65
|
+
`)}function q(e,t){let o=be(t);return`<!-- version-type: ${t} -->
|
|
66
66
|
# ${e}
|
|
67
67
|
|
|
68
68
|
${we}
|
|
69
69
|
|
|
70
70
|
${o}
|
|
71
|
-
`}function L(e,t){return`${
|
|
71
|
+
`}function L(e,t){return`${re(e)}.${t}.md`}function Q(e,t,o){let s=o||Te;return t==="major"?`<!-- version-type: ${t} -->
|
|
72
72
|
# ${e}
|
|
73
73
|
|
|
74
74
|
## ${n.BREAKING_CHANGES}
|
|
@@ -81,7 +81,7 @@ ${o}
|
|
|
81
81
|
|
|
82
82
|
## ${n.DEPENDENCIES}
|
|
83
83
|
- ${s}
|
|
84
|
-
`}var j=class extends
|
|
84
|
+
`}var j=class extends ie.BaseCommand{static paths=[["changelog","check"]];static usage=U.Command.Usage({description:"Validate changelog entries for all version manifests",details:`
|
|
85
85
|
This command validates that:
|
|
86
86
|
- Every release in \`.yarn/versions/*.yml\` has a changelog file
|
|
87
87
|
- Major releases have filled BREAKING CHANGES sections
|
|
@@ -98,7 +98,7 @@ Changelog validation failed:
|
|
|
98
98
|
Found ${a.length} error(s).
|
|
99
99
|
`),1}return this.context.stdout.write(`
|
|
100
100
|
\u2713 All ${r} changelog(s) are valid.
|
|
101
|
-
`),0}};var
|
|
101
|
+
`),0}};var ae=N("@yarnpkg/cli"),K=N("@yarnpkg/core"),u=N("@yarnpkg/fslib"),v=N("clipanion");var B=class extends ae.BaseCommand{static paths=[["changelog","create"]];static usage=v.Command.Usage({description:"Generate changelog drafts from version manifests",details:`
|
|
102
102
|
This command reads all version manifests in \`.yarn/versions/*.yml\`
|
|
103
103
|
and generates draft changelog files in \`.yarn/changelogs/\`.
|
|
104
104
|
|
|
@@ -112,13 +112,13 @@ Found ${a.length} error(s).
|
|
|
112
112
|
`,examples:[["Generate changelog drafts","yarn changelog create"],["Regenerate mismatched/invalid changelogs","yarn changelog create --force"],["Generate for Dependabot PR","yarn changelog create --dependabot"],["Generate with custom message",'yarn changelog create --dependabot -m "Bump lodash from 4.17.20 to 4.17.21"']]});verbose=v.Option.Boolean("-v,--verbose",!1,{description:"Show verbose output"});force=v.Option.Boolean("-f,--force",!1,{description:"Regenerate changelogs with mismatched version types or invalid entries"});dependabot=v.Option.Boolean("--dependabot",!1,{description:"Auto-fill changelog for dependency updates (Dependabot PRs)"});message=v.Option.String("-m,--message",{description:"Custom message for the changelog entry (used with --dependabot)"});async execute(){let t=await K.Configuration.find(this.context.cwd,this.context.plugins),{project:o}=await K.Project.find(t,this.context.cwd),s=u.ppath.join(o.cwd,F),i=u.ppath.join(o.cwd,I);if(await u.xfs.mkdirPromise(i,{recursive:!0}),!await u.xfs.existsPromise(s))return this.context.stdout.write(`No .yarn/versions directory found. Nothing to do.
|
|
113
113
|
`),0;let h=(await u.xfs.readdirPromise(s)).filter(D=>D.endsWith(".yml"));if(h.length===0)return this.context.stdout.write(`No version manifests found. Nothing to do.
|
|
114
114
|
`),0;let a=0,r=0,c=0;for(let D of h){let p=u.ppath.join(s,D),d=await u.xfs.readFilePromise(p,"utf8"),C=V(d,p);this.verbose&&this.context.stdout.write(`Processing manifest: ${D}
|
|
115
|
-
`);for(let g of C.releases){let E=L(g.packageName,C.id),R=u.ppath.join(i,E);if(await u.xfs.existsPromise(R)){let b=await u.xfs.readFilePromise(R,"utf8"),k=A(b),P=
|
|
115
|
+
`);for(let g of C.releases){let E=L(g.packageName,C.id),R=u.ppath.join(i,E);if(await u.xfs.existsPromise(R)){let b=await u.xfs.readFilePromise(R,"utf8"),k=A(b),P=oe(k,g.versionType);if(this.force&&P.shouldRegenerate){let x=this.dependabot?Q(g.packageName,g.versionType,this.message):q(g.packageName,g.versionType);await u.xfs.writeFilePromise(R,x);let S=[];P.hasVersionMismatch&&S.push(`${k.versionType} \u2192 ${g.versionType}`),P.contentErrors.length>0&&S.push(...P.contentErrors),this.context.stdout.write(` Regenerated: ${E} (${S.join(", ")})
|
|
116
116
|
`),r++;continue}if(this.verbose)if(P.shouldRegenerate){let x=[];P.hasVersionMismatch&&x.push(`version mismatch: ${k.versionType} vs ${g.versionType}`),P.contentErrors.length>0&&x.push(...P.contentErrors.map(S=>S.toLowerCase())),this.context.stdout.write(` Skipping ${g.packageName} (${x.join("; ")}, use --force to regenerate)
|
|
117
117
|
`)}else this.context.stdout.write(` Skipping ${g.packageName} (already exists)
|
|
118
|
-
`);c++;continue}let T=this.dependabot?
|
|
118
|
+
`);c++;continue}let T=this.dependabot?Q(g.packageName,g.versionType,this.message):q(g.packageName,g.versionType);await u.xfs.writeFilePromise(R,T),this.context.stdout.write(` Created: ${E} (${g.versionType})
|
|
119
119
|
`),a++}}let m=[`Created ${a}`];return r>0&&m.push(`regenerated ${r}`),m.push(`skipped ${c}`),this.context.stdout.write(`
|
|
120
120
|
Done! ${m.join(", ")} changelog draft(s).
|
|
121
|
-
`),0}};var ke={commands:[B,j,_]},Oe=ke;return
|
|
121
|
+
`),0}};var ke={commands:[B,j,_]},Oe=ke;return de($e);})();
|
|
122
122
|
return plugin;
|
|
123
123
|
}
|
|
124
124
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/yarn-plugin-changelog",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Yarn plugin for managing changelogs from version manifests",
|
|
5
5
|
"license": "GPL-2.0",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./bundles/@yarnpkg/plugin-changelog.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"bundles"
|
|
9
9
|
],
|
|
@@ -44,5 +44,9 @@
|
|
|
44
44
|
"@yarnpkg/builder": "^4.2.3",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"vitest": "^4.0.17"
|
|
47
|
-
}
|
|
48
|
-
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22.0.0"
|
|
50
|
+
},
|
|
51
|
+
"sideEffects": false
|
|
52
|
+
}
|