From 7793147e1a8d4db4ed837ecaa248d2edcb046ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 8 Apr 2026 17:25:55 +0200 Subject: [PATCH 1/5] fix: skip already published packages during release --- scripts/npm-publish-package.mjs | 57 +++++++++++++++++++++++++++++++++ scripts/npm-publish.sh | 35 ++++++++++++-------- 2 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 scripts/npm-publish-package.mjs diff --git a/scripts/npm-publish-package.mjs b/scripts/npm-publish-package.mjs new file mode 100644 index 000000000..7124f403d --- /dev/null +++ b/scripts/npm-publish-package.mjs @@ -0,0 +1,57 @@ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const [, , packageDirArg, ...publishArgs] = process.argv; + +if (!packageDirArg) { + console.error('Usage: node scripts/npm-publish-package.mjs [npm publish args...]'); + process.exit(1); +} + +const packageDir = path.resolve(packageDirArg); +const packageJsonPath = path.join(packageDir, 'package.json'); +const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + +if (packageJson.private) { + console.log(`Skipping private package in ${packageDir}`); + process.exit(0); +} + +if (!packageJson.name || !packageJson.version) { + console.error(`Package at ${packageDir} is missing a name or version`); + process.exit(1); +} + +const packageRef = `${packageJson.name}@${packageJson.version}`; +const registry = 'https://registry.npmjs.org/'; + +const viewResult = spawnSync( + 'npm', + ['view', packageRef, 'version', '--registry', registry, '--silent'], + { + cwd: packageDir, + encoding: 'utf8', + }, +); + +if (viewResult.status === 0) { + console.log(`Skipping already published package ${packageRef}`); + process.exit(0); +} + +const viewOutput = `${viewResult.stdout}\n${viewResult.stderr}`; + +if (!viewOutput.includes('E404') && !viewOutput.includes('404')) { + process.stderr.write(viewOutput); + process.exit(viewResult.status ?? 1); +} + +console.log(`Publishing ${packageRef}`); + +const publishResult = spawnSync('npm', ['publish', ...publishArgs], { + cwd: packageDir, + stdio: 'inherit', +}); + +process.exit(publishResult.status ?? 1); diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index ca61e4803..92d1390ed 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -1,5 +1,7 @@ #!/bin/bash -set -e +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" echo "Building all packages..." @@ -9,21 +11,28 @@ if [ -z "$NPM_TOKEN" ] && [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ]; then read -p "Enter NPM OTP: " OTP fi +publish_package() { + local package_dir="$1" + shift + + node "$ROOT_DIR/scripts/npm-publish-package.mjs" "$package_dir" "$@" +} + echo "NPM: Publishing all packages" -# In CI, trusted publishing should be non-interactive and not require OTP input. -if [ -n "$NPM_TOKEN" ] || [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then - pnpm -r run publish:npm -else - pnpm -r --no-bail run publish:npm --otp="$OTP" -fi +for package_json in "$ROOT_DIR"/packages/*/package.json; do + publish_args=() + if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then + publish_args+=(--otp="$OTP") + fi + + publish_package "${package_json%/package.json}" --access public "${publish_args[@]}" +done echo "NPM: Publishing template" -cd templates/rock-template-default -# In CI, trusted publishing should be non-interactive and not require OTP input. -if [ -n "$NPM_TOKEN" ] || [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then - npm publish -else - npm publish --otp="$OTP" +template_publish_args=() +if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then + template_publish_args+=(--otp="$OTP") fi +publish_package "$ROOT_DIR/templates/rock-template-default" "${template_publish_args[@]}" echo "Done" From d7a5107536e02940dae5a33c1d76f31d82ffbc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 8 Apr 2026 17:33:47 +0200 Subject: [PATCH 2/5] chore: simplify release publish skipping --- scripts/npm-publish-package.mjs | 57 --------------------------------- scripts/npm-publish.sh | 25 +++++++++++++-- 2 files changed, 22 insertions(+), 60 deletions(-) delete mode 100644 scripts/npm-publish-package.mjs diff --git a/scripts/npm-publish-package.mjs b/scripts/npm-publish-package.mjs deleted file mode 100644 index 7124f403d..000000000 --- a/scripts/npm-publish-package.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import { readFileSync } from 'node:fs'; -import path from 'node:path'; -import { spawnSync } from 'node:child_process'; - -const [, , packageDirArg, ...publishArgs] = process.argv; - -if (!packageDirArg) { - console.error('Usage: node scripts/npm-publish-package.mjs [npm publish args...]'); - process.exit(1); -} - -const packageDir = path.resolve(packageDirArg); -const packageJsonPath = path.join(packageDir, 'package.json'); -const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); - -if (packageJson.private) { - console.log(`Skipping private package in ${packageDir}`); - process.exit(0); -} - -if (!packageJson.name || !packageJson.version) { - console.error(`Package at ${packageDir} is missing a name or version`); - process.exit(1); -} - -const packageRef = `${packageJson.name}@${packageJson.version}`; -const registry = 'https://registry.npmjs.org/'; - -const viewResult = spawnSync( - 'npm', - ['view', packageRef, 'version', '--registry', registry, '--silent'], - { - cwd: packageDir, - encoding: 'utf8', - }, -); - -if (viewResult.status === 0) { - console.log(`Skipping already published package ${packageRef}`); - process.exit(0); -} - -const viewOutput = `${viewResult.stdout}\n${viewResult.stderr}`; - -if (!viewOutput.includes('E404') && !viewOutput.includes('404')) { - process.stderr.write(viewOutput); - process.exit(viewResult.status ?? 1); -} - -console.log(`Publishing ${packageRef}`); - -const publishResult = spawnSync('npm', ['publish', ...publishArgs], { - cwd: packageDir, - stdio: 'inherit', -}); - -process.exit(publishResult.status ?? 1); diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 92d1390ed..6ecc4b89e 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -7,19 +7,38 @@ echo "Building all packages..." pnpm build -if [ -z "$NPM_TOKEN" ] && [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ]; then +if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then read -p "Enter NPM OTP: " OTP fi publish_package() { local package_dir="$1" + local package_json="$package_dir/package.json" shift - node "$ROOT_DIR/scripts/npm-publish-package.mjs" "$package_dir" "$@" + local package_name + local package_version + local package_ref + + package_name=$(node -p "require(process.argv[1]).name" "$package_json") + package_version=$(node -p "require(process.argv[1]).version" "$package_json") + package_ref="${package_name}@${package_version}" + + if npm view "$package_ref" version --registry https://registry.npmjs.org/ --silent >/dev/null 2>&1; then + echo "Skipping already published package ${package_ref}" + return 0 + fi + + echo "Publishing ${package_ref}" + ( + cd "$package_dir" + npm publish "$@" + ) } echo "NPM: Publishing all packages" -for package_json in "$ROOT_DIR"/packages/*/package.json; do +mapfile -t package_jsons < <(rg -l '"publish:npm"' "$ROOT_DIR/packages" -g package.json | sort) +for package_json in "${package_jsons[@]}"; do publish_args=() if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then publish_args+=(--otp="$OTP") From 4511323db4a2a6ddf1169fa231818859d9c01abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 8 Apr 2026 17:38:41 +0200 Subject: [PATCH 3/5] fix: make release publish script portable --- scripts/npm-publish.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 6ecc4b89e..022d08cb6 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -37,8 +37,11 @@ publish_package() { } echo "NPM: Publishing all packages" -mapfile -t package_jsons < <(rg -l '"publish:npm"' "$ROOT_DIR/packages" -g package.json | sort) -for package_json in "${package_jsons[@]}"; do +for package_json in "$ROOT_DIR"/packages/*/package.json; do + if ! grep -q '"publish:npm"' "$package_json"; then + continue + fi + publish_args=() if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then publish_args+=(--otp="$OTP") @@ -52,6 +55,6 @@ template_publish_args=() if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then template_publish_args+=(--otp="$OTP") fi -publish_package "$ROOT_DIR/templates/rock-template-default" "${template_publish_args[@]}" +publish_package "$ROOT_DIR/templates/rock-template-default" --access public "${template_publish_args[@]}" echo "Done" From e80c55f00ee4c653159529a1ea7762b1809c0e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 8 Apr 2026 17:46:18 +0200 Subject: [PATCH 4/5] chore: remove NPM_TOKEN references --- scripts/npm-publish.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/npm-publish.sh b/scripts/npm-publish.sh index 022d08cb6..7a9885d34 100755 --- a/scripts/npm-publish.sh +++ b/scripts/npm-publish.sh @@ -7,7 +7,7 @@ echo "Building all packages..." pnpm build -if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then +if [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then read -p "Enter NPM OTP: " OTP fi @@ -43,7 +43,7 @@ for package_json in "$ROOT_DIR"/packages/*/package.json; do fi publish_args=() - if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then + if [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then publish_args+=(--otp="$OTP") fi @@ -52,7 +52,7 @@ done echo "NPM: Publishing template" template_publish_args=() -if [ -z "${NPM_TOKEN:-}" ] && [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then +if [ -z "${CI:-}" ] && [ -z "${GITHUB_ACTIONS:-}" ]; then template_publish_args+=(--otp="$OTP") fi publish_package "$ROOT_DIR/templates/rock-template-default" --access public "${template_publish_args[@]}" From 3294df0305f831dded7a2240400aa1889390f9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 8 Apr 2026 17:53:07 +0200 Subject: [PATCH 5/5] test: align provider github fixtures --- .../src/__tests__/providerGitHub.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/provider-github/src/__tests__/providerGitHub.test.ts b/packages/provider-github/src/__tests__/providerGitHub.test.ts index db01923a8..a16183d3c 100644 --- a/packages/provider-github/src/__tests__/providerGitHub.test.ts +++ b/packages/provider-github/src/__tests__/providerGitHub.test.ts @@ -11,7 +11,7 @@ const ARTIFACTS = [ id: 123, name: 'rock-android-debug-1234567890', archive_download_url: - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/123', + 'https://api.github.com/repos/callstack/rock/actions/artifacts/123', size_in_bytes: 10000, expires_at: '2025-05-20T12:00:00Z', }, @@ -20,7 +20,7 @@ const ARTIFACTS = [ id: 124, name: 'rock-android-debug-1234567890', archive_download_url: - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/124', + 'https://api.github.com/repos/callstack/rock/actions/artifacts/124', size_in_bytes: 10000, expires_at: '2025-05-20T12:00:00Z', }, @@ -41,7 +41,7 @@ test('providerGitHub implements list method returning an array of artifacts', as limit, }); expect(fetch).toHaveBeenCalledWith( - `https://api.github.com/repos/callstackincubator/rock/actions/artifacts?per_page=${limit}&page=1&name=rock-android-debug-1234567890`, + `https://api.github.com/repos/callstack/rock/actions/artifacts?per_page=${limit}&page=1&name=rock-android-debug-1234567890`, { headers: { Authorization: 'token TEST_TOKEN', @@ -52,7 +52,7 @@ test('providerGitHub implements list method returning an array of artifacts', as { id: '123', name: 'rock-android-debug-1234567890', - url: 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/123', + url: 'https://api.github.com/repos/callstack/rock/actions/artifacts/123', }, ]); }); @@ -64,7 +64,7 @@ test('providerGitHub implements download method returning a stream with artifact global.fetch = vi.fn((url) => { if ( url === - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts?per_page=100&page=1&name=rock-android-debug-1234567890' + 'https://api.github.com/repos/callstack/rock/actions/artifacts?per_page=100&page=1&name=rock-android-debug-1234567890' ) { return Promise.resolve( new Response(JSON.stringify({ artifacts: ARTIFACTS.slice(0, limit) })), @@ -72,7 +72,7 @@ test('providerGitHub implements download method returning a stream with artifact } if ( url === - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/123' + 'https://api.github.com/repos/callstack/rock/actions/artifacts/123' ) { return Promise.resolve(downloadResponse); } @@ -97,7 +97,7 @@ test('providerGitHub implements delete method', async () => { global.fetch = vi.fn((url, options) => { if ( url === - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts?per_page=100&page=1&name=rock-android-debug-1234567890' + 'https://api.github.com/repos/callstack/rock/actions/artifacts?per_page=100&page=1&name=rock-android-debug-1234567890' ) { return Promise.resolve( new Response(JSON.stringify({ artifacts: ARTIFACTS })), @@ -105,14 +105,14 @@ test('providerGitHub implements delete method', async () => { } if ( url === - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/123' && + 'https://api.github.com/repos/callstack/rock/actions/artifacts/123' && options.method === 'DELETE' ) { return Promise.resolve(new Response()); } if ( url === - 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/124' && + 'https://api.github.com/repos/callstack/rock/actions/artifacts/124' && options.method === 'DELETE' ) { return Promise.resolve(new Response()); @@ -131,11 +131,11 @@ test('providerGitHub implements delete method', async () => { expect(response).toEqual([ { name: 'rock-android-debug-1234567890', - url: 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/123', + url: 'https://api.github.com/repos/callstack/rock/actions/artifacts/123', }, { name: 'rock-android-debug-1234567890', - url: 'https://api.github.com/repos/callstackincubator/rock/actions/artifacts/124', + url: 'https://api.github.com/repos/callstack/rock/actions/artifacts/124', }, ]); });