mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
fix(update): refresh target before foreground prompts (#405)
This commit is contained in:
parent
7a4c80eae1
commit
07e2e0f094
3 changed files with 114 additions and 9 deletions
5
.changeset/refresh-update-prompt-target.md
Normal file
5
.changeset/refresh-update-prompt-target.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Refresh the update target before showing foreground update prompts so the displayed version matches the install.
|
||||
|
|
@ -42,6 +42,7 @@ export interface RunUpdatePreflightOptions {
|
|||
|
||||
const AUTO_INSTALL_FAILURE_PROMPT_THRESHOLD = 2;
|
||||
const AUTO_INSTALL_ACTIVE_TTL_MS = 6 * 60 * 60 * 1000;
|
||||
const USER_VISIBLE_UPDATE_REFRESH_TIMEOUT_MS = 1_000;
|
||||
|
||||
type UpdateLogger = Pick<Logger, 'info' | 'warn'>;
|
||||
|
||||
|
|
@ -191,6 +192,30 @@ function refreshAndMaybeInstallInBackground(
|
|||
})().catch(() => {});
|
||||
}
|
||||
|
||||
async function refreshUserVisibleUpdateTarget(
|
||||
currentVersion: string,
|
||||
fallbackTarget: UpdateTarget,
|
||||
): Promise<UpdateTarget | null> {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
const refresh = refreshUpdateCache()
|
||||
.then((refreshed) => selectUpdateTarget(currentVersion, refreshed.latest))
|
||||
.catch(() => fallbackTarget);
|
||||
const fallback = new Promise<UpdateTarget>((resolve) => {
|
||||
timeout = setTimeout(() => {
|
||||
resolve(fallbackTarget);
|
||||
}, USER_VISIBLE_UPDATE_REFRESH_TIMEOUT_MS);
|
||||
});
|
||||
return await Promise.race([refresh, fallback]);
|
||||
} catch {
|
||||
return fallbackTarget;
|
||||
} finally {
|
||||
if (timeout !== undefined) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
|
@ -536,16 +561,17 @@ export async function runUpdatePreflight(
|
|||
return 'continue';
|
||||
}
|
||||
|
||||
refreshInBackground();
|
||||
const source: InstallSource =
|
||||
!isInteractive
|
||||
? 'unsupported'
|
||||
: await detectInstallSource().catch(() => 'unsupported' as const);
|
||||
|
||||
const decision = decideUpdateAction(target, isInteractive, source, platform);
|
||||
if (decision === 'none') return 'continue';
|
||||
if (decision === 'none') {
|
||||
refreshInBackground();
|
||||
return 'continue';
|
||||
}
|
||||
|
||||
const installCommand = installCommandFor(source, target.version, platform);
|
||||
if (
|
||||
await tryStartAutomaticBackgroundInstall(
|
||||
installState,
|
||||
|
|
@ -557,26 +583,49 @@ export async function runUpdatePreflight(
|
|||
logger,
|
||||
)
|
||||
) {
|
||||
refreshInBackground();
|
||||
return 'continue';
|
||||
}
|
||||
|
||||
trackUpdatePrompted(options.track, currentVersion, target, source, decision);
|
||||
const userVisibleTarget = await refreshUserVisibleUpdateTarget(currentVersion, target);
|
||||
if (userVisibleTarget === null) return 'continue';
|
||||
if (
|
||||
await tryStartAutomaticBackgroundInstall(
|
||||
installState,
|
||||
currentVersion,
|
||||
userVisibleTarget,
|
||||
source,
|
||||
platform,
|
||||
options.track,
|
||||
logger,
|
||||
)
|
||||
) {
|
||||
return 'continue';
|
||||
}
|
||||
|
||||
const installCommand = installCommandFor(source, userVisibleTarget.version, platform);
|
||||
trackUpdatePrompted(options.track, currentVersion, userVisibleTarget, source, decision);
|
||||
|
||||
if (decision === 'manual-command') {
|
||||
stdout.write(renderManualUpdateMessage(currentVersion, target, source, installCommand));
|
||||
stdout.write(renderManualUpdateMessage(
|
||||
currentVersion,
|
||||
userVisibleTarget,
|
||||
source,
|
||||
installCommand,
|
||||
));
|
||||
return 'continue';
|
||||
}
|
||||
|
||||
const choice = await promptInstall(currentVersion, target, source, installCommand);
|
||||
const choice = await promptInstall(currentVersion, userVisibleTarget, source, installCommand);
|
||||
if (choice === 'skip') return 'continue';
|
||||
|
||||
try {
|
||||
await installUpdate(source, target.version, platform);
|
||||
stdout.write(renderInstallSuccessMessage(target));
|
||||
await installUpdate(source, userVisibleTarget.version, platform);
|
||||
stdout.write(renderInstallSuccessMessage(userVisibleTarget));
|
||||
return 'exit';
|
||||
} catch (error) {
|
||||
stderr.write(
|
||||
`warning: failed to install ${NPM_PACKAGE_NAME}@${target.version}: ` +
|
||||
`warning: failed to install ${NPM_PACKAGE_NAME}@${userVisibleTarget.version}: ` +
|
||||
`${formatErrorMessage(error)}\n`,
|
||||
);
|
||||
return 'continue';
|
||||
|
|
|
|||
|
|
@ -273,6 +273,57 @@ describe('runUpdatePreflight', () => {
|
|||
expect(stdout.join('')).toContain('Updated @moonshot-ai/kimi-code to 0.5.0');
|
||||
});
|
||||
|
||||
it('refreshes a stale cached target before showing the foreground install prompt', async () => {
|
||||
disableAutoInstall();
|
||||
mocks.readUpdateCache.mockResolvedValue(cacheWith('0.6.0'));
|
||||
mocks.refreshUpdateCache.mockResolvedValue(cacheWith('0.7.0'));
|
||||
mocks.detectInstallSource.mockResolvedValue('npm-global');
|
||||
mocks.promptForInstallChoice.mockResolvedValue('install');
|
||||
mockSpawnExit(0);
|
||||
const { stdout, options } = captureOutput();
|
||||
|
||||
await expect(runUpdatePreflight('0.5.0', options)).resolves.toBe('exit');
|
||||
|
||||
expect(refreshUpdateCache).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.promptForInstallChoice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
target: { version: '0.7.0' },
|
||||
installCommand: 'npm install -g @moonshot-ai/kimi-code@0.7.0',
|
||||
}),
|
||||
);
|
||||
expect(mocks.spawn).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/^npm(\.cmd)?$/),
|
||||
['install', '-g', '@moonshot-ai/kimi-code@0.7.0'],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
expect(stdout.join('')).toContain('Updated @moonshot-ai/kimi-code to 0.7.0');
|
||||
});
|
||||
|
||||
it('falls back to the cached foreground prompt target when the refresh hangs', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
disableAutoInstall();
|
||||
mocks.readUpdateCache.mockResolvedValue(cacheWith('0.6.0'));
|
||||
mocks.refreshUpdateCache.mockReturnValue(new Promise(() => {}));
|
||||
mocks.detectInstallSource.mockResolvedValue('npm-global');
|
||||
mocks.promptForInstallChoice.mockResolvedValue('skip');
|
||||
const { options } = captureOutput();
|
||||
|
||||
const result = runUpdatePreflight('0.5.0', options);
|
||||
await vi.advanceTimersByTimeAsync(1_000);
|
||||
|
||||
await expect(result).resolves.toBe('continue');
|
||||
expect(mocks.promptForInstallChoice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
target: { version: '0.6.0' },
|
||||
installCommand: 'npm install -g @moonshot-ai/kimi-code@0.6.0',
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('pnpm-global: spawns pnpm add -g', async () => {
|
||||
disableAutoInstall();
|
||||
mocks.readUpdateCache.mockResolvedValue(cacheWith('0.5.0'));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue