From a67d44cde54fbf2943f62a9e01051f54166540ee Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 26 Sep 2026 01:40:39 +0800 Subject: [PATCH] fix: pin Debian Python 3.10 base for full release matrix --- .github/workflows/build-docker-image.yml | 17 +++++- docker/Dockerfile.debian310 | 6 +- test/back/npm-publication.test.cjs | 70 ++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 test/back/npm-publication.test.cjs diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 88297261..511a2cab 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -474,7 +474,7 @@ jobs: publish: if: ${{ github.repository == 'whyour/qinglong' && github.ref == 'refs/heads/master' }} - needs: [build-alpine, build-debian] + needs: [build-alpine, build-debian, build-alpine310, build-debian310] runs-on: ubuntu-latest permissions: contents: read @@ -504,4 +504,17 @@ jobs: package-manager-cache: false - name: Publish npm package with OIDC - run: npm publish --access public --registry=https://registry.npmjs.org + shell: bash + run: | + set -euo pipefail + name=$(node -p 'require("./package.json").name') + version=$(node -p 'require("./package.json").version') + if npm view "$name@$version" version --json --registry=https://registry.npmjs.org > "$RUNNER_TEMP/npm-version.json" 2> "$RUNNER_TEMP/npm-version-error.log"; then + jq -e --arg version "$version" '. == $version' "$RUNNER_TEMP/npm-version.json" > /dev/null + echo "::notice::$name@$version is already published; skipping npm publication for this image rebuild." + elif jq -e '.error.code == "E404"' "$RUNNER_TEMP/npm-version.json" > /dev/null; then + npm publish --access public --registry=https://registry.npmjs.org + else + echo '::error::Could not check the published npm version; refusing to publish.' + exit 1 + fi diff --git a/docker/Dockerfile.debian310 b/docker/Dockerfile.debian310 index 907da2a1..688d7dde 100644 --- a/docker/Dockerfile.debian310 +++ b/docker/Dockerfile.debian310 @@ -2,7 +2,9 @@ # full Debian build matrix, including arm/v7, ppc64le, and s390x. FROM node:20-bookworm-slim AS nodebuilder -FROM python:3.10-slim-bookworm AS builder +# Keep the full release matrix, including s390x, on the same immutable base. +# The floating 3.10-slim-bookworm tag no longer includes s390x. +FROM python:3.10.19-slim-bookworm@sha256:23f63358922e79a794f71be8f3723c84e5ccca9638af3f74456dc73d6184499e AS builder COPY package.json .npmrc pnpm-lock.yaml /tmp/build/ COPY --from=nodebuilder /usr/local/bin/node /usr/local/bin/ COPY --from=nodebuilder /usr/local/lib/node_modules/. /usr/local/lib/node_modules/ @@ -14,7 +16,7 @@ RUN set -x && \ cd /tmp/build && \ pnpm install --prod --frozen-lockfile -FROM python:3.10-slim-bookworm +FROM python:3.10.19-slim-bookworm@sha256:23f63358922e79a794f71be8f3723c84e5ccca9638af3f74456dc73d6184499e ARG QL_MAINTAINER="whyour" LABEL maintainer="${QL_MAINTAINER}" diff --git a/test/back/npm-publication.test.cjs b/test/back/npm-publication.test.cjs new file mode 100644 index 00000000..9a816354 --- /dev/null +++ b/test/back/npm-publication.test.cjs @@ -0,0 +1,70 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); +const yaml = require('js-yaml'); +const workflow = yaml.load( + fs.readFileSync('.github/workflows/build-docker-image.yml', 'utf8'), +); +const publication = workflow.jobs.publish.steps.find( + (step) => step.name === 'Publish npm package with OIDC', +).run; + +test('npm publication waits for every release image, including Python 3.10', () => { + for (const name of [ + 'build-alpine', + 'build-debian', + 'build-alpine310', + 'build-debian310', + ]) + assert.ok(workflow.jobs.publish.needs.includes(name), name); +}); + +for (const [scenario, expectedStatus, published] of [ + ['existing', 0, false], + ['missing', 0, true], + ['unauthorized', 1, false], + ['network', 1, false], + ['unexpected-version', 1, false], +]) { + test(`npm publication handles ${scenario} without publishing an unverified version`, (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-npm-publication-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const bin = path.join(root, 'bin'); + fs.mkdirSync(bin); + fs.writeFileSync( + path.join(root, 'package.json'), + JSON.stringify({ name: '@fixture/qinglong', version: '2.22.0' }), + ); + fs.writeFileSync( + path.join(bin, 'npm'), + `#!/usr/bin/env node +const fs = require('node:fs'); +if (process.argv[2] === 'publish') { fs.writeFileSync('published', 'yes'); process.exit(0); } +if (process.argv[2] !== 'view') process.exit(99); +switch (process.env.QL_PUBLISH_SCENARIO) { +case 'existing': console.log(JSON.stringify('2.22.0')); break; +case 'unexpected-version': console.log(JSON.stringify('2.21.0')); break; +case 'missing': console.log(JSON.stringify({error:{code:'E404'}})); process.exit(1); +case 'unauthorized': console.log(JSON.stringify({error:{code:'E401'}})); process.exit(1); +case 'network': console.log('upstream unavailable'); process.exit(1); +} +`, + { mode: 0o755 }, + ); + const result = spawnSync('bash', ['-c', publication], { + cwd: root, + encoding: 'utf8', + env: { + ...process.env, + PATH: `${bin}${path.delimiter}${process.env.PATH}`, + RUNNER_TEMP: root, + QL_PUBLISH_SCENARIO: scenario, + }, + }); + assert.equal(result.status, expectedStatus, result.stdout + result.stderr); + assert.equal(fs.existsSync(path.join(root, 'published')), published); + }); +}