Skip to content

Commit 7ac3cdc

Browse files
feat(ci): add release automation
This commit adds support for automated releases to componentize-js, following the same setup that is in use by jco. Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
1 parent 777ac42 commit 7ac3cdc

File tree

11 files changed

+656
-7
lines changed

11 files changed

+656
-7
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: create-release-pr
2+
run-name: create-release-pr
3+
description: |
4+
Create a release PR for a project in the repository
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
type: string
11+
required: true
12+
description: |
13+
Version to prep for release (ex. `0.1.0`, `0.1.0-rc.0`)
14+
15+
permissions:
16+
contents: none
17+
18+
jobs:
19+
create-release-pr:
20+
runs-on: ubuntu-24.04
21+
permissions:
22+
id-token: write
23+
pull-requests: write
24+
contents: write
25+
issues: write
26+
steps:
27+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28+
with:
29+
fetch-depth: 0
30+
31+
# Install Rust deps
32+
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
33+
- uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2
34+
with:
35+
tool: just
36+
- uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2
37+
with:
38+
tool: git-cliff
39+
- uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2
40+
with:
41+
tool: cargo-get
42+
- uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2
43+
with:
44+
tool: cargo-edit
45+
46+
- name: Cache npm install
47+
id: cache-node-modules
48+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
49+
with:
50+
key: node-modules-dev-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('packages/**/package.json') }}
51+
path: |
52+
node_modules
53+
54+
- name: Install debug NPM packages
55+
run: |
56+
npm install -D
57+
58+
- name: Gather project metadata
59+
id: project-meta
60+
env:
61+
NEXT_VERSION: ${{ inputs.version }}
62+
shell: bash
63+
run: |
64+
if [[ $NEXT_VERSION == v* ]]; then
65+
echo "::error::next version [$NEXT_VERSION] starts with 'v' -- enter only the semver version (ex. '0.1.0', not 'v0.1.0')";
66+
exit 1;
67+
fi
68+
69+
export PROJECT_DIR=$PWD;
70+
export CURRENT_VERSION=$(node -e "process.stdout.write(require(process.env.PROJECT_DIR + '/package.json').version)");
71+
72+
echo -e "project-dir=$PROJECT_DIR"
73+
echo -e "current-version=$CURRENT_VERSION"
74+
echo -e "next-version=$NEXT_VERSION"
75+
76+
echo -e "project-dir=$PROJECT_DIR" >> $GITHUB_OUTPUT
77+
echo -e "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
78+
echo -e "next-version=$NEXT_VERSION" >> $GITHUB_OUTPUT
79+
80+
- name: Ensure next version is after current
81+
run: |
82+
IS_AFTER=$(node scripts/semver-lt.mjs ${{ steps.project-meta.outputs.current-version }} ${{ steps.project-meta.outputs.next-version }});
83+
if [ "$IS_AFTER" == "false" ]; then \
84+
echo "::error::project [componentize-js] next version [${{ steps.project-meta.outputs.next-version }}] is not after current version [${{ steps.project-meta.outputs.current-version }}]";
85+
exit 1;
86+
fi
87+
88+
# Set project version
89+
- name: Set project version
90+
working-directory: ${{ steps.project-meta.outputs.project-dir }}
91+
shell: bash
92+
run: |
93+
npm pkg set version=${{ steps.project-meta.outputs.next-version }};
94+
sed -i \
95+
"s/version('${{ steps.project-meta.outputs.current-version }}')/version('${{ steps.project-meta.outputs.next-version }}')/" \
96+
src/cli.js;
97+
98+
# Generate changelog
99+
#
100+
# NOTE: we use the 'latest' tag here, because starting from an rc/alpha/beta release
101+
# the rc/alpha/beta release tags are ignored and the "latest" tag is the previous stable version
102+
- name: Generate changelog
103+
working-directory: ${{ steps.project-meta.outputs.project-dir }}
104+
env:
105+
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
106+
run: |
107+
git cliff \
108+
--repository=${{ github.workspace }}/.git \
109+
--config=./cliff.toml \
110+
--latest \
111+
--tag=${{ steps.project-meta.outputs.next-version }} \
112+
--prepend=CHANGELOG.md
113+
114+
# Create release PR
115+
- name: Create release prep PR
116+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
117+
with:
118+
branch: prep-release-${{ steps.project-meta.outputs.project}}-v${{ steps.project-meta.outputs.next-version }}
119+
token: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
120+
commit-message: |
121+
release: componentize-js v${{ steps.project-meta.outputs.next-version }}
122+
title: |
123+
release: componentize-js v${{ steps.project-meta.outputs.next-version }}
124+
labels: |
125+
release-pr
126+
assignees: >-
127+
vados-cosmonic,
128+
tschneidereit
129+
signoff: true
130+
body: |
131+
This is a release prep branch for `componentize-js` release `v${{ steps.project-meta.outputs.next-version }}`.
132+
133+
To ensure this release is ready to be merged:
134+
- [ ] Review updated CHANGELOG(s)
135+
136+
After this PR is merged tagging, artifact builds and releasing will run automatically.

.github/workflows/release.yml

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
name: release
2+
run-name: release
3+
4+
on:
5+
push:
6+
# NOTE: pushes from CI without a PAT will not trigger the tags below
7+
tags:
8+
- "[0-9]+.[0-9]+.[0-9]+*"
9+
- "[0-9]+.[0-9]+.[0-9]+-*"
10+
branches:
11+
- "prep-release-v[0-9]+.[0-9]+.[0-9]+*"
12+
- "prep-release-v[0-9]+.[0-9]+.[0-9]+-*"
13+
14+
workflow_dispatch:
15+
inputs:
16+
version:
17+
type: string
18+
required: true
19+
description: |
20+
Version tag to release (e.x. `0.1.0`, `0.2.0`)
21+
22+
permissions:
23+
contents: none
24+
25+
jobs:
26+
meta:
27+
runs-on: ubuntu-24.04
28+
outputs:
29+
version: ${{ steps.meta.outputs.version }}
30+
project-dir: ${{ steps.project-meta.outputs.project-dir }}
31+
artifacts-glob: ${{ steps.project-meta.outputs.artifacts-glob }}
32+
artifact-name: ${{ steps.project-meta.outputs.artifact-name }}
33+
next-release-tag: ${{ steps.project-meta.outputs.next-release-tag }}
34+
is-prerelease: ${{ steps.project-meta.outputs.is-prerelease }}
35+
prerelease-tag: ${{ steps.project-meta.outputs.prerelease-tag }}
36+
steps:
37+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
38+
39+
- uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2
40+
with:
41+
tool: cargo-get
42+
43+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
44+
with:
45+
node-version: ">=22"
46+
47+
- name: Cache npm install
48+
id: cache-node-modules
49+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
50+
with:
51+
key: node-modules-dev-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('packages/**/package.json') }}
52+
path: |
53+
node_modules
54+
- name: Install debug NPM packages
55+
run: |
56+
npm install -D
57+
58+
- name: Collect metadata
59+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
60+
id: meta
61+
with:
62+
script: |
63+
if (context.payload.inputs?.version) {
64+
core.setOutput('version', context.payload.inputs.version);
65+
return;
66+
}
67+
68+
if (context.ref.startsWith('refs/tags/')) {
69+
match = context.ref.replace('refs/tags/', '').match(/^([^\s]+)$/);
70+
} else if (context.ref.startsWith('refs/heads/')) {
71+
match = context.ref.replace('refs/heads/', '').match(/^prep-release-v([^\s]+)$/);
72+
} else {
73+
throw new Error(`Unexpected context ref [${context.ref}]`);
74+
}
75+
if (!match) { throw new Error(`Failed to parse tag/branch: [${context.ref}]`); }
76+
const [_, version] = match;
77+
core.setOutput('version', version);
78+
79+
- name: Gather project metadata
80+
id: project-meta
81+
env:
82+
NEXT_VERSION: ${{ steps.meta.outputs.version }}
83+
shell: bash
84+
run: |
85+
if [[ $NEXT_VERSION == v* ]]; then
86+
echo "::error::next version [$NEXT_VERSION] starts with 'v' -- enter only the semver version (ex. '0.1.0', not 'v0.1.0')";
87+
exit 1;
88+
fi
89+
90+
export PROJECT_DIR=$PWD;
91+
export CURRENT_VERSION=$(node -e "process.stdout.write(require(process.env.PROJECT_DIR + '/package.json').version)");
92+
export ARTIFACTS_GLOB="packages/jco/bytecodealliance-componentize-js-*.tgz";
93+
export ARTIFACT_NAME="bytecodealliance-componentize-js-$NEXT_VERSION.tgz";
94+
95+
echo -e "project-dir=$PROJECT_DIR" >> $GITHUB_OUTPUT;
96+
echo -e "artifacts-glob=$ARTIFACTS_GLOB" >> $GITHUB_OUTPUT;
97+
echo -e "artifact-name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT;
98+
echo -e "next-release-tag=${NEXT_VERSION}" >> $GITHUB_OUTPUT;
99+
100+
export IS_PRERELEASE=$(node scripts/semver-is-prerelease.mjs $NEXT_VERSION);
101+
echo -e "is-prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT;
102+
export PRERELEASE_TAG=$(node scripts/semver-get-prerelease.mjs $NEXT_VERSION);
103+
echo -e "prerelease-tag=$PRERELEASE_TAG" >> $GITHUB_OUTPUT;
104+
105+
pack-npm-release:
106+
if: ${{ needs.meta.outputs.is-js-project == 'true' }}
107+
runs-on: ubuntu-24.04
108+
needs:
109+
- meta
110+
permissions:
111+
id-token: write
112+
attestations: write
113+
steps:
114+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
115+
with:
116+
fetch-depth: 0
117+
118+
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
119+
with:
120+
shared-key: jco-${{ hashFiles('Cargo.lock') }}
121+
122+
# NOTE: we must use a node version new-enough to have --experimental-wasm-jspi
123+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
124+
with:
125+
node-version: ">=22"
126+
127+
- name: Cache npm install
128+
id: cache-node-modules
129+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
130+
with:
131+
key: node-modules-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('packages/**/package.json') }}
132+
path: |
133+
node_modules
134+
- name: Install NPM packages
135+
run: |
136+
npm install
137+
138+
- name: Create release package
139+
working-directory: ${{ needs.meta.outputs.project-dir }}
140+
run: |
141+
npm pack
142+
143+
- uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
144+
with:
145+
subject-path: ${{ needs.meta.outputs.artifacts-glob }}
146+
147+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
148+
with:
149+
if-no-files-found: error
150+
name: ${{ needs.meta.outputs.project }}
151+
path: |
152+
${{ needs.meta.outputs.artifacts-glob }}
153+
154+
test-npm-release:
155+
runs-on: ubuntu-24.04
156+
needs:
157+
- meta
158+
- pack-npm-release
159+
steps:
160+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
161+
162+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
163+
with:
164+
path: artifacts
165+
166+
- name: Test built componentize-js NPM package
167+
shell: bash
168+
run: |
169+
export PACKAGE_DIR=${{ github.workspace }}/artifacts/componentize-js/${{ needs.meta.outputs.artifact-name }}
170+
cp -r examples/hello-world/guest /tmp/test
171+
cd /tmp/test
172+
npm install --save $PACKAGE_DIR
173+
npm run all
174+
175+
npm-publish:
176+
runs-on: ubuntu-24.04
177+
needs:
178+
- meta
179+
- test-npm-release
180+
steps:
181+
# NOTE: we need to checkout to pull npmrc
182+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
183+
184+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
185+
with:
186+
path: artifacts
187+
188+
- name: Publish componentize-js to NPM
189+
env:
190+
NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
191+
shell: bash
192+
run: |
193+
export PACKAGE_DIR=${{ github.workspace }}/artifacts/componentize-js/${{ needs.meta.outputs.artifact-name }}
194+
195+
export OPT_DRY_RUN="--dry-run"
196+
if [ "tag" == "${{ github.ref_type }}" ]; then
197+
export OPT_DRY_RUN="";
198+
fi
199+
200+
export OPT_RELEASE_TAG=""
201+
if [ "true" == "${{ needs.meta.outputs.is-prerelease }}" ]; then
202+
export OPT_RELEASE_TAG="--tag ${{ needs.meta.outputs.prerelease-tag }}";
203+
fi
204+
205+
npm publish -w @bytecodealliance/componentize-js $OPT_DRY_RUN $OPT_RELEASE_TAG $PACKAGE_DIR
206+
207+
create-gh-release:
208+
runs-on: ubuntu-24.04
209+
if: always()
210+
needs:
211+
- meta
212+
- test-npm-release
213+
- npm-publish
214+
permissions:
215+
contents: write
216+
steps:
217+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
218+
with:
219+
fetch-depth: 0
220+
221+
- uses: taiki-e/install-action@2cab843126c0d8cf950bf55f4e9b8413f70f553f # v2.54.1
222+
with:
223+
fallback: none
224+
tool: git-cliff
225+
226+
# Re-generate the current changelog so we can use it in the GH release announcement
227+
#
228+
# NOTE: if this workflow is being run due to a tag push, that's an *already committed* release
229+
# tag and likely the one corresponding to this release, so we use the latest
230+
#
231+
- name: Re-generate current changelog
232+
working-directory: ${{ needs.meta.outputs.project-dir }}
233+
env:
234+
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
235+
GITHUB_REPO: ${{ github.repository }}
236+
run: |
237+
if [ "tag" == "${{ github.ref_type }}" ]; then
238+
git cliff --repository=${{ github.workspace }}/.git --latest > CHANGELOG.current;
239+
else
240+
git cliff \
241+
--repository=${{ github.workspace }}/.git \
242+
--unreleased \
243+
--tag=${{ needs.meta.outputs.next-release-tag }} \
244+
> CHANGELOG.current;
245+
fi
246+
247+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
248+
with:
249+
path: artifacts
250+
251+
- name: Create GH release
252+
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
253+
with:
254+
token: ${{ secrets.RELEASE_PAT || github.token }}
255+
prerelease: ${{ github.ref_type != 'tag' }}
256+
draft: ${{ github.ref_type != 'tag' }}
257+
tag_name: ${{ needs.meta.outputs.next-release-tag }}
258+
generate_release_notes: false
259+
body_path: ${{ needs.meta.outputs.project-dir }}/CHANGELOG.current
260+
files: |
261+
./artifacts/*/*

0 commit comments

Comments
 (0)