diff --git a/apps/cli/.releaserc.cjs b/apps/cli/.releaserc.cjs index f6f2001067..3bd9569786 100644 --- a/apps/cli/.releaserc.cjs +++ b/apps/cli/.releaserc.cjs @@ -33,6 +33,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -75,7 +79,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'superdoc-cli', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -86,6 +90,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-cli** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/apps/create/.releaserc.cjs b/apps/create/.releaserc.cjs index 84dae1d137..2723c05fe5 100644 --- a/apps/create/.releaserc.cjs +++ b/apps/create/.releaserc.cjs @@ -13,6 +13,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; const config = { @@ -35,7 +39,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'create', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -46,6 +50,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/create** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/apps/mcp/.releaserc.cjs b/apps/mcp/.releaserc.cjs index 244bdfc62b..a03b63eabc 100644 --- a/apps/mcp/.releaserc.cjs +++ b/apps/mcp/.releaserc.cjs @@ -37,6 +37,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -78,7 +82,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'mcp', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -89,6 +93,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/mcp** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/apps/vscode-ext/.releaserc.cjs b/apps/vscode-ext/.releaserc.cjs index 0a76d84de8..14f1adc3e2 100644 --- a/apps/vscode-ext/.releaserc.cjs +++ b/apps/vscode-ext/.releaserc.cjs @@ -33,6 +33,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -89,7 +93,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'vscode-ext', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -101,6 +105,7 @@ config.plugins.push([ assets: [{ path: '*.vsix', label: 'VS Code Extension' }], successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **vscode-ext** v${nextRelease.version}', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/packages/esign/.releaserc.cjs b/packages/esign/.releaserc.cjs index 0a2637b43b..ddb86c40fb 100644 --- a/packages/esign/.releaserc.cjs +++ b/packages/esign/.releaserc.cjs @@ -21,6 +21,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -50,7 +54,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'esign', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -61,6 +65,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **esign** v${nextRelease.version}\n\nThe release is available on [GitHub release](https://github.com/superdoc-dev/superdoc/releases/tag/${nextRelease.gitTag})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/packages/react/.releaserc.cjs b/packages/react/.releaserc.cjs index 34ef798141..f1a076283a 100644 --- a/packages/react/.releaserc.cjs +++ b/packages/react/.releaserc.cjs @@ -33,6 +33,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -69,13 +73,17 @@ if (!isPrerelease) { } // Linear integration - labels issues with version on release -config.plugins.push(['semantic-release-linear-app', { teamKeys: ['SD'], addComment: true, packageName: 'react' }]); +config.plugins.push([ + 'semantic-release-linear-app', + { teamKeys: ['SD'], addComment: shouldCommentOnRelease, packageName: 'react' }, +]); config.plugins.push([ '@semantic-release/github', { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/react** v${nextRelease.version}\n\nThe release is available on [GitHub release](https://github.com/superdoc-dev/superdoc/releases/tag/${nextRelease.gitTag})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/packages/sdk/.releaserc.cjs b/packages/sdk/.releaserc.cjs index c9e2e92327..53a8253b8a 100644 --- a/packages/sdk/.releaserc.cjs +++ b/packages/sdk/.releaserc.cjs @@ -33,6 +33,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -102,7 +106,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'superdoc-sdk', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -113,6 +117,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-sdk** v${nextRelease.version}', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/packages/superdoc/.releaserc.cjs b/packages/superdoc/.releaserc.cjs index 06f2089178..64aa024ecc 100644 --- a/packages/superdoc/.releaserc.cjs +++ b/packages/superdoc/.releaserc.cjs @@ -61,6 +61,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isLocalPreview || isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -104,7 +108,7 @@ if (!isLocalPreview) { 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'superdoc', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -118,6 +122,7 @@ if (!isLocalPreview) { { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc** v${nextRelease.version}\n\nThe release is available on [GitHub release](https://github.com/superdoc-dev/superdoc/releases/tag/${nextRelease.gitTag})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); } diff --git a/packages/template-builder/.releaserc.cjs b/packages/template-builder/.releaserc.cjs index c48172cb4a..7d7499c971 100644 --- a/packages/template-builder/.releaserc.cjs +++ b/packages/template-builder/.releaserc.cjs @@ -21,6 +21,10 @@ const branches = [ const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); +// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest. +// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments. +const shouldCommentOnRelease = !isPrerelease; + // Use AI-powered notes for stable releases, conventional generator for prereleases const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }]; @@ -50,7 +54,7 @@ config.plugins.push([ 'semantic-release-linear-app', { teamKeys: ['SD'], - addComment: true, + addComment: shouldCommentOnRelease, packageName: 'template-builder', commentTemplate: 'shipped in {package} {releaseLink} {channel}', }, @@ -61,6 +65,7 @@ config.plugins.push([ { successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **template-builder** v${nextRelease.version}\n\nThe release is available on [GitHub release](https://github.com/superdoc-dev/superdoc/releases/tag/${nextRelease.gitTag})', + successCommentCondition: shouldCommentOnRelease ? undefined : false, }, ]); diff --git a/scripts/__tests__/release-local.test.mjs b/scripts/__tests__/release-local.test.mjs index c9d40c80ab..5fe504a653 100644 --- a/scripts/__tests__/release-local.test.mjs +++ b/scripts/__tests__/release-local.test.mjs @@ -449,6 +449,53 @@ test('stable recovery ignores PyPI gaps when SDK PyPI publishing is disabled', a ); }); +test('release configs suppress per-PR comment spam on prereleases', async () => { + const releasercPaths = [ + 'packages/superdoc/.releaserc.cjs', + 'packages/react/.releaserc.cjs', + 'packages/sdk/.releaserc.cjs', + 'packages/template-builder/.releaserc.cjs', + 'packages/esign/.releaserc.cjs', + 'apps/cli/.releaserc.cjs', + 'apps/mcp/.releaserc.cjs', + 'apps/vscode-ext/.releaserc.cjs', + 'apps/create/.releaserc.cjs', + ]; + + for (const releasercPath of releasercPaths) { + const content = await readRepoFile(releasercPath); + + const usesGithubPlugin = content.includes("'@semantic-release/github'"); + const usesLinearPlugin = content.includes("'semantic-release-linear-app'"); + + if (!usesGithubPlugin && !usesLinearPlugin) continue; + + assert.ok( + content.includes('const shouldCommentOnRelease = !isPrerelease'), + `${releasercPath}: must define shouldCommentOnRelease = !isPrerelease so the prerelease comment gate is consistent across configs`, + ); + + if (usesGithubPlugin) { + assert.ok( + content.includes('successCommentCondition: shouldCommentOnRelease ? undefined : false'), + `${releasercPath}: @semantic-release/github must gate successCommentCondition through shouldCommentOnRelease so prereleases don't re-comment on every PR after a stable -> main sync`, + ); + } + + if (usesLinearPlugin) { + assert.ok( + content.includes('addComment: shouldCommentOnRelease'), + `${releasercPath}: semantic-release-linear-app addComment must be gated through shouldCommentOnRelease so prereleases don't post duplicate Linear comments after a stable -> main sync`, + ); + assert.equal( + content.includes('addComment: true'), + false, + `${releasercPath}: semantic-release-linear-app must not hardcode addComment: true`, + ); + } + } +}); + test('release-state probes wrap fetch in bounded retry to absorb transient blips', async () => { const content = await readRepoFile('scripts/release-local-stable.mjs'); assert.ok(