From 7575e5b9bd908cb1c8d7a7a52a506449ca2c1411 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 24 Jul 2026 14:54:47 -0700 Subject: [PATCH 01/20] fix(ci): improve changed file detection in linter script --- bin/linter.mjs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index bd9a976ca944..2e9dd5fb1aef 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -68,11 +68,23 @@ function runGit(args, options = {}) { */ function getChangedFiles() { const base = process.env.GITHUB_BASE_REF || 'main'; + + // Attempt to fetch the base branch and set origin/${base} so it doesn't compare against itself + if (process.env.GITHUB_BASE_REF) { + try { + runGit(['fetch', 'origin', base, '--depth=1']); + } catch { + // Continue if network fetch fails or remote does not exist + } + } + const refsToTry = [ + `origin/${base}...HEAD`, + `${base}...HEAD`, + `upstream/${base}...HEAD`, + `origin/${base}`, base, `upstream/${base}`, - `origin/${base}`, - 'FETCH_HEAD', 'HEAD~1', 'HEAD^', ]; From 00944ff14232be83d4b1cd21e15e720395b15ff3 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 24 Jul 2026 15:15:04 -0700 Subject: [PATCH 02/20] fix(ci): use explicit refspec when fetching base branch --- bin/linter.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 2e9dd5fb1aef..b36f28aa8d00 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -72,7 +72,12 @@ function getChangedFiles() { // Attempt to fetch the base branch and set origin/${base} so it doesn't compare against itself if (process.env.GITHUB_BASE_REF) { try { - runGit(['fetch', 'origin', base, '--depth=1']); + runGit([ + 'fetch', + 'origin', + `+refs/heads/${base}:refs/remotes/origin/${base}`, + '--depth=1', + ]); } catch { // Continue if network fetch fails or remote does not exist } From 70084eebcf7667acbdc17348dab5876e92ac44a2 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 27 Jul 2026 13:33:09 -0700 Subject: [PATCH 03/20] fix(linter): use namespace import for typescript ESM compatibility --- bin/linter.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index b36f28aa8d00..7d4a802a0dcb 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -17,7 +17,7 @@ import {existsSync} from 'fs'; import path from 'path'; import {promisify} from 'util'; import {ESLint} from 'eslint'; -import ts from 'typescript'; +import * as ts from 'typescript'; // --- Globals & Promisified API Wrappers --- const execFileAsync = promisify(execFile); From ba238997434b29adaaeba0e596554658bcdf1323 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 14:08:07 -0700 Subject: [PATCH 04/20] fix(linter): make changed file resolution hermetic and improve base ref detection --- bin/linter.mjs | 111 +++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 7d4a802a0dcb..eb6afb9eacfe 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -63,73 +63,84 @@ function runGit(args, options = {}) { }); } +/** + * Helper to get modified/added TypeScript files against a given git ref. + * Uses merge-base when possible to compare against the common ancestor (e.g. when base and branch have both moved). + */ +function getDiffFiles(ref) { + let diffTarget = ref; + try { + const mergeBase = runGit(['merge-base', ref, 'HEAD']).trim(); + if (mergeBase) { + diffTarget = mergeBase; + } + } catch { + // If merge-base fails (e.g. shallow clone or invalid ref), fall back to using ref directly + } + + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + diffTarget, + '--', + '*.ts', + ]); + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); +} + /** * Returns a list of changed TypeScript files comparing against target branches/references. */ function getChangedFiles() { - const base = process.env.GITHUB_BASE_REF || 'main'; + const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); - // Attempt to fetch the base branch and set origin/${base} so it doesn't compare against itself - if (process.env.GITHUB_BASE_REF) { + if (isCI) { + const baseRef = process.env.GITHUB_BASE_REF; + if (!baseRef) { + throw new Error('Running in CI but GITHUB_BASE_REF environment variable is not set.'); + } try { - runGit([ - 'fetch', - 'origin', - `+refs/heads/${base}:refs/remotes/origin/${base}`, - '--depth=1', - ]); + const files = getDiffFiles(baseRef); + console.log(`Comparing against base reference: ${baseRef}`); + return files; } catch { - // Continue if network fetch fails or remote does not exist + throw new Error(`Failed to determine changed files against GITHUB_BASE_REF '${baseRef}' in CI.`); } } - const refsToTry = [ - `origin/${base}...HEAD`, - `${base}...HEAD`, - `upstream/${base}...HEAD`, - `origin/${base}`, - base, - `upstream/${base}`, - 'HEAD~1', - 'HEAD^', - ]; + let currentBranch = ''; + try { + currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); + } catch { + // Continue with fallback refs if branch detection fails + } + + if (currentBranch === 'main') { + try { + const files = getDiffFiles('HEAD~1'); + console.log('Comparing against base reference: HEAD~1'); + return files; + } catch { + throw new Error("Failed to determine changed files against 'HEAD~1' on main branch."); + } + } + const refsToTry = ['upstream/main', 'origin/main', 'main']; for (const ref of refsToTry) { try { - const output = runGit([ - 'diff', - '--name-only', - '--diff-filter=ACMRT', - ref, - '--', - '*.ts', - ]); - return output - .split('\n') - .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); + const files = getDiffFiles(ref); + console.log(`Comparing against base reference: ${ref}`); + return files; } catch { - // Continue to the next fallback ref + // Continue to next ref if this one fails/does not exist } } - // Fallback to checking uncommitted working tree changes against HEAD if all specific refs fail - try { - const output = runGit([ - 'diff', - '--name-only', - '--diff-filter=ACMRT', - 'HEAD', - '--', - '*.ts', - ]); - return output - .split('\n') - .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); - } catch { - return []; - } + throw new Error(`Failed to determine changed files. Tried refs: ${refsToTry.join(', ')}`); } // --- ESLint Checker --- From d795744ff304b78409d5980f998e918e8184f6bf Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 14:10:50 -0700 Subject: [PATCH 05/20] fix(linter): use default import for typescript in ESM module --- bin/linter.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index eb6afb9eacfe..75e18c6084e5 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -17,7 +17,7 @@ import {existsSync} from 'fs'; import path from 'path'; import {promisify} from 'util'; import {ESLint} from 'eslint'; -import * as ts from 'typescript'; +import ts from 'typescript'; // --- Globals & Promisified API Wrappers --- const execFileAsync = promisify(execFile); From 290434b27710e80a0e39aa320b63aefc9186e319 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 14:16:43 -0700 Subject: [PATCH 06/20] fix(linter): check remote main refs before HEAD~1 when on main branch --- bin/linter.mjs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 75e18c6084e5..c8424a44825d 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -120,13 +120,17 @@ function getChangedFiles() { } if (currentBranch === 'main') { - try { - const files = getDiffFiles('HEAD~1'); - console.log('Comparing against base reference: HEAD~1'); - return files; - } catch { - throw new Error("Failed to determine changed files against 'HEAD~1' on main branch."); + const mainRefs = ['origin/main', 'upstream/main', 'HEAD~1']; + for (const ref of mainRefs) { + try { + const files = getDiffFiles(ref); + console.log(`Comparing against base reference: ${ref}`); + return files; + } catch { + // Continue to next ref + } } + throw new Error("Failed to determine changed files on main branch."); } const refsToTry = ['upstream/main', 'origin/main', 'main']; From b0accfe4c0575b331f821aefc7c37e1045c8bd36 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 14:35:53 -0700 Subject: [PATCH 07/20] refactor(linter): consolidate getChangedFiles reference resolution logic --- bin/linter.mjs | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index c8424a44825d..e499490369f7 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -94,46 +94,34 @@ function getDiffFiles(ref) { /** * Returns a list of changed TypeScript files comparing against target branches/references. + * Fully hermetic (uses local git references and merge-base with no network calls). */ function getChangedFiles() { const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); + let refsToTry = []; + let isStrictCI = false; + if (isCI) { const baseRef = process.env.GITHUB_BASE_REF; if (!baseRef) { throw new Error('Running in CI but GITHUB_BASE_REF environment variable is not set.'); } + refsToTry = [baseRef]; + isStrictCI = true; + } else { + let currentBranch = ''; try { - const files = getDiffFiles(baseRef); - console.log(`Comparing against base reference: ${baseRef}`); - return files; + currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); } catch { - throw new Error(`Failed to determine changed files against GITHUB_BASE_REF '${baseRef}' in CI.`); + // Continue with fallback refs if branch detection fails } - } - let currentBranch = ''; - try { - currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); - } catch { - // Continue with fallback refs if branch detection fails + refsToTry = currentBranch === 'main' + ? ['origin/main', 'upstream/main', 'HEAD~1'] + : ['upstream/main', 'origin/main', 'main']; } - if (currentBranch === 'main') { - const mainRefs = ['origin/main', 'upstream/main', 'HEAD~1']; - for (const ref of mainRefs) { - try { - const files = getDiffFiles(ref); - console.log(`Comparing against base reference: ${ref}`); - return files; - } catch { - // Continue to next ref - } - } - throw new Error("Failed to determine changed files on main branch."); - } - - const refsToTry = ['upstream/main', 'origin/main', 'main']; for (const ref of refsToTry) { try { const files = getDiffFiles(ref); @@ -144,6 +132,10 @@ function getChangedFiles() { } } + if (isStrictCI) { + throw new Error(`Failed to determine changed files against GITHUB_BASE_REF '${refsToTry[0]}' in CI.`); + } + throw new Error(`Failed to determine changed files. Tried refs: ${refsToTry.join(', ')}`); } From e2c5ef3f47895ece0e1feda7aeb9e9afd130bb47 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 14:37:59 -0700 Subject: [PATCH 08/20] chore(linter): clean up comments and format branch check in linter.mjs --- bin/linter.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index e499490369f7..973973a39b69 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -94,7 +94,6 @@ function getDiffFiles(ref) { /** * Returns a list of changed TypeScript files comparing against target branches/references. - * Fully hermetic (uses local git references and merge-base with no network calls). */ function getChangedFiles() { const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); @@ -117,7 +116,7 @@ function getChangedFiles() { // Continue with fallback refs if branch detection fails } - refsToTry = currentBranch === 'main' + refsToTry = (currentBranch === 'main') ? ['origin/main', 'upstream/main', 'HEAD~1'] : ['upstream/main', 'origin/main', 'main']; } From 49c80ceffbe6bd5b2e41cc58c9a43194a8ce577b Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Thu, 30 Jul 2026 15:46:55 -0700 Subject: [PATCH 09/20] fix(linter): resolve origin/baseRef in CI for actions/checkout compatibility --- bin/linter.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 973973a39b69..901384f8a95c 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -106,7 +106,7 @@ function getChangedFiles() { if (!baseRef) { throw new Error('Running in CI but GITHUB_BASE_REF environment variable is not set.'); } - refsToTry = [baseRef]; + refsToTry = baseRef.startsWith('origin/') ? [baseRef] : [`origin/${baseRef}`, baseRef]; isStrictCI = true; } else { let currentBranch = ''; From 5b42816a8bc11f4899baf72f3ec7325c5db5f724 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 11:51:21 -0700 Subject: [PATCH 10/20] feat(linter): add --strict mode changed file detection --- .github/workflows/presubmit.yaml | 2 +- bin/linter.mjs | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index dc152e2bec88..2637066ed0cd 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -41,5 +41,5 @@ jobs: with: node-version: 24 - run: npm install - - run: npm run lint + - run: node ./bin/linter.mjs --strict --git-diff-arg "origin/${{ github.base_ref }}...HEAD" name: Run monorepo linter diff --git a/bin/linter.mjs b/bin/linter.mjs index 901384f8a95c..0fd1e47a2eed 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -92,10 +92,75 @@ function getDiffFiles(ref) { .filter(f => f.length > 0 && existsSync(f)); } +/** + * Helper to get modified/added TypeScript files in strict mode given GIT_DIFF_ARG. + * Validates that `git diff --quiet ${gitDiffArg}` succeeds (exit code 0 or 1). + */ +function getStrictDiffFiles(gitDiffArg) { + const args = gitDiffArg.trim().split(/\s+/); + + try { + runGit(['diff', '--quiet', ...args]); + } catch (err) { + if (err.status !== 1) { + throw new Error( + `Strict mode error: git diff --quiet ${gitDiffArg} failed with exit code ${err.status}.\n` + + `Ensure that the git reference '${gitDiffArg}' exists locally and that you have fetched the required commits/branches.\n` + + `Details: ${String(err.stderr || err.message || '').trim()}` + ); + } + } + + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + ...args, + '--', + '*.ts', + ]); + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); +} + +/** + * Retrieves the GIT_DIFF_ARG from CLI flags or environment variable. + */ +function getGitDiffArg() { + const cliIndex = process.argv.findIndex(arg => arg.startsWith('--git-diff-arg')); + if (cliIndex !== -1) { + const arg = process.argv[cliIndex]; + if (arg.includes('=')) { + return arg.substring(arg.indexOf('=') + 1); + } + if (process.argv[cliIndex + 1] && !process.argv[cliIndex + 1].startsWith('-')) { + return process.argv[cliIndex + 1]; + } + } + return process.env.GIT_DIFF_ARG; +} + /** * Returns a list of changed TypeScript files comparing against target branches/references. */ function getChangedFiles() { + const isStrict = process.argv.includes('--strict'); + + const gitDiffArg = getGitDiffArg(); + + if (isStrict) { + if (!gitDiffArg) { + throw new Error( + 'Strict mode is enabled (--strict), but GIT_DIFF_ARG was not provided. ' + + 'Please supply --git-diff-arg or set the GIT_DIFF_ARG environment variable.' + ); + } + console.log(`Strict mode enabled. Comparing using GIT_DIFF_ARG: ${gitDiffArg}`); + return getStrictDiffFiles(gitDiffArg); + } + const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); let refsToTry = []; From 3db57ca9b238ac24af2ecc75660648071023f298 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 11:51:21 -0700 Subject: [PATCH 11/20] fix(ci): fetch base branch in presubmit workflow for strict linter diff --- .github/workflows/presubmit.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index 2637066ed0cd..be6631a56c28 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -36,6 +36,8 @@ jobs: with: fetch-depth: 300 persist-credentials: false + - name: Fetch base branch for linter diff + run: git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} - name: Use Node.js 24 uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: From da4b704763a56900abd65ecb737a646101f1161e Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:47:29 -0700 Subject: [PATCH 12/20] refactor(linter): use environment variables for strict mode diff logic --- .github/workflows/presubmit.yaml | 5 +- bin/linter.mjs | 101 +++++++++++++------------------ 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index be6631a56c28..74035893d235 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -43,5 +43,8 @@ jobs: with: node-version: 24 - run: npm install - - run: node ./bin/linter.mjs --strict --git-diff-arg "origin/${{ github.base_ref }}...HEAD" + - run: node ./bin/linter.mjs name: Run monorepo linter + env: + STRICT: "true" + GIT_DIFF_ARG: "origin/${{ github.base_ref }}...HEAD" diff --git a/bin/linter.mjs b/bin/linter.mjs index 0fd1e47a2eed..55557ae0f7a3 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -26,7 +26,13 @@ const tsconfigCache = new Map(); // --- Main Runner (Entry Point) --- async function run() { try { - const changedTsFiles = getChangedFiles(); + const isStrict = Boolean(process.env.STRICT); + let changedTsFiles; + if (isStrict) { + changedTsFiles = getChangedFilesStrict(); + } else { + changedTsFiles = getChangedFiles(); + } if (changedTsFiles.length === 0) { console.log('No TypeScript files changed. Skipping checks.'); @@ -63,40 +69,18 @@ function runGit(args, options = {}) { }); } -/** - * Helper to get modified/added TypeScript files against a given git ref. - * Uses merge-base when possible to compare against the common ancestor (e.g. when base and branch have both moved). - */ -function getDiffFiles(ref) { - let diffTarget = ref; - try { - const mergeBase = runGit(['merge-base', ref, 'HEAD']).trim(); - if (mergeBase) { - diffTarget = mergeBase; - } - } catch { - // If merge-base fails (e.g. shallow clone or invalid ref), fall back to using ref directly +function getChangedFilesStrict() { + const gitDiffArg = getGitDiffArg(); + + if (!gitDiffArg) { + throw new Error( + 'Strict mode is enabled, but GIT_DIFF_ARG environment variable was not provided. ' + + 'Please set the GIT_DIFF_ARG environment variable.' + ); } - const output = runGit([ - 'diff', - '--name-only', - '--diff-filter=ACMRT', - diffTarget, - '--', - '*.ts', - ]); - return output - .split('\n') - .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); -} + console.log(`Strict mode enabled. Comparing using GIT_DIFF_ARG: ${gitDiffArg}`); -/** - * Helper to get modified/added TypeScript files in strict mode given GIT_DIFF_ARG. - * Validates that `git diff --quiet ${gitDiffArg}` succeeds (exit code 0 or 1). - */ -function getStrictDiffFiles(gitDiffArg) { const args = gitDiffArg.trim().split(/\s+/); try { @@ -125,41 +109,42 @@ function getStrictDiffFiles(gitDiffArg) { .filter(f => f.length > 0 && existsSync(f)); } +function getGitDiffArg() { + return process.env.GIT_DIFF_ARG; +} + /** - * Retrieves the GIT_DIFF_ARG from CLI flags or environment variable. + * Helper to get modified/added TypeScript files against a given git ref when not in "strict mode" */ -function getGitDiffArg() { - const cliIndex = process.argv.findIndex(arg => arg.startsWith('--git-diff-arg')); - if (cliIndex !== -1) { - const arg = process.argv[cliIndex]; - if (arg.includes('=')) { - return arg.substring(arg.indexOf('=') + 1); - } - if (process.argv[cliIndex + 1] && !process.argv[cliIndex + 1].startsWith('-')) { - return process.argv[cliIndex + 1]; +function getDiffFiles(ref) { + let diffTarget = ref; + try { + const mergeBase = runGit(['merge-base', ref, 'HEAD']).trim(); + if (mergeBase) { + diffTarget = mergeBase; } + } catch { + // If merge-base fails, fall back to using ref directly } - return process.env.GIT_DIFF_ARG; + + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + diffTarget, + '--', + '*.ts', + ]); + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); } /** - * Returns a list of changed TypeScript files comparing against target branches/references. + * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode */ function getChangedFiles() { - const isStrict = process.argv.includes('--strict'); - - const gitDiffArg = getGitDiffArg(); - - if (isStrict) { - if (!gitDiffArg) { - throw new Error( - 'Strict mode is enabled (--strict), but GIT_DIFF_ARG was not provided. ' + - 'Please supply --git-diff-arg or set the GIT_DIFF_ARG environment variable.' - ); - } - console.log(`Strict mode enabled. Comparing using GIT_DIFF_ARG: ${gitDiffArg}`); - return getStrictDiffFiles(gitDiffArg); - } const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); From 28667f27266cb12474a51d8fb4da852b6de201bc Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:49:06 -0700 Subject: [PATCH 13/20] style(linter): clean up unused space in getChangedFiles() --- bin/linter.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 55557ae0f7a3..0368426a6fb4 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -145,7 +145,6 @@ function getDiffFiles(ref) { * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode */ function getChangedFiles() { - const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); let refsToTry = []; From 409044a7016111a7d6f43331ed5b19ec53521353 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:50:38 -0700 Subject: [PATCH 14/20] refactor(linter): simplify getChangedFiles for default non-strict mode --- bin/linter.mjs | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 0368426a6fb4..8d71ae3f4de8 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -145,31 +145,17 @@ function getDiffFiles(ref) { * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode */ function getChangedFiles() { - const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITHUB_BASE_REF); - - let refsToTry = []; - let isStrictCI = false; - - if (isCI) { - const baseRef = process.env.GITHUB_BASE_REF; - if (!baseRef) { - throw new Error('Running in CI but GITHUB_BASE_REF environment variable is not set.'); - } - refsToTry = baseRef.startsWith('origin/') ? [baseRef] : [`origin/${baseRef}`, baseRef]; - isStrictCI = true; - } else { - let currentBranch = ''; - try { - currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); - } catch { - // Continue with fallback refs if branch detection fails - } - - refsToTry = (currentBranch === 'main') - ? ['origin/main', 'upstream/main', 'HEAD~1'] - : ['upstream/main', 'origin/main', 'main']; + let currentBranch = ''; + try { + currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); + } catch { + // Continue with fallback refs if branch detection fails } + const refsToTry = (currentBranch === 'main') + ? ['origin/main', 'upstream/main', 'HEAD~1'] + : ['upstream/main', 'origin/main', 'main']; + for (const ref of refsToTry) { try { const files = getDiffFiles(ref); @@ -180,10 +166,6 @@ function getChangedFiles() { } } - if (isStrictCI) { - throw new Error(`Failed to determine changed files against GITHUB_BASE_REF '${refsToTry[0]}' in CI.`); - } - throw new Error(`Failed to determine changed files. Tried refs: ${refsToTry.join(', ')}`); } From fd811c915e4a66c7814b4c6158d058ceaa2b2a05 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:54:13 -0700 Subject: [PATCH 15/20] refactor(linter): inline process.env.GIT_DIFF_ARG directly in getChangedFilesStrict --- bin/linter.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 8d71ae3f4de8..94bd541ff832 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -70,7 +70,7 @@ function runGit(args, options = {}) { } function getChangedFilesStrict() { - const gitDiffArg = getGitDiffArg(); + const gitDiffArg = process.env.GIT_DIFF_ARG; if (!gitDiffArg) { throw new Error( @@ -109,10 +109,6 @@ function getChangedFilesStrict() { .filter(f => f.length > 0 && existsSync(f)); } -function getGitDiffArg() { - return process.env.GIT_DIFF_ARG; -} - /** * Helper to get modified/added TypeScript files against a given git ref when not in "strict mode" */ From 00807c308d7a5a17a57d7060e53d6a1c8b2cc238 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:59:59 -0700 Subject: [PATCH 16/20] refactor(linter): use git diff ref...HEAD directly in getChangedFiles --- bin/linter.mjs | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 94bd541ff832..acf3a27be8a7 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -110,35 +110,7 @@ function getChangedFilesStrict() { } /** - * Helper to get modified/added TypeScript files against a given git ref when not in "strict mode" - */ -function getDiffFiles(ref) { - let diffTarget = ref; - try { - const mergeBase = runGit(['merge-base', ref, 'HEAD']).trim(); - if (mergeBase) { - diffTarget = mergeBase; - } - } catch { - // If merge-base fails, fall back to using ref directly - } - - const output = runGit([ - 'diff', - '--name-only', - '--diff-filter=ACMRT', - diffTarget, - '--', - '*.ts', - ]); - return output - .split('\n') - .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); -} - -/** - * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode + * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode. */ function getChangedFiles() { let currentBranch = ''; @@ -154,9 +126,19 @@ function getChangedFiles() { for (const ref of refsToTry) { try { - const files = getDiffFiles(ref); + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + `${ref}...HEAD`, + '--', + '*.ts', + ]); console.log(`Comparing against base reference: ${ref}`); - return files; + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); } catch { // Continue to next ref if this one fails/does not exist } From 384a07972aa8a930b6f0d51bc87e30b6f1006944 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 09:44:52 -0700 Subject: [PATCH 17/20] fix(linter): align strict mode CI diff logic with PR #9021 --- .github/workflows/presubmit.yaml | 6 ++---- bin/linter.mjs | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index 74035893d235..797117a8f125 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -34,10 +34,8 @@ jobs: steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: - fetch-depth: 300 + fetch-depth: 2 persist-credentials: false - - name: Fetch base branch for linter diff - run: git fetch origin ${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} - name: Use Node.js 24 uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: @@ -47,4 +45,4 @@ jobs: name: Run monorepo linter env: STRICT: "true" - GIT_DIFF_ARG: "origin/${{ github.base_ref }}...HEAD" + GIT_DIFF_ARG: "HEAD^1" diff --git a/bin/linter.mjs b/bin/linter.mjs index acf3a27be8a7..7fb04a8b1225 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -26,7 +26,7 @@ const tsconfigCache = new Map(); // --- Main Runner (Entry Point) --- async function run() { try { - const isStrict = Boolean(process.env.STRICT); + const isStrict = Boolean(process.env.STRICT || process.argv.includes('--strict')); let changedTsFiles; if (isStrict) { changedTsFiles = getChangedFilesStrict(); @@ -69,13 +69,27 @@ function runGit(args, options = {}) { }); } +function getGitDiffArg() { + const cliIndex = process.argv.findIndex(arg => arg.startsWith('--git-diff-arg')); + if (cliIndex !== -1) { + const arg = process.argv[cliIndex]; + if (arg.includes('=')) { + return arg.substring(arg.indexOf('=') + 1); + } + if (process.argv[cliIndex + 1] && !process.argv[cliIndex + 1].startsWith('-')) { + return process.argv[cliIndex + 1]; + } + } + return process.env.GIT_DIFF_ARG; +} + function getChangedFilesStrict() { - const gitDiffArg = process.env.GIT_DIFF_ARG; + const gitDiffArg = getGitDiffArg(); if (!gitDiffArg) { throw new Error( - 'Strict mode is enabled, but GIT_DIFF_ARG environment variable was not provided. ' + - 'Please set the GIT_DIFF_ARG environment variable.' + 'Strict mode is enabled, but GIT_DIFF_ARG environment variable or --git-diff-arg flag was not provided. ' + + 'Please set the GIT_DIFF_ARG environment variable or provide --git-diff-arg .' ); } From 3c3bd25b30d972fb8b3f2c01b144b3d80c0d98fb Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 15:03:56 -0700 Subject: [PATCH 18/20] revert(linter): restore getChangedFiles implementation from PR #8968 --- bin/linter.mjs | 52 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 7fb04a8b1225..bd8484d934d9 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -124,19 +124,30 @@ function getChangedFilesStrict() { } /** - * Returns a list of changed TypeScript files comparing against target branches/references when not in strict mode. + * Returns a list of changed TypeScript files comparing against target branches/references. */ function getChangedFiles() { - let currentBranch = ''; - try { - currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD']).trim(); - } catch { - // Continue with fallback refs if branch detection fails + const base = process.env.GITHUB_BASE_REF || 'main'; + + // Attempt to fetch the base branch and set origin/${base} so it doesn't compare against itself + if (process.env.GITHUB_BASE_REF) { + try { + runGit(['fetch', 'origin', base, '--depth=1']); + } catch { + // Continue if network fetch fails or remote does not exist + } } - const refsToTry = (currentBranch === 'main') - ? ['origin/main', 'upstream/main', 'HEAD~1'] - : ['upstream/main', 'origin/main', 'main']; + const refsToTry = [ + `origin/${base}...HEAD`, + `${base}...HEAD`, + `upstream/${base}...HEAD`, + `origin/${base}`, + base, + `upstream/${base}`, + 'HEAD~1', + 'HEAD^', + ]; for (const ref of refsToTry) { try { @@ -144,21 +155,36 @@ function getChangedFiles() { 'diff', '--name-only', '--diff-filter=ACMRT', - `${ref}...HEAD`, + ref, '--', '*.ts', ]); - console.log(`Comparing against base reference: ${ref}`); return output .split('\n') .map(f => f.trim()) .filter(f => f.length > 0 && existsSync(f)); } catch { - // Continue to next ref if this one fails/does not exist + // Continue to the next fallback ref } } - throw new Error(`Failed to determine changed files. Tried refs: ${refsToTry.join(', ')}`); + // Fallback to checking uncommitted working tree changes against HEAD if all specific refs fail + try { + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + 'HEAD', + '--', + '*.ts', + ]); + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); + } catch { + return []; + } } // --- ESLint Checker --- From 585034efceedb9f68650a94e0bdd7ff52a0560ee Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 15:12:03 -0700 Subject: [PATCH 19/20] refactor(linter): clean up strict mode handling and flags --- .github/workflows/presubmit.yaml | 3 +- bin/linter.mjs | 53 +++++++++----------------------- 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index 797117a8f125..c5de30eba54e 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -41,8 +41,7 @@ jobs: with: node-version: 24 - run: npm install - - run: node ./bin/linter.mjs + - run: node ./bin/linter.mjs --strict name: Run monorepo linter env: - STRICT: "true" GIT_DIFF_ARG: "HEAD^1" diff --git a/bin/linter.mjs b/bin/linter.mjs index bd8484d934d9..69f13bb234e9 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -26,7 +26,7 @@ const tsconfigCache = new Map(); // --- Main Runner (Entry Point) --- async function run() { try { - const isStrict = Boolean(process.env.STRICT || process.argv.includes('--strict')); + const isStrict = Boolean(process.argv.includes('--strict')); let changedTsFiles; if (isStrict) { changedTsFiles = getChangedFilesStrict(); @@ -69,22 +69,8 @@ function runGit(args, options = {}) { }); } -function getGitDiffArg() { - const cliIndex = process.argv.findIndex(arg => arg.startsWith('--git-diff-arg')); - if (cliIndex !== -1) { - const arg = process.argv[cliIndex]; - if (arg.includes('=')) { - return arg.substring(arg.indexOf('=') + 1); - } - if (process.argv[cliIndex + 1] && !process.argv[cliIndex + 1].startsWith('-')) { - return process.argv[cliIndex + 1]; - } - } - return process.env.GIT_DIFF_ARG; -} - function getChangedFilesStrict() { - const gitDiffArg = getGitDiffArg(); + const gitDiffArg = process.env.GIT_DIFF_ARG; if (!gitDiffArg) { throw new Error( @@ -98,7 +84,18 @@ function getChangedFilesStrict() { const args = gitDiffArg.trim().split(/\s+/); try { - runGit(['diff', '--quiet', ...args]); + const output = runGit([ + 'diff', + '--name-only', + '--diff-filter=ACMRT', + ...args, + '--', + '*.ts', + ]); + return output + .split('\n') + .map(f => f.trim()) + .filter(f => f.length > 0 && existsSync(f)); } catch (err) { if (err.status !== 1) { throw new Error( @@ -108,19 +105,6 @@ function getChangedFilesStrict() { ); } } - - const output = runGit([ - 'diff', - '--name-only', - '--diff-filter=ACMRT', - ...args, - '--', - '*.ts', - ]); - return output - .split('\n') - .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); } /** @@ -129,15 +113,6 @@ function getChangedFilesStrict() { function getChangedFiles() { const base = process.env.GITHUB_BASE_REF || 'main'; - // Attempt to fetch the base branch and set origin/${base} so it doesn't compare against itself - if (process.env.GITHUB_BASE_REF) { - try { - runGit(['fetch', 'origin', base, '--depth=1']); - } catch { - // Continue if network fetch fails or remote does not exist - } - } - const refsToTry = [ `origin/${base}...HEAD`, `${base}...HEAD`, From 21bad2f04f9c61f9ca973484134bea137779e039 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 15:13:13 -0700 Subject: [PATCH 20/20] style(linter): remove empty line in getChangedFiles --- bin/linter.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 69f13bb234e9..2eb8645c0881 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -112,7 +112,6 @@ function getChangedFilesStrict() { */ function getChangedFiles() { const base = process.env.GITHUB_BASE_REF || 'main'; - const refsToTry = [ `origin/${base}...HEAD`, `${base}...HEAD`,