mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-17 18:32:19 +02:00
fix: resolve GBRAIN_HOME with gbrain's parent-dir semantics (#2521)
gstack treated GBRAIN_HOME as the config directory; gbrain's configDir() treats it as the PARENT and always appends `.gbrain` itself (the contract is explicit in gbrain's source: GBRAIN_HOME=/tmp/x → /tmp/x/.gbrain/ config.json). With GBRAIN_HOME set, gstack classified engine status from a file gbrain never reads — the probe's two halves (file checks vs the spawned `gbrain sources list`) looked at DIFFERENT installs, so any resulting status was arbitrary: missing-config/broken-config against healthy installs, or a thin-client marker gstack saw that gbrain itself reported as "No brain configured". New shared resolver `gbrainConfigDir()` in lib/gbrain-exec.ts is the single source of truth. All seven gstack sites route through the contract: - lib/gbrain-local-status.ts gbrainConfigPath (the classifier's file half) - bin/gstack-gbrain-detect GBRAIN_CONFIG + readRemoteMcpUrl - lib/gbrain-exec.ts buildGbrainEnv (the probe's DATABASE_URL seed — fixing only the classifier would have left the split-brain in the spawn half, flagged by the reporter) - lib/gbrain-guards.ts gbrainHome (clones-dir + autopilot-lock paths) - lib/gstack-memory-helpers.ts gbrainConfigPath (engine-tier fallback) - bin/gstack-gbrain-install pre-doctor config check (shell) Unit tests cover GBRAIN_HOME set (config found at $GBRAIN_HOME/.gbrain), the old flat layout explicitly NOT read (both classifier and buildGbrainEnv), and unset (~/.gbrain unchanged). Existing fixtures that encoded the deviant flat layout are updated to gbrain's contract. Root-cause analysis by @d-danielsun in #2521. Deviation from the 3-site plan spec: the same deviant resolution existed in four more sites (buildGbrainEnv, gbrain-guards, memory-helpers, gbrain-install); fixing only three would have left gstack disagreeing with itself as well as with gbrain, so the whole class moved to the shared resolver in one change. Fixes #2521 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ce4a7bbb7e
commit
5854d122d3
@@ -43,18 +43,17 @@ import {
|
|||||||
resolveGbrainBin,
|
resolveGbrainBin,
|
||||||
readGbrainVersion,
|
readGbrainVersion,
|
||||||
} from "../lib/gbrain-local-status";
|
} from "../lib/gbrain-local-status";
|
||||||
import { isTransactionModePooler } from "../lib/gbrain-exec";
|
import { gbrainConfigDir, isTransactionModePooler } from "../lib/gbrain-exec";
|
||||||
|
|
||||||
const STATE_DIR = process.env.GSTACK_HOME || join(userHome(), ".gstack");
|
const STATE_DIR = process.env.GSTACK_HOME || join(userHome(), ".gstack");
|
||||||
const SCRIPT_DIR = __dirname;
|
const SCRIPT_DIR = __dirname;
|
||||||
const CONFIG_BIN = join(SCRIPT_DIR, "gstack-config");
|
const CONFIG_BIN = join(SCRIPT_DIR, "gstack-config");
|
||||||
// Honors GBRAIN_HOME — must stay consistent with lib/gbrain-local-status's
|
// Honors GBRAIN_HOME with gbrain's own configDir() semantics (#2521:
|
||||||
// config resolution, or the detect JSON reports gbrain_local_status "ok"
|
// GBRAIN_HOME is a parent dir, `.gbrain` is appended) — must stay consistent
|
||||||
// alongside gbrain_config_exists false for relocated-home users.
|
// with lib/gbrain-local-status's config resolution, or the detect JSON
|
||||||
const GBRAIN_CONFIG = join(
|
// reports gbrain_local_status "ok" alongside gbrain_config_exists false for
|
||||||
process.env.GBRAIN_HOME || join(userHome(), ".gbrain"),
|
// relocated-home users. Both route through gbrainConfigDir.
|
||||||
"config.json",
|
const GBRAIN_CONFIG = join(gbrainConfigDir(), "config.json");
|
||||||
);
|
|
||||||
const CLAUDE_JSON = join(userHome(), ".claude.json");
|
const CLAUDE_JSON = join(userHome(), ".claude.json");
|
||||||
|
|
||||||
function userHome(): string {
|
function userHome(): string {
|
||||||
@@ -232,8 +231,7 @@ function detectMcpMode(): "local-stdio" | "remote-http" | "none" {
|
|||||||
|
|
||||||
/** remote_mcp.mcp_url from gbrain's own config (thin-client marker, #2051). */
|
/** remote_mcp.mcp_url from gbrain's own config (thin-client marker, #2051). */
|
||||||
function readRemoteMcpUrl(): string {
|
function readRemoteMcpUrl(): string {
|
||||||
const gbrainHome = process.env.GBRAIN_HOME || join(userHome(), ".gbrain");
|
const cfg = tryReadJSON(join(gbrainConfigDir(), "config.json")) as
|
||||||
const cfg = tryReadJSON(join(gbrainHome, "config.json")) as
|
|
||||||
| { remote_mcp?: { mcp_url?: string } }
|
| { remote_mcp?: { mcp_url?: string } }
|
||||||
| null;
|
| null;
|
||||||
return cfg?.remote_mcp?.mcp_url || "";
|
return cfg?.remote_mcp?.mcp_url || "";
|
||||||
|
|||||||
@@ -235,7 +235,14 @@ fi
|
|||||||
# a hard gate so a broken gbrain is caught at setup, not at data-loss time.
|
# a hard gate so a broken gbrain is caught at setup, not at data-loss time.
|
||||||
# Pre-init installs skip this (config not written yet); the full
|
# Pre-init installs skip this (config not written yet); the full
|
||||||
# `/sync-gbrain --dry-run` self-test runs from /setup-gbrain after `gbrain init`.
|
# `/sync-gbrain --dry-run` self-test runs from /setup-gbrain after `gbrain init`.
|
||||||
_GBRAIN_HOME_CHECK="${GBRAIN_HOME:-$HOME/.gbrain}"
|
# #2521: GBRAIN_HOME is a PARENT dir per gbrain's configDir() contract —
|
||||||
|
# gbrain appends `.gbrain` itself, so the config lives at
|
||||||
|
# $GBRAIN_HOME/.gbrain/config.json (or ~/.gbrain/config.json when unset).
|
||||||
|
if [ -n "${GBRAIN_HOME:-}" ]; then
|
||||||
|
_GBRAIN_HOME_CHECK="$GBRAIN_HOME/.gbrain"
|
||||||
|
else
|
||||||
|
_GBRAIN_HOME_CHECK="$HOME/.gbrain"
|
||||||
|
fi
|
||||||
if [ -f "$_GBRAIN_HOME_CHECK/config.json" ]; then
|
if [ -f "$_GBRAIN_HOME_CHECK/config.json" ]; then
|
||||||
if ! gbrain doctor --fast >/dev/null 2>&1; then
|
if ! gbrain doctor --fast >/dev/null 2>&1; then
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
|
|||||||
+22
-9
@@ -21,10 +21,12 @@
|
|||||||
* spawn. This is the central bug the helper exists to prevent
|
* spawn. This is the central bug the helper exists to prevent
|
||||||
* regressing on.
|
* regressing on.
|
||||||
*
|
*
|
||||||
* 3. **`GBRAIN_HOME` honored consistently.** Other gstack helpers
|
* 3. **`GBRAIN_HOME` honored consistently — with gbrain's own semantics
|
||||||
* (`detectEngineTier`) already honor `GBRAIN_HOME`. `buildGbrainEnv`
|
* (#2521).** gbrain's configDir() treats `GBRAIN_HOME` as a PARENT
|
||||||
* reads from `${GBRAIN_HOME:-$HOME/.gbrain}/config.json` so all
|
* directory and always appends `.gbrain` itself (GBRAIN_HOME=/tmp/x
|
||||||
* gstack-side gbrain calls agree on which config file matters.
|
* → /tmp/x/.gbrain/config.json). Every gstack-side read goes through
|
||||||
|
* `gbrainConfigDir()` below so gstack and gbrain agree on which
|
||||||
|
* config file matters.
|
||||||
*
|
*
|
||||||
* **Escape hatch:** `GSTACK_RESPECT_ENV_DATABASE_URL=1` returns the
|
* **Escape hatch:** `GSTACK_RESPECT_ENV_DATABASE_URL=1` returns the
|
||||||
* caller's env unchanged. Use only when the brain intentionally lives in
|
* caller's env unchanged. Use only when the brain intentionally lives in
|
||||||
@@ -75,8 +77,21 @@ export function isTransactionModePooler(url: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an env dict with DATABASE_URL seeded from
|
* gbrain's config directory, matching gbrain's own configDir() contract
|
||||||
* `${GBRAIN_HOME:-$HOME/.gbrain}/config.json`. Returns the base env
|
* (#2521): `GBRAIN_HOME` is a PARENT directory — gbrain always appends
|
||||||
|
* `.gbrain` itself, so GBRAIN_HOME=/tmp/x reads /tmp/x/.gbrain/config.json.
|
||||||
|
* Unset → ~/.gbrain. Every gstack-side gbrain-config read MUST resolve
|
||||||
|
* through this helper, or gstack classifies engine status from a file
|
||||||
|
* gbrain never reads.
|
||||||
|
*/
|
||||||
|
export function gbrainConfigDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||||
|
if (env.GBRAIN_HOME) return join(env.GBRAIN_HOME, ".gbrain");
|
||||||
|
return join(env.HOME || homedir(), ".gbrain");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an env dict with DATABASE_URL seeded from gbrain's config.json
|
||||||
|
* (resolved via `gbrainConfigDir`). Returns the base env
|
||||||
* unchanged when:
|
* unchanged when:
|
||||||
* - `GSTACK_RESPECT_ENV_DATABASE_URL=1` (intentional opt-out),
|
* - `GSTACK_RESPECT_ENV_DATABASE_URL=1` (intentional opt-out),
|
||||||
* - the config file is missing or unparseable,
|
* - the config file is missing or unparseable,
|
||||||
@@ -98,9 +113,7 @@ export function buildGbrainEnv(opts: BuildGbrainEnvOptions = {}): NodeJS.Process
|
|||||||
const out: NodeJS.ProcessEnv = { ...baseEnv };
|
const out: NodeJS.ProcessEnv = { ...baseEnv };
|
||||||
if (baseEnv.GSTACK_RESPECT_ENV_DATABASE_URL === "1") return out;
|
if (baseEnv.GSTACK_RESPECT_ENV_DATABASE_URL === "1") return out;
|
||||||
|
|
||||||
const homeBase = baseEnv.HOME || homedir();
|
const configPath = join(gbrainConfigDir(baseEnv), "config.json");
|
||||||
const gbrainHome = baseEnv.GBRAIN_HOME || join(homeBase, ".gbrain");
|
|
||||||
const configPath = join(gbrainHome, "config.json");
|
|
||||||
if (!existsSync(configPath)) return out;
|
if (!existsSync(configPath)) return out;
|
||||||
|
|
||||||
let cfg: GbrainConfig = {};
|
let cfg: GbrainConfig = {};
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ import { execGbrainJson, execGbrainText, NEEDS_SHELL_ON_WINDOWS } from "./gbrain
|
|||||||
import { parseSourcesList, type GbrainSourceRow } from "./gbrain-sources";
|
import { parseSourcesList, type GbrainSourceRow } from "./gbrain-sources";
|
||||||
|
|
||||||
export function gbrainHome(env: NodeJS.ProcessEnv = process.env): string {
|
export function gbrainHome(env: NodeJS.ProcessEnv = process.env): string {
|
||||||
return env.GBRAIN_HOME || join(homedir(), ".gbrain");
|
// #2521: GBRAIN_HOME is a PARENT dir per gbrain's configDir() contract —
|
||||||
|
// gbrain appends `.gbrain` itself, so gstack must too.
|
||||||
|
return env.GBRAIN_HOME ? join(env.GBRAIN_HOME, ".gbrain") : join(homedir(), ".gbrain");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import {
|
|||||||
import { atomicWriteSync } from "./fs-atomic";
|
import { atomicWriteSync } from "./fs-atomic";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
import { buildGbrainEnv, NEEDS_SHELL_ON_WINDOWS } from "./gbrain-exec";
|
import { buildGbrainEnv, gbrainConfigDir, NEEDS_SHELL_ON_WINDOWS } from "./gbrain-exec";
|
||||||
|
|
||||||
export type LocalEngineStatus =
|
export type LocalEngineStatus =
|
||||||
| "ok"
|
| "ok"
|
||||||
@@ -122,11 +122,14 @@ export function cacheFilePath(): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Honors GBRAIN_HOME (codex D11) — same resolution as buildGbrainEnv. */
|
/**
|
||||||
|
* Honors GBRAIN_HOME (codex D11) with gbrain's own configDir() semantics
|
||||||
|
* (#2521): GBRAIN_HOME is a parent dir, `.gbrain` is appended. Same
|
||||||
|
* resolution as buildGbrainEnv — both route through gbrainConfigDir.
|
||||||
|
*/
|
||||||
function gbrainConfigPath(env?: NodeJS.ProcessEnv): string {
|
function gbrainConfigPath(env?: NodeJS.ProcessEnv): string {
|
||||||
const e = env ?? process.env;
|
const e = env ?? process.env;
|
||||||
const gbrainHome = e.GBRAIN_HOME || join(userHome(e), ".gbrain");
|
return join(gbrainConfigDir(e), "config.json");
|
||||||
return join(gbrainHome, "config.json");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
||||||
import { appendJsonl } from "./jsonl-store";
|
import { appendJsonl } from "./jsonl-store";
|
||||||
|
import { gbrainConfigDir } from "./gbrain-exec";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
import { execFileSync } from "child_process";
|
import { execFileSync } from "child_process";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
@@ -256,12 +257,13 @@ export function detectEngineTier(): EngineDetect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns gbrain's config.json path, honoring GBRAIN_HOME env var with a
|
// Returns gbrain's config.json path, honoring GBRAIN_HOME env var with a
|
||||||
// fallback to ~/.gbrain. gbrain >=0.25 dropped the top-level `engine` field
|
// fallback to ~/.gbrain. Resolution matches gbrain's own configDir()
|
||||||
|
// contract (#2521): GBRAIN_HOME is a parent dir, `.gbrain` is appended.
|
||||||
|
// gbrain >=0.25 dropped the top-level `engine` field
|
||||||
// from doctor output, so this file is the only reliable source for engine
|
// from doctor output, so this file is the only reliable source for engine
|
||||||
// detection on that version. See #1415.
|
// detection on that version. See #1415.
|
||||||
function gbrainConfigPath(): string {
|
function gbrainConfigPath(): string {
|
||||||
const root = process.env.GBRAIN_HOME || join(homedir(), ".gbrain");
|
return join(gbrainConfigDir(process.env), "config.json");
|
||||||
return join(root, "config.json");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Best-effort JSONL append to ~/.gstack/.gbrain-errors.jsonl. Never throws.
|
// Best-effort JSONL append to ~/.gstack/.gbrain-errors.jsonl. Never throws.
|
||||||
|
|||||||
@@ -77,17 +77,34 @@ describe("buildGbrainEnv", () => {
|
|||||||
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
expect(result.DATABASE_URL).toBe("postgresql://app/db");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("honors GBRAIN_HOME when set (config aligned with detectEngineTier)", () => {
|
it("honors GBRAIN_HOME when set, with gbrain's parent-dir semantics (#2521)", () => {
|
||||||
// Move the config to an alternate dir; set GBRAIN_HOME to point at it.
|
// Move the config to an alternate dir; set GBRAIN_HOME to point at its
|
||||||
|
// PARENT — gbrain's configDir() appends `.gbrain` itself, so
|
||||||
|
// GBRAIN_HOME=/x reads /x/.gbrain/config.json.
|
||||||
const altGbrainHome = join(home, "alt-gbrain");
|
const altGbrainHome = join(home, "alt-gbrain");
|
||||||
mkdirSync(altGbrainHome, { recursive: true });
|
mkdirSync(join(altGbrainHome, ".gbrain"), { recursive: true });
|
||||||
writeFileSync(join(altGbrainHome, "config.json"), JSON.stringify({ database_url: "postgresql://alt/db" }));
|
writeFileSync(
|
||||||
|
join(altGbrainHome, ".gbrain", "config.json"),
|
||||||
|
JSON.stringify({ database_url: "postgresql://alt/db" }),
|
||||||
|
);
|
||||||
// No file at the default ~/.gbrain location.
|
// No file at the default ~/.gbrain location.
|
||||||
const baseEnv = { HOME: home, GBRAIN_HOME: altGbrainHome };
|
const baseEnv = { HOME: home, GBRAIN_HOME: altGbrainHome };
|
||||||
const result = buildGbrainEnv({ baseEnv });
|
const result = buildGbrainEnv({ baseEnv });
|
||||||
expect(result.DATABASE_URL).toBe("postgresql://alt/db");
|
expect(result.DATABASE_URL).toBe("postgresql://alt/db");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores a config at $GBRAIN_HOME/config.json — gbrain never reads that file (#2521)", () => {
|
||||||
|
const altGbrainHome = join(home, "alt-gbrain-flat");
|
||||||
|
mkdirSync(altGbrainHome, { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
join(altGbrainHome, "config.json"),
|
||||||
|
JSON.stringify({ database_url: "postgresql://alt/db" }),
|
||||||
|
);
|
||||||
|
const baseEnv = { HOME: home, GBRAIN_HOME: altGbrainHome };
|
||||||
|
const result = buildGbrainEnv({ baseEnv });
|
||||||
|
expect(result.DATABASE_URL).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("returns a fresh env object — never the caller's env by identity", () => {
|
it("returns a fresh env object — never the caller's env by identity", () => {
|
||||||
// Codex review #11: object-identity equality lets later mutation of the
|
// Codex review #11: object-identity equality lets later mutation of the
|
||||||
// returned env leak back into the caller's view. The helper MUST clone.
|
// returned env leak back into the caller's view. The helper MUST clone.
|
||||||
|
|||||||
@@ -953,7 +953,9 @@ exit 1
|
|||||||
...process.env,
|
...process.env,
|
||||||
GSTACK_HOME: home,
|
GSTACK_HOME: home,
|
||||||
HOME: home,
|
HOME: home,
|
||||||
GBRAIN_HOME: gbrainHome,
|
// #2521: GBRAIN_HOME is the PARENT of .gbrain per gbrain's configDir()
|
||||||
|
// contract — pointing it at `home` resolves to home/.gbrain/config.json.
|
||||||
|
GBRAIN_HOME: home,
|
||||||
PATH: `${shimDir}:${process.env.PATH}`,
|
PATH: `${shimDir}:${process.env.PATH}`,
|
||||||
// Dead loopback port → the Sourcebot probe fails fast + deterministically
|
// Dead loopback port → the Sourcebot probe fails fast + deterministically
|
||||||
// (connection refused) instead of poking whatever operator dev server
|
// (connection refused) instead of poking whatever operator dev server
|
||||||
|
|||||||
@@ -269,20 +269,46 @@ describe("lib/gbrain-local-status — status classification", () => {
|
|||||||
expect(localEngineStatus({ noCache: true })).toBe("timeout");
|
expect(localEngineStatus({ noCache: true })).toBe("timeout");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("honors GBRAIN_HOME for config detection (codex D11)", () => {
|
it("honors GBRAIN_HOME for config detection (codex D11) with gbrain's parent-dir semantics (#2521)", () => {
|
||||||
// Config lives ONLY at the alternate GBRAIN_HOME; ~/.gbrain has none.
|
// Config lives ONLY under the alternate GBRAIN_HOME; ~/.gbrain has none.
|
||||||
|
// gbrain's configDir() treats GBRAIN_HOME as a PARENT dir and appends
|
||||||
|
// `.gbrain` itself: GBRAIN_HOME=/x → /x/.gbrain/config.json.
|
||||||
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: false });
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: false });
|
||||||
restoreEnv = applyEnv(env);
|
restoreEnv = applyEnv(env);
|
||||||
const altHome = join(env.tmp, "alt-gbrain");
|
const altHome = join(env.tmp, "alt-gbrain");
|
||||||
|
mkdirSync(join(altHome, ".gbrain"), { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
join(altHome, ".gbrain", "config.json"),
|
||||||
|
JSON.stringify({ engine: "pglite", database_url: "pglite:///fake" }),
|
||||||
|
);
|
||||||
|
// Without GBRAIN_HOME: misclassified as missing-config.
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("missing-config");
|
||||||
|
// With GBRAIN_HOME: the relocated config is found at $GBRAIN_HOME/.gbrain.
|
||||||
|
process.env.GBRAIN_HOME = altHome;
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT read $GBRAIN_HOME/config.json directly — gbrain never reads that file (#2521)", () => {
|
||||||
|
// A config placed at gstack's OLD (wrong) resolution must be invisible:
|
||||||
|
// gbrain itself would report "No brain configured" for this layout, so
|
||||||
|
// gstack classifying "ok" from it is the #2521 split-brain.
|
||||||
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: false });
|
||||||
|
restoreEnv = applyEnv(env);
|
||||||
|
const altHome = join(env.tmp, "alt-gbrain-flat");
|
||||||
mkdirSync(altHome, { recursive: true });
|
mkdirSync(altHome, { recursive: true });
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(altHome, "config.json"),
|
join(altHome, "config.json"),
|
||||||
JSON.stringify({ engine: "pglite", database_url: "pglite:///fake" }),
|
JSON.stringify({ engine: "pglite", database_url: "pglite:///fake" }),
|
||||||
);
|
);
|
||||||
// Without GBRAIN_HOME: misclassified as missing-config.
|
|
||||||
expect(localEngineStatus({ noCache: true })).toBe("missing-config");
|
|
||||||
// With GBRAIN_HOME: the relocated config is found.
|
|
||||||
process.env.GBRAIN_HOME = altHome;
|
process.env.GBRAIN_HOME = altHome;
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("missing-config");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with GBRAIN_HOME unset, config resolution stays at ~/.gbrain (#2521 unset half)", () => {
|
||||||
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: true });
|
||||||
|
restoreEnv = applyEnv(env);
|
||||||
|
// applyEnv deletes GBRAIN_HOME; config was written at $HOME/.gbrain.
|
||||||
|
expect(process.env.GBRAIN_HOME).toBeUndefined();
|
||||||
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
expect(localEngineStatus({ noCache: true })).toBe("ok");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -462,10 +462,13 @@ describe("detectEngineTier", () => {
|
|||||||
// Regression test for #1415: gbrain >=0.25 doctor output dropped the
|
// Regression test for #1415: gbrain >=0.25 doctor output dropped the
|
||||||
// top-level `engine` field. The detect path must fall back to config.json.
|
// top-level `engine` field. The detect path must fall back to config.json.
|
||||||
// We force the doctor call to fail (PATH stripped of gbrain) and write a
|
// We force the doctor call to fail (PATH stripped of gbrain) and write a
|
||||||
// synthetic config to GBRAIN_HOME so the fallback path is deterministic.
|
// synthetic config under GBRAIN_HOME so the fallback path is
|
||||||
|
// deterministic. Per gbrain's configDir() contract (#2521), GBRAIN_HOME
|
||||||
|
// is a parent dir — the config lives at $GBRAIN_HOME/.gbrain/config.json.
|
||||||
process.env.PATH = "/nonexistent-no-gbrain-here";
|
process.env.PATH = "/nonexistent-no-gbrain-here";
|
||||||
|
mkdirSync(join(testGbrainHome, ".gbrain"), { recursive: true });
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(testGbrainHome, "config.json"),
|
join(testGbrainHome, ".gbrain", "config.json"),
|
||||||
JSON.stringify({ engine: "postgres", database_url: "postgresql://test/example" }),
|
JSON.stringify({ engine: "postgres", database_url: "postgresql://test/example" }),
|
||||||
"utf-8"
|
"utf-8"
|
||||||
);
|
);
|
||||||
@@ -500,8 +503,9 @@ exit 0
|
|||||||
{ mode: 0o755 }
|
{ mode: 0o755 }
|
||||||
);
|
);
|
||||||
process.env.PATH = `${binDir}:${process.env.PATH || ""}`;
|
process.env.PATH = `${binDir}:${process.env.PATH || ""}`;
|
||||||
|
mkdirSync(join(testGbrainHome, ".gbrain"), { recursive: true });
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(testGbrainHome, "config.json"),
|
join(testGbrainHome, ".gbrain", "config.json"),
|
||||||
JSON.stringify({ engine: "pglite" }),
|
JSON.stringify({ engine: "pglite" }),
|
||||||
"utf-8"
|
"utf-8"
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user