From 020481d7c73406f1ba44da025b70695a582f6351 Mon Sep 17 00:00:00 2001 From: David Elner Date: Wed, 1 Jul 2026 14:16:39 +0000 Subject: [PATCH 1/3] Changed: Print richer env dump for failed JS workflows --- actions/release/lang/js/publish/action.yml | 22 ++++++++++++--- actions/release/lang/js/validate/action.yml | 22 ++++++++++++--- templates/steps/lang/js/env-dump.yml.erb | 30 +++++++++++++++------ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/actions/release/lang/js/publish/action.yml b/actions/release/lang/js/publish/action.yml index 568ede4..4f985b6 100644 --- a/actions/release/lang/js/publish/action.yml +++ b/actions/release/lang/js/publish/action.yml @@ -420,10 +420,24 @@ runs: npm --version echo "=== pnpm ===" pnpm --version || echo "(unavailable)" - echo "=== Registry ===" - pnpm config get registry - echo "=== package.json version ===" - node -p "require('./package.json').version" 2>/dev/null || echo "(unavailable)" + echo "=== package.json ===" + node -e ' + const p = require("./package.json"); + console.log(JSON.stringify({ + name: p.name, + version: p.version, + private: p.private || false, + packageManager: p.packageManager, + engines: p.engines, + devEngines: p.devEngines, + publishConfig: p.publishConfig, + lifecycle: Object.fromEntries( + ["prepublishOnly", "prepack", "prepare", "postpack"] + .filter((k) => p.scripts && p.scripts[k]) + .map((k) => [k, p.scripts[k]]), + ), + }, null, 2)); + ' 2>/dev/null || echo "(package.json unavailable)" - name: Build failure message if: ${{ failure() }} diff --git a/actions/release/lang/js/validate/action.yml b/actions/release/lang/js/validate/action.yml index cb9737e..d54fbd6 100644 --- a/actions/release/lang/js/validate/action.yml +++ b/actions/release/lang/js/validate/action.yml @@ -263,7 +263,21 @@ runs: npm --version echo "=== pnpm ===" pnpm --version || echo "(unavailable)" - echo "=== Registry ===" - pnpm config get registry - echo "=== package.json version ===" - node -p "require('./package.json').version" 2>/dev/null || echo "(unavailable)" + echo "=== package.json ===" + node -e ' + const p = require("./package.json"); + console.log(JSON.stringify({ + name: p.name, + version: p.version, + private: p.private || false, + packageManager: p.packageManager, + engines: p.engines, + devEngines: p.devEngines, + publishConfig: p.publishConfig, + lifecycle: Object.fromEntries( + ["prepublishOnly", "prepack", "prepare", "postpack"] + .filter((k) => p.scripts && p.scripts[k]) + .map((k) => [k, p.scripts[k]]), + ), + }, null, 2)); + ' 2>/dev/null || echo "(package.json unavailable)" diff --git a/templates/steps/lang/js/env-dump.yml.erb b/templates/steps/lang/js/env-dump.yml.erb index 920fe52..09271bb 100644 --- a/templates/steps/lang/js/env-dump.yml.erb +++ b/templates/steps/lang/js/env-dump.yml.erb @@ -1,9 +1,9 @@ <%# - Prints environment diagnostics (Node/npm/pnpm versions, the configured - registry, the package.json version) to help debug a failed release. Run from - the package root. + Prints environment diagnostics: Node/npm/pnpm versions and key package.json + fields (package manager, engines/devEngines, publishConfig, lifecycle scripts). + Scrapes the environment — takes no inputs. Run from the package root. - @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} + @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} -%> - name: Dump JavaScript environment shell: bash @@ -14,7 +14,21 @@ npm --version echo "=== pnpm ===" pnpm --version || echo "(unavailable)" - echo "=== Registry ===" - pnpm config get registry - echo "=== package.json version ===" - node -p "require('./package.json').version" 2>/dev/null || echo "(unavailable)" + echo "=== package.json ===" + node -e ' + const p = require("./package.json"); + console.log(JSON.stringify({ + name: p.name, + version: p.version, + private: p.private || false, + packageManager: p.packageManager, + engines: p.engines, + devEngines: p.devEngines, + publishConfig: p.publishConfig, + lifecycle: Object.fromEntries( + ["prepublishOnly", "prepack", "prepare", "postpack"] + .filter((k) => p.scripts && p.scripts[k]) + .map((k) => [k, p.scripts[k]]), + ), + }, null, 2)); + ' 2>/dev/null || echo "(package.json unavailable)" From 822b2465cc24f75cacd628ac6fcbfba6cdb237dd Mon Sep 17 00:00:00 2001 From: David Elner Date: Wed, 1 Jul 2026 16:37:04 +0000 Subject: [PATCH 2/3] Changed: Print richer env dump for failed Python workflows --- actions/release/lang/py/publish/action.yml | 16 ++++++++++++++-- actions/release/lang/py/validate/action.yml | 16 ++++++++++++++-- templates/steps/lang/py/env-dump.yml.erb | 16 ++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/actions/release/lang/py/publish/action.yml b/actions/release/lang/py/publish/action.yml index ffce1e8..c5e3f96 100644 --- a/actions/release/lang/py/publish/action.yml +++ b/actions/release/lang/py/publish/action.yml @@ -390,8 +390,20 @@ runs: echo "${UV_PYTHON:-(unset)}" echo "=== Built artifacts ===" ls -la dist 2>/dev/null || echo "(no dist/)" - echo "=== pyproject.toml ===" - head -40 pyproject.toml 2>/dev/null || echo "(no pyproject.toml)" + echo "=== pyproject.toml (release-relevant) ===" + uv run --no-project python -c ' + import json, tomllib, pathlib + t = tomllib.loads(pathlib.Path("pyproject.toml").read_text()) + proj = t.get("project", {}) + print(json.dumps({ + "name": proj.get("name"), + "version": proj.get("version") or ("(dynamic)" if "version" in proj.get("dynamic", []) else None), + "requires-python": proj.get("requires-python"), + "dependencies": proj.get("dependencies"), + "optional-dependencies": sorted((proj.get("optional-dependencies") or {}).keys()), + "build-system": t.get("build-system"), + }, indent=2)) + ' 2>/dev/null || echo "(pyproject.toml unavailable)" - name: Build failure message if: ${{ failure() }} diff --git a/actions/release/lang/py/validate/action.yml b/actions/release/lang/py/validate/action.yml index f5036b9..360aa41 100644 --- a/actions/release/lang/py/validate/action.yml +++ b/actions/release/lang/py/validate/action.yml @@ -228,5 +228,17 @@ runs: echo "${UV_PYTHON:-(unset)}" echo "=== Built artifacts ===" ls -la dist 2>/dev/null || echo "(no dist/)" - echo "=== pyproject.toml ===" - head -40 pyproject.toml 2>/dev/null || echo "(no pyproject.toml)" + echo "=== pyproject.toml (release-relevant) ===" + uv run --no-project python -c ' + import json, tomllib, pathlib + t = tomllib.loads(pathlib.Path("pyproject.toml").read_text()) + proj = t.get("project", {}) + print(json.dumps({ + "name": proj.get("name"), + "version": proj.get("version") or ("(dynamic)" if "version" in proj.get("dynamic", []) else None), + "requires-python": proj.get("requires-python"), + "dependencies": proj.get("dependencies"), + "optional-dependencies": sorted((proj.get("optional-dependencies") or {}).keys()), + "build-system": t.get("build-system"), + }, indent=2)) + ' 2>/dev/null || echo "(pyproject.toml unavailable)" diff --git a/templates/steps/lang/py/env-dump.yml.erb b/templates/steps/lang/py/env-dump.yml.erb index 52d9652..17ba3b0 100644 --- a/templates/steps/lang/py/env-dump.yml.erb +++ b/templates/steps/lang/py/env-dump.yml.erb @@ -16,5 +16,17 @@ echo "${UV_PYTHON:-(unset)}" echo "=== Built artifacts ===" ls -la dist 2>/dev/null || echo "(no dist/)" - echo "=== pyproject.toml ===" - head -40 pyproject.toml 2>/dev/null || echo "(no pyproject.toml)" + echo "=== pyproject.toml (release-relevant) ===" + uv run --no-project python -c ' + import json, tomllib, pathlib + t = tomllib.loads(pathlib.Path("pyproject.toml").read_text()) + proj = t.get("project", {}) + print(json.dumps({ + "name": proj.get("name"), + "version": proj.get("version") or ("(dynamic)" if "version" in proj.get("dynamic", []) else None), + "requires-python": proj.get("requires-python"), + "dependencies": proj.get("dependencies"), + "optional-dependencies": sorted((proj.get("optional-dependencies") or {}).keys()), + "build-system": t.get("build-system"), + }, indent=2)) + ' 2>/dev/null || echo "(pyproject.toml unavailable)" From ced9d89e4ca27f4f77f5b847dbd139e80554ce3f Mon Sep 17 00:00:00 2001 From: David Elner Date: Wed, 1 Jul 2026 16:59:31 +0000 Subject: [PATCH 3/3] Added: Richer GHA warnings/errors --- actions/release/lang/js/publish/action.yml | 18 ++++++++----- actions/release/lang/js/validate/action.yml | 27 ++++++++++++------- actions/release/lang/py/publish/action.yml | 9 ++++--- actions/release/lang/py/validate/action.yml | 19 +++++++------ actions/release/lang/ruby/publish/action.yml | 4 +-- actions/release/lang/ruby/validate/action.yml | 19 +++++++------ actions/release/notify-pending/action.yml | 2 +- templates/steps/lang/js/read-version.yml.erb | 8 ++++-- templates/steps/lang/js/setup.yml.erb | 6 ++++- templates/steps/lang/py/read-version.yml.erb | 8 ++++-- .../steps/lang/ruby/read-version.yml.erb | 8 ++++-- .../release/lang/js/npm-availability.yml.erb | 5 ++-- .../steps/release/lang/js/npm-publish.yml.erb | 8 +++--- .../release/lang/js/validate-channel.yml.erb | 2 +- .../release/lang/py/pypi-availability.yml.erb | 5 ++-- .../release/lang/py/pypi-publish.yml.erb | 5 +++- .../lang/ruby/rubygems-availability.yml.erb | 5 ++-- templates/steps/release/validate.yml.erb | 6 ++--- templates/steps/slack/send.yml.erb | 2 +- 19 files changed, 103 insertions(+), 63 deletions(-) diff --git a/actions/release/lang/js/publish/action.yml b/actions/release/lang/js/publish/action.yml index 4f985b6..041d36c 100644 --- a/actions/release/lang/js/publish/action.yml +++ b/actions/release/lang/js/publish/action.yml @@ -172,7 +172,11 @@ runs: - name: Install dependencies shell: bash working-directory: ${{ inputs.working_directory }} - run: pnpm install --frozen-lockfile + run: | + pnpm install --frozen-lockfile || { + echo "::error title=Dependency install failed::'pnpm install --frozen-lockfile' failed — the lockfile is likely out of sync with package.json (run 'pnpm install' and commit pnpm-lock.yaml)." + exit 1 + } - name: Build if: ${{ inputs.build == 'true' }} @@ -208,10 +212,12 @@ runs: [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then echo "DRY RUN: pnpm publish ${ARGS[*]} --dry-run" - pnpm publish "${ARGS[@]}" --dry-run - else - pnpm publish "${ARGS[@]}" + ARGS+=(--dry-run) fi + pnpm publish "${ARGS[@]}" || { + echo "::error title=npm publish failed::auth/ENEEDAUTH → the npm trusted publisher must list this repo, workflow, and (if gated) environment; 'cannot publish over ' → the version already exists on npm (bump it)." + exit 1 + } - name: Create GitHub release shell: bash @@ -404,7 +410,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" @@ -493,7 +499,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" diff --git a/actions/release/lang/js/validate/action.yml b/actions/release/lang/js/validate/action.yml index d54fbd6..13e2ad7 100644 --- a/actions/release/lang/js/validate/action.yml +++ b/actions/release/lang/js/validate/action.yml @@ -132,7 +132,11 @@ runs: - name: Install dependencies shell: bash working-directory: ${{ inputs.working_directory }} - run: pnpm install --frozen-lockfile + run: | + pnpm install --frozen-lockfile || { + echo "::error title=Dependency install failed::'pnpm install --frozen-lockfile' failed — the lockfile is likely out of sync with package.json (run 'pnpm install' and commit pnpm-lock.yaml)." + exit 1 + } - name: Read version id: read-version @@ -142,11 +146,15 @@ runs: VERSION_OVERRIDE: ${{ inputs.version }} run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT + VERSION="$VERSION_OVERRIDE" else VERSION=$(node -p "require('./package.json').version") - echo "version=$VERSION" >> $GITHUB_OUTPUT fi + if [ -z "$VERSION" ] || [ "$VERSION" = "undefined" ]; then + echo "::error title=Could not read version::resolved an empty version — check the \"version\" field in package.json (or pass a version override)." + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Check npm version availability shell: bash @@ -165,15 +173,14 @@ runs: "$REGISTRY/$PACKAGE_NAME/$VERSION") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME@$VERSION is available on npm." ;; *) - echo "Error: unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac @@ -188,7 +195,7 @@ runs: for c in "${OK[@]}"; do [ "$(echo "$c" | xargs)" = "$CHANNEL" ] && exit 0 done - echo "Error: release channel '$CHANNEL' is not in the allowed set: $ALLOWED" + echo "::error title=Release channel not allowed::release channel '$CHANNEL' is not in the allowed set: $ALLOWED" exit 1 - name: Validate SHA format @@ -197,7 +204,7 @@ runs: SHA: ${{ inputs.sha }} run: | if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then - echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." + echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." exit 1 fi @@ -217,9 +224,9 @@ runs: if git rev-parse "$TAG" >/dev/null 2>&1; then if [ "$DRY_RUN" = "true" ]; then - echo "Warning: Tag $TAG already exists — skipping in dry run" + echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run." else - echo "Error: Tag $TAG already exists — has the version been bumped?" + echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?" exit 1 fi fi diff --git a/actions/release/lang/py/publish/action.yml b/actions/release/lang/py/publish/action.yml index c5e3f96..9111b6f 100644 --- a/actions/release/lang/py/publish/action.yml +++ b/actions/release/lang/py/publish/action.yml @@ -179,7 +179,10 @@ runs: else ARGS+=(--no-attestations) fi - uv publish "${ARGS[@]}" + uv publish "${ARGS[@]}" || { + echo "::error title=PyPI publish failed::auth/OIDC error → the PyPI trusted publisher must list this repo, workflow, and (if gated) environment; a 400 'File already exists' → the version is already on PyPI (bump it)." + exit 1 + } - name: Create GitHub release shell: bash @@ -372,7 +375,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" @@ -459,7 +462,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" diff --git a/actions/release/lang/py/validate/action.yml b/actions/release/lang/py/validate/action.yml index 360aa41..5112662 100644 --- a/actions/release/lang/py/validate/action.yml +++ b/actions/release/lang/py/validate/action.yml @@ -112,13 +112,17 @@ runs: VERSION_FILE: ${{ inputs.version_file }} run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> "$GITHUB_OUTPUT" + VERSION="$VERSION_OVERRIDE" else # exec the version file in an isolated namespace (no package import needed), # matching how the SDKs expose VERSION in a standalone version.py. VERSION=$(uv run --no-project python -c "import runpy,sys; print(runpy.run_path(sys.argv[1])['VERSION'])" "$VERSION_FILE") - echo "version=$VERSION" >> "$GITHUB_OUTPUT" fi + if [ -z "$VERSION" ] || [ "$VERSION" = "None" ]; then + echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (or pass a version override)." + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Check PyPI version availability shell: bash @@ -135,15 +139,14 @@ runs: "https://pypi.org/pypi/$PACKAGE_NAME/$VERSION/json") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME $VERSION is available on PyPI." ;; *) - echo "Error: unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac @@ -154,7 +157,7 @@ runs: SHA: ${{ inputs.sha }} run: | if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then - echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." + echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." exit 1 fi @@ -174,9 +177,9 @@ runs: if git rev-parse "$TAG" >/dev/null 2>&1; then if [ "$DRY_RUN" = "true" ]; then - echo "Warning: Tag $TAG already exists — skipping in dry run" + echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run." else - echo "Error: Tag $TAG already exists — has the version been bumped?" + echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?" exit 1 fi fi diff --git a/actions/release/lang/ruby/publish/action.yml b/actions/release/lang/ruby/publish/action.yml index 0d63cdc..9bb7a30 100644 --- a/actions/release/lang/ruby/publish/action.yml +++ b/actions/release/lang/ruby/publish/action.yml @@ -305,7 +305,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" @@ -378,7 +378,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" diff --git a/actions/release/lang/ruby/validate/action.yml b/actions/release/lang/ruby/validate/action.yml index 7f887b6..4331cce 100644 --- a/actions/release/lang/ruby/validate/action.yml +++ b/actions/release/lang/ruby/validate/action.yml @@ -84,11 +84,15 @@ runs: VERSION_MODULE: ${{ inputs.version_module }} run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT + VERSION="$VERSION_OVERRIDE" else VERSION=$(ruby -r "./$VERSION_FILE" -e "puts Object.const_get(ENV.fetch('VERSION_MODULE')).const_get(:VERSION)") - echo "version=$VERSION" >> $GITHUB_OUTPUT fi + if [ -z "$VERSION" ]; then + echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (module $VERSION_MODULE), or pass a version override." + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Check RubyGems version availability shell: bash @@ -105,15 +109,14 @@ runs: "https://rubygems.org/api/v2/rubygems/$PACKAGE_NAME/versions/$VERSION.json") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME $VERSION is available on RubyGems." ;; *) - echo "Error: unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac @@ -124,7 +127,7 @@ runs: SHA: ${{ inputs.sha }} run: | if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then - echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." + echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." exit 1 fi @@ -144,9 +147,9 @@ runs: if git rev-parse "$TAG" >/dev/null 2>&1; then if [ "$DRY_RUN" = "true" ]; then - echo "Warning: Tag $TAG already exists — skipping in dry run" + echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run." else - echo "Error: Tag $TAG already exists — has the version been bumped?" + echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?" exit 1 fi fi diff --git a/actions/release/notify-pending/action.yml b/actions/release/notify-pending/action.yml index aa9ad99..c15936f 100644 --- a/actions/release/notify-pending/action.yml +++ b/actions/release/notify-pending/action.yml @@ -202,7 +202,7 @@ runs: if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE" diff --git a/templates/steps/lang/js/read-version.yml.erb b/templates/steps/lang/js/read-version.yml.erb index 466fd94..5f6957f 100644 --- a/templates/steps/lang/js/read-version.yml.erb +++ b/templates/steps/lang/js/read-version.yml.erb @@ -14,8 +14,12 @@ VERSION_OVERRIDE: <%= locals.fetch(:version) { "${{ inputs.version }}" } %> run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT + VERSION="$VERSION_OVERRIDE" else VERSION=$(node -p "require('./package.json').version") - echo "version=$VERSION" >> $GITHUB_OUTPUT fi + if [ -z "$VERSION" ] || [ "$VERSION" = "undefined" ]; then + echo "::error title=Could not read version::resolved an empty version — check the \"version\" field in package.json (or pass a version override)." + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/templates/steps/lang/js/setup.yml.erb b/templates/steps/lang/js/setup.yml.erb index 053e336..88a5f71 100644 --- a/templates/steps/lang/js/setup.yml.erb +++ b/templates/steps/lang/js/setup.yml.erb @@ -45,4 +45,8 @@ - name: Install dependencies shell: bash working-directory: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %> - run: pnpm install --frozen-lockfile + run: | + pnpm install --frozen-lockfile || { + echo "::error title=Dependency install failed::'pnpm install --frozen-lockfile' failed — the lockfile is likely out of sync with package.json (run 'pnpm install' and commit pnpm-lock.yaml)." + exit 1 + } diff --git a/templates/steps/lang/py/read-version.yml.erb b/templates/steps/lang/py/read-version.yml.erb index 18c9b87..289cd2d 100644 --- a/templates/steps/lang/py/read-version.yml.erb +++ b/templates/steps/lang/py/read-version.yml.erb @@ -15,10 +15,14 @@ VERSION_FILE: <%= locals.fetch(:version_file) { "${{ inputs.version_file }}" } %> run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> "$GITHUB_OUTPUT" + VERSION="$VERSION_OVERRIDE" else # exec the version file in an isolated namespace (no package import needed), # matching how the SDKs expose VERSION in a standalone version.py. VERSION=$(uv run --no-project python -c "import runpy,sys; print(runpy.run_path(sys.argv[1])['VERSION'])" "$VERSION_FILE") - echo "version=$VERSION" >> "$GITHUB_OUTPUT" fi + if [ -z "$VERSION" ] || [ "$VERSION" = "None" ]; then + echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (or pass a version override)." + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" diff --git a/templates/steps/lang/ruby/read-version.yml.erb b/templates/steps/lang/ruby/read-version.yml.erb index 7cc86e8..acdabac 100644 --- a/templates/steps/lang/ruby/read-version.yml.erb +++ b/templates/steps/lang/ruby/read-version.yml.erb @@ -16,8 +16,12 @@ VERSION_MODULE: <%= locals.fetch(:version_module) { "${{ inputs.version_module }}" } %> run: | if [ -n "$VERSION_OVERRIDE" ]; then - echo "version=$VERSION_OVERRIDE" >> $GITHUB_OUTPUT + VERSION="$VERSION_OVERRIDE" else VERSION=$(ruby -r "./$VERSION_FILE" -e "puts Object.const_get(ENV.fetch('VERSION_MODULE')).const_get(:VERSION)") - echo "version=$VERSION" >> $GITHUB_OUTPUT fi + if [ -z "$VERSION" ]; then + echo "::error title=Could not read version::resolved an empty version — check VERSION in $VERSION_FILE (module $VERSION_MODULE), or pass a version override." + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/templates/steps/release/lang/js/npm-availability.yml.erb b/templates/steps/release/lang/js/npm-availability.yml.erb index e5ac1a8..6700df1 100644 --- a/templates/steps/release/lang/js/npm-availability.yml.erb +++ b/templates/steps/release/lang/js/npm-availability.yml.erb @@ -27,15 +27,14 @@ "$REGISTRY/$PACKAGE_NAME/$VERSION") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME@$VERSION already exists on npm — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME@$VERSION is available on npm." ;; *) - echo "Error: unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from the npm registry (HTTP $CODE) checking $PACKAGE_NAME@$VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac diff --git a/templates/steps/release/lang/js/npm-publish.yml.erb b/templates/steps/release/lang/js/npm-publish.yml.erb index cfeca53..50e2808 100644 --- a/templates/steps/release/lang/js/npm-publish.yml.erb +++ b/templates/steps/release/lang/js/npm-publish.yml.erb @@ -32,7 +32,9 @@ [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then echo "DRY RUN: pnpm publish ${ARGS[*]} --dry-run" - pnpm publish "${ARGS[@]}" --dry-run - else - pnpm publish "${ARGS[@]}" + ARGS+=(--dry-run) fi + pnpm publish "${ARGS[@]}" || { + echo "::error title=npm publish failed::auth/ENEEDAUTH → the npm trusted publisher must list this repo, workflow, and (if gated) environment; 'cannot publish over ' → the version already exists on npm (bump it)." + exit 1 + } diff --git a/templates/steps/release/lang/js/validate-channel.yml.erb b/templates/steps/release/lang/js/validate-channel.yml.erb index 7dc2437..0345f47 100644 --- a/templates/steps/release/lang/js/validate-channel.yml.erb +++ b/templates/steps/release/lang/js/validate-channel.yml.erb @@ -17,5 +17,5 @@ for c in "${OK[@]}"; do [ "$(echo "$c" | xargs)" = "$CHANNEL" ] && exit 0 done - echo "Error: release channel '$CHANNEL' is not in the allowed set: $ALLOWED" + echo "::error title=Release channel not allowed::release channel '$CHANNEL' is not in the allowed set: $ALLOWED" exit 1 diff --git a/templates/steps/release/lang/py/pypi-availability.yml.erb b/templates/steps/release/lang/py/pypi-availability.yml.erb index 6553ad9..72fef1f 100644 --- a/templates/steps/release/lang/py/pypi-availability.yml.erb +++ b/templates/steps/release/lang/py/pypi-availability.yml.erb @@ -21,15 +21,14 @@ "https://pypi.org/pypi/$PACKAGE_NAME/$VERSION/json") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on PyPI — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME $VERSION is available on PyPI." ;; *) - echo "Error: unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from PyPI (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac diff --git a/templates/steps/release/lang/py/pypi-publish.yml.erb b/templates/steps/release/lang/py/pypi-publish.yml.erb index 45a54f1..4ed445d 100644 --- a/templates/steps/release/lang/py/pypi-publish.yml.erb +++ b/templates/steps/release/lang/py/pypi-publish.yml.erb @@ -38,4 +38,7 @@ else ARGS+=(--no-attestations) fi - uv publish "${ARGS[@]}" + uv publish "${ARGS[@]}" || { + echo "::error title=PyPI publish failed::auth/OIDC error → the PyPI trusted publisher must list this repo, workflow, and (if gated) environment; a 400 'File already exists' → the version is already on PyPI (bump it)." + exit 1 + } diff --git a/templates/steps/release/lang/ruby/rubygems-availability.yml.erb b/templates/steps/release/lang/ruby/rubygems-availability.yml.erb index 178d8f9..91fb05f 100644 --- a/templates/steps/release/lang/ruby/rubygems-availability.yml.erb +++ b/templates/steps/release/lang/ruby/rubygems-availability.yml.erb @@ -22,15 +22,14 @@ "https://rubygems.org/api/v2/rubygems/$PACKAGE_NAME/versions/$VERSION.json") case "$CODE" in 200) - echo "Error: $PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?" + echo "::error title=Version already published::$PACKAGE_NAME $VERSION already exists on RubyGems — has the version been bumped?" exit 1 ;; 404) echo "$PACKAGE_NAME $VERSION is available on RubyGems." ;; *) - echo "Error: unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION;" - echo "cannot confirm the version is unpublished — aborting rather than risk a bad release." + echo "::error title=Registry check failed::unexpected response from RubyGems (HTTP $CODE) checking $PACKAGE_NAME $VERSION — cannot confirm the version is unpublished; aborting rather than risk a bad release." exit 1 ;; esac diff --git a/templates/steps/release/validate.yml.erb b/templates/steps/release/validate.yml.erb index 2d0fb9a..20b08e4 100644 --- a/templates/steps/release/validate.yml.erb +++ b/templates/steps/release/validate.yml.erb @@ -18,7 +18,7 @@ SHA: <%= locals.fetch(:sha) { "${{ inputs.sha }}" } %> run: | if ! echo "$SHA" | grep -qE '^[0-9a-f]{40}$'; then - echo "Error: sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." + echo "::error title=Invalid release SHA::sha must be a full 40-character commit SHA — branch names and short SHAs are not accepted." exit 1 fi @@ -38,9 +38,9 @@ if git rev-parse "$TAG" >/dev/null 2>&1; then if [ "$DRY_RUN" = "true" ]; then - echo "Warning: Tag $TAG already exists — skipping in dry run" + echo "::warning title=Release tag already exists::Tag $TAG already exists — skipping in dry run." else - echo "Error: Tag $TAG already exists — has the version been bumped?" + echo "::error title=Release tag already exists::Tag $TAG already exists — has the version been bumped?" exit 1 fi fi diff --git a/templates/steps/slack/send.yml.erb b/templates/steps/slack/send.yml.erb index 360e3b8..d100927 100644 --- a/templates/steps/slack/send.yml.erb +++ b/templates/steps/slack/send.yml.erb @@ -30,7 +30,7 @@ if echo "$RESPONSE" | jq -e '.ok' > /dev/null 2>&1; then echo "Slack notification sent." elif [ "$FAIL_ON_ERROR" = "true" ]; then - echo "Slack notification failed: $RESPONSE" + echo "::error title=Slack notification failed::$RESPONSE" exit 1 else echo "::warning::Slack notification failed: $RESPONSE"