diff --git a/npm-packages/convex/src/cli/lib/deployment.test.ts b/npm-packages/convex/src/cli/lib/deployment.test.ts index 24f13c750..dff7b777e 100644 --- a/npm-packages/convex/src/cli/lib/deployment.test.ts +++ b/npm-packages/convex/src/cli/lib/deployment.test.ts @@ -1,5 +1,14 @@ -import { test, expect } from "vitest"; -import { changesToEnvVarFile, changesToGitIgnore } from "./deployment.js"; +import { test, expect, vi, beforeEach, afterEach } from "vitest"; +import { changesToEnvVarFile, changesToGitIgnore, writeDeploymentEnvVar } from "./deployment.js"; +import { Context } from "../../bundler/context.js"; + +vi.mock("./envvars.js", async (importOriginal) => { + const actual = (await importOriginal()) as any; + return { + ...actual, + gitIgnoreEnvVarFile: vi.fn().mockResolvedValue(false), + }; +}); const DEPLOYMENT = { team: "snoops", @@ -83,3 +92,40 @@ test("git ignore changes", () => { "!.env.local\n.env.local\n", ); }); + +const mockContext = { + fs: { + exists: vi.fn(), + readUtf8File: vi.fn(), + writeUtf8File: vi.fn(), + }, +} as unknown as Context; + +const originalProcessEnv = process.env; + +beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...originalProcessEnv }; +}); + +afterEach(() => { + process.env = originalProcessEnv; +}); + +test("writeDeploymentEnvVar skips file creation when CONVEX_DEPLOYMENT exists with correct value", async () => { + process.env.CONVEX_DEPLOYMENT = "prod:tall-bar"; + + const result = await writeDeploymentEnvVar( + mockContext, + "prod", + DEPLOYMENT, + null, + ); + + expect(result).toEqual({ + wroteToGitIgnore: false, + changedDeploymentEnvVar: false, + }); + + expect(mockContext.fs.writeUtf8File).not.toHaveBeenCalled(); +}); diff --git a/npm-packages/convex/src/cli/lib/deployment.ts b/npm-packages/convex/src/cli/lib/deployment.ts index 4bb41299c..3f83c4acc 100644 --- a/npm-packages/convex/src/cli/lib/deployment.ts +++ b/npm-packages/convex/src/cli/lib/deployment.ts @@ -49,6 +49,17 @@ export async function writeDeploymentEnvVar( }, existingValue: string | null, ): Promise<{ wroteToGitIgnore: boolean; changedDeploymentEnvVar: boolean }> { + const deploymentEnvVarValue = + deploymentType + ":" + deployment.deploymentName; + + // Check if the correct value already exists in process.env + if (process.env[CONVEX_DEPLOYMENT_ENV_VAR_NAME] === deploymentEnvVarValue) { + return { + wroteToGitIgnore: false, + changedDeploymentEnvVar: false, + }; + } + const existingFile = ctx.fs.exists(ENV_VAR_FILE_PATH) ? ctx.fs.readUtf8File(ENV_VAR_FILE_PATH) : null; @@ -57,8 +68,6 @@ export async function writeDeploymentEnvVar( deploymentType, deployment, ); - const deploymentEnvVarValue = - deploymentType + ":" + deployment.deploymentName; if (changedFile !== null) { ctx.fs.writeUtf8File(ENV_VAR_FILE_PATH, changedFile); diff --git a/npm-packages/convex/src/cli/lib/envvars.test.ts b/npm-packages/convex/src/cli/lib/envvars.test.ts new file mode 100644 index 000000000..5bf53f6ed --- /dev/null +++ b/npm-packages/convex/src/cli/lib/envvars.test.ts @@ -0,0 +1,66 @@ +import {afterEach, beforeEach, expect, test, vi} from "vitest"; +import {writeConvexUrlToEnvFile} from "./envvars.js"; +import {Context} from "../../bundler/context.js"; + +vi.mock("./utils/utils.js", () => ({ + loadPackageJson: vi.fn().mockResolvedValue({name: "test-project"}), + ENV_VAR_FILE_PATH: ".env.local", +})); + +const mockContext = { + fs: { + exists: vi.fn() as any, + readUtf8File: vi.fn() as any, + writeUtf8File: vi.fn() as any, + }, + crash: vi.fn() as any, +} as unknown as Context; + +const originalProcessEnv = process.env; + +beforeEach(() => { + vi.clearAllMocks(); + process.env = {...originalProcessEnv}; +}); + +afterEach(() => { + process.env = originalProcessEnv; +}); + +test("writeConvexUrlToEnvFile process.env behavior", async () => { + // Test core functionality: skip file creation when env var exists with correct value + process.env.CONVEX_URL = "https://test.convex.cloud"; + (mockContext.fs.exists as any).mockReturnValue(false); + + let result = await writeConvexUrlToEnvFile(mockContext, "https://test.convex.cloud"); + expect(result).toBeNull(); // Should skip file creation + expect(mockContext.fs.writeUtf8File).not.toHaveBeenCalled(); + + // Test different value - should create file + vi.clearAllMocks(); + process.env.CONVEX_URL = "https://different.convex.cloud"; + + result = await writeConvexUrlToEnvFile(mockContext, "https://test.convex.cloud"); + expect(result).not.toBeNull(); // Should create file + expect(mockContext.fs.writeUtf8File).toHaveBeenCalled(); + + // Test missing env var - should create file + vi.clearAllMocks(); + delete process.env.CONVEX_URL; + + result = await writeConvexUrlToEnvFile(mockContext, "https://test.convex.cloud"); + expect(result).not.toBeNull(); // Should create file + expect(mockContext.fs.writeUtf8File).toHaveBeenCalled(); + + // Empty string should trigger file creation + vi.clearAllMocks(); + process.env.CONVEX_URL = ""; + result = await writeConvexUrlToEnvFile(mockContext, "https://test.convex.cloud"); + expect(result).not.toBeNull(); + + // Whitespace should trigger file creation + vi.clearAllMocks(); + process.env.CONVEX_URL = " "; + result = await writeConvexUrlToEnvFile(mockContext, "https://test.convex.cloud"); + expect(result).not.toBeNull(); +}); diff --git a/npm-packages/convex/src/cli/lib/envvars.ts b/npm-packages/convex/src/cli/lib/envvars.ts index 1d1f628a0..f64cfe280 100644 --- a/npm-packages/convex/src/cli/lib/envvars.ts +++ b/npm-packages/convex/src/cli/lib/envvars.ts @@ -29,6 +29,16 @@ export async function writeConvexUrlToEnvFile( ctx: Context, value: string, ): Promise { + // Check if any of the expected environment variables already has the correct value + const { envVar: suggestedEnvVar } = await suggestedEnvVarName(ctx); + + // Check process.env for the suggested env var or any of the expected names + for (const envVarName of [suggestedEnvVar, ...EXPECTED_NAMES]) { + if (process.env[envVarName] === value) { + return null; + } + } + const writeConfig = await envVarWriteConfig(ctx, value); if (writeConfig === null) {