Skip to content

Commit d563680

Browse files
committed
fix(@angular/build): anchor debug ID comment matching and make injection idempotent
Anchor the DEBUG_ID_COMMENT regex to the end of the file to prevent it from matching and replacing comments nested inside string literals or template literals. Introduce the stripDebugIdFromSourceMap utility to clean source map text before hashing, making injectDebugIds idempotent in-memory.
1 parent eaedbd8 commit d563680

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

packages/angular/build/src/builders/application/inject-debug-ids.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
generateDebugId,
1212
injectDebugIdIntoJs,
1313
injectDebugIdIntoSourceMap,
14+
stripDebugIdFromSourceMap,
1415
} from '../../utils/debug-id';
1516

1617
/**
@@ -42,7 +43,8 @@ export function injectDebugIds(outputFiles: BuildOutputFile[]): void {
4243
continue;
4344
}
4445

45-
const id = generateDebugId(map.contents);
46+
const mapTextForHash = stripDebugIdFromSourceMap(map.text);
47+
const id = generateDebugId(mapTextForHash);
4648
file.contents = encoder.encode(injectDebugIdIntoJs(file.text, id));
4749
map.contents = encoder.encode(injectDebugIdIntoSourceMap(map.text, id));
4850
}

packages/angular/build/src/utils/debug-id.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export function generateDebugId(name: string | Uint8Array): string {
4040
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
4141
}
4242

43-
/** Pattern matching an existing `//# debugId=<uuid>` comment anywhere in the file. */
44-
const DEBUG_ID_COMMENT = /\n\/\/# debugId=[^\r\n]*\n?/;
43+
/** Pattern matching an existing `//# debugId=<uuid>` comment at the end of the file. */
44+
const DEBUG_ID_COMMENT = /(\n\/\/# debugId=[^\r\n]*)(\n\/\/# sourceMappingURL=[^\r\n]*)?(\s*)$/;
4545

4646
/** Pattern matching the `//# sourceMappingURL=` comment, used to position the debug-id line. */
4747
const SOURCE_MAPPING_URL_COMMENT = /\n\/\/# sourceMappingURL=[^\r\n]*\s*$/;
@@ -58,7 +58,7 @@ export function injectDebugIdIntoJs(text: string, id: string): string {
5858

5959
// Replace any existing debugId comment to keep the operation idempotent.
6060
if (DEBUG_ID_COMMENT.test(text)) {
61-
return text.replace(DEBUG_ID_COMMENT, `\n${comment}\n`);
61+
return text.replace(DEBUG_ID_COMMENT, (_, p1, p2, p3) => `\n${comment}${p2 || ''}${p3 || ''}`);
6262
}
6363

6464
if (SOURCE_MAPPING_URL_COMMENT.test(text)) {
@@ -96,3 +96,11 @@ export function injectDebugIdIntoSourceMap(json: string, id: string): string {
9696

9797
return JSON.stringify(parsed, null, indent);
9898
}
99+
100+
/**
101+
* Strips any existing `debugId` field from the source map JSON string to restore
102+
* the original JSON contents for deterministic/idempotent hashing.
103+
*/
104+
export function stripDebugIdFromSourceMap(json: string): string {
105+
return json.replace(/,\s*"debugId"\s*:\s*"[^"]*"|\s*"debugId"\s*:\s*"[^"]*"\s*,?/g, '');
106+
}

packages/angular/build/src/utils/debug-id_spec.ts

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

9-
import { generateDebugId, injectDebugIdIntoJs, injectDebugIdIntoSourceMap } from './debug-id';
9+
import {
10+
generateDebugId,
11+
injectDebugIdIntoJs,
12+
injectDebugIdIntoSourceMap,
13+
stripDebugIdFromSourceMap,
14+
} from './debug-id';
1015

1116
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
1217

@@ -59,6 +64,17 @@ describe('debug-id', () => {
5964
// Re-running with the same id is a no-op.
6065
expect(injectDebugIdIntoJs(result, id)).toBe(result);
6166
});
67+
68+
it('does not replace a debugId comment that is inside a string or template literal', () => {
69+
const text =
70+
'const t = `\n//# debugId=00000000-0000-5000-8000-000000000000\n`;\n//# sourceMappingURL=foo.js.map\n';
71+
const result = injectDebugIdIntoJs(text, id);
72+
expect(result).toBe(
73+
'const t = `\n//# debugId=00000000-0000-5000-8000-000000000000\n`;\n' +
74+
`//# debugId=${id}\n` +
75+
'//# sourceMappingURL=foo.js.map\n',
76+
);
77+
});
6278
});
6379

6480
describe('injectDebugIdIntoSourceMap', () => {
@@ -95,4 +111,27 @@ describe('debug-id', () => {
95111
expect(injectDebugIdIntoSourceMap(malformed, id)).toBe(malformed);
96112
});
97113
});
114+
115+
describe('stripDebugIdFromSourceMap', () => {
116+
it('removes the debugId field from pretty-printed source maps', () => {
117+
const original = '{\n\t"version": 3,\n\t"mappings": ""\n}';
118+
const updated =
119+
'{\n\t"version": 3,\n\t"mappings": "",\n\t"debugId": "11111111-2222-5333-9444-555555555555"\n}';
120+
expect(stripDebugIdFromSourceMap(updated)).toBe(original);
121+
});
122+
123+
it('removes the debugId field from minified source maps', () => {
124+
const original = '{"version":3,"mappings":""}';
125+
const updated =
126+
'{"version":3,"mappings":"","debugId":"11111111-2222-5333-9444-555555555555"}';
127+
expect(stripDebugIdFromSourceMap(updated)).toBe(original);
128+
});
129+
130+
it('removes the debugId field when it is the first property', () => {
131+
const original = '{\n\t"version": 3,\n\t"mappings": ""\n}';
132+
const updated =
133+
'{\n\t"debugId": "11111111-2222-5333-9444-555555555555",\n\t"version": 3,\n\t"mappings": ""\n}';
134+
expect(stripDebugIdFromSourceMap(updated)).toBe(original);
135+
});
136+
});
98137
});

0 commit comments

Comments
 (0)