From 0cd05bd1b84e06a26242c4228510204b3ecce3f3 Mon Sep 17 00:00:00 2001 From: Adam Cassis Date: Wed, 15 Apr 2026 11:57:23 +0200 Subject: [PATCH 1/2] fix(release): fail loudly when GitHub release asset is missing Hard-fail at startup if GITHUB_REPOSITORY is unset, and add a verification plugin that aborts semantic-release before publish if the release ZIP is not on disk. Closes a gap where @semantic-release/github silently ignores missing assets, which previously allowed releases to be published with no attached archive. --- scripts/github/release.js | 20 +++++++++++++++++--- scripts/github/verify-release-asset.js | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 scripts/github/verify-release-asset.js diff --git a/scripts/github/release.js b/scripts/github/release.js index 391468e..39d67f3 100644 --- a/scripts/github/release.js +++ b/scripts/github/release.js @@ -10,8 +10,17 @@ const { files, ...otherArgs } = require( 'yargs/yargs' )( const filesList = files.split( ',' ); -// Get repository name from GitHub Actions environment variable (format: owner/repo). -const repoName = process.env.GITHUB_REPOSITORY?.split( '/' )[ 1 ] || 'unknown'; +if ( ! process.env.GITHUB_REPOSITORY ) { + console.error( + 'GITHUB_REPOSITORY is not set. This script must run inside GitHub Actions ' + + 'so the release asset path can be resolved. Aborting before semantic-release ' + + 'publishes a release without an attached ZIP.' + ); + process.exit( 1 ); +} + +const repoName = process.env.GITHUB_REPOSITORY.split( '/' )[ 1 ]; +const releaseAssetPath = `./release/${ repoName }.zip`; utils.log( `Releasing ${ repoName }…` ); @@ -20,7 +29,7 @@ const getConfig = ({ gitBranchName }) => { const githubConfig = { assets: [ { - path: `./release/${ repoName }.zip`, + path: releaseAssetPath, label: `${ repoName }.zip`, }, ], @@ -97,6 +106,11 @@ const getConfig = ({ gitBranchName }) => { }, ] ); + // Verify the release archive exists on disk after `release:archive` ran. + // `@semantic-release/github` silently ignores missing assets, so without this + // guard a misconfigured archive step would publish a release with no ZIP. + config.prepare.push( require.resolve( './verify-release-asset.js' ) ); + // Unless on a hotfix or epic branch, add a commit that updates the files. if ( [ 'hotfix', 'epic' ].indexOf( branchType ) === -1 ) { let assets = filesList; diff --git a/scripts/github/verify-release-asset.js b/scripts/github/verify-release-asset.js new file mode 100644 index 0000000..fa18601 --- /dev/null +++ b/scripts/github/verify-release-asset.js @@ -0,0 +1,25 @@ +'use strict'; + +const fs = require( 'fs' ); +const path = require( 'path' ); + +const repoName = process.env.GITHUB_REPOSITORY?.split( '/' )[ 1 ]; +const releaseAssetPath = path.resolve( `./release/${ repoName }.zip` ); + +async function prepare() { + if ( ! repoName ) { + throw new Error( + 'GITHUB_REPOSITORY is not set; cannot determine release asset path.' + ); + } + if ( ! fs.existsSync( releaseAssetPath ) ) { + throw new Error( + `Release asset not found at ${ releaseAssetPath }. ` + + 'The `release:archive` script must produce this file before ' + + 'semantic-release publishes the GitHub release.' + ); + } + console.log( `[verify-release-asset] OK: ${ releaseAssetPath }` ); +} + +module.exports = { prepare }; From ff41ba7702338407bf019df0c6b8c58d5b433d36 Mon Sep 17 00:00:00 2001 From: Adam Cassis Date: Wed, 15 Apr 2026 12:07:33 +0200 Subject: [PATCH 2/2] fix(release): validate GITHUB_REPOSITORY format and use shared logger Reject malformed GITHUB_REPOSITORY values (not in owner/repo form) so a value like "foo" no longer slips through and produces undefined.zip. Switch the verification plugin to utils.log for output consistency. --- scripts/github/release.js | 10 +++++++++- scripts/github/verify-release-asset.js | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/github/release.js b/scripts/github/release.js index 39d67f3..1315be3 100644 --- a/scripts/github/release.js +++ b/scripts/github/release.js @@ -19,7 +19,15 @@ if ( ! process.env.GITHUB_REPOSITORY ) { process.exit( 1 ); } -const repoName = process.env.GITHUB_REPOSITORY.split( '/' )[ 1 ]; +const [ repoOwner, repoName ] = process.env.GITHUB_REPOSITORY.split( '/' ); +if ( ! repoOwner || ! repoName ) { + console.error( + `GITHUB_REPOSITORY must be in "owner/repo" format; received "${ process.env.GITHUB_REPOSITORY }". ` + + 'Aborting before semantic-release publishes a release with an invalid asset path.' + ); + process.exit( 1 ); +} + const releaseAssetPath = `./release/${ repoName }.zip`; utils.log( `Releasing ${ repoName }…` ); diff --git a/scripts/github/verify-release-asset.js b/scripts/github/verify-release-asset.js index fa18601..e637957 100644 --- a/scripts/github/verify-release-asset.js +++ b/scripts/github/verify-release-asset.js @@ -3,6 +3,8 @@ const fs = require( 'fs' ); const path = require( 'path' ); +const utils = require( '../utils/index.js' ); + const repoName = process.env.GITHUB_REPOSITORY?.split( '/' )[ 1 ]; const releaseAssetPath = path.resolve( `./release/${ repoName }.zip` ); @@ -19,7 +21,7 @@ async function prepare() { 'semantic-release publishes the GitHub release.' ); } - console.log( `[verify-release-asset] OK: ${ releaseAssetPath }` ); + utils.log( `Verified release asset at ${ releaseAssetPath }.` ); } module.exports = { prepare };