diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index dc152e2bec8..74035893d23 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -36,10 +36,15 @@ 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: node-version: 24 - run: npm install - - run: npm run lint + - 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 bd9a976ca94..acf3a27be8a 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,19 +69,60 @@ function runGit(args, options = {}) { }); } +function getChangedFilesStrict() { + const gitDiffArg = process.env.GIT_DIFF_ARG; + + 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.' + ); + } + + console.log(`Strict mode enabled. Comparing using GIT_DIFF_ARG: ${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)); +} + /** - * 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 base = process.env.GITHUB_BASE_REF || 'main'; - const refsToTry = [ - base, - `upstream/${base}`, - `origin/${base}`, - 'FETCH_HEAD', - 'HEAD~1', - 'HEAD^', - ]; + 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 { @@ -83,36 +130,21 @@ function getChangedFiles() { 'diff', '--name-only', '--diff-filter=ACMRT', - ref, + `${ref}...HEAD`, '--', '*.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 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 ---