Skip to content

Commit 2e687bb

Browse files
committed
refactor(@angular/build): use newer Node.js cp API for asset copying
When using Node.js v22 (minimum of v22.11 for v22 with 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 2e687bb

File tree

2 files changed

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

2 files changed

+27
-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

+14
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
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';
7+
import { getGlobalVariable } from '../../utils/env';
8+
9+
const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22;
610

711
export default async function () {
812
await writeFile('public/.file', '');
913
await writeFile('public/test.abc', 'hello world');
14+
const originalStats = fs.statSync('public/test.abc', { bigint: true });
1015

1116
await ng('build', '--configuration=development');
1217

@@ -15,6 +20,15 @@ export default async function () {
1520
await expectFileToMatch('dist/test-project/browser/test.abc', 'hello world');
1621
await expectToFail(() => expectFileToExist('dist/test-project/browser/.gitkeep'));
1722

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

0 commit comments

Comments
 (0)