Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/bootstrap-harness-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@smooai/config': minor
---

`bootstrapFetch` now honors a `SMOOAI_HARNESS_<KEY>` env override (e.g.
`SMOOAI_HARNESS_DATABASE_URL`, `SMOOAI_HARNESS_RLS_DATABASE_URL`) that
short-circuits the HTTP fetch entirely — the same §15 escape hatch
`packages/db` `drizzleClient.resolveDbUrl` already honors at runtime. This makes
the prod-script override work uniformly across ALL cold-start config consumers
(db-migrate and friends), not just the runtime drizzle client. Previously
`bootstrapFetch` ignored the override and fell through to `env='development'`
when no SST stage was set, silently fetching the wrong environment's value.
35 changes: 34 additions & 1 deletion src/bootstrap/__tests__/bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { resetBootstrapCacheForTests, bootstrapFetch } from '../index';
import { resetBootstrapCacheForTests, bootstrapFetch, harnessEnvKey } from '../index';

// Snapshot + restore process.env for full test isolation.
const originalEnv = { ...process.env };
Expand Down Expand Up @@ -65,6 +65,39 @@ describe('bootstrapFetch', () => {
expect(fetchMock).toHaveBeenCalledTimes(2);
});

describe('SMOOAI_HARNESS_<KEY> escape hatch', () => {
it('maps camelCase keys to SCREAMING_SNAKE harness env names', () => {
expect(harnessEnvKey('databaseUrl')).toBe('SMOOAI_HARNESS_DATABASE_URL');
expect(harnessEnvKey('rlsDatabaseUrl')).toBe('SMOOAI_HARNESS_RLS_DATABASE_URL');
expect(harnessEnvKey('voyageApiKey')).toBe('SMOOAI_HARNESS_VOYAGE_API_KEY');
});

it('short-circuits the HTTP fetch entirely when the override is set', async () => {
const fetchMock = mockFetchResponses([]); // any fetch call → "ran out of queued responses"
process.env.SMOOAI_HARNESS_DATABASE_URL = 'postgres://forced-prod/db';

const value = await bootstrapFetch('databaseUrl');
expect(value).toBe('postgres://forced-prod/db');
expect(fetchMock).not.toHaveBeenCalled();
});

it('uses the per-key env name (rlsDatabaseUrl)', async () => {
const fetchMock = mockFetchResponses([]);
process.env.SMOOAI_HARNESS_RLS_DATABASE_URL = 'postgres://forced-rls/db';

expect(await bootstrapFetch('rlsDatabaseUrl')).toBe('postgres://forced-rls/db');
expect(fetchMock).not.toHaveBeenCalled();
});

it('ignores an empty override and falls through to the HTTP fetch', async () => {
const fetchMock = mockFetchResponses([{ body: { access_token: 'TOKEN' } }, { body: { values: { databaseUrl: 'postgres://from-http' } } }]);
process.env.SMOOAI_HARNESS_DATABASE_URL = '';

expect(await bootstrapFetch('databaseUrl')).toBe('postgres://from-http');
expect(fetchMock).toHaveBeenCalledTimes(2);
});
});

it('returns undefined for a missing key without throwing', async () => {
mockFetchResponses([{ body: { access_token: 'TOKEN' } }, { body: { values: { otherKey: 'x' } } }]);

Expand Down
22 changes: 22 additions & 0 deletions src/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ function resolveEnv(environment?: string): string {
return stage;
}

/**
* Map a camelCase config key to its `SMOOAI_HARNESS_<SCREAMING_SNAKE>` env-var
* name (the documented §15 prod-script override). `databaseUrl` →
* `SMOOAI_HARNESS_DATABASE_URL`, `rlsDatabaseUrl` → `SMOOAI_HARNESS_RLS_DATABASE_URL`.
* Exported for tests.
*/
export function harnessEnvKey(key: string): string {
return `SMOOAI_HARNESS_${key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase()}`;
}

let cached: Record<string, unknown> | undefined;
let cachedEnv: string | undefined;

Expand All @@ -130,6 +140,18 @@ export function resetBootstrapCacheForTests(): void {
* NOT throw on missing keys — only on env/auth/network errors.
*/
export async function bootstrapFetch(key: string, options?: BootstrapOptions): Promise<string | undefined> {
// Local-harness escape hatch (mirrors packages/db `drizzleClient.resolveDbUrl`,
// the documented §15 prod-script override): an explicit `SMOOAI_HARNESS_<KEY>`
// env var short-circuits the HTTP fetch entirely, so prod-targeting scripts
// (db-migrate and friends) can be forced deterministically from a developer
// machine. Without this, bootstrapFetch falls through to env='development'
// (no SST stage) and silently fetches the WRONG environment's value over HTTP.
// The env name is the SCREAMING_SNAKE_CASE of the camelCase key:
// databaseUrl → SMOOAI_HARNESS_DATABASE_URL
// rlsDatabaseUrl → SMOOAI_HARNESS_RLS_DATABASE_URL
const override = process.env[harnessEnvKey(key)];
if (override !== undefined && override.length > 0) return override;

const env = resolveEnv(options?.environment);
if (cached === undefined || cachedEnv !== env) {
const creds = readCreds();
Expand Down
Loading