From 65d7097e74a1f89fb93af6ba39eca3a046812649 Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams Date: Tue, 9 Jun 2026 07:33:02 -0700 Subject: [PATCH 1/5] Add changesets-based versioning and consolidated release workflow Adopt changesets to track per-PR changes and drive releases, keeping continuity with the existing vX.Y.Z git tags (changesets uses the v-prefixed format for single-package repos; package.json is seeded at the current 1.3.3). Consolidate the release pipeline into a single workflow triggered after a green Test Suite on main: a changesets step opens/updates the "Version Packages" PR, and once merged the same workflow builds, signs, tags, creates the GitHub Release (notes from CHANGELOG.md), and dispatches the Homebrew update. Because there is no cross-workflow trigger, the built-in GITHUB_TOKEN covers all in-repo steps; the GitHub App token is used only for the cross-repo Homebrew dispatch (unchanged scope). Supply-chain hardening: SHA-pin all GitHub Actions, exact dependency versions with a committed pnpm-lock.yaml, a pnpm install cooldown (minimumReleaseAge), and no dependency lifecycle scripts. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/README.md | 31 + .changeset/config.json | 11 + .changeset/release-tooling-changesets.md | 5 + .github/workflows/release.yml | 151 ++-- .gitignore | 5 +- .npmrc | 2 + RELEASING.md | 66 ++ package.json | 15 + pnpm-lock.yaml | 871 +++++++++++++++++++++++ pnpm-workspace.yaml | 15 + 10 files changed, 1123 insertions(+), 49 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/release-tooling-changesets.md create mode 100644 .npmrc create mode 100644 RELEASING.md create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..7733b4de --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,31 @@ +# Changesets + +This directory holds [changesets](https://github.com/changesets/changesets): one +markdown file per set of changes, declaring how they bump the version and a +human-readable summary that becomes the `CHANGELOG.md` entry. + +`transitland-lib` is a Go project; changesets is used only to track changes and +drive versioning/releases. Versions stay continuous with the existing `vX.Y.Z` +git tags (changesets uses the `v`-prefixed format for single-package repos). + +## Adding a changeset (in your feature PR) + +```bash +pnpm changeset +``` + +Pick the bump level (patch / minor / major) and write a short summary. Commit +the generated `.changeset/.md` file with your PR. + +## What happens next (automated in CI) + +1. When your PR merges to `main`, a bot opens/updates a **"Version Packages"** PR + that consumes pending changesets, bumps `package.json`, and writes + `CHANGELOG.md`. +2. Merging that PR creates the `vX.Y.Z` git tag and GitHub Release, which builds + and ships the binaries. + +You do not run `changeset version` or `changeset tag` by hand — CI does. + +See [`RELEASING.md`](../RELEASING.md) for the full flow and the dependency +hardening policy. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..3fcde187 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "interline-io/transitland-lib" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/release-tooling-changesets.md b/.changeset/release-tooling-changesets.md new file mode 100644 index 00000000..b7df31fd --- /dev/null +++ b/.changeset/release-tooling-changesets.md @@ -0,0 +1,5 @@ +--- +"transitland-lib": patch +--- + +Adopt changesets for versioning and a consolidated, automated release workflow (continuous with the existing vX.Y.Z tags). Hardened with SHA-pinned actions, a committed pnpm lockfile, an install cooldown, and no dependency lifecycle scripts. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c336b9e2..8d8b07a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,82 @@ name: Release +# Consolidated release pipeline driven by changesets. +# +# Triggered only after the Test Suite finishes successfully on main (so a release +# never races ahead of tests). One of two things happens per run: +# +# * Pending changesets exist -> the changesets action opens/updates the +# "Version Packages" PR (bumps package.json, writes CHANGELOG.md). No release. +# * No changesets, and the committed version has no git tag yet (i.e. the +# "Version Packages" PR was just merged) -> build, sign, tag, release, and +# dispatch the Homebrew update. +# +# Because everything happens in this single workflow, the built-in GITHUB_TOKEN +# is sufficient for all in-repo steps (creating the Release here does not need to +# trigger any other workflow). The GitHub App token is used ONLY for the +# cross-repo Homebrew dispatch, exactly as before. +# +# External actions are pinned to commit SHAs; bump deliberately. + on: - release: - types: [created] + workflow_run: + workflows: ["Test Suite"] + types: [completed] + branches: [main] jobs: - check-tests: + changesets: + if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest permissions: - contents: read - actions: read + contents: write + pull-requests: write + outputs: + release: ${{ steps.detect.outputs.release }} + version: ${{ steps.detect.outputs.version }} steps: - - name: Check Test Suite Status - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - script: | - const { data: workflows } = await github.rest.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'test.yml', - head_sha: context.sha, - status: 'completed' - }); - const testRun = workflows.workflow_runs.find(run => run.conclusion === 'success'); - if (!testRun) { - core.setFailed('Test Suite workflow must pass before release can be built'); - } + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + - name: Set up pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + with: + run_install: false + - name: Set up Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: pnpm + - name: Install release tooling + run: pnpm install --frozen-lockfile --ignore-scripts + - name: Create or update Version Packages PR + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + version: pnpm changeset:version + title: "Version Packages" + commit: "Version Packages" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Detect release + id: detect + # Read the committed version (HEAD), not any working-tree mutation left by + # the changesets action. Release only when that version has no tag yet. + run: | + VERSION="$(git show HEAD:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + if git rev-parse "v$VERSION" >/dev/null 2>&1; then + echo "release=false" >> "$GITHUB_OUTPUT" + echo "v$VERSION already tagged; nothing to release." + else + echo "release=true" >> "$GITHUB_OUTPUT" + echo "v$VERSION is not tagged yet; will build and release." + fi build-linux: - needs: check-tests + needs: changesets + if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: ubuntu-latest permissions: contents: read @@ -38,13 +86,15 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ matrix.go-version }} - name: Build on Linux working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=$(git describe --tags --abbrev=0)" + run: CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" - name: Store Linux binary uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -52,7 +102,8 @@ jobs: path: ${{ github.workspace }}/cmd/transitland/transitland build-macos-intel: - needs: check-tests + needs: changesets + if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: macos-15-large # macOS on Intel permissions: contents: read @@ -62,13 +113,15 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ matrix.go-version }} - name: Build on macOS working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=$(git describe --tags --abbrev=0)" + run: CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" - name: Import Code-Signing Certificates uses: Apple-Actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 with: @@ -93,7 +146,8 @@ jobs: path: ${{ github.workspace }}/transitland.zip build-macos-apple: - needs: check-tests + needs: changesets + if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: macos-15 # macOS on Apple Silicon permissions: contents: read @@ -103,13 +157,15 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ matrix.go-version }} - name: Build on macOS working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -buildvcs=true -ldflags "-X main.tag=$(git describe --tags --abbrev=0)" + run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" - name: Import Code-Signing Certificates uses: Apple-Actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 with: @@ -134,11 +190,16 @@ jobs: path: ${{ github.workspace }}/transitland.zip release: - needs: [build-linux, build-macos-intel, build-macos-apple] + needs: [changesets, build-linux, build-macos-intel, build-macos-apple] + if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: ubuntu-latest permissions: contents: write steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} - name: Download Linux binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -164,31 +225,25 @@ jobs: mv transitland-macos-apple/transitland transitland-macos-apple/transitland-macos-apple - name: Copy and rename Linux binary run: cp transitland-linux/transitland transitland-linux/transitland-linux - - name: Upload Release Assets - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 - with: - files: | - transitland-linux/transitland-linux - transitland-macos-intel/transitland-macos-intel - transitland-macos-apple/transitland-macos-apple - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-notes: - needs: release - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Generate release notes + - name: Create tag and GitHub Release with assets env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - TAG: ${{ github.event.release.tag_name }} - run: gh release edit "$TAG" --generate-notes + VERSION: v${{ needs.changesets.outputs.version }} + TARGET: ${{ github.event.workflow_run.head_sha }} + # Extract the notes for this version from CHANGELOG.md (the first + # "## " section). gh creates the tag at $TARGET atomically. + run: | + NOTES="$(awk '/^## /{n++} n==1{print} n==2{exit}' CHANGELOG.md)" + gh release create "$VERSION" --target "$TARGET" \ + --title "$VERSION" \ + --notes "${NOTES:-Release $VERSION}" \ + transitland-linux/transitland-linux \ + transitland-macos-intel/transitland-macos-intel \ + transitland-macos-apple/transitland-macos-apple trigger-homebrew-update: - needs: release + needs: [changesets, release] + if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: ubuntu-latest permissions: actions: write @@ -215,6 +270,6 @@ jobs: workflow_id: 'update-formula.yml', ref: 'main', inputs: { - version: '${{ github.event.release.tag_name }}' + version: 'v${{ needs.changesets.outputs.version }}' } }); diff --git a/.gitignore b/.gitignore index a9836502..785b3c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ tldb/test.db **/.DS_Store tmp **/tmp -transitland \ No newline at end of file +transitland + +# Node / changesets release tooling +node_modules/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..be7e0efb --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +# Defense in depth: never execute dependency lifecycle scripts on install. +ignore-scripts=true diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..f29523e1 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,66 @@ +# Releasing transitland-lib + +Releases are driven by [changesets](https://github.com/changesets/changesets). +Versioning is continuous with the historical `vX.Y.Z` git tags — changesets uses +the `v`-prefixed tag format for single-package repositories, so the series simply +continues (`v1.3.3` → `v1.3.4` → ...). + +The Go module version is still embedded at build time from the git tag (via +`-ldflags -X main.tag=...`, read in `version.go`); `package.json` is only the +bookkeeping the changesets tooling reads. The two stay in lockstep because the +release tag is derived from the `package.json` version. + +## Day-to-day: add a changeset to your PR + +When a PR makes a user-visible change, record it: + +```bash +pnpm changeset +``` + +Choose the bump (patch / minor / major) and write a short summary. Commit the +generated `.changeset/.md` alongside your code. PRs without a changeset are +fine for changes that don't warrant a release (CI, docs, internal refactors). + +## How a release happens (automated) + +Everything runs in `.github/workflows/release.yml`, which fires only after the +**Test Suite** passes on `main`: + +1. **Merge feature PRs.** Each merge to `main` (after tests pass) triggers the + release workflow. While unreleased changesets exist, it opens/updates a + **"Version Packages"** PR that bumps `package.json` and writes `CHANGELOG.md`. +2. **Merge the "Version Packages" PR.** After tests pass on that merge, the + workflow sees the new version has no tag yet, then builds + signs the Linux + and macOS binaries, creates the `vX.Y.Z` tag and GitHub Release (notes pulled + from `CHANGELOG.md`, binaries attached), and dispatches the Homebrew formula + update. + +You never run `changeset version`, `changeset tag`, or create the Release by +hand — CI does. The built-in `GITHUB_TOKEN` covers all in-repo steps; the GitHub +App token is used only for the cross-repo Homebrew dispatch. + +> Note: the auto-generated "Version Packages" PR does not get its own Test Suite +> run (pushes made with `GITHUB_TOKEN` don't trigger workflows). It only edits +> `package.json`, `CHANGELOG.md`, and `.changeset/*`; the full suite still runs on +> `main` before anything is built or released. + +## Dependency / supply-chain policy + +The changesets tooling is third-party JavaScript and is treated as untrusted: + +- **All GitHub Actions are pinned to commit SHAs** (with a `# vX.Y.Z` comment). + Bump them deliberately, never via floating tags. +- **Exact dependency versions** in `package.json` (no `^`/`~`), with a committed + `pnpm-lock.yaml` (integrity-hashed). CI installs with + `pnpm install --frozen-lockfile --ignore-scripts`. +- **Cooldown:** `minimumReleaseAge` in `pnpm-workspace.yaml` keeps freshly + published versions out of the lockfile for a few days, so a compromised release + has time to be caught/yanked before we can pull it in. It applies when + resolving (local `pnpm install` / `pnpm update`), not to frozen CI installs. +- **No install scripts:** `onlyBuiltDependencies: []` (pnpm) plus + `ignore-scripts=true` (`.npmrc`) prevent dependency lifecycle scripts from + running. + +To update the release tooling, run `pnpm update` locally (the cooldown will hold +back anything too new), review the lockfile diff, and open a normal PR. diff --git a/package.json b/package.json new file mode 100644 index 00000000..b77c36b1 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "transitland-lib", + "version": "1.3.3", + "private": true, + "description": "Release tooling (changesets) for transitland-lib. The library itself is Go; this package only manages versioning and changelogs.", + "packageManager": "pnpm@11.5.1", + "scripts": { + "changeset": "changeset", + "changeset:version": "changeset version" + }, + "devDependencies": { + "@changesets/changelog-github": "0.7.0", + "@changesets/cli": "2.31.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..7da6e86b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,871 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@changesets/changelog-github': + specifier: 0.7.0 + version: 0.7.0 + '@changesets/cli': + specifier: 2.31.0 + version: 2.31.0 + +packages: + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.1.1': + resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} + + '@changesets/assemble-release-plan@6.0.10': + resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/changelog-github@0.7.0': + resolution: {integrity: sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==} + + '@changesets/cli@2.31.0': + resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} + hasBin: true + + '@changesets/config@3.1.4': + resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.4': + resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} + + '@changesets/get-github-info@0.8.0': + resolution: {integrity: sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==} + + '@changesets/get-release-plan@4.0.16': + resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.3': + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.7': + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + human-id@4.2.0: + resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} + hasBin: true + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.2: + resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + +snapshots: + + '@babel/runtime@7.29.7': {} + + '@changesets/apply-release-plan@7.1.1': + dependencies: + '@changesets/config': 3.1.4 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.8.2 + + '@changesets/assemble-release-plan@6.0.10': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.8.2 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/changelog-github@0.7.0': + dependencies: + '@changesets/get-github-info': 0.8.0 + '@changesets/types': 6.1.0 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + + '@changesets/cli@2.31.0': + dependencies: + '@changesets/apply-release-plan': 7.1.1 + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.4 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/get-release-plan': 4.0.16 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3 + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.8.2 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.4': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.8.2 + + '@changesets/get-github-info@0.8.0': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@changesets/get-release-plan@4.0.16': + dependencies: + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/config': 3.1.4 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.2.0 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.2.0 + prettier: 2.8.8 + + '@inquirer/external-editor@1.0.3': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.29.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.29.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@types/node@12.20.55': {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chardet@2.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dataloader@1.4.0: {} + + detect-indent@6.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dotenv@8.6.0: {} + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + esprima@4.0.1: {} + + extendable-error@0.1.7: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + human-id@4.2.0: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.startcase@4.4.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mri@1.2.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + pify@4.0.1: {} + + prettier@2.8.8: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + + resolve-from@5.0.0: {} + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + semver@7.8.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + term-size@2.2.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + universalify@0.1.2: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..ae9d7d43 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,15 @@ +# Single-package repo: the root IS the package. This file mainly carries pnpm +# hardening settings (cooldown, no install scripts). +packages: + - "." + +# Supply-chain cooldown: a newly published version must be at least this many +# minutes old before pnpm will resolve it into the lockfile. Guards against +# installing a compromised release before it can be detected/yanked. Only +# affects resolution (local `pnpm install`/`pnpm update`), not `--frozen-lockfile`. +minimumReleaseAge: 4320 # 3 days +minimumReleaseAgeStrict: true + +# Do not run dependency lifecycle scripts (preinstall/install/postinstall). +# Empty allowlist => no package is permitted to run build scripts. +onlyBuiltDependencies: [] From 0786f3bd20301789784e3ff25206ce8ea0d6f04f Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams Date: Tue, 9 Jun 2026 09:19:52 -0700 Subject: [PATCH 2/5] Add build provenance, checksums, trimpath, and v2 release docs - Publish SLSA build provenance (actions/attest-build-provenance, keyless via GitHub OIDC) over a SHA256SUMS checksum file; SHA256SUMS is also attached as a release asset. Adds id-token/attestations permissions to the release job. - Build all binaries with -trimpath and -s -w for reproducibility and size. - Document Go v2+ module-path discipline (a changesets `major` bump requires moving the module path to /vN) and how to verify provenance/checksums. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/release-tooling-changesets.md | 2 +- .github/workflows/release.yml | 39 +++++++++++++++--------- RELEASING.md | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/.changeset/release-tooling-changesets.md b/.changeset/release-tooling-changesets.md index b7df31fd..a041db84 100644 --- a/.changeset/release-tooling-changesets.md +++ b/.changeset/release-tooling-changesets.md @@ -2,4 +2,4 @@ "transitland-lib": patch --- -Adopt changesets for versioning and a consolidated, automated release workflow (continuous with the existing vX.Y.Z tags). Hardened with SHA-pinned actions, a committed pnpm lockfile, an install cooldown, and no dependency lifecycle scripts. +Adopt changesets for versioning and a consolidated, automated release workflow (continuous with the existing vX.Y.Z tags). Hardened with SHA-pinned actions, a committed pnpm lockfile, an install cooldown, and no dependency lifecycle scripts. Releases now publish SLSA build provenance and a SHA256SUMS checksum file, and binaries are built with -trimpath. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d8b07a3..c4c885bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Build on Linux working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" + run: CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -trimpath -buildvcs=true -ldflags "-s -w -X main.tag=v${{ needs.changesets.outputs.version }}" - name: Store Linux binary uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -121,7 +121,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Build on macOS working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" + run: CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -trimpath -buildvcs=true -ldflags "-s -w -X main.tag=v${{ needs.changesets.outputs.version }}" - name: Import Code-Signing Certificates uses: Apple-Actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 with: @@ -165,7 +165,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Build on macOS working-directory: ${{ github.workspace }}/cmd/transitland - run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -buildvcs=true -ldflags "-X main.tag=v${{ needs.changesets.outputs.version }}" + run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -trimpath -buildvcs=true -ldflags "-s -w -X main.tag=v${{ needs.changesets.outputs.version }}" - name: Import Code-Signing Certificates uses: Apple-Actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7.0.0 with: @@ -194,7 +194,9 @@ jobs: if: ${{ needs.changesets.outputs.release == 'true' }} runs-on: ubuntu-latest permissions: - contents: write + contents: write # create the tag, release, and upload assets + id-token: write # OIDC identity for keyless provenance signing + attestations: write # record the build provenance attestation steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -215,16 +217,24 @@ jobs: with: name: transitland-macos-apple path: transitland-macos-apple - - name: Unzip and rename macOS Intel binary + - name: Assemble artifacts and checksums + # Flatten the three binaries into dist/ with their final asset names and + # produce SHA256SUMS (basenames only, so `sha256sum -c` works after download). run: | + mkdir -p dist unzip -j transitland-macos-intel/transitland.zip -d transitland-macos-intel/ - mv transitland-macos-intel/transitland transitland-macos-intel/transitland-macos-intel - - name: Unzip and rename macOS Apple Silicon binary - run: | unzip -j transitland-macos-apple/transitland.zip -d transitland-macos-apple/ - mv transitland-macos-apple/transitland transitland-macos-apple/transitland-macos-apple - - name: Copy and rename Linux binary - run: cp transitland-linux/transitland transitland-linux/transitland-linux + cp transitland-linux/transitland dist/transitland-linux + cp transitland-macos-intel/transitland dist/transitland-macos-intel + cp transitland-macos-apple/transitland dist/transitland-macos-apple + ( cd dist && sha256sum transitland-linux transitland-macos-intel transitland-macos-apple > SHA256SUMS ) + cat dist/SHA256SUMS + - name: Attest build provenance + # SLSA build provenance for every binary listed in SHA256SUMS. Verify with: + # gh attestation verify --repo interline-io/transitland-lib + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-checksums: dist/SHA256SUMS - name: Create tag and GitHub Release with assets env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -237,9 +247,10 @@ jobs: gh release create "$VERSION" --target "$TARGET" \ --title "$VERSION" \ --notes "${NOTES:-Release $VERSION}" \ - transitland-linux/transitland-linux \ - transitland-macos-intel/transitland-macos-intel \ - transitland-macos-apple/transitland-macos-apple + dist/transitland-linux \ + dist/transitland-macos-intel \ + dist/transitland-macos-apple \ + dist/SHA256SUMS trigger-homebrew-update: needs: [changesets, release] diff --git a/RELEASING.md b/RELEASING.md index f29523e1..fa48d4d1 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -45,6 +45,38 @@ App token is used only for the cross-repo Homebrew dispatch. > `package.json`, `CHANGELOG.md`, and `.changeset/*`; the full suite still runs on > `main` before anything is built or released. +## Major versions (v2+) — read before choosing a `major` bump + +`transitland-lib` is both a CLI and a **Go library** imported by other projects, +so Go module rules apply. A `major` changeset will bump to `2.0.0` and tag +`v2.0.0` — but for Go modules **a v2+ release also requires changing the module +path**, which is not automatic: + +- Update `module github.com/interline-io/transitland-lib` → + `.../transitland-lib/v2` in `go.mod`. +- Update internal imports of the module path accordingly. +- Consumers must then `import ".../transitland-lib/v2"` and `go get .../v2`. + +Without the `/vN` suffix, `go get` cannot resolve `v2.0.0` and downstream builds +break. Because changesets makes a major bump look as cheap as any other, treat +`major` as a deliberate, separately-reviewed change that includes the module-path +move (and ideally a migration note in the changelog). For `v0`/`v1` this does not +apply. + +Never delete or re-point a published tag — consumers may depend on it. + +## Build provenance & verification + +Released binaries carry [SLSA build provenance](https://docs.github.com/en/actions/concepts/security/artifact-attestations) +(keyless, via GitHub OIDC + Sigstore) and a `SHA256SUMS` asset. To verify a +downloaded binary: + +```bash +gh attestation verify ./transitland-linux --repo interline-io/transitland-lib +# and/or +sha256sum -c SHA256SUMS +``` + ## Dependency / supply-chain policy The changesets tooling is third-party JavaScript and is treated as untrusted: From 53a95620c0a0d0bd682a5b7d647d1a38b60e6976 Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams Date: Tue, 9 Jun 2026 13:23:22 -0700 Subject: [PATCH 3/5] Address Copilot review: serialize releases, precise tag check, sync schema - Add a workflow-level concurrency group so two Test Suite completions can't race on the Version Packages PR or tag/release creation. - Detect an existing release tag with `git rev-parse --verify refs/tags/...` instead of a bare ref lookup that could match a branch named v. - Point the changesets config $schema at the installed @changesets/config 3.1.4. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/config.json | 2 +- .github/workflows/release.yml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 3fcde187..5d21fda3 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,5 +1,5 @@ { - "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", "changelog": ["@changesets/changelog-github", { "repo": "interline-io/transitland-lib" }], "commit": false, "fixed": [], diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4c885bb..e9100971 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,13 @@ on: types: [completed] branches: [main] +# Serialize release runs so two Test Suite completions finishing close together +# can't race on the Version Packages PR or on tag/release creation. Do not cancel +# an in-flight run mid-release. +concurrency: + group: release + cancel-in-progress: false + jobs: changesets: if: ${{ github.event.workflow_run.conclusion == 'success' }} @@ -66,7 +73,7 @@ jobs: run: | VERSION="$(git show HEAD:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - if git rev-parse "v$VERSION" >/dev/null 2>&1; then + if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then echo "release=false" >> "$GITHUB_OUTPUT" echo "v$VERSION already tagged; nothing to release." else From fbdc390b0e400c431626a2ad05c566482d328ee0 Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams Date: Tue, 9 Jun 2026 13:40:20 -0700 Subject: [PATCH 4/5] Update README for changesets release flow - Rewrite the Releases section: releases are now driven by changesets (pnpm changeset -> Version Packages PR -> automated tag/build/publish), replacing the manual tag + homebrew-edit steps. Link to RELEASING.md. - Document SHA256SUMS + build-provenance verification under prebuilt binaries. - Fix stale CI badge (was the non-existent "Test & Release" workflow) to point at the Test Suite workflow. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index af5cbf36..4b35df7c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `transitland-lib` is a library and command-line tool for reading, writing, and processing transit data in [GTFS](http://gtfs.org) and related formats. The library is structured as a set of data sources, filters, and transformations that can be mixed together in a variety of ways to create processing pipelines. The library supports the [DMFR](https://github.com/transitland/distributed-mobility-feed-registry) format to describe feed resources. -![Test & Release](https://github.com/interline-io/transitland-lib/workflows/Test%20&%20Release/badge.svg) [![GoDoc](https://godoc.org/github.com/interline-io/transitland-lib/tl?status.svg)](https://godoc.org/github.com/interline-io/transitland-lib/tl) ![Go Report Card](https://goreportcard.com/badge/github.com/interline-io/transitland-lib) +[![Test Suite](https://github.com/interline-io/transitland-lib/actions/workflows/test.yml/badge.svg)](https://github.com/interline-io/transitland-lib/actions/workflows/test.yml) [![GoDoc](https://godoc.org/github.com/interline-io/transitland-lib/tl?status.svg)](https://godoc.org/github.com/interline-io/transitland-lib/tl) ![Go Report Card](https://goreportcard.com/badge/github.com/interline-io/transitland-lib) ## Table of Contents @@ -24,7 +24,12 @@ ### Download prebuilt binary -The `transitland` binaries for Linux and macOS are attached to each [release](https://github.com/interline-io/transitland-lib/releases). +The `transitland` binaries for Linux and macOS are attached to each [release](https://github.com/interline-io/transitland-lib/releases), along with a `SHA256SUMS` file and [SLSA build provenance](https://docs.github.com/en/actions/concepts/security/artifact-attestations). To verify a download: + +```bash +gh attestation verify ./transitland-linux --repo interline-io/transitland-lib +sha256sum -c SHA256SUMS +``` ### Install using homebrew @@ -139,13 +144,23 @@ Test cases generally run within transactions; you do not need to regenerate the ### Releases -Releases follow [Semantic Versioning](https://semver.org/) conventions. +Releases follow [Semantic Versioning](https://semver.org/) and are driven by +[changesets](https://github.com/changesets/changesets); version numbers continue +the existing `vX.Y.Z` tag series. + +In a PR that makes a user-visible change, record it with: + +```bash +pnpm changeset +``` -To cut a new release: +Merging to `main` opens a "Version Packages" PR that bumps the version and +updates `CHANGELOG.md`. Merging *that* PR automatically tags the release, builds, +signs, and publishes the binaries, and updates the homebrew formula — no manual +tagging or formula edits required. -1. Run `go generate ./...` to update auto-generated documentation. -2. Create a GitHub release. This will create a tag and GitHub Actions will create & attach code-signed binaries. -3. Download the files from the release, and update the [homebrew formula](https://github.com/interline-io/homebrew-transitland-lib/blob/master/transitland-lib.rb) with the updated sha256 hashes and version tag. +See [RELEASING.md](RELEASING.md) for the full flow, build-provenance/checksum +verification, and major-version (`/vN` module path) guidance. ## Licenses From 305d42c07a3806ff6fb65500594aeff4b9a2756c Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams Date: Tue, 9 Jun 2026 13:43:29 -0700 Subject: [PATCH 5/5] Link homebrew tap, note go generate prereq, drop em dashes - Link the interline-io/homebrew-transitland-lib tap from the README homebrew install and Releases sections. - Document `go generate ./...` as a development prerequisite (enforced by check-go-generate.yml) in the Development section. - Replace em dashes with plain punctuation across the release docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/README.md | 2 +- README.md | 9 ++++++--- RELEASING.md | 14 +++++++------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.changeset/README.md b/.changeset/README.md index 7733b4de..128ea6c4 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -25,7 +25,7 @@ the generated `.changeset/.md` file with your PR. 2. Merging that PR creates the `vX.Y.Z` git tag and GitHub Release, which builds and ships the binaries. -You do not run `changeset version` or `changeset tag` by hand — CI does. +You do not run `changeset version` or `changeset tag` by hand; CI does. See [`RELEASING.md`](../RELEASING.md) for the full flow and the dependency hardening policy. diff --git a/README.md b/README.md index 4b35df7c..d03e2ee7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ sha256sum -c SHA256SUMS ### Install using homebrew -The `transitland` binary can be installed using homebrew. The executable is code-signed and notarized. +The `transitland` binary can be installed using homebrew from the [interline-io/homebrew-transitland-lib](https://github.com/interline-io/homebrew-transitland-lib) tap. The executable is code-signed and notarized. ```bash brew install interline-io/transitland-lib/transitland-lib @@ -121,6 +121,8 @@ We welcome the addition of more readers and writers. GitHub Actions runs all tests, stores code coverage reports as artifacts, and prepares releases. +When you change anything that feeds auto-generated code or docs (GraphQL schema, CLI commands), run `go generate ./...` and commit the result as part of your PR. This is a prerequisite: CI (`check-go-generate.yml`) fails if the generated output is out of date. If your PR makes a user-visible change, also add a changeset with `pnpm changeset` (see [Releases](#releases)). + For running tests locally, the following instructions should help get started: 1. Set `TL_TEST_SERVER_DATABASE_URL` to the connection string to a test database @@ -156,8 +158,9 @@ pnpm changeset Merging to `main` opens a "Version Packages" PR that bumps the version and updates `CHANGELOG.md`. Merging *that* PR automatically tags the release, builds, -signs, and publishes the binaries, and updates the homebrew formula — no manual -tagging or formula edits required. +signs, and publishes the binaries, and updates the +[homebrew formula](https://github.com/interline-io/homebrew-transitland-lib). +No manual tagging or formula edits are required. See [RELEASING.md](RELEASING.md) for the full flow, build-provenance/checksum verification, and major-version (`/vN` module path) guidance. diff --git a/RELEASING.md b/RELEASING.md index fa48d4d1..a9bf479f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,9 +1,9 @@ # Releasing transitland-lib Releases are driven by [changesets](https://github.com/changesets/changesets). -Versioning is continuous with the historical `vX.Y.Z` git tags — changesets uses +Versioning is continuous with the historical `vX.Y.Z` git tags. Changesets uses the `v`-prefixed tag format for single-package repositories, so the series simply -continues (`v1.3.3` → `v1.3.4` → ...). +continues (`v1.3.3` to `v1.3.4` and so on). The Go module version is still embedded at build time from the git tag (via `-ldflags -X main.tag=...`, read in `version.go`); `package.json` is only the @@ -37,7 +37,7 @@ Everything runs in `.github/workflows/release.yml`, which fires only after the update. You never run `changeset version`, `changeset tag`, or create the Release by -hand — CI does. The built-in `GITHUB_TOKEN` covers all in-repo steps; the GitHub +hand; CI does. The built-in `GITHUB_TOKEN` covers all in-repo steps; the GitHub App token is used only for the cross-repo Homebrew dispatch. > Note: the auto-generated "Version Packages" PR does not get its own Test Suite @@ -45,14 +45,14 @@ App token is used only for the cross-repo Homebrew dispatch. > `package.json`, `CHANGELOG.md`, and `.changeset/*`; the full suite still runs on > `main` before anything is built or released. -## Major versions (v2+) — read before choosing a `major` bump +## Major versions (v2+): read before choosing a `major` bump `transitland-lib` is both a CLI and a **Go library** imported by other projects, so Go module rules apply. A `major` changeset will bump to `2.0.0` and tag -`v2.0.0` — but for Go modules **a v2+ release also requires changing the module +`v2.0.0`, but for Go modules **a v2+ release also requires changing the module path**, which is not automatic: -- Update `module github.com/interline-io/transitland-lib` → +- Update `module github.com/interline-io/transitland-lib` to `.../transitland-lib/v2` in `go.mod`. - Update internal imports of the module path accordingly. - Consumers must then `import ".../transitland-lib/v2"` and `go get .../v2`. @@ -63,7 +63,7 @@ break. Because changesets makes a major bump look as cheap as any other, treat move (and ideally a migration note in the changelog). For `v0`/`v1` this does not apply. -Never delete or re-point a published tag — consumers may depend on it. +Never delete or re-point a published tag; consumers may depend on it. ## Build provenance & verification