Skip to content

Commit 6937c1c

Browse files
committed
Merge branch 'claude/fix-cli-version-tag' into 'main'
fix(cli): use CLI version instead of stale .env tag See merge request postgres-ai/postgres_ai!153
2 parents 278237c + 4fbc5c2 commit 6937c1c

3 files changed

Lines changed: 121 additions & 10 deletions

File tree

cli/bin/postgres-ai.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,25 +1204,25 @@ mon
12041204
// Update .env with custom tag if provided
12051205
const envFile = path.resolve(projectDir, ".env");
12061206

1207-
// Build .env content, preserving important existing values
1208-
// Read existing .env first to preserve CI/custom settings
1209-
let existingTag: string | null = null;
1207+
// Build .env content, preserving important existing values (registry, password)
1208+
// Note: PGAI_TAG is intentionally NOT preserved - the CLI version should always match Docker images
12101209
let existingRegistry: string | null = null;
12111210
let existingPassword: string | null = null;
12121211

12131212
if (fs.existsSync(envFile)) {
12141213
const existingEnv = fs.readFileSync(envFile, "utf8");
1215-
// Extract existing values
1216-
const tagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
1217-
if (tagMatch) existingTag = tagMatch[1].trim();
1214+
// Extract existing values (except tag - always use CLI version)
12181215
const registryMatch = existingEnv.match(/^PGAI_REGISTRY=(.+)$/m);
12191216
if (registryMatch) existingRegistry = registryMatch[1].trim();
12201217
const pwdMatch = existingEnv.match(/^GF_SECURITY_ADMIN_PASSWORD=(.+)$/m);
12211218
if (pwdMatch) existingPassword = pwdMatch[1].trim();
12221219
}
12231220

1224-
// Priority: CLI --tag flag > PGAI_TAG env var > existing .env > package version
1225-
const imageTag = opts.tag || process.env.PGAI_TAG || existingTag || pkg.version;
1221+
// Priority: CLI --tag flag > package version
1222+
// Note: We intentionally do NOT use process.env.PGAI_TAG here because Bun auto-loads .env files,
1223+
// which would cause stale .env values to override the CLI version. The CLI version should always
1224+
// match the Docker images. Users can override with --tag if needed.
1225+
const imageTag = opts.tag || pkg.version;
12261226

12271227
const envLines: string[] = [`PGAI_TAG=${imageTag}`];
12281228
if (existingRegistry) {

cli/test/init.integration.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
399399
}
400400
});
401401

402+
// 60s timeout for PostgreSQL startup + multiple SQL queries in slow CI
402403
test("explain_generic validates input and prevents SQL injection", async () => {
403404
pg = await createTempPostgres();
404405

@@ -495,5 +496,5 @@ describe.skipIf(skipTests)("integration: prepare-db", () => {
495496
} finally {
496497
await pg.cleanup();
497498
}
498-
});
499+
}, { timeout: 60000 });
499500
});

cli/test/init.test.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { describe, test, expect, beforeAll } from "bun:test";
1+
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
22
import { resolve } from "path";
3+
import * as fs from "fs";
4+
import * as os from "os";
35

46
// Import from source directly since we're using Bun
57
import * as init from "../lib/init";
@@ -415,3 +417,111 @@ describe("CLI commands", () => {
415417
expect(r.stderr).toMatch(/Cannot use --api-key with --demo mode/);
416418
});
417419
});
420+
421+
describe("imageTag priority behavior", () => {
422+
// Tests for the imageTag priority: --tag flag > PGAI_TAG env var > pkg.version
423+
// This verifies the fix that prevents stale .env PGAI_TAG from being used
424+
425+
let tempDir: string;
426+
427+
beforeAll(() => {
428+
tempDir = fs.mkdtempSync(resolve(os.tmpdir(), "pgai-test-"));
429+
});
430+
431+
afterAll(() => {
432+
if (tempDir && fs.existsSync(tempDir)) {
433+
fs.rmSync(tempDir, { recursive: true, force: true });
434+
}
435+
});
436+
437+
test("stale .env PGAI_TAG is NOT used - CLI version takes precedence", () => {
438+
// Create a stale .env with an old tag value
439+
const testDir = resolve(tempDir, "stale-tag-test");
440+
fs.mkdirSync(testDir, { recursive: true });
441+
fs.writeFileSync(resolve(testDir, ".env"), "PGAI_TAG=beta\n");
442+
// Create minimal docker-compose.yml so resolvePaths() finds it
443+
fs.writeFileSync(resolve(testDir, "docker-compose.yml"), "version: '3'\nservices: {}\n");
444+
445+
// Run from the test directory (so resolvePaths finds docker-compose.yml)
446+
const cliPath = resolve(import.meta.dir, "..", "bin", "postgres-ai.ts");
447+
const bunBin = typeof process.execPath === "string" && process.execPath.length > 0 ? process.execPath : "bun";
448+
const result = Bun.spawnSync([bunBin, cliPath, "mon", "local-install", "--db-url", "postgresql://u:p@h:5432/d", "--yes"], {
449+
env: { ...process.env, PGAI_TAG: undefined },
450+
cwd: testDir,
451+
});
452+
453+
// Read the .env that was written
454+
const envContent = fs.readFileSync(resolve(testDir, ".env"), "utf8");
455+
456+
// The .env should NOT contain the stale "beta" tag - it should use pkg.version
457+
expect(envContent).not.toMatch(/PGAI_TAG=beta/);
458+
// It should contain the CLI version (0.0.0-dev.0 in dev)
459+
expect(envContent).toMatch(/PGAI_TAG=\d+\.\d+\.\d+|PGAI_TAG=0\.0\.0-dev/);
460+
});
461+
462+
test("--tag flag takes priority over pkg.version", () => {
463+
const testDir = resolve(tempDir, "tag-flag-test");
464+
fs.mkdirSync(testDir, { recursive: true });
465+
fs.writeFileSync(resolve(testDir, "docker-compose.yml"), "version: '3'\nservices: {}\n");
466+
467+
const cliPath = resolve(import.meta.dir, "..", "bin", "postgres-ai.ts");
468+
const bunBin = typeof process.execPath === "string" && process.execPath.length > 0 ? process.execPath : "bun";
469+
const result = Bun.spawnSync([bunBin, cliPath, "mon", "local-install", "--tag", "v1.2.3-custom", "--db-url", "postgresql://u:p@h:5432/d", "--yes"], {
470+
env: { ...process.env, PGAI_TAG: undefined },
471+
cwd: testDir,
472+
});
473+
474+
const envContent = fs.readFileSync(resolve(testDir, ".env"), "utf8");
475+
expect(envContent).toMatch(/PGAI_TAG=v1\.2\.3-custom/);
476+
477+
// Verify stdout confirms the tag being used
478+
const stdout = new TextDecoder().decode(result.stdout);
479+
expect(stdout).toMatch(/Using image tag: v1\.2\.3-custom/);
480+
});
481+
482+
test("PGAI_TAG env var is intentionally ignored (Bun auto-loads .env)", () => {
483+
// Note: We do NOT use process.env.PGAI_TAG because Bun auto-loads .env files,
484+
// which would cause stale .env values to pollute the environment.
485+
// Users should use --tag flag to override, not env vars.
486+
const testDir = resolve(tempDir, "env-var-ignored-test");
487+
fs.mkdirSync(testDir, { recursive: true });
488+
fs.writeFileSync(resolve(testDir, "docker-compose.yml"), "version: '3'\nservices: {}\n");
489+
490+
const cliPath = resolve(import.meta.dir, "..", "bin", "postgres-ai.ts");
491+
const bunBin = typeof process.execPath === "string" && process.execPath.length > 0 ? process.execPath : "bun";
492+
const result = Bun.spawnSync([bunBin, cliPath, "mon", "local-install", "--db-url", "postgresql://u:p@h:5432/d", "--yes"], {
493+
env: { ...process.env, PGAI_TAG: "v2.0.0-from-env" },
494+
cwd: testDir,
495+
});
496+
497+
const envContent = fs.readFileSync(resolve(testDir, ".env"), "utf8");
498+
// PGAI_TAG env var should be IGNORED - uses pkg.version instead
499+
expect(envContent).not.toMatch(/PGAI_TAG=v2\.0\.0-from-env/);
500+
expect(envContent).toMatch(/PGAI_TAG=\d+\.\d+\.\d+|PGAI_TAG=0\.0\.0-dev/);
501+
});
502+
503+
test("existing registry and password are preserved while tag is updated", () => {
504+
const testDir = resolve(tempDir, "preserve-test");
505+
fs.mkdirSync(testDir, { recursive: true });
506+
// Create .env with stale tag but valid registry and password
507+
fs.writeFileSync(resolve(testDir, ".env"),
508+
"PGAI_TAG=stale-tag\nPGAI_REGISTRY=my.registry.com\nGF_SECURITY_ADMIN_PASSWORD=secret123\n");
509+
fs.writeFileSync(resolve(testDir, "docker-compose.yml"), "version: '3'\nservices: {}\n");
510+
511+
const cliPath = resolve(import.meta.dir, "..", "bin", "postgres-ai.ts");
512+
const bunBin = typeof process.execPath === "string" && process.execPath.length > 0 ? process.execPath : "bun";
513+
const result = Bun.spawnSync([bunBin, cliPath, "mon", "local-install", "--db-url", "postgresql://u:p@h:5432/d", "--yes"], {
514+
env: { ...process.env, PGAI_TAG: undefined },
515+
cwd: testDir,
516+
});
517+
518+
const envContent = fs.readFileSync(resolve(testDir, ".env"), "utf8");
519+
520+
// Tag should be updated (not stale-tag)
521+
expect(envContent).not.toMatch(/PGAI_TAG=stale-tag/);
522+
523+
// But registry and password should be preserved
524+
expect(envContent).toMatch(/PGAI_REGISTRY=my\.registry\.com/);
525+
expect(envContent).toMatch(/GF_SECURITY_ADMIN_PASSWORD=secret123/);
526+
});
527+
});

0 commit comments

Comments
 (0)