• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

javascript-obfuscator / javascript-obfuscator / 29217582589

10 Jul 2026 04:46PM UTC coverage: 95.97% (-0.02%) from 95.994%
29217582589

push

github

web-flow
Fixed directory obfuscation with a set `sourceMapFileName` making all files share and overwrite one `.map` (#1436)

2019 of 2214 branches covered (91.19%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

6126 of 6273 relevant lines covered (97.66%)

20108641.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.75
/src/cli/utils/ObfuscatedCodeFileUtils.ts
1
import * as fs from 'fs';
4✔
2
import * as path from 'path';
4✔
3

4
import { TInputCLIOptions } from '../../types/options/TInputCLIOptions';
5

6
import { StringSeparator } from '../../enums/StringSeparator';
4✔
7

8
import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
4✔
9

10
export class ObfuscatedCodeFileUtils {
4✔
11
    /**
12
     * @type {string}
13
     */
14
    private readonly inputPath: string;
15

16
    /**
17
     * @type {TInputCLIOptions}
18
     */
19
    private readonly options: TInputCLIOptions;
20

21
    /**
22
     * @param {string} inputPath
23
     * @param {TInputCLIOptions} options
24
     */
25
    public constructor(inputPath: string, options: TInputCLIOptions) {
26
        this.inputPath = path.normalize(inputPath);
252✔
27
        this.options = options;
252✔
28
    }
29

30
    /**
31
     * @param {string} filePath
32
     * @returns {string}
33
     */
34
    public getOutputCodePath(filePath: string): string {
35
        const normalizedFilePath: string = path.normalize(filePath);
172✔
36
        const normalizedRawOutputPath: string | null = this.options.output ? path.normalize(this.options.output) : null;
172✔
37

38
        if (!normalizedRawOutputPath) {
172✔
39
            return normalizedFilePath
48✔
40
                .split(StringSeparator.Dot)
41
                .map((value: string, index: number) => {
42
                    return index === 0 ? `${value}${JavaScriptObfuscatorCLI.obfuscatedFilePrefix}` : value;
96✔
43
                })
44
                .join(StringSeparator.Dot);
45
        }
46

47
        const rawInputPathStats: fs.Stats = fs.lstatSync(this.inputPath);
124✔
48
        const outputPathExtName: string = path.extname(normalizedRawOutputPath);
124✔
49

50
        const isDirectoryRawInputPath: boolean = rawInputPathStats.isDirectory();
124✔
51
        const isDirectoryRawOutputPath: boolean =
52
            !JavaScriptObfuscatorCLI.availableInputExtensions.includes(outputPathExtName);
124✔
53

54
        if (isDirectoryRawInputPath) {
124✔
55
            if (isDirectoryRawOutputPath) {
44✔
56
                const parsedNormalizedFilePath: path.ParsedPath = path.parse(normalizedFilePath);
40✔
57

58
                // Make ending with "/" consistent
59
                if (!parsedNormalizedFilePath.dir.endsWith('/') && this.inputPath.endsWith('/')) {
40✔
60
                    parsedNormalizedFilePath.dir += '/';
8✔
61
                }
62

63
                const baseOutputPath: string = path.join(
40✔
64
                    parsedNormalizedFilePath.dir.replace(this.inputPath, ''),
65
                    parsedNormalizedFilePath.base
66
                );
67

68
                return path.join(normalizedRawOutputPath, baseOutputPath);
40✔
69
            } else {
70
                throw new Error('Output path for directory obfuscation should be a directory path');
4✔
71
            }
72
        } else {
73
            if (isDirectoryRawOutputPath) {
80✔
74
                return path.join(normalizedRawOutputPath, path.basename(filePath));
4✔
75
            } else {
76
                return normalizedRawOutputPath;
76✔
77
            }
78
        }
79
    }
80

81
    /**
82
     * @param {string} outputCodePath
83
     * @param {string} sourceMapFileName
84
     * @returns {string}
85
     */
86
    public getOutputSourceMapPath(outputCodePath: string, sourceMapFileName: string = ''): string {
16✔
87
        if (!outputCodePath) {
116✔
88
            throw new Error('Output code path is empty');
4✔
89
        }
90

91
        sourceMapFileName = this.getUniqueSourceMapFileName(outputCodePath, sourceMapFileName);
112✔
92

93
        let normalizedOutputCodePath: string = path.normalize(outputCodePath);
112✔
94
        let parsedOutputCodePath: path.ParsedPath = path.parse(normalizedOutputCodePath);
112✔
95

96
        if (!parsedOutputCodePath.ext && !sourceMapFileName) {
112✔
97
            throw new Error('Source map file name should be set when output code path is a directory path');
4✔
98
        }
99

100
        if (sourceMapFileName) {
108✔
101
            const indexOfLastSeparator: number = normalizedOutputCodePath.lastIndexOf(path.sep);
60✔
102
            let sourceMapPath: string;
103

104
            if (parsedOutputCodePath.ext) {
60✔
105
                // File path with directory, like: `foo/bar.js`, or without, like: `bar.js`
106
                const isFilePathWithDirectory: boolean = indexOfLastSeparator > 0;
48✔
107

108
                sourceMapPath = isFilePathWithDirectory ? normalizedOutputCodePath.slice(0, indexOfLastSeparator) : '';
48✔
109
            } else {
110
                sourceMapPath = normalizedOutputCodePath;
12✔
111
            }
112

113
            // remove possible drive letter for win32 environment
114
            const normalizedSourceMapFilePath: string = sourceMapFileName.replace(/^[a-zA-Z]:\\*/, '');
60✔
115

116
            normalizedOutputCodePath = path.join(sourceMapPath, normalizedSourceMapFilePath);
60✔
117
        }
118

119
        if (!/\.js\.map$/.test(normalizedOutputCodePath)) {
108✔
120
            parsedOutputCodePath = path.parse(normalizedOutputCodePath);
84✔
121
            const outputCodePathWithoutExtension: string = path.join(
84✔
122
                parsedOutputCodePath.dir,
123
                parsedOutputCodePath.name
124
            );
125

126
            normalizedOutputCodePath = `${outputCodePathWithoutExtension}.js.map`;
84✔
127
        } else if (/\.js$/.test(normalizedOutputCodePath)) {
24!
UNCOV
128
            normalizedOutputCodePath += '.map';
×
129
        }
130

131
        return normalizedOutputCodePath;
108✔
132
    }
133

134
    /**
135
     * @param {string} outputPath
136
     * @param {string} data
137
     */
138
    public writeFile(outputPath: string, data: string): void {
139
        fs.mkdirSync(path.dirname(outputPath), { recursive: true });
148✔
140

141
        fs.writeFileSync(outputPath, data, {
148✔
142
            encoding: JavaScriptObfuscatorCLI.encoding
143
        });
144
    }
145

146
    /**
147
     * For directory obfuscation a single `sourceMapFileName` would be shared by every file and the
148
     * source maps would overwrite each other, so it is prefixed with the output code file name to
149
     * keep each source map unique.
150
     * https://github.com/javascript-obfuscator/javascript-obfuscator/issues/817
151
     *
152
     * @param {string} outputCodePath
153
     * @param {string} sourceMapFileName
154
     * @returns {string}
155
     */
156
    private getUniqueSourceMapFileName(outputCodePath: string, sourceMapFileName: string): string {
157
        if (!sourceMapFileName || !this.isDirectoryInputPath()) {
112✔
158
            return sourceMapFileName;
104✔
159
        }
160

161
        const outputCodeName: string = path.parse(outputCodePath).name;
8✔
162
        const parsedSourceMapFileName: path.ParsedPath = path.parse(sourceMapFileName);
8✔
163

164
        // keep any leading directory part of `sourceMapFileName`, prefix only its file name
165
        return path.join(parsedSourceMapFileName.dir, `${outputCodeName}-${parsedSourceMapFileName.base}`);
8✔
166
    }
167

168
    /**
169
     * @returns {boolean}
170
     */
171
    private isDirectoryInputPath(): boolean {
172
        try {
60✔
173
            return fs.lstatSync(this.inputPath).isDirectory();
60✔
174
        } catch {
175
            return false;
48✔
176
        }
177
    }
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc