Skip to content

Commit 2809844

Browse files
committed
refactor(@angular/build): export NormalizedAssetsPattern and avoid mutating input patterns
Export the `NormalizedAssetsPattern` interface from `normalize-asset-patterns.ts`, avoid mutating input `AssetPatternClass` objects in place, and simplify directory detection using `statSync(..., { throwIfNoEntry: false })`. Also import `normalizeAssetPatterns` directly in `library/options.ts` and add test coverage for asset `ignore` patterns.
1 parent 2b5a9a6 commit 2809844

3 files changed

Lines changed: 39 additions & 30 deletions

File tree

‎packages/angular/build/src/builders/library/options.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import type { BuilderContext } from '@angular-devkit/architect';
1010
import path from 'node:path';
1111
import type { StylesheetPluginsass } from '../../tools/esbuild/stylesheets/stylesheet-plugin-factory';
12-
import { normalizeAssetPatterns } from '../../utils';
1312
import { supportColor } from '../../utils/color';
1413
import { assertIsError } from '../../utils/error';
14+
import { normalizeAssetPatterns } from '../../utils/normalize-asset-patterns';
1515
import { normalizeCacheOptions } from '../../utils/normalize-cache';
1616
import { toPosixPath } from '../../utils/path';
1717
import {

‎packages/angular/build/src/builders/library/tests/options/assets_spec.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
1515
await harness.writeFiles({
1616
'projects/lib/assets-dir/file-a.png': 'PNG_A',
1717
'projects/lib/assets-dir/file-b.png': 'PNG_B',
18+
'projects/lib/assets-dir/ignored.png': 'PNG_IGNORED',
1819
'projects/lib/assets-dir/file-c.svg': 'SVG_C',
1920
});
2021

@@ -25,6 +26,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
2526
glob: '**/*.png',
2627
input: 'projects/lib/assets-dir',
2728
output: 'assets',
29+
ignore: ['ignored.png'],
2830
},
2931
],
3032
});
@@ -34,6 +36,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
3436

3537
expect(harness.readFile('dist/lib/assets/file-a.png')).toBe('PNG_A');
3638
expect(harness.readFile('dist/lib/assets/file-b.png')).toBe('PNG_B');
39+
expect(harness.hasFile('dist/lib/assets/ignored.png')).toBeFalse();
3740
expect(harness.hasFile('dist/lib/assets/file-c.svg')).toBeFalse();
3841
});
3942

‎packages/angular/build/src/utils/normalize-asset-patterns.ts‎

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

9-
import assert from 'node:assert';
109
import { statSync } from 'node:fs';
11-
import * as path from 'node:path';
12-
import { AssetPattern, AssetPatternClass } from '../builders/application/schema';
10+
import path from 'node:path';
11+
import type { AssetPattern, AssetPatternClass } from '../builders/application/schema';
1312
import { isSubDirectory } from './path';
1413

14+
/**
15+
* Asset pattern configuration normalized into its object form with a resolved `output` directory.
16+
*/
17+
export interface NormalizedAssetsPattern extends AssetPatternClass {
18+
/** Relative output directory within the build output path. */
19+
output: string;
20+
}
21+
22+
/**
23+
* Normalizes raw string and object asset patterns into {@link NormalizedAssetsPattern} objects
24+
* and validates that all asset inputs and outputs stay within the workspace and output directories.
25+
*
26+
* @param assetPatterns Raw string or object asset patterns from builder options.
27+
* @param workspaceRoot Workspace root directory path.
28+
* @param projectRoot Project root directory path.
29+
* @param projectSourceRoot Optional project source root directory path.
30+
* @returns An array of normalized asset pattern configurations.
31+
*/
1532
export function normalizeAssetPatterns(
1633
assetPatterns: AssetPattern[],
1734
workspaceRoot: string,
1835
projectRoot: string,
1936
projectSourceRoot: string | undefined,
20-
): (AssetPatternClass & { output: string })[] {
37+
): NormalizedAssetsPattern[] {
2138
if (assetPatterns.length === 0) {
2239
return [];
2340
}
@@ -29,6 +46,8 @@ export function normalizeAssetPatterns(
2946
const resolvedProjectRoot = path.resolve(resolvedWorkspaceRoot, projectRoot);
3047

3148
return assetPatterns.map((assetPattern) => {
49+
let normalized: NormalizedAssetsPattern;
50+
3251
// Normalize string asset patterns to objects.
3352
if (typeof assetPattern === 'string') {
3453
const assetPath = path.normalize(assetPattern);
@@ -46,45 +65,32 @@ export function normalizeAssetPatterns(
4665
throw new Error(`The ${assetPattern} asset path must be within the workspace root.`);
4766
}
4867

49-
let glob: string, input: string;
50-
let isDirectory: boolean;
51-
68+
let isDirectory = true;
5269
try {
53-
isDirectory = statSync(resolvedAssetPath).isDirectory();
54-
} catch {
55-
isDirectory = true;
56-
}
57-
58-
if (isDirectory) {
59-
// Folders get a recursive star glob.
60-
glob = '**/*';
61-
// Input directory is their original path.
62-
input = assetPath;
63-
} else {
64-
// Files are their own glob.
65-
glob = path.basename(assetPath);
66-
// Input directory is their original dirname.
67-
input = path.dirname(assetPath);
68-
}
70+
isDirectory = statSync(resolvedAssetPath, { throwIfNoEntry: false })?.isDirectory() ?? true;
71+
} catch {}
72+
const glob = isDirectory ? '**/*' : path.basename(assetPath);
73+
const input = isDirectory ? assetPath : path.dirname(assetPath);
6974

7075
// Output directory for both is the relative path from the root to input.
7176
const output = path.relative(root, path.resolve(resolvedWorkspaceRoot, input));
7277

73-
assetPattern = { glob, input, output };
78+
normalized = { glob, input, output };
7479
} else {
7580
if (!isSubDirectory(resolvedWorkspaceRoot, assetPattern.input)) {
7681
throw new Error(`The ${assetPattern.input} asset path must be within the workspace root.`);
7782
}
7883

79-
assetPattern.output = path.join('.', assetPattern.output ?? '');
84+
normalized = {
85+
...assetPattern,
86+
output: path.join('.', assetPattern.output ?? ''),
87+
};
8088
}
8189

82-
assert(assetPattern.output !== undefined);
83-
84-
if (assetPattern.output.startsWith('..')) {
90+
if (normalized.output.startsWith('..')) {
8591
throw new Error('An asset cannot be written to a location outside of the output path.');
8692
}
8793

88-
return assetPattern as AssetPatternClass & { output: string };
94+
return normalized;
8995
});
9096
}

0 commit comments

Comments
 (0)