From a01f3fa2cdfd2cf3f9df4af4d08ee99b1bf6bf60 Mon Sep 17 00:00:00 2001 From: Dani Espinoza Charrier Date: Sat, 24 Oct 2026 17:11:40 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20a=20shipped-bundle=20caller=20=E2=80=94?= =?UTF-8?q?=20the=20one=20app=20whose=20bundle=20ships=20as=20committed=20?= =?UTF-8?q?bytes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit side_menu (instalador #38) is seeded from upstream's AGPL 6.0.1 tarball because its source is unreachable, so it carries no build manifest of its own: its js/ bundle is vendored bytes, not something the release builds. The packer refused any committed js/*.js — the right default, kept — and gains the one exception, named by the caller: `shipped-bundle: true` makes the workflow set SHIPPED_BUNDLE=1, which accepts the committed bundle and still refuses a package.json beside it, because a buildable app builds instead of shipping. Deterministic packing, the tag/version check, the signature.json refusal and signing all stay exactly as they are. - scripts/pack-app.sh: the bundle rule and its exception, one branch; two self-test arms (a shipped bundle packs and ships; a package.json refuses it) — 27 checks OK. - app-release.yml: the shipped-bundle input, documented at the top with the one caller it exists for; actionlint clean. Assisted-by: pi:glm-5.3 --- .github/workflows/app-release.yml | 11 ++++++++++ scripts/pack-app.sh | 35 +++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/app-release.yml b/.github/workflows/app-release.yml index 683f3a3..acc8767 100644 --- a/.github/workflows/app-release.yml +++ b/.github/workflows/app-release.yml @@ -22,6 +22,10 @@ # aps-common copies, then, inside the caller's protected `release` environment (the app key's only # home), signs SHA256SUMS, proves it against the suite's apps.pub and publishes # .tar.gz, info.xml, SHA256SUMS and SHA256SUMS.minisig. The build job never sees the key. +# +# The one caller escape hatch is `shipped-bundle: true`, for an app whose upstream is unreachable +# and whose tarball is the source of truth: its bundle ships as committed bytes (with a no-op +# build) and the packer accepts it — for that caller alone, and never beside a package.json. name: App release on: @@ -43,6 +47,11 @@ on: description: The build command, run in app-dir. default: npm ci && npm run build type: string + shipped-bundle: + description: The app ships its bundle as committed bytes (upstream unreachable, no build + manifest — side_menu). The build input stays the no-op; a package.json refuses it. + default: false + type: boolean publish: description: Publish on a v* tag. False only builds and packs. default: true @@ -96,6 +105,8 @@ jobs: BUILD: ${{ inputs.build }} run: bash -euo pipefail -c "$BUILD" - name: Pack + env: + SHIPPED_BUNDLE: ${{ inputs.shipped-bundle && '1' || '0' }} run: bash tools/scripts/pack-app.sh pack "$APP_ID" "$APP_DIR" dist - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: diff --git a/scripts/pack-app.sh b/scripts/pack-app.sh index 782ded0..cdb4439 100644 --- a/scripts/pack-app.sh +++ b/scripts/pack-app.sh @@ -10,6 +10,11 @@ # pack-app.sh sign OUT PUB OUT/SHA256SUMS.minisig, proven against PUB # pack-app.sh --self-test hermetic: fixture repos and throwaway keys, no network # +# pack reads SHIPPED_BUNDLE=1 to accept a committed js/ bundle — the caller's `shipped-bundle` +# input, for the one kind of app that may carry one: upstream unreachable and no build manifest +# (side_menu), so the bundle ships as vendored bytes. A package.json still refuses it: an app +# that can build ships nothing committed. +# # sign reads the app key from APP_SIGNING_KEY (the .sec file's contents) and its passphrase from # APP_SIGNING_KEY_PASSWORD ("" for a key made with signify -G -n). APS_COMMON_URL overrides the # aps-common clone URL. @@ -69,8 +74,18 @@ pack() { local id="$1" dir="$2" out="$3" ts rules tracked list [ -f "$dir/appinfo/info.xml" ] || die "$dir/appinfo/info.xml not found" [ "$(info_field "$dir/appinfo/info.xml" id)" = "$id" ] || die "info.xml's is not $id" - tracked="$(git -C "$dir" ls-files -- ':(glob)js/*.js' ':(glob)js/*.mjs')" - [ -z "$tracked" ] || die "the bundle is committed (${tracked//$'\n'/ }); the release builds it — remove it from git" + # The bundle rule and its one exception. The release builds the bundle, so a committed js/ is + # refused — unless the app ships it (SHIPPED_BUNDLE=1, the workflow's `shipped-bundle` input): + # the honest state of an app whose upstream is unreachable and whose tarball is the source of + # truth, so the bundle arrives as vendored bytes. A package.json still refuses it, because a + # buildable app always builds instead. + if [ "${SHIPPED_BUNDLE:-0}" = 1 ]; then + [ ! -f "$dir/package.json" ] \ + || die "shipped-bundle refuses a package.json — the release builds a bundle it can build" + else + tracked="$(git -C "$dir" ls-files -- ':(glob)js/*.js' ':(glob)js/*.mjs')" + [ -z "$tracked" ] || die "the bundle is committed (${tracked//$'\n'/ }); the release builds it — remove it from git" + fi mkdir -p "$out" [ -z "$(ls -A "$out")" ] || die "$out is not empty" scratch @@ -224,6 +239,22 @@ if [ "${1:-}" = "--self-test" ]; then run pack demo "$tmp/unbuilt" "$tmp/o8" check "refused: a committed bundle — the release builds it" '[ "$rc" -ne 0 ]' + # shipped-bundle — the caller's declaration that the bundle ships as vendored bytes: an app + # cloned from a source nobody can reach (side_menu), with no build manifest of its own. The + # committed bundle packs and ships; a package.json still refuses it, because a buildable app + # builds instead of shipping. + git clone -q "$tmp/app" "$tmp/shipped" + build "$tmp/shipped" + ( cd "$tmp/shipped" && git rm -q package.json && git add -f js/demo-main.mjs \ + && git -c user.email=t@test.invalid -c user.name=t commit -qm "a bundle that ships as vendored bytes" ) + shipped() { rc=0; SHIPPED_BUNDLE=1 bash "$SELF" pack demo "$1" "$2" >/dev/null 2>&1 || rc=$?; } + shipped "$tmp/shipped" "$tmp/o9" + check "shipped-bundle: a committed bundle with no build packs, and it ships" \ + '[ "$rc" -eq 0 ] && [ -f "$tmp/o9/demo.tar.gz" ] && has "$tmp/o9" js/demo-main.mjs' + shipped "$tmp/app" "$tmp/o10" + check "shipped-bundle: a package.json refuses it — a buildable app never ships its bundle committed" \ + '[ "$rc" -ne 0 ] && [ ! -e "$tmp/o10/demo.tar.gz" ]' + # throwaway keys: a passphrase-less one, an encrypted one, and an unrelated public key ( umask 077 && signify -G -n -p "$tmp/plain.pub" -s "$tmp/plain.sec" -c test \ && printf 'pw\npw\n' | signify -G -p "$tmp/enc.pub" -s "$tmp/enc.sec" -c test >/dev/null \