Skip to content

Commit 384feda

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`, use `export type *` in `library/index.ts`, remove the redundant `tslibVersion` truthiness check in `generatePackageManifests`, and add test coverage for asset `ignore` patterns.
1 parent 2b5a9a6 commit 384feda

5 files changed

Lines changed: 48 additions & 39 deletions

File tree

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

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

9-
import { Builder, createBuilder } from '@angular-devkit/architect';
9+
import { type Builder, createBuilder } from '@angular-devkit/architect';
1010
import { executeLibraryBuilder } from './builder';
1111
import type { Schema as LibraryBuilderOptions } from './schema';
1212

‎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/pipeline/package-manifests.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,10 @@ export async function generatePackageManifests(
7878
};
7979

8080
if (hasTslibImport && !rawPackageJson.dependencies?.['tslib']) {
81-
const tslibVersion = await getAngularTslibRange(options.workspaceRoot);
82-
if (tslibVersion) {
83-
distPackageJson.dependencies = {
84-
...rawPackageJson.dependencies,
85-
tslib: tslibVersion,
86-
};
87-
}
81+
distPackageJson.dependencies = {
82+
...rawPackageJson.dependencies,
83+
tslib: await getAngularTslibRange(options.workspaceRoot),
84+
};
8885
}
8986

9087
// Retain scripts if keepLifecycleScripts is set
@@ -159,6 +156,11 @@ export async function generatePackageManifests(
159156

160157
/**
161158
* Creates or updates export conditions for an entry point, preserving custom user-defined conditions.
159+
*
160+
* @param existingConditions Existing export conditions from the user's `package.json`.
161+
* @param dtsPath Relative POSIX path to the bundled `.d.ts` file.
162+
* @param fesmPath Relative POSIX path to the bundled FESM `.mjs` file.
163+
* @returns The merged export conditions object.
162164
*/
163165
function createExportConditions(
164166
existingConditions: unknown,

‎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: 34 additions & 30 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,30 @@ 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-
52-
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-
}
68+
const isDirectory =
69+
statSync(resolvedAssetPath, { throwIfNoEntry: false })?.isDirectory() ?? true;
70+
const glob = isDirectory ? '**/*' : path.basename(assetPath);
71+
const input = isDirectory ? assetPath : path.dirname(assetPath);
6972

7073
// Output directory for both is the relative path from the root to input.
7174
const output = path.relative(root, path.resolve(resolvedWorkspaceRoot, input));
7275

73-
assetPattern = { glob, input, output };
76+
normalized = { glob, input, output };
7477
} else {
7578
if (!isSubDirectory(resolvedWorkspaceRoot, assetPattern.input)) {
7679
throw new Error(`The ${assetPattern.input} asset path must be within the workspace root.`);
7780
}
7881

79-
assetPattern.output = path.join('.', assetPattern.output ?? '');
82+
normalized = {
83+
...assetPattern,
84+
output: path.join('.', assetPattern.output ?? ''),
85+
};
8086
}
8187

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

88-
return assetPattern as AssetPatternClass & { output: string };
92+
return normalized;
8993
});
9094
}

0 commit comments

Comments
 (0)