import * as fs from 'fs'; import * as path from 'path'; import * as rimraf from 'rimraf'; import * as sinon from 'sinon'; import { resolveSources } from 'source-map-resolve'; import { assert } from 'chai'; import { ISourceMap } from '../../../src/interfaces/source-code/ISourceMap'; import { StdoutWriteMock } from '../../mocks/StdoutWriteMock'; import { JavaScriptObfuscatorCLI } from '../../../src/JavaScriptObfuscatorCLIFacade'; import { ProApiClient } from '../../../src/pro-api/ProApiClient'; import { parseSourceMapFromObfuscatedCode } from '../../helpers/parseSourceMapFromObfuscatedCode'; describe('JavaScriptObfuscatorCLI', function (): void { this.timeout(100000); const expectedError: RegExp = /Given input path must be a valid/; const fixturesDirName: string = path.join('test', 'fixtures'); const fixtureFileName: string = 'sample.js'; const fixtureFilePath: string = path.join(fixturesDirName, fixtureFileName); const outputDirName: string = path.join('test', 'tmp'); const outputFileName: string = 'sample-obfuscated.js'; const outputFilePath: string = path.join(outputDirName, outputFileName); const configDirName: string = path.join('test', 'fixtures'); const configFileName: string = 'config.js'; const configFilePath: string = path.join(configDirName, configFileName); describe('run', () => { before(async () => { fs.mkdirSync(outputDirName, { recursive: true }); }); describe('Variant #1: obfuscation of single file', () => { describe('--output` option is set', () => { describe('Variant #1: input file path is before options', () => { let isFileExist: boolean; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0' ]); isFileExist = fs.existsSync(outputFilePath); }); it('should create file with obfuscated code in `--output` directory', () => { assert.equal(isFileExist, true); }); after(() => { fs.unlinkSync(outputFilePath); }); }); describe('Variant #2: input file path is after options', () => { let isFileExist: boolean; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', fixtureFilePath ]); isFileExist = fs.existsSync(outputFilePath); }); it('should create file with obfuscated code in `--output` directory', () => { assert.equal(isFileExist, true); }); after(() => { fs.unlinkSync(outputFilePath); }); }); }); describe("`--output` option isn't set", () => { describe('Variant #1: default behaviour', () => { let outputFixturesFilePath: string, isFileExist: boolean; before(async () => { outputFixturesFilePath = path.join(fixturesDirName, outputFileName); await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator', fixtureFilePath]); isFileExist = fs.existsSync(outputFixturesFilePath); }); it(`should create file \`${outputFileName}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist, true); }); after(() => { fs.unlinkSync(outputFixturesFilePath); }); }); describe('Variant #2: invalid input file path', () => { it(`should throw an error`, async () => { try { await await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', path.join('wrong', 'file', 'path') ]); assert.fail('Expected error was not thrown'); } catch (error) { assert.match((error as Error).message, expectedError); } }); }); describe("Variant #3: input file extension isn't `.js`", () => { const expectedError: RegExp = /Given input path must be a valid/; const outputFileName: string = 'sample-obfuscated.ts'; const outputFilePath: string = path.join(outputDirName, outputFileName); before(async () => { fs.writeFileSync(outputFilePath, 'data'); }); it(`should throw an error`, async () => { try { await await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator', outputFilePath]); assert.fail('Expected error was not thrown'); } catch (error) { assert.match((error as Error).message, expectedError); } }); after(() => { fs.unlinkSync(outputFilePath); }); }); }); describe('--exclude option', () => { describe('Variant #1: --exclude option is pointed on different file', () => { let isFileExist: boolean; before(async () => { await await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--exclude', path.join('**', 'foo.js') ]); isFileExist = fs.existsSync(outputFilePath); }); it('should create file with obfuscated code in `--output` directory', () => { assert.equal(isFileExist, true); }); }); describe('Variant #2: --exclude option is pointed on input file', () => { it('should throw an error', async () => { try { await await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--exclude', path.join('**', 'sample.js') ]); assert.fail('Expected error was not thrown'); } catch (error) { assert.match((error as Error).message, expectedError); } }); }); }); }); describe('Variant #2: obfuscation of directory', () => { describe(`Variant #1: default behaviour`, () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; const outputFileName3: string = 'baz-obfuscated.js'; const readFileEncoding = 'utf8'; const regExp1: RegExp = /^var a1_0x(\w){4,6} *= *0x1;$/; const regExp2: RegExp = /^var a0_0x(\w){4,6} *= *0x2;$/; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, outputFixturesFilePath3: string, isFileExist1: boolean, isFileExist2: boolean, isFileExist3: boolean, fileContent1: string, fileContent2: string; before(async () => { outputFixturesFilePath1 = path.join(directoryPath, outputFileName1); outputFixturesFilePath2 = path.join(directoryPath, outputFileName2); outputFixturesFilePath3 = path.join(directoryPath, outputFileName3); await await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--rename-globals', 'true' ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); isFileExist3 = fs.existsSync(outputFixturesFilePath3); fileContent1 = fs.readFileSync(outputFixturesFilePath1, readFileEncoding); fileContent2 = fs.readFileSync(outputFixturesFilePath2, readFileEncoding); }); it(`should create file \`${outputFileName1}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist1, true); }); it(`should create file \`${outputFileName2}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist2, true); }); it(`shouldn't create file \`${outputFileName3}\` in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist3, false); }); it(`match #1: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent1, regExp1); }); it(`match #2: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent2, regExp2); }); after(() => { rimraf.sync(outputFixturesFilePath1); rimraf.sync(outputFixturesFilePath2); }); }); describe('Variant #2: obfuscation of directory with `identifiersPrefix` option value', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const identifiersPrefix: string = 'foo'; const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; const readFileEncoding = 'utf8'; const regExp1: RegExp = /^var foo1_0x(\w){4,6} *= *0x1;$/; const regExp2: RegExp = /^var foo0_0x(\w){4,6} *= *0x2;$/; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, isFileExist1: boolean, isFileExist2: boolean, fileContent1: string, fileContent2: string; before(async () => { outputFixturesFilePath1 = path.join(directoryPath, outputFileName1); outputFixturesFilePath2 = path.join(directoryPath, outputFileName2); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--identifiers-prefix', identifiersPrefix, '--rename-globals', 'true' ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); fileContent1 = fs.readFileSync(outputFixturesFilePath1, readFileEncoding); fileContent2 = fs.readFileSync(outputFixturesFilePath2, readFileEncoding); }); it(`should create file \`${outputFileName1}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist1, true); }); it(`should create file \`${outputFileName2}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist2, true); }); it(`match #1: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent1, regExp1); }); it(`match #2: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent2, regExp2); }); after(() => { rimraf.sync(outputFixturesFilePath1); rimraf.sync(outputFixturesFilePath2); }); }); describe('Variant #3: obfuscation of directory with `output` option', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const outputDirectoryName: string = 'obfuscated'; const outputDirectoryPath: string = path.join(directoryPath, outputDirectoryName); const outputFileName1: string = 'foo.js'; const outputFileName2: string = 'bar.js'; const outputFileName3: string = 'baz.js'; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, outputFixturesFilePath3: string, isFileExist1: boolean, isFileExist2: boolean, isFileExist3: boolean; before(async () => { outputFixturesFilePath1 = path.join(outputDirectoryPath, outputFileName1); outputFixturesFilePath2 = path.join(outputDirectoryPath, outputFileName2); outputFixturesFilePath3 = path.join(outputDirectoryPath, outputFileName3); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--output', outputDirectoryPath ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); isFileExist3 = fs.existsSync(outputFixturesFilePath3); }); it( `should create file \`${outputFileName1}\` with obfuscated code in ` + `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`, () => { assert.equal(isFileExist1, true); } ); it( `should create file \`${outputFileName2}\` with obfuscated code in ` + `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`, () => { assert.equal(isFileExist2, true); } ); it( `shouldn't create file \`${outputFileName3}\` in ` + `\`${path.join(fixturesDirName, outputDirectoryName)}\` directory`, () => { assert.equal(isFileExist3, false); } ); after(() => { rimraf.sync(outputDirectoryPath); }); }); describe('Variant #4: --exclude option', () => { describe('Variant #1: --exclude option is pointed on different file', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; const outputFileName3: string = 'baz-obfuscated.js'; const readFileEncoding = 'utf8'; const regExp1: RegExp = /^var a1_0x(\w){4,6} *= *0x1;$/; const regExp2: RegExp = /^var a0_0x(\w){4,6} *= *0x2;$/; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, outputFixturesFilePath3: string, isFileExist1: boolean, isFileExist2: boolean, isFileExist3: boolean, fileContent1: string, fileContent2: string; before(async () => { outputFixturesFilePath1 = path.join(directoryPath, outputFileName1); outputFixturesFilePath2 = path.join(directoryPath, outputFileName2); outputFixturesFilePath3 = path.join(directoryPath, outputFileName3); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--exclude', path.join('**', 'bark.js'), '--rename-globals', 'true' ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); isFileExist3 = fs.existsSync(outputFixturesFilePath3); fileContent1 = fs.readFileSync(outputFixturesFilePath1, readFileEncoding); fileContent2 = fs.readFileSync(outputFixturesFilePath2, readFileEncoding); }); it(`should create file \`${outputFileName1}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist1, true); }); it(`should create file \`${outputFileName2}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist2, true); }); it(`shouldn't create file \`${outputFileName3}\` in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist3, false); }); it(`match #1: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent1, regExp1); }); it(`match #2: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent2, regExp2); }); after(() => { rimraf.sync(outputFixturesFilePath1); rimraf.sync(outputFixturesFilePath2); }); }); describe('Variant #2: --exclude option is pointed on file under obfuscating directory', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; const outputFileName3: string = 'baz-obfuscated.js'; const readFileEncoding = 'utf8'; const regExp1: RegExp = /^var a0_0x(\w){4,6} *= *0x2;$/; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, outputFixturesFilePath3: string, isFileExist1: boolean, isFileExist2: boolean, isFileExist3: boolean, fileContent1: string; before(async () => { outputFixturesFilePath1 = path.join(directoryPath, outputFileName1); outputFixturesFilePath2 = path.join(directoryPath, outputFileName2); outputFixturesFilePath3 = path.join(directoryPath, outputFileName3); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--exclude', path.join('**', 'foo.js'), '--rename-globals', 'true' ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); isFileExist3 = fs.existsSync(outputFixturesFilePath3); fileContent1 = fs.readFileSync(outputFixturesFilePath2, readFileEncoding); }); it(`shouldn't create file \`${outputFileName1}\` in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist1, false); }); it(`should create file \`${outputFileName2}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist2, true); }); it(`shouldn't create file \`${outputFileName3}\` in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist3, false); }); it(`match #1: should create file with obfuscated code with prefixed identifier`, () => { assert.match(fileContent1, regExp1); }); after(() => { rimraf.sync(outputFixturesFilePath1); rimraf.sync(outputFixturesFilePath2); }); }); }); }); describe('`--sourceMap` option is set', () => { const outputSourceMapPath: string = `${outputFilePath}.map`; describe('Variant #1: `--sourceMapMode` option value is `separate`', () => { describe('Variant #1: default behaviour', () => { let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true' ]); try { sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(sourceMapContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); } catch (e) { isFileExist = false; } }); it('should create file with source map in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `version`', () => { assert.property(sourceMapObject, 'version'); }); it('source map from created file should contains property `names`', () => { assert.property(sourceMapObject, 'names'); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { rimraf.sync(outputFilePath); rimraf.sync(outputSourceMapPath); }); }); describe('Variant #2: `sourceMapBaseUrl` option is set', () => { let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-base-url', 'http://localhost:9000/' ]); try { sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(sourceMapContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); } catch (e) { isFileExist = false; } }); it('should create file with source map in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `version`', () => { assert.property(sourceMapObject, 'version'); }); it('source map from created file should contains property `names`', () => { assert.property(sourceMapObject, 'names'); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { fs.unlinkSync(outputFilePath); fs.unlinkSync(outputSourceMapPath); }); }); describe('Variant #3: `--sourceMapFileName` option is set', () => { const sourceMapFileName: string = 'test'; const sourceMapFilePath: string = `${sourceMapFileName}.js.map`; const outputSourceMapFilePath: string = path.join(outputDirName, sourceMapFilePath); let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-file-name', sourceMapFileName ]); try { sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const sourceMapContent: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(sourceMapContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); } catch (e) { isFileExist = false; } }); it('should create source map file with given name in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `version`', () => { assert.property(sourceMapObject, 'version'); }); it('source map from created file should contains property `names`', () => { assert.property(sourceMapObject, 'names'); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { fs.unlinkSync(outputFilePath); fs.unlinkSync(outputSourceMapFilePath); }); }); describe('Variant #4: `sourceMapSourcesMode` option is set', () => { describe('Variant #1: `sourcesContent` value', () => { const expectedSourceMapSourceName: string = 'sourceMap'; let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-sources-mode', 'sources-content' ]); try { sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(sourceMapContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); } catch (e) { isFileExist = false; } }); it('should create file with source map in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `sources`', () => { assert.property(sourceMapObject, 'sources'); }); it('source map from created file should contains property `sourcesContent`', () => { assert.property(sourceMapObject, 'sourcesContent'); }); it('source map source should has correct sources', () => { assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName); }); it('source map source should has correct sources content', () => { assert.equal(sourceMapObject.sourcesContent[0], sourceCodeContent); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { rimraf.sync(outputFilePath); rimraf.sync(outputSourceMapPath); }); }); describe('Variant #2: `sources` value', () => { const expectedSourceMapSourceName: string = path.basename(fixtureFileName); let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-sources-mode', 'sources' ]); try { sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const sourceMapContent: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(sourceMapContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); } catch (e) { isFileExist = false; } }); it('should create file with source map in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `sources`', () => { assert.property(sourceMapObject, 'sources'); }); it('source map from created file should not contains property `sourcesContent`', () => { assert.notProperty(sourceMapObject, 'sourcesContent'); }); it('source map source should has correct sources', () => { assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { rimraf.sync(outputFilePath); rimraf.sync(outputSourceMapPath); }); }); }); }); describe('Variant #2: `--sourceMapMode` option is `inline`', () => { describe('Variant #1: default behaviour', () => { let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-mode', 'inline' ]); isFileExist = fs.existsSync(outputSourceMapPath); sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); }); it("shouldn't create file with source map", () => { assert.equal(isFileExist, false); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { fs.unlinkSync(outputFilePath); }); }); describe('Variant #2: `sourceMapSourcesMode` option is set', () => { describe('Variant #1: `sourcesContent` value', () => { const expectedSourceMapSourceName: string = 'sourceMap'; let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-mode', 'inline', '--source-map-sources-mode', 'sources-content' ]); isFileExist = fs.existsSync(outputSourceMapPath); sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); }); it("shouldn't create file with source map", () => { assert.equal(isFileExist, false); }); it('source map from created file should contains property `sources`', () => { assert.property(sourceMapObject, 'sources'); }); it('source map from created file should contains property `sourcesContent`', () => { assert.property(sourceMapObject, 'sourcesContent'); }); it('source map source should has correct sources', () => { assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName); }); it('source map source should has correct sources content', () => { assert.equal(sourceMapObject.sourcesContent[0], sourceCodeContent); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { fs.unlinkSync(outputFilePath); }); }); describe('Variant #2: `sources` value', () => { const expectedSourceMapSourceName: string = path.basename(fixtureFileName); let isFileExist: boolean, resolvedSources: string, sourceCodeContent: string, sourceMapObject: ISourceMap; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--compact', 'true', '--self-defending', '0', '--source-map', 'true', '--source-map-mode', 'inline', '--source-map-sources-mode', 'sources' ]); isFileExist = fs.existsSync(outputSourceMapPath); sourceCodeContent = fs.readFileSync(fixtureFilePath, { encoding: 'utf8' }); const obfuscatedCodeContent = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); sourceMapObject = parseSourceMapFromObfuscatedCode(obfuscatedCodeContent); await new Promise((resolve) => { resolveSources(sourceMapObject, fixtureFilePath, fs.readFile, (error, result) => { resolvedSources = typeof result.sourcesContent[0] === 'string' ? result.sourcesContent[0] : ''; resolve(); }); }); }); it("shouldn't create file with source map", () => { assert.equal(isFileExist, false); }); it('source map from created file should contains property `sources`', () => { assert.property(sourceMapObject, 'sources'); }); it('source map from created file should contains property `sourcesContent`', () => { assert.notProperty(sourceMapObject, 'sourcesContent'); }); it('source map source should has correct sources', () => { assert.equal(sourceMapObject.sources[0], expectedSourceMapSourceName); }); it('should resolve correct sources from source map', () => { assert.equal(resolvedSources, sourceCodeContent); }); after(() => { fs.unlinkSync(outputFilePath); }); }); }); }); }); describe('help output', () => { let callback: sinon.SinonSpy, stdoutWriteMock: StdoutWriteMock, stubExit: sinon.SinonStub; beforeEach(() => { stubExit = sinon.stub(process, 'exit'); callback = sinon.spy(console, 'log'); stdoutWriteMock = new StdoutWriteMock(process.stdout.write); }); describe('`--help` option is set without any additional parameters', () => { let isConsoleLogCalled: boolean; beforeEach(async () => { stdoutWriteMock.mute(); await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator', '--help']); stdoutWriteMock.restore(); isConsoleLogCalled = callback.called; }); it('should print `console.log` help', () => { assert.equal(isConsoleLogCalled, true); }); }); describe('`--help` option is set before file path', () => { let isConsoleLogCalled: boolean; beforeEach(async () => { stdoutWriteMock.mute(); await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator', '--help', fixtureFilePath]); stdoutWriteMock.restore(); isConsoleLogCalled = callback.called; }); it('should print `console.log` help', () => { assert.equal(isConsoleLogCalled, true); }); }); describe('`--help` option is set after file path', () => { let isConsoleLogCalled: boolean; beforeEach(async () => { stdoutWriteMock.mute(); await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator', fixtureFilePath, '--help']); stdoutWriteMock.restore(); isConsoleLogCalled = callback.called; }); it('should print `console.log` help', () => { assert.equal(isConsoleLogCalled, true); }); }); describe('no arguments passed', () => { let isConsoleLogCalled: boolean; beforeEach(async () => { stdoutWriteMock.mute(); await JavaScriptObfuscatorCLI.obfuscate(['node', 'javascript-obfuscator']); stdoutWriteMock.restore(); isConsoleLogCalled = callback.called; }); it('should print `console.log` help', () => { assert.equal(isConsoleLogCalled, true); }); }); afterEach(() => { stubExit.restore(); callback.restore(); }); }); describe('`--config` option is set', () => { describe('Base options', () => { const outputSourceMapPath: string = `${outputFilePath}.map`; let isFileExist: boolean, sourceMapObject: any; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--config', configFilePath ]); try { const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; sourceMapObject = JSON.parse(content); } catch (e) { isFileExist = false; } }); it('should create file with source map in the same directory as output file', () => { assert.equal(isFileExist, true); }); it('source map from created file should contains property `version`', () => { assert.property(sourceMapObject, 'version'); }); it('source map from created file should contains property `sources`', () => { assert.property(sourceMapObject, 'sources'); }); it('source map from created file should contains property `names`', () => { assert.property(sourceMapObject, 'names'); }); after(() => { fs.unlinkSync(outputFilePath); fs.unlinkSync(outputSourceMapPath); }); }); describe('`--exclude` option', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; let outputFixturesFilePath1: string, outputFixturesFilePath2: string, isFileExist1: boolean, isFileExist2: boolean; before(async () => { outputFixturesFilePath1 = path.join(directoryPath, outputFileName1); outputFixturesFilePath2 = path.join(directoryPath, outputFileName2); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--config', configFilePath ]); isFileExist1 = fs.existsSync(outputFixturesFilePath1); isFileExist2 = fs.existsSync(outputFixturesFilePath2); }); it(`shouldn't create file \`${outputFileName1}\` in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist1, false); }); it(`should create file \`${outputFileName2}\` with obfuscated code in \`${fixturesDirName}\` directory`, () => { assert.equal(isFileExist2, true); }); after(() => { rimraf.sync(outputFixturesFilePath1); rimraf.sync(outputFixturesFilePath2); }); }); }); describe('`--config` option is set but overridden by CLI option', () => { const outputSourceMapPath: string = `${outputFilePath}.map`; let isFileExist: boolean; before(async () => { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', fixtureFilePath, '--output', outputFilePath, '--config', configFilePath, '--source-map', 'false' ]); try { fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }); isFileExist = true; } catch (e) { isFileExist = false; } }); it('should create file without source map in the same directory as output file', () => { assert.equal(isFileExist, false); }); after(() => { fs.unlinkSync(outputFilePath); }); }); describe('Logging', () => { describe('Obfuscating file message', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation'); const inputFileName1: string = 'foo.js'; const inputFileName2: string = 'bar.js'; const inputFilePath1: string = path.join(directoryPath, inputFileName1); const inputFilePath2: string = path.join(directoryPath, inputFileName2); const outputFileName1: string = 'foo-obfuscated.js'; const outputFileName2: string = 'bar-obfuscated.js'; const outputFilePath1: string = path.join(directoryPath, outputFileName1); const outputFilePath2: string = path.join(directoryPath, outputFileName2); const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath1}...`; const expectedLoggingMessage2: string = `[javascript-obfuscator-cli] Obfuscating file: ${inputFilePath2}...`; let consoleLogSpy: sinon.SinonSpy, loggingMessageResult1: string, loggingMessageResult2: string; before(async () => { consoleLogSpy = sinon.spy(console, 'log'); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--rename-globals', 'true' ]); loggingMessageResult1 = consoleLogSpy.getCall(1).args[0]; loggingMessageResult2 = consoleLogSpy.getCall(0).args[0]; }); it('Variant #1: should log file name to the console', () => { assert.include(loggingMessageResult1, expectedLoggingMessage1); }); it('Variant #2: should log file name to the console', () => { assert.include(loggingMessageResult2, expectedLoggingMessage2); }); after(() => { rimraf.sync(outputFilePath1); rimraf.sync(outputFilePath2); consoleLogSpy.restore(); }); }); describe('Error message', () => { const directoryPath: string = path.join(fixturesDirName, 'directory-obfuscation-error'); const inputFileName: string = 'foo.js'; const inputFilePath: string = path.join(directoryPath, inputFileName); const expectedLoggingMessage1: string = `[javascript-obfuscator-cli] Error in file: ${inputFilePath}...`; let consoleLogSpy: sinon.SinonSpy, loggingMessageResult: string; before(async () => { consoleLogSpy = sinon.spy(console, 'log'); try { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', directoryPath, '--rename-globals', 'true' ]); } catch {} loggingMessageResult = consoleLogSpy.getCall(1).args[0]; }); it('Should log file name to the console', () => { assert.include(loggingMessageResult, expectedLoggingMessage1); }); after(() => { consoleLogSpy.restore(); }); }); }); describe('`--pro-api-token` option', () => { let fetchStub: sinon.SinonStub; let proApiFilePath: string; before(async () => { proApiFilePath = path.join(outputDirName, 'pro-api-test.js'); fs.writeFileSync(proApiFilePath, 'const test = 1;'); }); afterEach(() => { if (fetchStub) { fetchStub.restore(); } }); after(() => { if (fs.existsSync(proApiFilePath)) { fs.unlinkSync(proApiFilePath); } }); describe('Variant #1: Pro API token with vmObfuscation', () => { it('should use Pro API when --pro-api-token and --vm-obfuscation are provided', async () => { const mockResponse = { ok: true, status: 200, text: async () => JSON.stringify({ type: 'result', code: 'var obfuscated=1;', sourceMap: '' }) } as Response; fetchStub = sinon.stub(global, 'fetch').resolves(mockResponse); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', proApiFilePath, '--output', path.join(outputDirName, 'pro-api-output.js'), '--pro-api-token', 'test-token-123', '--vm-obfuscation', 'true' ]); // Wait for async operations await new Promise((resolve) => setTimeout(resolve, 100)); assert.isTrue(fetchStub.called, 'fetch should be called for Pro API'); const calledUrl = fetchStub.firstCall?.args[0]; assert.include(calledUrl, 'obfuscator.io/api/v1'); }); }); describe('Variant #2: Pro API token with parseHtml', () => { it('should use Pro API when --pro-api-token and --parse-html are provided', async () => { const mockResponse = { ok: true, status: 200, text: async () => JSON.stringify({ type: 'result', code: 'var obfuscated=1;', sourceMap: '' }) } as Response; fetchStub = sinon.stub(global, 'fetch').resolves(mockResponse); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', proApiFilePath, '--output', path.join(outputDirName, 'pro-api-output2.js'), '--pro-api-token', 'test-token-123', '--parse-html', 'true' ]); // Wait for async operations await new Promise((resolve) => setTimeout(resolve, 100)); assert.isTrue(fetchStub.called, 'fetch should be called for Pro API'); }); }); describe('Variant #3: Pro API token with version', () => { it('should pass version to Pro API URL when --pro-api-version is provided', async () => { const mockResponse = { ok: true, status: 200, text: async () => JSON.stringify({ type: 'result', code: 'var obfuscated=1;', sourceMap: '' }) } as Response; fetchStub = sinon.stub(global, 'fetch').resolves(mockResponse); await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', proApiFilePath, '--output', path.join(outputDirName, 'pro-api-output3.js'), '--pro-api-token', 'test-token-123', '--pro-api-version', '5.0.0-beta.20', '--vm-obfuscation', 'true' ]); // Wait for async operations await new Promise((resolve) => setTimeout(resolve, 100)); assert.isTrue(fetchStub.called, 'fetch should be called for Pro API'); const calledUrl = fetchStub.firstCall?.args[0]; assert.include(calledUrl, 'version=5.0.0-beta.20'); }); }); describe('Variant #4: Pro API token without Pro features throws error', () => { it('should throw error when --pro-api-token is provided but no Pro features enabled', async () => { const outputPath = path.join(outputDirName, 'local-output.js'); try { await JavaScriptObfuscatorCLI.obfuscate([ 'node', 'javascript-obfuscator', proApiFilePath, '--output', outputPath, '--pro-api-token', 'test-token-123', '--compact', 'true' ]); assert.fail('Expected error was not thrown'); } catch (error) { assert.include( (error as Error).message, 'Obfuscator.io Pro obfuscation works only when Pro features set.' ); } }); }); }); describe('hasProFeatures static method', () => { it('should return true for vmObfuscation', () => { assert.isTrue(ProApiClient.hasProFeatures({ vmObfuscation: true })); }); it('should return true for parseHtml', () => { assert.isTrue(ProApiClient.hasProFeatures({ parseHtml: true })); }); it('should return false for no Pro features', () => { assert.isFalse(ProApiClient.hasProFeatures({ compact: true })); }); }); after(() => { rimraf.sync(outputDirName); }); }); });