relion 0.2.0 → 0.4.0

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.
Files changed (42) hide show
  1. package/dist/cli.js +1 -0
  2. package/dist/index.d.ts +230 -0
  3. package/dist/index.js +27 -0
  4. package/package.json +34 -53
  5. package/src/cli.js +0 -6
  6. package/src/commands.js +0 -176
  7. package/src/defaults.js +0 -60
  8. package/src/index.js +0 -75
  9. package/src/lib/checkpoint.js +0 -23
  10. package/src/lib/configuration.js +0 -35
  11. package/src/lib/detect-package-manager.js +0 -52
  12. package/src/lib/format-commit-message.js +0 -4
  13. package/src/lib/latest-semver-tag.js +0 -34
  14. package/src/lib/lifecycles/bump.js +0 -242
  15. package/src/lib/lifecycles/changelog.js +0 -106
  16. package/src/lib/lifecycles/commit.js +0 -67
  17. package/src/lib/lifecycles/tag.js +0 -61
  18. package/src/lib/print-error.js +0 -15
  19. package/src/lib/run-exec.js +0 -20
  20. package/src/lib/run-execFile.js +0 -20
  21. package/src/lib/run-lifecycle-script.js +0 -18
  22. package/src/lib/stringify-package.js +0 -34
  23. package/src/lib/updaters/index.js +0 -130
  24. package/src/lib/updaters/types/csproj.js +0 -13
  25. package/src/lib/updaters/types/gradle.js +0 -16
  26. package/src/lib/updaters/types/json.js +0 -25
  27. package/src/lib/updaters/types/maven.js +0 -43
  28. package/src/lib/updaters/types/openapi.js +0 -15
  29. package/src/lib/updaters/types/plain-text.js +0 -7
  30. package/src/lib/updaters/types/python.js +0 -30
  31. package/src/lib/updaters/types/yaml.js +0 -15
  32. package/src/lib/write-file.js +0 -6
  33. package/src/preset/constants.js +0 -16
  34. package/src/preset/index.js +0 -19
  35. package/src/preset/parser.js +0 -11
  36. package/src/preset/templates/commit.hbs +0 -19
  37. package/src/preset/templates/footer.hbs +0 -10
  38. package/src/preset/templates/header.hbs +0 -10
  39. package/src/preset/templates/index.js +0 -13
  40. package/src/preset/templates/main.hbs +0 -21
  41. package/src/preset/whatBump.js +0 -33
  42. package/src/preset/writer.js +0 -201
package/dist/cli.js ADDED
@@ -0,0 +1 @@
1
+ import{resolve as e}from"node:path";import{pathToFileURL as t}from"node:url";import n from"./index.js";import{cli as r}from"cleye";let i;try{let n=t(e(process.cwd(),`relion.config.ts`)).href;i=(await import(n)).default}catch(e){throw Error(`Error loading config: ${e.message}`)}const a=r({name:`relion`,flags:{bump:{alias:`b`,type:Boolean,description:`Bump the version`,default:!1},changelog:{alias:`l`,type:Boolean,description:`Generate a changelog`,default:!1},commit:{alias:`c`,type:Boolean,description:`Create a commit`,default:!1},tag:{alias:`t`,type:Boolean,description:`Create a tag`,default:!1},profile:{alias:`p`,type:String,description:`Use config profile`},dryRun:{alias:`d`,type:Boolean,description:`Run without making any changes`,default:!1},latest:{alias:`L`,type:Boolean,description:`Use the latest-release commit range in changelog`,default:!1}}});a.flags.bump||(i.bump=!1),a.flags.changelog||(i.changelog=!1),a.flags.commit||(i.commit=!1),a.flags.tag||(i.tag=!1),a.flags.profile&&(i.profile=a.flags.profile),a.flags.dryRun&&(i.dryRun=a.flags.dryRun),a.flags.latest&&i.changelog&&(i.changelog===!0&&(i.changelog={}),i.changelog.commitRange=`latest-release`),await n(i);export{};
@@ -0,0 +1,230 @@
1
+ import { HelperDeclareSpec } from "handlebars";
2
+
3
+ //#region src/types/config.d.ts
4
+ type FalseOrComplete<T> = false | Required<T>;
5
+ interface UserConfig {
6
+ bump?: boolean | BumpFiles;
7
+ changelog?: boolean | ChangelogOptions;
8
+ commit?: boolean | CommitOptions;
9
+ tag?: boolean | TagOptions;
10
+ newTagFormat?: string;
11
+ versionSourceFile?: string | VersionedFile;
12
+ releaseVersion?: string;
13
+ releaseType?: ReleaseType;
14
+ zeroMajorBreakingIsMinor?: boolean;
15
+ context?: Context;
16
+ commitsParser?: CommitsParser;
17
+ prevReleaseTagPattern?: RegExp;
18
+ dryRun?: boolean;
19
+ profile?: string;
20
+ [profile: `_${string}`]: UserConfig | undefined;
21
+ }
22
+ type OptionalKeys = 'releaseVersion' | 'releaseType' | 'context' | 'profile';
23
+ interface MergedConfig extends Omit<Required<UserConfig>, OptionalKeys>, Pick<UserConfig, OptionalKeys> {
24
+ changelog: FalseOrComplete<ChangelogOptions>;
25
+ commit: FalseOrComplete<CommitOptions>;
26
+ tag: FalseOrComplete<TagOptions>;
27
+ commitsParser: CompleteCommitsParser;
28
+ }
29
+ interface TransformedConfig extends Omit<MergedConfig, 'changelog'> {
30
+ versionSourceFile: VersionedFile;
31
+ bump: FalseOrComplete<VersionedFile[]>;
32
+ changelog: false | ResolvedChangelogOptions;
33
+ }
34
+ interface ContextualConfig extends TransformedConfig {
35
+ context: ResolvedContext;
36
+ }
37
+ type ResolvedConfig = ContextualConfig;
38
+ type BumpFiles = (string | VersionedFile)[];
39
+ interface ChangelogOptions {
40
+ stdout?: boolean;
41
+ outputFile?: string;
42
+ commitRange?: CommitRange;
43
+ sections?: ChangelogSectionDefinition[];
44
+ header?: string;
45
+ prevReleaseHeaderPattern?: RegExp;
46
+ helpers?: HelperDeclareSpec;
47
+ partials?: Record<string, string>;
48
+ }
49
+ type CompleteChangelogOptions = Required<ChangelogOptions>;
50
+ interface ResolvedChangelogOptions extends Omit<CompleteChangelogOptions, 'partials'> {
51
+ compiledPartials: Record<string, HandlebarsTemplateDelegate>;
52
+ }
53
+ interface CommitOptions {
54
+ message?: string;
55
+ signOff?: boolean;
56
+ gpgSign?: boolean;
57
+ stageAll?: boolean;
58
+ extraArgs?: string;
59
+ }
60
+ type CompleteCommitOptions = Required<CommitOptions>;
61
+ interface TagOptions {
62
+ name?: string;
63
+ message?: string;
64
+ gpgSign?: boolean;
65
+ force?: boolean;
66
+ extraArgs?: string;
67
+ }
68
+ type CompleteTagOptions = Required<TagOptions>;
69
+ interface CommitsParser {
70
+ breakingChangesPattern?: RegExp;
71
+ breakingChangeListPattern?: RegExp;
72
+ headerPattern?: RegExp;
73
+ tagPattern?: RegExp;
74
+ coAuthorPattern?: RegExp;
75
+ ghEmailPattern?: RegExp;
76
+ refPattern?: RegExp;
77
+ refActionPattern?: RegExp;
78
+ refLabelPattern?: RegExp;
79
+ remoteUrlPattern?: RegExp;
80
+ signerPattern?: RegExp;
81
+ dateSource?: 'authorDate' | 'committerDate';
82
+ dateFormat?: string;
83
+ }
84
+ type CompleteCommitsParser = Required<CommitsParser>;
85
+ interface RepoInfo {
86
+ host?: string;
87
+ owner?: string;
88
+ name?: string;
89
+ homepage?: string;
90
+ }
91
+ interface Context {
92
+ commits?: Commit[] | RawCommit[];
93
+ currentVersion?: string;
94
+ currentTag?: string;
95
+ newVersion?: string;
96
+ newTag?: string;
97
+ repo?: RepoInfo;
98
+ [key: string]: unknown;
99
+ }
100
+ interface ResolvedContext extends Required<Context> {
101
+ commits: Commit[];
102
+ releases: ReleaseWithGroupedCommits[] | null;
103
+ }
104
+ type CommitRange = 'all' | 'unreleased' | 'latest-release' | {
105
+ versionTag: string;
106
+ } | {
107
+ from: 'firstCommit' | (string & {});
108
+ } | {
109
+ to: 'HEAD' | (string & {});
110
+ } | {
111
+ from: 'firstCommit' | (string & {});
112
+ to: 'HEAD' | (string & {});
113
+ };
114
+ type ReleaseType = 'major' | 'minor' | 'patch';
115
+ interface VersionedFile {
116
+ filePath: string;
117
+ versionPattern: RegExp;
118
+ }
119
+ interface DefaultVersionedFile extends Omit<VersionedFile, 'filePath'> {
120
+ filePathRegex: RegExp;
121
+ }
122
+ //#endregion
123
+ //#region src/enums.d.ts
124
+ declare enum GpgSigLabel {
125
+ G = "valid",
126
+ B = "bad",
127
+ U = "valid, unknown validity",
128
+ X = "valid, expired",
129
+ Y = "valid, made by expired key",
130
+ R = "valid, made by revoked key",
131
+ E = "cannot check (missing key)",
132
+ N = "no signature",
133
+ }
134
+ declare enum RefType {
135
+ issue = "issue",
136
+ pr = "PR",
137
+ }
138
+ //#endregion
139
+ //#region src/types/commit.d.ts
140
+ type RawCommit = CommitMessageString | {
141
+ hash?: string;
142
+ message: string;
143
+ tagRefs?: string;
144
+ authorName?: string;
145
+ authorEmail?: string;
146
+ authorTs?: string;
147
+ committerName?: string;
148
+ committerEmail?: string;
149
+ committerTs?: string;
150
+ gpgSigCode?: GpgSigCode;
151
+ gpgSigKeyId?: string;
152
+ };
153
+ interface Commit extends CommitMessage {
154
+ hash?: string;
155
+ tags?: string[];
156
+ authors?: Contributor[];
157
+ committer?: Contributor;
158
+ refs?: Reference[];
159
+ gpgSig?: GpgSig;
160
+ date?: string;
161
+ }
162
+ interface CommitMessage {
163
+ type: string;
164
+ scope?: string;
165
+ subject: string;
166
+ body?: string;
167
+ breakingChanges?: string | string[];
168
+ footer?: string;
169
+ }
170
+ type CommitMessageString = string;
171
+ interface Reference {
172
+ action: string;
173
+ type?: RefType;
174
+ owner?: string;
175
+ repo?: string;
176
+ number: string;
177
+ }
178
+ type RefLabel = Pick<Reference, 'owner' | 'repo' | 'number'>;
179
+ interface Contributor {
180
+ name: string;
181
+ email: string;
182
+ hasSignedOff?: boolean;
183
+ ghLogin?: string;
184
+ ghUrl?: string;
185
+ }
186
+ type GithubUserInfo = Pick<Contributor, 'ghLogin' | 'ghUrl'>;
187
+ interface GpgSig {
188
+ code: GpgSigCode;
189
+ label: GpgSigLabel;
190
+ keyId?: string;
191
+ }
192
+ type GpgSigCode = 'G' | 'B' | 'U' | 'X' | 'Y' | 'R' | 'E' | 'N';
193
+ interface RawReference {
194
+ action: string;
195
+ labels: string;
196
+ }
197
+ //#endregion
198
+ //#region src/types/changelog.d.ts
199
+ interface ChangelogSectionDefinition {
200
+ title: string;
201
+ commitType: 'breaking' | '*' | (string & {}) | string[];
202
+ filter?: (commit: Commit) => boolean;
203
+ }
204
+ interface ResolvedChangelogSection {
205
+ title: string;
206
+ commits: Commit[];
207
+ }
208
+ interface ReleaseWithFlatCommits {
209
+ tag: string;
210
+ version?: string;
211
+ date?: string;
212
+ commits: Commit[];
213
+ }
214
+ interface ReleaseWithGroupedCommits extends Omit<ReleaseWithFlatCommits, 'commits'> {
215
+ commitGroups: ResolvedChangelogSection[];
216
+ }
217
+ interface ReleaseContext extends ReleaseWithGroupedCommits, ResolvedContext {
218
+ prevTag?: string;
219
+ prevVersion?: string;
220
+ }
221
+ type ChangelogSectionsMap = Record<string, ChangelogSectionDefinition>;
222
+ interface DefaultChangelogSections extends ChangelogSectionsMap {
223
+ [Symbol.iterator](): Iterator<ChangelogSectionDefinition>;
224
+ }
225
+ //#endregion
226
+ //#region src/relion.d.ts
227
+ declare function relion(userConfig: UserConfig): Promise<void>;
228
+ declare const defineConfig: (config: UserConfig) => UserConfig;
229
+ //#endregion
230
+ export { BumpFiles, ChangelogOptions, ChangelogSectionDefinition, ChangelogSectionsMap, Commit, CommitMessage, CommitOptions, CommitRange, CommitsParser, CompleteChangelogOptions, CompleteCommitOptions, CompleteCommitsParser, CompleteTagOptions, Context, ContextualConfig, Contributor, DefaultChangelogSections, DefaultVersionedFile, FalseOrComplete, GithubUserInfo, GpgSig, GpgSigCode, MergedConfig, RawCommit, RawReference, RefLabel, Reference, ReleaseContext, ReleaseType, ReleaseWithFlatCommits, ReleaseWithGroupedCommits, RepoInfo, ResolvedChangelogOptions, ResolvedChangelogSection, ResolvedConfig, ResolvedContext, TagOptions, TransformedConfig, UserConfig, VersionedFile, relion as default, defineConfig };
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import{existsSync as e,readFileSync as t,writeFileSync as n}from"node:fs";import r from"handlebars";import i from"semver";import{execSync as a}from"node:child_process";const o=e=>{if(!e.bump)return;let r=e.bump,i=e.context.newVersion;r.forEach(r=>{let a=t(r.filePath,`utf8`),o=a.replace(r.versionPattern,`$1${i}$3`);e.dryRun||n(r.filePath,o,`utf8`),console.log(`Updated version in '${r.filePath}' to '${i}'`)})},s={bump:!1,changelog:!1,commit:!1,tag:!1,versionSourceFile:`./package.json`,newTagFormat:`v{{newVersion}}`,prevReleaseTagPattern:/^v(?<version>\d+\.\d+\.\d+)/,zeroMajorBreakingIsMinor:!0,dryRun:!1,context:{commitHyperlink:!0,refHyperlink:!0},commitsParser:{headerPattern:/^(?<type>\w+)(?:\((?<scope>.+)\))?(?<bang>!)?: (?<subject>.+)/s,breakingChangesPattern:/BREAKING CHANGES?:\s*(?<content>.+)/s,breakingChangeListPattern:/- (.+)/g,tagPattern:/tag: (?<tag>.*?)[,)]/g,coAuthorPattern:/Co-authored-by: (?<name>.+?) <(?<email>.+)>/g,signerPattern:/Signed-off-by: (?<name>.+?) <(?<email>.+)>/g,ghEmailPattern:/^(?:\d+\+)?(?<username>.+)@users\.noreply\.github\.com$/,remoteUrlPattern:/^(https:\/\/|git@)(?<host>[^/:]+)[/:](?<owner>.+?)\/(?<name>.+?)(?:\..*)?$/,refPattern:/^(?<action>.+?) (?<labels>.+)$/gm,refLabelPattern:/(?:(?<owner>\S+?)\/(?<repo>\S+?))?#(?<number>\d+)/g,refActionPattern:/Fixes|Closes|Refs/i,dateSource:`authorDate`,dateFormat:`YYYY-MM-DD`}},c={breaking:{title:`⚠️ BREAKING CHANGES`,commitType:`breaking`},feat:{title:`✨ Features`,commitType:`feat`},fix:{title:`🩹 Fixes`,commitType:`fix`},perf:{title:`⚡ Performance`,commitType:`perf`},refactor:{title:`🚜 Refactoring`,commitType:`refactor`},docs:{title:`📚 Documentation`,commitType:`docs`},style:{title:`🎨 Formatting`,commitType:`style`},build:{title:`📦 Build`,commitType:`build`},ci:{title:`🚀 CI`,commitType:`ci`},revert:{title:`♻️ Reverts`,commitType:`revert`},deps:{title:`🧩 Dependencies`,commitType:`chore`,filter:e=>!!e.scope?.includes(`deps`)},chore:{title:`🛠️ Chores`,commitType:`chore`},test:{title:`🧪 Tests`,commitType:`test`},misc:{title:`⚙️ Miscellaneous`,commitType:`*`},[Symbol.iterator](){return Object.values(this)[Symbol.iterator]()}},l={stdout:!1,outputFile:`./CHANGELOG.md`,commitRange:`unreleased`,sections:[...c],header:`# Changelog
2
+
3
+
4
+ `,prevReleaseHeaderPattern:/^##.*?\d+\.\d+\.\d+/m,helpers:{repeat:(e,t)=>e.repeat(t)},partials:{}},u={message:`release({{repo.name}}): {{newTag}}`,signOff:!1,gpgSign:!1,stageAll:!0,extraArgs:``},d={name:`{{newTag}}`,message:`release({{repo.name}}): {{newTag}}`,gpgSign:!1,force:!1,extraArgs:``},f=[{filePathRegex:/package\.json$/,versionPattern:/(^.*?"version".*?")(.*?)(")/s},{filePathRegex:/package-lock\.json$/,versionPattern:/(^.*?"version".*?"|"packages".*?"".*"version".*?")(.*?)(")/gs}],p=async e=>{let t=m(e),n=h(t),r=g(n),i=await _(r),a=x(i);return a},m=e=>{let t=e.profile;if(!t)return e;let n=e[`_${t}`];if(!n)throw Error(`Profile "${t}" not found in configuration.`);let r=(t,...r)=>{let i=e=>Object.prototype.toString.call(e)===`[object Object]`,a=(e,t)=>{let n=i(e),r=i(t);if(n&&r)return{...e,...t};if(!n&&r)return t;if(n&&!r)return e},o=e[t],s=n[t],c=a(o,s);if(c!==void 0)return r.forEach(e=>{c[e]=a(o?.[e],s?.[e])}),c};return{...e,...n,commitsParser:r(`commitsParser`),changelog:r(`changelog`,`partials`,`helpers`),commit:r(`commit`),tag:r(`tag`),context:r(`context`)}},h=e=>{let t=(e,t,n,...r)=>{if(t==null||t===!1)return!1;if(t===!0)return n;if(typeof t!=`object`)throw Error(`Invalid value for ${e}. It should be a boolean or an object.`);let i={...n,...t};return r.forEach(e=>i[e]={...n[e],...t[e]}),i};return{...s,...e,commitsParser:{...s.commitsParser,...e.commitsParser},changelog:t(`changelog`,e.changelog,l,`partials`,`helpers`),commit:t(`commit`,e.commit,u),tag:t(`tag`,e.tag,d)}},g=e=>{let t=e=>{let t=f.find(t=>t.filePathRegex.test(e));if(t)return{filePath:e,versionPattern:t.versionPattern};throw Error(`File ${e} doesn't match any default versioned files. Please provide a custom version pattern for this file.`)},n=e=>{if(e===!1)return!1;if(e===!0)return[i];if(Array.isArray(e))return[i,...e.map(e=>typeof e==`string`?t(e):e)];throw Error(`Invalid value for bump. It should be a boolean or an array.`)},i=typeof e.versionSourceFile==`string`?t(e.versionSourceFile):e.versionSourceFile;return{...e,versionSourceFile:i,bump:n(e.bump),changelog:e.changelog===!1?!1:{...e.changelog,compiledPartials:Object.fromEntries(Object.entries(e.changelog.partials).map(([e,t])=>[e,r.compile(t)]))}}},_=async e=>{let t=e.context??{},n=F(e.commitsParser.remoteUrlPattern);t.repo={...n,...t.repo};let r=e.changelog?e.changelog.commitRange:`unreleased`;t.commits=e.context?.commits?await Promise.all(e.context.commits.map(async t=>typeof t==`object`&&`message`in t||typeof t==`string`?(await O([t],e.commitsParser,e.prevReleaseTagPattern))[0]:t)):await O(r,e.commitsParser,e.prevReleaseTagPattern),t.currentVersion??=C(e.versionSourceFile),t.currentTag??=R(e.prevReleaseTagPattern)[0],t.newVersion??=await w(e,t.currentVersion),t.newTag??=S(e.newTagFormat,t);let i={...e,context:t};return t.releases=e.changelog?v(t.commits,e.changelog.sections,i):null,i},v=(e,t,n)=>{let r={};return e.forEach(e=>{let t=e.tags?.find(e=>n.prevReleaseTagPattern.test(e));if(t)r[t]??={tag:t,version:n.prevReleaseTagPattern.exec(t)?.groups?.version,date:e.date,commits:[e]};else{let t=Object.keys(r).at(-1);t?r[t].commits.push(e):r[n.context.newTag]={tag:n.context.newTag,version:n.context.newVersion,date:e.date,commits:[e]}}}),Object.values(r).map(e=>y(e,t))},y=(e,t)=>{let{commits:n,...r}=e;return{...r,commitGroups:b(n,t)}},b=(e,t)=>{let n=Object.fromEntries(t.map(e=>[e.title,[]]));return e.forEach(e=>{let r=!!e.breakingChanges,i=!1,a=!1;for(let o of t){if(o.filter&&!o.filter(e))continue;let t=[o.commitType].flat();if(r&&!a&&t.includes(`breaking`)){n[o.title].push(e),a=!0;continue}if(!i&&(t.includes(e.type)||t.includes(`*`))&&(n[o.title].push(e),i=!0),i&&(!r||a))return}}),Object.keys(n).forEach(e=>{n[e].length||delete n[e]}),Object.entries(n).map(([e,t])=>({title:e,commits:t}))},x=e=>({...e,commit:e.commit?{...e.commit,message:S(e.commit.message,e.context)}:e.commit,tag:e.tag?{...e.tag,name:S(e.tag.name,e.context),message:S(e.tag.message,e.context)}:e.tag}),S=(e,t)=>{let n=r.compile(e);return n(t)},C=e=>{let n=t(e.filePath,`utf8`),r=e.versionPattern.exec(n)?.[2];if(!r)throw Error(`Version not found in '${e.filePath}' with pattern '${e.versionPattern}'`);if(!i.valid(r))throw Error(`Invalid version format in '${e.filePath}': '${r}'`);return console.log(`Current version from '${e.filePath}': '${r}'`),r},w=async(e,t)=>{if(e.releaseVersion){if(!i.valid(e.releaseVersion))throw Error(`Invalid release version format: '${e.releaseVersion}'`);return e.releaseVersion}let n;if(e.releaseType)n=e.releaseType;else{let r=await O(`unreleased`,e.commitsParser,e.prevReleaseTagPattern);n=T(r),e.zeroMajorBreakingIsMinor&&i.major(t)===0&&n===`major`&&(n=`minor`)}let r=E(t,n);return console.log(`Determined new version: '${r}' (release type: '${n}')`),r},T=e=>{let t=e.some(e=>e.breakingChanges);if(t)return`major`;let n=e.some(e=>e.type===`feat`);return n?`minor`:`patch`},E=(e,t)=>i.inc(e,t)??(()=>{throw Error(`Failed to calculate new version from '${e}' with release type '${t}'`)})();let D=function(e){return e.G=`valid`,e.B=`bad`,e.U=`valid, unknown validity`,e.X=`valid, expired`,e.Y=`valid, made by expired key`,e.R=`valid, made by revoked key`,e.E=`cannot check (missing key)`,e.N=`no signature`,e}({});const O=async(e,t,n)=>{let r=Array.isArray(e)?e:I(e,n),i=t;return(await Promise.all(r.map(async e=>{typeof e==`string`&&(e={message:e});let{hash:t,tagRefs:n}=e,r=e.message.trim();if(!r)throw Error(`Message is missing for commit: ${JSON.stringify(e)}`);let a;try{a=k(r,i)}catch(e){console.warn(`Error parsing commit '${t??`<no hash>`}':`,e.message);return}let{type:o,scope:s,subject:c,body:l,breakingChanges:u,footer:d}=a,f=n?[...n.matchAll(i.tagPattern)].map(e=>e.groups?.tag??``):[],p=d?[...d.matchAll(i.signerPattern)].map(e=>e.groups):[],m=[],h=e=>{m.some(t=>t.email===e.email)||m.push(e)},g=e.authorName&&e.authorEmail?j({name:e.authorName,email:e.authorEmail},p):void 0;g&&h(g);let _=e.committerName&&e.committerEmail?j({name:e.committerName,email:e.committerEmail},p):void 0;_&&h(_);let v=d?[...d.matchAll(i.coAuthorPattern)].map(e=>e.groups).map(e=>j(e,p)):[];v.forEach(e=>h(e));let y=await M(d??``,i),b=e.gpgSigCode?{code:e.gpgSigCode,label:D[e.gpgSigCode],keyId:e.gpgSigKeyId}:void 0,x=e[i.dateSource===`committerDate`?`committerTs`:`authorTs`];typeof x==`string`&&(x=N(new Date(x*1e3),i.dateFormat));let S={hash:t,type:o,scope:s,subject:c,body:l,breakingChanges:u,footer:d,committer:_,gpgSig:b,date:x,tags:f.length?f:void 0,authors:m.length?m:void 0,refs:y.length?y:void 0};return S}))).filter(e=>e!==void 0)},k=(e,t)=>{let[n,...r]=e.split(`
5
+
6
+ `),i=t.headerPattern.exec(n);if(!i?.groups)throw Error(`Commit header '${n}' doesn't match expected format`);let{type:a,scope:o,bang:s,subject:c}=i.groups,l,u=r.find(e=>t.breakingChangesPattern.test(e));u?(l=A(u,t),r.splice(r.indexOf(u),1)):s&&(l=c);let d=r.findIndex(e=>e.match(t.refActionPattern)??e.match(t.coAuthorPattern)??e.match(t.signerPattern)),[f,p]=d===-1?[r.join(`
7
+
8
+ `),``]:[r.slice(0,d).join(`
9
+
10
+ `),r.slice(d).join(`
11
+
12
+ `)];return{type:a,scope:o||void 0,subject:c,body:f||void 0,breakingChanges:l,footer:p||void 0}},A=(e,t)=>{let n=t.breakingChangesPattern.exec(e)?.groups?.content;if(!n)throw Error(`Failed to extract breaking changes content from '${e}' using pattern "${t.breakingChangesPattern}"`);let r=[...n.matchAll(t.breakingChangeListPattern)];return r.length?r.map(e=>e[1]):n},j=(e,t)=>{let n=t.some(t=>t.email===e.email&&t.name===e.name);return{...e,hasSignedOff:n,ghLogin:e.name,ghUrl:`https://github.com/${e.name}`}},M=async(e,t)=>await Promise.all([...e.matchAll(t.refPattern)].map(e=>e.groups).filter(e=>t.refActionPattern.test(e.action)).flatMap(e=>[...e.labels.matchAll(t.refLabelPattern)].map(e=>e.groups).filter(e=>!!e.number).map(t=>({action:e.action,owner:t.owner,repo:t.repo,number:t.number})))),N=(e,t)=>{let n=e=>e.toString().padStart(2,`0`),r={YYYY:e.getUTCFullYear().toString(),MM:n(e.getUTCMonth()+1),DD:n(e.getUTCDate()),HH:n(e.getUTCHours()),mm:n(e.getUTCMinutes()),ss:n(e.getUTCSeconds())};return t.replace(/YYYY|MM|DD|HH|mm|ss/g,e=>r[e])},P=/##COMMIT##\n#HASH# (?<hash>.+)?\n#MSG# (?<message>[\s\S]*?)\n#REFS#\s+(?<tagRefs>.+)?\n#AUTHOR-NAME# (?<authorName>.+)?\n#AUTHOR-EMAIL# (?<authorEmail>.+)?\n#AUTHOR-DATE# (?<authorTs>.+)?\n#COMMITTER-NAME# (?<committerName>.+)?\n#COMMITTER-EMAIL# (?<committerEmail>.+)?\n#COMMITTER-DATE# (?<committerTs>.+)?\n#GPGSIG-CODE# (?<gpgSigCode>.+)?\n#GPGSIG-KEYID# (?<gpgSigKeyId>.+)?/g,F=e=>{let t=a(`git remote get-url origin`,{encoding:`utf8`}).trim(),n=e.exec(t);if(!n?.groups)throw Error(`Couldn't parse remote URL: `+t);let{host:r,owner:i,name:o}=n.groups,s=`https://${r}/${i}/${o}`;return{host:r,owner:i,name:o,homepage:s}},I=(e,t)=>{let n=L(),r=R(t),i,o;if(e===`all`)i=n,o=`HEAD`;else if(e===`unreleased`)i=r[0]??n,o=`HEAD`;else if(e===`latest-release`)i=r[1]??n,o=r[0]?r[0]+`^`:`HEAD`;else if(`from`in e||`to`in e){let t=`from`in e?e.from:`firstCommit`;i=t===`firstCommit`?n:t,o=`to`in e?e.to:`HEAD`}else if(`versionTag`in e){let t=r.indexOf(e.versionTag);if(t===-1)throw Error(`Version tag '${e.versionTag}' not found`);i=r[t],o=r[t+1]??`HEAD`}else throw Error(`Invalid commit range provided`);let s=a(`git log "${i}..${o}" --pretty="##COMMIT##%n#HASH# %h%n#MSG# %B%n#REFS# %d%n#AUTHOR-NAME# %an%n#AUTHOR-EMAIL# %ae%n#AUTHOR-DATE# %at%n#COMMITTER-NAME# %cn%n#COMMITTER-EMAIL# %ce%n#COMMITTER-DATE# %ct%n#GPGSIG-CODE# %G?%n#GPGSIG-KEYID# %GK%n"`,{encoding:`utf8`});return[...s.matchAll(P)].map(e=>e.groups)},L=()=>a(`git rev-list --max-parents=0 HEAD`,{encoding:`utf8`}).trim(),R=e=>{let t=a(`git tag --sort=-creatordate`,{encoding:`utf8`});return t.split(`
13
+ `).filter(t=>e.test(t))};var z=`{{#>header}}
14
+ ## &ensp; [\` 📦 {{tag}} \`]({{repo.homepage}}/{{#if prevTag}}compare/{{prevTag}}...{{tag}}{{else}}commits/{{tag}}{{/if}})
15
+
16
+ {{/header}}
17
+ {{#each commitGroups}}
18
+ ### {{{repeat '&nbsp;' 5}}}{{title}}
19
+ {{#each commits}}
20
+ * {{#if scope}}\`{{scope}}\` {{/if}}{{{subject}}} {{#if ../../commitHyperlink}}[\`{{hash}}\`]({{../../repo.homepage}}/commit/{{hash}}){{else}}{{hash}}{{/if}}
21
+ {{/each}}
22
+
23
+ {{/each}}
24
+ ##### &emsp;&ensp;&nbsp; 🔗 [Full Commit History: {{#if prevTag}}\`{{prevTag}}\` → \`{{tag}}\`]({{repo.homepage}}/compare/{{prevTag}}...{{tag}}){{else}}\`{{tag}}\`]({{repo.homepage}}/commits/{{tag}}){{/if}} &ensp;/&ensp; _{{date}}_
25
+
26
+
27
+ `;const B=e=>{if(!e.changelog)return;let t=e.changelog,n=e.context.releases;if(!n)return;let i=R(e.prevReleaseTagPattern);r.registerPartial(t.compiledPartials),r.registerHelper(t.helpers);let a=t.header;n.forEach((t,r)=>{let o=n[r+1],s,c;if(o)s=o.tag,c=U(s,e.prevReleaseTagPattern);else{let n=i.indexOf(t.tag);n===-1?(s=e.context.currentTag,c=U(s,e.prevReleaseTagPattern)):(s=i[n+1],c=s&&U(s,e.prevReleaseTagPattern))}let l={...t,...e.context,prevTag:s,prevVersion:c},u=H(z,l);a+=u}),t.stdout&&console.log(`Generated changelog:\n${a}`),t.outputFile&&(console.log(`Writing changelog to file '${t.outputFile}'`),e.dryRun||V(t.outputFile,a,t.prevReleaseHeaderPattern))},V=(r,i,a)=>{let o=e(r)?t(r,{encoding:`utf8`}):``,s=o.search(a),c=o.slice(s),l=i+c;n(r,l,{encoding:`utf8`})},H=(e,t)=>{let n=r.compile(e);return n(t)},U=(e,t)=>t.exec(e)?.groups?.version,W=e=>{if(!e.commit)return;let t=e.commit;console.log(`Committing with options:`,t),t.stageAll&&!e.dryRun&&a(`git add -A`,{stdio:`inherit`}),e.dryRun||a(`git commit -m "${t.message}" ${t.signOff?`-s`:``} ${t.gpgSign?`-S`:``} ${t.extraArgs}`,{stdio:`inherit`})},G=e=>{if(!e.tag)return;let t=e.tag;console.log(`Tagging with options:`,t),e.dryRun||a(`git tag -a ${t.name} -m "${t.message}" ${t.gpgSign?`-s`:``} ${t.force?`-f`:``} ${t.extraArgs}`,{stdio:`inherit`})};async function K(e){let t=await p(e);o(t),B(t),W(t),G(t)}const q=e=>e;export{K as default,q as defineConfig};
package/package.json CHANGED
@@ -1,17 +1,12 @@
1
1
  {
2
2
  "name": "relion",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Release workflow helper for Node.js projects.",
5
- "author": "Kh4f",
5
+ "author": "Kh4f <kh4f.dev@gmail.com>",
6
6
  "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/Kh4f/relion.git"
10
- },
11
- "bugs": {
12
- "url": "https://github.com/Kh4f/relion/issues"
13
- },
14
- "homepage": "https://github.com/Kh4f/relion#readme",
7
+ "repository": "https://github.com/kh4f/relion",
8
+ "bugs": "https://github.com/kh4f/relion/issues",
9
+ "homepage": "https://github.com/kh4f/relion#readme",
15
10
  "keywords": [
16
11
  "relion",
17
12
  "release",
@@ -22,64 +17,50 @@
22
17
  "standard-version",
23
18
  "commit-and-tag-version"
24
19
  ],
25
- "main": "./src/index.js",
26
- "type": "module",
27
- "bin": "./src/cli.js",
28
20
  "files": [
29
- "src",
21
+ "dist",
30
22
  "package.json",
31
23
  "LICENSE",
32
24
  "README.md"
33
25
  ],
26
+ "type": "module",
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
+ "bin": "dist/cli.js",
30
+ "dependencies": {
31
+ "cleye": "^1.3.4",
32
+ "handlebars": "^4.7.8",
33
+ "semver": "^7.7.2"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^9.34.0",
37
+ "@stylistic/eslint-plugin": "^5.3.1",
38
+ "@types/node": "^24.3.1",
39
+ "@types/semver": "^7.7.1",
40
+ "eslint": "^9.34.0",
41
+ "globals": "^16.3.0",
42
+ "lint-staged": "^16.1.6",
43
+ "simple-git-hooks": "^2.13.1",
44
+ "tsdown": "^0.14.2",
45
+ "typescript": "^5.9.2",
46
+ "typescript-eslint": "^8.42.0",
47
+ "vitest": "^3.2.4"
48
+ },
34
49
  "simple-git-hooks": {
35
50
  "pre-commit": "pnpm lint-staged"
36
51
  },
37
52
  "lint-staged": {
38
53
  "*.{js,ts}": "pnpm lint:fix"
39
54
  },
40
- "dependencies": {
41
- "chalk": "^5.4.1",
42
- "compare-func": "^2.0.0",
43
- "conventional-changelog": "^6.0.0",
44
- "conventional-changelog-config-spec": "^2.1.0",
45
- "conventional-changelog-conventionalcommits": "^8.0.0",
46
- "conventional-recommended-bump": "^11.0.0",
47
- "detect-indent": "^7.0.1",
48
- "detect-newline": "^4.0.1",
49
- "dotgitignore": "^2.1.0",
50
- "figures": "^6.1.0",
51
- "find-up": "^7.0.0",
52
- "git-semver-tags": "^8.0.0",
53
- "jsdom": "^26.1.0",
54
- "mergician": "^2.0.2",
55
- "semver": "^7.7.2",
56
- "w3c-xmlserializer": "^5.0.0",
57
- "yaml": "^2.8.0",
58
- "yargs": "^17.7.2"
59
- },
60
- "devDependencies": {
61
- "@eslint/js": "^9.29.0",
62
- "@stylistic/eslint-plugin": "^4.4.1",
63
- "@types/node": "^24.0.3",
64
- "eslint": "^9.29.0",
65
- "globals": "^16.2.0",
66
- "lint-staged": "^16.1.2",
67
- "prettier": "^3.5.3",
68
- "simple-git-hooks": "^2.13.0",
69
- "tsdown": "^0.12.8",
70
- "typescript": "^5.8.3",
71
- "typescript-eslint": "^8.34.1",
72
- "vitest": "^3.2.4"
73
- },
74
55
  "scripts": {
56
+ "build": "tsdown",
75
57
  "lint": "eslint",
76
- "lint:fix": "eslint --fix",
77
58
  "test": "vitest run",
78
- "test:watch": "vitest",
79
- "build": "tsdown",
80
59
  "build:watch": "tsdown --watch",
81
60
  "build:prod": "tsdown --production",
82
- "release": "node ./src/cli.js --changelog --bump --commit --tag",
83
- "release:gh-notes": "node ./src/cli.js --changelog --profile gh-release-notes "
61
+ "test:watch": "vitest",
62
+ "lint:fix": "eslint --fix",
63
+ "release": "node dist/cli.js -blct",
64
+ "release:github": "node dist/cli.js -l -p github -L"
84
65
  }
85
66
  }
package/src/cli.js DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import relion from './index.js'
4
- import cmdParser from './commands.js'
5
-
6
- relion(cmdParser.argv)
package/src/commands.js DELETED
@@ -1,176 +0,0 @@
1
- import spec from 'conventional-changelog-config-spec'
2
- import { getConfiguration } from './lib/configuration.js'
3
- import defaults from './defaults.js'
4
- import yargs from 'yargs/yargs'
5
- import { hideBin } from 'yargs/helpers'
6
-
7
- const yargsInstance = yargs(hideBin(process.argv))
8
- .usage('Usage: $0 [options]')
9
- .option('bumpFiles', {
10
- default: defaults.bumpFiles,
11
- array: true,
12
- })
13
- .option('release-as', {
14
- alias: 'r',
15
- describe: 'Specify the release type manually (like npm version <major|minor|patch>)',
16
- requiresArg: true,
17
- string: true,
18
- })
19
- .option('prerelease', {
20
- alias: 'P',
21
- describe: 'make a pre-release with optional option value to specify a tag id',
22
- string: true,
23
- })
24
- .option('infile', {
25
- alias: 'i',
26
- describe: 'Read the CHANGELOG from this file',
27
- default: defaults.infile,
28
- })
29
- .option('sign', {
30
- alias: 's',
31
- describe: 'Should the git commit and tag be signed?',
32
- type: 'boolean',
33
- default: defaults.sign,
34
- })
35
- .option('signoff', {
36
- describe: 'Should the git commit have a "Signed-off-by" trailer',
37
- type: 'boolean',
38
- default: defaults.signoff,
39
- })
40
- .option('no-verify', {
41
- alias: 'n',
42
- describe: 'Bypass pre-commit or commit-msg git hooks during the commit phase',
43
- type: 'boolean',
44
- default: defaults.noVerify,
45
- })
46
- .option('commit-all', {
47
- alias: 'C',
48
- describe: 'Commit all staged changes, not just files affected by relion',
49
- type: 'boolean',
50
- default: defaults.commitAll,
51
- })
52
- .option('silent', {
53
- describe: 'Don\'t print logs and errors',
54
- type: 'boolean',
55
- default: defaults.silent,
56
- })
57
- .option('tag-prefix', {
58
- alias: 'T',
59
- describe: 'Set a custom prefix for the git tag to be created',
60
- type: 'string',
61
- default: defaults.tagPrefix,
62
- })
63
- .option('release-count', {
64
- describe:
65
- 'How many releases of changelog you want to generate. It counts from the upcoming release. Useful when you forgot to generate any previous changelog. Set to 0 to regenerate all.',
66
- type: 'number',
67
- default: defaults.releaseCount,
68
- })
69
- .option('tag-force', {
70
- describe: 'Allow tag replacement',
71
- type: 'boolean',
72
- default: defaults.tagForce,
73
- })
74
- .option('scripts', {
75
- describe: 'Provide scripts to execute for lifecycle events (prebump, precommit, etc.,)',
76
- default: defaults.scripts,
77
- })
78
- .option('bump', {
79
- alias: 'b',
80
- describe: 'Bump the version in bumpFiles',
81
- type: 'boolean',
82
- default: defaults.bump,
83
- })
84
- .option('changelog', {
85
- alias: 'l',
86
- describe: 'Generate a changelog',
87
- type: 'boolean',
88
- default: defaults.changelog,
89
- })
90
- .option('commit', {
91
- alias: 'c',
92
- describe: 'Create a git commit',
93
- type: 'boolean',
94
- default: defaults.commit,
95
- })
96
- .option('tag', {
97
- alias: 't',
98
- describe: 'Create a git tag',
99
- type: 'boolean',
100
- default: defaults.tag,
101
- })
102
- .option('all', {
103
- alias: 'a',
104
- describe: 'Run all lifecycle events',
105
- type: 'boolean',
106
- default: defaults.all,
107
- })
108
- .option('context.linkReferences', {
109
- describe: 'Should all references be linked?',
110
- type: 'boolean',
111
- default: defaults.context.linkReferences,
112
- })
113
- .option('context.fullChangelogLink', {
114
- describe: 'Add a "Full Changelog: v1...v2" link to the footer',
115
- type: 'boolean',
116
- default: defaults.context.fullChangelogLink,
117
- })
118
- .option('profile', {
119
- alias: 'p',
120
- describe: 'Specify a named config profile to merge with the base config (properties under _<profile-name> in .versionrc)',
121
- type: 'string',
122
- })
123
- .option('dry-run', {
124
- type: 'boolean',
125
- default: defaults.dryRun,
126
- describe: 'See the commands that running relion would run',
127
- })
128
- .option('git-tag-fallback', {
129
- type: 'boolean',
130
- default: defaults.gitTagFallback,
131
- describe:
132
- 'fallback to git tags for version, if no meta-information file is found (e.g., package.json)',
133
- })
134
- .option('path', {
135
- type: 'string',
136
- describe: 'Only populate commits made under this path',
137
- })
138
- .option('lerna-package', {
139
- type: 'string',
140
- describe: 'Name of the package from which the tags will be extracted',
141
- })
142
- .option('npmPublishHint', {
143
- type: 'string',
144
- default: defaults.npmPublishHint,
145
- describe: 'Customized publishing hint',
146
- })
147
- .check((argv) => {
148
- if (typeof argv.scripts !== 'object' || Array.isArray(argv.scripts)) {
149
- throw Error('scripts must be an object')
150
- }
151
- else {
152
- return true
153
- }
154
- })
155
- .alias('version', 'v')
156
- .alias('help', 'h')
157
- .example('$0', 'Update changelog and tag release')
158
- .example(
159
- '$0 -m "%s: see changelog for details"',
160
- 'Update changelog and tag release with custom commit message',
161
- )
162
- .pkgConf('relion')
163
- .config(await getConfiguration())
164
- .wrap(97)
165
-
166
- Object.keys(spec.properties).forEach((propertyKey) => {
167
- const property = spec.properties[propertyKey]
168
- if (!defaults.preset[propertyKey]) return
169
- yargsInstance.option('preset.' + propertyKey, {
170
- type: property.type,
171
- describe: property.description,
172
- default: defaults.preset[propertyKey],
173
- })
174
- })
175
-
176
- export default yargsInstance
package/src/defaults.js DELETED
@@ -1,60 +0,0 @@
1
- const defaultPresetURL = import.meta.resolve('./preset/index.js')
2
-
3
- const defaults = {
4
- bump: false,
5
- changelog: false,
6
- commit: false,
7
- tag: false,
8
- all: false,
9
-
10
- infile: 'CHANGELOG.md',
11
- sign: false,
12
- signoff: false,
13
- noVerify: false,
14
- commitAll: false,
15
- silent: false,
16
- tagPrefix: 'v',
17
- releaseCount: 1,
18
- scripts: {},
19
- dryRun: false,
20
- tagForce: false,
21
- gitTagFallback: true,
22
- npmPublishHint: undefined,
23
- bumpFiles: ['package.json', 'package-lock.json'],
24
-
25
- context: {
26
- linkReferences: true,
27
- fullChangelogLink: false,
28
- },
29
-
30
- preset: {
31
- name: defaultPresetURL,
32
- header: '# Changelog\n\n\n',
33
- types: [
34
- { type: 'feat', section: 'Features' },
35
- { type: 'fix', section: 'Bug Fixes' },
36
- { type: 'chore', hidden: true },
37
- { type: 'docs', hidden: true },
38
- { type: 'style', hidden: true },
39
- { type: 'refactor', hidden: true },
40
- { type: 'perf', hidden: true },
41
- { type: 'test', hidden: true },
42
- ],
43
- /* Is set to true automatically if version < 1.0.0,
44
- or matches the user config if provided.
45
- */
46
- // preMajor: true,
47
- commitUrlFormat: '{{host}}/{{owner}}/{{repository}}/commit/{{hash}}',
48
- compareUrlFormat: '{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}',
49
- issueUrlFormat: '{{host}}/{{owner}}/{{repository}}/issues/{{id}}',
50
- userUrlFormat: '{{host}}/{{user}}',
51
- releaseCommitMessageFormat: 'chore(release): {{currentTag}}',
52
- issuePrefixes: ['#'],
53
- },
54
-
55
- writerOpts: {
56
- commitsSort: false,
57
- },
58
- }
59
-
60
- export default defaults