mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* fix(migrate): v0_13_0 shells out to `gbrain` shim, not `process.execPath` On bun-installed trees, process.execPath is the bun runtime itself. `bun extract links ...` got reinterpreted as `bun run extract` and crashed the upgrade mid-Phase B. The canonical shim on PATH already wraps the right runtime+entrypoint; trust it. Regression-guarded by test/migrations-v0_13_0.test.ts which greps the source for `process.execPath` and `bun` invocations. This was Bug 1 of tonight's v0.13 → v0.14 upgrade-night postmortem. * fix(autopilot): resolveGbrainCliPath prefers shim, never returns .ts argv[1] check used to short-circuit on /cli.ts, so bun-source installs got a .ts path back. spawn() then failed EACCES because TypeScript source isn't executable, and autopilot silently lost its worker. Reordered probes: which gbrain (shim) first, then compiled execPath, then argv[1] only if it ends in /gbrain. Deleted the .ts branch entirely — no valid case exists. Rewrote the existing test that enshrined the buggy .ts return. Critical regression guard: resolver MUST NEVER return a .ts path across any combination of argv[1] + execPath + shim availability. This was Bug 4 of tonight's v0.13 → v0.14 upgrade-night postmortem. * chore: bump version and changelog (v0.15.3) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(db): resolvePrepare() helper for PgBouncer transaction-mode pools Adds port-6543 auto-detect with a 4-level precedence chain: GBRAIN_PREPARE env var → ?prepare= URL param → port auto-detect → default. Wires into the module-singleton connect() so the main CLI path no longer hits "prepared statement does not exist" against Supabase transaction pooler. Returns boolean | undefined; undefined means omit the option and let postgres.js default (true) stand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(postgres-engine): honor resolvePrepare in worker-instance pool Without this, \`gbrain jobs work\` against a Supabase pooler URL hits "prepared statement does not exist" under load even after the module singleton was fixed in db.ts. Community PR #270 (@notjbg) caught this second path that #284 had missed. Reuses the shared helper, no regex duplication. Co-Authored-By: Jonah Berg <jonah.berg.g@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): pgbouncer_prepare check URL-only check (no DB roundtrip) that reads the configured URL via loadConfig() and flags the footgun: port 6543 with prepared statements still enabled. Warns with the exact env override (GBRAIN_PREPARE=false) and URL-query alternative (?prepare=false). Works for both the module singleton and worker-instance engines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: resolvePrepare precedence matrix + postgres-engine wiring guard - test/resolve-prepare.test.ts: 11 cases covering env override, URL query param, port auto-detect, malformed URLs, postgres:// scheme, URL-encoded credentials. Uses bun:test — #284's original vitest file would never have run in this project. - test/postgres-engine.test.ts: new source-level grep case asserting the worker-pool connect() branch calls db.resolvePrepare(url) and includes a typeof prepare === 'boolean' check. Mirrors the existing SET LOCAL regression guard. If anyone rips out the wiring, the build fails before shipping starts dropping rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.15.4) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Jonah Berg <jonah.berg.g@gmail.com>
80 lines
3.6 KiB
TypeScript
80 lines
3.6 KiB
TypeScript
/**
|
|
* Tests for the v0.13.0 frontmatter relationship indexing migration.
|
|
*
|
|
* Iron rule (regression guard for Bug 1, v0.14.0 upgrade night): phase
|
|
* handlers must shell out to the bare string `gbrain`, NOT to
|
|
* `process.execPath`. On bun-installed trees execPath is the bun runtime;
|
|
* `bun extract ...` gets interpreted as `bun run extract` and the upgrade
|
|
* crashes mid-migration. The canonical shim on PATH is the right target.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const SRC_PATH = join(__dirname, '..', 'src', 'commands', 'migrations', 'v0_13_0.ts');
|
|
|
|
describe('v0.13.0 — Frontmatter relationship indexing migration', () => {
|
|
test('registered in the TS migration registry', async () => {
|
|
const { migrations, getMigration } = await import('../src/commands/migrations/index.ts');
|
|
const versions = migrations.map(m => m.version);
|
|
expect(versions).toContain('0.13.0');
|
|
const m = getMigration('0.13.0');
|
|
expect(m).not.toBeNull();
|
|
expect(typeof m!.orchestrator).toBe('function');
|
|
});
|
|
|
|
test('phase functions exported for unit testing', async () => {
|
|
const { __testing } = await import('../src/commands/migrations/v0_13_0.ts');
|
|
expect(typeof __testing.phaseASchema).toBe('function');
|
|
expect(typeof __testing.phaseBBackfill).toBe('function');
|
|
expect(typeof __testing.phaseCVerify).toBe('function');
|
|
});
|
|
|
|
test('dry-run skips all side-effect phases', async () => {
|
|
const { v0_13_0 } = await import('../src/commands/migrations/v0_13_0.ts');
|
|
const result = await v0_13_0.orchestrator({ yes: true, dryRun: true });
|
|
expect(result.version).toBe('0.13.0');
|
|
for (const phase of result.phases) {
|
|
expect(phase.status).toBe('skipped');
|
|
expect(phase.detail).toBe('dry-run');
|
|
}
|
|
});
|
|
|
|
// ── Regression guards (Bug 1) ──────────────────────────────
|
|
|
|
test('source does NOT reference process.execPath (Bug 1 regression)', () => {
|
|
// process.execPath on a bun install is the bun runtime itself, so
|
|
// `${process.execPath} extract` becomes `bun run extract` and dies.
|
|
// See v0.14.0 upgrade-night postmortem.
|
|
const src = readFileSync(SRC_PATH, 'utf-8');
|
|
expect(src).not.toContain('process.execPath');
|
|
});
|
|
|
|
test('source does NOT build commands from a GBRAIN constant (Bug 1 regression)', () => {
|
|
// Earlier revisions used `const GBRAIN = process.execPath` and built
|
|
// commands as `${GBRAIN} extract ...`. The constant was the vector.
|
|
const src = readFileSync(SRC_PATH, 'utf-8');
|
|
expect(src).not.toMatch(/const\s+GBRAIN\s*=/);
|
|
expect(src).not.toMatch(/\$\{GBRAIN\}/);
|
|
});
|
|
|
|
test('phase commands invoke bare `gbrain` shell-out (Bug 1 fix)', () => {
|
|
const src = readFileSync(SRC_PATH, 'utf-8');
|
|
// All three phases shell out to bare `gbrain` so the canonical shim
|
|
// on PATH wins. This is the shape v0_12_0 has always used.
|
|
expect(src).toContain("execSync('gbrain init --migrate-only'");
|
|
expect(src).toContain("execSync('gbrain extract links --source db --include-frontmatter'");
|
|
expect(src).toContain("execSync('gbrain call get_stats'");
|
|
});
|
|
|
|
test('phase commands never reference `bun` or `.ts` paths (Bug 1 regression)', () => {
|
|
// Belt-and-suspenders: even if someone reintroduces a runtime-path
|
|
// helper, they must not produce `bun ...` or `<path>.ts` as the spawn
|
|
// target.
|
|
const src = readFileSync(SRC_PATH, 'utf-8');
|
|
expect(src).not.toMatch(/execSync\([^)]*\bbun\b/);
|
|
expect(src).not.toMatch(/execSync\([^)]*\.ts/);
|
|
});
|
|
});
|