diff --git a/packages/angular/build/src/builders/application/inject-debug-ids.ts b/packages/angular/build/src/builders/application/inject-debug-ids.ts index 61c7defb4fd0..59dc9c76f3af 100644 --- a/packages/angular/build/src/builders/application/inject-debug-ids.ts +++ b/packages/angular/build/src/builders/application/inject-debug-ids.ts @@ -11,6 +11,7 @@ import { generateDebugId, injectDebugIdIntoJs, injectDebugIdIntoSourceMap, + stripDebugIdFromSourceMap, } from '../../utils/debug-id'; /** @@ -42,8 +43,10 @@ export function injectDebugIds(outputFiles: BuildOutputFile[]): void { continue; } - const id = generateDebugId(map.contents); + const mapText = map.text; + const mapTextForHash = stripDebugIdFromSourceMap(mapText); + const id = generateDebugId(mapTextForHash); file.contents = encoder.encode(injectDebugIdIntoJs(file.text, id)); - map.contents = encoder.encode(injectDebugIdIntoSourceMap(map.text, id)); + map.contents = encoder.encode(injectDebugIdIntoSourceMap(mapText, id)); } } diff --git a/packages/angular/build/src/utils/debug-id.ts b/packages/angular/build/src/utils/debug-id.ts index 5093c293269a..a03fd571ec72 100644 --- a/packages/angular/build/src/utils/debug-id.ts +++ b/packages/angular/build/src/utils/debug-id.ts @@ -40,8 +40,8 @@ export function generateDebugId(name: string | Uint8Array): string { return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`; } -/** Pattern matching an existing `//# debugId=` comment anywhere in the file. */ -const DEBUG_ID_COMMENT = /\n\/\/# debugId=[^\r\n]*\n?/; +/** Pattern matching an existing `//# debugId=` comment at the end of the file. */ +const DEBUG_ID_COMMENT = /(\n\/\/# debugId=[^\r\n]*)(\n\/\/# sourceMappingURL=[^\r\n]*)?(\s*)$/; /** Pattern matching the `//# sourceMappingURL=` comment, used to position the debug-id line. */ const SOURCE_MAPPING_URL_COMMENT = /\n\/\/# sourceMappingURL=[^\r\n]*\s*$/; @@ -58,7 +58,7 @@ export function injectDebugIdIntoJs(text: string, id: string): string { // Replace any existing debugId comment to keep the operation idempotent. if (DEBUG_ID_COMMENT.test(text)) { - return text.replace(DEBUG_ID_COMMENT, `\n${comment}\n`); + return text.replace(DEBUG_ID_COMMENT, (_, p1, p2, p3) => `\n${comment}${p2 || ''}${p3 || ''}`); } if (SOURCE_MAPPING_URL_COMMENT.test(text)) { @@ -96,3 +96,11 @@ export function injectDebugIdIntoSourceMap(json: string, id: string): string { return JSON.stringify(parsed, null, indent); } + +/** + * Strips any existing `debugId` field from the source map JSON string to restore + * the original JSON contents for deterministic/idempotent hashing. + */ +export function stripDebugIdFromSourceMap(json: string): string { + return json.replace(/,\s*"debugId"\s*:\s*"[^"]*"|\s*"debugId"\s*:\s*"[^"]*"\s*,?/g, ''); +} diff --git a/packages/angular/build/src/utils/debug-id_spec.ts b/packages/angular/build/src/utils/debug-id_spec.ts index d5e833d6a903..f254e28f6eee 100644 --- a/packages/angular/build/src/utils/debug-id_spec.ts +++ b/packages/angular/build/src/utils/debug-id_spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { generateDebugId, injectDebugIdIntoJs, injectDebugIdIntoSourceMap } from './debug-id'; +import { + generateDebugId, + injectDebugIdIntoJs, + injectDebugIdIntoSourceMap, + stripDebugIdFromSourceMap, +} from './debug-id'; const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; @@ -59,6 +64,17 @@ describe('debug-id', () => { // Re-running with the same id is a no-op. expect(injectDebugIdIntoJs(result, id)).toBe(result); }); + + it('does not replace a debugId comment that is inside a string or template literal', () => { + const text = + 'const t = `\n//# debugId=00000000-0000-5000-8000-000000000000\n`;\n//# sourceMappingURL=foo.js.map\n'; + const result = injectDebugIdIntoJs(text, id); + expect(result).toBe( + 'const t = `\n//# debugId=00000000-0000-5000-8000-000000000000\n`;\n' + + `//# debugId=${id}\n` + + '//# sourceMappingURL=foo.js.map\n', + ); + }); }); describe('injectDebugIdIntoSourceMap', () => { @@ -95,4 +111,27 @@ describe('debug-id', () => { expect(injectDebugIdIntoSourceMap(malformed, id)).toBe(malformed); }); }); + + describe('stripDebugIdFromSourceMap', () => { + it('removes the debugId field from pretty-printed source maps', () => { + const original = '{\n\t"version": 3,\n\t"mappings": ""\n}'; + const updated = + '{\n\t"version": 3,\n\t"mappings": "",\n\t"debugId": "11111111-2222-5333-9444-555555555555"\n}'; + expect(stripDebugIdFromSourceMap(updated)).toBe(original); + }); + + it('removes the debugId field from minified source maps', () => { + const original = '{"version":3,"mappings":""}'; + const updated = + '{"version":3,"mappings":"","debugId":"11111111-2222-5333-9444-555555555555"}'; + expect(stripDebugIdFromSourceMap(updated)).toBe(original); + }); + + it('removes the debugId field when it is the first property', () => { + const original = '{\n\t"version": 3,\n\t"mappings": ""\n}'; + const updated = + '{\n\t"debugId": "11111111-2222-5333-9444-555555555555",\n\t"version": 3,\n\t"mappings": ""\n}'; + expect(stripDebugIdFromSourceMap(updated)).toBe(original); + }); + }); });