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

javascript-obfuscator / javascript-obfuscator / 29134760830

10 Jul 2026 04:46PM UTC coverage: 95.97%. Remained the same
29134760830

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%)

1 existing line in 1 file now uncovered.

6126 of 6273 relevant lines covered (97.66%)

30113426.58 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';
6✔
2
import * as path from 'path';
6✔
3

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

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

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

10
export class ObfuscatedCodeFileUtils {
6✔
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);
378✔
27
        this.options = options;
378✔
28
    }
29

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

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

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

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

54
        if (isDirectoryRawInputPath) {
186✔
55
            if (isDirectoryRawOutputPath) {
66✔
56
                const parsedNormalizedFilePath: path.ParsedPath = path.parse(normalizedFilePath);
60✔
57

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

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

68
                return path.join(normalizedRawOutputPath, baseOutputPath);
60✔
69
            } else {
70
                throw new Error('Output path for directory obfuscation should be a directory path');
6✔
71
            }
72
        } else {
73
            if (isDirectoryRawOutputPath) {
120✔
74
                return path.join(normalizedRawOutputPath, path.basename(filePath));
6✔
75
            } else {
76
                return normalizedRawOutputPath;
114✔
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 {
24✔
87
        if (!outputCodePath) {
174✔
88
            throw new Error('Output code path is empty');
6✔
89
        }
90

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

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

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

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

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

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

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

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

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

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

131
        return normalizedOutputCodePath;
162✔
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 });
222✔
140

141
        fs.writeFileSync(outputPath, data, {
222✔
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()) {
168✔
158
            return sourceMapFileName;
156✔
159
        }
160

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

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

168
    /**
169
     * @returns {boolean}
170
     */
171
    private isDirectoryInputPath(): boolean {
172
        try {
90✔
173
            return fs.lstatSync(this.inputPath).isDirectory();
90✔
174
        } catch {
175
            return false;
72✔
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