Workers SDK Issue Reports

← Back to Dashboard

#9154 Wrangler upgrade check does not respect deprecated packages

Recommendation:KEEP OPEN
Difficulty:medium
Reasoning:

Bug confirmed in code: update-check.ts uses update-check npm package which only checks dist-tags.latest without verifying deprecation status. npm registry provides deprecated field but it's never checked.

Suggested Action:

Implement deprecation checking - either replace update-check library or add post-check deprecation verification

Analysis Report

Issue Review: cloudflare/workers-sdk#9154

Summary

Wrangler's upgrade check banner suggests upgrading to deprecated versions instead of skipping them for the next non-deprecated release.

Findings

  • Created: 2025-05-06
  • Updated: 2025-05-06
  • Version: 4.14.1 → 4.60.0 (current)
  • Component: Wrangler (update-check)
  • Labels: bug
  • Comments: 0

Key Evidence

  • No PRs reference or fix this issue (#9154)
  • Changelog has no entries related to fixing deprecated version handling
  • Code review confirms the root cause: packages/wrangler/src/update-check.ts uses the update-check npm package which only checks dist-tags.latest without verifying deprecation status
  • npm registry provides deprecated field in version metadata (verified: wrangler@4.14.2 has deprecation message)
  • The update-check library (update-check@1.5.4) has no built-in support for filtering deprecated versions

Recommendation

Status: KEEP OPEN

Reasoning: The bug is still present in the codebase. The update-check.ts file has not been modified to handle deprecated versions. While the current latest version (4.60.0) is not deprecated, the issue will resurface whenever a version needs to be deprecated.

Action: Implement deprecation checking in the update check logic.


Root Cause Analysis

The issue is in packages/wrangler/src/update-check.ts:

async function doUpdateCheck(): Promise<string | undefined> {
  let update: Result | null = null;
  const pkg = { name: wranglerName, version: wranglerVersion };
  try {
    update = await checkForUpdate(pkg, {
      distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
    });
  } catch {
    // ignore error
  }
  return update?.latest;
}

The update-check library simply fetches dist-tags.latest from npm and compares versions. It does not:

  1. Check if the latest version is deprecated
  2. Look for an alternative non-deprecated version

The npm registry does provide deprecation info:

// curl https://registry.npmjs.org/wrangler/4.14.2
{
  "version": "4.14.2",
  "deprecated": "This version has a bug in wrangler dev..."
}

Proposed Solution

Replace the update-check library with a custom implementation that checks for deprecation. Here's the approach:

Option 1: Custom npm registry query (Recommended)

// packages/wrangler/src/update-check.ts
import https from "node:https";
import { name as wranglerName, version as wranglerVersion } from "../package.json";

interface NpmPackageInfo {
  "dist-tags": Record<string, string>;
  versions: Record<string, { deprecated?: string }>;
}

async function fetchPackageInfo(packageName: string): Promise<NpmPackageInfo | null> {
  return new Promise((resolve) => {
    const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
    
    https.get(url, { 
      headers: { accept: "application/json" },
      timeout: 5000 
    }, (res) => {
      if (res.statusCode !== 200) {
        resolve(null);
        res.resume();
        return;
      }
      
      let data = "";
      res.on("data", (chunk) => (data += chunk));
      res.on("end", () => {
        try {
          resolve(JSON.parse(data));
        } catch {
          resolve(null);
        }
      });
    }).on("error", () => resolve(null));
  });
}

function findLatestNonDeprecated(pkgInfo: NpmPackageInfo, distTag: string): string | undefined {
  const taggedVersion = pkgInfo["dist-tags"][distTag];
  if (!taggedVersion) return undefined;
  
  // If the tagged version is not deprecated, use it
  const versionInfo = pkgInfo.versions[taggedVersion];
  if (!versionInfo?.deprecated) {
    return taggedVersion;
  }
  
  // Find the highest non-deprecated version
  const versions = Object.entries(pkgInfo.versions)
    .filter(([, info]) => !info.deprecated)
    .map(([ver]) => ver)
    .sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
  
  return versions[0];
}

async function doUpdateCheck(): Promise<string | undefined> {
  const distTag = wranglerVersion.startsWith("0.0.0") ? "beta" : "latest";
  
  try {
    const pkgInfo = await fetchPackageInfo(wranglerName);
    if (!pkgInfo) return undefined;
    
    const latestVersion = findLatestNonDeprecated(pkgInfo, distTag);
    if (!latestVersion) return undefined;
    
    // Compare versions
    if (latestVersion.localeCompare(wranglerVersion, undefined, { numeric: true }) > 0) {
      return latestVersion;
    }
  } catch {
    // Ignore errors - update check is non-critical
  }
  
  return undefined;
}

let updateCheckPromise: Promise<string | undefined>;
export function updateCheck(): Promise<string | undefined> {
  return (updateCheckPromise ??= doUpdateCheck());
}

Option 2: Keep update-check but add deprecation verification

// packages/wrangler/src/update-check.ts
import https from "node:https";
import checkForUpdate from "update-check";
import { name as wranglerName, version as wranglerVersion } from "../package.json";

async function isVersionDeprecated(version: string): Promise<boolean> {
  return new Promise((resolve) => {
    const url = `https://registry.npmjs.org/${wranglerName}/${version}`;
    
    https.get(url, { 
      headers: { accept: "application/json" },
      timeout: 3000 
    }, (res) => {
      if (res.statusCode !== 200) {
        resolve(false);
        res.resume();
        return;
      }
      
      let data = "";
      res.on("data", (chunk) => (data += chunk));
      res.on("end", () => {
        try {
          const info = JSON.parse(data);
          resolve(!!info.deprecated);
        } catch {
          resolve(false);
        }
      });
    }).on("error", () => resolve(false));
  });
}

async function doUpdateCheck(): Promise<string | undefined> {
  const pkg = { name: wranglerName, version: wranglerVersion };
  
  try {
    const update = await checkForUpdate(pkg, {
      distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
    });
    
    if (update?.latest) {
      // Skip deprecated versions
      if (await isVersionDeprecated(update.latest)) {
        return undefined; // Don't suggest deprecated version
      }
      return update.latest;
    }
  } catch {
    // Ignore errors
  }
  
  return undefined;
}

let updateCheckPromise: Promise<string | undefined>;
export function updateCheck(): Promise<string | undefined> {
  return (updateCheckPromise ??= doUpdateCheck());
}

Implementation Difficulty

Medium

Justification:

  • Code changes are straightforward: The fix is isolated to a single file (update-check.ts)
  • No breaking changes: The API remains the same
  • Testing complexity: Need to mock npm registry responses and test various deprecation scenarios
  • Dependency consideration: Option 1 removes the update-check dependency, which simplifies the codebase but requires implementing caching logic if desired

Files to Modify

  1. packages/wrangler/src/update-check.ts - Main implementation
  2. packages/wrangler/src/__tests__/update-check.test.ts - Tests (may need to create)
  3. packages/wrangler/package.json - Remove update-check dependency (if using Option 1)

Testing Recommendations

  1. Unit tests for update-check.ts:

    • Mock npm registry responses
    • Test case: latest version is NOT deprecated → return latest version
    • Test case: latest version IS deprecated → return undefined (Option 2) or find non-deprecated (Option 1)
    • Test case: network error → return undefined gracefully
    • Test case: timeout → return undefined gracefully
  2. Integration test:

    • Run wrangler with a mock npm registry that returns a deprecated latest version
    • Verify the banner does not suggest the deprecated version
  3. Manual testing:

    • Temporarily deprecate a test package version
    • Run wrangler and verify behavior

Example Test

// packages/wrangler/src/__tests__/update-check.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import https from "node:https";

describe("updateCheck", () => {
  beforeEach(() => {
    vi.resetModules();
  });

  it("should skip deprecated versions", async () => {
    // Mock npm registry response
    vi.spyOn(https, "get").mockImplementation((url, opts, cb) => {
      const mockRes = {
        statusCode: 200,
        on: (event: string, handler: Function) => {
          if (event === "data") {
            handler(JSON.stringify({
              "dist-tags": { latest: "4.14.2" },
              versions: {
                "4.14.1": {},
                "4.14.2": { deprecated: "Bug found" }
              }
            }));
          }
          if (event === "end") handler();
          return mockRes;
        },
        resume: () => {}
      };
      if (typeof cb === "function") cb(mockRes);
      return { on: () => ({}) } as any;
    });

    const { updateCheck } = await import("../update-check");
    const result = await updateCheck();
    
    // Should not return the deprecated version
    expect(result).not.toBe("4.14.2");
  });
});

Notes & Feedback (0)

No notes yet.

Add Note