Skip to content

Commit b3de0de

Browse files
committed
fix(@angular/build): allow application assets in workspace root
This commit refactors `normalizeAssetPatterns` to: 1. Allow asset paths located in the workspace root or project root, relaxing the previous strict requirement of being within the source root. 2. Determine the output path by calculating the relative path from the most specific root (source, project, or workspace) to the asset input. 3. Remove the unused `MissingAssetSourceRootException` class in favor of a standard `Error` with a clear message. This enables users to include workspace-level assets (like `LICENSE` or `README.md`) using the shorthand string syntax without errors.
1 parent 284c47d commit b3de0de

File tree

3 files changed

+29
-31
lines changed

3 files changed

+29
-31
lines changed

packages/angular/build/src/builders/application/tests/options/assets_spec.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,19 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
107107
harness.expectFile('dist/browser/test.svg').toNotExist();
108108
});
109109

110-
it('fail if asset path is not within project source root', async () => {
111-
await harness.writeFile('test.svg', '<svg></svg>');
110+
it('copies an asset from project root (outside source root)', async () => {
111+
await harness.writeFile('extra.txt', 'extra');
112112

113113
harness.useTarget('build', {
114114
...BASE_OPTIONS,
115-
assets: ['test.svg'],
115+
assets: ['extra.txt'],
116116
});
117117

118-
const { error } = await harness.executeOnce({ outputLogsOnException: false });
118+
const { result } = await harness.executeOnce();
119119

120-
expect(error?.message).toMatch('path must start with the project source root');
120+
expect(result?.success).toBe(true);
121121

122-
harness.expectFile('dist/browser/test.svg').toNotExist();
122+
harness.expectFile('dist/browser/extra.txt').content.toBe('extra');
123123
});
124124
});
125125

@@ -359,6 +359,17 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
359359
harness.expectFile('dist/browser/subdirectory/test.svg').content.toBe('<svg></svg>');
360360
});
361361

362+
it('fails if asset path is outside workspace root', async () => {
363+
harness.useTarget('build', {
364+
...BASE_OPTIONS,
365+
assets: ['../outside.txt'],
366+
});
367+
368+
const { error } = await harness.executeOnce({ outputLogsOnException: false });
369+
370+
expect(error?.message).toMatch('asset path must be within the workspace root');
371+
});
372+
362373
it('fails if output option is not within project output path', async () => {
363374
await harness.writeFile('test.svg', '<svg></svg>');
364375

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ import { statSync } from 'node:fs';
1111
import * as path from 'node:path';
1212
import { AssetPattern, AssetPatternClass } from '../builders/application/schema';
1313

14-
export class MissingAssetSourceRootException extends Error {
15-
constructor(path: string) {
16-
super(`The ${path} asset path must start with the project source root.`);
17-
}
18-
}
19-
2014
export function normalizeAssetPatterns(
2115
assetPatterns: AssetPattern[],
2216
workspaceRoot: string,
@@ -30,16 +24,24 @@ export function normalizeAssetPatterns(
3024
// When sourceRoot is not available, we default to ${projectRoot}/src.
3125
const sourceRoot = projectSourceRoot || path.join(projectRoot, 'src');
3226
const resolvedSourceRoot = path.resolve(workspaceRoot, sourceRoot);
27+
const resolvedProjectRoot = path.resolve(workspaceRoot, projectRoot);
3328

3429
return assetPatterns.map((assetPattern) => {
3530
// Normalize string asset patterns to objects.
3631
if (typeof assetPattern === 'string') {
3732
const assetPath = path.normalize(assetPattern);
3833
const resolvedAssetPath = path.resolve(workspaceRoot, assetPath);
34+
let root: string;
3935

4036
// Check if the string asset is within sourceRoot.
41-
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
42-
throw new MissingAssetSourceRootException(assetPattern);
37+
if (resolvedAssetPath.startsWith(resolvedSourceRoot)) {
38+
root = resolvedSourceRoot;
39+
} else if (resolvedAssetPath.startsWith(resolvedProjectRoot)) {
40+
root = resolvedProjectRoot;
41+
} else if (resolvedAssetPath.startsWith(workspaceRoot)) {
42+
root = workspaceRoot;
43+
} else {
44+
throw new Error(`The ${assetPattern} asset path must be within the workspace root.`);
4345
}
4446

4547
let glob: string, input: string;
@@ -63,8 +65,8 @@ export function normalizeAssetPatterns(
6365
input = path.dirname(assetPath);
6466
}
6567

66-
// Output directory for both is the relative path from source root to input.
67-
const output = path.relative(resolvedSourceRoot, path.resolve(workspaceRoot, input));
68+
// Output directory for both is the relative path from the root to input.
69+
const output = path.relative(root, path.resolve(workspaceRoot, input));
6870

6971
assetPattern = { glob, input, output };
7072
} else {

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/assets_spec.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,6 @@ describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
106106

107107
harness.expectFile('dist/test.svg').toNotExist();
108108
});
109-
110-
it('fail if asset path is not within project source root', async () => {
111-
await harness.writeFile('test.svg', '<svg></svg>');
112-
113-
harness.useTarget('build', {
114-
...BASE_OPTIONS,
115-
assets: ['test.svg'],
116-
});
117-
118-
const { error } = await harness.executeOnce({ outputLogsOnException: false });
119-
120-
expect(error?.message).toMatch('path must start with the project source root');
121-
122-
harness.expectFile('dist/test.svg').toNotExist();
123-
});
124109
});
125110

126111
describe('longhand syntax', () => {

0 commit comments

Comments
 (0)