Support pre-release publishing in npm-publish workflow#1464
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the npm publish GitHub Actions workflow to automatically choose an npm dist-tag based on the version in package.json, so pre-release versions (alpha/beta/rc) don’t accidentally publish under latest.
Changes:
- Add a
Determine dist-tagstep that derives a dist-tag frompackage.jsonversion prerelease label, defaulting tolatest. - Pass the computed dist-tag into
npm publish --tagfor both dry-run and real publish paths.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Pre-release version (e.g. 1.9.0-alpha.0) — extract pre-release label | ||
| TAG=$(echo "$VERSION" | sed 's/.*-\([a-zA-Z]*\).*/\1/') | ||
| echo "tag=$TAG" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "tag=latest" >> $GITHUB_OUTPUT | ||
| fi | ||
| echo "Version: $VERSION, Tag: $(cat $GITHUB_OUTPUT | grep tag | cut -d= -f2)" |
There was a problem hiding this comment.
The dist-tag extraction via sed 's/.*-\([a-zA-Z]*\).*/\1/' is not SemVer-robust: valid prerelease identifiers can include digits/hyphens (e.g. 1.2.3-alpha1.0, 1.2.3-0.3.7), which would yield a truncated or empty TAG. If TAG becomes empty, the publish step may fail or mis-handle subsequent args. Consider parsing the first prerelease identifier more reliably (e.g., take the substring after -, strip build metadata after +, then take up to the first .) and fail the step if the derived tag is empty; also quote $GITHUB_OUTPUT in the redirect to avoid path/word-splitting issues.
| # Pre-release version (e.g. 1.9.0-alpha.0) — extract pre-release label | |
| TAG=$(echo "$VERSION" | sed 's/.*-\([a-zA-Z]*\).*/\1/') | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| else | |
| echo "tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Version: $VERSION, Tag: $(cat $GITHUB_OUTPUT | grep tag | cut -d= -f2)" | |
| # Pre-release version (e.g. 1.9.0-alpha.0, 1.2.3-0.3.7) — extract first pre-release identifier | |
| PRERELEASE=${VERSION#*-} # remove everything up to and including the first '-' | |
| PRERELEASE=${PRERELEASE%%+*} # strip build metadata (anything after '+') | |
| TAG=${PRERELEASE%%.*} # take the first identifier before any '.' | |
| if [[ -z "$TAG" ]]; then | |
| echo "Error: failed to derive dist-tag from version '$VERSION'" >&2 | |
| exit 1 | |
| fi | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=latest" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "Version: $VERSION, Tag: $(grep '^tag=' "$GITHUB_OUTPUT" | tail -n1 | cut -d= -f2)" |
| echo "tag=latest" >> $GITHUB_OUTPUT | ||
| fi | ||
| echo "Version: $VERSION, Tag: $(cat $GITHUB_OUTPUT | grep tag | cut -d= -f2)" |
There was a problem hiding this comment.
This log line reads back from $GITHUB_OUTPUT using cat | grep | cut. That file can contain multiple outputs and grep tag is not anchored, so it could log the wrong value if more outputs are ever added. Also, if grep finds no match it will exit non-zero, which could fail the step depending on shell settings. Prefer setting a TAG variable in both branches and logging that directly (or anchor the match to ^tag= if you keep this approach).
| echo "tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Version: $VERSION, Tag: $(cat $GITHUB_OUTPUT | grep tag | cut -d= -f2)" | |
| TAG=latest | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| fi | |
| echo "Version: $VERSION, Tag: $TAG" |
| npm publish --provenance --access public --tag ${{ steps.dist-tag.outputs.tag }} --dry-run ./dist/rclnodejs-*.tgz | ||
| else | ||
| npm publish --provenance --access public ./dist/rclnodejs-*.tgz | ||
| npm publish --provenance --access public --tag ${{ steps.dist-tag.outputs.tag }} ./dist/rclnodejs-*.tgz |
There was a problem hiding this comment.
--tag ${{ steps.dist-tag.outputs.tag }} is unquoted. If the output is unexpectedly empty, the next token (e.g., --dry-run or the tarball path) can be consumed as the tag value, producing a confusing failure mode. Quote the substitution and consider guarding against an empty tag before invoking npm publish.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - name: Determine dist-tag | ||
| id: dist-tag | ||
| run: | |
There was a problem hiding this comment.
id: dist-tag is referenced as ${{ steps.dist-tag.outputs.tag }}. In GitHub Actions expressions, step IDs containing - generally can’t be accessed via dot notation (the - is treated as an operator). Rename the step id to something like dist_tag (or distTag) or use bracket syntax: steps['dist-tag'].outputs.tag.
| const v = require('./package.json').version; | ||
| const pre = v.split('-')[1]; | ||
| if (!pre) { 'latest' } | ||
| else { pre.split('.')[0].replace(/[0-9]+$/, '') || 'next' } |
There was a problem hiding this comment.
The node -p snippet doesn’t actually return a value: if (!pre) { 'latest' } else { ... } is a statement, so node -p will print undefined (or an empty/incorrect value), which can lead to publishing with an invalid dist-tag. Make the last evaluated expression a string (e.g., a ternary), or wrap the logic in an IIFE that returns the tag, and consider validating that the computed tag is not undefined/null and matches npm’s dist-tag rules.
| const v = require('./package.json').version; | |
| const pre = v.split('-')[1]; | |
| if (!pre) { 'latest' } | |
| else { pre.split('.')[0].replace(/[0-9]+$/, '') || 'next' } | |
| (() => { | |
| const v = require('./package.json').version; | |
| const pre = v.split('-')[1]; | |
| const tag = !pre | |
| ? 'latest' | |
| : (pre.split('.')[0].replace(/[0-9]+$/, '') || 'next'); | |
| if (!tag || !/^[a-z0-9][a-z0-9._-]*$/.test(tag)) { | |
| throw new Error('Invalid dist-tag: ' + tag); | |
| } | |
| return tag; | |
| })() |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| echo "::error::Failed to determine dist-tag" | ||
| exit 1 | ||
| fi | ||
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
echo "tag=$TAG" >> "$GITHUB_OUTPUT" can behave unexpectedly if the value ever starts with - or contains characters that echo interprets; using printf 'tag=%s\n' "$TAG" >> "$GITHUB_OUTPUT" is more robust for writing outputs in bash.
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| printf 'tag=%s\n' "$TAG" >> "$GITHUB_OUTPUT" |
Add automatic dist-tag detection to the publish step. npm requires `--tag` when publishing pre-release versions to prevent them from becoming the default `latest` on the registry. The new `Determine dist-tag` step reads the version from `package.json` and extracts the pre-release label (e.g. `alpha`, `beta`, `rc`) if present, otherwise defaults to `latest`. This tag is passed to `npm publish --tag`. Examples: - `1.9.0-alpha.0` → `--tag alpha` - `1.9.0-beta.1` → `--tag beta` - `1.9.0-rc.0` → `--tag rc` - `1.9.0` → `--tag latest` Fix: #1459
Add automatic dist-tag detection to the publish step. npm requires
--tagwhen publishing pre-release versions to prevent them from becoming the defaultlateston the registry.The new
Determine dist-tagstep reads the version frompackage.jsonand extracts the pre-release label (e.g.alpha,beta,rc) if present, otherwise defaults tolatest. This tag is passed tonpm publish --tag.Examples:
1.9.0-alpha.0→--tag alpha1.9.0-beta.1→--tag beta1.9.0-rc.0→--tag rc1.9.0→--tag latestFix: #1459