mirror of
https://github.com/banisterious/obsidian-custom-selected-word-count.git
synced 2026-07-22 05:49:07 +00:00
feat(ci): release workflow with build-provenance attestations
Adopt the same release pipeline that landed in Draft Bench, adapted to this plugin's existing scripts and assets. What lands together: - `.github/workflows/release.yml`. Tag-push trigger matching plain SemVer (`1.6.6`) and pre-release SemVer (`1.6.6-rc1`). Runs `npm ci`, `npm run lint` (the obsidianmd ruleset), and `npm run build`. Calls `actions/attest-build-provenance@v2` on `main.js`, `manifest.json`, and `styles.css` so each release asset carries a verifiable GitHub artifact attestation. Creates a draft release with the three assets attached; pre-release tags get `--prerelease`. The draft step preserves the editorial gate - release-description markdown is pasted into the draft by hand before publishing, keeping writing-style review on every release. - `.nvmrc` pinning Node 22.20.0 (matches local dev). Both CI and local environments resolve the same Node via `actions/setup-node@v4`'s `node-version-file: '.nvmrc'`. - `docs/developer/release.md` rewritten to document the CI flow: required artifacts, the trial-run procedure with `-rc1` tags, the attestation-verification command, and the recovery path for a failed CI run. Customizations from the reference workflow: - Dropped the `lint:css` step. The plugin has CSS sources but no stylelint config; adding one is real follow-up work and the community-site rescan covers CSS lint server-side until that lands. - Dropped the `test` step. No test infrastructure yet; that's Phase 4 of the audit plan. The step is straightforward to add later. Release assets are the standard Obsidian three: `main.js` (built fresh by CI), `manifest.json` (committed), `styles.css` (committed).
This commit is contained in:
parent
f65aac45a3
commit
8dff24e71e
3 changed files with 179 additions and 12 deletions
73
.github/workflows/release.yml
vendored
Normal file
73
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# Plain SemVer tags (release): 1.6.6, 2.0.0
|
||||
- '*.*.*'
|
||||
# Pre-release tags (rc / beta / alpha): 1.6.6-rc1, 2.0.0-beta.2
|
||||
- '*.*.*-*'
|
||||
|
||||
# `gh release create` needs write access; the attestation action
|
||||
# needs an OIDC token and attestation upload permission.
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
# Prevent a second run for the same tag from racing the first.
|
||||
# Cancelling in-progress means a later push wins.
|
||||
concurrency:
|
||||
group: release-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint (eslint)
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Attest provenance
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
main.js
|
||||
manifest.json
|
||||
styles.css
|
||||
|
||||
- name: Create draft release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# SemVer pre-release tags carry a hyphen after the version
|
||||
# core (e.g., 1.6.6-rc1). Plain release tags do not.
|
||||
PRERELEASE_FLAG=""
|
||||
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
|
||||
PRERELEASE_FLAG="--prerelease"
|
||||
fi
|
||||
# Draft + empty notes: the author pastes the audited
|
||||
# release-description markdown into the draft on the web
|
||||
# UI before publishing. CI's job ends with assets uploaded
|
||||
# and the release in draft state.
|
||||
gh release create "$GITHUB_REF_NAME" \
|
||||
--title "$GITHUB_REF_NAME" \
|
||||
--draft \
|
||||
--notes "" \
|
||||
$PRERELEASE_FLAG \
|
||||
main.js manifest.json styles.css
|
||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
22.20.0
|
||||
|
|
@ -3,7 +3,10 @@
|
|||
## Table of contents
|
||||
|
||||
- [Required release artifacts](#required-release-artifacts)
|
||||
- [Release flow (CI-driven)](#release-flow-ci-driven)
|
||||
- [Trial-run procedure](#trial-run-procedure)
|
||||
- [Verifying a release locally](#verifying-a-release-locally)
|
||||
- [Recovering from a failed run](#recovering-from-a-failed-run)
|
||||
|
||||
## Required release artifacts
|
||||
|
||||
|
|
@ -18,10 +21,85 @@ Every GitHub release for this plugin attaches exactly three files:
|
|||
The Obsidian in-app community catalog fetches these three URLs from a
|
||||
release's assets to install or update the plugin. No additional files
|
||||
should be attached: no source zip, no debug bundle, no archive of the
|
||||
entire repository. The audit plan's Phase 0 was triggered in part by
|
||||
finding a `custom-selected-word-count-v1.6.2.zip` source archive in
|
||||
the repo root, which is not part of how Obsidian installs plugins and
|
||||
should not appear in releases.
|
||||
entire repository.
|
||||
|
||||
`main.js` and `main.js.map` are gitignored. Each release is built
|
||||
fresh by CI from the tagged source. `styles.css` and `manifest.json`
|
||||
are committed source files.
|
||||
|
||||
## Release flow (CI-driven)
|
||||
|
||||
The release pipeline lives at `.github/workflows/release.yml`. The
|
||||
trigger is a tag push matching plain SemVer (`1.6.6`, `2.0.0`) or a
|
||||
pre-release SemVer (`1.6.6-rc1`, `2.0.0-beta.2`). On any matching
|
||||
tag the workflow:
|
||||
|
||||
1. Checks out the tagged commit and installs dependencies with
|
||||
`npm ci`.
|
||||
2. Runs `npm run lint` (ESLint, including the
|
||||
`eslint-plugin-obsidianmd` ruleset).
|
||||
3. Runs `npm run build` (typecheck plus esbuild production bundle).
|
||||
4. Calls `actions/attest-build-provenance@v2` against `main.js`,
|
||||
`manifest.json`, and `styles.css` so each release asset carries a
|
||||
verifiable GitHub artifact attestation.
|
||||
5. Creates a **draft** GitHub release with those three files
|
||||
attached. Pre-release tags get `--prerelease`; plain SemVer tags
|
||||
do not.
|
||||
|
||||
The author then opens the draft release on the GitHub web UI, pastes
|
||||
the audited release-description markdown, and clicks Publish. The
|
||||
draft step is intentional: it preserves the editorial gate so the
|
||||
release description follows the project's writing-style conventions
|
||||
(minimal em-dashes, ASCII arrows, no AI attribution).
|
||||
|
||||
### Cutting a release
|
||||
|
||||
```sh
|
||||
# On main, with a clean working tree:
|
||||
npm --tag-version-prefix= version patch # or minor / major
|
||||
git push origin main
|
||||
git push origin <new-tag> # e.g. 1.6.6
|
||||
```
|
||||
|
||||
The `npm --tag-version-prefix=` flag matches the repo's plain
|
||||
SemVer tag convention. The project's `version` lifecycle script
|
||||
synchronizes `manifest.json` and `versions.json` with the bumped
|
||||
`package.json` version.
|
||||
|
||||
Once the tag is pushed, the workflow runs (~3-5 minutes) and the
|
||||
draft release appears on the repo's Releases page.
|
||||
|
||||
## Trial-run procedure
|
||||
|
||||
Before tagging the actual next release, push a release-candidate tag
|
||||
first to validate the pipeline end-to-end:
|
||||
|
||||
```sh
|
||||
git push # any pending commits
|
||||
git tag <version>-rc1 # e.g. 1.6.6-rc1
|
||||
git push origin <version>-rc1
|
||||
```
|
||||
|
||||
Verify the run in the Actions tab. The workflow should:
|
||||
|
||||
1. Pass all steps with green checkmarks.
|
||||
2. Produce a draft release flagged "Pre-release" with the three
|
||||
assets attached.
|
||||
3. Carry a valid attestation. Download and verify with:
|
||||
```sh
|
||||
gh release download <version>-rc1 --pattern main.js
|
||||
gh attestation verify main.js --repo banisterious/obsidian-custom-selected-word-count
|
||||
```
|
||||
Expected output: `Verification succeeded!`
|
||||
|
||||
Cleanup the rc once verified:
|
||||
|
||||
```sh
|
||||
gh release delete <version>-rc1 --yes --cleanup-tag
|
||||
```
|
||||
|
||||
`--cleanup-tag` removes both the local and remote tags in a single
|
||||
command.
|
||||
|
||||
## Verifying a release locally
|
||||
|
||||
|
|
@ -29,13 +107,28 @@ Before tagging:
|
|||
|
||||
- `npm run build` produces a clean `main.js` (typecheck plus esbuild
|
||||
bundle).
|
||||
- `npm run lint` runs to completion. The audit plan's Phase 1 through
|
||||
Phase 3 clear the current outstanding findings progressively; new
|
||||
errors should be addressed or explicitly scoped before release.
|
||||
- `npm run lint` runs to completion. The catalog-acceptance ruleset
|
||||
is `eslint-plugin-obsidianmd`; new errors flagged by that ruleset
|
||||
should be addressed before tagging.
|
||||
- `CHANGELOG.md`'s `[Unreleased]` section reflects everything that
|
||||
will ship with the tag, then is moved under a dated version heading
|
||||
at release time.
|
||||
will ship with the tag, then is moved under a dated version
|
||||
heading at release time.
|
||||
|
||||
A future Phase 6 task adds a GitHub release workflow with build-
|
||||
provenance attestations. Until then, releases are cut manually from
|
||||
the maintainer's workstation.
|
||||
## Recovering from a failed run
|
||||
|
||||
If CI fails after a tag is pushed (lint regression, build break,
|
||||
attestation step error), the published tag is wrong and the draft
|
||||
release should be discarded:
|
||||
|
||||
```sh
|
||||
# Delete the bad tag locally and on the remote:
|
||||
git tag -d <tag>
|
||||
git push --delete origin <tag>
|
||||
|
||||
# If a draft release was created, delete it (the assets it carries
|
||||
# are stale):
|
||||
gh release delete <tag> --yes
|
||||
```
|
||||
|
||||
Fix the underlying issue, commit, push, then re-tag and push the
|
||||
tag. The workflow re-runs from scratch.
|
||||
|
|
|
|||
Loading…
Reference in a new issue