From d44dbb2f41a206df0eb1664e2b4e5bf170293909 Mon Sep 17 00:00:00 2001 From: Matsu Date: Tue, 18 Aug 2026 10:49:52 +0300 Subject: [PATCH] ci: Surface errors on bump-versions and similiar scripts (#36479) --- .github/scripts/bump-versions.mjs | 16 +++++----- .github/scripts/detect-new-packages.mjs | 13 ++------ .github/scripts/ensure-provenance-fields.mjs | 13 ++------ .github/scripts/pnpm-utils.mjs | 33 +++++++++++++++----- .github/workflows/release-create-pr.yml | 10 +++++- 5 files changed, 46 insertions(+), 39 deletions(-) diff --git a/.github/scripts/bump-versions.mjs b/.github/scripts/bump-versions.mjs index 09dcf499015..867f523d042 100644 --- a/.github/scripts/bump-versions.mjs +++ b/.github/scripts/bump-versions.mjs @@ -5,6 +5,7 @@ import { resolve } from 'path'; import child_process from 'child_process'; import { promisify } from 'util'; import assert from 'assert'; +import { getMonorepoProjects } from './pnpm-utils.mjs'; const exec = promisify(child_process.exec); @@ -184,13 +185,7 @@ async function bumpVersions() { releaseType === 'experimental' ? (await exec('git rev-parse --short=8 HEAD')).stdout.trim() : undefined; - const packages = JSON.parse( - ( - await exec( - `pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`, - ) - ).stdout, - ); + const packages = await getMonorepoProjects(); /** @type {Record} */ const packageMap = {}; @@ -290,5 +285,10 @@ async function bumpVersions() { // only run when executed directly, not when imported by tests if (import.meta.url === `file://${process.argv[1]}`) { - bumpVersions(); + try { + await bumpVersions(); + } catch (error) { + console.error(error); + process.exit(1); + } } diff --git a/.github/scripts/detect-new-packages.mjs b/.github/scripts/detect-new-packages.mjs index 6cb551e2fc0..45d7d218537 100644 --- a/.github/scripts/detect-new-packages.mjs +++ b/.github/scripts/detect-new-packages.mjs @@ -14,19 +14,10 @@ * 1 – One or more public packages have never been published */ -import child_process from 'child_process'; -import { promisify } from 'util'; import { writeGithubOutput } from './github-helpers.mjs'; +import { getMonorepoProjects } from './pnpm-utils.mjs'; -const exec = promisify(child_process.exec); - -const packages = JSON.parse( - ( - await exec( - `pnpm ls -r --only-projects --json | jq -r '[.[] | { name:.name, private: .private}]'`, - ) - ).stdout, -); +const packages = await getMonorepoProjects(); const newPackages = []; diff --git a/.github/scripts/ensure-provenance-fields.mjs b/.github/scripts/ensure-provenance-fields.mjs index f6362a238a7..303e13283f2 100644 --- a/.github/scripts/ensure-provenance-fields.mjs +++ b/.github/scripts/ensure-provenance-fields.mjs @@ -1,21 +1,12 @@ import { writeFile, readFile, copyFile } from 'fs/promises'; import { resolve, dirname } from 'path'; -import child_process from 'child_process'; import { fileURLToPath } from 'url'; -import { promisify } from 'util'; - -const exec = promisify(child_process.exec); +import { getMonorepoProjects } from './pnpm-utils.mjs'; const commonFiles = ['LICENSE.md', 'LICENSE_EE.md']; const baseDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); -const packages = JSON.parse( - ( - await exec( - `pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`, - ) - ).stdout, -); +const packages = await getMonorepoProjects(); for (let { name, path, version, private: isPrivate } of packages) { if (isPrivate) continue; diff --git a/.github/scripts/pnpm-utils.mjs b/.github/scripts/pnpm-utils.mjs index 95ca5bd389a..6c90359a61f 100644 --- a/.github/scripts/pnpm-utils.mjs +++ b/.github/scripts/pnpm-utils.mjs @@ -1,7 +1,7 @@ import child_process from 'child_process'; import { promisify } from 'node:util'; -const exec = promisify(child_process.exec); +const execFile = promisify(child_process.execFile); /** * @typedef PnpmPackage @@ -15,11 +15,28 @@ const exec = promisify(child_process.exec); * @returns { Promise } * */ export async function getMonorepoProjects() { - return JSON.parse( - ( - await exec( - `pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`, - ) - ).stdout, - ); + let stdout; + + // No shell and no `| jq`: a pipeline hides pnpm's exit code (jq exits 0 on + // empty input), which turns a failing pnpm into an empty package list. + try { + ({ stdout } = await execFile('pnpm', ['ls', '-r', '--only-projects', '--json'], { + // Unprojected output is ~600KB and grows with the workspace. + maxBuffer: 64 * 1024 * 1024, + })); + } catch (error) { + const details = error.stderr?.trim() || error.message; + throw new Error(`\`pnpm ls -r --only-projects --json\` failed: ${details}`); + } + + if (!stdout.trim()) { + throw new Error('`pnpm ls -r --only-projects --json` produced no output'); + } + + return JSON.parse(stdout).map(({ name, version, path, private: isPrivate }) => ({ + name, + version, + path, + private: Boolean(isPrivate), + })); } diff --git a/.github/workflows/release-create-pr.yml b/.github/workflows/release-create-pr.yml index 539912dcebf..994070ff5fd 100644 --- a/.github/workflows/release-create-pr.yml +++ b/.github/workflows/release-create-pr.yml @@ -89,8 +89,16 @@ jobs: corepack enable - name: Bump package versions + # Assign first: a command substitution nested in another command's + # arguments (e.g. `echo "X=$(node …)"`) discards the script's exit code. + shell: bash run: | - echo "NEXT_RELEASE=$(node .github/scripts/bump-versions.mjs)" >> "$GITHUB_ENV" + NEXT_RELEASE="$(node .github/scripts/bump-versions.mjs)" + if [ -z "$NEXT_RELEASE" ]; then + echo "::error::bump-versions.mjs produced no version" + exit 1 + fi + echo "NEXT_RELEASE=$NEXT_RELEASE" >> "$GITHUB_ENV" env: RELEASE_TYPE: ${{ inputs.release-type }}