feat: publish the exact @ekklesia package train to the npm registry (ADR 0053) #81

Merged
Zayin_Admin merged 3 commits from feat/package-train-publication into main 2026-10-08 16:31:57 +00:00
4 changed files with 22 additions and 14 deletions
Showing only changes of commit 1f30c86d4f - Show all commits
+8 -7
View File
@@ -165,12 +165,12 @@ jobs:
# packages, and verifies. Nothing is published and no credential exists here. A future publish
# job must depend on this job and publish only the artifacts it proved.
- name: Build, reproduce, and consume the public package train
run: pnpm test:package-release --keep .package-release
run: pnpm test:package-release --keep package-train
- name: Describe the proven release train
id: release
run: |
release="$(find .package-release -mindepth 1 -maxdepth 1 -type d)"
release="$(find package-train -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")"
@@ -179,14 +179,15 @@ jobs:
} >>"$GITHUB_OUTPUT"
cat "$manifest"
# The exact release artifacts, for the jobs that consume them. Version 3 of the artifact
# The exact release artifacts, for the jobs that consume them, from a directory whose name does
# not start with a dot: the artifact actions leave hidden files out. Version 3 of the artifact
# actions: version 4 refuses any server that is not github.com, which this Gitea is not, while
# Gitea implements the protocol version 3 uses. A consumer checks the files, not the wrapper.
- name: Hand the release train on
uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de # v3.2.2-node20
with:
name: package-train
path: .package-release/
path: package-train/
if-no-files-found: error
retention-days: 7
@@ -228,13 +229,13 @@ jobs:
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
with:
name: package-train
path: .package-release/
path: package-train/
- name: Check it is exactly that train
env:
EXPECTED_MANIFEST_SHA256: ${{ needs.package-release.outputs.manifest_sha256 }}
run: |
release="$(find .package-release -mindepth 1 -maxdepth 1 -type d)"
release="$(find package-train -mindepth 1 -maxdepth 1 -type d)"
actual="$(sha256sum "$release/ekklesia-package-train.json" | cut -d' ' -f1)"
if [[ -z "$EXPECTED_MANIFEST_SHA256" || "$actual" != "$EXPECTED_MANIFEST_SHA256" ]]; then
echo "The received release manifest is not the one the package-release job proved." >&2
@@ -243,7 +244,7 @@ jobs:
(cd "$release" && sha256sum --check --strict SHA256SUMS)
- name: Publish it to a disposable Gitea, and prove the round trip and recovery
run: bash scripts/test-package-publication.sh --release "$(find .package-release -mindepth 1 -maxdepth 1 -type d)"
run: bash scripts/test-package-publication.sh --release "$(find package-train -mindepth 1 -maxdepth 1 -type d)"
# Whatever happened above, this job's own stacks do not outlive it.
- name: Remove this job's CI stacks
+7 -6
View File
@@ -59,12 +59,12 @@ jobs:
# 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
run: pnpm test:package-release --keep package-train
- name: Describe the proven release train
id: release
run: |
release="$(find .package-release -mindepth 1 -maxdepth 1 -type d)"
release="$(find package-train -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")"
@@ -72,12 +72,13 @@ jobs:
echo "manifest_sha256=$(sha256sum "$manifest" | cut -d' ' -f1)"
} >>"$GITHUB_OUTPUT"
# Artifact actions version 3: version 4 refuses any server that is not github.com.
# Artifact actions version 3: version 4 refuses any server that is not github.com. The directory
# name must not start with a dot: the artifact actions leave hidden files out.
- name: Hand the release train on
uses: actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de # v3.2.2-node20
with:
name: package-train
path: .package-release/
path: package-train/
if-no-files-found: error
retention-days: 90
@@ -102,7 +103,7 @@ jobs:
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
with:
name: package-train
path: .package-release/
path: package-train/
# 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
@@ -115,7 +116,7 @@ jobs:
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)" \
--release "$(find package-train -mindepth 1 -maxdepth 1 -type d)" \
--registry "$EKKLESIA_PACKAGES_REGISTRY" \
--version "$TRAIN_VERSION" \
--revision "$TRAIN_REVISION" \
+2
View File
@@ -26,3 +26,5 @@ packages/module-pack/.fixtures/
# `pnpm packages:release` writes package release trains here (scripts/package-release.ts).
.package-release/
# CI keeps the proven release here to hand it on as a workflow artifact (scripts/test-package-release.sh --keep).
/package-train/
+5 -1
View File
@@ -531,12 +531,16 @@ describe("when the real registry can be written to", () => {
expect(publishRuns).not.toMatch(/pnpm|npm (?:install|ci|run|pack)|package-release\.ts/u);
});
it("uses the artifact actions that work on this Gitea, pinned by commit", async () => {
it("uses the artifact actions that work on this Gitea, pinned by commit, on visible paths", async () => {
for (const file of ["ci.yml", "publish-packages.yml"]) {
const text = await readFile(path.join(repositoryRoot, ".github/workflows", file), "utf8");
for (const use of text.matchAll(/uses: (actions\/(?:up|down)load-artifact)@(\S+)/gu)) {
expect(use[2], `${file} ${use[1]}`).toMatch(/^[0-9a-f]{40}$/u);
}
// The artifact actions leave hidden files out: a path starting with a dot uploads nothing.
for (const artifactPath of text.matchAll(/^\s+path: (\S+)$/gmu)) {
expect(artifactPath[1], file).not.toMatch(/^\.|\/\./u);
}
}
});
});