Skip to content

feat(repo): add utility scripts#8447

Open
feywind wants to merge 1 commit into
mainfrom
feywind-add-scripts
Open

feat(repo): add utility scripts#8447
feywind wants to merge 1 commit into
mainfrom
feywind-add-scripts

Conversation

@feywind
Copy link
Copy Markdown
Contributor

@feywind feywind commented Jun 5, 2026

So far there's just the one: a release version discrepancy checker. I'm holding this open a bit though in case anyone else has something.

@feywind feywind requested a review from a team as a code owner June 5, 2026 20:46
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new Node.js script, bin/check-release-discrepancies.cjs, designed to scan the monorepo for public packages and compare their local versions against the NPM registry. The feedback highlights several key areas for improvement: enhancing the fetch retry logic to handle transient 5xx errors and parse HTTP-date retry-after headers; correcting the script name in the help message; narrowing the directory exclusion filter to prevent skipping valid packages containing the word 'template'; utilizing the standard semver package instead of a custom version parser; and restricting progress bar updates to TTY environments to keep CI/CD logs clean.

Comment on lines +165 to +183
// Fetch wrapper with retry and exponential backoff on 429 rate limit
async function fetchWithRetry(url, options = {}, retries = 6, delay = 3000) {
try {
const res = await fetch(url, options);
if (res.status === 429 && retries > 0) {
const retryAfterHeader = res.headers.get('retry-after');
const waitTime = retryAfterHeader ? (parseInt(retryAfterHeader, 10) * 1000) : delay;
await new Promise(resolve => setTimeout(resolve, waitTime));
return fetchWithRetry(url, options, retries - 1, delay * 2);
}
return res;
} catch (err) {
if (retries > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
return fetchWithRetry(url, options, retries - 1, delay * 2);
}
throw err;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are two issues with the current retry logic:\n1. It only retries on HTTP 429 and network exceptions, but transient 5xx server errors (e.g., 500, 502, 503, 504) should also be retried.\n2. The retry-after header can be either a number of seconds or an HTTP-date (per RFC 7231). If it is an HTTP-date, parseInt will return NaN, causing the script to retry immediately without waiting. Parsing the date fallback correctly prevents this.

// Fetch wrapper with retry and exponential backoff on transient errors\nasync function fetchWithRetry(url, options = {}, retries = 6, delay = 3000) {\n  try {\n    const res = await fetch(url, options);\n    const isTransientError = res.status === 429 || (res.status >= 500 && res.status < 600);\n    if (isTransientError && retries > 0) {\n      let waitTime = delay;\n      if (res.status === 429) {\n        const retryAfterHeader = res.headers.get('retry-after');\n        if (retryAfterHeader) {\n          const parsedSeconds = parseInt(retryAfterHeader, 10);\n          if (!isNaN(parsedSeconds)) {\n            waitTime = parsedSeconds * 1000;\n          } else {\n            const parsedDate = Date.parse(retryAfterHeader);\n            if (!isNaN(parsedDate)) {\n              waitTime = Math.max(0, parsedDate - Date.now());\n            }\n          }\n        }\n      }\n      await new Promise(resolve => setTimeout(resolve, waitTime));\n      return fetchWithRetry(url, options, retries - 1, delay * 2);\n    }\n    return res;\n  } catch (err) {\n    if (retries > 0) {\n      await new Promise(resolve => setTimeout(resolve, delay));\n      return fetchWithRetry(url, options, retries - 1, delay * 2);\n    }\n    throw err;\n  }\n}

console.log(`${BOLD}google-cloud-node Package Release Discrepancy Checker${RESET}`);
console.log('\nChecks all local Node.js packages against the NPM registry to find released version mismatches.\n');
console.log(`${BOLD}Usage:${RESET}`);
console.log(' ./bin/check-release-discrepancies.sh [options]\n');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The help message references ./bin/check-release-discrepancies.sh, but the script is named check-release-discrepancies.cjs. Updating this ensures the usage instructions are accurate.

  console.log('  ./bin/check-release-discrepancies.cjs [options]\n');

Comment on lines +62 to +64
name === 'templates' ||
name === 'template' ||
name.includes('template')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using name.includes('template') is overly broad and redundant. It will accidentally skip any legitimate package directory that contains the word 'template' (e.g., google-cloud-template-parser). It is safer to match exact directory names like template and templates.

        name === 'templates' ||\n        name === 'template'

Comment on lines +111 to +133
function compareVersions(v1, v2) {
const clean = (v) => v.replace(/^[^0-9]+/, '');
const parts1 = clean(v1).split(/[-+]/)[0].split('.').map(Number);
const parts2 = clean(v2).split(/[-+]/)[0].split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}

const idx1 = clean(v1).indexOf('-');
const idx2 = clean(v2).indexOf('-');
const pre1 = idx1 !== -1 ? clean(v1).substring(idx1 + 1) : null;
const pre2 = idx2 !== -1 ? clean(v2).substring(idx2 + 1) : null;

if (pre1 && !pre2) return -1;
if (!pre1 && pre2) return 1;
if (pre1 && pre2) {
return pre1.localeCompare(pre2, undefined, { numeric: true, sensitivity: 'base' });
}
return 0;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Implementing a custom SemVer parser is prone to subtle bugs. For example, the current implementation does not correctly strip build metadata (e.g., +build) from prerelease tags before comparing them, and comparing non-numeric characters (like NaN from invalid version formats) will result in incorrect comparisons. Since this is a Node.js monorepo, using the standard semver package is highly recommended for robustness.

function compareVersions(v1, v2) {\n  const semver = require('semver');\n  return semver.compare(v1, v2);\n}

Comment on lines +155 to +163
function updateProgress() {
const total = packages.length;
const percentage = Math.round((completed / total) * 100);
const width = 30;
const filledWidth = Math.round((percentage / 100) * width);
const emptyWidth = width - filledWidth;
const bar = '='.repeat(filledWidth) + (filledWidth < width ? '>' : '') + ' '.repeat(Math.max(0, emptyWidth - 1));
process.stdout.write(`\r\x1b[36m[Checking NPM Registry]\x1b[0m Progress: ${percentage}% [${bar}] ${completed}/${total}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Writing carriage returns (\\r) and ANSI escape codes to stdout on every completed task can severely clutter logs in non-TTY environments (such as CI/CD pipelines). Checking process.stdout.isTTY before printing the progress bar avoids this issue.

function updateProgress() {\n  if (!process.stdout.isTTY) {\n    return;\n  }\n  const total = packages.length;\n  const percentage = Math.round((completed / total) * 100);\n  const width = 30;\n  const filledWidth = Math.round((percentage / 100) * width);\n  const emptyWidth = width - filledWidth;\n  const bar = '='.repeat(filledWidth) + (filledWidth < width ? '>' : '') + ' '.repeat(Math.max(0, emptyWidth - 1));\n  process.stdout.write(`\\r\\x1b[36m[Checking NPM Registry]\\x1b[0m Progress: ${percentage}% [${bar}] ${completed}/${total}`);\n}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant