diff --git a/dev/src/cli/cli.ts b/dev/src/cli/cli.ts index f227c979f..dca7a0bdb 100644 --- a/dev/src/cli/cli.ts +++ b/dev/src/cli/cli.ts @@ -14,7 +14,6 @@ import { setLogLevel as setAdkCoreLogLevel, } from '@google/adk'; import {Argument, Command, Option} from 'commander'; -import dotenv from 'dotenv'; import * as path from 'path'; import {runIntegrationTests} from '../integration/run_integration_tests.js'; import {AdkApiServer} from '../server/adk_api_server.js'; @@ -27,8 +26,6 @@ import {runAgent} from './cli_run.js'; import {deployToAgentEngine} from './deploy/cli_deploy_agent_engine.js'; import {deployToCloudRun} from './deploy/cli_deploy_cloud_run.js'; -dotenv.config({quiet: true}); - const LOG_LEVEL_MAP: Record = { 'debug': LogLevel.DEBUG, 'info': LogLevel.INFO, diff --git a/dev/src/cli_entrypoint.ts b/dev/src/cli_entrypoint.ts index 4b354b9a8..e2beeabd0 100644 --- a/dev/src/cli_entrypoint.ts +++ b/dev/src/cli_entrypoint.ts @@ -4,9 +4,11 @@ * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ +import dotenv from 'dotenv'; import {createProgram} from './cli/cli.js'; try { + dotenv.config({quiet: true}); createProgram().parse(process.argv); } catch (e) { console.error(e); diff --git a/dev/test/cli/cli_test.ts b/dev/test/cli/cli_test.ts index 75d003802..d4198b1b3 100644 --- a/dev/test/cli/cli_test.ts +++ b/dev/test/cli/cli_test.ts @@ -5,6 +5,10 @@ */ import {LogLevel, setLogLevel} from '@google/adk'; +import dotenv from 'dotenv'; +import * as fs from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; import {afterEach, beforeEach, describe, expect, it, Mock, vi} from 'vitest'; import {createProgram} from '../../src/cli/cli.js'; import {createAgent} from '../../src/cli/cli_create.js'; @@ -49,6 +53,9 @@ vi.mock('@google/adk', async (importOriginal) => { }; }); +/** Env var no production code reads, so the assertion pins the mechanism. */ +const DOTENV_SENTINEL = 'ADK_DOTENV_IMPORT_SENTINEL'; + describe('CLI Entrypoint', () => { let program: ReturnType; @@ -415,4 +422,39 @@ describe('CLI Entrypoint', () => { }); }); }); + + describe('module import side effects', () => { + let envDir: string; + + beforeEach(async () => { + envDir = await fs.mkdtemp(path.join(os.tmpdir(), 'adk-cli-dotenv-')); + await fs.writeFile( + path.join(envDir, '.env'), + `${DOTENV_SENTINEL}=leaked\n`, + ); + // dotenv resolves `${process.cwd()}/.env` at call time, so the spy + // redirects the lookup the way a real chdir would. + vi.spyOn(process, 'cwd').mockReturnValue(envDir); + }); + + afterEach(async () => { + delete process.env[DOTENV_SENTINEL]; + await fs.rm(envDir, {recursive: true, force: true}); + }); + + it('should not read a .env file from the working directory on import', async () => { + // Without the reset, cli.js is already in the module registry from this + // file's static import and would not re-evaluate. + vi.resetModules(); + await import('../../src/cli/cli.js'); + + expect(process.env[DOTENV_SENTINEL]).toBeUndefined(); + }); + + it('should still load that .env once dotenv.config runs, as the entrypoint does', () => { + dotenv.config({quiet: true}); + + expect(process.env[DOTENV_SENTINEL]).toBe('leaked'); + }); + }); });