Skip to content

Commit 9106838

Browse files
authored
Add v1 of workflows for parsing SemVer and for publishing JavaScript packages to npmjs and GitHub packages (#16)
* v1 of reusable workflow to process a semver and to publish JavaScript packages to npm and github * Make the leading v optional for inputs.version
1 parent ba10312 commit 9106838

3 files changed

Lines changed: 272 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
version:
5+
description: "Optional version override (SemVer WITHOUT leading v), e.g. 1.2.3 or 1.2.3-rc.1"
6+
type: string
7+
required: false
8+
default: ""
9+
outputs:
10+
version:
11+
value: ${{ jobs.extract.outputs.version }}
12+
dist_tag:
13+
value: ${{ jobs.extract.outputs.dist_tag }}
14+
15+
jobs:
16+
extract:
17+
runs-on: ubuntu-latest
18+
outputs:
19+
version: ${{ steps.out.outputs.version }}
20+
dist_tag: ${{ steps.out.outputs.dist_tag }}
21+
steps:
22+
- id: out
23+
shell: bash
24+
run: |
25+
# SemVer (no leading "v" in the regex; leading "v" is stripped before validation)
26+
SEMVER_RE='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
27+
28+
VERSION_INPUT='${{ inputs.version }}'
29+
30+
# Decide source: input override or tag
31+
SOURCE="inputs.version"
32+
VERSION_STRING="$VERSION_INPUT"
33+
if [[ -z "$VERSION_STRING" ]]; then
34+
SOURCE="tag"
35+
if [[ "${GITHUB_REF:-}" != refs/tags/* ]]; then
36+
echo "::error::No inputs.version provided and this run is not a tag ref (GITHUB_REF='${GITHUB_REF:-}')"
37+
exit 1
38+
fi
39+
VERSION_STRING="$GITHUB_REF_NAME"
40+
41+
# Tag must start with v (only when using tag source)
42+
if [[ "$VERSION_STRING" != v* ]]; then
43+
echo "::error::Tag must start with 'v' (got '$VERSION_STRING')"
44+
exit 1
45+
fi
46+
fi
47+
48+
# Normalize leading v, then validate SemVer
49+
VERSION="${VERSION_STRING#v}"
50+
if [[ ! "$VERSION" =~ $SEMVER_RE ]]; then
51+
if [[ "$SOURCE" == "inputs.version" ]]; then
52+
echo "::error::Invalid inputs.version: '$VERSION_STRING' (expected SemVer like 1.2.3 or 1.2.3-rc.1 with an optional leading v)"
53+
else
54+
echo "::error::Invalid tag version: '$VERSION_STRING' (expected v followed by SemVer like v1.2.3 or v1.2.3-rc.1)"
55+
fi
56+
exit 1
57+
fi
58+
59+
# Compute dist_tag:
60+
# - stable => latest
61+
# - prerelease => first identifier before dot (rc.1 -> rc)
62+
NO_BUILD="${VERSION%%+*}" # drop +build -> 1.2.3-rc.1
63+
DIST_TAG="latest"
64+
if [[ "$NO_BUILD" == *-* ]]; then
65+
PRERELEASE="${NO_BUILD#*-}" # -> rc.1
66+
DIST_TAG="${PRERELEASE%%.*}" # -> rc
67+
fi
68+
69+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
70+
echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT"
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
version:
5+
description: 'Version number to release e.g., v1.2.3 or v1.2.3-rc.1'
6+
required: true
7+
type: string
8+
9+
dist_tag:
10+
description: 'Dist tag to release e.g., latest, alpha, beta, rc, etc.'
11+
required: true
12+
type: string
13+
14+
dry_run:
15+
description: 'Run all checks but do not actually publish packages.'
16+
default: false
17+
required: false
18+
type: boolean
19+
20+
runner_label:
21+
description: 'Label for the runner to be used for the jobs.'
22+
default: 'ubuntu-latest'
23+
required: false
24+
type: string
25+
26+
27+
jobs:
28+
publish-javascript-github:
29+
runs-on: ${{ inputs.runner_label }}
30+
permissions:
31+
contents: read
32+
packages: write
33+
34+
steps:
35+
- name: Checkout repository contents
36+
uses: actions/checkout@v6
37+
38+
- name: Set up Node.js
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: 'lts/*'
42+
registry-url: 'https://registry.npmjs.org'
43+
44+
- name: Get package name
45+
shell: bash
46+
run: |
47+
set -euo pipefail
48+
PACKAGE_NAME="$(npm pkg get name | tr -d '"')"
49+
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> "$GITHUB_ENV"
50+
51+
- name: Validate package.json version
52+
shell: bash
53+
run: |
54+
EXPECTED_VERSION="${{ inputs.version }}"
55+
PKG_VERSION="$(npm pkg get version | tr -d '"')"
56+
57+
if [[ "$PKG_VERSION" != "$EXPECTED_VERSION" ]]; then
58+
echo "::error::package.json version ($PKG_VERSION) does not match release version ($EXPECTED_VERSION)"
59+
exit 1
60+
fi
61+
62+
- name: Install npm dependencies
63+
shell: bash
64+
run: npm ci
65+
66+
- name: Publish to GitHub Packages
67+
id: publish_github
68+
env:
69+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70+
continue-on-error: true
71+
run: |
72+
set -euo pipefail
73+
74+
npm config set "@fastly:registry" "https://npm.pkg.github.com/"
75+
npm config set "//npm.pkg.github.com/:_authToken" "$NODE_AUTH_TOKEN"
76+
77+
DIST_TAG="${{ inputs.dist_tag }}"
78+
echo "Publishing ${PACKAGE_NAME} to pkg.github.com using dist-tag: ${DIST_TAG}"
79+
if [[ '${{ inputs.dry_run }}' == 'true' ]]; then
80+
npm publish --dry-run --tag "$DIST_TAG"
81+
else
82+
npm publish --tag "$DIST_TAG"
83+
fi
84+
85+
- name: Publish status
86+
if: always()
87+
shell: bash
88+
run: |
89+
set -euo pipefail
90+
91+
if [[ '${{ inputs.dry_run }}' == 'true' ]]; then
92+
echo "::notice::Dry run enabled — no package was actually published."
93+
exit 0
94+
fi
95+
96+
echo "github outcome: ${{ steps.publish_github.outcome || 'skipped' }}"
97+
if [[ "${{ steps.publish_github.outcome }}" == 'failure' ]]; then
98+
echo "::error::Publish to GitHub Packages failed."
99+
echo "::notice::Hint: Ensure the caller workflow grants 'permissions: packages: write'."
100+
exit 1
101+
fi
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
version:
5+
description: 'Version number to release e.g., v1.2.3 or v1.2.3-rc.1'
6+
required: true
7+
type: string
8+
9+
dist_tag:
10+
description: 'Dist tag to release e.g., latest, alpha, beta, rc, etc.'
11+
required: true
12+
type: string
13+
14+
dry_run:
15+
description: 'Run all checks but do not actually publish packages.'
16+
default: false
17+
required: false
18+
type: boolean
19+
20+
runner_label:
21+
description: 'Label for the runner to be used for the jobs.'
22+
default: 'ubuntu-latest'
23+
required: false
24+
type: string
25+
26+
27+
jobs:
28+
publish-javascript-npmjs:
29+
runs-on: ${{ inputs.runner_label }}
30+
permissions:
31+
contents: read
32+
id-token: write
33+
34+
steps:
35+
- name: Checkout repository contents
36+
uses: actions/checkout@v6
37+
38+
- name: Set up Node.js
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: 'lts/*'
42+
registry-url: 'https://registry.npmjs.org'
43+
44+
- name: Get package name
45+
shell: bash
46+
run: |
47+
set -euo pipefail
48+
PACKAGE_NAME="$(npm pkg get name | tr -d '"')"
49+
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> "$GITHUB_ENV"
50+
51+
- name: Validate package.json version
52+
shell: bash
53+
run: |
54+
EXPECTED_VERSION="${{ inputs.version }}"
55+
PKG_VERSION="$(npm pkg get version | tr -d '"')"
56+
57+
if [[ "$PKG_VERSION" != "$EXPECTED_VERSION" ]]; then
58+
echo "::error::package.json version ($PKG_VERSION) does not match release version ($EXPECTED_VERSION)"
59+
exit 1
60+
fi
61+
62+
- name: Install npm dependencies
63+
shell: bash
64+
run: npm ci
65+
66+
- name: Publish to npmjs.org
67+
id: publish_npmjs
68+
run: |
69+
set -euo pipefail
70+
71+
if [[ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" || -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]]; then
72+
echo "::error::OIDC not available."
73+
echo "::notice::Hint: Caller must grant: permissions: id-token: write"
74+
exit 1
75+
fi
76+
77+
DIST_TAG="${{ inputs.dist_tag }}"
78+
echo "Publishing ${PACKAGE_NAME} to npmjs.org using dist-tag: ${DIST_TAG}"
79+
if [[ '${{ inputs.dry_run }}' == 'true' ]]; then
80+
npm publish --dry-run --access=public --tag "$DIST_TAG"
81+
else
82+
npm publish --access=public --tag "$DIST_TAG"
83+
fi
84+
85+
- name: Publish status
86+
if: always()
87+
shell: bash
88+
run: |
89+
set -euo pipefail
90+
91+
if [[ '${{ inputs.dry_run }}' == 'true' ]]; then
92+
echo "::notice::Dry run enabled — no package was actually published."
93+
exit 0
94+
fi
95+
96+
echo "npmjs outcome: ${{ steps.publish_npmjs.outcome || 'skipped' }}"
97+
if [[ "${{ steps.publish_npmjs.outcome }}" == 'failure' ]]; then
98+
echo "::error::Publish to npmjs.org failed."
99+
echo "::notice::Hint: Ensure that the caller repo/workflow is registered as a Trusted Publisher in npm for ${PACKAGE_NAME}."
100+
exit 1
101+
fi

0 commit comments

Comments
 (0)