From 4626dd5d573329e1cc816a45bd03eae87c1c5d3a Mon Sep 17 00:00:00 2001 From: Timofey Kachalov Date: Fri, 10 Jul 2026 20:13:19 +0400 Subject: [PATCH] Fixed `sourceMapFileName` ending in `.js.map` (e.g. `foo.min.js.map`) being mangled in the emitted `//# sourceMappingURL=` comment (#1433) --- CHANGELOG.md | 1 + .../normalizer-rules/SourceMapFileNameRule.ts | 14 ++++- .../functional-tests/issues/issue1312.spec.ts | 52 +++++++++++++++++++ .../options/OptionsNormalizer.spec.ts | 42 +++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 test/functional-tests/issues/issue1312.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e323b335..9239cdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Change Log v5.4.7 --- +* Fixed `sourceMapFileName` ending in `.js.map` (e.g. `foo.min.js.map`) being mangled in the emitted `//# sourceMappingURL=` comment. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 * Fixed `URIError: URI malformed` crash when `stringArray` with `base64`/`rc4` encoding processed a string literal containing lone surrogate code units (e.g. `"[^\uD800-\uDFFF]"`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1431 v5.4.6 diff --git a/src/options/normalizer-rules/SourceMapFileNameRule.ts b/src/options/normalizer-rules/SourceMapFileNameRule.ts index ac5bf600..c8b40d95 100644 --- a/src/options/normalizer-rules/SourceMapFileNameRule.ts +++ b/src/options/normalizer-rules/SourceMapFileNameRule.ts @@ -12,7 +12,19 @@ export const SourceMapFileNameRule: TOptionsNormalizerRule = (options: IOptions) let { sourceMapFileName }: { sourceMapFileName: string } = options; if (sourceMapFileName) { - sourceMapFileName = sourceMapFileName.replace(/^\/+/, '').replace(/(?:\.js)?(?:\.map)?$/, ''); + sourceMapFileName = sourceMapFileName.replace(/^\/+/, ''); + + // a fully-qualified `*.js.map` file name is already in the canonical form, so it is kept as-is + // (otherwise the extension-stripping heuristic below would mangle names like `foo.min.js.map`) + // https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 + if (sourceMapFileName.endsWith('.js.map')) { + return { + ...options, + sourceMapFileName + }; + } + + sourceMapFileName = sourceMapFileName.replace(/(?:\.js)?(?:\.map)?$/, ''); let sourceMapFileNameParts: string[] = sourceMapFileName.split(StringSeparator.Dot); const sourceMapFileNamePartsCount: number = sourceMapFileNameParts.length; diff --git a/test/functional-tests/issues/issue1312.spec.ts b/test/functional-tests/issues/issue1312.spec.ts new file mode 100644 index 00000000..6b6c8f15 --- /dev/null +++ b/test/functional-tests/issues/issue1312.spec.ts @@ -0,0 +1,52 @@ +import { assert } from 'chai'; + +import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes'; + +import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode'; + +import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscatorFacade'; + +// +// https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 +// +describe('Issue #1312', () => { + describe('`sourceMappingURL` should use the provided `sourceMapFileName`', () => { + describe('Variant #1: `*.min.js.map` file name', () => { + const sourceMappingUrlRegExp: RegExp = /\/\/# sourceMappingURL=a\.min\.js\.map$/; + + let obfuscatedCode: string; + + before(() => { + obfuscatedCode = JavaScriptObfuscator.obfuscate("console.log('Hello World');", { + ...NO_ADDITIONAL_NODES_PRESET, + sourceMap: true, + sourceMapMode: SourceMapMode.Separate, + sourceMapFileName: 'a.min.js.map' + }).getObfuscatedCode(); + }); + + it('should keep the full `.js.map` file name in the `sourceMappingURL`', () => { + assert.match(obfuscatedCode, sourceMappingUrlRegExp); + }); + }); + + describe('Variant #2: base name without extension still gets `.js.map`', () => { + const sourceMappingUrlRegExp: RegExp = /\/\/# sourceMappingURL=a\.js\.map$/; + + let obfuscatedCode: string; + + before(() => { + obfuscatedCode = JavaScriptObfuscator.obfuscate("console.log('Hello World');", { + ...NO_ADDITIONAL_NODES_PRESET, + sourceMap: true, + sourceMapMode: SourceMapMode.Separate, + sourceMapFileName: 'a' + }).getObfuscatedCode(); + }); + + it('should append `.js.map` to a bare file name', () => { + assert.match(obfuscatedCode, sourceMappingUrlRegExp); + }); + }); + }); +}); diff --git a/test/functional-tests/options/OptionsNormalizer.spec.ts b/test/functional-tests/options/OptionsNormalizer.spec.ts index d09abc24..7b84abce 100644 --- a/test/functional-tests/options/OptionsNormalizer.spec.ts +++ b/test/functional-tests/options/OptionsNormalizer.spec.ts @@ -700,6 +700,48 @@ describe('OptionsNormalizer', () => { assert.deepEqual(optionsPreset, expectedOptionsPreset); }); }); + + // https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 + describe('Full `.js.map` file name is preserved as-is', () => { + before(() => { + optionsPreset = getNormalizedOptions({ + ...getDefaultOptions(), + sourceMapBaseUrl: 'http://localhost:9000', + sourceMapFileName: 'outputSourceMapName.min.js.map' + }); + + expectedOptionsPreset = { + ...getDefaultOptions(), + sourceMapBaseUrl: 'http://localhost:9000/', + sourceMapFileName: 'outputSourceMapName.min.js.map' + }; + }); + + it('should normalize options preset', () => { + assert.deepEqual(optionsPreset, expectedOptionsPreset); + }); + }); + + // https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 + describe('Full `.js.map` file name with leading slashes', () => { + before(() => { + optionsPreset = getNormalizedOptions({ + ...getDefaultOptions(), + sourceMapBaseUrl: 'http://localhost:9000', + sourceMapFileName: '//outputSourceMapName.min.js.map' + }); + + expectedOptionsPreset = { + ...getDefaultOptions(), + sourceMapBaseUrl: 'http://localhost:9000/', + sourceMapFileName: 'outputSourceMapName.min.js.map' + }; + }); + + it('should normalize options preset', () => { + assert.deepEqual(optionsPreset, expectedOptionsPreset); + }); + }); }); describe('splitStringsChunkLengthRule', () => {