Skip to content

Commit c98b6cf

Browse files
feat(ci): add release automation (#226)
* 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> * fix(ops): do not filter non conventional commits Signed-off-by: Victor Adossi <vadossi@cosmonic.com> --------- Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
1 parent 777ac42 commit c98b6cf

File tree

11 files changed

+639
-7
lines changed

11 files changed

+639
-7
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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: git-cliff
36+
37+
- name: Cache npm install
38+
id: cache-node-modules
39+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
40+
with:
41+
key: node-modules-dev-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('packages/**/package.json') }}
42+
path: |
43+
node_modules
44+
45+
- name: Install debug NPM packages
46+
run: |
47+
npm install -D
48+
49+
- name: Gather project metadata
50+
id: project-meta
51+
env:
52+
NEXT_VERSION: ${{ inputs.version }}
53+
shell: bash
54+
run: |
55+
if [[ $NEXT_VERSION == v* ]]; then
56+
echo "::error::next version [$NEXT_VERSION] starts with 'v' -- enter only the semver version (ex. '0.1.0', not 'v0.1.0')";
57+
exit 1;
58+
fi
59+
60+
export PROJECT_DIR=$PWD;
61+
export CURRENT_VERSION=$(node -e "process.stdout.write(require(process.env.PROJECT_DIR + '/package.json').version)");
62+
63+
echo -e "project-dir=$PROJECT_DIR"
64+
echo -e "current-version=$CURRENT_VERSION"
65+
echo -e "next-version=$NEXT_VERSION"
66+
67+
echo -e "project-dir=$PROJECT_DIR" >> $GITHUB_OUTPUT
68+
echo -e "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
69+
echo -e "next-version=$NEXT_VERSION" >> $GITHUB_OUTPUT
70+
71+
- name: Ensure next version is after current
72+
run: |
73+
IS_AFTER=$(node scripts/semver-lt.mjs ${{ steps.project-meta.outputs.current-version }} ${{ steps.project-meta.outputs.next-version }});
74+
if [ "$IS_AFTER" == "false" ]; then \
75+
echo "::error::project [componentize-js] next version [${{ steps.project-meta.outputs.next-version }}] is not after current version [${{ steps.project-meta.outputs.current-version }}]";
76+
exit 1;
77+
fi
78+
79+
# Set project version
80+
- name: Set project version
81+
working-directory: ${{ steps.project-meta.outputs.project-dir }}
82+
shell: bash
83+
run: |
84+
npm pkg set version=${{ steps.project-meta.outputs.next-version }};
85+
sed -i \
86+
"s/version('${{ steps.project-meta.outputs.current-version }}')/version('${{ steps.project-meta.outputs.next-version }}')/" \
87+
src/cli.js;
88+
89+
# Generate changelog
90+
#
91+
# NOTE: we use the 'latest' tag here, because starting from an rc/alpha/beta release
92+
# the rc/alpha/beta release tags are ignored and the "latest" tag is the previous stable version
93+
- name: Generate changelog
94+
working-directory: ${{ steps.project-meta.outputs.project-dir }}
95+
env:
96+
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
97+
run: |
98+
git cliff \
99+
--repository=${{ github.workspace }}/.git \
100+
--config=./cliff.toml \
101+
--latest \
102+
--tag=${{ steps.project-meta.outputs.next-version }} \
103+
--prepend=CHANGELOG.md
104+
105+
# Create release PR
106+
- name: Create release prep PR
107+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
108+
with:
109+
branch: prep-release-${{ steps.project-meta.outputs.project}}-v${{ steps.project-meta.outputs.next-version }}
110+
token: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
111+
commit-message: |
112+
release: componentize-js v${{ steps.project-meta.outputs.next-version }}
113+
title: |
114+
release: componentize-js v${{ steps.project-meta.outputs.next-version }}
115+
labels: |
116+
release-pr
117+
assignees: >-
118+
vados-cosmonic,
119+
tschneidereit
120+
signoff: true
121+
body: |
122+
This is a release prep branch for `componentize-js` release `v${{ steps.project-meta.outputs.next-version }}`.
123+
124+
To ensure this release is ready to be merged:
125+
- [ ] Review updated CHANGELOG(s)
126+
127+
After this PR is merged tagging, artifact builds and releasing will run automatically.

.github/workflows/release.yml

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

0 commit comments

Comments
 (0)