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
15 changes: 15 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changesets

Every normal pull request needs one `.changeset/*.md` fragment. Add a release-note changeset for application or operator-workflow changes:

```bash
npm run changeset
```

For docs-only, internal, or other changes that should merge without changing the application version, create an empty changeset:

```bash
npm run changeset:empty
```

The release workflow turns pending changesets into a `Release` pull request. Merging that pull request updates the application version and changelog; a release workflow run for that merged pull request then creates a Git tag and GitHub Release. The private application package is never published to npm.
20 changes: 20 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://unpkg.com/@changesets/config@4.0.0/schema.json",
"changelog": [
"@changesets/changelog-github",
{
"repo": "hypercerts-org/orglabeler"
}
],
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": {
"version": true,
"tag": true
}
}
5 changes: 5 additions & 0 deletions .changeset/initial-release-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"certified-organization-labeler": patch
---

Add automated release notes and GitHub Releases, with hardened workflow dependency execution, for maintainers and operators.
29 changes: 26 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
ci:
Expand All @@ -13,19 +14,41 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Require a changeset
if: >-
github.event_name == 'pull_request' &&
(
github.event.pull_request.head.ref != 'changeset-release/main' ||
github.event.pull_request.head.repo.full_name != github.repository
)
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
changeset_files="$(git diff --name-only --diff-filter=AM "$BASE_SHA...$HEAD_SHA" -- '.changeset/*.md' ':!.changeset/README.md')"
if [ -z "$changeset_files" ]; then
echo "::error::This pull request needs a changeset. Run 'npm run changeset', or use 'npm run changeset:empty' when no version bump is needed."
exit 1
fi
npm run changeset:status -- --since "$BASE_SHA"

- name: Type-check
run: npx tsc --noEmit
run: npm run type-check

- name: Build
run: npm run build
Expand Down
201 changes: 201 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
name: Release

on:
push:
branches: [main]
pull_request:
branches: [main]
types: [closed]

permissions: {}

concurrency:
# Keep version-PR updates serialized without letting rapid main pushes
# replace a pending release-publication event.
group: release-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false

jobs:
select-mode:
name: Select Changesets mode
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
mode: ${{ steps.select-mode.outputs.mode }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts

- name: Select Changesets mode
id: select-mode
uses: changesets/action/select-mode@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1

version:
name: Create or update Release PR
if: github.event_name == 'push' && needs.select-mode.outputs.mode == 'version'
needs: select-mode
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts

- name: Version package, changelog, and lockfile
uses: changesets/action/version@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: npm run version:packages
commit-message: 'release: version packages'
pr-title: Release
pr-base-branch: main

publish:
name: Validate and publish GitHub Release
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.head.ref == 'changeset-release/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out exact pushed commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
persist-credentials: false

- name: Verify release commit
env:
EXPECTED_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
set -euo pipefail
if [ -z "$EXPECTED_SHA" ]; then
echo "::error::The merged Release pull request did not provide a merge commit SHA"
exit 1
fi
actual_sha="$(git rev-parse HEAD)"
if [ "$actual_sha" != "$EXPECTED_SHA" ]; then
echo "::error::Expected commit $EXPECTED_SHA but checked out $actual_sha"
exit 1
fi

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts

- name: Build required native dependencies
run: npm rebuild better-sqlite3 esbuild sharp

- name: Run tests
run: npm test

- name: Type-check
run: npm run type-check

- name: Build
run: npm run build
env:
NEXT_PUBLIC_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}

- name: Check existing tag and GitHub Release
id: preflight
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
version="$(node --print "require('./package.json').version")"
if [ -z "$version" ]; then
echo "::error::Could not derive a package version from package.json"
exit 1
fi
tag="v${version}"
api_url="${GITHUB_API_URL%/}"

get_status() {
local endpoint="$1"
local status
if ! status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
--header 'Accept: application/vnd.github+json' \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header 'X-GitHub-Api-Version: 2022-11-28' \
"${api_url}/${endpoint}")"; then
echo "::error::Unable to query GitHub while checking ${tag}" >&2
exit 1
fi
printf '%s' "$status"
}

tag_status="$(get_status "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}")"
release_status="$(get_status "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}")"
case "${tag_status}:${release_status}" in
200:200)
echo "::notice::${tag} already has both a remote tag and GitHub Release; nothing to publish."
echo 'publish=false' >> "$GITHUB_OUTPUT"
;;
404:404)
echo "No existing tag or GitHub Release found for ${tag}; publishing is required."
echo 'publish=true' >> "$GITHUB_OUTPUT"
;;
200:404)
echo "::error::Release state is inconsistent for ${tag}: the remote tag exists but the GitHub Release is missing. Reconcile the tag and Release manually, then rerun this workflow."
exit 1
;;
404:200)
echo "::error::Release state is inconsistent for ${tag}: the GitHub Release exists but the remote tag is missing. Reconcile the tag and Release manually, then rerun this workflow."
exit 1
;;
*)
echo "::error::Unexpected GitHub API response while checking ${tag}: tag=${tag_status}, release=${release_status}"
exit 1
;;
esac

# The package is private, so Changesets emits a tag-only release and does
# not run npm publish. This action creates the tag and GitHub Release.
- name: Create tag and GitHub Release
if: steps.preflight.outputs.publish == 'true'
uses: changesets/action/publish@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
create-github-releases: true
push-git-tags: true
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ npm run reset # Clear local databases for fresh start

## Documentation Maintenance

When behavior, configuration, deployment, scripts, or operator workflow changes, check whether the relevant docs need updates in the same change.
When behavior, configuration, deployment, scripts, or operator workflow changes, check whether the relevant docs need updates in the same change. Every normal pull request needs a Changeset fragment under `.changeset/`; use a release fragment for user- or operator-visible changes and an empty fragment when the application version should not change. The generated `Release` pull request and GitHub Release workflow are maintained separately from Railway deployment operations.

Current docs to consider:

- `README.md` — project overview, setup, scripts, env vars, production shape
- `docs/RELEASING.md` — contributor and maintainer Changesets/GitHub Release workflow
- `.changeset/README.md` — Changesets authoring and empty-fragment guidance
- `DEPLOYMENT.md` — generic hosted deployment guidance
- `RAILWAY.md` — Railway-specific deployment guidance
- `AGENTS.md` — agent-facing architecture, workflow, and maintenance instructions
- `src/app/docs/page.tsx` — dashboard documentation shown in the app
- `.agents/skills/orglabeler/SKILL.md` — downstream consumer integration guidance
- `.env.example` — documented environment variable defaults/examples
- `.github/workflows/release.yml` — automated versioning, tag, and GitHub Release workflow; keep its Changesets action inputs aligned with the current v2 model

If a new documentation file is introduced, add it to this list and include when it should be checked.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This fork monitors both `app.certified.actor.profile` and `app.certified.actor.o
## Quick Start

### Prerequisites
- Node.js 22+
- Node.js 22.13+ (within Node 22)
- An AT Protocol account dedicated to the labeler

### Setup
Expand Down Expand Up @@ -53,6 +53,10 @@ npm run start:service # Caddy on $PORT, Next on NEXT_PORT, labeler on LABELER_

Tap runs as a separate service. Point `TAP_URL` at that service's URL; there is no localhost fallback and the app will not start without it.

## Releases

Every normal pull request needs either a release changeset from `npm run changeset` or an empty changeset from `npm run changeset:empty` when no version bump is needed. Maintainers merge the generated **Release** pull request; the workflow then creates a Git tag and GitHub Release without publishing the private package to npm. See [docs/RELEASING.md](docs/RELEASING.md) for the contributor and maintainer flow.

## Architecture

The runtime is split into three pieces:
Expand Down Expand Up @@ -177,7 +181,7 @@ This is important because `subscribeLabels` uses WebSockets, which need a real r

## Tech Stack

- **Runtime:** Node.js 22
- **Runtime:** Node.js 22.13+ within Node 22
- **Framework:** Next.js 16, React 19, TypeScript
- **Styling:** Tailwind CSS v4, OKLCH colors
- **Labeler:** @skyware/labeler, @atproto/tap
Expand Down
27 changes: 27 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Releasing

This repository uses Changesets to version the application and create GitHub Releases. The package is private and is never published to npm. Releases are not coupled to Railway or any deployment.

## Contributors

1. Every normal pull request needs one changeset fragment. For user- or operator-visible changes, add a release-note fragment:
```bash
npm run changeset
```
2. Commit the generated `.changeset/*.md` file with the pull request. For docs-only, internal, or other changes that should merge without a version bump, use an empty changeset instead:
```bash
npm run changeset:empty
```

Empty changesets satisfy the CI check; `.changeset/README.md` is documentation, not a fragment.

## Maintainers

1. Merge the normal pull request into `main` after the existing CI checks pass.
2. A push to `main` with pending fragments runs Changesets in version mode. It creates or updates one pull request from `changeset-release/main` titled **Release**, using the generated commit message `release: version packages`. That pull request updates `package.json`, `package-lock.json`, and `CHANGELOG.md`.
3. Review and merge **Release**. The release workflow listens for that merged pull request, and publishes only when its head repository is this repository and its head ref is exactly `changeset-release/main`. Before tagging anything, it checks out and validates the exact merge commit, installs dependencies without implicit lifecycle scripts, explicitly rebuilds the required native dependencies, then runs `npm test`, `npm run type-check`, and `npm run build`.
4. After validation, the workflow checks whether the derived tag and GitHub Release already both exist. It no-ops when both exist, fails for a partial state that needs manual reconciliation, and otherwise Changesets creates a tag-only release for the private package and the corresponding GitHub Release (for example, `v0.1.1`) from the generated changelog. No npm publish or npm token is used.

The repository setting **Settings → Actions → General → Allow GitHub Actions to create and approve pull requests** must be enabled for the version workflow to create or update **Release**.

The workflow intentionally uses only the automatically provided `GITHUB_TOKEN`. GitHub suppresses new workflow runs caused solely by actions performed with that token, so updates to the generated release branch may not automatically receive a fresh CI run. The changeset check skips `changeset-release/main` only when that branch belongs to this repository. If branch protection requires CI on the generated Release PR, use **Actions → CI → Run workflow**, select `changeset-release/main`, and run it manually before merging **Release**. The publish path always performs its own exact-merge-commit test, type-check, and build immediately before creating the tag and GitHub Release.
Loading