relizy 1.5.0 → 1.6.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.
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import process from 'node:process';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { printBanner, logger } from '@maz-ui/node';
7
7
  import { Command } from 'commander';
8
- import { av as isInCI, S as getCIName, f as bump, h as changelog, aM as publish, aK as providerRelease, b3 as social, aJ as prComment, aV as release } from './shared/relizy.CobLJLr_.mjs';
8
+ import { aA as isInCI, U as getCIName, h as bump, j as changelog, aR as publish, aP as providerRelease, b8 as social, aO as prComment, a_ as release } from './shared/relizy.DhYBvZNi.mjs';
9
9
  import 'node:child_process';
10
10
  import '@maz-ui/utils/helpers/formatJson';
11
11
  import 'c12';
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LogLevel } from '@maz-ui/node';
2
- import { GitCommit, ChangelogConfig as ChangelogConfig$1, SemverBumpType } from 'changelogen';
2
+ import { RawGitCommit, GitCommit, ChangelogConfig as ChangelogConfig$1, SemverBumpType } from 'changelogen';
3
3
  import { ReleaseType } from 'semver';
4
4
  import { DeepPartial } from '@maz-ui/utils/ts-helpers/DeepPartial';
5
5
  import * as _slack_web_api from '@slack/web-api';
@@ -194,11 +194,61 @@ declare function createCommitAndTags({ config, noVerify, bumpedPackages, newVers
194
194
  dryRun?: boolean;
195
195
  logLevel: LogLevel;
196
196
  }): Promise<string[]>;
197
- declare function pushCommitAndTags({ config, dryRun, logLevel, cwd }: {
197
+ interface GitUpstream {
198
+ /** Remote name, e.g. `origin` */
199
+ remote: string;
200
+ /** Remote branch ref, e.g. `refs/heads/main` */
201
+ mergeRef: string;
202
+ /** Remote-tracking ref, e.g. `refs/remotes/origin/main` */
203
+ trackingRef: string;
204
+ /** Short remote-tracking name, e.g. `origin/main` */
205
+ name: string;
206
+ }
207
+ /**
208
+ * Resolve the upstream of the current branch. Returns null on a detached HEAD
209
+ * or when the branch has no upstream configured.
210
+ */
211
+ declare function getGitUpstream(cwd: string): Promise<GitUpstream | null>;
212
+ /**
213
+ * Ensure the current branch contains every commit of its remote counterpart.
214
+ *
215
+ * A release commit created on a branch that is behind its remote can never be
216
+ * pushed. Since packages are published before the push, running this check
217
+ * right before publishing prevents shipping a version whose release commit is
218
+ * then rejected (leaving an orphan tag and the repository stuck on the
219
+ * previous version).
220
+ *
221
+ * Skipped (no error) on a detached HEAD, without upstream, or when the remote
222
+ * cannot be fetched.
223
+ */
224
+ declare function assertBranchUpToDateWithRemote({ cwd }: {
225
+ cwd: string;
226
+ }): Promise<void>;
227
+ declare function pushCommitAndTags({ config, dryRun, logLevel, cwd, tags, }: {
198
228
  config: ResolvedRelizyConfig;
199
229
  dryRun: boolean;
200
230
  logLevel?: LogLevel;
201
231
  cwd: string;
232
+ tags?: string[];
233
+ }): Promise<void>;
234
+ /**
235
+ * Compute the git tag names a release will create, mirroring `createCommitAndTags`.
236
+ */
237
+ declare function getReleaseTagNames({ config, bumpedPackages, newVersion, }: {
238
+ config: ResolvedRelizyConfig;
239
+ bumpedPackages?: BumpResultTruthy['bumpedPackages'];
240
+ newVersion?: string;
241
+ }): string[];
242
+ /**
243
+ * Fail early when a tag the release is about to create already exists.
244
+ *
245
+ * This typically happens after a release that published its packages and
246
+ * pushed its tag, but whose release commit was rejected: the branch is still
247
+ * on the previous version, so the next run computes the same version again.
248
+ */
249
+ declare function assertReleaseTagsAvailable({ tags, cwd }: {
250
+ tags: string[];
251
+ cwd: string;
202
252
  }): Promise<void>;
203
253
  /**
204
254
  * Rollback modified files to their last committed state
@@ -249,6 +299,14 @@ declare function retagAnnotatedLocal({ tag, commit, message, signed, cwd, logLev
249
299
  * Force-push a single tag to origin. Used only after explicit confirmation.
250
300
  */
251
301
  declare function pushTagForce(tag: string, cwd?: string, logLevel?: LogLevel): Promise<void>;
302
+ /**
303
+ * Drop-in replacement for changelogen's `getGitDiff`, which splits the
304
+ * `git log` output on `----\n` and the subject line on `|`. A commit body
305
+ * containing a `----` line (GitHub adds `---------` before the
306
+ * `Co-authored-by:` trailers of a squash merge) then loses its trailers and its
307
+ * `--name-status` file list, and a subject containing `|` is truncated.
308
+ */
309
+ declare function getGitDiff(from: string | undefined, to?: string, cwd?: string): RawGitCommit[];
252
310
 
253
311
  declare function github(options: ProviderReleaseOptions): Promise<PostedRelease[]>;
254
312
 
@@ -869,23 +927,24 @@ declare function shouldFilterPrereleaseTags(currentVersion: string, graduating:
869
927
  */
870
928
  declare function extractVersionFromTag(tag: string, packageName?: string): string | null;
871
929
  /**
872
- * Checks if a tag's version is compatible with the current version.
873
- * A tag is compatible if its major version is less than or equal to the current major version.
930
+ * Computes a canary version with format `{nextVersion}-0.{preid}-{sha}`.
874
931
  *
875
- * This prevents accidentally using tags from future major versions (e.g., v5.0.0-beta.0)
876
- * when bumping a current stable version (e.g., 4.1.1 → 4.1.2).
932
+ * The leading numeric `0` prerelease identifier makes the canary sort below
933
+ * every named prerelease (alpha, beta, rc...) and the stable release of the
934
+ * same version, so it never matches a range like `^5.0.0-beta.0` and is never
935
+ * suggested by update tools. Keeping preid and sha in a single identifier also
936
+ * keeps the version valid when the sha is all digits with a leading zero.
877
937
  *
878
- * @param payload - The payload to check
938
+ * @param payload - The payload to compute the canary version from
879
939
  * @param payload.currentVersion - The current package version
880
- * @param payload.releaseType - The release type
940
+ * @param payload.releaseType - The release type detected from commits (defaults to patch)
881
941
  * @param payload.preid - The pre-release identifier
882
- * @param payload.sha - The sha of the commit
883
- * @returns true if the tag version's major is <= current major version
942
+ * @param payload.sha - The short sha of the commit
943
+ * @returns The canary version
884
944
  *
885
945
  * @example
886
- * isTagVersionCompatibleWithCurrent('4.1.1', '4.1.0') // true - same major
887
- * isTagVersionCompatibleWithCurrent('5.0.0-beta.0', '4.1.1') // false - newer major
888
- * isTagVersionCompatibleWithCurrent('3.9.9', '4.1.1') // true - older major
946
+ * getCanaryVersion({ currentVersion: '1.2.3', releaseType: 'minor', sha: 'a3f4b2c' }) // '1.3.0-0.canary-a3f4b2c'
947
+ * getCanaryVersion({ currentVersion: '5.0.0-beta.44', releaseType: 'patch', sha: 'a3f4b2c' }) // '5.0.0-0.canary-a3f4b2c'
889
948
  */
890
949
  declare function getCanaryVersion({ currentVersion, releaseType, preid, sha, }: {
891
950
  currentVersion: string;
@@ -893,6 +952,22 @@ declare function getCanaryVersion({ currentVersion, releaseType, preid, sha, }:
893
952
  preid?: string;
894
953
  sha: string;
895
954
  }): string;
955
+ /**
956
+ * Checks if a tag's version is compatible with the current version.
957
+ * A tag is compatible if its major version is less than or equal to the current major version.
958
+ *
959
+ * This prevents accidentally using tags from future major versions (e.g., v5.0.0-beta.0)
960
+ * when bumping a current stable version (e.g., 4.1.1 → 4.1.2).
961
+ *
962
+ * @param tagVersion - The version extracted from the tag
963
+ * @param currentVersion - The current package version
964
+ * @returns true if the tag version's major is <= current major version
965
+ *
966
+ * @example
967
+ * isTagVersionCompatibleWithCurrent('4.1.1', '4.1.0') // true - same major
968
+ * isTagVersionCompatibleWithCurrent('5.0.0-beta.0', '4.1.1') // false - newer major
969
+ * isTagVersionCompatibleWithCurrent('3.9.9', '4.1.1') // true - older major
970
+ */
896
971
  declare function isTagVersionCompatibleWithCurrent(tagVersion: string, currentVersion: string): boolean;
897
972
 
898
973
  type VersionMode = 'unified' | 'independent' | 'selective';
@@ -2264,5 +2339,5 @@ declare function socialSafetyCheck({ config }: {
2264
2339
  }): Promise<void>;
2265
2340
  declare function social(options?: Partial<SocialOptions>): Promise<SocialResult>;
2266
2341
 
2267
- export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, buildReleaseSummary, bump, capReleaseTypeForZeroMajor, changelog, checkGitStatusIfDirty, collectContributorNames, collectPackageBumps, confirmBump, createCommitAndTags, createGitlabRelease, defineConfig, detectGitProvider, detectPackageManager, detectPullRequest, determinePublishTag, determineReleaseType, determineSemverChange, executeBuildCmd, executeFormatCmd, executeHook, expandPackagesToBumpWithDependents, extractChangelogSummary, extractVersionFromPackageTag, extractVersionFromTag, fetchGitTags, filterOutPrivatePackages, findGitHubPR, findGitLabMR, findReachableCommitBySubject, formatChangelogForSlack, formatPackagesForSlack, formatSlackMessage, formatTweetMessage, generateChangelog, generateMarkDown, getAuthCommand, getBumpedIndependentPackages, getBumpedPackageIndependently, getCIName, getCanaryVersion, getCommitChangedFiles, getCommitSubject, getCurrentGitBranch, getCurrentGitRef, getDefaultConfig, getDependentsOf, getFirstCommit, getGitStatus, getIndependentTag, getLastPackageTag, getLastRepoTag, getLastStableTag, getLastTag, getModifiedReleaseFilePatterns, getNpmRegistry, getPackageCommits, getPackageDependencies, getPackageNewVersion, getPackages, getPackagesOrBumpedPackages, getPackagesToPublishInIndependentMode, getPackagesToPublishInSelectiveMode, getPreid, getReleaseUrl, getRootPackage, getShortCommitSha, getSlackToken, getSlackWebhookUrl, getTwitterCredentials, github, gitlab, globDirsAbsolute, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isCommitOnlyInIgnoredPackages, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, isVersionAlreadyPublishedError, isVersionPublished, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveIgnoredPackagePaths, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2268
- export type { AIConfig, AIPromptTarget, AIProviderName, AISocialConfig, AISystemPromptOverrides, AITargetConfig, BumpConfig, BumpOptions, BumpResult, BumpResultFalsy, BumpResultTruthy, ChangelogConfig, ChangelogInclude, ChangelogOptions, ClaudeCodeProviderOptions, ConfigType, GitProvider, GitlabRelease, GitlabReleaseResponse, HookConfig, HookStep, HookType, MonorepoConfig, OnRewrittenTag, PackageBase, PackageBumpEntry, PackageManager, PostedRelease, PrCommentConfig, PrCommentMode, PrCommentOptions, PrCommentStatus, ProviderReleaseOptions, ProviderReleaseResult, PublishConfig, PublishOptions, PublishResponse, PullRequestInfo, ReadPackage, Reference, RegistryTarget, ReleaseConfig, ReleaseContext, ReleaseOptions, RelizyConfig, RepoConfig, ResolvedConfig, ResolvedRelizyConfig, ResolvedTags, ResolvedTwitterCredentials, RootPackage, SlackCredentials, SlackOptions, SlackPackageEntry, SlackSocialConfig, SocialConfig, SocialNetworkResult, SocialOptions, SocialResult, Step, TemplatesConfig, TokensConfig, TwitterCredentials, TwitterOptions, TwitterSocialConfig, VersionMode };
2342
+ export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, assertBranchUpToDateWithRemote, assertReleaseTagsAvailable, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, buildReleaseSummary, bump, capReleaseTypeForZeroMajor, changelog, checkGitStatusIfDirty, collectContributorNames, collectPackageBumps, confirmBump, createCommitAndTags, createGitlabRelease, defineConfig, detectGitProvider, detectPackageManager, detectPullRequest, determinePublishTag, determineReleaseType, determineSemverChange, executeBuildCmd, executeFormatCmd, executeHook, expandPackagesToBumpWithDependents, extractChangelogSummary, extractVersionFromPackageTag, extractVersionFromTag, fetchGitTags, filterOutPrivatePackages, findGitHubPR, findGitLabMR, findReachableCommitBySubject, formatChangelogForSlack, formatPackagesForSlack, formatSlackMessage, formatTweetMessage, generateChangelog, generateMarkDown, getAuthCommand, getBumpedIndependentPackages, getBumpedPackageIndependently, getCIName, getCanaryVersion, getCommitChangedFiles, getCommitSubject, getCurrentGitBranch, getCurrentGitRef, getDefaultConfig, getDependentsOf, getFirstCommit, getGitDiff, getGitStatus, getGitUpstream, getIndependentTag, getLastPackageTag, getLastRepoTag, getLastStableTag, getLastTag, getModifiedReleaseFilePatterns, getNpmRegistry, getPackageCommits, getPackageDependencies, getPackageNewVersion, getPackages, getPackagesOrBumpedPackages, getPackagesToPublishInIndependentMode, getPackagesToPublishInSelectiveMode, getPreid, getReleaseTagNames, getReleaseUrl, getRootPackage, getShortCommitSha, getSlackToken, getSlackWebhookUrl, getTwitterCredentials, github, gitlab, globDirsAbsolute, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isCommitOnlyInIgnoredPackages, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, isVersionAlreadyPublishedError, isVersionPublished, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveIgnoredPackagePaths, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2343
+ export type { AIConfig, AIPromptTarget, AIProviderName, AISocialConfig, AISystemPromptOverrides, AITargetConfig, BumpConfig, BumpOptions, BumpResult, BumpResultFalsy, BumpResultTruthy, ChangelogConfig, ChangelogInclude, ChangelogOptions, ClaudeCodeProviderOptions, ConfigType, GitProvider, GitUpstream, GitlabRelease, GitlabReleaseResponse, HookConfig, HookStep, HookType, MonorepoConfig, OnRewrittenTag, PackageBase, PackageBumpEntry, PackageManager, PostedRelease, PrCommentConfig, PrCommentMode, PrCommentOptions, PrCommentStatus, ProviderReleaseOptions, ProviderReleaseResult, PublishConfig, PublishOptions, PublishResponse, PullRequestInfo, ReadPackage, Reference, RegistryTarget, ReleaseConfig, ReleaseContext, ReleaseOptions, RelizyConfig, RepoConfig, ResolvedConfig, ResolvedRelizyConfig, ResolvedTags, ResolvedTwitterCredentials, RootPackage, SlackCredentials, SlackOptions, SlackPackageEntry, SlackSocialConfig, SocialConfig, SocialNetworkResult, SocialOptions, SocialResult, Step, TemplatesConfig, TokensConfig, TwitterCredentials, TwitterOptions, TwitterSocialConfig, VersionMode };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LogLevel } from '@maz-ui/node';
2
- import { GitCommit, ChangelogConfig as ChangelogConfig$1, SemverBumpType } from 'changelogen';
2
+ import { RawGitCommit, GitCommit, ChangelogConfig as ChangelogConfig$1, SemverBumpType } from 'changelogen';
3
3
  import { ReleaseType } from 'semver';
4
4
  import { DeepPartial } from '@maz-ui/utils/ts-helpers/DeepPartial';
5
5
  import * as _slack_web_api from '@slack/web-api';
@@ -194,11 +194,61 @@ declare function createCommitAndTags({ config, noVerify, bumpedPackages, newVers
194
194
  dryRun?: boolean;
195
195
  logLevel: LogLevel;
196
196
  }): Promise<string[]>;
197
- declare function pushCommitAndTags({ config, dryRun, logLevel, cwd }: {
197
+ interface GitUpstream {
198
+ /** Remote name, e.g. `origin` */
199
+ remote: string;
200
+ /** Remote branch ref, e.g. `refs/heads/main` */
201
+ mergeRef: string;
202
+ /** Remote-tracking ref, e.g. `refs/remotes/origin/main` */
203
+ trackingRef: string;
204
+ /** Short remote-tracking name, e.g. `origin/main` */
205
+ name: string;
206
+ }
207
+ /**
208
+ * Resolve the upstream of the current branch. Returns null on a detached HEAD
209
+ * or when the branch has no upstream configured.
210
+ */
211
+ declare function getGitUpstream(cwd: string): Promise<GitUpstream | null>;
212
+ /**
213
+ * Ensure the current branch contains every commit of its remote counterpart.
214
+ *
215
+ * A release commit created on a branch that is behind its remote can never be
216
+ * pushed. Since packages are published before the push, running this check
217
+ * right before publishing prevents shipping a version whose release commit is
218
+ * then rejected (leaving an orphan tag and the repository stuck on the
219
+ * previous version).
220
+ *
221
+ * Skipped (no error) on a detached HEAD, without upstream, or when the remote
222
+ * cannot be fetched.
223
+ */
224
+ declare function assertBranchUpToDateWithRemote({ cwd }: {
225
+ cwd: string;
226
+ }): Promise<void>;
227
+ declare function pushCommitAndTags({ config, dryRun, logLevel, cwd, tags, }: {
198
228
  config: ResolvedRelizyConfig;
199
229
  dryRun: boolean;
200
230
  logLevel?: LogLevel;
201
231
  cwd: string;
232
+ tags?: string[];
233
+ }): Promise<void>;
234
+ /**
235
+ * Compute the git tag names a release will create, mirroring `createCommitAndTags`.
236
+ */
237
+ declare function getReleaseTagNames({ config, bumpedPackages, newVersion, }: {
238
+ config: ResolvedRelizyConfig;
239
+ bumpedPackages?: BumpResultTruthy['bumpedPackages'];
240
+ newVersion?: string;
241
+ }): string[];
242
+ /**
243
+ * Fail early when a tag the release is about to create already exists.
244
+ *
245
+ * This typically happens after a release that published its packages and
246
+ * pushed its tag, but whose release commit was rejected: the branch is still
247
+ * on the previous version, so the next run computes the same version again.
248
+ */
249
+ declare function assertReleaseTagsAvailable({ tags, cwd }: {
250
+ tags: string[];
251
+ cwd: string;
202
252
  }): Promise<void>;
203
253
  /**
204
254
  * Rollback modified files to their last committed state
@@ -249,6 +299,14 @@ declare function retagAnnotatedLocal({ tag, commit, message, signed, cwd, logLev
249
299
  * Force-push a single tag to origin. Used only after explicit confirmation.
250
300
  */
251
301
  declare function pushTagForce(tag: string, cwd?: string, logLevel?: LogLevel): Promise<void>;
302
+ /**
303
+ * Drop-in replacement for changelogen's `getGitDiff`, which splits the
304
+ * `git log` output on `----\n` and the subject line on `|`. A commit body
305
+ * containing a `----` line (GitHub adds `---------` before the
306
+ * `Co-authored-by:` trailers of a squash merge) then loses its trailers and its
307
+ * `--name-status` file list, and a subject containing `|` is truncated.
308
+ */
309
+ declare function getGitDiff(from: string | undefined, to?: string, cwd?: string): RawGitCommit[];
252
310
 
253
311
  declare function github(options: ProviderReleaseOptions): Promise<PostedRelease[]>;
254
312
 
@@ -869,23 +927,24 @@ declare function shouldFilterPrereleaseTags(currentVersion: string, graduating:
869
927
  */
870
928
  declare function extractVersionFromTag(tag: string, packageName?: string): string | null;
871
929
  /**
872
- * Checks if a tag's version is compatible with the current version.
873
- * A tag is compatible if its major version is less than or equal to the current major version.
930
+ * Computes a canary version with format `{nextVersion}-0.{preid}-{sha}`.
874
931
  *
875
- * This prevents accidentally using tags from future major versions (e.g., v5.0.0-beta.0)
876
- * when bumping a current stable version (e.g., 4.1.1 → 4.1.2).
932
+ * The leading numeric `0` prerelease identifier makes the canary sort below
933
+ * every named prerelease (alpha, beta, rc...) and the stable release of the
934
+ * same version, so it never matches a range like `^5.0.0-beta.0` and is never
935
+ * suggested by update tools. Keeping preid and sha in a single identifier also
936
+ * keeps the version valid when the sha is all digits with a leading zero.
877
937
  *
878
- * @param payload - The payload to check
938
+ * @param payload - The payload to compute the canary version from
879
939
  * @param payload.currentVersion - The current package version
880
- * @param payload.releaseType - The release type
940
+ * @param payload.releaseType - The release type detected from commits (defaults to patch)
881
941
  * @param payload.preid - The pre-release identifier
882
- * @param payload.sha - The sha of the commit
883
- * @returns true if the tag version's major is <= current major version
942
+ * @param payload.sha - The short sha of the commit
943
+ * @returns The canary version
884
944
  *
885
945
  * @example
886
- * isTagVersionCompatibleWithCurrent('4.1.1', '4.1.0') // true - same major
887
- * isTagVersionCompatibleWithCurrent('5.0.0-beta.0', '4.1.1') // false - newer major
888
- * isTagVersionCompatibleWithCurrent('3.9.9', '4.1.1') // true - older major
946
+ * getCanaryVersion({ currentVersion: '1.2.3', releaseType: 'minor', sha: 'a3f4b2c' }) // '1.3.0-0.canary-a3f4b2c'
947
+ * getCanaryVersion({ currentVersion: '5.0.0-beta.44', releaseType: 'patch', sha: 'a3f4b2c' }) // '5.0.0-0.canary-a3f4b2c'
889
948
  */
890
949
  declare function getCanaryVersion({ currentVersion, releaseType, preid, sha, }: {
891
950
  currentVersion: string;
@@ -893,6 +952,22 @@ declare function getCanaryVersion({ currentVersion, releaseType, preid, sha, }:
893
952
  preid?: string;
894
953
  sha: string;
895
954
  }): string;
955
+ /**
956
+ * Checks if a tag's version is compatible with the current version.
957
+ * A tag is compatible if its major version is less than or equal to the current major version.
958
+ *
959
+ * This prevents accidentally using tags from future major versions (e.g., v5.0.0-beta.0)
960
+ * when bumping a current stable version (e.g., 4.1.1 → 4.1.2).
961
+ *
962
+ * @param tagVersion - The version extracted from the tag
963
+ * @param currentVersion - The current package version
964
+ * @returns true if the tag version's major is <= current major version
965
+ *
966
+ * @example
967
+ * isTagVersionCompatibleWithCurrent('4.1.1', '4.1.0') // true - same major
968
+ * isTagVersionCompatibleWithCurrent('5.0.0-beta.0', '4.1.1') // false - newer major
969
+ * isTagVersionCompatibleWithCurrent('3.9.9', '4.1.1') // true - older major
970
+ */
896
971
  declare function isTagVersionCompatibleWithCurrent(tagVersion: string, currentVersion: string): boolean;
897
972
 
898
973
  type VersionMode = 'unified' | 'independent' | 'selective';
@@ -2264,5 +2339,5 @@ declare function socialSafetyCheck({ config }: {
2264
2339
  }): Promise<void>;
2265
2340
  declare function social(options?: Partial<SocialOptions>): Promise<SocialResult>;
2266
2341
 
2267
- export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, buildReleaseSummary, bump, capReleaseTypeForZeroMajor, changelog, checkGitStatusIfDirty, collectContributorNames, collectPackageBumps, confirmBump, createCommitAndTags, createGitlabRelease, defineConfig, detectGitProvider, detectPackageManager, detectPullRequest, determinePublishTag, determineReleaseType, determineSemverChange, executeBuildCmd, executeFormatCmd, executeHook, expandPackagesToBumpWithDependents, extractChangelogSummary, extractVersionFromPackageTag, extractVersionFromTag, fetchGitTags, filterOutPrivatePackages, findGitHubPR, findGitLabMR, findReachableCommitBySubject, formatChangelogForSlack, formatPackagesForSlack, formatSlackMessage, formatTweetMessage, generateChangelog, generateMarkDown, getAuthCommand, getBumpedIndependentPackages, getBumpedPackageIndependently, getCIName, getCanaryVersion, getCommitChangedFiles, getCommitSubject, getCurrentGitBranch, getCurrentGitRef, getDefaultConfig, getDependentsOf, getFirstCommit, getGitStatus, getIndependentTag, getLastPackageTag, getLastRepoTag, getLastStableTag, getLastTag, getModifiedReleaseFilePatterns, getNpmRegistry, getPackageCommits, getPackageDependencies, getPackageNewVersion, getPackages, getPackagesOrBumpedPackages, getPackagesToPublishInIndependentMode, getPackagesToPublishInSelectiveMode, getPreid, getReleaseUrl, getRootPackage, getShortCommitSha, getSlackToken, getSlackWebhookUrl, getTwitterCredentials, github, gitlab, globDirsAbsolute, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isCommitOnlyInIgnoredPackages, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, isVersionAlreadyPublishedError, isVersionPublished, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveIgnoredPackagePaths, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2268
- export type { AIConfig, AIPromptTarget, AIProviderName, AISocialConfig, AISystemPromptOverrides, AITargetConfig, BumpConfig, BumpOptions, BumpResult, BumpResultFalsy, BumpResultTruthy, ChangelogConfig, ChangelogInclude, ChangelogOptions, ClaudeCodeProviderOptions, ConfigType, GitProvider, GitlabRelease, GitlabReleaseResponse, HookConfig, HookStep, HookType, MonorepoConfig, OnRewrittenTag, PackageBase, PackageBumpEntry, PackageManager, PostedRelease, PrCommentConfig, PrCommentMode, PrCommentOptions, PrCommentStatus, ProviderReleaseOptions, ProviderReleaseResult, PublishConfig, PublishOptions, PublishResponse, PullRequestInfo, ReadPackage, Reference, RegistryTarget, ReleaseConfig, ReleaseContext, ReleaseOptions, RelizyConfig, RepoConfig, ResolvedConfig, ResolvedRelizyConfig, ResolvedTags, ResolvedTwitterCredentials, RootPackage, SlackCredentials, SlackOptions, SlackPackageEntry, SlackSocialConfig, SocialConfig, SocialNetworkResult, SocialOptions, SocialResult, Step, TemplatesConfig, TokensConfig, TwitterCredentials, TwitterOptions, TwitterSocialConfig, VersionMode };
2342
+ export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, assertBranchUpToDateWithRemote, assertReleaseTagsAvailable, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, buildReleaseSummary, bump, capReleaseTypeForZeroMajor, changelog, checkGitStatusIfDirty, collectContributorNames, collectPackageBumps, confirmBump, createCommitAndTags, createGitlabRelease, defineConfig, detectGitProvider, detectPackageManager, detectPullRequest, determinePublishTag, determineReleaseType, determineSemverChange, executeBuildCmd, executeFormatCmd, executeHook, expandPackagesToBumpWithDependents, extractChangelogSummary, extractVersionFromPackageTag, extractVersionFromTag, fetchGitTags, filterOutPrivatePackages, findGitHubPR, findGitLabMR, findReachableCommitBySubject, formatChangelogForSlack, formatPackagesForSlack, formatSlackMessage, formatTweetMessage, generateChangelog, generateMarkDown, getAuthCommand, getBumpedIndependentPackages, getBumpedPackageIndependently, getCIName, getCanaryVersion, getCommitChangedFiles, getCommitSubject, getCurrentGitBranch, getCurrentGitRef, getDefaultConfig, getDependentsOf, getFirstCommit, getGitDiff, getGitStatus, getGitUpstream, getIndependentTag, getLastPackageTag, getLastRepoTag, getLastStableTag, getLastTag, getModifiedReleaseFilePatterns, getNpmRegistry, getPackageCommits, getPackageDependencies, getPackageNewVersion, getPackages, getPackagesOrBumpedPackages, getPackagesToPublishInIndependentMode, getPackagesToPublishInSelectiveMode, getPreid, getReleaseTagNames, getReleaseUrl, getRootPackage, getShortCommitSha, getSlackToken, getSlackWebhookUrl, getTwitterCredentials, github, gitlab, globDirsAbsolute, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isCommitOnlyInIgnoredPackages, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, isVersionAlreadyPublishedError, isVersionPublished, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveIgnoredPackagePaths, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2343
+ export type { AIConfig, AIPromptTarget, AIProviderName, AISocialConfig, AISystemPromptOverrides, AITargetConfig, BumpConfig, BumpOptions, BumpResult, BumpResultFalsy, BumpResultTruthy, ChangelogConfig, ChangelogInclude, ChangelogOptions, ClaudeCodeProviderOptions, ConfigType, GitProvider, GitUpstream, GitlabRelease, GitlabReleaseResponse, HookConfig, HookStep, HookType, MonorepoConfig, OnRewrittenTag, PackageBase, PackageBumpEntry, PackageManager, PostedRelease, PrCommentConfig, PrCommentMode, PrCommentOptions, PrCommentStatus, ProviderReleaseOptions, ProviderReleaseResult, PublishConfig, PublishOptions, PublishResponse, PullRequestInfo, ReadPackage, Reference, RegistryTarget, ReleaseConfig, ReleaseContext, ReleaseOptions, RelizyConfig, RepoConfig, ResolvedConfig, ResolvedRelizyConfig, ResolvedTags, ResolvedTwitterCredentials, RootPackage, SlackCredentials, SlackOptions, SlackPackageEntry, SlackSocialConfig, SocialConfig, SocialNetworkResult, SocialOptions, SocialResult, Step, TemplatesConfig, TokensConfig, TwitterCredentials, TwitterOptions, TwitterSocialConfig, VersionMode };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { N as NEW_PACKAGE_MARKER, P as PR_COMMENT_MARKER, b as buildChangelogBody, a as buildCommentBody, c as buildCompareLink, d as buildContributors, e as buildReleaseSummary, f as bump, g as capReleaseTypeForZeroMajor, h as changelog, i as checkGitStatusIfDirty, j as collectContributorNames, k as collectPackageBumps, l as confirmBump, m as createCommitAndTags, n as createGitlabRelease, o as defineConfig, p as detectGitProvider, q as detectPackageManager, r as detectPullRequest, s as determinePublishTag, t as determineReleaseType, u as determineSemverChange, v as executeBuildCmd, w as executeFormatCmd, x as executeHook, y as expandPackagesToBumpWithDependents, z as extractChangelogSummary, A as extractVersionFromPackageTag, B as extractVersionFromTag, C as fetchGitTags, D as filterOutPrivatePackages, E as findGitHubPR, F as findGitLabMR, G as findReachableCommitBySubject, H as formatChangelogForSlack, I as formatPackagesForSlack, J as formatSlackMessage, K as formatTweetMessage, L as generateChangelog, M as generateMarkDown, O as getAuthCommand, Q as getBumpedIndependentPackages, R as getBumpedPackageIndependently, S as getCIName, T as getCanaryVersion, U as getCommitChangedFiles, V as getCommitSubject, W as getCurrentGitBranch, X as getCurrentGitRef, Y as getDefaultConfig, Z as getDependentsOf, _ as getFirstCommit, $ as getGitStatus, a0 as getIndependentTag, a1 as getLastPackageTag, a2 as getLastRepoTag, a3 as getLastStableTag, a4 as getLastTag, a5 as getModifiedReleaseFilePatterns, a6 as getNpmRegistry, a7 as getPackageCommits, a8 as getPackageDependencies, a9 as getPackageNewVersion, aa as getPackages, ab as getPackagesOrBumpedPackages, ac as getPackagesToPublishInIndependentMode, ad as getPackagesToPublishInSelectiveMode, ae as getPreid, af as getReleaseUrl, ag as getRootPackage, ah as getShortCommitSha, ai as getSlackToken, aj as getSlackWebhookUrl, ak as getTwitterCredentials, al as github, am as gitlab, an as globDirsAbsolute, ao as hasLernaJson, ap as isAncestor, aq as isBumpedPackage, ar as isChangedPreid, as as isCommitOnlyInIgnoredPackages, at as isGraduating, au as isGraduatingToStableBetweenVersion, av as isInCI, aw as isPrerelease, ax as isPrereleaseReleaseType, ay as isStableReleaseType, az as isTagVersionCompatibleWithCurrent, aA as isVersionAlreadyPublishedError, aB as isVersionPublished, aC as loadRelizyConfig, aD as mergeTypes, aE as parseChangelogMarkdown, aF as parseGitRemoteUrl, aG as postPrComment, aH as postReleaseToSlack, aI as postReleaseToTwitter, aJ as prComment, aK as providerRelease, aL as providerReleaseSafetyCheck, aM as publish, aN as publishPackage, aO as publishSafetyCheck, aP as pushCommitAndTags, aQ as pushTagForce, aR as readPackageJson, aS as readPackages, aT as reconcileFromTag, aU as redactSecrets, aV as release, aW as resetRewrittenTagCache, aX as resolveAllConfiguredRegistryTargets, aY as resolveIgnoredPackagePaths, aZ as resolveRegistryTargetsForPackage, a_ as resolveRegistryTargetsForPackages, a$ as resolveTags, b0 as retagAnnotatedLocal, b1 as rollbackModifiedFiles, b2 as shouldFilterPrereleaseTags, b3 as social, b4 as socialSafetyCheck, b5 as tagExists, b6 as topologicalSort, b7 as tryPostPrComment, b8 as updateLernaVersion, b9 as withRegistryNpmrc, ba as writeChangelogToFile, bb as writeVersion } from './shared/relizy.CobLJLr_.mjs';
1
+ export { N as NEW_PACKAGE_MARKER, P as PR_COMMENT_MARKER, a as assertBranchUpToDateWithRemote, b as assertReleaseTagsAvailable, c as buildChangelogBody, d as buildCommentBody, e as buildCompareLink, f as buildContributors, g as buildReleaseSummary, h as bump, i as capReleaseTypeForZeroMajor, j as changelog, k as checkGitStatusIfDirty, l as collectContributorNames, m as collectPackageBumps, n as confirmBump, o as createCommitAndTags, p as createGitlabRelease, q as defineConfig, r as detectGitProvider, s as detectPackageManager, t as detectPullRequest, u as determinePublishTag, v as determineReleaseType, w as determineSemverChange, x as executeBuildCmd, y as executeFormatCmd, z as executeHook, A as expandPackagesToBumpWithDependents, B as extractChangelogSummary, C as extractVersionFromPackageTag, D as extractVersionFromTag, E as fetchGitTags, F as filterOutPrivatePackages, G as findGitHubPR, H as findGitLabMR, I as findReachableCommitBySubject, J as formatChangelogForSlack, K as formatPackagesForSlack, L as formatSlackMessage, M as formatTweetMessage, O as generateChangelog, Q as generateMarkDown, R as getAuthCommand, S as getBumpedIndependentPackages, T as getBumpedPackageIndependently, U as getCIName, V as getCanaryVersion, W as getCommitChangedFiles, X as getCommitSubject, Y as getCurrentGitBranch, Z as getCurrentGitRef, _ as getDefaultConfig, $ as getDependentsOf, a0 as getFirstCommit, a1 as getGitDiff, a2 as getGitStatus, a3 as getGitUpstream, a4 as getIndependentTag, a5 as getLastPackageTag, a6 as getLastRepoTag, a7 as getLastStableTag, a8 as getLastTag, a9 as getModifiedReleaseFilePatterns, aa as getNpmRegistry, ab as getPackageCommits, ac as getPackageDependencies, ad as getPackageNewVersion, ae as getPackages, af as getPackagesOrBumpedPackages, ag as getPackagesToPublishInIndependentMode, ah as getPackagesToPublishInSelectiveMode, ai as getPreid, aj as getReleaseTagNames, ak as getReleaseUrl, al as getRootPackage, am as getShortCommitSha, an as getSlackToken, ao as getSlackWebhookUrl, ap as getTwitterCredentials, aq as github, ar as gitlab, as as globDirsAbsolute, at as hasLernaJson, au as isAncestor, av as isBumpedPackage, aw as isChangedPreid, ax as isCommitOnlyInIgnoredPackages, ay as isGraduating, az as isGraduatingToStableBetweenVersion, aA as isInCI, aB as isPrerelease, aC as isPrereleaseReleaseType, aD as isStableReleaseType, aE as isTagVersionCompatibleWithCurrent, aF as isVersionAlreadyPublishedError, aG as isVersionPublished, aH as loadRelizyConfig, aI as mergeTypes, aJ as parseChangelogMarkdown, aK as parseGitRemoteUrl, aL as postPrComment, aM as postReleaseToSlack, aN as postReleaseToTwitter, aO as prComment, aP as providerRelease, aQ as providerReleaseSafetyCheck, aR as publish, aS as publishPackage, aT as publishSafetyCheck, aU as pushCommitAndTags, aV as pushTagForce, aW as readPackageJson, aX as readPackages, aY as reconcileFromTag, aZ as redactSecrets, a_ as release, a$ as resetRewrittenTagCache, b0 as resolveAllConfiguredRegistryTargets, b1 as resolveIgnoredPackagePaths, b2 as resolveRegistryTargetsForPackage, b3 as resolveRegistryTargetsForPackages, b4 as resolveTags, b5 as retagAnnotatedLocal, b6 as rollbackModifiedFiles, b7 as shouldFilterPrereleaseTags, b8 as social, b9 as socialSafetyCheck, ba as tagExists, bb as topologicalSort, bc as tryPostPrComment, bd as updateLernaVersion, be as withRegistryNpmrc, bf as writeChangelogToFile, bg as writeVersion } from './shared/relizy.DhYBvZNi.mjs';
2
2
  import '@maz-ui/node';
3
3
  import 'node:child_process';
4
4
  import 'node:process';
@@ -3,7 +3,7 @@ import { execSync, spawnSync } from 'node:child_process';
3
3
  import process$1 from 'node:process';
4
4
  import { formatJson } from '@maz-ui/utils/helpers/formatJson';
5
5
  import { setupDotenv, loadConfig } from 'c12';
6
- import { getGitDiff, parseCommits, resolveRepoConfig, getRepoConfig, formatCompareChanges, formatReference, createGithubRelease } from 'changelogen';
6
+ import { parseCommits, resolveRepoConfig, getRepoConfig, formatCompareChanges, formatReference, createGithubRelease } from 'changelogen';
7
7
  import { defu } from 'defu';
8
8
  import { existsSync, readFileSync, statSync, writeFileSync, rmSync } from 'node:fs';
9
9
  import path, { join, relative, sep } from 'node:path';
@@ -202,6 +202,25 @@ async function pushTagForce(tag, cwd, logLevel) {
202
202
  { cwd, logLevel, noStderr: true, noStdout: true }
203
203
  );
204
204
  }
205
+ const COMMIT_SEPARATOR = "";
206
+ const FIELD_SEPARATOR = "";
207
+ function getGitDiff(from, to = "HEAD", cwd) {
208
+ const range = from ? `${from}...${to}` : to;
209
+ const output = execSync(
210
+ `git --no-pager log "${range}" --pretty="%x1E%s%x1F%h%x1F%an%x1F%ae%n%b" --name-status`,
211
+ { cwd, encoding: "utf8" }
212
+ ).trim();
213
+ return output.split(COMMIT_SEPARATOR).slice(1).map((record) => {
214
+ const [firstLine = "", ...body] = record.split("\n");
215
+ const [message = "", shortHash = "", authorName = "", authorEmail = ""] = firstLine.split(FIELD_SEPARATOR);
216
+ return {
217
+ message,
218
+ shortHash,
219
+ author: { name: authorName, email: authorEmail },
220
+ body: body.join("\n")
221
+ };
222
+ });
223
+ }
205
224
 
206
225
  const sessionCache = /* @__PURE__ */ new Map();
207
226
  function resetRewrittenTagCache() {
@@ -231,7 +250,7 @@ function buildExplanation({
231
250
  }) {
232
251
  const lines = [
233
252
  `\u26A0\uFE0F Tag "${from}" points to a commit that is no longer in the history of "${to}".`,
234
- `It was most likely rewritten by a "git rebase" after the tag was created.`,
253
+ `It was most likely rewritten by a "git rebase" after the tag was created, or its release commit was never pushed to "${to}" (rejected push during a release).`,
235
254
  `Generating a changelog from it would span the whole divergent range (often the entire history since the last stable release, with duplicated commits) instead of the real changes.`
236
255
  ];
237
256
  if (twin) {
@@ -764,7 +783,7 @@ async function getPackageCommits({
764
783
  from: actualFrom,
765
784
  to
766
785
  };
767
- const rawCommits = await getGitDiff(actualFrom, to, changelogConfig.cwd);
786
+ const rawCommits = getGitDiff(actualFrom, to, changelogConfig.cwd);
768
787
  const allCommits = parseCommits(rawCommits, changelogConfig);
769
788
  const hasBreakingChanges = allCommits.some((commit) => commit.isBreaking);
770
789
  logger.debug(`Has breaking changes: ${hasBreakingChanges}`);
@@ -1345,7 +1364,7 @@ function getCanaryVersion({
1345
1364
  if (!nextStable) {
1346
1365
  throw new Error(`Unable to compute canary version from "${currentVersion}" with release type "${effectiveType}"`);
1347
1366
  }
1348
- return `${nextStable}-${preid}.${sha}.0`;
1367
+ return `${nextStable}-0.${preid}-${sha}`;
1349
1368
  }
1350
1369
  function isTagVersionCompatibleWithCurrent(tagVersion, currentVersion) {
1351
1370
  try {
@@ -2421,7 +2440,7 @@ async function publishToRegistryTarget({
2421
2440
  return;
2422
2441
  }
2423
2442
  throw new Error(
2424
- `Version ${packageNameAndVersion} already exists on the registry${registryLabel} and cannot be overwritten. This usually happens when re-running a release on the same commit (e.g. a canary rerun). Enable publish.skipExistingVersions (or pass --skip-existing-versions) to skip already-published versions instead of failing.`,
2443
+ `Version ${packageNameAndVersion} already exists on the registry${registryLabel} and cannot be overwritten. This usually happens when re-running a release on the same commit (e.g. a canary rerun), or after a release that published this version but failed to push its release commit (merge its tag into your branch to recover). Enable publish.skipExistingVersions (or pass --skip-existing-versions) to skip already-published versions instead of failing.`,
2425
2444
  { cause: error }
2426
2445
  );
2427
2446
  } else {
@@ -2974,17 +2993,158 @@ async function createCommitAndTags({
2974
2993
  throw error;
2975
2994
  }
2976
2995
  }
2977
- async function pushCommitAndTags({ config, dryRun, logLevel, cwd }) {
2996
+ const quietExec = { noStderr: true, noStdout: true, noSuccess: true, noError: true };
2997
+ async function getGitUpstream(cwd) {
2998
+ try {
2999
+ const branch = getCurrentGitBranch(cwd);
3000
+ if (!branch || branch === "HEAD") {
3001
+ return null;
3002
+ }
3003
+ const [{ stdout: remote }, { stdout: mergeRef }, { stdout: trackingRef }] = await Promise.all([
3004
+ execPromise(`git config --get branch.${branch}.remote`, { cwd, ...quietExec }),
3005
+ execPromise(`git config --get branch.${branch}.merge`, { cwd, ...quietExec }),
3006
+ execPromise("git rev-parse --symbolic-full-name @{upstream}", { cwd, ...quietExec })
3007
+ ]);
3008
+ if (!remote.trim() || !mergeRef.trim() || !trackingRef.trim()) {
3009
+ return null;
3010
+ }
3011
+ return {
3012
+ remote: remote.trim(),
3013
+ mergeRef: mergeRef.trim(),
3014
+ trackingRef: trackingRef.trim(),
3015
+ name: trackingRef.trim().replace(/^refs\/remotes\//, "")
3016
+ };
3017
+ } catch {
3018
+ return null;
3019
+ }
3020
+ }
3021
+ async function fetchUpstream(upstream, cwd) {
3022
+ await execPromise(`git fetch ${upstream.remote} +${upstream.mergeRef}:${upstream.trackingRef}`, { cwd, ...quietExec });
3023
+ }
3024
+ async function assertBranchUpToDateWithRemote({ cwd }) {
3025
+ const upstream = await getGitUpstream(cwd);
3026
+ if (!upstream) {
3027
+ logger.debug("No upstream branch configured, skipping remote sync check");
3028
+ return;
3029
+ }
3030
+ try {
3031
+ await fetchUpstream(upstream, cwd);
3032
+ } catch (error) {
3033
+ logger.warn(`Could not fetch "${upstream.name}", skipping remote sync check`);
3034
+ logger.debug("Fetch error:", error);
3035
+ return;
3036
+ }
3037
+ const { stdout } = await execPromise(`git rev-list --count HEAD..${upstream.trackingRef}`, { cwd, ...quietExec });
3038
+ const missingCommits = Number.parseInt(stdout.trim(), 10) || 0;
3039
+ if (missingCommits > 0) {
3040
+ throw new Error(
3041
+ `The current branch is behind "${upstream.name}" by ${missingCommits} commit(s): new commits were pushed while the release was running.
3042
+ Releasing now would publish packages whose release commit cannot be pushed.
3043
+ Re-run the release from the latest commit of "${upstream.name}".`
3044
+ );
3045
+ }
3046
+ logger.debug(`Branch is up to date with "${upstream.name}"`);
3047
+ }
3048
+ function isPushRejectedAsBehind(error) {
3049
+ if (!error || typeof error !== "object") {
3050
+ return false;
3051
+ }
3052
+ const { message, stderr } = error;
3053
+ const output = `${typeof message === "string" ? message : ""}
3054
+ ${typeof stderr === "string" ? stderr : ""}`;
3055
+ return output.includes("(non-fast-forward)") || output.includes("(fetch first)");
3056
+ }
3057
+ async function recoverRejectedPush({
3058
+ config,
3059
+ command,
3060
+ tags,
3061
+ pushError,
3062
+ logLevel,
3063
+ cwd
3064
+ }) {
3065
+ const upstream = await getGitUpstream(cwd);
3066
+ if (!upstream) {
3067
+ throw pushError;
3068
+ }
3069
+ logger.warn(`Push rejected: "${upstream.name}" received new commits during the release. Merging them into the release commit and retrying...`);
3070
+ try {
3071
+ await fetchUpstream(upstream, cwd);
3072
+ const noVerifyFlag = config.release.noVerify ? " --no-verify" : "";
3073
+ await execPromise(`git merge --no-edit${noVerifyFlag} ${upstream.name}`, { cwd, ...quietExec });
3074
+ } catch (mergeError) {
3075
+ await execPromise("git merge --abort", { cwd, ...quietExec }).catch(() => {
3076
+ });
3077
+ if (tags.length > 0) {
3078
+ const tagRefs = tags.map((tag) => `refs/tags/${tag}`).join(" ");
3079
+ await execPromise(`git push ${upstream.remote} ${tagRefs}`, { cwd, ...quietExec }).catch(() => {
3080
+ });
3081
+ }
3082
+ const recoverRef = tags[0] ?? "HEAD";
3083
+ throw new Error(
3084
+ `Push rejected: "${upstream.name}" received new commits during the release, and they could not be merged automatically into the release commit.
3085
+ The packages are already published, but the release commit is not on the branch.
3086
+ To recover, merge it manually: git fetch --tags && git merge ${recoverRef} && git push`,
3087
+ { cause: mergeError }
3088
+ );
3089
+ }
3090
+ await execPromise(command, { noStderr: true, noStdout: true, logLevel, cwd });
3091
+ logger.success(`Merged "${upstream.name}" into the release commit and pushed`);
3092
+ }
3093
+ async function pushCommitAndTags({
3094
+ config,
3095
+ dryRun,
3096
+ logLevel,
3097
+ cwd,
3098
+ tags = []
3099
+ }) {
2978
3100
  logger.start("Start push changes and tags");
2979
3101
  const command = config.release.gitTag ? "git push --follow-tags" : "git push";
2980
3102
  if (dryRun) {
2981
3103
  logger.info(`[dry-run] ${command}`);
2982
3104
  } else {
2983
3105
  logger.debug(`Executing: ${command}`);
2984
- await execPromise(command, { noStderr: true, noStdout: true, logLevel, cwd });
3106
+ try {
3107
+ await execPromise(command, { noStderr: true, noStdout: true, noError: true, logLevel, cwd });
3108
+ } catch (error) {
3109
+ if (!isPushRejectedAsBehind(error)) {
3110
+ logger.error(`${command} failed`, error);
3111
+ throw error;
3112
+ }
3113
+ await recoverRejectedPush({ config, command, tags, pushError: error, logLevel, cwd });
3114
+ }
2985
3115
  }
2986
3116
  logger.success("Pushing changes and tags completed!");
2987
3117
  }
3118
+ function getReleaseTagNames({
3119
+ config,
3120
+ bumpedPackages,
3121
+ newVersion
3122
+ }) {
3123
+ if (!config.release.gitTag) {
3124
+ return [];
3125
+ }
3126
+ if (config.monorepo?.versionMode === "independent") {
3127
+ return (bumpedPackages ?? []).filter((pkg) => pkg.newVersion).map((pkg) => getIndependentTag({ name: pkg.name, version: pkg.newVersion }));
3128
+ }
3129
+ const version = newVersion || readPackageJson(config.cwd)?.version;
3130
+ const tagName = version ? config.templates.tagBody?.replaceAll("{{newVersion}}", version) : void 0;
3131
+ return tagName ? [tagName] : [];
3132
+ }
3133
+ async function assertReleaseTagsAvailable({ tags, cwd }) {
3134
+ for (const tag of tags) {
3135
+ if (!await tagExists(tag, cwd)) {
3136
+ continue;
3137
+ }
3138
+ if (await isAncestor(tag, "HEAD", cwd)) {
3139
+ throw new Error(`Tag "${tag}" already exists. Check the versions in your package.json files, they are behind the latest release.`);
3140
+ }
3141
+ throw new Error(
3142
+ `Tag "${tag}" already exists but is not part of the current branch history.
3143
+ A previous release most likely published this version and pushed its tag, but its release commit was never pushed to the branch.
3144
+ To recover, merge the release commit, then re-run the release: git fetch --tags && git merge ${tag} && git push`
3145
+ );
3146
+ }
3147
+ }
2988
3148
  async function rollbackModifiedFiles({
2989
3149
  config
2990
3150
  }) {
@@ -3061,6 +3221,7 @@ function getShortCommitSha(cwd, length = 7) {
3061
3221
 
3062
3222
  const CHANGELOG_RELEASE_HEAD_REGEX = /^#{2,}\s+(?:\S.*)?(v?(\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?)).*$/gm;
3063
3223
  const VERSION_REGEX = /^v?(\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?)$/;
3224
+ const GITHUB_NOREPLY_EMAIL_REGEX = /^(?:\d+\+)?([^@]+)@users\.noreply\.github\.com$/i;
3064
3225
  function buildCompareLink({ config, from, to, isFirstCommit }) {
3065
3226
  if (!config.repo || !from || !to) {
3066
3227
  return "";
@@ -3106,16 +3267,13 @@ function collectContributorNames({ commits, config }) {
3106
3267
  return [];
3107
3268
  }
3108
3269
  const names = /* @__PURE__ */ new Set();
3109
- for (const commit of commits) {
3110
- if (!commit.author) {
3111
- continue;
3112
- }
3113
- const name = formatName(commit.author.name);
3270
+ for (const author of commits.flatMap(getCommitAuthors)) {
3271
+ const name = formatName(author.name);
3114
3272
  if (!name || name.includes("[bot]")) {
3115
3273
  continue;
3116
3274
  }
3117
3275
  if (config.excludeAuthors && config.excludeAuthors.some(
3118
- (v) => name.includes(v) || commit.author.email?.includes(v)
3276
+ (v) => name.includes(v) || author.email?.includes(v)
3119
3277
  )) {
3120
3278
  continue;
3121
3279
  }
@@ -3123,6 +3281,12 @@ function collectContributorNames({ commits, config }) {
3123
3281
  }
3124
3282
  return Array.from(names);
3125
3283
  }
3284
+ function getCommitAuthors(commit) {
3285
+ return [commit.author, ...commit.authors ?? []].filter(Boolean);
3286
+ }
3287
+ function getGithubUsernameFromNoreplyEmail(email) {
3288
+ return GITHUB_NOREPLY_EMAIL_REGEX.exec(email)?.[1];
3289
+ }
3126
3290
  async function buildContributors({ commits, config }) {
3127
3291
  const names = collectContributorNames({ commits, config });
3128
3292
  if (names.length === 0) {
@@ -3132,13 +3296,11 @@ async function buildContributors({ commits, config }) {
3132
3296
  for (const name of names) {
3133
3297
  _authors.set(name, { email: /* @__PURE__ */ new Set(), name });
3134
3298
  }
3135
- for (const commit of commits) {
3136
- if (!commit.author)
3137
- continue;
3138
- const name = formatName(commit.author.name);
3299
+ for (const author of commits.flatMap(getCommitAuthors)) {
3300
+ const name = formatName(author.name);
3139
3301
  const entry = _authors.get(name);
3140
- if (entry && commit.author.email) {
3141
- entry.email.add(commit.author.email);
3302
+ if (entry && author.email) {
3303
+ entry.email.add(author.email);
3142
3304
  }
3143
3305
  }
3144
3306
  if (config.repo?.provider === "github") {
@@ -3148,6 +3310,11 @@ async function buildContributors({ commits, config }) {
3148
3310
  if (!meta) {
3149
3311
  return;
3150
3312
  }
3313
+ const noreplyUsername = [...meta.email].map(getGithubUsernameFromNoreplyEmail).find(Boolean);
3314
+ if (noreplyUsername) {
3315
+ meta.github = noreplyUsername;
3316
+ return;
3317
+ }
3151
3318
  for (const data of [...meta.email, meta.name]) {
3152
3319
  const { user } = await fetch$1(`https://ungh.cc/users/find/${data}`).then((r) => r.json()).catch(() => ({ user: null }));
3153
3320
  if (user) {
@@ -6652,6 +6819,20 @@ function getReleaseConfig(options = {}) {
6652
6819
  }
6653
6820
  });
6654
6821
  }
6822
+ function shouldPushReleaseCommit(config) {
6823
+ return Boolean(config.release.push && config.release.commit);
6824
+ }
6825
+ async function guardBeforePublish({ config, dryRun, guard }) {
6826
+ try {
6827
+ await guard();
6828
+ } catch (error) {
6829
+ if (!dryRun) {
6830
+ logger.fail("Release aborted before publishing, rolling back modified files...");
6831
+ await rollbackModifiedFiles({ config });
6832
+ }
6833
+ throw error;
6834
+ }
6835
+ }
6655
6836
  async function releaseSafetyCheck({
6656
6837
  config,
6657
6838
  provider
@@ -6665,7 +6846,8 @@ async function releaseSafetyCheck({
6665
6846
  await Promise.all([
6666
6847
  providerReleaseSafetyCheck({ config, provider }),
6667
6848
  publishSafetyCheck({ config }),
6668
- socialSafetyCheck({ config })
6849
+ socialSafetyCheck({ config }),
6850
+ ...shouldPushReleaseCommit(config) ? [assertBranchUpToDateWithRemote({ cwd: config.cwd })] : []
6669
6851
  ]);
6670
6852
  logger.success("Safety checks passed");
6671
6853
  } catch (error) {
@@ -6774,6 +6956,16 @@ async function release(options = {}) {
6774
6956
  });
6775
6957
  return;
6776
6958
  }
6959
+ if (config.release.commit) {
6960
+ await guardBeforePublish({
6961
+ config,
6962
+ dryRun,
6963
+ guard: () => assertReleaseTagsAvailable({
6964
+ tags: getReleaseTagNames({ config, bumpedPackages: bumpResult.bumpedPackages, newVersion: bumpResult.newVersion }),
6965
+ cwd: config.cwd
6966
+ })
6967
+ });
6968
+ }
6777
6969
  logger.box("Generate changelogs");
6778
6970
  if (config.release.changelog) {
6779
6971
  await changelog({
@@ -6793,6 +6985,13 @@ async function release(options = {}) {
6793
6985
  } else {
6794
6986
  logger.info("Skipping changelog generation (--no-changelog)");
6795
6987
  }
6988
+ if (shouldPushReleaseCommit(config)) {
6989
+ await guardBeforePublish({
6990
+ config,
6991
+ dryRun,
6992
+ guard: () => assertBranchUpToDateWithRemote({ cwd: config.cwd })
6993
+ });
6994
+ }
6796
6995
  logger.box("Publish packages to registry");
6797
6996
  let publishResponse;
6798
6997
  if (config.release.publish) {
@@ -6844,7 +7043,8 @@ async function release(options = {}) {
6844
7043
  config,
6845
7044
  dryRun,
6846
7045
  logLevel: config.logLevel,
6847
- cwd: config.cwd
7046
+ cwd: config.cwd,
7047
+ tags: createdTags
6848
7048
  });
6849
7049
  await executeHook("success:push", config, dryRun);
6850
7050
  } catch (error) {
@@ -6937,4 +7137,4 @@ async function release(options = {}) {
6937
7137
  }
6938
7138
  }
6939
7139
 
6940
- export { getGitStatus as $, extractVersionFromPackageTag as A, extractVersionFromTag as B, fetchGitTags as C, filterOutPrivatePackages as D, findGitHubPR as E, findGitLabMR as F, findReachableCommitBySubject as G, formatChangelogForSlack as H, formatPackagesForSlack as I, formatSlackMessage as J, formatTweetMessage as K, generateChangelog as L, generateMarkDown as M, NEW_PACKAGE_MARKER as N, getAuthCommand as O, PR_COMMENT_MARKER as P, getBumpedIndependentPackages as Q, getBumpedPackageIndependently as R, getCIName as S, getCanaryVersion as T, getCommitChangedFiles as U, getCommitSubject as V, getCurrentGitBranch as W, getCurrentGitRef as X, getDefaultConfig as Y, getDependentsOf as Z, getFirstCommit as _, buildCommentBody as a, resolveTags as a$, getIndependentTag as a0, getLastPackageTag as a1, getLastRepoTag as a2, getLastStableTag as a3, getLastTag as a4, getModifiedReleaseFilePatterns as a5, getNpmRegistry as a6, getPackageCommits as a7, getPackageDependencies as a8, getPackageNewVersion as a9, isVersionAlreadyPublishedError as aA, isVersionPublished as aB, loadRelizyConfig as aC, mergeTypes as aD, parseChangelogMarkdown as aE, parseGitRemoteUrl as aF, postPrComment as aG, postReleaseToSlack as aH, postReleaseToTwitter as aI, prComment as aJ, providerRelease as aK, providerReleaseSafetyCheck as aL, publish as aM, publishPackage as aN, publishSafetyCheck as aO, pushCommitAndTags as aP, pushTagForce as aQ, readPackageJson as aR, readPackages as aS, reconcileFromTag as aT, redactSecrets as aU, release as aV, resetRewrittenTagCache as aW, resolveAllConfiguredRegistryTargets as aX, resolveIgnoredPackagePaths as aY, resolveRegistryTargetsForPackage as aZ, resolveRegistryTargetsForPackages as a_, getPackages as aa, getPackagesOrBumpedPackages as ab, getPackagesToPublishInIndependentMode as ac, getPackagesToPublishInSelectiveMode as ad, getPreid as ae, getReleaseUrl as af, getRootPackage as ag, getShortCommitSha as ah, getSlackToken as ai, getSlackWebhookUrl as aj, getTwitterCredentials as ak, github as al, gitlab as am, globDirsAbsolute as an, hasLernaJson as ao, isAncestor as ap, isBumpedPackage as aq, isChangedPreid as ar, isCommitOnlyInIgnoredPackages as as, isGraduating as at, isGraduatingToStableBetweenVersion as au, isInCI as av, isPrerelease as aw, isPrereleaseReleaseType as ax, isStableReleaseType as ay, isTagVersionCompatibleWithCurrent as az, buildChangelogBody as b, retagAnnotatedLocal as b0, rollbackModifiedFiles as b1, shouldFilterPrereleaseTags as b2, social as b3, socialSafetyCheck as b4, tagExists as b5, topologicalSort as b6, tryPostPrComment as b7, updateLernaVersion as b8, withRegistryNpmrc as b9, writeChangelogToFile as ba, writeVersion as bb, buildCompareLink as c, buildContributors as d, buildReleaseSummary as e, bump as f, capReleaseTypeForZeroMajor as g, changelog as h, checkGitStatusIfDirty as i, collectContributorNames as j, collectPackageBumps as k, confirmBump as l, createCommitAndTags as m, createGitlabRelease as n, defineConfig as o, detectGitProvider as p, detectPackageManager as q, detectPullRequest as r, determinePublishTag as s, determineReleaseType as t, determineSemverChange as u, executeBuildCmd as v, executeFormatCmd as w, executeHook as x, expandPackagesToBumpWithDependents as y, extractChangelogSummary as z };
7140
+ export { getDependentsOf as $, expandPackagesToBumpWithDependents as A, extractChangelogSummary as B, extractVersionFromPackageTag as C, extractVersionFromTag as D, fetchGitTags as E, filterOutPrivatePackages as F, findGitHubPR as G, findGitLabMR as H, findReachableCommitBySubject as I, formatChangelogForSlack as J, formatPackagesForSlack as K, formatSlackMessage as L, formatTweetMessage as M, NEW_PACKAGE_MARKER as N, generateChangelog as O, PR_COMMENT_MARKER as P, generateMarkDown as Q, getAuthCommand as R, getBumpedIndependentPackages as S, getBumpedPackageIndependently as T, getCIName as U, getCanaryVersion as V, getCommitChangedFiles as W, getCommitSubject as X, getCurrentGitBranch as Y, getCurrentGitRef as Z, getDefaultConfig as _, assertBranchUpToDateWithRemote as a, resetRewrittenTagCache as a$, getFirstCommit as a0, getGitDiff as a1, getGitStatus as a2, getGitUpstream as a3, getIndependentTag as a4, getLastPackageTag as a5, getLastRepoTag as a6, getLastStableTag as a7, getLastTag as a8, getModifiedReleaseFilePatterns as a9, isInCI as aA, isPrerelease as aB, isPrereleaseReleaseType as aC, isStableReleaseType as aD, isTagVersionCompatibleWithCurrent as aE, isVersionAlreadyPublishedError as aF, isVersionPublished as aG, loadRelizyConfig as aH, mergeTypes as aI, parseChangelogMarkdown as aJ, parseGitRemoteUrl as aK, postPrComment as aL, postReleaseToSlack as aM, postReleaseToTwitter as aN, prComment as aO, providerRelease as aP, providerReleaseSafetyCheck as aQ, publish as aR, publishPackage as aS, publishSafetyCheck as aT, pushCommitAndTags as aU, pushTagForce as aV, readPackageJson as aW, readPackages as aX, reconcileFromTag as aY, redactSecrets as aZ, release as a_, getNpmRegistry as aa, getPackageCommits as ab, getPackageDependencies as ac, getPackageNewVersion as ad, getPackages as ae, getPackagesOrBumpedPackages as af, getPackagesToPublishInIndependentMode as ag, getPackagesToPublishInSelectiveMode as ah, getPreid as ai, getReleaseTagNames as aj, getReleaseUrl as ak, getRootPackage as al, getShortCommitSha as am, getSlackToken as an, getSlackWebhookUrl as ao, getTwitterCredentials as ap, github as aq, gitlab as ar, globDirsAbsolute as as, hasLernaJson as at, isAncestor as au, isBumpedPackage as av, isChangedPreid as aw, isCommitOnlyInIgnoredPackages as ax, isGraduating as ay, isGraduatingToStableBetweenVersion as az, assertReleaseTagsAvailable as b, resolveAllConfiguredRegistryTargets as b0, resolveIgnoredPackagePaths as b1, resolveRegistryTargetsForPackage as b2, resolveRegistryTargetsForPackages as b3, resolveTags as b4, retagAnnotatedLocal as b5, rollbackModifiedFiles as b6, shouldFilterPrereleaseTags as b7, social as b8, socialSafetyCheck as b9, tagExists as ba, topologicalSort as bb, tryPostPrComment as bc, updateLernaVersion as bd, withRegistryNpmrc as be, writeChangelogToFile as bf, writeVersion as bg, buildChangelogBody as c, buildCommentBody as d, buildCompareLink as e, buildContributors as f, buildReleaseSummary as g, bump as h, capReleaseTypeForZeroMajor as i, changelog as j, checkGitStatusIfDirty as k, collectContributorNames as l, collectPackageBumps as m, confirmBump as n, createCommitAndTags as o, createGitlabRelease as p, defineConfig as q, detectGitProvider as r, detectPackageManager as s, detectPullRequest as t, determinePublishTag as u, determineReleaseType as v, determineSemverChange as w, executeBuildCmd as x, executeFormatCmd as y, executeHook as z };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relizy",
3
3
  "type": "module",
4
- "version": "1.5.0",
4
+ "version": "1.6.0",
5
5
  "description": "Changelogen adapter for monorepo management with unified and independent versioning",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",