Skip to content

Commit faca2b0

Browse files
authored
fix(@angular/build): preserve entry-point displayName casing and flatten bundle names
- Preserve original casing in entry point `displayName` so case-sensitive TypeScript `paths` mappings resolve camelCase secondary entry points while keeping `bundleName` lowercased and flattened. - Skip static `.d.ts` and `.d.mts` subpath exports in `normalizeEntryPoints` so they are preserved as non-compiled static exports in `package.json#exports`. - Validate that resolved entry point file paths stay within `projectRoot` using `isSubDirectory`.
1 parent 3fcf6c8 commit faca2b0

2 files changed

Lines changed: 71 additions & 6 deletions

File tree

‎packages/angular/build/src/builders/library/pipeline/entry-points.ts‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import path from 'node:path';
10-
import { toPosixPath } from '../../../utils/path';
10+
import { isSubDirectory, toPosixPath } from '../../../utils/path';
1111
import type { BuilderState } from '../builder-state';
1212
import type { NormalizedEntryPoint, NormalizedLibraryOptions, PackageJsonData } from '../types';
1313
import type { BundleEntryPointInput } from './bundler';
@@ -55,11 +55,17 @@ function normalizeEntryPoint(
5555
}
5656

5757
const subpath = isPrimary ? '.' : `./${name}`;
58-
const displayName = (isPrimary ? packageName : `${packageName}/${name}`).toLowerCase();
58+
const displayName = isPrimary ? packageName : `${packageName}/${name}`;
5959
const bundleName = getEntryPointBundleName(packageName, name);
6060
const entryFilePath = toPosixPath(path.resolve(projectRoot, targetPath));
6161

62-
if (!/(?<!\.d)\.(?:ts|mts)$/.test(entryFilePath)) {
62+
if (!isSubDirectory(projectRoot, entryFilePath)) {
63+
throw new Error(
64+
`Entry point '${key}' file path '${entryFilePath}' is outside the project root '${projectRoot}'.`,
65+
);
66+
}
67+
68+
if (!/(?<!\.d)\.m?ts$/.test(entryFilePath)) {
6369
throw new Error(
6470
`Entry point '${key}' file path must be a TypeScript file ('.ts' or '.mts'): '${entryFilePath}'.`,
6571
);
@@ -157,8 +163,8 @@ export function normalizeEntryPoints(
157163
continue;
158164
}
159165

160-
if (!isPrimary && !/\.m?ts$/.test(target)) {
161-
// Static asset, stylesheet, or package.json export; preserve in package.json without compiling.
166+
if (!isPrimary && !/(?<!\.d)\.m?ts$/.test(target)) {
167+
// Static asset, stylesheet, declaration file, or package.json export; preserve in package.json without compiling.
162168
continue;
163169
}
164170

‎packages/angular/build/src/builders/library/pipeline/entry-points_spec.ts‎

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,46 @@ describe('entry-points pipeline', () => {
240240
);
241241
});
242242

243-
it('should ignore non-TypeScript exports for secondary subpaths', () => {
243+
it('should preserve casing in displayName while lowercasing bundleName', () => {
244+
const rawExports = {
245+
'.': './src/public-api.ts',
246+
'./myFeature': './my-feature/src/public-api.ts',
247+
};
248+
249+
const entryPoints = normalizeEntryPoints(
250+
rawExports,
251+
projectRoot,
252+
packageJsonPath,
253+
'@My-Scope/My-Lib',
254+
);
255+
256+
expect(entryPoints.get('.')).toEqual({
257+
subpath: '.',
258+
name: '.',
259+
displayName: '@My-Scope/My-Lib',
260+
bundleName: 'my-scope-my-lib',
261+
entryFilePath: join(projectRoot, 'src/public-api.ts'),
262+
isPrimary: true,
263+
});
264+
265+
expect(entryPoints.get('myFeature')).toEqual({
266+
subpath: './myFeature',
267+
name: 'myFeature',
268+
displayName: '@My-Scope/My-Lib/myFeature',
269+
bundleName: 'my-scope-my-lib-myfeature',
270+
entryFilePath: join(projectRoot, 'my-feature/src/public-api.ts'),
271+
isPrimary: false,
272+
});
273+
});
274+
275+
it('should ignore non-TypeScript exports and static declaration files for secondary subpaths', () => {
244276
const rawExports = {
245277
'.': './src/public-api.ts',
246278
'./package.json': './package.json',
247279
'./styles.css': './styles.css',
248280
'./theme': { sass: './theme.scss' },
281+
'./custom-types': './src/custom-types.d.ts',
282+
'./custom-esm-types': { default: './src/custom-types.d.mts' },
249283
};
250284

251285
const entryPoints = normalizeEntryPoints(
@@ -260,6 +294,31 @@ describe('entry-points pipeline', () => {
260294
expect(entryPoints.has('package.json')).toBeFalse();
261295
expect(entryPoints.has('styles.css')).toBeFalse();
262296
expect(entryPoints.has('theme')).toBeFalse();
297+
expect(entryPoints.has('custom-types')).toBeFalse();
298+
expect(entryPoints.has('custom-esm-types')).toBeFalse();
299+
});
300+
301+
it('should throw when entry point file path escapes projectRoot', () => {
302+
expect(() =>
303+
normalizeEntryPoints(
304+
{ '.': '../other-lib/src/public-api.ts' },
305+
projectRoot,
306+
packageJsonPath,
307+
packageName,
308+
),
309+
).toThrowError(/is outside the project root/);
310+
311+
expect(() =>
312+
normalizeEntryPoints(
313+
{
314+
'.': './src/public-api.ts',
315+
'./testing': '../../outside/public-api.ts',
316+
},
317+
projectRoot,
318+
packageJsonPath,
319+
packageName,
320+
),
321+
).toThrowError(/is outside the project root/);
263322
});
264323

265324
it('should throw when exports is missing or not an object/string', () => {

0 commit comments

Comments
 (0)