relizy 1.5.0-beta.2 → 1.5.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 { ar as isInCI, R as getCIName, e as bump, g as changelog, aG as publish, aE as providerRelease, aY as social, aD as prComment, aP as release } from './shared/relizy.B0MWtDvR.mjs';
8
+ import { as as isInCI, S as getCIName, f as bump, h as changelog, aJ as publish, aH as providerRelease, a$ as social, aG as prComment, aS as release } from './shared/relizy.CdiXgxMk.mjs';
9
9
  import 'node:child_process';
10
10
  import '@maz-ui/utils/helpers/formatJson';
11
11
  import 'c12';
@@ -109,7 +109,7 @@ program.command("changelog").description("Generate changelogs for all packages")
109
109
  process.exit(1);
110
110
  }
111
111
  });
112
- program.command("publish").description("Publish packages to registry").option("--registry <url>", "Custom registry URL").option("--tag <tag>", "Publish with specific tag (default: latest for stable, next for prerelease)").option("--access <type>", "Package access level (public or restricted)").option("--otp <code>", "One-time password for 2FA").option("--build-cmd <cmd>", 'Command to build packages before publish (e.g. "pnpm build")').option("--publish-token <token>", 'NPM token (e.g. "123456") - only supported for pnpm and npm').action(async (options) => {
112
+ program.command("publish").description("Publish packages to registry").option("--registry <url>", "Custom registry URL").option("--tag <tag>", "Publish with specific tag (default: latest for stable, next for prerelease)").option("--access <type>", "Package access level (public or restricted)").option("--otp <code>", "One-time password for 2FA").option("--build-cmd <cmd>", 'Command to build packages before publish (e.g. "pnpm build")').option("--publish-token <token>", 'NPM token (e.g. "123456") - only supported for pnpm and npm').option("--skip-existing-versions", "Skip packages whose version already exists on the registry instead of failing").action(async (options) => {
113
113
  try {
114
114
  await publish({
115
115
  registry: options.registry,
@@ -118,6 +118,7 @@ program.command("publish").description("Publish packages to registry").option("-
118
118
  otp: options.otp,
119
119
  buildCmd: options.buildCmd,
120
120
  token: options.publishToken,
121
+ skipExistingVersions: options.skipExistingVersions,
121
122
  dryRun: program.opts().dryRun,
122
123
  logLevel: program.opts().logLevel,
123
124
  configName: program.opts().config
@@ -173,7 +174,7 @@ program.command("pr-comment").description("Post or re-post a PR comment with rel
173
174
  process.exit(1);
174
175
  }
175
176
  });
176
- program.command("release").description("Complete release workflow (bump + changelog + commit + tag + push to remote + publish release)").option("--major", "Bump major version").option("--minor", "Bump minor version").option("--patch", "Bump patch version").option("--prerelease", "Bump prerelease version").option("--premajor", "Bump premajor version").option("--preminor", "Bump preminor version").option("--prepatch", "Bump prepatch version").option("--preid <id>", "Prerelease identifier (alpha, beta, rc, etc.)").option("--suffix <suffix>", "Custom suffix for prerelease versions - replace the last .X with .suffix (e.g. 1.0.0-beta.0 -> 1.0.0-beta.suffix)").option("--from <ref>", "Start commit reference").option("--to <ref>", "End commit reference").option("--no-push", "Skip push changes and tags to remote").option("--no-provider-release", "Skip release creation (GitHub/GitLab)").option("--no-publish", "Skip npm publish").option("--registry <url>", "Custom npm registry URL").option("--tag <tag>", "Publish with specific tag (default: latest for stable, next for prerelease)").option("--access <type>", "Package access level (public or restricted)").option("--otp <code>", "One-time password for 2FA").option("--no-verify", "Skip git hooks during commit").option("--format-cmd <cmd>", 'Command to format CHANGELOG files after generation (e.g. "pnpm lint")').option("--build-cmd <cmd>", 'Command to build packages before publish (e.g. "pnpm build")').option("--no-root-changelog", "Skip generation of root changelog file").option("--token <token>", "Git token (github or gitlab)").option("--force", "Bump even if there are no commits").option("--no-clean", "Skip check if the working directory is clean").option("--no-commit", "Skip commit and tag").option("--no-git-tag", "Skip tag creation").option("--no-changelog", "Skip changelog generation files").option("--provider <provider>", "Git provider (github or gitlab)").option("--no-social", "Skip social media posting").option("--no-pr-comment", "Skip PR comment posting").option("--yes", "Skip confirmation prompt about bumping packages").option("--publish-token <token>", 'NPM token (e.g. "123456") - only supported for pnpm and npm').option("--canary", "Publish a canary release from the current commit").option("--include-private", "Include private packages in bump and changelog phases").option("--ai", "Force-enable AI for release notes and social posts").option("--no-ai", "Force-disable AI for release notes and social posts").action(async (options) => {
177
+ program.command("release").description("Complete release workflow (bump + changelog + commit + tag + push to remote + publish release)").option("--major", "Bump major version").option("--minor", "Bump minor version").option("--patch", "Bump patch version").option("--prerelease", "Bump prerelease version").option("--premajor", "Bump premajor version").option("--preminor", "Bump preminor version").option("--prepatch", "Bump prepatch version").option("--preid <id>", "Prerelease identifier (alpha, beta, rc, etc.)").option("--suffix <suffix>", "Custom suffix for prerelease versions - replace the last .X with .suffix (e.g. 1.0.0-beta.0 -> 1.0.0-beta.suffix)").option("--from <ref>", "Start commit reference").option("--to <ref>", "End commit reference").option("--no-push", "Skip push changes and tags to remote").option("--no-provider-release", "Skip release creation (GitHub/GitLab)").option("--no-publish", "Skip npm publish").option("--registry <url>", "Custom npm registry URL").option("--tag <tag>", "Publish with specific tag (default: latest for stable, next for prerelease)").option("--access <type>", "Package access level (public or restricted)").option("--otp <code>", "One-time password for 2FA").option("--no-verify", "Skip git hooks during commit").option("--format-cmd <cmd>", 'Command to format CHANGELOG files after generation (e.g. "pnpm lint")').option("--build-cmd <cmd>", 'Command to build packages before publish (e.g. "pnpm build")').option("--no-root-changelog", "Skip generation of root changelog file").option("--token <token>", "Git token (github or gitlab)").option("--force", "Bump even if there are no commits").option("--no-clean", "Skip check if the working directory is clean").option("--no-commit", "Skip commit and tag").option("--no-git-tag", "Skip tag creation").option("--no-changelog", "Skip changelog generation files").option("--provider <provider>", "Git provider (github or gitlab)").option("--no-social", "Skip social media posting").option("--no-pr-comment", "Skip PR comment posting").option("--yes", "Skip confirmation prompt about bumping packages").option("--publish-token <token>", 'NPM token (e.g. "123456") - only supported for pnpm and npm').option("--skip-existing-versions", "Skip packages whose version already exists on the registry instead of failing").option("--canary", "Publish a canary release from the current commit").option("--include-private", "Include private packages in bump and changelog phases").option("--ai", "Force-enable AI for release notes and social posts").option("--no-ai", "Force-disable AI for release notes and social posts").action(async (options) => {
177
178
  try {
178
179
  await release({
179
180
  type: getReleaseType(options),
@@ -201,6 +202,7 @@ program.command("release").description("Complete release workflow (bump + change
201
202
  rootChangelog: hasCliFlag("--no-root-changelog") ? false : void 0,
202
203
  token: options.token,
203
204
  publishToken: options.publishToken,
205
+ skipExistingVersions: options.skipExistingVersions,
204
206
  logLevel: program.opts().logLevel,
205
207
  force: options.force,
206
208
  yes: options.yes,
package/dist/index.d.mts CHANGED
@@ -29,6 +29,7 @@ declare function getDefaultConfig(): {
29
29
  token: string | undefined;
30
30
  safetyCheck: true;
31
31
  safetyCheckTimeout: number;
32
+ skipExistingVersions: boolean;
32
33
  packageManager: PackageManager;
33
34
  };
34
35
  tokens: {
@@ -401,6 +402,45 @@ declare function withRegistryNpmrc<T>({ config, registryTarget, packageName, pac
401
402
  packageManager: PackageManager;
402
403
  fn: () => Promise<T>;
403
404
  }): Promise<T>;
405
+ /**
406
+ * Detect whether a failed publish is caused by the version already existing on
407
+ * the registry (the registry refusing to overwrite an immutable asset), as
408
+ * opposed to any other publish failure. This is what makes a retry safe: the
409
+ * artifact is already there, so the failure is really a "nothing to do".
410
+ *
411
+ * Registries phrase this differently, so we match the known variants:
412
+ * - npm public registry / verdaccio: `EPUBLISHCONFLICT`, `cannot publish over
413
+ * the previously published versions`, `you cannot publish over ...`;
414
+ * - Nexus: `Repository does not allow updating assets` (HTTP 400);
415
+ * - Artifactory/JFrog and others: `already exists` / `version already exists`;
416
+ * - a bare HTTP 409 Conflict from a registry that returns no message body.
417
+ */
418
+ declare function isVersionAlreadyPublishedError(error: unknown): boolean;
419
+ /**
420
+ * Ask the registry whether `pkg@version` already exists. This is the primary,
421
+ * robust idempotency check: it relies on the standard registry metadata
422
+ * endpoint that every npm-compatible registry (npm, Nexus, JFrog, Verdaccio,
423
+ * GitHub/GitLab Packages) implements to serve `install`, rather than on the
424
+ * wording of a publish error - which varies per registry.
425
+ *
426
+ * Strategy: for npm/pnpm, use the `view` command first (it inherits the full
427
+ * `.npmrc` environment - proxy, CA, scoped auth). If that is inconclusive, or
428
+ * for yarn/bun (no usable `view`), fall back to a direct HTTP request to the
429
+ * packument endpoint so every package manager is covered.
430
+ *
431
+ * Returns:
432
+ * - `true` - the version is already published;
433
+ * - `false` - the package, or that specific version, is not on the registry;
434
+ * - `null` - the check was inconclusive (auth/network/registry error), so the
435
+ * caller should fall back to attempting the publish rather than skipping.
436
+ */
437
+ declare function isVersionPublished({ pkg, version, config, packageManager, registryTarget, }: {
438
+ pkg: PackageBase;
439
+ version: string;
440
+ config: ResolvedRelizyConfig;
441
+ packageManager: PackageManager;
442
+ registryTarget: RegistryTarget;
443
+ }): Promise<boolean | null>;
404
444
  declare function getAuthCommand({ packageManager, config, otp, registryTarget, }: {
405
445
  packageManager: PackageManager;
406
446
  config: ResolvedRelizyConfig;
@@ -1367,6 +1407,17 @@ type PublishConfig = ChangelogConfig$1['publish'] & {
1367
1407
  * single-registry behavior.
1368
1408
  */
1369
1409
  registries?: RegistryTarget[];
1410
+ /**
1411
+ * Skip a package (per registry) instead of failing when its version already
1412
+ * exists on the registry. Makes re-running a release idempotent: a retried
1413
+ * job whose version is already published (e.g. a canary rerun on the same
1414
+ * commit, or a partially completed publish) succeeds and only publishes what
1415
+ * is missing, rather than crashing on the registry's "cannot overwrite"
1416
+ * error. When disabled (the default), that situation still fails, but with a
1417
+ * clear error message instead of the raw registry error.
1418
+ * @default false
1419
+ */
1420
+ skipExistingVersions?: boolean;
1370
1421
  };
1371
1422
  interface PublishOptions extends PublishConfig {
1372
1423
  /**
@@ -2104,6 +2155,19 @@ interface CommentBodyParams {
2104
2155
  }
2105
2156
  declare function buildCommentBody(params: CommentBodyParams): string;
2106
2157
  declare function prComment(options?: PrCommentOptions): Promise<boolean>;
2158
+ /**
2159
+ * Best-effort wrapper around {@link prComment} for the release flow: honours the
2160
+ * `--no-pr-comment` gate and swallows errors so a failed PR comment never aborts
2161
+ * an otherwise successful release.
2162
+ */
2163
+ declare function tryPostPrComment({ config, releaseContext, prNumber, dryRun, logLevel, configName, }: {
2164
+ config: ResolvedRelizyConfig;
2165
+ releaseContext: ReleaseContext;
2166
+ prNumber?: number;
2167
+ dryRun: boolean;
2168
+ logLevel?: string;
2169
+ configName?: string;
2170
+ }): Promise<boolean>;
2107
2171
 
2108
2172
  declare function providerReleaseSafetyCheck({ config, provider }: {
2109
2173
  config: ResolvedRelizyConfig;
@@ -2119,6 +2183,22 @@ declare function publish(options?: Partial<PublishOptions>): Promise<{
2119
2183
  publishedPackages: PackageBase[];
2120
2184
  } | undefined>;
2121
2185
 
2186
+ /**
2187
+ * Build the final "Release workflow completed!" summary box from the outcome of
2188
+ * each release step. Pure formatting - no side effects - so it stays easy to read
2189
+ * and to test in isolation, and keeps the `release()` orchestration lean.
2190
+ */
2191
+ declare function buildReleaseSummary({ config, bumpResult, publishResponse, createdTags, provider, postedReleases, providerError, socialResults, prCommentPosted, }: {
2192
+ config: ResolvedRelizyConfig;
2193
+ bumpResult: BumpResultTruthy;
2194
+ publishResponse?: PublishResponse;
2195
+ createdTags: string[];
2196
+ provider?: GitProvider;
2197
+ postedReleases: PostedRelease[];
2198
+ providerError?: string;
2199
+ socialResults?: SocialResult;
2200
+ prCommentPosted: boolean;
2201
+ }): string;
2122
2202
  declare function release(options?: Partial<ReleaseOptions>): Promise<void>;
2123
2203
 
2124
2204
  declare function socialSafetyCheck({ config }: {
@@ -2126,5 +2206,5 @@ declare function socialSafetyCheck({ config }: {
2126
2206
  }): Promise<void>;
2127
2207
  declare function social(options?: Partial<SocialOptions>): Promise<SocialResult>;
2128
2208
 
2129
- export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, 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, 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, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2209
+ 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, 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, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, 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, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2130
2210
  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 };
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ declare function getDefaultConfig(): {
29
29
  token: string | undefined;
30
30
  safetyCheck: true;
31
31
  safetyCheckTimeout: number;
32
+ skipExistingVersions: boolean;
32
33
  packageManager: PackageManager;
33
34
  };
34
35
  tokens: {
@@ -401,6 +402,45 @@ declare function withRegistryNpmrc<T>({ config, registryTarget, packageName, pac
401
402
  packageManager: PackageManager;
402
403
  fn: () => Promise<T>;
403
404
  }): Promise<T>;
405
+ /**
406
+ * Detect whether a failed publish is caused by the version already existing on
407
+ * the registry (the registry refusing to overwrite an immutable asset), as
408
+ * opposed to any other publish failure. This is what makes a retry safe: the
409
+ * artifact is already there, so the failure is really a "nothing to do".
410
+ *
411
+ * Registries phrase this differently, so we match the known variants:
412
+ * - npm public registry / verdaccio: `EPUBLISHCONFLICT`, `cannot publish over
413
+ * the previously published versions`, `you cannot publish over ...`;
414
+ * - Nexus: `Repository does not allow updating assets` (HTTP 400);
415
+ * - Artifactory/JFrog and others: `already exists` / `version already exists`;
416
+ * - a bare HTTP 409 Conflict from a registry that returns no message body.
417
+ */
418
+ declare function isVersionAlreadyPublishedError(error: unknown): boolean;
419
+ /**
420
+ * Ask the registry whether `pkg@version` already exists. This is the primary,
421
+ * robust idempotency check: it relies on the standard registry metadata
422
+ * endpoint that every npm-compatible registry (npm, Nexus, JFrog, Verdaccio,
423
+ * GitHub/GitLab Packages) implements to serve `install`, rather than on the
424
+ * wording of a publish error - which varies per registry.
425
+ *
426
+ * Strategy: for npm/pnpm, use the `view` command first (it inherits the full
427
+ * `.npmrc` environment - proxy, CA, scoped auth). If that is inconclusive, or
428
+ * for yarn/bun (no usable `view`), fall back to a direct HTTP request to the
429
+ * packument endpoint so every package manager is covered.
430
+ *
431
+ * Returns:
432
+ * - `true` - the version is already published;
433
+ * - `false` - the package, or that specific version, is not on the registry;
434
+ * - `null` - the check was inconclusive (auth/network/registry error), so the
435
+ * caller should fall back to attempting the publish rather than skipping.
436
+ */
437
+ declare function isVersionPublished({ pkg, version, config, packageManager, registryTarget, }: {
438
+ pkg: PackageBase;
439
+ version: string;
440
+ config: ResolvedRelizyConfig;
441
+ packageManager: PackageManager;
442
+ registryTarget: RegistryTarget;
443
+ }): Promise<boolean | null>;
404
444
  declare function getAuthCommand({ packageManager, config, otp, registryTarget, }: {
405
445
  packageManager: PackageManager;
406
446
  config: ResolvedRelizyConfig;
@@ -1367,6 +1407,17 @@ type PublishConfig = ChangelogConfig$1['publish'] & {
1367
1407
  * single-registry behavior.
1368
1408
  */
1369
1409
  registries?: RegistryTarget[];
1410
+ /**
1411
+ * Skip a package (per registry) instead of failing when its version already
1412
+ * exists on the registry. Makes re-running a release idempotent: a retried
1413
+ * job whose version is already published (e.g. a canary rerun on the same
1414
+ * commit, or a partially completed publish) succeeds and only publishes what
1415
+ * is missing, rather than crashing on the registry's "cannot overwrite"
1416
+ * error. When disabled (the default), that situation still fails, but with a
1417
+ * clear error message instead of the raw registry error.
1418
+ * @default false
1419
+ */
1420
+ skipExistingVersions?: boolean;
1370
1421
  };
1371
1422
  interface PublishOptions extends PublishConfig {
1372
1423
  /**
@@ -2104,6 +2155,19 @@ interface CommentBodyParams {
2104
2155
  }
2105
2156
  declare function buildCommentBody(params: CommentBodyParams): string;
2106
2157
  declare function prComment(options?: PrCommentOptions): Promise<boolean>;
2158
+ /**
2159
+ * Best-effort wrapper around {@link prComment} for the release flow: honours the
2160
+ * `--no-pr-comment` gate and swallows errors so a failed PR comment never aborts
2161
+ * an otherwise successful release.
2162
+ */
2163
+ declare function tryPostPrComment({ config, releaseContext, prNumber, dryRun, logLevel, configName, }: {
2164
+ config: ResolvedRelizyConfig;
2165
+ releaseContext: ReleaseContext;
2166
+ prNumber?: number;
2167
+ dryRun: boolean;
2168
+ logLevel?: string;
2169
+ configName?: string;
2170
+ }): Promise<boolean>;
2107
2171
 
2108
2172
  declare function providerReleaseSafetyCheck({ config, provider }: {
2109
2173
  config: ResolvedRelizyConfig;
@@ -2119,6 +2183,22 @@ declare function publish(options?: Partial<PublishOptions>): Promise<{
2119
2183
  publishedPackages: PackageBase[];
2120
2184
  } | undefined>;
2121
2185
 
2186
+ /**
2187
+ * Build the final "Release workflow completed!" summary box from the outcome of
2188
+ * each release step. Pure formatting - no side effects - so it stays easy to read
2189
+ * and to test in isolation, and keeps the `release()` orchestration lean.
2190
+ */
2191
+ declare function buildReleaseSummary({ config, bumpResult, publishResponse, createdTags, provider, postedReleases, providerError, socialResults, prCommentPosted, }: {
2192
+ config: ResolvedRelizyConfig;
2193
+ bumpResult: BumpResultTruthy;
2194
+ publishResponse?: PublishResponse;
2195
+ createdTags: string[];
2196
+ provider?: GitProvider;
2197
+ postedReleases: PostedRelease[];
2198
+ providerError?: string;
2199
+ socialResults?: SocialResult;
2200
+ prCommentPosted: boolean;
2201
+ }): string;
2122
2202
  declare function release(options?: Partial<ReleaseOptions>): Promise<void>;
2123
2203
 
2124
2204
  declare function socialSafetyCheck({ config }: {
@@ -2126,5 +2206,5 @@ declare function socialSafetyCheck({ config }: {
2126
2206
  }): Promise<void>;
2127
2207
  declare function social(options?: Partial<SocialOptions>): Promise<SocialResult>;
2128
2208
 
2129
- export { NEW_PACKAGE_MARKER, PR_COMMENT_MARKER, buildChangelogBody, buildCommentBody, buildCompareLink, buildContributors, 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, 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, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, isGraduating, isGraduatingToStableBetweenVersion, isInCI, isPrerelease, isPrereleaseReleaseType, isStableReleaseType, isTagVersionCompatibleWithCurrent, loadRelizyConfig, mergeTypes, parseChangelogMarkdown, parseGitRemoteUrl, postPrComment, postReleaseToSlack, postReleaseToTwitter, prComment, providerRelease, providerReleaseSafetyCheck, publish, publishPackage, publishSafetyCheck, pushCommitAndTags, pushTagForce, readPackageJson, readPackages, reconcileFromTag, redactSecrets, release, resetRewrittenTagCache, resolveAllConfiguredRegistryTargets, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2209
+ 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, 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, hasLernaJson, isAncestor, isBumpedPackage, isChangedPreid, 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, resolveRegistryTargetsForPackage, resolveRegistryTargetsForPackages, resolveTags, retagAnnotatedLocal, rollbackModifiedFiles, shouldFilterPrereleaseTags, social, socialSafetyCheck, tagExists, topologicalSort, tryPostPrComment, updateLernaVersion, withRegistryNpmrc, writeChangelogToFile, writeVersion };
2130
2210
  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 };
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 bump, f as capReleaseTypeForZeroMajor, g as changelog, h as checkGitStatusIfDirty, i as collectContributorNames, j as collectPackageBumps, k as confirmBump, l as createCommitAndTags, m as createGitlabRelease, n as defineConfig, o as detectGitProvider, p as detectPackageManager, q as detectPullRequest, r as determinePublishTag, s as determineReleaseType, t as determineSemverChange, u as executeBuildCmd, v as executeFormatCmd, w as executeHook, x as expandPackagesToBumpWithDependents, y as extractChangelogSummary, z as extractVersionFromPackageTag, A as extractVersionFromTag, B as fetchGitTags, C as filterOutPrivatePackages, D as findGitHubPR, E as findGitLabMR, F as findReachableCommitBySubject, G as formatChangelogForSlack, H as formatPackagesForSlack, I as formatSlackMessage, J as formatTweetMessage, K as generateChangelog, L as generateMarkDown, M as getAuthCommand, O as getBumpedIndependentPackages, Q as getBumpedPackageIndependently, R as getCIName, S as getCanaryVersion, T as getCommitSubject, U as getCurrentGitBranch, V as getCurrentGitRef, W as getDefaultConfig, X as getDependentsOf, Y as getFirstCommit, Z as getGitStatus, _ as getIndependentTag, $ as getLastPackageTag, a0 as getLastRepoTag, a1 as getLastStableTag, a2 as getLastTag, a3 as getModifiedReleaseFilePatterns, a4 as getNpmRegistry, a5 as getPackageCommits, a6 as getPackageDependencies, a7 as getPackageNewVersion, a8 as getPackages, a9 as getPackagesOrBumpedPackages, aa as getPackagesToPublishInIndependentMode, ab as getPackagesToPublishInSelectiveMode, ac as getPreid, ad as getReleaseUrl, ae as getRootPackage, af as getShortCommitSha, ag as getSlackToken, ah as getSlackWebhookUrl, ai as getTwitterCredentials, aj as github, ak as gitlab, al as hasLernaJson, am as isAncestor, an as isBumpedPackage, ao as isChangedPreid, ap as isGraduating, aq as isGraduatingToStableBetweenVersion, ar as isInCI, as as isPrerelease, at as isPrereleaseReleaseType, au as isStableReleaseType, av as isTagVersionCompatibleWithCurrent, aw as loadRelizyConfig, ax as mergeTypes, ay as parseChangelogMarkdown, az as parseGitRemoteUrl, aA as postPrComment, aB as postReleaseToSlack, aC as postReleaseToTwitter, aD as prComment, aE as providerRelease, aF as providerReleaseSafetyCheck, aG as publish, aH as publishPackage, aI as publishSafetyCheck, aJ as pushCommitAndTags, aK as pushTagForce, aL as readPackageJson, aM as readPackages, aN as reconcileFromTag, aO as redactSecrets, aP as release, aQ as resetRewrittenTagCache, aR as resolveAllConfiguredRegistryTargets, aS as resolveRegistryTargetsForPackage, aT as resolveRegistryTargetsForPackages, aU as resolveTags, aV as retagAnnotatedLocal, aW as rollbackModifiedFiles, aX as shouldFilterPrereleaseTags, aY as social, aZ as socialSafetyCheck, a_ as tagExists, a$ as topologicalSort, b0 as updateLernaVersion, b1 as withRegistryNpmrc, b2 as writeChangelogToFile, b3 as writeVersion } from './shared/relizy.B0MWtDvR.mjs';
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 getCommitSubject, V as getCurrentGitBranch, W as getCurrentGitRef, X as getDefaultConfig, Y as getDependentsOf, Z as getFirstCommit, _ as getGitStatus, $ as getIndependentTag, a0 as getLastPackageTag, a1 as getLastRepoTag, a2 as getLastStableTag, a3 as getLastTag, a4 as getModifiedReleaseFilePatterns, a5 as getNpmRegistry, a6 as getPackageCommits, a7 as getPackageDependencies, a8 as getPackageNewVersion, a9 as getPackages, aa as getPackagesOrBumpedPackages, ab as getPackagesToPublishInIndependentMode, ac as getPackagesToPublishInSelectiveMode, ad as getPreid, ae as getReleaseUrl, af as getRootPackage, ag as getShortCommitSha, ah as getSlackToken, ai as getSlackWebhookUrl, aj as getTwitterCredentials, ak as github, al as gitlab, am as hasLernaJson, an as isAncestor, ao as isBumpedPackage, ap as isChangedPreid, aq as isGraduating, ar as isGraduatingToStableBetweenVersion, as as isInCI, at as isPrerelease, au as isPrereleaseReleaseType, av as isStableReleaseType, aw as isTagVersionCompatibleWithCurrent, ax as isVersionAlreadyPublishedError, ay as isVersionPublished, az as loadRelizyConfig, aA as mergeTypes, aB as parseChangelogMarkdown, aC as parseGitRemoteUrl, aD as postPrComment, aE as postReleaseToSlack, aF as postReleaseToTwitter, aG as prComment, aH as providerRelease, aI as providerReleaseSafetyCheck, aJ as publish, aK as publishPackage, aL as publishSafetyCheck, aM as pushCommitAndTags, aN as pushTagForce, aO as readPackageJson, aP as readPackages, aQ as reconcileFromTag, aR as redactSecrets, aS as release, aT as resetRewrittenTagCache, aU as resolveAllConfiguredRegistryTargets, aV as resolveRegistryTargetsForPackage, aW as resolveRegistryTargetsForPackages, aX as resolveTags, aY as retagAnnotatedLocal, aZ as rollbackModifiedFiles, a_ as shouldFilterPrereleaseTags, a$ as social, b0 as socialSafetyCheck, b1 as tagExists, b2 as topologicalSort, b3 as tryPostPrComment, b4 as updateLernaVersion, b5 as withRegistryNpmrc, b6 as writeChangelogToFile, b7 as writeVersion } from './shared/relizy.CdiXgxMk.mjs';
2
2
  import '@maz-ui/node';
3
3
  import 'node:child_process';
4
4
  import 'node:process';
@@ -2079,6 +2079,127 @@ function isOtpError(error) {
2079
2079
  ];
2080
2080
  return otpPatterns.some((pattern) => searchText.includes(pattern));
2081
2081
  }
2082
+ function isVersionAlreadyPublishedError(error) {
2083
+ if (typeof error !== "object" || error === null) {
2084
+ return false;
2085
+ }
2086
+ const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2087
+ const searchText = `${errorMessage} ${String(error).toLowerCase()}`;
2088
+ const patterns = [
2089
+ "epublishconflict",
2090
+ "cannot publish over",
2091
+ "does not allow updating assets",
2092
+ "version already exists",
2093
+ "already exists in the repository",
2094
+ "status 409",
2095
+ "409 conflict"
2096
+ ];
2097
+ return patterns.some((pattern) => searchText.includes(pattern));
2098
+ }
2099
+ function canQueryVersionViaView(packageManager) {
2100
+ return packageManager === "npm" || packageManager === "pnpm";
2101
+ }
2102
+ function isPackageNotFoundError(error) {
2103
+ if (typeof error !== "object" || error === null) {
2104
+ return false;
2105
+ }
2106
+ const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2107
+ const searchText = `${errorMessage} ${String(error).toLowerCase()}`;
2108
+ return searchText.includes("e404") || searchText.includes("404 not found") || searchText.includes("is not in this registry") || searchText.includes("no match found");
2109
+ }
2110
+ async function queryVersionViaView({
2111
+ pkg,
2112
+ version,
2113
+ config,
2114
+ packageManager,
2115
+ registryTarget
2116
+ }) {
2117
+ const args = ["view", `${pkg.name}@${version}`, "version", "--json"];
2118
+ if (registryTarget.registry) {
2119
+ args.push("--registry", registryTarget.registry);
2120
+ }
2121
+ const command = `${packageManager} ${args.join(" ")}`;
2122
+ try {
2123
+ const { stdout } = await withRegistryNpmrc({
2124
+ config,
2125
+ registryTarget,
2126
+ packageName: pkg.name,
2127
+ packageManager,
2128
+ fn: () => execPromise(command, {
2129
+ cwd: pkg.path,
2130
+ noStdout: true,
2131
+ noStderr: true,
2132
+ noSuccess: true,
2133
+ noError: true,
2134
+ logLevel: config.logLevel
2135
+ })
2136
+ });
2137
+ const output = (stdout || "").trim();
2138
+ return output.length > 0 && output !== "undefined" && output !== "[]";
2139
+ } catch (error) {
2140
+ if (isPackageNotFoundError(error)) {
2141
+ return false;
2142
+ }
2143
+ logger.debug(`\`view\` check was inconclusive for ${pkg.name}@${version}: ${error instanceof Error ? error.message : String(error)}`);
2144
+ return null;
2145
+ }
2146
+ }
2147
+ function buildPackumentUrl(registry, packageName) {
2148
+ const base = registry.endsWith("/") ? registry : `${registry}/`;
2149
+ const encodedName = packageName.replace("/", "%2F");
2150
+ return `${base}${encodedName}`;
2151
+ }
2152
+ async function queryVersionViaHttp({
2153
+ pkg,
2154
+ version,
2155
+ config,
2156
+ registryTarget
2157
+ }) {
2158
+ const registry = registryTarget.registry || getNpmRegistry(config.cwd);
2159
+ if (!registry) {
2160
+ return null;
2161
+ }
2162
+ const token = registryTarget.token || config.publish.token || config.tokens.registry;
2163
+ const headers = { accept: "application/json" };
2164
+ if (token) {
2165
+ headers.authorization = `Bearer ${token}`;
2166
+ }
2167
+ const timeoutMs = config.publish.safetyCheckTimeout ?? 15e3;
2168
+ try {
2169
+ const response = await fetch(buildPackumentUrl(registry, pkg.name), {
2170
+ headers,
2171
+ signal: AbortSignal.timeout(timeoutMs)
2172
+ });
2173
+ if (response.status === 404) {
2174
+ return false;
2175
+ }
2176
+ if (!response.ok) {
2177
+ logger.debug(`Packument request for ${pkg.name} returned HTTP ${response.status} - treating as inconclusive`);
2178
+ return null;
2179
+ }
2180
+ const packument = await response.json();
2181
+ return Boolean(packument.versions && Object.hasOwn(packument.versions, version));
2182
+ } catch (error) {
2183
+ logger.debug(`Packument request for ${pkg.name} failed: ${error instanceof Error ? error.message : String(error)}`);
2184
+ return null;
2185
+ }
2186
+ }
2187
+ async function isVersionPublished({
2188
+ pkg,
2189
+ version,
2190
+ config,
2191
+ packageManager,
2192
+ registryTarget
2193
+ }) {
2194
+ if (canQueryVersionViaView(packageManager)) {
2195
+ const viaView = await queryVersionViaView({ pkg, version, config, packageManager, registryTarget });
2196
+ if (viaView !== null) {
2197
+ return viaView;
2198
+ }
2199
+ logger.debug(`Falling back to a direct registry request for ${pkg.name}@${version}`);
2200
+ }
2201
+ return queryVersionViaHttp({ pkg, version, config, registryTarget });
2202
+ }
2082
2203
  function promptOtpWithTimeout(timeout = 9e4) {
2083
2204
  return new Promise((resolve, reject) => {
2084
2205
  const timer = setTimeout(() => {
@@ -2178,6 +2299,14 @@ async function publishToRegistryTarget({
2178
2299
  registryLabel
2179
2300
  }) {
2180
2301
  const tag = determinePublishTag(pkg.newVersion || pkg.version, registryTarget.tag ?? config.publish.tag);
2302
+ if (config.publish.skipExistingVersions && !dryRun) {
2303
+ const version = pkg.newVersion || pkg.version;
2304
+ const alreadyPublished = await isVersionPublished({ pkg, version, config, packageManager, registryTarget });
2305
+ if (alreadyPublished) {
2306
+ logger.warn(`Skipping ${packageNameAndVersion}${registryLabel}: version already exists on the registry (publish.skipExistingVersions enabled)`);
2307
+ return;
2308
+ }
2309
+ }
2181
2310
  let dynamicOtp;
2182
2311
  const maxAttempts = 2;
2183
2312
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -2215,6 +2344,15 @@ async function publishToRegistryTarget({
2215
2344
  } catch (error) {
2216
2345
  if (isOtpError(error) && attempt < maxAttempts - 1) {
2217
2346
  dynamicOtp = await handleOtpError();
2347
+ } else if (isVersionAlreadyPublishedError(error)) {
2348
+ if (config.publish.skipExistingVersions) {
2349
+ logger.warn(`Skipping ${packageNameAndVersion}${registryLabel}: version already exists on the registry (publish.skipExistingVersions enabled)`);
2350
+ return;
2351
+ }
2352
+ throw new Error(
2353
+ `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.`,
2354
+ { cause: error }
2355
+ );
2218
2356
  } else {
2219
2357
  logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2220
2358
  throw error;
@@ -2372,6 +2510,9 @@ function getDefaultConfig() {
2372
2510
  // (e.g. a corporate proxy) is honored instead of forcing the public one.
2373
2511
  safetyCheck: true,
2374
2512
  safetyCheckTimeout: 15e3,
2513
+ // Annotated as `boolean` (not the inferred `false` literal) so the resolved
2514
+ // config type stays assignable to true when overridden by config/CLI.
2515
+ skipExistingVersions: false,
2375
2516
  packageManager: detectPackageManager(process$1.cwd())
2376
2517
  },
2377
2518
  tokens: {
@@ -3910,10 +4051,11 @@ function collectPackageBumps({
3910
4051
  }) {
3911
4052
  if (bumpedPackages && bumpedPackages.length > 0) {
3912
4053
  return bumpedPackages.map((pkg) => {
3913
- const hasTransition = Boolean(pkg.newVersion && pkg.oldVersion !== pkg.newVersion);
4054
+ const displayOldVersion = extractVersionFromTag(pkg.fromTag, pkg.name) || pkg.oldVersion;
4055
+ const hasTransition = Boolean(pkg.newVersion && displayOldVersion !== pkg.newVersion);
3914
4056
  return {
3915
4057
  name: pkg.name,
3916
- oldVersion: pkg.oldVersion,
4058
+ oldVersion: displayOldVersion,
3917
4059
  newVersion: pkg.newVersion,
3918
4060
  version: pkg.newVersion || pkg.version,
3919
4061
  hasTransition
@@ -5500,9 +5642,10 @@ function buildSuccessComment({
5500
5642
  const distTag = config.publish?.tag;
5501
5643
  const installCmd = getInstallCommand(config.publish?.packageManager);
5502
5644
  const lines = [PR_COMMENT_MARKER, "", "## \u{1F680} Release published", ""];
5645
+ const previousVersion = extractVersionFromTag(bumpResult?.fromTag ?? "") || bumpResult?.oldVersion;
5503
5646
  lines.push(...buildMetadataLines({
5504
5647
  version,
5505
- oldVersion: bumpResult?.oldVersion,
5648
+ oldVersion: previousVersion,
5506
5649
  tags,
5507
5650
  distTag,
5508
5651
  date,
@@ -5617,6 +5760,32 @@ ${body}`
5617
5760
  }
5618
5761
  return await postPrComment({ config, pr, body });
5619
5762
  }
5763
+ async function tryPostPrComment({
5764
+ config,
5765
+ releaseContext,
5766
+ prNumber,
5767
+ dryRun,
5768
+ logLevel,
5769
+ configName
5770
+ }) {
5771
+ if (!config.release.prComment) {
5772
+ logger.info("Skipping PR comment (--no-pr-comment)");
5773
+ return false;
5774
+ }
5775
+ try {
5776
+ return await prComment({
5777
+ prNumber,
5778
+ dryRun,
5779
+ logLevel,
5780
+ configName,
5781
+ config,
5782
+ releaseContext
5783
+ });
5784
+ } catch (error) {
5785
+ logger.warn("PR comment posting failed:", error);
5786
+ return false;
5787
+ }
5788
+ }
5620
5789
 
5621
5790
  async function providerReleaseSafetyCheck({ config, provider }) {
5622
5791
  if (!config.safetyCheck || !config.release.providerRelease) {
@@ -5801,7 +5970,8 @@ async function publish(options = {}) {
5801
5970
  registries: options.registries,
5802
5971
  tag: options.tag,
5803
5972
  buildCmd: options.buildCmd,
5804
- token: options.token
5973
+ token: options.token,
5974
+ skipExistingVersions: options.skipExistingVersions
5805
5975
  },
5806
5976
  logLevel: options.logLevel,
5807
5977
  safetyCheck: options.safetyCheck
@@ -6388,7 +6558,8 @@ function getReleaseConfig(options = {}) {
6388
6558
  registries: options.registries,
6389
6559
  tag: options.tag,
6390
6560
  buildCmd: options.buildCmd,
6391
- token: options.publishToken
6561
+ token: options.publishToken,
6562
+ skipExistingVersions: options.skipExistingVersions
6392
6563
  },
6393
6564
  release: {
6394
6565
  commit: options.commit,
@@ -6428,31 +6599,51 @@ async function releaseSafetyCheck({
6428
6599
  throw error;
6429
6600
  }
6430
6601
  }
6431
- async function tryPostPrComment({
6602
+ function buildReleaseSummary({
6432
6603
  config,
6433
- releaseContext,
6434
- prNumber,
6435
- dryRun,
6436
- logLevel,
6437
- configName
6604
+ bumpResult,
6605
+ publishResponse,
6606
+ createdTags,
6607
+ provider,
6608
+ postedReleases,
6609
+ providerError,
6610
+ socialResults,
6611
+ prCommentPosted
6438
6612
  }) {
6439
- if (!config.release.prComment) {
6440
- logger.info("Skipping PR comment (--no-pr-comment)");
6441
- return false;
6613
+ const publishedPackageCount = publishResponse?.publishedPackages.length ?? 0;
6614
+ const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || readPackageJson(config.cwd)?.version;
6615
+ let providerDisplay = "Disabled";
6616
+ if (config.release.providerRelease) {
6617
+ if (providerError) {
6618
+ providerDisplay = `Failed: ${providerError}`;
6619
+ } else {
6620
+ providerDisplay = `${postedReleases.length} release${postedReleases.length !== 1 ? "s" : ""}`;
6621
+ }
6442
6622
  }
6443
- try {
6444
- return await prComment({
6445
- prNumber,
6446
- dryRun,
6447
- logLevel,
6448
- configName,
6449
- config,
6450
- releaseContext
6451
- });
6452
- } catch (error) {
6453
- logger.warn("PR comment posting failed:", error);
6454
- return false;
6623
+ let socialDisplay = "Disabled";
6624
+ if (config.release.social && socialResults) {
6625
+ if (socialResults.hasErrors) {
6626
+ const failed = socialResults.results.filter((r) => !r.success).map((r) => r.platform);
6627
+ const succeeded = socialResults.results.filter((r) => r.success).map((r) => r.platform);
6628
+ socialDisplay = `${succeeded.length} succeeded, ${failed.length} failed (${failed.join(", ")})`;
6629
+ } else {
6630
+ socialDisplay = `${socialResults.results.length} succeeded`;
6631
+ }
6455
6632
  }
6633
+ let prCommentDisplay = "Disabled";
6634
+ if (config.release.prComment) {
6635
+ prCommentDisplay = prCommentPosted ? "Posted" : "Failed";
6636
+ }
6637
+ return `Release workflow completed!
6638
+
6639
+ Version: ${versionDisplay ?? "Unknown"}
6640
+ Tag(s): ${createdTags?.length ? createdTags.join(", ") : "None"}
6641
+ Pushed: ${config.release.push ? "Yes" : "Disabled"}
6642
+ Published packages: ${config.release.publish ? publishedPackageCount : "Disabled"}
6643
+ Provider release: ${providerDisplay}
6644
+ Social media: ${socialDisplay}
6645
+ PR comment: ${prCommentDisplay}
6646
+ Git provider: ${provider}`;
6456
6647
  }
6457
6648
  async function release(options = {}) {
6458
6649
  const isCanary = options.canary ?? false;
@@ -6641,40 +6832,17 @@ async function release(options = {}) {
6641
6832
  logLevel: config.logLevel,
6642
6833
  configName: options.configName
6643
6834
  });
6644
- const publishedPackageCount = publishResponse?.publishedPackages.length ?? 0;
6645
- const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || readPackageJson(config.cwd)?.version;
6646
- let providerDisplay = "Disabled";
6647
- if (config.release.providerRelease) {
6648
- if (providerError) {
6649
- providerDisplay = `Failed: ${providerError}`;
6650
- } else {
6651
- providerDisplay = `${postedReleases.length} release${postedReleases.length !== 1 ? "s" : ""}`;
6652
- }
6653
- }
6654
- let socialDisplay = "Disabled";
6655
- if (config.release.social && socialResults) {
6656
- if (socialResults.hasErrors) {
6657
- const failed = socialResults.results.filter((r) => !r.success).map((r) => r.platform);
6658
- const succeeded = socialResults.results.filter((r) => r.success).map((r) => r.platform);
6659
- socialDisplay = `${succeeded.length} succeeded, ${failed.length} failed (${failed.join(", ")})`;
6660
- } else {
6661
- socialDisplay = `${socialResults.results.length} succeeded`;
6662
- }
6663
- }
6664
- let prCommentDisplay = "Disabled";
6665
- if (config.release.prComment) {
6666
- prCommentDisplay = prCommentPosted ? "Posted" : "Failed";
6667
- }
6668
- logger.box(`Release workflow completed!
6669
-
6670
- Version: ${versionDisplay ?? "Unknown"}
6671
- Tag(s): ${createdTags?.length ? createdTags.join(", ") : "None"}
6672
- Pushed: ${config.release.push ? "Yes" : "Disabled"}
6673
- Published packages: ${config.release.publish ? publishedPackageCount : "Disabled"}
6674
- Provider release: ${providerDisplay}
6675
- Social media: ${socialDisplay}
6676
- PR comment: ${prCommentDisplay}
6677
- Git provider: ${provider}`);
6835
+ logger.box(buildReleaseSummary({
6836
+ config,
6837
+ bumpResult,
6838
+ publishResponse,
6839
+ createdTags,
6840
+ provider,
6841
+ postedReleases,
6842
+ providerError,
6843
+ socialResults,
6844
+ prCommentPosted
6845
+ }));
6678
6846
  await executeHook("success:release", config, dryRun);
6679
6847
  } catch (error) {
6680
6848
  logger.error("Error during release workflow!\n\n", error);
@@ -6695,4 +6863,4 @@ Git provider: ${provider}`);
6695
6863
  }
6696
6864
  }
6697
6865
 
6698
- export { getLastPackageTag as $, extractVersionFromTag as A, fetchGitTags as B, filterOutPrivatePackages as C, findGitHubPR as D, findGitLabMR as E, findReachableCommitBySubject as F, formatChangelogForSlack as G, formatPackagesForSlack as H, formatSlackMessage as I, formatTweetMessage as J, generateChangelog as K, generateMarkDown as L, getAuthCommand as M, NEW_PACKAGE_MARKER as N, getBumpedIndependentPackages as O, PR_COMMENT_MARKER as P, getBumpedPackageIndependently as Q, getCIName as R, getCanaryVersion as S, getCommitSubject as T, getCurrentGitBranch as U, getCurrentGitRef as V, getDefaultConfig as W, getDependentsOf as X, getFirstCommit as Y, getGitStatus as Z, getIndependentTag as _, buildCommentBody as a, topologicalSort as a$, getLastRepoTag as a0, getLastStableTag as a1, getLastTag as a2, getModifiedReleaseFilePatterns as a3, getNpmRegistry as a4, getPackageCommits as a5, getPackageDependencies as a6, getPackageNewVersion as a7, getPackages as a8, getPackagesOrBumpedPackages as a9, postPrComment as aA, postReleaseToSlack as aB, postReleaseToTwitter as aC, prComment as aD, providerRelease as aE, providerReleaseSafetyCheck as aF, publish as aG, publishPackage as aH, publishSafetyCheck as aI, pushCommitAndTags as aJ, pushTagForce as aK, readPackageJson as aL, readPackages as aM, reconcileFromTag as aN, redactSecrets as aO, release as aP, resetRewrittenTagCache as aQ, resolveAllConfiguredRegistryTargets as aR, resolveRegistryTargetsForPackage as aS, resolveRegistryTargetsForPackages as aT, resolveTags as aU, retagAnnotatedLocal as aV, rollbackModifiedFiles as aW, shouldFilterPrereleaseTags as aX, social as aY, socialSafetyCheck as aZ, tagExists as a_, getPackagesToPublishInIndependentMode as aa, getPackagesToPublishInSelectiveMode as ab, getPreid as ac, getReleaseUrl as ad, getRootPackage as ae, getShortCommitSha as af, getSlackToken as ag, getSlackWebhookUrl as ah, getTwitterCredentials as ai, github as aj, gitlab as ak, hasLernaJson as al, isAncestor as am, isBumpedPackage as an, isChangedPreid as ao, isGraduating as ap, isGraduatingToStableBetweenVersion as aq, isInCI as ar, isPrerelease as as, isPrereleaseReleaseType as at, isStableReleaseType as au, isTagVersionCompatibleWithCurrent as av, loadRelizyConfig as aw, mergeTypes as ax, parseChangelogMarkdown as ay, parseGitRemoteUrl as az, buildChangelogBody as b, updateLernaVersion as b0, withRegistryNpmrc as b1, writeChangelogToFile as b2, writeVersion as b3, buildCompareLink as c, buildContributors as d, bump as e, capReleaseTypeForZeroMajor as f, changelog as g, checkGitStatusIfDirty as h, collectContributorNames as i, collectPackageBumps as j, confirmBump as k, createCommitAndTags as l, createGitlabRelease as m, defineConfig as n, detectGitProvider as o, detectPackageManager as p, detectPullRequest as q, determinePublishTag as r, determineReleaseType as s, determineSemverChange as t, executeBuildCmd as u, executeFormatCmd as v, executeHook as w, expandPackagesToBumpWithDependents as x, extractChangelogSummary as y, extractVersionFromPackageTag as z };
6866
+ export { getIndependentTag 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, getCommitSubject as U, getCurrentGitBranch as V, getCurrentGitRef as W, getDefaultConfig as X, getDependentsOf as Y, getFirstCommit as Z, getGitStatus as _, buildCommentBody as a, social as a$, getLastPackageTag as a0, getLastRepoTag as a1, getLastStableTag as a2, getLastTag as a3, getModifiedReleaseFilePatterns as a4, getNpmRegistry as a5, getPackageCommits as a6, getPackageDependencies as a7, getPackageNewVersion as a8, getPackages as a9, mergeTypes as aA, parseChangelogMarkdown as aB, parseGitRemoteUrl as aC, postPrComment as aD, postReleaseToSlack as aE, postReleaseToTwitter as aF, prComment as aG, providerRelease as aH, providerReleaseSafetyCheck as aI, publish as aJ, publishPackage as aK, publishSafetyCheck as aL, pushCommitAndTags as aM, pushTagForce as aN, readPackageJson as aO, readPackages as aP, reconcileFromTag as aQ, redactSecrets as aR, release as aS, resetRewrittenTagCache as aT, resolveAllConfiguredRegistryTargets as aU, resolveRegistryTargetsForPackage as aV, resolveRegistryTargetsForPackages as aW, resolveTags as aX, retagAnnotatedLocal as aY, rollbackModifiedFiles as aZ, shouldFilterPrereleaseTags as a_, getPackagesOrBumpedPackages as aa, getPackagesToPublishInIndependentMode as ab, getPackagesToPublishInSelectiveMode as ac, getPreid as ad, getReleaseUrl as ae, getRootPackage as af, getShortCommitSha as ag, getSlackToken as ah, getSlackWebhookUrl as ai, getTwitterCredentials as aj, github as ak, gitlab as al, hasLernaJson as am, isAncestor as an, isBumpedPackage as ao, isChangedPreid as ap, isGraduating as aq, isGraduatingToStableBetweenVersion as ar, isInCI as as, isPrerelease as at, isPrereleaseReleaseType as au, isStableReleaseType as av, isTagVersionCompatibleWithCurrent as aw, isVersionAlreadyPublishedError as ax, isVersionPublished as ay, loadRelizyConfig as az, buildChangelogBody as b, socialSafetyCheck as b0, tagExists as b1, topologicalSort as b2, tryPostPrComment as b3, updateLernaVersion as b4, withRegistryNpmrc as b5, writeChangelogToFile as b6, writeVersion as b7, 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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relizy",
3
3
  "type": "module",
4
- "version": "1.5.0-beta.2",
4
+ "version": "1.5.0-beta.3",
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",
@@ -55,7 +55,7 @@
55
55
  "node": ">=20.0.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@slack/web-api": "^7.0.0",
58
+ "@slack/web-api": "^7.0.0 || ^8.0.0",
59
59
  "@yoloship/claude-sdk": "^0.2.0-beta.6",
60
60
  "twitter-api-v2": "^1.20.0"
61
61
  },
@@ -73,7 +73,7 @@
73
73
  "dependencies": {
74
74
  "@inquirer/prompts": "^8.7.2",
75
75
  "@maz-ui/node": "5.0.0-beta.26",
76
- "@maz-ui/utils": "5.0.0-beta.25",
76
+ "@maz-ui/utils": "5.0.0-beta.39",
77
77
  "c12": "^3.3.3",
78
78
  "changelogen": "^0.6.2",
79
79
  "commander": "^15.0.0",
@@ -85,28 +85,28 @@
85
85
  "semver": "^7.8.5"
86
86
  },
87
87
  "devDependencies": {
88
- "@commitlint/cli": "^21.2.2",
89
- "@commitlint/config-conventional": "21.2.2",
90
- "@commitlint/cz-commitlint": "^21.2.2",
91
- "@commitlint/types": "^21.2.0",
88
+ "@commitlint/cli": "^21.2.3",
89
+ "@commitlint/config-conventional": "21.2.3",
90
+ "@commitlint/cz-commitlint": "^21.2.3",
91
+ "@commitlint/types": "^21.2.3",
92
92
  "@maz-ui/eslint-config": "5.0.0-beta.43",
93
- "@slack/web-api": "7.16.0",
93
+ "@slack/web-api": "8.1.1",
94
94
  "@types/micromatch": "^4.0.10",
95
- "@types/node": "^25.9.2",
95
+ "@types/node": "^26.6.2",
96
96
  "@types/semver": "^7.8.0",
97
- "@vitest/coverage-v8": "^5.0.0",
97
+ "@vitest/coverage-v8": "^5.0.1",
98
98
  "@yoloship/claude-sdk": "0.2.0-beta.6",
99
99
  "cross-env": "10.1.0",
100
- "eslint": "^10.10.0",
100
+ "eslint": "^10.11.0",
101
101
  "husky": "9.1.7",
102
102
  "jiti": "2.7.0",
103
103
  "lint-staged": "^17.5.1",
104
- "memfs": "^4.78.0",
105
- "tsx": "^4.23.13",
104
+ "memfs": "^4.78.1",
105
+ "tsx": "^4.23.15",
106
106
  "twitter-api-v2": "^1.29.1",
107
- "typescript": "^6.0.3",
107
+ "typescript": "^6.0.0",
108
108
  "unbuild": "^3.6.1",
109
- "vitest": "^5.0.0"
109
+ "vitest": "^5.0.1"
110
110
  },
111
111
  "lint-staged": {
112
112
  "*.{js,jsx,ts,tsx,mjs,mts,cjs,md,yml,json}": [