From 7d920bab1a5bc59e521b75ca2e0e865008aee7b9 Mon Sep 17 00:00:00 2001 From: kzoeps Date: Thu, 3 Sep 2026 13:36:49 +0600 Subject: [PATCH 1/4] release: automate changelog and GitHub releases --- .changeset/README.md | 15 + .changeset/config.json | 20 + .changeset/initial-release-process.md | 5 + .github/workflows/ci.yml | 23 ++ .github/workflows/release.yml | 198 ++++++++++ AGENTS.md | 5 +- README.md | 8 +- docs/RELEASING.md | 27 ++ package-lock.json | 507 +++++++++++++++++++++++++- package.json | 10 +- 10 files changed, 806 insertions(+), 12 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/initial-release-process.md create mode 100644 .github/workflows/release.yml create mode 100644 docs/RELEASING.md diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..6622570 --- /dev/null +++ b/.changeset/README.md @@ -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. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..22d0ad2 --- /dev/null +++ b/.changeset/config.json @@ -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 + } +} diff --git a/.changeset/initial-release-process.md b/.changeset/initial-release-process.md new file mode 100644 index 0000000..ec73e89 --- /dev/null +++ b/.changeset/initial-release-process.md @@ -0,0 +1,5 @@ +--- +"certified-organization-labeler": patch +--- + +Add the automated release-notes process for maintainers and operators. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11e06c3..93e1274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: jobs: ci: @@ -14,6 +15,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 @@ -24,6 +28,25 @@ jobs: - 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 + npx changeset status --since "$BASE_SHA" + - name: Type-check run: npx tsc --noEmit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1d846bf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,198 @@ +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@v4 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Select Changesets mode + id: select-mode + uses: changesets/action/select-mode@v2 + + 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@v4 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Version package, changelog, and lockfile + uses: changesets/action/version@v2 + 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@v4 + 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@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Type-check + run: npx tsc --noEmit + + - 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@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + create-github-releases: true + push-git-tags: true diff --git a/AGENTS.md b/AGENTS.md index 985bea0..b2c33d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index 4d2f0d6..fa28e49 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: @@ -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 diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..f55c339 --- /dev/null +++ b/docs/RELEASING.md @@ -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, then runs `npm test`, `npx tsc --noEmit`, 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. diff --git a/package-lock.json b/package-lock.json index 0ea83b8..0cf5e23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,8 @@ }, "devDependencies": { "@atproto/lex": "^0.0.20", + "@changesets/changelog-github": "^1.0.0", + "@changesets/cli": "^3.0.1", "@types/better-sqlite3": "^7.6.0", "@types/express": "^5.0.0", "@types/node": "^22.0.0", @@ -40,7 +42,7 @@ "typescript": "^5.0.0" }, "engines": { - "node": ">=22 <23" + "node": ">=22.13 <23" } }, "node_modules/@alloc/quick-lru": { @@ -852,6 +854,306 @@ "node": ">= 18" } }, + "node_modules/@changesets/apply-release-plan": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-8.0.0.tgz", + "integrity": "sha512-kUd2pbf1w5/AYmBMb0Tt+rkIPCjFJdT0SZMrkOjJT/WV/QbtmvkyB5jkV0oaNPheavprZk+SfUiozUti7TIL2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^4.0.0", + "@changesets/format": "^0.1.1", + "@changesets/git": "^4.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "jsonc-parser": "^3.3.1", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-7.0.0.tgz", + "integrity": "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-1.0.0.tgz", + "integrity": "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/changelog-github": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-1.0.0.tgz", + "integrity": "sha512-PNCSH5CGJrqOKxRjrMp2NLhceQv2QBs6HkjrLvYQqsEE2/ItN5R8GD/mskZZluoNWYvjPYFsY9T6swXfp5E0UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-github-info": "^1.0.0", + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/cli": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-3.0.1.tgz", + "integrity": "sha512-3IVpRSgiKDj2aRbBHKtWTXSRH2/iR3bWxau9iQUoEsZjDhRIj9nhl90k7FWMxZAJvLgJBbgMq8+qS0xQsenYsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^8.0.0", + "@changesets/assemble-release-plan": "^7.0.0", + "@changesets/changelog-git": "^1.0.0", + "@changesets/config": "^4.0.0", + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/git": "^4.0.0", + "@changesets/pre": "^3.0.0", + "@changesets/read": "^1.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@changesets/write": "^1.0.1", + "@clack/prompts": "^1.7.0", + "@manypkg/get-packages": "^3.1.0", + "@pnpm/deps.graph-sequencer": "^1100.0.1", + "cac": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "launch-editor": "^2.14.1", + "package-manager-detector": "^1.6.0", + "semver": "^7.8.1", + "tinyexec": "^1.3.0" + }, + "bin": { + "changeset": "bin.js" + }, + "engines": { + "node": "^22.11 || ^24 || >=26", + "npm": ">=10.9.0", + "pnpm": ">=10.0.0", + "yarn": ">=4.5.2" + } + }, + "node_modules/@changesets/config": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-4.0.0.tgz", + "integrity": "sha512-mw95/YrkOuhZZxfnVAA4bSXOFUi+KlhzOBTM8C4x777NhUU6HWIl9Z+K+nME+E4PVsv5NQVQwTfiHihAS1A/ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-1.0.0.tgz", + "integrity": "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/format": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/format/-/format-0.1.2.tgz", + "integrity": "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-3.0.0.tgz", + "integrity": "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-1.0.0.tgz", + "integrity": "sha512-nf5zPSqEKW0eXiJ4ltbjyIQ/srUHfxbghkLCjKdOLI3LehKXhiqxXcpja39eGPaj6qqGx2lFCrLJ7VWYUf9H2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "dataloader": "^2.2.3" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/git": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-4.0.0.tgz", + "integrity": "sha512-uIEswpPUgzBBqrC0qg13byNaPorzhf05LE2T+gRizEKCXvMsJC6NPJp5iNDSV/gYj4Viqh09UiOF5E7XWeH5lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.4", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-1.0.0.tgz", + "integrity": "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "yaml": "^2.9.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/pre": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-3.0.0.tgz", + "integrity": "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/read": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-1.0.0.tgz", + "integrity": "sha512-8TdE2PwG6yArPt5Ozej83Z6iHz1G8BDBKvIEKZ455MccR4K3GNbLR2XqjBxa+5yLFnEd3xAOPXYboBg1pt8stQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^4.0.0", + "@changesets/parse": "^1.0.0", + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-1.0.0.tgz", + "integrity": "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/types": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-7.0.0.tgz", + "integrity": "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/write": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-1.0.1.tgz", + "integrity": "sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/format": "^0.1.1", + "@changesets/types": "^7.0.0", + "human-id": "^4.2.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -2018,6 +2320,48 @@ "win32" ] }, + "node_modules/@manypkg/find-root": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-3.1.0.tgz", + "integrity": "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@manypkg/tools": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-3.1.0.tgz", + "integrity": "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@manypkg/find-root": "^3.1.0", + "@manypkg/tools": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@manypkg/tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@manypkg/tools/-/tools-2.1.2.tgz", + "integrity": "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jju": "^1.4.0", + "tinyglobby": "^0.2.13", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", @@ -2200,6 +2544,19 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@pnpm/deps.graph-sequencer": { + "version": "1100.0.1", + "resolved": "https://registry.npmjs.org/@pnpm/deps.graph-sequencer/-/deps.graph-sequencer-1100.0.1.tgz", + "integrity": "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.13" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, "node_modules/@skyware/bot": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@skyware/bot/-/bot-0.4.0.tgz", @@ -3028,6 +3385,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -3258,6 +3625,13 @@ "node": ">= 12" } }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "dev": true, + "license": "MIT" + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -3670,12 +4044,39 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, "node_modules/fast-uri": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", "license": "MIT" }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastify": { "version": "4.29.1", "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", @@ -4003,6 +4404,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/human-id": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.1.tgz", + "integrity": "sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4035,6 +4446,17 @@ ], "license": "BSD-3-Clause" }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4080,6 +4502,13 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -4110,6 +4539,13 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4119,6 +4555,30 @@ "node": ">=6" } }, + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" + } + }, + "node_modules/launch-editor/node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/libsql": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.7.tgz", @@ -4781,6 +5241,13 @@ "wrappy": "1" } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4819,9 +5286,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -5323,9 +5790,9 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5780,6 +6247,16 @@ "license": "MIT", "optional": true }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6028,6 +6505,22 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index f87dbc4..dcf62a8 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "certified-organization-labeler", "description": "Certified Organization Labeler fork for app.certified.actor.organization", "version": "0.1.0", + "private": true, "type": "module", "scripts": { "dev": "next dev", @@ -16,7 +17,10 @@ "set-labels": "tsx src/labeler/set-labels.ts", "setup": "tsx src/labeler/setup.ts", "reset": "tsx src/labeler/reset-db.ts", - "gen:lexicons": "ts-lex build --lexicons ./lexicons --out ./src/lexicons --clear --import-ext \"\"" + "gen:lexicons": "ts-lex build --lexicons ./lexicons --out ./src/lexicons --clear --import-ext \"\"", + "changeset": "changeset", + "changeset:empty": "changeset --empty", + "version:packages": "changeset version && npm install --package-lock-only" }, "dependencies": { "@atproto/api": "0.19.9", @@ -42,10 +46,12 @@ "tsx": "^4.0.0" }, "engines": { - "node": ">=22 <23" + "node": ">=22.13 <23" }, "devDependencies": { "@atproto/lex": "^0.0.20", + "@changesets/changelog-github": "^1.0.0", + "@changesets/cli": "^3.0.1", "@types/better-sqlite3": "^7.6.0", "@types/express": "^5.0.0", "@types/node": "^22.0.0", From dacd03020d23211efff1321b705dcf4fb84b5f31 Mon Sep 17 00:00:00 2001 From: kzoeps Date: Thu, 3 Sep 2026 14:04:23 +0600 Subject: [PATCH 2/4] github-actions: pin dependencies to verified SHAs --- .changeset/pin-github-actions.md | 2 ++ .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 18 +++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .changeset/pin-github-actions.md diff --git a/.changeset/pin-github-actions.md b/.changeset/pin-github-actions.md new file mode 100644 index 0000000..a845151 --- /dev/null +++ b/.changeset/pin-github-actions.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93e1274..3ae7f7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,13 +14,13 @@ 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' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d846bf..8905075 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,14 +26,14 @@ jobs: mode: ${{ steps.select-mode.outputs.mode }} steps: - name: Check out repository - uses: actions/checkout@v4 + 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@v4 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -43,7 +43,7 @@ jobs: - name: Select Changesets mode id: select-mode - uses: changesets/action/select-mode@v2 + uses: changesets/action/select-mode@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1 version: name: Create or update Release PR @@ -55,14 +55,14 @@ jobs: pull-requests: write steps: - name: Check out repository - uses: actions/checkout@v4 + 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@v4 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -71,7 +71,7 @@ jobs: run: npm ci - name: Version package, changelog, and lockfile - uses: changesets/action/version@v2 + uses: changesets/action/version@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: npm run version:packages @@ -91,7 +91,7 @@ jobs: contents: write steps: - name: Check out exact pushed commit - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.merge_commit_sha }} fetch-depth: 0 @@ -113,7 +113,7 @@ jobs: fi - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -191,7 +191,7 @@ jobs: # 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@v2 + uses: changesets/action/publish@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} create-github-releases: true From badee98496524a52acc1ce4b93b0c425f6bef456 Mon Sep 17 00:00:00 2001 From: kzoeps Date: Thu, 3 Sep 2026 14:35:00 +0600 Subject: [PATCH 3/4] github-actions: restrict dependency lifecycle scripts --- .changeset/harden-release-dependencies.md | 5 ++++ .github/workflows/ci.yml | 4 +-- .github/workflows/release.yml | 11 +++++--- docs/RELEASING.md | 2 +- package.json | 4 ++- tests/workflow-security.test.ts | 33 +++++++++++++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 .changeset/harden-release-dependencies.md create mode 100644 tests/workflow-security.test.ts diff --git a/.changeset/harden-release-dependencies.md b/.changeset/harden-release-dependencies.md new file mode 100644 index 0000000..b83fd82 --- /dev/null +++ b/.changeset/harden-release-dependencies.md @@ -0,0 +1,5 @@ +--- +"certified-organization-labeler": patch +--- + +Harden release automation by preventing implicit dependency lifecycle scripts and avoiding on-demand package execution. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ae7f7f..df366e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,10 +45,10 @@ jobs: 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 - npx changeset status --since "$BASE_SHA" + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8905075..bfa458c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: cache: npm - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Select Changesets mode id: select-mode @@ -68,7 +68,7 @@ jobs: cache: npm - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Version package, changelog, and lockfile uses: changesets/action/version@8488615a623b1b9c987934bb89eae8af6a946ac1 # v2.1.1 @@ -119,13 +119,16 @@ jobs: cache: npm - name: Install dependencies - run: npm ci + 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: npx tsc --noEmit + run: npm run type-check - name: Build run: npm run build diff --git a/docs/RELEASING.md b/docs/RELEASING.md index f55c339..884f989 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -19,7 +19,7 @@ Empty changesets satisfy the CI check; `.changeset/README.md` is documentation, 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, then runs `npm test`, `npx tsc --noEmit`, and `npm run build`. +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**. diff --git a/package.json b/package.json index dcf62a8..b247df9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "gen:lexicons": "ts-lex build --lexicons ./lexicons --out ./src/lexicons --clear --import-ext \"\"", "changeset": "changeset", "changeset:empty": "changeset --empty", - "version:packages": "changeset version && npm install --package-lock-only" + "changeset:status": "changeset status", + "type-check": "tsc --noEmit", + "version:packages": "changeset version && npm install --package-lock-only --ignore-scripts" }, "dependencies": { "@atproto/api": "0.19.9", diff --git a/tests/workflow-security.test.ts b/tests/workflow-security.test.ts new file mode 100644 index 0000000..446b464 --- /dev/null +++ b/tests/workflow-security.test.ts @@ -0,0 +1,33 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' + +const packageJson = JSON.parse(readFileSync('package.json', 'utf8')) as { + scripts: Record +} +const ciWorkflow = readFileSync('.github/workflows/ci.yml', 'utf8') +const releaseWorkflow = readFileSync('.github/workflows/release.yml', 'utf8') + +test('workflow commands use installed package scripts instead of npx', () => { + assert.equal(packageJson.scripts['changeset:status'], 'changeset status') + assert.equal(packageJson.scripts['type-check'], 'tsc --noEmit') + assert.doesNotMatch(ciWorkflow, /\bnpx\b/) + assert.doesNotMatch(releaseWorkflow, /\bnpx\b/) +}) + +test('release dependency installs disable implicit lifecycle scripts', () => { + const installCommands = releaseWorkflow.match(/^\s+run: npm ci.*$/gm) + + assert.deepEqual(installCommands, [ + ' run: npm ci --ignore-scripts', + ' run: npm ci --ignore-scripts', + ' run: npm ci --ignore-scripts', + ]) +}) + +test('release validation explicitly rebuilds required native dependencies', () => { + assert.match( + releaseWorkflow, + /run: npm ci --ignore-scripts\n\n - name: Build required native dependencies\n run: npm rebuild better-sqlite3 esbuild sharp/, + ) +}) From 4d7bd3648b5c6efe9b9b4f97b44cf42a1ebf0018 Mon Sep 17 00:00:00 2001 From: kzoeps Date: Thu, 3 Sep 2026 14:42:29 +0600 Subject: [PATCH 4/4] changesets: consolidate release notes --- .changeset/harden-release-dependencies.md | 5 ----- .changeset/initial-release-process.md | 2 +- .changeset/pin-github-actions.md | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 .changeset/harden-release-dependencies.md delete mode 100644 .changeset/pin-github-actions.md diff --git a/.changeset/harden-release-dependencies.md b/.changeset/harden-release-dependencies.md deleted file mode 100644 index b83fd82..0000000 --- a/.changeset/harden-release-dependencies.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"certified-organization-labeler": patch ---- - -Harden release automation by preventing implicit dependency lifecycle scripts and avoiding on-demand package execution. diff --git a/.changeset/initial-release-process.md b/.changeset/initial-release-process.md index ec73e89..8192334 100644 --- a/.changeset/initial-release-process.md +++ b/.changeset/initial-release-process.md @@ -2,4 +2,4 @@ "certified-organization-labeler": patch --- -Add the automated release-notes process for maintainers and operators. +Add automated release notes and GitHub Releases, with hardened workflow dependency execution, for maintainers and operators. diff --git a/.changeset/pin-github-actions.md b/.changeset/pin-github-actions.md deleted file mode 100644 index a845151..0000000 --- a/.changeset/pin-github-actions.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ----