Skip to content

Commit 000ec67

Browse files
committed
fix(@angular/build): reject entry points that collide on bundle name
A bundle name flattens '/' to '-', so './zz/child' and './zz-child' both become 'lib-zz-child'. The bundler input map is keyed by that name, so the second entry point silently overwrote the first: the build exited 0, one entry point's code was missing, and both 'exports' keys resolved to the surviving file. Fixes #34128
1 parent c5b8bf0 commit 000ec67

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ function normalizeEntryPoints(
295295
const exportsRecord = typeof rawExports === 'string' ? { '.': rawExports } : rawExports;
296296

297297
const entryPoints = new Map<string, NormalizedEntryPoint>();
298+
// Bundle names flatten '/' to '-', so two distinct subpaths can produce one.
299+
const bundleNameOrigins = new Map<string, string>();
298300
let hasPrimary = false;
299301

300302
for (const [key, value] of Object.entries(exportsRecord)) {
@@ -346,6 +348,15 @@ function normalizeEntryPoints(
346348
);
347349
}
348350

351+
const collidingKey = bundleNameOrigins.get(entryPoint.bundleName);
352+
if (collidingKey !== undefined) {
353+
throw new Error(
354+
`Entry points '${collidingKey}' and '${key}' both produce the bundle name ` +
355+
`'${entryPoint.bundleName}'. Entry point subpaths must not differ only by '/' versus '-'.`,
356+
);
357+
}
358+
359+
bundleNameOrigins.set(entryPoint.bundleName, key);
349360
entryPoints.set(entryPoint.name, entryPoint);
350361

351362
if (entryPoint.isPrimary) {

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,59 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
4545
expect(result?.success).toBeTrue();
4646
});
4747

48+
it('should succeed with a nested entry point beside a similarly named flat one', async () => {
49+
await harness.writeFiles({
50+
'projects/lib/zz/child/public-api.ts': 'export const NESTED = 42;\n',
51+
'projects/lib/zz-sibling/public-api.ts': 'export const FLAT = 7;\n',
52+
});
53+
await harness.modifyFile('projects/lib/package.json', (content) => {
54+
const pkg = JSON.parse(content);
55+
pkg.exports = {
56+
'.': './src/public-api.ts',
57+
'./zz/child': './zz/child/public-api.ts',
58+
'./zz-sibling': './zz-sibling/public-api.ts',
59+
};
60+
61+
return JSON.stringify(pkg, null, 2);
62+
});
63+
64+
const { result } = await harness.executeOnce();
65+
expect(result?.success).toBeTrue();
66+
harness.expectFile('dist/lib/fesm2022/lib-zz-child.mjs').toExist();
67+
harness.expectFile('dist/lib/fesm2022/lib-zz-sibling.mjs').toExist();
68+
});
69+
70+
it('should fail when two entry points produce the same bundle name', async () => {
71+
// A bundle name flattens '/' to '-', so './zz/child' and './zz-child' both
72+
// become 'lib-zz-child'. Without a guard the second silently overwrites the
73+
// first in the bundler input map: the build exits 0, one entry point's code
74+
// is gone, and both 'exports' keys resolve to the surviving file.
75+
await harness.writeFiles({
76+
'projects/lib/zz/child/public-api.ts': 'export const NESTED = 42;\n',
77+
'projects/lib/zz-child/public-api.ts': 'export const FLAT = 7;\n',
78+
});
79+
await harness.modifyFile('projects/lib/package.json', (content) => {
80+
const pkg = JSON.parse(content);
81+
pkg.exports = {
82+
'.': './src/public-api.ts',
83+
'./zz/child': './zz/child/public-api.ts',
84+
'./zz-child': './zz-child/public-api.ts',
85+
};
86+
87+
return JSON.stringify(pkg, null, 2);
88+
});
89+
90+
const { result, error } = await harness.executeOnce({
91+
outputLogsOnException: false,
92+
outputLogsOnFailure: false,
93+
});
94+
expect(result).toBeUndefined();
95+
expect(error).toBeDefined();
96+
expect((error as Error).message).toMatch(
97+
/both produce the bundle name 'lib-zz-child'/,
98+
);
99+
});
100+
48101
it('should fail when entry point is not a .ts or .mts file', async () => {
49102
await harness.modifyFile('projects/lib/package.json', (content) => {
50103
const pkg = JSON.parse(content);

0 commit comments

Comments
 (0)