Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: PR Title

# Releases are driven by semantic-release from the squash-merge commit, which is
# the PR title. Validate it as a Conventional Commit so a bad title can't produce
# a wrong (or missing) release.
# Releases are driven from the squash-merge commit, which is the PR title.
# Validate it as a Conventional Commit so a bad title can't produce a wrong (or
# missing) release. `release` is allowed so the shared release pipeline's standing
# PR (`release(version): Release <version>`) passes this check.
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
Expand Down Expand Up @@ -40,3 +41,4 @@ jobs:
ci
chore
revert
release
55 changes: 55 additions & 0 deletions .github/workflows/release-prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Caller stub: release preparation (stage 1).
#
# The workflow_run trigger cannot live in the reusable workflow, so it lives
# here. The single job forwards the tested commit (or a manually dispatched SHA)
# to the reusable prepare workflow, which re-confirms the SHA is still the
# default-branch head and opens/refreshes the standing release PR.
#
# Escape hatch: workflow_dispatch lets you plan manually (e.g. a dry-run smoke
# test before your first release) without waiting for a CI completion.

name: Prepare release

on:
workflow_run:
# Must match the name: of this repo's CI workflow (ci.yml is "CI").
workflows: ["CI"]
types: [completed]
workflow_dispatch:
inputs:
targetSha:
description: "Commit SHA to plan from (defaults to the default-branch head)"
required: false
type: string

permissions: {}

concurrency:
group: release-prepare
cancel-in-progress: false

jobs:
prepare:
# Manual dispatch always runs; the automatic path only runs for a green
# CI run from a push to the default branch.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'master')
permissions:
contents: write
pull-requests: write
uses: chhoumann/obsidian-plugin-workflows/.github/workflows/release-prepare.yml@v2
Comment thread
chhoumann marked this conversation as resolved.
with:
plugin-name: metaedit
package-manager: pnpm
default-branch: master
node-version: "24"
target-sha: ${{ github.event.workflow_run.head_sha || inputs.targetSha }}
release-bot-app-slug: metaedit-release-bot
app-id: ${{ vars.RELEASE_APP_ID }}
workflows-ref: v2
# dry-run: true # plan only; skip opening the PR (smoke test)
secrets:
release-app-private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
38 changes: 38 additions & 0 deletions .github/workflows/release-trigger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Caller stub: release trigger (stage 2).
#
# pull_request_target cannot live in the reusable workflow, so it lives here.
# The instant a release PR merges, this forwards the PR number to the reusable
# validate workflow, which performs the full merged-PR forensics and dispatches
# the release. The workflow never checks out PR head code, so pull_request_target
# is safe here.

name: Trigger release

on:
pull_request_target:
types: [closed]

permissions: {}

concurrency:
group: release-trigger
cancel-in-progress: false

jobs:
validate:
if: >-
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'master' &&
startsWith(github.event.pull_request.head.ref, 'release/')
permissions:
actions: write
contents: write
pull-requests: read
uses: chhoumann/obsidian-plugin-workflows/.github/workflows/release-validate.yml@v2
with:
pr-number: ${{ github.event.pull_request.number }}
plugin-name: metaedit
package-manager: pnpm
default-branch: master
release-bot-app-slug: metaedit-release-bot
release-workflow: release.yml
77 changes: 38 additions & 39 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,51 +1,50 @@
# Caller stub: release (stage 3).
#
# workflow_dispatch is the only trigger: the reusable validate workflow (stage 2)
# dispatches this on the durable release-run/<version> recovery branch. Keeping a
# plain workflow_dispatch here also makes the release itself replayable for
# recovery without re-running the whole pipeline.

name: Release

on:
workflow_dispatch:
inputs:
releasePr:
description: "Merged machine-generated release PR number to recover"
required: true
type: string

permissions: {}

concurrency:
group: release-${{ github.ref }}
group: release
cancel-in-progress: false

permissions:
contents: read

jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
actions: read
attestations: write
contents: write
issues: write # semantic-release success comments
pull-requests: write # semantic-release success comments
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Full history + tags so semantic-release can compute the next version.
fetch-depth: 0
# semantic-release manages its own push auth via GITHUB_TOKEN; keep
# the checkout token out of the git config.
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Use Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
check-latest: true
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Run tests
run: pnpm run test
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm semantic-release
id-token: write
pull-requests: read
uses: chhoumann/obsidian-plugin-workflows/.github/workflows/release.yml@v2
with:
release-pr: ${{ inputs.releasePr }}
plugin-name: metaedit
package-manager: pnpm
default-branch: master
node-version: "24"
release-bot-app-slug: metaedit-release-bot
release-assets: '["main.js","manifest.json","styles.css"]'
build-command: pnpm run build
verify-commands: |
pnpm run lint
pnpm run typecheck
pnpm run test
workflows-ref: v2
secrets:
# Optional: unset secrets resolve to '' and the notify job skips them.
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
discord-webhook: ${{ secrets.DISCORD_WEBHOOK }}
26 changes: 16 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ hand-written, committed release asset.
- Use the GitHub CLI (`gh`) for issues, PRs, and releases.
- When resolving a GitHub issue, use `gh issue develop <issue-number>` to
create/link the working branch before implementation.
- Follow Conventional Commits (`feat:`, `fix:`, `test:`, `docs:`, `chore:`,
`release(version): ...`) so semantic-release can determine versions. The PR
title becomes the squash-merge commit and drives the released version.
- Follow Conventional Commits (`feat:`, `fix:`, `test:`, `docs:`, `chore:`) so
the shared release pipeline can determine versions. The PR title becomes the
squash-merge commit and drives the released version.
- GitHub does not allow approving your own PR from the same account; do not block
merge waiting for self-approval.

Expand Down Expand Up @@ -127,13 +127,19 @@ The runner emits canonical `OBSIDIAN_E2E_*` env names, plus legacy
artifacts.

## Release & PR Expectations
Releases are semantic-release based and cut manually via the Release workflow
(Actions tab or `gh workflow run release.yml`); pushes to `master` do not
auto-release. `version-bump.mjs` keeps `manifest.json` and `versions.json` in sync with the
package version and Obsidian `minAppVersion`. Release assets are `main.js`,
`manifest.json`, and `styles.css`. Treat unexpected diffs in `package.json`,
`pnpm-lock.yaml`, `manifest.json`, or `versions.json` as blockers until
understood.
Releases run on the shared forensic PR-to-release pipeline in
[`chhoumann/obsidian-plugin-workflows`](https://github.com/chhoumann/obsidian-plugin-workflows),
consumed via the three caller stubs in `.github/workflows/`
(`release-prepare.yml`, `release-trigger.yml`, `release.yml`). After every green
push to `master`, the `metaedit-release-bot` App opens or refreshes one standing
release PR containing only the synchronized version files (`package.json`,
`manifest.json`, `versions.json`) and generated notes; **merging that PR is the
sole release act.** There is no auto-release and no manual dispatch on the happy
path. The shared pipeline materializes the version files and keeps
`manifest.json` / `versions.json` in sync with the package version and Obsidian
`minAppVersion`. Release assets are `main.js`, `manifest.json`, and `styles.css`.
Treat unexpected diffs in `package.json`, `manifest.json`, or `versions.json` as
blockers until understood.

Pull requests should include: a concise summary of the user-facing change;
linked issues when relevant; screenshots or recordings for visible UI changes;
Expand Down
64 changes: 5 additions & 59 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"version": "1.10.1",
"description": "MetaEdit helps you manage your metadata.",
"main": "main.js",
"repository": {
"type": "git",
"url": "https://github.com/chhoumann/MetaEdit.git"
},
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "pnpm run typecheck && node esbuild.config.mjs production",
Expand All @@ -15,9 +19,7 @@
"obsidian:e2e": "obsidian-e2e run",
"provision:e2e-vault": "obsidian-e2e provision",
"start:e2e-obsidian": "obsidian-e2e start",
"stop:e2e-obsidian": "obsidian-e2e stop",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"semantic-release": "semantic-release"
"stop:e2e-obsidian": "obsidian-e2e stop"
},
"keywords": [],
"author": "Christian B. B. Houmann",
Expand All @@ -27,7 +29,6 @@
"node": ">=20.18.1"
},
"devDependencies": {
"@semantic-release/git": "^10.0.1",
"@types/node": "26.1.0",
"@vitest/coverage-v8": "4.1.9",
"cz-conventional-changelog": "^3.3.0",
Expand All @@ -39,7 +40,6 @@
"globals": "^17.7.0",
"obsidian": "1.13.1",
"obsidian-e2e": "^0.8.1",
"semantic-release": "^25.0.5",
"svelte": "^5.56.4",
"svelte-check": "^4.7.1",
"svelte-eslint-parser": "^1.8.0",
Expand All @@ -63,59 +63,5 @@
"overrides": {
"handlebars": "^4.7.9"
}
},
"release": {
"tagFormat": "${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"releaseRules": [
{
"type": "chore",
"release": "patch"
}
]
}
],
"@semantic-release/release-notes-generator",
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": [
"package.json",
"pnpm-lock.yaml",
"manifest.json",
"versions.json"
],
"message": "release(version): Release ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "main.js",
"label": "main.js"
},
{
"path": "manifest.json",
"label": "manifest.json"
},
{
"path": "styles.css",
"label": "styles.css"
}
]
}
]
]
}
}
Loading