Selaa lähdekoodia

Improved CLI directory obfuscation
Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/157
Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/156

sanex3339 7 vuotta sitten
vanhempi
commit
a3a52f88a1

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 ===
+v0.14.0
+---
+* **Breaking change:** Now CLI obfuscating directory recursively. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/157
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/156
+
 v0.13.0
 ---
 * **Breaking change:** `mangle` option was removed.

+ 6 - 2
README.md

@@ -210,11 +210,15 @@ javascript-obfuscator samples/sample.js --output output/output.js --compact true
 // creates a new file output/output.js
 ```
 
-#### Obfuscate directory
+#### Obfuscate directory recursively
 
 Usage:
 ```sh
-javascript-obfuscator input_directory_name [options]
+javascript-obfuscator ./dist [options]
+// creates a new obfuscated files under `./dist` directory near the input files with `obfuscated` postfix
+
+javascript-obfuscator ./dist --output ./dist/obfuscated [options]
+// creates a folder structure with obfuscated files under `./dist/obfuscated` path
 ```
 
 Obfuscation of all `.js` files under input directory. If this directory contains already obfuscated files with `-obfuscated` postfix - these files will ignored.

+ 0 - 0
bin/javascript-obfuscator.js → bin/javascript-obfuscator


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
dist/index.js


+ 10 - 8
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.13.0",
+  "version": "0.14.0-beta.1",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -16,17 +16,17 @@
   },
   "main": "dist/index.js",
   "bin": {
-    "javascript-obfuscator": "./bin/javascript-obfuscator.js"
+    "javascript-obfuscator": "./bin/javascript-obfuscator"
   },
   "dependencies": {
     "chalk": "2.3.0",
-    "chance": "1.0.12",
+    "chance": "1.0.13",
     "class-validator": "0.7.3",
     "commander": "2.12.2",
     "escodegen-wallaby": "1.6.15",
     "esprima": "4.0.0",
     "estraverse": "4.2.0",
-    "inversify": "4.8.0",
+    "inversify": "4.9.0",
     "md5": "2.2.1",
     "mkdirp": "0.5.1",
     "opencollective": "1.0.3",
@@ -46,8 +46,9 @@
     "@types/estree": "0.0.38",
     "@types/md5": "2.1.32",
     "@types/mkdirp": "0.5.2",
-    "@types/mocha": "2.2.44",
-    "@types/node": "8.5.1",
+    "@types/mocha": "2.2.45",
+    "@types/node": "8.5.2",
+    "@types/rimraf": "^2.0.2",
     "@types/sinon": "4.1.2",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.13.3",
@@ -59,11 +60,12 @@
     "chai": "4.1.2",
     "coveralls": "3.0.0",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "4.0.1",
+    "mocha": "4.1.0",
     "pre-commit": "1.2.2",
+    "rimraf": "^2.6.2",
     "sinon": "4.1.3",
     "threads": "^0.10.0",
-    "ts-node": "4.0.2",
+    "ts-node": "4.1.0",
     "tslint": "5.8.0",
     "tslint-eslint-rules": "4.1.1",
     "tslint-language-service": "0.9.7",

+ 9 - 3
src/cli/JavaScriptObfuscatorCLI.ts

@@ -143,7 +143,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     }
 
     public initialize (): void {
-        this.inputPath = this.arguments[0] || '';
+        this.inputPath = path.normalize(this.arguments[0] || '');
         this.commands = <commander.CommanderStatic>(new commander.Command());
 
         this.configureCommands();
@@ -331,13 +331,19 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
      * @param {TSourceCodeData} sourceCodeData
      */
     private processSourceCodeData (sourceCodeData: TSourceCodeData): void {
+        const outputPath: string = this.inputCLIOptions.output
+            ? path.normalize(this.inputCLIOptions.output)
+            : '';
+
         if (!Array.isArray(sourceCodeData)) {
-            const outputCodePath: string = this.inputCLIOptions.output || CLIUtils.getOutputCodePath(this.inputPath);
+            const outputCodePath: string = outputPath || CLIUtils.getOutputCodePath(this.inputPath);
 
             this.processSourceCode(sourceCodeData, outputCodePath);
         } else {
             sourceCodeData.forEach(({ filePath, content }: IFileData) => {
-                const outputCodePath: string = CLIUtils.getOutputCodePath(filePath);
+                const outputCodePath: string = outputPath
+                    ? path.join(outputPath, filePath)
+                    : CLIUtils.getOutputCodePath(filePath);
 
                 this.processSourceCode(content, outputCodePath);
             });

+ 2 - 1
src/cli/utils/CLIUtils.ts

@@ -12,7 +12,8 @@ export class CLIUtils {
      * @returns {string}
      */
     public static getOutputCodePath (inputPath: string): string {
-        return inputPath
+        return path
+            .normalize(inputPath)
             .split('.')
             .map((value: string, index: number) => {
                 return index === 0 ? `${value}${JavaScriptObfuscatorCLI.obfuscatedFilePrefix}` : value;

+ 16 - 7
src/cli/utils/SourceCodeReader.ts

@@ -18,7 +18,7 @@ export class SourceCodeReader {
         }
 
         if (SourceCodeReader.isDirectoryPath(inputPath)) {
-            return SourceCodeReader.readDirectory(inputPath);
+            return SourceCodeReader.readDirectoryRecursive(inputPath);
         }
 
         throw new ReferenceError(`Given input path must be a valid source code file or directory path`);
@@ -50,17 +50,26 @@ export class SourceCodeReader {
 
     /**
      * @param {string} directoryPath
+     * @param {IFileData[]} fileData
      * @returns {IFileData[]}
      */
-    private static readDirectory (directoryPath: string): IFileData[] {
-        return fs.readdirSync(directoryPath, JavaScriptObfuscatorCLI.encoding)
-            .filter(SourceCodeReader.isValidFile)
-            .map((fileName: string) => {
+    private static readDirectoryRecursive (directoryPath: string, fileData: IFileData[] = []): IFileData[] {
+        fs.readdirSync(directoryPath, JavaScriptObfuscatorCLI.encoding)
+            .forEach((fileName: string) => {
                 const filePath: string = `${directoryPath}/${fileName}`;
-                const content: string = fs.readFileSync(filePath, JavaScriptObfuscatorCLI.encoding);
 
-                return { filePath, content };
+                if (SourceCodeReader.isDirectoryPath(filePath)) {
+                    fileData.push(
+                        ...SourceCodeReader.readDirectoryRecursive(filePath)
+                    );
+                } else if (SourceCodeReader.isFilePath(filePath) && SourceCodeReader.isValidFile(fileName)) {
+                    const content: string = fs.readFileSync(filePath, JavaScriptObfuscatorCLI.encoding);
+
+                    fileData.push({ filePath, content });
+                }
             });
+
+        return fileData;
     }
 
     /**

+ 68 - 5
test/functional-tests/cli/JavaScriptObfuscatorCLI.spec.ts

@@ -1,5 +1,6 @@
 import * as fs from 'fs';
 import * as mkdirp from 'mkdirp';
+import * as rimraf from 'rimraf';
 import * as sinon from 'sinon';
 
 import { assert } from 'chai';
@@ -170,8 +171,70 @@ describe('JavaScriptObfuscatorCLI', function (): void {
             });
 
             after(() => {
-                fs.unlinkSync(outputFixturesFilePath1);
-                fs.unlinkSync(outputFixturesFilePath2);
+                rimraf.sync(outputFixturesFilePath1);
+                rimraf.sync(outputFixturesFilePath2);
+            });
+        });
+
+        describe('Variant #3: obfuscation of directory with `output` option', () => {
+            const directoryPath: string = `${fixturesDirName}/directory-obfuscation`;
+            const outputDirectoryName: string = 'obfuscated';
+            const outputDirectoryPath: string = `${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(() => {
+                outputFixturesFilePath1 = `${outputDirectoryPath}/${directoryPath}/${outputFileName1}`;
+                outputFixturesFilePath2 = `${outputDirectoryPath}/${directoryPath}/${outputFileName2}`;
+                outputFixturesFilePath3 = `${outputDirectoryPath}/${directoryPath}/${outputFileName3}`;
+
+                JavaScriptObfuscator.runCLI([
+                    '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 ` +
+                `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                () => {
+                    assert.equal(isFileExist1, true);
+                }
+            );
+
+            it(
+                `should create file \`${outputFileName2}\` with obfuscated code in ` +
+                `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                () => {
+                    assert.equal(isFileExist2, true);
+                }
+            );
+
+            it(
+                `shouldn't create file \`${outputFileName3}\` in ` +
+                `\`${fixturesDirName}/${outputDirectoryName}\` directory`,
+                () => {
+                    assert.equal(isFileExist3, false);
+                }
+            );
+
+            after(() => {
+                rimraf.sync(outputDirectoryPath);
             });
         });
 
@@ -225,8 +288,8 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                     });
 
                     after(() => {
-                        fs.unlinkSync(outputFilePath);
-                        fs.unlinkSync(outputSourceMapPath);
+                        rimraf.sync(outputFilePath);
+                        rimraf.sync(outputSourceMapPath);
                     });
                 });
 
@@ -516,7 +579,7 @@ describe('JavaScriptObfuscatorCLI', function (): void {
         });
 
         after(() => {
-            fs.rmdirSync(outputDirName);
+            rimraf.sync(outputDirName);
         });
     });
 });

+ 15 - 4
test/unit-tests/cli/utils/CLIUtils.spec.ts

@@ -6,11 +6,22 @@ import { CLIUtils } from '../../../../src/cli/utils/CLIUtils';
 
 describe('CLIUtils', () => {
     describe('getOutputCodePath (inputPath: string): string', () => {
-        let expectedInputPath: string = 'test/input/test-obfuscated.js',
-            inputPath: string = 'test/input/test.js';
+        describe('variant #1: base input path', () => {
+            let expectedOutputPath: string = 'test/input/test-obfuscated.js',
+                inputPath: string = 'test/input/test.js';
 
-        it('should output path based on `inputPath`', () => {
-            assert.equal(CLIUtils.getOutputCodePath(inputPath), expectedInputPath);
+            it('should output path based on `inputPath`', () => {
+                assert.equal(CLIUtils.getOutputCodePath(inputPath), expectedOutputPath);
+            });
+        });
+
+        describe('variant #2: relative input path with dot', () => {
+            let expectedOutputPath: string = 'input-obfuscated.js',
+                inputPath: string = './input.js';
+
+            it('should output path based on `inputPath`', () => {
+                assert.equal(CLIUtils.getOutputCodePath(inputPath), expectedOutputPath);
+            });
         });
     });
 

+ 71 - 11
test/unit-tests/cli/utils/SourceCodeReader.spec.ts

@@ -1,5 +1,6 @@
 import * as fs from 'fs';
 import * as mkdirp from 'mkdirp';
+import * as rimraf from 'rimraf';
 
 import { assert } from 'chai';
 
@@ -8,17 +9,17 @@ import { IFileData } from '../../../../src/interfaces/cli/IFileData';
 
 describe('SourceCodeReader', () => {
     const fileContent: string = 'test';
-    const tmpDir: string = 'test/tmp';
+    const tmpDirectoryPath: string = 'test/tmp';
 
     before(() => {
-        mkdirp.sync(tmpDir);
+        mkdirp.sync(tmpDirectoryPath);
     });
 
     describe('readSourceCode (inputPath: string): void', () => {
         describe('Variant #1: input path is a file path', () => {
             describe('`inputPath` is a valid path', () => {
                 const tmpFileName: string = 'test.js';
-                const inputPath: string = `${tmpDir}/${tmpFileName}`;
+                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;
 
                 let result: string | IFileData[];
 
@@ -38,7 +39,7 @@ describe('SourceCodeReader', () => {
 
             describe('`inputPath` is not a valid path', () => {
                 const tmpFileName: string = 'test.js';
-                const inputPath: string = `${tmpDir}/${tmpFileName}`;
+                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;
 
                 let testFunc: () => void;
 
@@ -53,7 +54,7 @@ describe('SourceCodeReader', () => {
 
             describe('`inputPath` has invalid extension', () => {
                 const tmpFileName: string = 'test.ts';
-                const inputPath: string = `${tmpDir}/${tmpFileName}`;
+                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;
 
                 let testFunc: () => void;
 
@@ -78,10 +79,10 @@ describe('SourceCodeReader', () => {
                 const tmpFileName2: string = 'bar.js';
                 const tmpFileName3: string = 'baz.png';
                 const tmpFileName4: string = 'bark-obfuscated.js';
-                const filePath1: string = `${tmpDir}/${tmpFileName1}`;
-                const filePath2: string = `${tmpDir}/${tmpFileName2}`;
-                const filePath3: string = `${tmpDir}/${tmpFileName3}`;
-                const filePath4: string = `${tmpDir}/${tmpFileName4}`;
+                const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
+                const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
+                const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
+                const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;
 
                 const expectedResult: IFileData[] = [
                     {
@@ -101,7 +102,7 @@ describe('SourceCodeReader', () => {
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
-                    result = SourceCodeReader.readSourceCode(tmpDir);
+                    result = SourceCodeReader.readSourceCode(tmpDirectoryPath);
                 });
 
                 it('should return files data', () => {
@@ -129,10 +130,69 @@ describe('SourceCodeReader', () => {
                     assert.throws(testFunc, ReferenceError);
                 });
             });
+
+            describe('`inputPath` is a directory with sub-directories', () => {
+                const parentDirectoryName1: string = 'parent1';
+                const parentDirectoryName2: string = 'parent';
+                const parentDirectoryPath1: string = `${tmpDirectoryPath}/${parentDirectoryName1}`;
+                const parentDirectoryPath2: string = `${tmpDirectoryPath}/${parentDirectoryName2}`;
+                const tmpFileName1: string = 'foo.js';
+                const tmpFileName2: string = 'bar.js';
+                const tmpFileName3: string = 'baz.js';
+                const tmpFileName4: string = 'bark.js';
+                const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
+                const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
+                const filePath3: string = `${parentDirectoryPath1}/${tmpFileName3}`;
+                const filePath4: string = `${parentDirectoryPath2}/${tmpFileName4}`;
+
+                const expectedResult: IFileData[] = [
+                    {
+                        filePath: filePath2,
+                        content: fileContent
+                    },
+                    {
+                        filePath: filePath1,
+                        content: fileContent
+                    },
+                    {
+                        filePath: filePath4,
+                        content: fileContent
+                    },
+                    {
+                        filePath: filePath3,
+                        content: fileContent
+                    }
+                ];
+
+                let result: string | IFileData[];
+
+                before(() => {
+                    mkdirp.sync(parentDirectoryPath1);
+                    mkdirp.sync(parentDirectoryPath2);
+                    fs.writeFileSync(filePath1, fileContent);
+                    fs.writeFileSync(filePath2, fileContent);
+                    fs.writeFileSync(filePath3, fileContent);
+                    fs.writeFileSync(filePath4, fileContent);
+                    result = SourceCodeReader.readSourceCode(tmpDirectoryPath);
+                });
+
+                it('should return files data', () => {
+                    assert.deepEqual(result, expectedResult);
+                });
+
+                after(() => {
+                    fs.unlinkSync(filePath1);
+                    fs.unlinkSync(filePath2);
+                    fs.unlinkSync(filePath3);
+                    fs.unlinkSync(filePath4);
+                    rimraf.sync(parentDirectoryPath1);
+                    rimraf.sync(parentDirectoryPath2);
+                });
+            });
         });
     });
 
     after(() => {
-        fs.rmdirSync(tmpDir);
+        rimraf.sync(tmpDirectoryPath);
     });
 });

+ 44 - 19
yarn.lock

@@ -36,29 +36,54 @@
   version "0.0.38"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2"
 
+"@types/events@*":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
+
+"@types/glob@*":
+  version "5.0.34"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.34.tgz#ee626c9be3da877d717911c6101eee0a9871bbf4"
+  dependencies:
+    "@types/events" "*"
+    "@types/minimatch" "*"
+    "@types/node" "*"
+
 "@types/[email protected]":
   version "2.1.32"
   resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.32.tgz#93e23437fcd17a7b9ca98d02aa6002e835842fe8"
   dependencies:
     "@types/node" "*"
 
+"@types/minimatch@*":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.2.tgz#09c06877e478a5d5f32ce5017c2eb2b33006f6f5"
+
 "@types/[email protected]":
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
   dependencies:
     "@types/node" "*"
 
-"@types/[email protected]":
-  version "2.2.44"
-  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e"
+"@types/[email protected]":
+  version "2.2.45"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.45.tgz#816572b6e45164526a36d4faa123e8267d6d5d0a"
+  dependencies:
+    "@types/node" "*"
 
 "@types/node@*":
   version "8.0.53"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
 
-"@types/[email protected]":
-  version "8.5.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.1.tgz#4ec3020bcdfe2abffeef9ba3fbf26fca097514b5"
+"@types/[email protected]":
+  version "8.5.2"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.2.tgz#83b8103fa9a2c2e83d78f701a9aa7c9539739aa5"
+
+"@types/rimraf@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
+  dependencies:
+    "@types/glob" "*"
+    "@types/node" "*"
 
 "@types/[email protected]":
   version "4.1.2"
@@ -955,9 +980,9 @@ [email protected], chalk@^2.1.0, chalk@^2.3.0:
     escape-string-regexp "^1.0.5"
     supports-color "^4.0.0"
 
[email protected]2:
-  version "1.0.12"
-  resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.12.tgz#6764c9cb7b4f34856fb780d07f0e17a6601c6c08"
[email protected]3:
+  version "1.0.13"
+  resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.13.tgz#666bec2db42b3084456a3e4f4c28a82db5ccb7e6"
 
 charenc@~0.0.1:
   version "0.0.2"
@@ -1973,9 +1998,9 @@ invariant@^2.2.2:
   dependencies:
     loose-envify "^1.0.0"
 
-inversify@4.8.0:
-  version "4.8.0"
-  resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.8.0.tgz#750c3c204ee9fe9a7529062dc6196c69d05fee7e"
+inversify@4.9.0:
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.9.0.tgz#caa01f5856cfa0499aaaed9b1a635ef0c4a9f4d3"
 
 invert-kv@^1.0.0:
   version "1.0.0"
@@ -2541,9 +2566,9 @@ [email protected], [email protected], "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0:
   dependencies:
     minimist "0.0.8"
 
-mocha@4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b"
[email protected].0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"
   dependencies:
     browser-stdout "1.3.0"
     commander "2.11.0"
@@ -3212,7 +3237,7 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@^2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   dependencies:
@@ -3648,9 +3673,9 @@ trim-right@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
 
-ts-node@4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-4.0.2.tgz#cb3d039b9898fdc79ad09ab7e69c84564c8c41ee"
+ts-node@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-4.1.0.tgz#36d9529c7b90bb993306c408cd07f7743de20712"
   dependencies:
     arrify "^1.0.0"
     chalk "^2.3.0"

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä