CI / verify (pull_request) Successful in 1m1s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / package-release (pull_request) Failing after 43s
CI / package-publication (pull_request) Skipped
CI / image-build (pull_request) Successful in 29s
CI / deploy-smoke (split) (pull_request) Canceled after 0s
CI / deploy-smoke (same-origin) (pull_request) Canceled after 38s
Publishes one package release train to the Gitea npm registry under River_of_Glory_Church, whose packages are publicly readable on purpose: exactly the .tgz files the package-release gate proved, and nothing else. Nothing is published by this change. - scripts/publish-package-train.ts validates the whole local train before any request (gate manifest hash, version, revision, unmodified source, the nine packages, every artifact's size, SHA-256, integrity, SHA256SUMS line, and inner package.json), then publishes in dependency order with `npm publish <file.tgz>` under a staging tag. Each package is reconciled first (absent: publish; same bytes: accept; other bytes: hard stop, version burned), then read back: metadata (name, version, integrity, dependency maps, normalized bin, tarball on the registry) and the tarball itself, byte for byte, also anonymously. `latest` moves only after all nine verify; reruns converge and never delete anything. It writes a publication record. The token comes from EKKLESIA_PACKAGES_PUBLISH_TOKEN only, through a temporary npm config that refers to the variable. - scripts/test-package-publication.sh proves it against a disposable Gitea 1.27.2 with public owners, a publisher team and an outsider: anonymous reads, refused unauthorized publishing, byte-identical round trips, the registry's own 409, partial-train recovery, the burned version hard stop, and dist-tag retry convergence. - CI: package-release keeps its proven release and hands it on as an artifact with its manifest hash; the new package-publication job publishes exactly that artifact to the disposable Gitea. Artifact actions v3 (node20): v4 refuses any server other than github.com. - .github/workflows/publish-packages.yml publishes to the real registry only by manual dispatch on main, after its own gate run, with the secret in the publish step alone. - docs: the public-read model, the workflow, the record, credentials, immutability policy, provider moves, and the first-publication steps. Co-Authored-By: Claude Opus 5.5 <[email protected]>
133 lines
5.3 KiB
YAML
133 lines
5.3 KiB
YAML
name: Publish packages
|
|
|
|
# Publishes one public @ekklesia/* package train (ADR 0053, docs/development/releases.md) to the
|
|
# npm registry: exactly the artifacts this run's package-release gate proves, and nothing else.
|
|
#
|
|
# Only ever started by hand, on `main`, by someone with write access to this repository: no pull
|
|
# request or push can start it. It needs the version-set commit of a release (`version` must equal
|
|
# the platform version there), and the repository secret EKKLESIA_PACKAGES_PUBLISH_TOKEN, a token of
|
|
# the dedicated publisher account with only the `write:package` scope. Packages are publicly
|
|
# readable; publishing is what this protects.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: The package train version to publish, equal to the platform version on main.
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Never cancel a publication part way, and never run two at once. A rerun resumes safely.
|
|
concurrency:
|
|
group: publish-packages
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
# The only registry-specific setting: where the train is published.
|
|
EKKLESIA_PACKAGES_REGISTRY: https://git.riverofglorymn.org/api/packages/River_of_Glory_Church/npm/
|
|
|
|
jobs:
|
|
package-release:
|
|
if: github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
outputs:
|
|
version: ${{ steps.release.outputs.version }}
|
|
revision: ${{ steps.release.outputs.revision }}
|
|
manifest_sha256: ${{ steps.release.outputs.manifest_sha256 }}
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version-file: .node-version
|
|
|
|
- name: Activate pinned pnpm
|
|
run: |
|
|
corepack enable
|
|
corepack prepare [email protected] --activate
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# The same credential-free gate as CI: build, reproduce, and consume the train.
|
|
- name: Build, reproduce, and consume the public package train
|
|
run: pnpm test:package-release --keep .package-release
|
|
|
|
- name: Describe the proven release train
|
|
id: release
|
|
run: |
|
|
release="$(find .package-release -mindepth 1 -maxdepth 1 -type d)"
|
|
manifest="$release/ekklesia-package-train.json"
|
|
{
|
|
echo "version=$(node -p 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).version' "$manifest")"
|
|
echo "revision=$(node -p 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).source.revision' "$manifest")"
|
|
echo "manifest_sha256=$(sha256sum "$manifest" | cut -d' ' -f1)"
|
|
} >>"$GITHUB_OUTPUT"
|
|
|
|
# Artifact actions version 3: version 4 refuses any server that is not github.com.
|
|
- name: Hand the release train on
|
|
uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de # v3.2.2-node20
|
|
with:
|
|
name: package-train
|
|
path: .package-release/
|
|
if-no-files-found: error
|
|
retention-days: 90
|
|
|
|
publish:
|
|
needs: package-release
|
|
if: github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
# Only for the publication tool itself: nothing is built or installed in this job.
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version-file: .node-version
|
|
|
|
- name: Receive the release train the gate proved
|
|
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
|
|
with:
|
|
name: package-train
|
|
path: .package-release/
|
|
|
|
# The tool validates the whole train against its manifest, that manifest against the gate's,
|
|
# its version against the request, and its revision against this commit, before it sends
|
|
# anything; then publishes, verifies every package byte for byte, and moves `latest` last.
|
|
- name: Publish the exact release train
|
|
env:
|
|
EKKLESIA_PACKAGES_PUBLISH_TOKEN: ${{ secrets.EKKLESIA_PACKAGES_PUBLISH_TOKEN }}
|
|
TRAIN_VERSION: ${{ inputs.version }}
|
|
TRAIN_REVISION: ${{ github.sha }}
|
|
MANIFEST_SHA256: ${{ needs.package-release.outputs.manifest_sha256 }}
|
|
run: |
|
|
node scripts/publish-package-train.ts \
|
|
--release "$(find .package-release -mindepth 1 -maxdepth 1 -type d)" \
|
|
--registry "$EKKLESIA_PACKAGES_REGISTRY" \
|
|
--version "$TRAIN_VERSION" \
|
|
--revision "$TRAIN_REVISION" \
|
|
--manifest-sha256 "$MANIFEST_SHA256" \
|
|
--record publication-record.json \
|
|
--require-public-read
|
|
|
|
- name: Keep the publication record
|
|
uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de # v3.2.2-node20
|
|
with:
|
|
name: package-train-publication-record
|
|
path: publication-record.json
|
|
if-no-files-found: error
|
|
retention-days: 90
|