Przeglądaj źródła

fix(auto-update): suppress migration double toast

Alvin Unreal 2 miesięcy temu
rodzic
commit
00d0c24dca

+ 31 - 1
src/hooks/auto-update-checker/checker.test.ts

@@ -210,11 +210,41 @@ describe('auto-update-checker/checker', () => {
 
       expect(result).toEqual({
         latestVersion: '1.1.2',
-        latestMajorVersion: '1.1.2',
+        latestMajorVersion: null,
         blockedByMajor: false,
       });
 
       globalThis.fetch = originalFetch;
     });
+
+    test('uses channel tag as blocking major version', async () => {
+      const originalFetch = globalThis.fetch;
+      globalThis.fetch = mock(async () =>
+        Response.json({
+          'dist-tags': {
+            latest: '1.9.0',
+            beta: '2.0.0-beta.1',
+          },
+          versions: {
+            '1.9.0': {},
+            '2.0.0-beta.1': {},
+          },
+        }),
+      ) as never;
+
+      const { getLatestCompatibleVersion } = await import(
+        `./checker?test=${importCounter++}`
+      );
+
+      const result = await getLatestCompatibleVersion('1.8.0-beta.1', 'beta');
+
+      expect(result).toEqual({
+        latestVersion: null,
+        latestMajorVersion: '2.0.0-beta.1',
+        blockedByMajor: true,
+      });
+
+      globalThis.fetch = originalFetch;
+    });
   });
 });

+ 40 - 9
src/hooks/auto-update-checker/checker.ts

@@ -344,6 +344,11 @@ export function updatePinnedVersion(
 export async function getLatestVersion(
   channel: string = 'latest',
 ): Promise<string | null> {
+  const distTags = await fetchDistTags();
+  return distTags?.[channel] ?? distTags?.latest ?? null;
+}
+
+async function fetchDistTags(): Promise<NpmDistTags | null> {
   const controller = new AbortController();
   const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
 
@@ -356,7 +361,7 @@ export async function getLatestVersion(
     if (!response.ok) return null;
 
     const data = (await response.json()) as NpmDistTags;
-    return data[channel] ?? data.latest ?? null;
+    return data;
   } catch {
     return null;
   } finally {
@@ -396,10 +401,12 @@ export async function getLatestCompatibleVersion(
 
     const data = (await response.json()) as NpmPackageMetadata;
     const distTags = data['dist-tags'] ?? { latest: '' };
-    const latestMajorVersion = distTags.latest || null;
     const taggedVersion = distTags[channel] ?? distTags.latest ?? null;
-    const tagged = taggedVersion ? parseVersion(taggedVersion) : null;
-    const blockedByMajor = Boolean(tagged && tagged.major > current.major);
+    const latestMajorVersion = getBlockingMajorVersion(current, [
+      taggedVersion,
+      distTags.latest,
+    ]);
+    const blockedByMajor = latestMajorVersion !== null;
 
     const versions = Object.keys(data.versions ?? {})
       .filter((version) => {
@@ -424,13 +431,37 @@ async function getCompatibleFromDistTags(
   current: ParsedVersion,
   channel: string,
 ): Promise<CompatibleVersionResult> {
-  const latestVersion = await getLatestVersion(channel);
-  const latest = latestVersion ? parseVersion(latestVersion) : null;
-  const blockedByMajor = Boolean(latest && latest.major > current.major);
+  const distTags = await fetchDistTags();
+  if (!distTags) {
+    return {
+      latestVersion: null,
+      latestMajorVersion: null,
+      blockedByMajor: false,
+    };
+  }
+
+  const latestVersion = distTags[channel] ?? distTags.latest ?? null;
+  const latestMajorVersion = getBlockingMajorVersion(current, [
+    latestVersion,
+    distTags.latest,
+  ]);
+  const blockedByMajor = latestMajorVersion !== null;
 
   return {
-    latestVersion: blockedByMajor ? null : latestVersion,
-    latestMajorVersion: latestVersion,
+    latestVersion,
+    latestMajorVersion,
     blockedByMajor,
   };
 }
+
+function getBlockingMajorVersion(
+  current: ParsedVersion,
+  candidates: Array<string | null | undefined>,
+): string | null {
+  for (const candidate of candidates) {
+    const parsed = candidate ? parseVersion(candidate) : null;
+    if (parsed && parsed.major > current.major) return candidate ?? null;
+  }
+
+  return null;
+}

+ 31 - 0
src/hooks/auto-update-checker/index.test.ts

@@ -340,4 +340,35 @@ describe('auto-update-checker/index', () => {
     expect(cacheMocks.preparePackageUpdate).not.toHaveBeenCalled();
     expect(crossSpawnMock).not.toHaveBeenCalled();
   });
+
+  test('shows only migration toast when compatible and blocked major updates coexist', async () => {
+    checkerMocks.findPluginEntry.mockImplementation(() => ({
+      pinnedVersion: null,
+      isPinned: false,
+    }));
+    checkerMocks.getCachedVersion.mockImplementation(() => '1.0.0');
+    checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
+      latestVersion: '1.5.0',
+      latestMajorVersion: '2.0.0',
+      blockedByMajor: true,
+    }));
+
+    const { createAutoUpdateCheckerHook } = await import(
+      `./index?test=${importCounter++}`
+    );
+    const { ctx, showToast } = createCtx();
+
+    const hook = createAutoUpdateCheckerHook(ctx as never);
+    hook.event({ event: { type: 'session.created', properties: {} } });
+    await waitForCalls(showToast);
+
+    expect(showToast).toHaveBeenCalledTimes(1);
+    expect(showToast).toHaveBeenCalledWith({
+      body: expect.objectContaining({
+        title: 'oh-my-opencode-slim v2.0.0 is available.',
+      }),
+    });
+    expect(cacheMocks.preparePackageUpdate).not.toHaveBeenCalled();
+    expect(crossSpawnMock).not.toHaveBeenCalled();
+  });
 });

+ 4 - 0
src/hooks/auto-update-checker/index.ts

@@ -80,6 +80,10 @@ async function runBackgroundUpdateCheck(
   const latestInfo = await getLatestCompatibleVersion(currentVersion, channel);
   if (latestInfo.blockedByMajor && latestInfo.latestMajorVersion) {
     showMajorUpgradeToast(ctx, latestInfo.latestMajorVersion);
+    log(
+      `[auto-update-checker] Major update available; skipping auto-update: ${latestInfo.latestMajorVersion}`,
+    );
+    return;
   }
 
   const latestVersion = latestInfo.latestVersion;