mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
fix(pins): CI exact pins for this platform are alternatives; the installer prefers a non-CI exact pin, else the install default among CI alternatives, else the highest verified (integration item 1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
ba887cc7ce
commit
5630ea3c34
5 files changed
+155
-18
No files matched your search
+23
-8
@@ -125,23 +125,38 @@ if [ -f "$VERDICT" ]; then
|
||||
REC_RULE=$(gstack_install_verdict_field "$VERDICT" bun_rule) REC_BUN_INSTALL=$(gstack_install_verdict_field "$VERDICT" bun_install)
|
||||
fi
|
||||
LAUNCH_DIR="" RES_BIN="" RES_VER="" RES_RULE=""
|
||||
PINS_OUT="" PINS_MODE=none RUNNER="" PIN="" PIN_SRC="" PIN_EXACT=""
|
||||
PINS_OUT="" PINS_MODE=none RUNNER="" PIN="" PIN_SRC="" PIN_EXACT="" PIN_ALTS=""
|
||||
# read_pins RUNNER — the project's pins through the doctor's parser
|
||||
# (lib/runtime-pins.ts: engines, packageManager, .tool-versions, .bun-version,
|
||||
# CI lanes) when RUNNER can run it, else the package.json / .bun-version subset
|
||||
# read with jq. Sets PINS_MODE (parser|jq|none) and PIN_EXACT (the one exact Bun pin).
|
||||
# read with jq. Sets PINS_MODE (parser|jq|none) and PIN_EXACT, the exact Bun
|
||||
# to prefer: the one non-CI exact pin (two different ones conflict), else
|
||||
# among this platform's CI exact pins (alternatives; any one satisfies CI)
|
||||
# the install version when listed, else the highest verified one, else the
|
||||
# highest one.
|
||||
read_pins() {
|
||||
RUNNER="$1" PINS_OUT="" PINS_MODE=none PIN="" PIN_SRC="" PIN_EXACT=""
|
||||
RUNNER="$1" PINS_OUT="" PINS_MODE=none PIN="" PIN_SRC="" PIN_EXACT="" PIN_ALTS=""
|
||||
[ -n "$PROJECT" ] || return 0
|
||||
[ -n "$RUNNER" ] && [ -f "$ROOT/bin/gstack-runtime-pins.ts" ] && PINS_OUT=$("$RUNNER" "$ROOT/bin/gstack-runtime-pins.ts" --project "$PROJECT" 2>/dev/null)
|
||||
if printf '%s\n' "$PINS_OUT" | grep -q '^PINS_VERDICT: '; then
|
||||
PINS_MODE=parser
|
||||
_exact=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN: bun \([^ ]*\) exact \([^ ]*\) lane=[a-z]*$/\1 \2/p')
|
||||
[ -n "$_exact" ] || return 0
|
||||
if [ "$(printf '%s\n' "$_exact" | cut -d' ' -f1 | sort -u | wc -l | tr -d ' ')" -gt 1 ]; then
|
||||
_exact=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN_EXACT_REQUIRED: bun \([^ ]*\) \([^ ]*\)$/\1 \2/p')
|
||||
if [ "$(printf '%s\n' "$_exact" | grep -c .)" -gt 1 ]; then
|
||||
stop RUNTIME_PIN_CONFLICT "Bun conflict: $PROJECT pins more than one exact Bun ($(printf '%s\n' "$_exact" | sed 's/^\([^ ]*\) \(.*\)$/\2 pins \1/' | tr '\n' ';' | sed 's/;$//; s/;/; /g'))" "make the project's exact Bun pins agree"
|
||||
fi
|
||||
PIN_EXACT=$(printf '%s\n' "$_exact" | head -1 | cut -d' ' -f1) PIN_SRC="$PROJECT/$(printf '%s\n' "$_exact" | head -1 | cut -d' ' -f2 | sed 's/:[0-9]*$//')"
|
||||
if [ -n "$_exact" ]; then
|
||||
PIN_EXACT=${_exact%% *} PIN_SRC="$PROJECT/$(printf '%s' "${_exact#* }" | sed 's/:[0-9]*$//')"
|
||||
return 0
|
||||
fi
|
||||
_alts=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN_CI_ALTERNATIVES: bun //p')
|
||||
[ -n "$_alts" ] || return 0
|
||||
case "$_alts" in *' '*) PIN_ALTS="$_alts" ;; esac
|
||||
PIN_SRC="$PROJECT/.github/workflows (CI exact pins: $_alts)"
|
||||
case " $_alts " in *" $INSTALL_VERSION "*) PIN_EXACT="$INSTALL_VERSION"; return 0 ;; esac
|
||||
for _v in $(printf '%s\n' $_alts | sort -t. -k1,1nr -k2,2nr -k3,3nr); do
|
||||
gstack_bun_archive_sha256 linux-x64 "$_v" >/dev/null && [ "$(gstack_bun_status "$_v")" = ok ] && { PIN_EXACT="$_v"; return 0; }
|
||||
done
|
||||
PIN_EXACT=$(printf '%s\n' $_alts | sort -t. -k1,1nr -k2,2nr -k3,3nr | head -1)
|
||||
return 0
|
||||
fi
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
@@ -244,7 +259,7 @@ else
|
||||
resolve || conflict "bun $WANT in $STORE does not satisfy $PROJECT's pins $(pins_text) (judged: ${_gb_judged:-none})"
|
||||
fi
|
||||
[ -n "${GSTACK_BUN_INSTALL_VERSION:-}" ] && say "install version $INSTALL_VERSION from GSTACK_BUN_INSTALL_VERSION (default $GSTACK_BUN_DEFAULT)"
|
||||
say "bun $RES_VER at $RES_BIN ($(gstack_bun_rule_text "$RES_RULE"); satisfies gstack's supported minimum $GSTACK_BUN_TESTED${PIN_EXACT:+ and the project pin $PIN_EXACT}$([ -z "$PIN_EXACT" ] && [ -n "$PIN" ] && printf ' and the project pin %s' "$PIN")$([ -z "$PIN_EXACT" ] && [ "$PINS_MODE" = parser ] && printf ' and the project pins'))"
|
||||
say "bun $RES_VER at $RES_BIN ($(gstack_bun_rule_text "$RES_RULE"); satisfies gstack's supported minimum $GSTACK_BUN_TESTED$([ -n "$PIN_ALTS" ] && printf " and the project's CI pins (alternatives: %s)" "$PIN_ALTS" || printf '%s' "${PIN_EXACT:+ and the project pin $PIN_EXACT}")$([ -z "$PIN_EXACT" ] && [ -n "$PIN" ] && printf ' and the project pin %s' "$PIN")$([ -z "$PIN_EXACT" ] && [ "$PINS_MODE" = parser ] && printf ' and the project pins'))"
|
||||
fi
|
||||
if [ -n "$RES_BIN" ] && [ "$(gstack_realpath "$(command -v bun 2>/dev/null || echo /nonexistent)" 2>/dev/null)" != "$RES_BIN" ]; then
|
||||
LAUNCH_DIR=$(dirname "$RES_BIN")
|
||||
|
||||
+16
-8
@@ -21,7 +21,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { findInstallVerdict, readInstallVerdict, sourceRoot } from '../install-verdict';
|
||||
import { resultCodeSuffix } from '../result-codes';
|
||||
import { collectPins, evaluatePins, parseVersion, satisfies } from '../runtime-pins';
|
||||
import { collectPins, evaluatePins, exactPlan, parseVersion, satisfies } from '../runtime-pins';
|
||||
import { resolveStateRoot } from '../state-root';
|
||||
|
||||
type Env = NodeJS.ProcessEnv;
|
||||
@@ -106,16 +106,24 @@ function enginesBun(file: string): string | null {
|
||||
try { const v = JSON.parse(fs.readFileSync(file, 'utf8'))?.engines?.bun; return typeof v === 'string' && v.trim() ? v.trim() : null; } catch { return null; }
|
||||
}
|
||||
|
||||
const PLATFORM_LANE: Record<string, string> = { linux: 'linux', darwin: 'macos', win32: 'windows' };
|
||||
const byVersionDesc = (a: string, b: string) => { const x = parseVersion(a)!, y = parseVersion(b)!; return y[0] - x[0] || y[1] - x[1] || y[2] - x[2]; };
|
||||
|
||||
/** The project's pins as the resolver's eligibility hook, and its one applicable exact Bun pin. */
|
||||
export function projectBunPins(repoRoot: string | null): { pinExact: string | null; accept: (version: string) => boolean } {
|
||||
/**
|
||||
* The project's pins as the resolver's eligibility hook, and the exact Bun to
|
||||
* prefer (read_pins in bin/gstack-capy-install): the one non-CI exact pin,
|
||||
* else among the CI alternatives the install version, the highest verified,
|
||||
* or the highest.
|
||||
*/
|
||||
export function projectBunPins(repoRoot: string | null, env: Env = process.env): { pinExact: string | null; accept: (version: string) => boolean } {
|
||||
const gstackPin = enginesBun(path.join(GSTACK_ROOT, 'package.json'));
|
||||
const pins = repoRoot ? collectPins(repoRoot).filter(p => p.tool === 'bun') : [];
|
||||
const here = PLATFORM_LANE[process.platform] ?? 'any';
|
||||
const exact = [...new Set(pins.filter(p => p.kind === 'exact' && (p.lane === 'any' || p.lane === here)).map(p => p.constraint.replace(/^v/, '')))];
|
||||
const plan = exactPlan(pins, 'bun');
|
||||
const alts = plan.ciAlternatives.filter(v => parseVersion(v)).sort(byVersionDesc);
|
||||
const installVersion = bunInstallVersion(env);
|
||||
const pinExact = plan.required.length ? (plan.required.length === 1 && parseVersion(plan.required[0].version) ? plan.required[0].version : null)
|
||||
: alts.find(v => v === installVersion) ?? alts.find(v => BUN_VERIFIED.includes(v) && bunStatusOk(v)) ?? alts[0] ?? null;
|
||||
return {
|
||||
pinExact: exact.length === 1 && parseVersion(exact[0]) ? exact[0] : null,
|
||||
pinExact,
|
||||
accept: (version: string) => (!gstackPin || satisfies(version, gstackPin) === true) && evaluatePins(pins, { bun: version }).failures.length === 0,
|
||||
};
|
||||
}
|
||||
@@ -157,7 +165,7 @@ export function launchRuntime(repoRoot: string, env: NodeJS.ProcessEnv): LaunchR
|
||||
} else {
|
||||
verdict = 'ok';
|
||||
}
|
||||
const { pinExact, accept } = projectBunPins(project);
|
||||
const { pinExact, accept } = projectBunPins(project, env);
|
||||
const { resolution } = resolveBun({ store: bunStore(resolveStateRoot(env)), env, pinExact, accept });
|
||||
if (resolution) return launch(resolution.bin, resolution.version, `resolved: ${resolution.rule}`, verdict, verdictLine);
|
||||
return { dir: null, version: null, env, label: `PATH (no eligible Bun in the runtime store, $BUN_INSTALL/bin, ~/.bun/bin or PATH)${verdictLine ? `; ${verdictLine}` : ''}`, verdict, verdictLine };
|
||||
|
||||
+33
-2
@@ -11,6 +11,11 @@
|
||||
* the platform lane of the job's `runs-on`, so a Windows job's pin never fails
|
||||
* a Linux run. A value this module cannot evaluate (`lts/*`, a matrix
|
||||
* expression) is reported as skipped, never silently dropped.
|
||||
*
|
||||
* CI exact pins that apply to this platform are alternatives: a repository's
|
||||
* lanes may test several Bun versions (gstack pins its minimum everywhere and
|
||||
* runs one job on its install default), so the running Bun satisfies CI when
|
||||
* it equals any one of them. CI ranges and every non-CI pin stay required.
|
||||
*/
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
@@ -210,20 +215,43 @@ export function evaluatePins(pins: Pin[], running: Running, platform: NodeJS.Pla
|
||||
const version = running[tool];
|
||||
const applicable = mine.filter(p => p.lane === 'any' || p.lane === here);
|
||||
const unmet: Pin[] = [];
|
||||
const ciExact: Pin[] = [];
|
||||
for (const pin of applicable) {
|
||||
if (pin.kind === 'unevaluable') { skipped.push(`${tool} ${pin.constraint} (${pin.source}) not evaluated`); continue; }
|
||||
if (!version) { skipped.push(`${tool} ${pin.constraint} (${pin.source}): ${tool} not found on PATH`); continue; }
|
||||
evaluated++;
|
||||
if (satisfies(version, pin.constraint) === false) unmet.push(pin);
|
||||
if (pin.field === 'CI' && pin.kind === 'exact') ciExact.push(pin);
|
||||
else if (satisfies(version, pin.constraint) === false) unmet.push(pin);
|
||||
}
|
||||
const ciMet = ciExact.some(pin => satisfies(version!, pin.constraint) !== false);
|
||||
if (!ciMet) unmet.push(...ciExact);
|
||||
if (unmet.length === 0) continue;
|
||||
const [first, ...rest] = groupPins(unmet);
|
||||
const context = rest.map(g => `${describe(g)} pins ${g.pin.constraint}`);
|
||||
failures.push(`${tool} ${version} outside ${first.pin.field} ${first.pin.constraint} (${first.pin.source}${context.length ? '; ' + context.join('; ') : ''})`);
|
||||
const alternatives = ciMet ? [] : [...new Set(ciExact.map(p => p.constraint))];
|
||||
const anyOf = alternatives.length > 1 ? `; CI exact pins are alternatives, any one of ${alternatives.join(', ')} satisfies CI` : '';
|
||||
failures.push(`${tool} ${version} outside ${first.pin.field} ${first.pin.constraint} (${first.pin.source}${context.length ? '; ' + context.join('; ') : ''}${anyOf})`);
|
||||
}
|
||||
return { pins, summary, failures, skipped, verdict: failures.length ? 'fail' : evaluated ? 'pass' : 'none' };
|
||||
}
|
||||
|
||||
export interface ExactPlan {
|
||||
/** Distinct non-CI exact pins (engines, packageManager, .tool-versions, .bun-version): all required. */
|
||||
required: Array<{ version: string; source: string }>;
|
||||
/** Distinct CI exact pins for this platform's lanes: any one satisfies CI. */
|
||||
ciAlternatives: string[];
|
||||
}
|
||||
|
||||
/** The exact pins of one tool that apply on this platform, split into required and CI alternatives. */
|
||||
export function exactPlan(pins: Pin[], tool: Tool, platform: NodeJS.Platform = process.platform): ExactPlan {
|
||||
const here = PLATFORM_LANE[platform] ?? 'any';
|
||||
const exact = pins.filter(p => p.tool === tool && p.kind === 'exact' && (p.lane === 'any' || p.lane === here));
|
||||
const strip = (v: string) => v.replace(/^v/, '');
|
||||
const required: ExactPlan['required'] = [];
|
||||
for (const p of exact.filter(p => p.field !== 'CI')) if (!required.some(r => r.version === strip(p.constraint))) required.push({ version: strip(p.constraint), source: p.source });
|
||||
return { required, ciAlternatives: [...new Set(exact.filter(p => p.field === 'CI').map(p => strip(p.constraint)))] };
|
||||
}
|
||||
|
||||
/** The wire lines bin/gstack-doctor-check.sh reads (PINS_VERDICT last). */
|
||||
export function renderPinLines(report: PinReport, platform: NodeJS.Platform = process.platform): string[] {
|
||||
const here = PLATFORM_LANE[platform] ?? 'any';
|
||||
@@ -231,6 +259,9 @@ export function renderPinLines(report: PinReport, platform: NodeJS.Platform = pr
|
||||
for (const s of report.summary) lines.push(`PIN_SUMMARY: ${s}`);
|
||||
for (const s of report.skipped) lines.push(`PIN_SKIP: ${s}`);
|
||||
for (const f of report.failures) lines.push(`PIN_FAIL: ${f}`);
|
||||
const plan = exactPlan(report.pins, 'bun', platform);
|
||||
for (const r of plan.required) lines.push(`PIN_EXACT_REQUIRED: bun ${r.version} ${r.source}`);
|
||||
if (plan.ciAlternatives.length) lines.push(`PIN_CI_ALTERNATIVES: bun ${plan.ciAlternatives.join(' ')}`);
|
||||
lines.push(`PINS_VERDICT: ${report.verdict}`);
|
||||
return lines;
|
||||
}
|
||||
@@ -437,6 +437,41 @@ describe.skipIf(process.platform !== 'linux')('gstack-capy-install: one resoluti
|
||||
expect(outside.stdout).toContain(`GSTACK_LAUNCH: PATH=${bi}/bin:$PATH `);
|
||||
});
|
||||
|
||||
// Integration item 1: gstack's own CI pins 1.4.2 on every lane and 1.4.3 on
|
||||
// the install-default job. They are alternatives, so the real checkout as
|
||||
// --project certifies with either Bun; a repo whose CI pins only 1.4.3 still
|
||||
// refuses 1.4.2.
|
||||
test('the real gstack checkout as --project with a separate global install certifies with ~/.bun 1.4.2 and with 1.4.3; nothing is downloaded', () => {
|
||||
for (const version of ['1.4.2', '1.4.3']) {
|
||||
const f = fixture({ bun: '1.3.14' });
|
||||
withPins(f);
|
||||
realBun(path.join(f.home, '.bun/bin/bun'), version);
|
||||
const r = run(f, ['--for', 'review', '--project', REPO], {}, f.home);
|
||||
expect(r.status, r.stdout + r.stderr).toBe(0);
|
||||
expect(r.stderr).not.toContain('RUNTIME_PIN_CONFLICT');
|
||||
expect(r.stdout).toContain(`bun ${version} at ${f.home}/.bun/bin/bun ($BUN_INSTALL/bin; satisfies gstack's supported minimum 1.4.2 and the project's CI pins (alternatives: 1.4.2 1.4.3))`);
|
||||
expect(f.calls().some(l => l.startsWith('curl '))).toBe(false);
|
||||
expect(verdictFor(f, r)).toMatchObject({ project: REPO, bun_version: version, bun_rule: 'BUN_INSTALL' });
|
||||
expect(run(f, ['--check', '--for', 'review', '--project', REPO], {}, f.home).status).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('CI alternatives with nothing installed: the install default is published when it is one of them; a gbrain-like repo (CI 1.4.3 only) refuses ~/.bun 1.4.2 and installs 1.4.3', () => {
|
||||
const f = fixture({ bun: '1.3.14' });
|
||||
withPins(f);
|
||||
const r = run(f, ['--for', 'review', '--project', REPO], { STUB_CURL_ZIP: zipFor(f, '1.4.3') }, f.home);
|
||||
expect(r.status, r.stdout + r.stderr).toBe(0);
|
||||
expect(r.stdout).toContain(`bun 1.4.3 at ${store(f, '1.4.3')}/bun (`);
|
||||
const g = fixture({ bun: '1.3.14' });
|
||||
withPins(g);
|
||||
realBun(path.join(g.home, '.bun/bin/bun'), '1.4.2');
|
||||
const project = ciProject(g, 'gbrain', '1.4.3');
|
||||
const gr = run(g, ['--for', 'review'], { STUB_CURL_ZIP: zipFor(g, '1.4.3') }, project);
|
||||
expect(gr.status, gr.stdout + gr.stderr).toBe(0);
|
||||
expect(gr.stdout).toContain(`judged: bun 1.4.2 at ${g.home}/.bun/bin/bun (BUN_INSTALL)`);
|
||||
expect(gr.stdout).toContain(`bun 1.4.3 at ${store(g, '1.4.3')}/bun (the project's exact pin`);
|
||||
});
|
||||
|
||||
test('no installed Bun satisfies the pins: the line names the Bun actually judged, the exact pin is published in the store, and the next --check agrees', () => {
|
||||
const f = fixture({ bun: '1.3.14' });
|
||||
withPins(f);
|
||||
|
||||
@@ -136,6 +136,54 @@ describe('collectPins and evaluatePins', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Integration item 1: CI exact pins for this platform are alternatives (any
|
||||
// one satisfies CI); non-CI pins stay required.
|
||||
describe('CI exact pins are alternatives', () => {
|
||||
const lane = (name: string, version: string, os = 'ubuntu-latest') => [` ${name}:`, ` runs-on: ${os}`, ' steps:', ' - uses: oven-sh/setup-bun@v2', ' with:', ` bun-version: ${version}`];
|
||||
const workflow = (...jobs: string[][]) => ['jobs:', ...jobs.flat(), ''].join('\n');
|
||||
|
||||
test('the real gstack checkout (CI 1.4.2 lanes plus the 1.4.3 install-default job) passes on 1.4.2 and on 1.4.3, and names the alternatives on 1.4.1', () => {
|
||||
const pins = collectPins(ROOT);
|
||||
const ci = [...new Set(pins.filter(p => p.tool === 'bun' && p.field === 'CI' && p.kind === 'exact' && p.lane !== 'windows' && p.lane !== 'macos').map(p => p.constraint))].sort();
|
||||
expect(ci).toEqual(['1.4.2', '1.4.3']);
|
||||
for (const bun of ['1.4.2', '1.4.3']) expect(evaluatePins(pins, { bun }, 'linux').failures.filter(f => f.startsWith('bun'))).toEqual([]);
|
||||
const low = evaluatePins(pins, { bun: '1.4.1' }, 'linux').failures.find(f => f.startsWith('bun '))!;
|
||||
expect(low).toStartWith('bun 1.4.1 outside engines.bun >=1.4.2 (package.json:');
|
||||
expect(low).toContain('CI exact pins are alternatives, any one of 1.4.2, 1.4.3 satisfies CI');
|
||||
expect(renderPinLines(evaluatePins(pins, { bun: '1.4.2' }, 'linux'), 'linux')).toContain('PIN_CI_ALTERNATIVES: bun 1.4.2 1.4.3');
|
||||
});
|
||||
|
||||
test('gbrain-like (every CI lane 1.4.3) still fails a local 1.4.2 exactly as before', () => {
|
||||
const dir = project({ '.github/workflows/ci.yml': workflow(lane('a', '1.4.3'), lane('b', '1.4.3')) });
|
||||
const fail = evaluatePins(collectPins(dir), { bun: '1.4.2' }, 'linux');
|
||||
expect(fail.verdict).toBe('fail');
|
||||
expect(fail.failures).toEqual(['bun 1.4.2 outside CI 1.4.3 (.github/workflows/ci.yml:7)']);
|
||||
expect(evaluatePins(collectPins(dir), { bun: '1.4.3' }, 'linux').verdict).toBe('pass');
|
||||
});
|
||||
|
||||
test('GBRA-39, gbrain-evals-like: one workflow pins 1.4.2, another 1.4.3; a local 1.4.3 used to fail "outside CI 1.4.2" and now passes', () => {
|
||||
const dir = project({
|
||||
'.github/workflows/test.yml': workflow(lane('unit', '1.4.3')),
|
||||
'.github/workflows/evals.yml': workflow(lane('evals', '1.4.2')),
|
||||
});
|
||||
for (const bun of ['1.4.2', '1.4.3']) expect(evaluatePins(collectPins(dir), { bun }, 'linux').verdict).toBe('pass');
|
||||
expect(evaluatePins(collectPins(dir), { bun: '1.4.4' }, 'linux').failures).toEqual([expect.stringContaining('any one of ')]);
|
||||
});
|
||||
|
||||
test('non-CI exact pins stay required beside CI alternatives; another platform\'s CI pin is not an alternative here', () => {
|
||||
const dir = project({
|
||||
'.bun-version': '1.4.2\n',
|
||||
'.github/workflows/ci.yml': workflow(lane('linux', '1.4.3'), lane('mac', '1.4.4', 'macos-latest')),
|
||||
});
|
||||
const r = evaluatePins(collectPins(dir), { bun: '1.4.3' }, 'linux');
|
||||
expect(r.failures).toEqual(['bun 1.4.3 outside .bun-version 1.4.2 (.bun-version:1)']);
|
||||
const lines = renderPinLines(r, 'linux');
|
||||
expect(lines).toContain('PIN_EXACT_REQUIRED: bun 1.4.2 .bun-version:1');
|
||||
expect(lines).toContain('PIN_CI_ALTERNATIVES: bun 1.4.3');
|
||||
expect(evaluatePins(collectPins(dir), { bun: '1.4.4' }, 'linux').verdict).toBe('fail');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bin/gstack-runtime-pins.ts', () => {
|
||||
const run = (args: string[]) => spawnSync(process.execPath, [path.join(ROOT, 'bin/gstack-runtime-pins.ts'), ...args], { encoding: 'utf8', timeout: 30_000 });
|
||||
|
||||
|
||||
Reference in new issue
Block a user