Skip to content

Commit 254041d

Browse files
MeAkibalan-agius4
authored andcommitted
fix(@angular/build): copy assets replaced by a rename in watch mode
When an asset is replaced by renaming another file over it, as editors with atomic save do, the native file watcher on Linux reports the asset as added instead of modified. The incremental build only copied existing assets that were reported as modified, so the output kept the previous contents of the asset. Existing assets reported as added are now copied as well. The new spec disables polling so that the native watcher is used.
1 parent 2ebb5e1 commit 254041d

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

‎packages/angular/build/src/builders/application/build-action.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ function* emitOutputResults(
321321
if (!previousAssetsInfo.has(destination)) {
322322
incrementalResult.added.push(destination);
323323
incrementalResult.background = false;
324-
} else if (changes.modified.has(source)) {
324+
} else if (changes.modified.has(source) || changes.added.has(source)) {
325+
// A file replaced by a rename (e.g. an atomic save) can be reported as added.
325326
incrementalResult.modified.push(destination);
326327
incrementalResult.background = false;
327328
} else {

‎packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { rename } from 'node:fs/promises';
910
import { buildApplication } from '../../index';
1011
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
1112

@@ -43,6 +44,39 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
4344
]);
4445
});
4546

47+
it('emits an asset replaced by renaming another file over it', async () => {
48+
harness.useTarget('build', {
49+
...BASE_OPTIONS,
50+
assets: [
51+
{
52+
glob: '**/*',
53+
input: 'public',
54+
},
55+
],
56+
watch: true,
57+
// Polling reports a replaced file as modified, while native watchers can report it as added.
58+
poll: undefined,
59+
});
60+
61+
await harness.executeWithCases([
62+
async ({ result }) => {
63+
expect(result?.success).toBeTrue();
64+
harness.expectFile('dist/browser/asset.txt').content.toContain('foo');
65+
66+
// Editors with atomic save write a temporary file and rename it over the original.
67+
await harness.writeFile('asset.txt.tmp', 'bar');
68+
await rename(
69+
harness.resolvePath('asset.txt.tmp'),
70+
harness.resolvePath('public/asset.txt'),
71+
);
72+
},
73+
({ result }) => {
74+
expect(result?.success).toBeTrue();
75+
harness.expectFile('dist/browser/asset.txt').content.toContain('bar');
76+
},
77+
]);
78+
});
79+
4680
it('remove deleted asset from output', async () => {
4781
await Promise.all([
4882
harness.writeFile('public/asset-two.txt', 'bar'),

0 commit comments

Comments
 (0)