diff --git a/packages/angular/build/src/tools/esbuild/watcher.ts b/packages/angular/build/src/tools/esbuild/watcher.ts index 1b9fd15d4561..fe41275e479e 100644 --- a/packages/angular/build/src/tools/esbuild/watcher.ts +++ b/packages/angular/build/src/tools/esbuild/watcher.ts @@ -252,7 +252,7 @@ class WatcherQueue { } export async function createWatcher(options?: WatcherOptions): Promise { - if (options?.polling) { + if (options?.polling || options?.followSymlinks) { return createChokidarWatcher(options); } diff --git a/packages/angular/build/src/tools/esbuild/watcher_spec.ts b/packages/angular/build/src/tools/esbuild/watcher_spec.ts index 2c82510a5ae5..5c7853012762 100644 --- a/packages/angular/build/src/tools/esbuild/watcher_spec.ts +++ b/packages/angular/build/src/tools/esbuild/watcher_spec.ts @@ -11,6 +11,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { setTimeout } from 'node:timers/promises'; import { + type BuildWatcher, ChangedFiles, createWatcher, getDirectoryPath, @@ -482,5 +483,42 @@ describe('Watcher', () => { await watcher.close(); }, 10000); + + it('should detect changes behind a directory symlink when followSymlinks is true', async () => { + const externalDir = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), 'watcher-external-')), + ); + let watcher: BuildWatcher | undefined; + + try { + const externalTargetFile = path.join(externalDir, 'index.ts'); + fs.writeFileSync(externalTargetFile, 'export const a = 1;'); + + const symlinkDir = path.join(tempDir, 'symlinked-lib'); + fs.symlinkSync(externalDir, symlinkDir, 'junction'); + + const symlinkedFile = path.join(symlinkDir, 'index.ts'); + + watcher = await createWatcher({ + followSymlinks: true, + cwd: tempDir, + }); + + watcher.add(symlinkedFile); + await setTimeout(150); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(externalTargetFile, 'export const a = 2;'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.some((f: string) => f.includes('index.ts'))).toBeTrue(); + } finally { + await watcher?.close(); + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }, 10000); }); });