mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
# 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483)
This commit is contained in:
@@ -5,14 +5,26 @@
|
||||
*/
|
||||
|
||||
export async function readStdin(): Promise<string> {
|
||||
const MAX_STDIN_SIZE = 8 * 1024 * 1024; // 8MB
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = '';
|
||||
let totalSize = 0;
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
const onReadable = () => {
|
||||
let chunk;
|
||||
while ((chunk = process.stdin.read()) !== null) {
|
||||
if (totalSize + chunk.length > MAX_STDIN_SIZE) {
|
||||
const remainingSize = MAX_STDIN_SIZE - totalSize;
|
||||
data += chunk.slice(0, remainingSize);
|
||||
console.warn(
|
||||
`Warning: stdin input truncated to ${MAX_STDIN_SIZE} bytes.`,
|
||||
);
|
||||
process.stdin.destroy(); // Stop reading further
|
||||
break;
|
||||
}
|
||||
data += chunk;
|
||||
totalSize += chunk.length;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const BUILTIN_SEATBELT_PROFILES = [
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the current user's UID/GID should be used, false otherwise.
|
||||
*/
|
||||
async function shouldUseCurrentUserInSandbox(): Promise<boolean> {
|
||||
const envVar = process.env.SANDBOX_SET_UID_GID?.toLowerCase().trim();
|
||||
const envVar = process.env['SANDBOX_SET_UID_GID']?.toLowerCase().trim();
|
||||
|
||||
if (envVar === '1' || envVar === 'true') {
|
||||
return true;
|
||||
@@ -108,7 +108,7 @@ function parseImageName(image: string): string {
|
||||
}
|
||||
|
||||
function ports(): string[] {
|
||||
return (process.env.SANDBOX_PORTS ?? '')
|
||||
return (process.env['SANDBOX_PORTS'] ?? '')
|
||||
.split(',')
|
||||
.filter((p) => p.trim())
|
||||
.map((p) => p.trim());
|
||||
@@ -121,8 +121,8 @@ function entrypoint(workdir: string): string[] {
|
||||
const pathSeparator = isWindows ? ';' : ':';
|
||||
|
||||
let pathSuffix = '';
|
||||
if (process.env.PATH) {
|
||||
const paths = process.env.PATH.split(pathSeparator);
|
||||
if (process.env['PATH']) {
|
||||
const paths = process.env['PATH'].split(pathSeparator);
|
||||
for (const p of paths) {
|
||||
const containerPath = getContainerPath(p);
|
||||
if (
|
||||
@@ -137,8 +137,8 @@ function entrypoint(workdir: string): string[] {
|
||||
}
|
||||
|
||||
let pythonPathSuffix = '';
|
||||
if (process.env.PYTHONPATH) {
|
||||
const paths = process.env.PYTHONPATH.split(pathSeparator);
|
||||
if (process.env['PYTHONPATH']) {
|
||||
const paths = process.env['PYTHONPATH'].split(pathSeparator);
|
||||
for (const p of paths) {
|
||||
const containerPath = getContainerPath(p);
|
||||
if (
|
||||
@@ -168,12 +168,12 @@ function entrypoint(workdir: string): string[] {
|
||||
|
||||
const cliArgs = process.argv.slice(2).map((arg) => quote([arg]));
|
||||
const cliCmd =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? process.env.DEBUG
|
||||
process.env['NODE_ENV'] === 'development'
|
||||
? process.env['DEBUG']
|
||||
? 'npm run debug --'
|
||||
: 'npm rebuild && npm run start --'
|
||||
: process.env.DEBUG
|
||||
? `node --inspect-brk=0.0.0.0:${process.env.DEBUG_PORT || '9229'} $(which qwen)`
|
||||
: process.env['DEBUG']
|
||||
? `node --inspect-brk=0.0.0.0:${process.env['DEBUG_PORT'] || '9229'} $(which qwen)`
|
||||
: 'qwen';
|
||||
|
||||
const args = [...shellCmds, cliCmd, ...cliArgs];
|
||||
@@ -187,7 +187,7 @@ export async function start_sandbox(
|
||||
cliConfig?: Config,
|
||||
) {
|
||||
const patcher = new ConsolePatcher({
|
||||
debugMode: cliConfig?.getDebugMode() || !!process.env.DEBUG,
|
||||
debugMode: cliConfig?.getDebugMode() || !!process.env['DEBUG'],
|
||||
stderr: true,
|
||||
});
|
||||
patcher.patch();
|
||||
@@ -195,11 +195,11 @@ export async function start_sandbox(
|
||||
try {
|
||||
if (config.command === 'sandbox-exec') {
|
||||
// disallow BUILD_SANDBOX
|
||||
if (process.env.BUILD_SANDBOX) {
|
||||
if (process.env['BUILD_SANDBOX']) {
|
||||
console.error('ERROR: cannot BUILD_SANDBOX when using macOS Seatbelt');
|
||||
process.exit(1);
|
||||
}
|
||||
const profile = (process.env.SEATBELT_PROFILE ??= 'permissive-open');
|
||||
const profile = (process.env['SEATBELT_PROFILE'] ??= 'permissive-open');
|
||||
let profileFile = new URL(`sandbox-macos-${profile}.sb`, import.meta.url)
|
||||
.pathname;
|
||||
// if profile name is not recognized, then look for file under project settings directory
|
||||
@@ -219,7 +219,7 @@ export async function start_sandbox(
|
||||
console.error(`using macos seatbelt (profile: ${profile}) ...`);
|
||||
// if DEBUG is set, convert to --inspect-brk in NODE_OPTIONS
|
||||
const nodeOptions = [
|
||||
...(process.env.DEBUG ? ['--inspect-brk'] : []),
|
||||
...(process.env['DEBUG'] ? ['--inspect-brk'] : []),
|
||||
...nodeArgs,
|
||||
].join(' ');
|
||||
|
||||
@@ -275,22 +275,22 @@ export async function start_sandbox(
|
||||
].join(' '),
|
||||
);
|
||||
// start and set up proxy if GEMINI_SANDBOX_PROXY_COMMAND is set
|
||||
const proxyCommand = process.env.GEMINI_SANDBOX_PROXY_COMMAND;
|
||||
const proxyCommand = process.env['GEMINI_SANDBOX_PROXY_COMMAND'];
|
||||
let proxyProcess: ChildProcess | undefined = undefined;
|
||||
let sandboxProcess: ChildProcess | undefined = undefined;
|
||||
const sandboxEnv = { ...process.env };
|
||||
if (proxyCommand) {
|
||||
const proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env['HTTPS_PROXY'] ||
|
||||
process.env['https_proxy'] ||
|
||||
process.env['HTTP_PROXY'] ||
|
||||
process.env['http_proxy'] ||
|
||||
'http://localhost:8877';
|
||||
sandboxEnv['HTTPS_PROXY'] = proxy;
|
||||
sandboxEnv['https_proxy'] = proxy; // lower-case can be required, e.g. for curl
|
||||
sandboxEnv['HTTP_PROXY'] = proxy;
|
||||
sandboxEnv['http_proxy'] = proxy;
|
||||
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
||||
const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'];
|
||||
if (noProxy) {
|
||||
sandboxEnv['NO_PROXY'] = noProxy;
|
||||
sandboxEnv['no_proxy'] = noProxy;
|
||||
@@ -358,7 +358,7 @@ export async function start_sandbox(
|
||||
// if BUILD_SANDBOX is set, then call scripts/build_sandbox.js under gemini-cli repo
|
||||
//
|
||||
// note this can only be done with binary linked from gemini-cli repo
|
||||
if (process.env.BUILD_SANDBOX) {
|
||||
if (process.env['BUILD_SANDBOX']) {
|
||||
if (!gcPath.includes('gemini-cli/packages/')) {
|
||||
console.error(
|
||||
'ERROR: cannot build sandbox using installed gemini binary; ' +
|
||||
@@ -408,8 +408,8 @@ export async function start_sandbox(
|
||||
const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
|
||||
|
||||
// add custom flags from SANDBOX_FLAGS
|
||||
if (process.env.SANDBOX_FLAGS) {
|
||||
const flags = parse(process.env.SANDBOX_FLAGS, process.env).filter(
|
||||
if (process.env['SANDBOX_FLAGS']) {
|
||||
const flags = parse(process.env['SANDBOX_FLAGS'], process.env).filter(
|
||||
(f): f is string => typeof f === 'string',
|
||||
);
|
||||
args.push(...flags);
|
||||
@@ -456,8 +456,8 @@ export async function start_sandbox(
|
||||
}
|
||||
|
||||
// mount ADC file if GOOGLE_APPLICATION_CREDENTIALS is set
|
||||
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
||||
const adcFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
||||
if (process.env['GOOGLE_APPLICATION_CREDENTIALS']) {
|
||||
const adcFile = process.env['GOOGLE_APPLICATION_CREDENTIALS'];
|
||||
if (fs.existsSync(adcFile)) {
|
||||
args.push('--volume', `${adcFile}:${getContainerPath(adcFile)}:ro`);
|
||||
args.push(
|
||||
@@ -468,8 +468,8 @@ export async function start_sandbox(
|
||||
}
|
||||
|
||||
// mount paths listed in SANDBOX_MOUNTS
|
||||
if (process.env.SANDBOX_MOUNTS) {
|
||||
for (let mount of process.env.SANDBOX_MOUNTS.split(',')) {
|
||||
if (process.env['SANDBOX_MOUNTS']) {
|
||||
for (let mount of process.env['SANDBOX_MOUNTS'].split(',')) {
|
||||
if (mount.trim()) {
|
||||
// parse mount as from:to:opts
|
||||
let [from, to, opts] = mount.trim().split(':');
|
||||
@@ -500,22 +500,22 @@ export async function start_sandbox(
|
||||
ports().forEach((p) => args.push('--publish', `${p}:${p}`));
|
||||
|
||||
// if DEBUG is set, expose debugging port
|
||||
if (process.env.DEBUG) {
|
||||
const debugPort = process.env.DEBUG_PORT || '9229';
|
||||
if (process.env['DEBUG']) {
|
||||
const debugPort = process.env['DEBUG_PORT'] || '9229';
|
||||
args.push(`--publish`, `${debugPort}:${debugPort}`);
|
||||
}
|
||||
|
||||
// copy proxy environment variables, replacing localhost with SANDBOX_PROXY_NAME
|
||||
// copy as both upper-case and lower-case as is required by some utilities
|
||||
// GEMINI_SANDBOX_PROXY_COMMAND implies HTTPS_PROXY unless HTTP_PROXY is set
|
||||
const proxyCommand = process.env.GEMINI_SANDBOX_PROXY_COMMAND;
|
||||
const proxyCommand = process.env['GEMINI_SANDBOX_PROXY_COMMAND'];
|
||||
|
||||
if (proxyCommand) {
|
||||
let proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env['HTTPS_PROXY'] ||
|
||||
process.env['https_proxy'] ||
|
||||
process.env['HTTP_PROXY'] ||
|
||||
process.env['http_proxy'] ||
|
||||
'http://localhost:8877';
|
||||
proxy = proxy.replace('localhost', SANDBOX_PROXY_NAME);
|
||||
if (proxy) {
|
||||
@@ -524,7 +524,7 @@ export async function start_sandbox(
|
||||
args.push('--env', `HTTP_PROXY=${proxy}`);
|
||||
args.push('--env', `http_proxy=${proxy}`);
|
||||
}
|
||||
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
||||
const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'];
|
||||
if (noProxy) {
|
||||
args.push('--env', `NO_PROXY=${noProxy}`);
|
||||
args.push('--env', `no_proxy=${noProxy}`);
|
||||
@@ -562,71 +562,71 @@ export async function start_sandbox(
|
||||
args.push('--name', containerName, '--hostname', containerName);
|
||||
|
||||
// copy GEMINI_API_KEY(s)
|
||||
if (process.env.GEMINI_API_KEY) {
|
||||
args.push('--env', `GEMINI_API_KEY=${process.env.GEMINI_API_KEY}`);
|
||||
if (process.env['GEMINI_API_KEY']) {
|
||||
args.push('--env', `GEMINI_API_KEY=${process.env['GEMINI_API_KEY']}`);
|
||||
}
|
||||
if (process.env.GOOGLE_API_KEY) {
|
||||
args.push('--env', `GOOGLE_API_KEY=${process.env.GOOGLE_API_KEY}`);
|
||||
if (process.env['GOOGLE_API_KEY']) {
|
||||
args.push('--env', `GOOGLE_API_KEY=${process.env['GOOGLE_API_KEY']}`);
|
||||
}
|
||||
|
||||
// copy OPENAI_API_KEY and related env vars for Qwen
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
args.push('--env', `OPENAI_API_KEY=${process.env.OPENAI_API_KEY}`);
|
||||
if (process.env['OPENAI_API_KEY']) {
|
||||
args.push('--env', `OPENAI_API_KEY=${process.env['OPENAI_API_KEY']}`);
|
||||
}
|
||||
// copy TAVILY_API_KEY for web search tool
|
||||
if (process.env.TAVILY_API_KEY) {
|
||||
args.push('--env', `TAVILY_API_KEY=${process.env.TAVILY_API_KEY}`);
|
||||
if (process.env['TAVILY_API_KEY']) {
|
||||
args.push('--env', `TAVILY_API_KEY=${process.env['TAVILY_API_KEY']}`);
|
||||
}
|
||||
if (process.env.OPENAI_BASE_URL) {
|
||||
args.push('--env', `OPENAI_BASE_URL=${process.env.OPENAI_BASE_URL}`);
|
||||
if (process.env['OPENAI_BASE_URL']) {
|
||||
args.push('--env', `OPENAI_BASE_URL=${process.env['OPENAI_BASE_URL']}`);
|
||||
}
|
||||
if (process.env.OPENAI_MODEL) {
|
||||
args.push('--env', `OPENAI_MODEL=${process.env.OPENAI_MODEL}`);
|
||||
if (process.env['OPENAI_MODEL']) {
|
||||
args.push('--env', `OPENAI_MODEL=${process.env['OPENAI_MODEL']}`);
|
||||
}
|
||||
|
||||
// copy GOOGLE_GENAI_USE_VERTEXAI
|
||||
if (process.env.GOOGLE_GENAI_USE_VERTEXAI) {
|
||||
if (process.env['GOOGLE_GENAI_USE_VERTEXAI']) {
|
||||
args.push(
|
||||
'--env',
|
||||
`GOOGLE_GENAI_USE_VERTEXAI=${process.env.GOOGLE_GENAI_USE_VERTEXAI}`,
|
||||
`GOOGLE_GENAI_USE_VERTEXAI=${process.env['GOOGLE_GENAI_USE_VERTEXAI']}`,
|
||||
);
|
||||
}
|
||||
|
||||
// copy GOOGLE_GENAI_USE_GCA
|
||||
if (process.env.GOOGLE_GENAI_USE_GCA) {
|
||||
if (process.env['GOOGLE_GENAI_USE_GCA']) {
|
||||
args.push(
|
||||
'--env',
|
||||
`GOOGLE_GENAI_USE_GCA=${process.env.GOOGLE_GENAI_USE_GCA}`,
|
||||
`GOOGLE_GENAI_USE_GCA=${process.env['GOOGLE_GENAI_USE_GCA']}`,
|
||||
);
|
||||
}
|
||||
|
||||
// copy GOOGLE_CLOUD_PROJECT
|
||||
if (process.env.GOOGLE_CLOUD_PROJECT) {
|
||||
if (process.env['GOOGLE_CLOUD_PROJECT']) {
|
||||
args.push(
|
||||
'--env',
|
||||
`GOOGLE_CLOUD_PROJECT=${process.env.GOOGLE_CLOUD_PROJECT}`,
|
||||
`GOOGLE_CLOUD_PROJECT=${process.env['GOOGLE_CLOUD_PROJECT']}`,
|
||||
);
|
||||
}
|
||||
|
||||
// copy GOOGLE_CLOUD_LOCATION
|
||||
if (process.env.GOOGLE_CLOUD_LOCATION) {
|
||||
if (process.env['GOOGLE_CLOUD_LOCATION']) {
|
||||
args.push(
|
||||
'--env',
|
||||
`GOOGLE_CLOUD_LOCATION=${process.env.GOOGLE_CLOUD_LOCATION}`,
|
||||
`GOOGLE_CLOUD_LOCATION=${process.env['GOOGLE_CLOUD_LOCATION']}`,
|
||||
);
|
||||
}
|
||||
|
||||
// copy GEMINI_MODEL
|
||||
if (process.env.GEMINI_MODEL) {
|
||||
args.push('--env', `GEMINI_MODEL=${process.env.GEMINI_MODEL}`);
|
||||
if (process.env['GEMINI_MODEL']) {
|
||||
args.push('--env', `GEMINI_MODEL=${process.env['GEMINI_MODEL']}`);
|
||||
}
|
||||
|
||||
// copy TERM and COLORTERM to try to maintain terminal setup
|
||||
if (process.env.TERM) {
|
||||
args.push('--env', `TERM=${process.env.TERM}`);
|
||||
if (process.env['TERM']) {
|
||||
args.push('--env', `TERM=${process.env['TERM']}`);
|
||||
}
|
||||
if (process.env.COLORTERM) {
|
||||
args.push('--env', `COLORTERM=${process.env.COLORTERM}`);
|
||||
if (process.env['COLORTERM']) {
|
||||
args.push('--env', `COLORTERM=${process.env['COLORTERM']}`);
|
||||
}
|
||||
|
||||
// Pass through IDE mode environment variables
|
||||
@@ -645,7 +645,9 @@ export async function start_sandbox(
|
||||
// sandbox can then set up this new VIRTUAL_ENV directory using sandbox.bashrc (see below)
|
||||
// directory will be empty if not set up, which is still preferable to having host binaries
|
||||
if (
|
||||
process.env.VIRTUAL_ENV?.toLowerCase().startsWith(workdir.toLowerCase())
|
||||
process.env['VIRTUAL_ENV']
|
||||
?.toLowerCase()
|
||||
.startsWith(workdir.toLowerCase())
|
||||
) {
|
||||
const sandboxVenvPath = path.resolve(
|
||||
SETTINGS_DIRECTORY_NAME,
|
||||
@@ -656,17 +658,17 @@ export async function start_sandbox(
|
||||
}
|
||||
args.push(
|
||||
'--volume',
|
||||
`${sandboxVenvPath}:${getContainerPath(process.env.VIRTUAL_ENV)}`,
|
||||
`${sandboxVenvPath}:${getContainerPath(process.env['VIRTUAL_ENV'])}`,
|
||||
);
|
||||
args.push(
|
||||
'--env',
|
||||
`VIRTUAL_ENV=${getContainerPath(process.env.VIRTUAL_ENV)}`,
|
||||
`VIRTUAL_ENV=${getContainerPath(process.env['VIRTUAL_ENV'])}`,
|
||||
);
|
||||
}
|
||||
|
||||
// copy additional environment variables from SANDBOX_ENV
|
||||
if (process.env.SANDBOX_ENV) {
|
||||
for (let env of process.env.SANDBOX_ENV.split(',')) {
|
||||
if (process.env['SANDBOX_ENV']) {
|
||||
for (let env of process.env['SANDBOX_ENV'].split(',')) {
|
||||
if ((env = env.trim())) {
|
||||
if (env.includes('=')) {
|
||||
console.error(`SANDBOX_ENV: ${env}`);
|
||||
@@ -682,7 +684,7 @@ export async function start_sandbox(
|
||||
}
|
||||
|
||||
// copy NODE_OPTIONS
|
||||
const existingNodeOptions = process.env.NODE_OPTIONS || '';
|
||||
const existingNodeOptions = process.env['NODE_OPTIONS'] || '';
|
||||
const allNodeOptions = [
|
||||
...(existingNodeOptions ? [existingNodeOptions] : []),
|
||||
...nodeArgs,
|
||||
@@ -707,7 +709,7 @@ export async function start_sandbox(
|
||||
let userFlag = '';
|
||||
const finalEntrypoint = entrypoint(workdir);
|
||||
|
||||
if (process.env.GEMINI_CLI_INTEGRATION_TEST === 'true') {
|
||||
if (process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true') {
|
||||
args.push('--user', 'root');
|
||||
userFlag = '--user root';
|
||||
} else if (await shouldUseCurrentUserInSandbox()) {
|
||||
|
||||
@@ -239,7 +239,7 @@ describe('SettingsUtils', () => {
|
||||
expect(shouldShowInDialog('showMemoryUsage')).toBe(true);
|
||||
expect(shouldShowInDialog('vimMode')).toBe(true);
|
||||
expect(shouldShowInDialog('hideWindowTitle')).toBe(true);
|
||||
expect(shouldShowInDialog('usageStatisticsEnabled')).toBe(true);
|
||||
expect(shouldShowInDialog('usageStatisticsEnabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for settings marked to hide from dialog', () => {
|
||||
@@ -286,7 +286,7 @@ describe('SettingsUtils', () => {
|
||||
expect(allKeys).toContain('ideMode');
|
||||
expect(allKeys).toContain('disableAutoUpdate');
|
||||
expect(allKeys).toContain('showMemoryUsage');
|
||||
expect(allKeys).toContain('usageStatisticsEnabled');
|
||||
expect(allKeys).not.toContain('usageStatisticsEnabled');
|
||||
expect(allKeys).not.toContain('selectedAuthType');
|
||||
expect(allKeys).not.toContain('coreTools');
|
||||
expect(allKeys).not.toContain('theme'); // Now hidden
|
||||
@@ -302,7 +302,7 @@ describe('SettingsUtils', () => {
|
||||
expect(keys).toContain('showMemoryUsage');
|
||||
expect(keys).toContain('vimMode');
|
||||
expect(keys).toContain('hideWindowTitle');
|
||||
expect(keys).toContain('usageStatisticsEnabled');
|
||||
expect(keys).not.toContain('usageStatisticsEnabled');
|
||||
expect(keys).not.toContain('selectedAuthType'); // Advanced setting
|
||||
expect(keys).not.toContain('useExternalAuth'); // Advanced setting
|
||||
});
|
||||
@@ -329,7 +329,7 @@ describe('SettingsUtils', () => {
|
||||
expect(dialogKeys).toContain('showMemoryUsage');
|
||||
expect(dialogKeys).toContain('vimMode');
|
||||
expect(dialogKeys).toContain('hideWindowTitle');
|
||||
expect(dialogKeys).toContain('usageStatisticsEnabled');
|
||||
expect(dialogKeys).not.toContain('usageStatisticsEnabled');
|
||||
expect(dialogKeys).toContain('ideMode');
|
||||
expect(dialogKeys).toContain('disableAutoUpdate');
|
||||
|
||||
@@ -392,7 +392,7 @@ describe('SettingsUtils', () => {
|
||||
new Set(),
|
||||
updatedPendingSettings,
|
||||
);
|
||||
expect(displayValue).toBe('true*'); // Should show true with * indicating change
|
||||
expect(displayValue).toBe('true'); // Should show true (no * since value matches default)
|
||||
|
||||
// Test that modified settings also show the * indicator
|
||||
const modifiedSettings = new Set([key]);
|
||||
@@ -602,7 +602,7 @@ describe('SettingsUtils', () => {
|
||||
mergedSettings,
|
||||
modifiedSettings,
|
||||
);
|
||||
expect(result).toBe('false'); // matches default, no *
|
||||
expect(result).toBe('false*');
|
||||
});
|
||||
|
||||
it('should show default value when setting is not in scope', () => {
|
||||
|
||||
@@ -91,7 +91,10 @@ export function getRestartRequiredSettings(): string[] {
|
||||
/**
|
||||
* Recursively gets a value from a nested object using a key path array.
|
||||
*/
|
||||
function getNestedValue(obj: Record<string, unknown>, path: string[]): unknown {
|
||||
export function getNestedValue(
|
||||
obj: Record<string, unknown>,
|
||||
path: string[],
|
||||
): unknown {
|
||||
const [first, ...rest] = path;
|
||||
if (!first || !(first in obj)) {
|
||||
return undefined;
|
||||
@@ -332,6 +335,20 @@ export function setPendingSettingValue(
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter: Set a setting value (boolean, number, string, etc.) in the pending settings
|
||||
*/
|
||||
export function setPendingSettingValueAny(
|
||||
key: string,
|
||||
value: unknown,
|
||||
pendingSettings: Settings,
|
||||
): Settings {
|
||||
const path = key.split('.');
|
||||
const newSettings = structuredClone(pendingSettings);
|
||||
setNestedValue(newSettings, path, value);
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any modified settings require a restart
|
||||
*/
|
||||
@@ -382,11 +399,9 @@ export function saveModifiedSettings(
|
||||
// We need to set the whole parent object.
|
||||
const [parentKey] = path;
|
||||
if (parentKey) {
|
||||
// Ensure value is a boolean for setPendingSettingValue
|
||||
const booleanValue = typeof value === 'boolean' ? value : false;
|
||||
const newParentValue = setPendingSettingValue(
|
||||
const newParentValue = setPendingSettingValueAny(
|
||||
settingKey,
|
||||
booleanValue,
|
||||
value,
|
||||
loadedSettings.forScope(scope).settings,
|
||||
)[parentKey as keyof Settings];
|
||||
|
||||
@@ -431,11 +446,12 @@ export function getDisplayValue(
|
||||
const isChangedFromDefault =
|
||||
typeof defaultValue === 'boolean' ? value !== defaultValue : value === true;
|
||||
const isInModifiedSettings = modifiedSettings.has(key);
|
||||
const hasPendingChanges =
|
||||
pendingSettings && settingExistsInScope(key, pendingSettings);
|
||||
|
||||
// Add * indicator when value differs from default, is in modified settings, or has pending changes
|
||||
if (isChangedFromDefault || isInModifiedSettings || hasPendingChanges) {
|
||||
// Mark as modified if setting exists in current scope OR is in modified settings
|
||||
if (settingExistsInScope(key, settings) || isInModifiedSettings) {
|
||||
return `${valueString}*`; // * indicates setting is set in current scope
|
||||
}
|
||||
if (isChangedFromDefault || isInModifiedSettings) {
|
||||
return `${valueString}*`; // * indicates changed from default value
|
||||
}
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@ import { getPackageJson } from './package.js';
|
||||
|
||||
export async function getCliVersion(): Promise<string> {
|
||||
const pkgJson = await getPackageJson();
|
||||
return process.env.CLI_VERSION || pkgJson?.version || 'unknown';
|
||||
return process.env['CLI_VERSION'] || pkgJson?.version || 'unknown';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user