diff --git a/README.md b/README.md index 9581cc0..ab2cadd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ A github action that compare a list of JSON files an return the missing keys between each other. +## Action configurations + +| Input Name | Description | Required | Default | +|--------------|-----------------------------------------------------------------------------------------------------------------|----------|--------------------| +| `files` | List of JSON file paths to check between each other. Should be at least 2 files. | `false` | - | +| `search_path`| Path to a folder that will be inspected to search all JSON files and compare each other. | `false` | - | +| `search_pattern` | Regular expression used on search path to find desired files. | `false` | `\.json$` | +| `ignore_file`| Path to a specific ignore file configuration | `false` | `.json-diff-ignore.json` | + ## Usage You can compare specific files using the next configuration: @@ -28,3 +37,31 @@ Or if you want to search on a specific route you can use the next configuration: ``` This will find on the specified route all files that match the `search_pattern` to compare each other + +### Ignore rules + +If you need to ignore some specific keys on certain files you can use an ignore json file on your project +root. By the fault the name of this files is `.json-diff-ignore.json` and has this structure: + +```json +[ + { + "pattern": "regexp/file\\.json$", + "ignoreKeys": ["key1"] + }, + { + "pattern": "regexp/file2\\.json$", + "ignoreKeys": ["key2"] + } +] +``` + +If you what to specify a specific path to find the ignore file you can do that by adding the next configuration: + +```yml +- name: Run JSON Diff Action + uses: Drafteame/json-diff-action@main + with: + # ... + ignore_file: /path/to/.json-diff-ignore.json +``` diff --git a/action.yml b/action.yml index cdaa3b5..1a6c1ea 100644 --- a/action.yml +++ b/action.yml @@ -16,7 +16,13 @@ inputs: description: Regular expression used on search path to find desired files. required: false default: '\\.json$' + ignore_file: + description: Path to a specific ignore file configuration + required: false + default: '.json-diff-ignore.json' runs: using: "docker" image: "Dockerfile" + + diff --git a/index.js b/index.js index f4bdc7f..7656e4a 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ const main = () => { core.getInput("files"), core.getInput("search_path"), core.getInput("search_pattern"), + core.getInput("ignore_file"), ); try { diff --git a/src/Action.js b/src/Action.js index de382e3..f85b36e 100644 --- a/src/Action.js +++ b/src/Action.js @@ -1,6 +1,7 @@ import lodash from "lodash"; import fs from "fs"; import path from "path"; +import core from "@actions/core"; import { NotEnoughFiles, @@ -30,13 +31,25 @@ export default class Action { */ #searchPattern; + /** + * Map with rules to ignore specified keys on specific files that match one or more patterns + * @type {Array} + */ + #ignoreRules; + + /** + * Object with the loaded ignore keys by file + * @type {Object} + */ + #ignoreKeysByFile; + /** * Final list of files after initial validations * @type {Array} */ #fileList; - constructor(files, searchPath, searchPattern) { + constructor(files, searchPath, searchPattern, ignoreFile) { this.#files = files; this.#searchPath = searchPath; @@ -46,6 +59,9 @@ export default class Action { this.#searchPattern = searchPattern; } + this.#ignoreRules = this.#getIgnoreRules(ignoreFile); + this.#ignoreKeysByFile = {}; + this.#fileList = this.#validateInputs(); } @@ -58,6 +74,15 @@ export default class Action { return this.#fileList; } + /** + * Return the loaded ignore rules. + * + * @returns {Array} + */ + getIgnoreRules() { + return this.#ignoreRules; + } + /** * Execute comparison of file keys and return the found differences. * @@ -94,6 +119,10 @@ export default class Action { keysFile2 .filter((key) => { + if (this.#keyShouldBeIgnored(file1, key)) { + return false; + } + const notInKeys1 = !keysFile1.includes(key); const notInMissing = !missing.includes(key); @@ -237,4 +266,71 @@ export default class Action { return newPath; } + + /** + * Return ignore file configuration to apply on checks + * + * @param {string} ignoreFile Path to the specified ignore file + * + * @return {Array} + */ + #getIgnoreRules(ignoreFile) { + let file = ".json-diff-ignore.json"; + + if (!lodash.isEmpty(ignoreFile)) { + file = ignoreFile; + } + + if (!fs.existsSync(file)) { + return []; + } + + const content = fs.readFileSync(file, { encoding: "utf-8" }); + + try { + return JSON.parse(content); + } catch (e) { + core.info( + `Error reading ignore file from '${file}', no rules will be applied...'`, + ); + return []; + } + } + + /** + * Validate if a given key should be ignored or not by the given file. + * + * @param {string} file File that should ignore or not the given key if not present on it. + * @param {string} key Key that should be ignored or not by the file. + * + * @returns {boolean} + */ + #keyShouldBeIgnored(file, key) { + if (file in this.#ignoreKeysByFile) { + return this.#ignoreKeysByFile[file].includes(key); + } + + let keysToIgnore = []; + + this.#ignoreRules.forEach((rule) => { + const pattern = rule.pattern || null; + const keys = rule.ignoreKeys || []; + + if (lodash.isEmpty(pattern) || keys.length === 0) { + return; + } + + const regexp = new RegExp(pattern); + + if (regexp.test(file)) { + keys.forEach((item) => { + keysToIgnore.push(item); + }); + } + }); + + this.#ignoreKeysByFile[file] = keysToIgnore; + + return keysToIgnore.includes(key); + } } diff --git a/tests/Action.test.js b/tests/Action.test.js index d33b55f..8f1c45b 100644 --- a/tests/Action.test.js +++ b/tests/Action.test.js @@ -37,6 +37,8 @@ describe("Action core functions", () => { const searchPath = ``; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); + try { new Action(files, searchPath, searchPattern); } catch (e) { @@ -54,6 +56,7 @@ describe("Action core functions", () => { const searchPath = ``; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.returns(true); try { @@ -76,6 +79,7 @@ describe("Action core functions", () => { const searchPath = ``; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.returns(true); try { @@ -98,6 +102,7 @@ describe("Action core functions", () => { const searchPath = ``; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs("some/path/to/file1.json").returns(true); fsStub.existsSync.withArgs("some/path/to/file2.json").returns(false); @@ -119,6 +124,7 @@ describe("Action core functions", () => { const searchPath = ``; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.returns(true); try { @@ -137,6 +143,7 @@ describe("Action core functions", () => { const searchPath = `some/search/folder`; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(false); try { @@ -156,6 +163,7 @@ describe("Action core functions", () => { const searchPath = `some/search/folder`; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(false); @@ -176,6 +184,7 @@ describe("Action core functions", () => { const searchPath = `some/search/folder`; const searchPattern = ``; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -203,6 +212,7 @@ describe("Action core functions", () => { const searchPath = `some/search/folder`; const searchPattern = `json$`; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -228,6 +238,7 @@ describe("Action core functions", () => { const searchPath = `/some/search/folder/`; const searchPattern = `json$`; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -255,6 +266,7 @@ describe("Action core functions", () => { const searchPath = `/some/search/folder/`; const searchPattern = `json$`; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -311,6 +323,7 @@ describe("Action core functions", () => { const searchPath = `/some/search/folder/`; const searchPattern = `json$`; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -366,6 +379,7 @@ describe("Action core functions", () => { const searchPath = `/some/search/folder/`; const searchPattern = `json$`; + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(false); fsStub.existsSync.withArgs(searchPath).returns(true); isDirectoryStub.returns(true); @@ -416,4 +430,162 @@ describe("Action core functions", () => { expect.fail(`Error: ${e.message}`); } }); + + it("Should load default ignore configuration", () => { + const files = ` + some/path/to/file1.json + some/path/to/file2.json + `; + const searchPath = ``; + const searchPattern = ``; + + let ignoreConfig = [ + { + pattern: "file1\\.json", + ignoreKeys: ["key1", "key2"], + }, + { + pattern: "file2\\.json", + ignoreKeys: ["key11", "key22"], + }, + ]; + + fsStub.existsSync.withArgs(".json-diff-ignore.json").returns(true); + + fsStub.readFileSync + .withArgs(".json-diff-ignore.json", { + encoding: "utf-8", + }) + .returns(JSON.stringify(ignoreConfig)); + + fsStub.existsSync.returns(true); + + try { + const action = new Action(files, searchPath, searchPattern); + + expect(action.getFileList()).to.contains("some/path/to/file1.json"); + expect(action.getFileList()).to.contains("some/path/to/file2.json"); + + expect(JSON.stringify(action.getIgnoreRules())).to.be.equal( + JSON.stringify(ignoreConfig), + ); + } catch (e) { + expect.fail(`Error: ${e.message}`); + return; + } + }); + + it("Should load custom path for ignore file", () => { + const files = ` + some/path/to/file1.json + some/path/to/file2.json + `; + const searchPath = ``; + const searchPattern = ``; + const ignoreFile = `path/to/.json-diff-ignore.json`; + + let ignoreConfig = [ + { + pattern: "file1\\.json", + ignoreKeys: ["key1", "key2"], + }, + { + pattern: "file2\\.json", + ignoreKeys: ["key11", "key22"], + }, + ]; + + fsStub.existsSync.withArgs(ignoreFile).returns(true); + + fsStub.readFileSync + .withArgs(ignoreFile, { + encoding: "utf-8", + }) + .returns(JSON.stringify(ignoreConfig)); + + fsStub.existsSync.returns(true); + + try { + const action = new Action(files, searchPath, searchPattern, ignoreFile); + + expect(action.getFileList()).to.contains("some/path/to/file1.json"); + expect(action.getFileList()).to.contains("some/path/to/file2.json"); + + expect(JSON.stringify(action.getIgnoreRules())).to.be.equal( + JSON.stringify(ignoreConfig), + ); + } catch (e) { + expect.fail(`Error: ${e.message}`); + return; + } + }); + + it("Should ignore keys if found on ignore configuration", () => { + const files = ` + some/path/to/file1.json + some/path/to/file2.json + `; + const searchPath = ``; + const searchPattern = ``; + const ignoreFile = `path/to/.json-diff-ignore.json`; + + let ignoreConfig = [ + { + pattern: "file1\\.json", + ignoreKeys: ["key1"], + }, + { + pattern: "file2\\.json", + ignoreKeys: ["key2"], + }, + ]; + + fsStub.existsSync.withArgs(ignoreFile).returns(true); + + fsStub.readFileSync + .withArgs(ignoreFile, { + encoding: "utf-8", + }) + .returns(JSON.stringify(ignoreConfig)); + + fsStub.existsSync.returns(true); + + let readOpts = { encoding: "utf-8" }; + + fsStub.readFileSync.withArgs("some/path/to/file1.json", readOpts).returns( + JSON.stringify({ + common: 1, + missing1: "some", + missing2: "some", + key2: "asd", + }), + ); + + fsStub.readFileSync.withArgs("some/path/to/file2.json", readOpts).returns( + JSON.stringify({ + common: 1, + missing1: "some", + missing2: "some", + key1: "asd", + }), + ); + + try { + const action = new Action(files, searchPath, searchPattern, ignoreFile); + + expect(action.getFileList()).to.contains("some/path/to/file1.json"); + expect(action.getFileList()).to.contains("some/path/to/file2.json"); + + expect(JSON.stringify(action.getIgnoreRules())).to.be.equal( + JSON.stringify(ignoreConfig), + ); + + const missing = action.run(); + + expect(JSON.stringify(missing)).to.be.equal(JSON.stringify({})); + } catch (e) { + expect.fail(`Error: ${e.message}`); + return; + } + }); });