mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-04 20:01:58 +00:00
chore(release): set up tag-driven HealthChat release pipeline
Convert build-healthchat workflow from branch-push to tag-push trigger (healthchat-v*), add version/workspace-ref/release-notes resolution, and create a GitHub Release on tag push. Pin healthchat-workspace to v0.5.0-rc.4 via .healthchat-workspace-version, seed the changelog with 1.0.0, and document the runbook in the releasing-healthchat skill.
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
name: releasing-healthchat
|
||||||
|
description: Cut a versioned HealthChat release of element-web by tagging release/healthchat. Use when the user asks to release, publish, tag, or cut a new HealthChat version, or asks about the release/deployment process for HealthChat element-web.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Releasing HealthChat (element-web)
|
||||||
|
|
||||||
|
Releases are driven by git tags of the form `healthchat-v<semver>` pushed to
|
||||||
|
`origin`. The tag triggers `.github/workflows/build-healthchat.yaml`, which
|
||||||
|
builds the HealthChat Docker image, publishes it to ghcr.io under
|
||||||
|
`healthchat-web:<version>`, `:stable`, and `:<sha>`, and creates a GitHub
|
||||||
|
Release whose body is the matching section of `HEALTHCHAT_CHANGES.md`.
|
||||||
|
|
||||||
|
Publish target: **ghcr.io Docker image + GitHub Release only.** No npm,
|
||||||
|
no tarball distribution, no desktop/mobile fan-out.
|
||||||
|
|
||||||
|
## Preflight checklist
|
||||||
|
|
||||||
|
Verify each item before tagging. If any fail, fix and commit first.
|
||||||
|
|
||||||
|
- [ ] On branch `release/healthchat` with a clean working tree (`git status`).
|
||||||
|
- [ ] `HEALTHCHAT_CHANGES.md` has a section `## Changes in <new-version> (<date>)`
|
||||||
|
at the top of the file. The pipeline's awk extractor matches this exact
|
||||||
|
heading format.
|
||||||
|
- [ ] `.healthchat-workspace-version` points at the healthchat-workspace tag
|
||||||
|
or ref that should ship with this release. Bump it in the same commit
|
||||||
|
as the changelog entry if needed.
|
||||||
|
- [ ] `package.json` version can optionally be bumped to match (cosmetic —
|
||||||
|
the git tag is the source of truth for the build).
|
||||||
|
- [ ] No uncommitted changes to `.github/workflows/build-healthchat.yaml`,
|
||||||
|
`Dockerfile`, or `docker/` that haven't been manually sanity-checked.
|
||||||
|
|
||||||
|
## Cutting the release (normal path)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from release/healthchat, fully committed:
|
||||||
|
git tag healthchat-v<MAJOR>.<MINOR>.<PATCH>
|
||||||
|
git push origin healthchat-v<MAJOR>.<MINOR>.<PATCH>
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `git tag healthchat-v1.0.0`
|
||||||
|
- `git tag healthchat-v1.4.0-rc1`
|
||||||
|
- `git tag healthchat-v1.4.0-hotfix1`
|
||||||
|
|
||||||
|
The workflow triggers automatically. Watch it via `gh run watch` or GitHub
|
||||||
|
Actions UI.
|
||||||
|
|
||||||
|
## Manual / retry path (workflow_dispatch)
|
||||||
|
|
||||||
|
Use when retrying a failed run, testing the pipeline, or rebuilding an image
|
||||||
|
without moving tags.
|
||||||
|
|
||||||
|
1. GitHub → Actions → "Release HealthChat" → **Run workflow**.
|
||||||
|
2. Fill in:
|
||||||
|
- **version** (required): e.g. `1.4.0`. Same regex validation as tag path.
|
||||||
|
- **workspace_ref** (optional): overrides `.healthchat-workspace-version`.
|
||||||
|
- **release_notes_override** (optional): replaces the `HEALTHCHAT_CHANGES.md`
|
||||||
|
lookup.
|
||||||
|
3. Run. Docker image is pushed. **No GitHub Release is created on the dispatch
|
||||||
|
path** — only the tag-push path creates Releases (keeps manual retries from
|
||||||
|
clobbering the release history).
|
||||||
|
|
||||||
|
## Tag format
|
||||||
|
|
||||||
|
```
|
||||||
|
healthchat-v<MAJOR>.<MINOR>.<PATCH>[-<suffix>]
|
||||||
|
```
|
||||||
|
|
||||||
|
Suffix conventions (informational; regex accepts any
|
||||||
|
`[0-9A-Za-z.-]+`):
|
||||||
|
|
||||||
|
- `-rc1`, `-rc2` — release candidates
|
||||||
|
- `-hotfix1` — post-release fixes on an existing minor
|
||||||
|
- `-dryrun1`, `-renew-test` — one-off pipeline tests
|
||||||
|
|
||||||
|
The `healthchat-` prefix keeps these tags distinct from upstream
|
||||||
|
element-hq `v<semver>` tags if they are ever fetched into the repo.
|
||||||
|
|
||||||
|
Regex used for validation (must match):
|
||||||
|
`^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$`
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
|
||||||
|
`HEALTHCHAT_CHANGES.md` is the single source. The workflow extracts the
|
||||||
|
section matching the version being released. Format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Changes in 1.4.0 (2026-04-21)
|
||||||
|
|
||||||
|
Short summary line (optional).
|
||||||
|
|
||||||
|
🙌 Improvements
|
||||||
|
|
||||||
|
- Bullet one.
|
||||||
|
- Bullet two.
|
||||||
|
```
|
||||||
|
|
||||||
|
If no matching section exists, the pipeline falls back to
|
||||||
|
`HealthChat <version> — automated build from <ref>.` and emits a workflow
|
||||||
|
warning. Prefer fixing the changelog over relying on the fallback.
|
||||||
|
|
||||||
|
## After the release
|
||||||
|
|
||||||
|
- [ ] Workflow run is green in GitHub Actions.
|
||||||
|
- [ ] `docker pull ghcr.io/<owner>/healthchat-web:<version>` succeeds.
|
||||||
|
- [ ] GitHub Release exists at
|
||||||
|
`https://github.com/<owner>/<repo>/releases/tag/healthchat-v<version>`
|
||||||
|
with the correct body.
|
||||||
|
- [ ] The `:stable` tag on ghcr.io now points at the new digest
|
||||||
|
(`docker buildx imagetools inspect ghcr.io/<owner>/healthchat-web:stable`).
|
||||||
|
|
||||||
|
## Required secrets
|
||||||
|
|
||||||
|
Configured in repo Settings → Secrets and variables → Actions:
|
||||||
|
|
||||||
|
| Secret | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `HEALTHCHAT_WORKSPACE_TOKEN` | PAT with read access to `Novaloop-AG/healthchat-workspace`. Needed by `actions/checkout` for the private plugin repo. |
|
||||||
|
| `GITHUB_TOKEN` | Auto-provided. Used for ghcr.io login and GitHub Release creation. No manual setup required. |
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
- **Bad image, notes fine** → fix the cause, commit, tag `healthchat-v<ver>-hotfix1`.
|
||||||
|
- **Bad release notes** → edit `HEALTHCHAT_CHANGES.md` and the GitHub Release
|
||||||
|
body manually (GitHub UI). The ghcr.io tag is immutable, don't try to move it.
|
||||||
|
- **Completely wrong tag** → delete the GitHub Release (UI). Do **not** delete
|
||||||
|
the ghcr.io image tag — treat it as burned. Re-tag with a new version.
|
||||||
|
|
||||||
|
## Known branch divergence on rebase
|
||||||
|
|
||||||
|
These files are HealthChat-only and will conflict cleanly when rebasing
|
||||||
|
`release/healthchat` onto upstream. Always keep the HealthChat side (`--ours`
|
||||||
|
during rebase):
|
||||||
|
|
||||||
|
- `.github/workflows/build-healthchat.yaml`
|
||||||
|
- `HEALTHCHAT_CHANGES.md`
|
||||||
|
- `.healthchat-workspace-version`
|
||||||
|
- `.claude/skills/releasing-healthchat/SKILL.md`
|
||||||
@@ -1,42 +1,133 @@
|
|||||||
name: build-healthchat-app
|
name: Release HealthChat
|
||||||
|
|
||||||
|
# Tag-driven release pipeline for HealthChat builds of element-web.
|
||||||
|
# Mirrors ../element-ios/.github/workflows/release-healthchat.yml.
|
||||||
|
# Runbook: .claude/skills/releasing-healthchat/SKILL.md
|
||||||
|
|
||||||
# run on branch release/healthchat
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- release/healthchat
|
- "healthchat-v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version (e.g. 1.4.0). Required when running manually."
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
workspace_ref:
|
||||||
|
description: "Override healthchat-workspace ref (defaults to .healthchat-workspace-version)."
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
release_notes_override:
|
||||||
|
description: "Optional release notes; falls back to HEALTHCHAT_CHANGES.md section for the given version."
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # create GitHub Release
|
||||||
|
packages: write # push Docker image
|
||||||
|
id-token: write # attestations
|
||||||
|
attestations: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-healthchat-${{ github.event.inputs.version || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-healthchat-app:
|
release:
|
||||||
|
name: Build & publish HealthChat image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read # This is required to read the repo content
|
|
||||||
packages: write # This is required to push the Docker image
|
|
||||||
id-token: write # Required to get the certificate for the attestation
|
|
||||||
attestations: write # Required to push the attestation
|
|
||||||
env:
|
env:
|
||||||
DOCKER_REGISTRY: "ghcr.io"
|
DOCKER_REGISTRY: "ghcr.io"
|
||||||
DOCKER_IMAGE: ${{ format('{0}/healthchat-web', github.repository) }}
|
DOCKER_IMAGE: ${{ format('{0}/healthchat-web', github.repository) }}
|
||||||
DOCKER_IMAGE_TAG: "latest"
|
|
||||||
DOCKER_PUSH: true
|
DOCKER_PUSH: true
|
||||||
DOCKER_SIGNING: false
|
DOCKER_SIGNING: false
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
HEALTHCHAT_WORKSPACE_REF: v0.5.0-rc.4
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Resolve version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
|
else
|
||||||
|
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
||||||
|
fi
|
||||||
|
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
|
||||||
|
echo "::error::Invalid version '$VERSION' — expected e.g. 1.4.0 or 1.4.0-rc1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved version: $VERSION"
|
||||||
|
|
||||||
|
- name: Resolve workspace ref
|
||||||
|
id: workspace
|
||||||
|
env:
|
||||||
|
OVERRIDE: ${{ github.event.inputs.workspace_ref }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ -n "$OVERRIDE" ]]; then
|
||||||
|
REF="$OVERRIDE"
|
||||||
|
echo "Using workspace ref from workflow input: $REF"
|
||||||
|
elif [[ -f .healthchat-workspace-version ]]; then
|
||||||
|
REF=$(head -n1 .healthchat-workspace-version | tr -d '[:space:]')
|
||||||
|
if [[ -z "$REF" ]]; then
|
||||||
|
echo "::error::.healthchat-workspace-version is empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Using workspace ref from .healthchat-workspace-version: $REF"
|
||||||
|
else
|
||||||
|
echo "::error::.healthchat-workspace-version missing and no workspace_ref override provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Extract release notes
|
||||||
|
id: notes
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
OVERRIDE: ${{ github.event.inputs.release_notes_override }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ -n "$OVERRIDE" ]]; then
|
||||||
|
NOTES="$OVERRIDE"
|
||||||
|
echo "Using release notes from workflow input"
|
||||||
|
else
|
||||||
|
NOTES=$(awk -v ver="$VERSION" '
|
||||||
|
/^## Changes in / {
|
||||||
|
if (found) { exit }
|
||||||
|
if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next }
|
||||||
|
}
|
||||||
|
found { print }
|
||||||
|
' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}')
|
||||||
|
if [[ -z "$NOTES" ]]; then
|
||||||
|
NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}."
|
||||||
|
echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo 'notes<<NOTES_EOF'
|
||||||
|
echo "$NOTES"
|
||||||
|
echo 'NOTES_EOF'
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Checkout healthchat-workspace
|
- name: Checkout healthchat-workspace
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: Novaloop-AG/healthchat-workspace
|
repository: Novaloop-AG/healthchat-workspace
|
||||||
path: healthchat-workspace
|
path: healthchat-workspace
|
||||||
token: ${{ secrets.HEALTHCHAT_WORKSPACE_TOKEN }}
|
token: ${{ secrets.HEALTHCHAT_WORKSPACE_TOKEN }}
|
||||||
ref: ${{ env.HEALTHCHAT_WORKSPACE_REF }}
|
ref: ${{ steps.workspace.outputs.ref }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
- name: Build element-plugin
|
- name: Build element-plugin
|
||||||
run: |
|
run: |
|
||||||
cd healthchat-workspace/element-plugin
|
cd healthchat-workspace/element-plugin
|
||||||
@@ -45,27 +136,33 @@ jobs:
|
|||||||
cd module
|
cd module
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Stage plugin artifacts for Docker
|
- name: Stage plugin artifacts for Docker
|
||||||
run: |
|
run: |
|
||||||
mkdir -p plugin-dist/elements
|
mkdir -p plugin-dist/elements
|
||||||
cp healthchat-workspace/element-plugin/module/dist/healthchat-module.js plugin-dist/healthchat-module.js
|
cp healthchat-workspace/element-plugin/module/dist/healthchat-module.js plugin-dist/healthchat-module.js
|
||||||
cp healthchat-workspace/element-plugin/dist/elements/browser/main.js plugin-dist/elements/healthchat-elements.js
|
cp healthchat-workspace/element-plugin/dist/elements/browser/main.js plugin-dist/elements/healthchat-elements.js
|
||||||
|
|
||||||
- name: Log in to the Github Container registry
|
- name: Log in to the Github Container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.DOCKER_REGISTRY }}
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
|
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ env.DOCKER_IMAGE_TAG }}
|
type=raw,value=${{ steps.version.outputs.version }}
|
||||||
|
type=raw,value=stable
|
||||||
type=raw,value=${{ github.sha }}
|
type=raw,value=${{ github.sha }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build and push Docker images
|
- name: Build and push Docker images
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -75,8 +172,6 @@ jobs:
|
|||||||
push: ${{ env.DOCKER_PUSH }}
|
push: ${{ env.DOCKER_PUSH }}
|
||||||
tags: ${{ steps.metadata.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
labels: ${{ steps.metadata.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
#cache-from: type=gha
|
|
||||||
#cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Generate artifact attestation
|
- name: Generate artifact attestation
|
||||||
if: ${{ env.DOCKER_SIGNING == 'true' }}
|
if: ${{ env.DOCKER_SIGNING == 'true' }}
|
||||||
@@ -85,3 +180,11 @@ jobs:
|
|||||||
subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
|
subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
|
||||||
subject-digest: ${{ steps.push.outputs.digest }}
|
subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
push-to-registry: ${{ env.DOCKER_PUSH }}
|
push-to-registry: ${{ env.DOCKER_PUSH }}
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
name: HealthChat ${{ steps.version.outputs.version }}
|
||||||
|
body: ${{ steps.notes.outputs.notes }}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
v0.5.0-rc.4
|
||||||
@@ -111,4 +111,7 @@ yarn link && yarn install
|
|||||||
|
|
||||||
# In element-web directory
|
# In element-web directory
|
||||||
yarn link matrix-js-sdk && yarn install && yarn start
|
yarn link matrix-js-sdk && yarn install && yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Releasing HealthChat
|
||||||
|
Releases are cut by pushing a `healthchat-v<semver>` tag against `release/healthchat`, which triggers `.github/workflows/build-healthchat.yaml`. Release notes come from `HEALTHCHAT_CHANGES.md`; the plugin ref is pinned in `.healthchat-workspace-version`. Full runbook: `releasing-healthchat` skill.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# HealthChat Changelog
|
||||||
|
|
||||||
|
Release notes for HealthChat builds of element-web. The release pipeline
|
||||||
|
(`.github/workflows/build-healthchat.yaml`) extracts the matching `## Changes
|
||||||
|
in <version>` section and uses it as the body of the GitHub Release.
|
||||||
|
|
||||||
|
One section per released version, newest on top. Upstream element-hq
|
||||||
|
`CHANGELOG.md` is kept separate and untouched.
|
||||||
|
|
||||||
|
## Changes in 1.0.0 (2026-04-21)
|
||||||
|
|
||||||
|
First tagged HealthChat release of element-web.
|
||||||
|
|
||||||
|
🙌 Improvements
|
||||||
|
|
||||||
|
- Initial HealthChat branding, theming, and healthchat-workspace module integration.
|
||||||
Reference in New Issue
Block a user