mirror of https://github.com/garrytan/gstack.git
feat: add shared call-time isConductor() helper
Single source of truth for Conductor host detection in TS consumers (CONDUCTOR_WORKSPACE_PATH / CONDUCTOR_PORT). Reads the passed env at call time, not a module-load snapshot, so unit tests can pin the env inline without Bun --preload (esm-hoist-breaks-env-pin-bootstrap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a5833c413f
commit
f5f7e32505
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Conductor host detection — single source of truth for TS consumers.
|
||||||
|
*
|
||||||
|
* Conductor (the Mac app that runs many coding agents in parallel) sets
|
||||||
|
* CONDUCTOR_WORKSPACE_PATH / CONDUCTOR_PORT in the session env. The same two
|
||||||
|
* vars are what `bin/gstack-session-kind` keys on (it collapses Conductor into
|
||||||
|
* `interactive`, so it can't be reused to distinguish Conductor specifically —
|
||||||
|
* hence this dedicated helper).
|
||||||
|
*
|
||||||
|
* IMPORTANT: detection is a CALL-TIME read of the passed-in env (default
|
||||||
|
* `process.env`), never a module-load-time snapshot. ESM hoists static imports
|
||||||
|
* above any in-file `process.env.X = ...`, so a load-time read can't be pinned
|
||||||
|
* by a test without Bun --preload. Reading at call time lets unit tests set
|
||||||
|
* `process.env.CONDUCTOR_WORKSPACE_PATH` inline before invoking. See the
|
||||||
|
* `esm-hoist-breaks-env-pin-bootstrap` learning.
|
||||||
|
*/
|
||||||
|
export function isConductor(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||||
|
return !!(env.CONDUCTOR_WORKSPACE_PATH || env.CONDUCTOR_PORT);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { isConductor } from '../lib/is-conductor';
|
||||||
|
|
||||||
|
describe('is-conductor', () => {
|
||||||
|
test('true when CONDUCTOR_WORKSPACE_PATH is set', () => {
|
||||||
|
expect(isConductor({ CONDUCTOR_WORKSPACE_PATH: '/Users/x/conductor/ws' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('true when CONDUCTOR_PORT is set', () => {
|
||||||
|
expect(isConductor({ CONDUCTOR_PORT: '55070' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('true when both are set', () => {
|
||||||
|
expect(isConductor({ CONDUCTOR_WORKSPACE_PATH: '/ws', CONDUCTOR_PORT: '55070' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('false when neither is set', () => {
|
||||||
|
expect(isConductor({ HOME: '/Users/x', PATH: '/usr/bin' })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('false on an empty env', () => {
|
||||||
|
expect(isConductor({})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('false when the vars are present but empty (Codex #1 hardening — empty != set)', () => {
|
||||||
|
expect(isConductor({ CONDUCTOR_WORKSPACE_PATH: '', CONDUCTOR_PORT: '' })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reads the passed env at call time, not a module-load snapshot', () => {
|
||||||
|
const env: NodeJS.ProcessEnv = {};
|
||||||
|
expect(isConductor(env)).toBe(false);
|
||||||
|
// mutate AFTER the first call — a call-time read must see the new value
|
||||||
|
env.CONDUCTOR_PORT = '55070';
|
||||||
|
expect(isConductor(env)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults to process.env when no arg is passed', () => {
|
||||||
|
const saved = process.env.CONDUCTOR_PORT;
|
||||||
|
try {
|
||||||
|
process.env.CONDUCTOR_PORT = '12345';
|
||||||
|
expect(isConductor()).toBe(true);
|
||||||
|
delete process.env.CONDUCTOR_PORT;
|
||||||
|
// CONDUCTOR_WORKSPACE_PATH may be set in a real Conductor session; guard the assertion
|
||||||
|
if (!process.env.CONDUCTOR_WORKSPACE_PATH) expect(isConductor()).toBe(false);
|
||||||
|
} finally {
|
||||||
|
if (saved === undefined) delete process.env.CONDUCTOR_PORT;
|
||||||
|
else process.env.CONDUCTOR_PORT = saved;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue