From 5a6b2f265944cc283c3d1b6a403d67db99ee0261 Mon Sep 17 00:00:00 2001 From: lx-1997 <13500790262@163.com> Date: Wed, 28 Oct 2026 23:42:11 +0800 Subject: [PATCH] fix(cli): avoid locale import cycle during environment setup --- src/cli/config.ts | 13 ++++---- test/cli-env-locale-import.spec.mjs | 49 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 test/cli-env-locale-import.spec.mjs diff --git a/src/cli/config.ts b/src/cli/config.ts index badaaffe..7dda0d09 100644 --- a/src/cli/config.ts +++ b/src/cli/config.ts @@ -55,7 +55,7 @@ import { recordDotenvOrigin, } from '../utils/startup-env.js'; import { officialEnvOffers } from './env-credentials.js'; -import { isZhLocale, setupCopy } from './cli-locale.js'; +import { isZhLocale } from './cli-locale.js'; import { isDeviceTrustEnv, parseDeviceTrustList } from '../safety/device-trust.js'; import type { PricingConfig } from './model-pricing.js'; import type { StatusLineConfig } from './status-line.js'; @@ -2077,11 +2077,12 @@ export function resolveCliConfig( baseUrl: offer.baseUrl, }; autoEnvKey = { apiKey: offer.apiKey, source: `env:${offer.keyVar}` }; - autoEnvNotice = setupCopy(undefined, '[moss] Using {key} → {provider} @ {baseUrl}', { - key: offer.keyVar, - provider: offer.provider, - baseUrl: offer.baseUrl, - }); + // Config also initializes module defaults while cli-locale is importing it; + // its translation table is not initialized yet in that import order. + autoEnvNotice = uiText( + `[moss] Using ${offer.keyVar} → ${offer.provider} @ ${offer.baseUrl}`, + `[moss] 使用 ${offer.keyVar} → ${offer.provider} @ ${offer.baseUrl}` + ); autoEnvKeys.add('provider'); autoEnvKeys.add('model'); autoEnvKeys.add('baseUrl'); diff --git a/test/cli-env-locale-import.spec.mjs b/test/cli-env-locale-import.spec.mjs new file mode 100644 index 00000000..8408896b --- /dev/null +++ b/test/cli-env-locale-import.spec.mjs @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; + +for (const locale of ['zh_CN.UTF-8', 'en_US.UTF-8']) { + test(`CLI module initializes with one environment provider in ${locale}`, () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'moss-env-locale-')); + try { + const env = { ...process.env }; + for (const name of Object.keys(env)) { + if ( + name.startsWith('MOSS_') || + /(?:API_KEY|API_TOKEN|ACCESS_TOKEN|BASE_URL)$/.test(name) || + /^(?:MOSS_LANG|MOSS_LOCALE|HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|http_proxy|https_proxy|all_proxy)$/.test( + name + ) + ) + delete env[name]; + } + Object.assign(env, { + MOSS_CONFIG_DIR: dir, + LANG: locale, + LC_ALL: locale, + DEEPSEEK_API_KEY: 'fixture-key-never-send', + }); + const target = new URL('../dist/cli/oneshot.js', import.meta.url).href; + const configModule = new URL('../dist/cli/config.js', import.meta.url).href; + const code = `globalThis.fetch = () => { throw new Error('Unexpected fetch during module initialization'); }; await import(${JSON.stringify(target)}); const {resolveCliConfig} = await import(${JSON.stringify(configModule)}); console.log(JSON.stringify({notice:resolveCliConfig(process.env,{},{}).autoEnvNotice}));`; + const result = spawnSync(process.execPath, ['--input-type=module', '-e', code], { + cwd: dir, + env, + encoding: 'utf8', + timeout: 10000, + }); + assert.equal(result.status, 0, result.stderr); + const { notice } = JSON.parse(result.stdout); + assert.match( + notice, + locale.startsWith('zh') ? /使用 DEEPSEEK_API_KEY/ : /Using DEEPSEEK_API_KEY/ + ); + assert.doesNotMatch(result.stdout + result.stderr, /fixture-key-never-send/); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}