#9154 Wrangler upgrade check does not respect deprecated packages
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.
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.tsuses theupdate-checknpm package which only checksdist-tags.latestwithout verifying deprecation status - npm registry provides
deprecatedfield in version metadata (verified:wrangler@4.14.2has deprecation message) - The
update-checklibrary (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:
- Check if the
latestversion is deprecated - 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-checkdependency, which simplifies the codebase but requires implementing caching logic if desired
Files to Modify
packages/wrangler/src/update-check.ts- Main implementationpackages/wrangler/src/__tests__/update-check.test.ts- Tests (may need to create)packages/wrangler/package.json- Removeupdate-checkdependency (if using Option 1)
Testing Recommendations
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
Integration test:
- Run wrangler with a mock npm registry that returns a deprecated latest version
- Verify the banner does not suggest the deprecated version
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.