Skip to content

Commit 51e39d5

Browse files
committedMar 25, 2025·
refactor(@angular/build): use newer Node.js cp API for asset copying
When using Node.js v22 (minimum Node.js v22.11 for Angular v20), the application build system will now use the Node.js `cp` filesystem API instead of the `copyFile` API. This newer API provides equivalent functionality while also preserving timestamps for copied assets. Additionally, it supports potential future internal refactorings to support full direct directory copying.
1 parent e816d46 commit 51e39d5

File tree

2 files changed

+30
-1
lines changed
  • packages/angular/build/src/builders/application
  • tests/legacy-cli/e2e/tests/build

2 files changed

+30
-1
lines changed
 

‎packages/angular/build/src/builders/application/index.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
import { Result, ResultKind } from './results';
2929
import { Schema as ApplicationBuilderOptions } from './schema';
3030

31+
const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22;
32+
3133
export type { ApplicationBuilderOptions };
3234

3335
export async function* buildApplicationInternal(
@@ -211,7 +213,17 @@ export async function* buildApplication(
211213
await fs.writeFile(fullFilePath, file.contents);
212214
} else {
213215
// Copy file contents
214-
await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE);
216+
if (isNodeV22orHigher) {
217+
// Use newer `cp` API on Node.js 22+ (minimum v22 for CLI is 22.11)
218+
await fs.cp(file.inputPath, fullFilePath, {
219+
mode: fs.constants.COPYFILE_FICLONE,
220+
preserveTimestamps: true,
221+
});
222+
} else {
223+
// For Node.js 20 use `copyFile` (`cp` is not stable for v20)
224+
// TODO: Remove when Node.js 20 is no longer supported
225+
await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE);
226+
}
215227
}
216228
});
217229

‎tests/legacy-cli/e2e/tests/build/assets.ts

+17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import assert from 'node:assert/strict';
12
import * as fs from 'node:fs';
23
import { expectFileToExist, expectFileToMatch, writeFile } from '../../utils/fs';
34
import { ng } from '../../utils/process';
45
import { updateJsonFile } from '../../utils/project';
56
import { expectToFail } from '../../utils/utils';
67

8+
const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22;
9+
710
export default async function () {
811
await writeFile('public/.file', '');
912
await writeFile('public/test.abc', 'hello world');
13+
const originalStats = fs.statSync('public/test.abc', { bigint: true });
1014

1115
await ng('build', '--configuration=development');
1216

@@ -15,6 +19,19 @@ export default async function () {
1519
await expectFileToMatch('dist/test-project/browser/test.abc', 'hello world');
1620
await expectToFail(() => expectFileToExist('dist/test-project/browser/.gitkeep'));
1721

22+
// Timestamp preservation only support with application build system on Node.js v22+
23+
if (isNodeV22orHigher) {
24+
const outputStats = fs.statSync('dist/test-project/browser/test.abc', { bigint: true });
25+
assert(
26+
originalStats.mtimeMs === outputStats.mtimeMs,
27+
'Asset file modified timestamp should be preserved.',
28+
);
29+
assert(
30+
originalStats.ctimeMs === outputStats.ctimeMs,
31+
'Asset file created timestamp should be preserved.',
32+
);
33+
}
34+
1835
// Ensure `followSymlinks` option follows symlinks
1936
await updateJsonFile('angular.json', (workspaceJson) => {
2037
const appArchitect = workspaceJson.projects['test-project'].architect;

0 commit comments

Comments
 (0)
Please sign in to comment.