mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-04 04:10:50 +02:00
fix(ci): one bun version everywhere + drift tripwire
Lanes disagreed four ways: 1.3.13 (free-tests, windows, Dockerfile.ci), latest (quality-gate, make-pdf-gate), unpinned (skill-docs, version-gate — setup-bun installs latest), 1.3.10 (.gitlab-ci.yml). Different Bun versions change the runner output shapes the strict classifiers regex-match, spawn semantics, and shell parsing — a lane on a different Bun tests a different product; Dockerfile.ci's own comment records this class biting once already (silent 1.3.13/1.3.14 drift). All surfaces pinned to 1.3.13; test/bun-version-drift.test.ts scans every workflow setup-bun stanza + Dockerfile.ci + .gitlab-ci.yml and fails on any mismatch or unpinned stanza. skill-docs also gains --frozen-lockfile (was bare bun install). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e2904be7a4
commit
ace904d40a
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: 1.3.13
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: 1.3.13
|
||||||
|
|
||||||
- name: Install frozen dependencies
|
- name: Install frozen dependencies
|
||||||
run: bun install --frozen-lockfile --ignore-scripts
|
run: bun install --frozen-lockfile --ignore-scripts
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
- run: bun install
|
with:
|
||||||
|
bun-version: 1.3.13
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
# One generation pass for ALL 10 hosts. gen-skill-docs --host all
|
# One generation pass for ALL 10 hosts. gen-skill-docs --host all
|
||||||
# hard-fails on any per-host generation error (scripts/gen-skill-docs.ts
|
# hard-fails on any per-host generation error (scripts/gen-skill-docs.ts
|
||||||
# aggregates failures and exits non-zero), so every host is gated on
|
# aggregates failures and exits non-zero), so every host is gated on
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: 1.3.13
|
||||||
|
|
||||||
- name: Read versions
|
- name: Read versions
|
||||||
id: versions
|
id: versions
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ stages:
|
|||||||
- check
|
- check
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
BUN_VERSION: "1.3.10"
|
BUN_VERSION: "1.3.13"
|
||||||
|
|
||||||
.setup-bun: &setup-bun
|
.setup-bun: &setup-bun
|
||||||
- apt-get update -qq && apt-get install -qq -y curl jq git
|
- apt-get update -qq && apt-get install -qq -y curl jq git
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* One Bun version across every CI surface.
|
||||||
|
*
|
||||||
|
* The drift class this pins: Dockerfile.ci's comment records that the old
|
||||||
|
* `| BUN_VERSION=x.y.z bash` form silently installed latest on every image
|
||||||
|
* rebuild (observed 1.3.13/1.3.14 drift vs the 1.3.10 devs ran locally),
|
||||||
|
* and before 2026-08-29 the lanes disagreed four ways (1.3.13 / latest /
|
||||||
|
* unpinned / 1.3.10). Different Bun versions change test-runner OUTPUT
|
||||||
|
* SHAPES the strict classifiers regex-match, spawn semantics, and shell
|
||||||
|
* parsing — a lane on a different Bun is testing a different product.
|
||||||
|
*
|
||||||
|
* Bumping Bun: change every surface in one commit; this test names each one.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
|
||||||
|
const ROOT = path.resolve(__dirname, '..');
|
||||||
|
const WORKFLOWS_DIR = path.join(ROOT, '.github', 'workflows');
|
||||||
|
|
||||||
|
interface Pin {
|
||||||
|
surface: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectPins(): Pin[] {
|
||||||
|
const pins: Pin[] = [];
|
||||||
|
|
||||||
|
for (const name of fs.readdirSync(WORKFLOWS_DIR).sort()) {
|
||||||
|
if (!/\.ya?ml$/.test(name)) continue;
|
||||||
|
const source = fs.readFileSync(path.join(WORKFLOWS_DIR, name), 'utf-8');
|
||||||
|
const lines = source.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (!/uses:\s*oven-sh\/setup-bun@/.test(lines[i])) continue;
|
||||||
|
// A pinned stanza is `with:` + `bun-version: <v>` within the next few
|
||||||
|
// lines; an unpinned setup-bun is itself drift (installs latest).
|
||||||
|
const window = lines.slice(i + 1, i + 4).join('\n');
|
||||||
|
const m = window.match(/bun-version:\s*["']?([\w.]+)["']?/);
|
||||||
|
pins.push({
|
||||||
|
surface: `${name}:${i + 1}`,
|
||||||
|
version: m ? m[1] : '<unpinned setup-bun — installs latest>',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dockerfile = fs.readFileSync(
|
||||||
|
path.join(ROOT, '.github', 'docker', 'Dockerfile.ci'), 'utf-8');
|
||||||
|
const dockerPin = dockerfile.match(/bash -s ["']?bun-v([\w.]+)["']?/);
|
||||||
|
pins.push({
|
||||||
|
surface: 'Dockerfile.ci',
|
||||||
|
version: dockerPin ? dockerPin[1] : '<no bun-vX.Y.Z positional arg>',
|
||||||
|
});
|
||||||
|
|
||||||
|
const gitlab = fs.readFileSync(path.join(ROOT, '.gitlab-ci.yml'), 'utf-8');
|
||||||
|
const gitlabPin = gitlab.match(/BUN_VERSION:\s*["']?([\w.]+)["']?/);
|
||||||
|
pins.push({
|
||||||
|
surface: '.gitlab-ci.yml',
|
||||||
|
version: gitlabPin ? gitlabPin[1] : '<no BUN_VERSION>',
|
||||||
|
});
|
||||||
|
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('bun version pins', () => {
|
||||||
|
test('every CI surface pins the same bun version', () => {
|
||||||
|
const pins = collectPins();
|
||||||
|
// Sanity: the scan found the known surfaces (a regex rot that finds
|
||||||
|
// nothing must fail loudly, not vacuously pass).
|
||||||
|
expect(pins.length).toBeGreaterThanOrEqual(6);
|
||||||
|
|
||||||
|
const versions = [...new Set(pins.map((p) => p.version))];
|
||||||
|
const detail = pins.map((p) => `${p.surface} → ${p.version}`).join('\n');
|
||||||
|
expect(versions, `bun version drift across CI surfaces:\n${detail}`).toHaveLength(1);
|
||||||
|
expect(versions[0]).toMatch(/^\d+\.\d+\.\d+$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user