diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index d648331b70de..e77d03277780 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -291,7 +291,6 @@ jasmine_test( name = "application_integration_tests", size = "medium", data = [":application_integration_test_lib"], - flaky = True, shard_count = 25, ) @@ -303,7 +302,6 @@ jasmine_test( # Force IPv4 to resolve RBE resolution issues "NODE_OPTIONS": "--dns-result-order=ipv4first", }, - flaky = True, shard_count = 10, ) @@ -316,7 +314,6 @@ jasmine_test( # or standardize to avoid complex configuration like this! "PUPPETEER_DOWNLOAD_PATH": "../../../node_modules/puppeteer/downloads", }, - flaky = True, shard_count = 10, ) @@ -324,7 +321,6 @@ jasmine_test( name = "unit-test_integration_tests", size = "medium", data = [":unit-test_integration_test_lib"], - flaky = True, shard_count = 5, ) diff --git a/packages/angular/build/src/utils/watcher.ts b/packages/angular/build/src/utils/watcher.ts index 8d47ba48e907..382367a57033 100644 --- a/packages/angular/build/src/utils/watcher.ts +++ b/packages/angular/build/src/utils/watcher.ts @@ -752,6 +752,7 @@ async function createChokidarWatcher( ): Promise { const chokidar = chokidarModule ?? (await import('chokidar')); const watchedFiles = new Set(); + const fileStats = new Map(); let queue: WatcherQueue; if (options?.polling) { @@ -811,16 +812,36 @@ async function createChokidarWatcher( return; } - if (type !== 'removed') { - const stat = fs.statSync(rawPath, { throwIfNoEntry: false }); - // Ignore historical events from before watcher initialization, but allow a 1000 ms window - // to account for coarse filesystem timestamp resolution (e.g., ext4/overlayfs integer second - // mtime truncation on Linux) where files modified during startup may have truncated .000 ms mtimes. - if (stat && stat.mtimeMs < initTime - 1000) { - return; - } + if (type === 'removed') { + fileStats.delete(lookupKey); + queue.addChange(type, rawPath); + + return; + } + + const stat = fs.statSync(rawPath, { throwIfNoEntry: false }); + if (!stat) { + return; } + // Ignore historical events from before watcher initialization, but allow a 1000 ms window + // to account for coarse filesystem timestamp resolution (e.g., ext4/overlayfs integer second + // mtime truncation on Linux) where files modified during startup may have truncated .000 ms mtimes. + if (stat.mtimeMs < initTime - 1000) { + return; + } + + const previousStat = fileStats.get(lookupKey); + if ( + type === 'modified' && + previousStat && + previousStat.mtimeMs === stat.mtimeMs && + previousStat.size === stat.size + ) { + return; + } + + fileStats.set(lookupKey, { mtimeMs: stat.mtimeMs, size: stat.size }); queue.addChange(type, rawPath); }; @@ -833,8 +854,23 @@ async function createChokidarWatcher( return this; }, - next() { - return queue.next(); + async next() { + const result = await queue.next(); + if (result.value) { + const files = [...result.value.added, ...result.value.modified]; + await Promise.all( + files.map(async (file) => { + const stat = await fs.promises.stat(file).catch(() => undefined); + if (stat?.isFile()) { + const posixPath = toPosixPathNormalized(file); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + fileStats.set(lookupKey, { mtimeMs: stat.mtimeMs, size: stat.size }); + } + }), + ); + } + + return result; }, add(paths) { @@ -873,6 +909,7 @@ async function createChokidarWatcher( const lookupKey = toLookupKey(posixPath, isCaseSensitive); if (watchedFiles.has(lookupKey)) { watchedFiles.delete(lookupKey); + fileStats.delete(lookupKey); // When the last watched file in a package is removed, unwatch the package directory. const { isPackage, unwatchPkgDir } = nodeModulesManager.removePackageFile(