diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts index 8d06213833cd..d03d2741b105 100644 --- a/packages/angular/build/src/builders/application/build-action.ts +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -321,7 +321,8 @@ function* emitOutputResults( if (!previousAssetsInfo.has(destination)) { incrementalResult.added.push(destination); incrementalResult.background = false; - } else if (changes.modified.has(source)) { + } else if (changes.modified.has(source) || changes.added.has(source)) { + // A file replaced by a rename (e.g. an atomic save) can be reported as added. incrementalResult.modified.push(destination); incrementalResult.background = false; } else { diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts index 1c36f64c1892..04610625eb09 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import { rename } from 'node:fs/promises'; import { buildApplication } from '../../index'; import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; @@ -43,6 +44,39 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { ]); }); + it('emits an asset replaced by renaming another file over it', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [ + { + glob: '**/*', + input: 'public', + }, + ], + watch: true, + // Polling reports a replaced file as modified, while native watchers can report it as added. + poll: undefined, + }); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('foo'); + + // Editors with atomic save write a temporary file and rename it over the original. + await harness.writeFile('asset.txt.tmp', 'bar'); + await rename( + harness.resolvePath('asset.txt.tmp'), + harness.resolvePath('public/asset.txt'), + ); + }, + ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('bar'); + }, + ]); + }); + it('remove deleted asset from output', async () => { await Promise.all([ harness.writeFile('public/asset-two.txt', 'bar'),