From 82f35a48aa511586f6bba59df4ff25c0e85d9a82 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 11 Sep 2026 17:16:48 -0700 Subject: [PATCH 1/3] fix(linter): ignore file changes in packages directory --- .eslintrc.json | 1 + bin/linter.mjs | 43 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6e764a67aec..86b5c75c1b2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -92,6 +92,7 @@ ], // LINT.IfChange(ignorePatterns) "ignorePatterns": [ + "packages/**", "**/node_modules", "**/build", "**/dist", diff --git a/bin/linter.mjs b/bin/linter.mjs index 2a4443215dc..41f36377a66 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -32,6 +32,8 @@ async function run() { changedTsFiles = getChangedFiles(); } + changedTsFiles = changedTsFiles.filter(shouldLintFile); + if (changedTsFiles.length === 0) { console.log('No TypeScript files changed. Skipping checks.'); return; @@ -63,6 +65,22 @@ async function run() { // --- Git Changed Files Logic --- +let repoRoot; + +/** + * Resolves and caches the repository root path. + */ +function getRepoRoot() { + if (!repoRoot) { + try { + repoRoot = runGit(['rev-parse', '--show-toplevel']).trim(); + } catch (_err) { + repoRoot = process.cwd(); + } + } + return repoRoot; +} + /** * Executes a Git command synchronously. */ @@ -175,8 +193,12 @@ function getChangedFiles() { // --- ESLint Checker --- +// Top-level repository directories that the linter ignores (e.g. generated packages) +const IGNORED_ROOT_DIRS = new Set(['packages']); + +// Recursive path segments ignored anywhere in any package (build artifacts, fixtures, etc.) // LINT.IfChange(ignored_path_segments) -const IGNORED_PATH_SEGMENTS = [ +const IGNORED_PATH_SEGMENTS = new Set([ 'node_modules', 'build', 'dist', @@ -190,19 +212,30 @@ const IGNORED_PATH_SEGMENTS = [ 'coverage', '.nyc_output', 'protos', -]; +]); // LINT.ThenChange(.eslintrc.json:ignorePatterns) /** * Determines whether a file should undergo ESLint checks. - * Excludes declaration files (*.d.ts), auto-generated artifacts, and test baselines/fixtures. + * Excludes declaration files (*.d.ts), auto-generated artifacts, test baselines/fixtures, + * and top-level ignored directories. */ function shouldLintFile(filePath) { if (filePath.endsWith('.d.ts')) { return false; } - const segments = filePath.split(/[\\/]/); - return !segments.some(seg => IGNORED_PATH_SEGMENTS.includes(seg)); + const relPath = path + .relative(getRepoRoot(), path.resolve(filePath)) + .replace(/\\/g, '/'); + const segments = relPath.split('/'); + + // 1. Ignore if inside an ignored top-level directory (e.g. packages/) + if (IGNORED_ROOT_DIRS.has(segments[0])) { + return false; + } + + // 2. Ignore if any segment matches an artifact or fixture folder + return !segments.some(seg => IGNORED_PATH_SEGMENTS.has(seg)); } /** From 29be9f8af9bc04eb9f0cc035e88eb15a4a29c927 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 11 Sep 2026 17:24:19 -0700 Subject: [PATCH 2/3] fix(linter): resolve paths relative to repo root when executed from subdirectories --- bin/linter.mjs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/bin/linter.mjs b/bin/linter.mjs index 41f36377a66..5d4c8428403 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -73,7 +73,10 @@ let repoRoot; function getRepoRoot() { if (!repoRoot) { try { - repoRoot = runGit(['rev-parse', '--show-toplevel']).trim(); + repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], { + encoding: 'utf8', + stdio: 'pipe', + }).trim(); } catch (_err) { repoRoot = process.cwd(); } @@ -88,6 +91,7 @@ function runGit(args, options = {}) { return execFileSync('git', args, { encoding: 'utf8', stdio: 'pipe', + cwd: getRepoRoot(), ...options, }); } @@ -126,7 +130,7 @@ function getChangedFilesStrict() { return output .split('\n') .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); + .filter(f => f.length > 0 && existsSync(path.resolve(getRepoRoot(), f))); } catch (err) { if (err.status !== 1) { throw new Error( @@ -166,7 +170,9 @@ function getChangedFiles() { return output .split('\n') .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); + .filter( + f => f.length > 0 && existsSync(path.resolve(getRepoRoot(), f)), + ); } catch { // Continue to the next fallback ref } @@ -185,7 +191,7 @@ function getChangedFiles() { return output .split('\n') .map(f => f.trim()) - .filter(f => f.length > 0 && existsSync(f)); + .filter(f => f.length > 0 && existsSync(path.resolve(getRepoRoot(), f))); } catch { return []; } @@ -225,7 +231,7 @@ function shouldLintFile(filePath) { return false; } const relPath = path - .relative(getRepoRoot(), path.resolve(filePath)) + .relative(getRepoRoot(), path.resolve(getRepoRoot(), filePath)) .replace(/\\/g, '/'); const segments = relPath.split('/'); @@ -267,7 +273,7 @@ async function checkEslint(filesToCheck) { const absPkgDir = path.resolve(pkgDir); const eslint = new ESLint({ cwd: absPkgDir, - resolvePluginsRelativeTo: process.cwd(), + resolvePluginsRelativeTo: getRepoRoot(), overrideConfig: { parserOptions: { tsconfigRootDir: absPkgDir, @@ -276,7 +282,7 @@ async function checkEslint(filesToCheck) { }); const relativeFiles = files.map(f => - path.relative(absPkgDir, path.resolve(f)), + path.relative(absPkgDir, path.resolve(getRepoRoot(), f)), ); const results = await eslint.lintFiles(relativeFiles); const formatter = await eslint.loadFormatter('stylish'); @@ -317,7 +323,7 @@ async function checkEslint(filesToCheck) { * Caches directories to avoid redundant disk operations. */ function findTsconfigDir(filePath) { - let currentDir = path.resolve(path.dirname(filePath)); + let currentDir = path.resolve(getRepoRoot(), path.dirname(filePath)); const root = path.parse(currentDir).root; while (currentDir && currentDir !== root) { @@ -388,7 +394,7 @@ async function checkTypeSafety(packagesToCheck) { try { console.log(` Type checking ${pkg}...`); await execFileAsync('node', [ - 'node_modules/typescript/bin/tsc', + path.join(getRepoRoot(), 'node_modules/typescript/bin/tsc'), '--noEmit', '--project', path.join(pkg, 'tsconfig.json'), From bc96d34b41207b55590370edc85abb87a5f97df9 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 11 Sep 2026 17:36:35 -0700 Subject: [PATCH 3/3] fix(linter): ensure hermetic execution across environments and directories --- .eslintrc.json | 6 ++++++ bin/linter.mjs | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 86b5c75c1b2..86c157a17c4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -88,6 +88,12 @@ "@typescript-eslint/no-explicit-any": ["off"], "@typescript-eslint/no-floating-promises": ["off"] } + }, + { + "files": ["bin/**/*.mjs"], + "parserOptions": { + "ecmaVersion": 2020 + } } ], // LINT.IfChange(ignorePatterns) diff --git a/bin/linter.mjs b/bin/linter.mjs index 5d4c8428403..ea5b0fe7585 100755 --- a/bin/linter.mjs +++ b/bin/linter.mjs @@ -15,6 +15,7 @@ import {execFileSync, execFile} from 'child_process'; import {existsSync} from 'fs'; import path from 'path'; +import {fileURLToPath} from 'url'; import {promisify} from 'util'; import {ESLint} from 'eslint'; @@ -65,6 +66,7 @@ async function run() { // --- Git Changed Files Logic --- +const REPO_ROOT = path.resolve(fileURLToPath(new URL('..', import.meta.url))); let repoRoot; /** @@ -78,7 +80,7 @@ function getRepoRoot() { stdio: 'pipe', }).trim(); } catch (_err) { - repoRoot = process.cwd(); + repoRoot = REPO_ROOT; } } return repoRoot; @@ -92,6 +94,10 @@ function runGit(args, options = {}) { encoding: 'utf8', stdio: 'pipe', cwd: getRepoRoot(), + env: { + ...process.env, + GIT_TERMINAL_PROMPT: '0', + }, ...options, }); } @@ -259,7 +265,7 @@ async function checkEslint(filesToCheck) { // Group files by package directory to set tsconfigRootDir properly for typescript-eslint const filesByPkg = new Map(); for (const file of filesToProcess) { - const pkgDir = findTsconfigDir(file) || process.cwd(); + const pkgDir = findTsconfigDir(file) || getRepoRoot(); if (!filesByPkg.has(pkgDir)) { filesByPkg.set(pkgDir, []); } @@ -368,7 +374,13 @@ async function ensurePackageDependencies(packages) { const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; await execFileAsync( npmCmd, - ['install', '--no-audit', '--no-fund', '--ignore-scripts'], + [ + 'install', + '--no-audit', + '--no-fund', + '--ignore-scripts', + '--prefer-offline', + ], { cwd: pkg, }, @@ -393,7 +405,7 @@ async function checkTypeSafety(packagesToCheck) { const checks = Array.from(packagesToCheck).map(async pkg => { try { console.log(` Type checking ${pkg}...`); - await execFileAsync('node', [ + await execFileAsync(process.execPath, [ path.join(getRepoRoot(), 'node_modules/typescript/bin/tsc'), '--noEmit', '--project',