diff --git a/.gitignore b/.gitignore index 3091757..6b622a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -coverage \ No newline at end of file +coverage +.vscode +codiga-cli*.tgz \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..1d04daa --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +DEVELOPMENT.md +tests +coverage +babel.config.js +jest.config.js +.github +.vscode +codiga-cli*.tgz \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ed94f44..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "git.ignoreLimitWarning": true -} \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 1bf0f9f..8ba6762 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -22,9 +22,32 @@ ### Running the tests -```bash -npm run test -``` +- Install and symlink the package as described in the Getting Started + + ```bash + npm i + npm link + ``` + +- Run the test script + + ```bash + npm run test + ``` + +> We'll automatically run the tests on `push` and `merge-request` actions. You can see past workflow runs [here](https://github.com/codiga/codiga-cli/actions/workflows/main.yml). + +### Release a new version + +- Open a MR with your new changes + - Bump the version in `package.json` and `package-lock.json` and commit it as well +- Once that's merged, go to [releases](https://github.com/codiga/codiga-cli/releases) and draft a new release + - Choose a tag > Create a new tag > `vX.X.X` (should match your new version above) + - Generate release notes + - Publish release +- Verify the following: + - The [Release Github Action](https://github.com/codiga/codiga-cli/actions/workflows/release.yml) is successful + - The [NPM package](https://www.npmjs.com/package/@codiga/cli) was updated ### Notes diff --git a/package-lock.json b/package-lock.json index caf1994..e480b9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@codiga/cli", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ac452db..fe85e46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codiga/cli", - "version": "1.0.4", + "version": "1.0.5", "description": "A Codiga CLI used to integrate Codiga easily in your projects", "homepage": "https://github.com/codiga/codiga-cli", "repository": { diff --git a/src/checkPush.js b/src/checkPush.js index 68dfe52..3ff66b0 100644 --- a/src/checkPush.js +++ b/src/checkPush.js @@ -145,6 +145,17 @@ export async function checkPush(remoteShaArg, localShaArg) { if (violations.length === 0 && errors.length === 0) { process.exit(0); } else { + printEmptyLine(); + printInfo("Do you consider these violations as false positives?"); + printSuggestion( + " ↳ You can add the following flag to your `git push` to bypass this check:", + "--no-verify" + ); + printSuggestion( + " ↳ Consider commenting on those rules in the Codiga Hub, so the maintainer can improve them:", + "https://app.codiga.io/hub/rulesets" + ); + printEmptyLine(); process.exit(1); } } diff --git a/tests/check-push.test.js b/tests/check-push.test.js index c24b4fd..a8f9baa 100644 --- a/tests/check-push.test.js +++ b/tests/check-push.test.js @@ -1,6 +1,5 @@ import { ACTION_GIT_PUSH_HOOK, BLANK_SHA } from "../utils/constants"; -import { setToken } from "../utils/store"; -import { executeCommand, SAMPLE_TOKEN } from "./test-utils"; +import { executeCommand } from "./test-utils"; describe("codiga git-push-hook", () => { test("check for same SHAs", async () => { diff --git a/tests/fixtures/rulesetsMock.js b/tests/fixtures/rulesetsMock.js new file mode 100644 index 0000000..e666725 --- /dev/null +++ b/tests/fixtures/rulesetsMock.js @@ -0,0 +1,41 @@ +export function getRulesetsWithRulesMock(rulesets) { + return mockedRulesets.filter((ruleset) => rulesets.includes(ruleset.name)); +} + +export const mockedRulesets = [ + { + id: 1, + name: "testing-ruleset-1", + rules: [ + { + id: 11, + name: "testing-rule-11", + content: "ZnVuY3Rpb24gdmlzaXQobm9kZSl7CiAgaWYoIW5vZGUpIHJldHVybiAKfQ==", + ruleType: "Ast", + language: "Javascript", + pattern: null, + elementChecked: "HtmlElement", + }, + ], + }, + { + id: 2, + name: "testing-ruleset-2", + rules: [ + { + id: 12, + name: "testing-rule-22", + content: "ZnVuY3Rpb24gdmlzaXQobm9kZSl7CiAgaWYoIW5vZGUpIHJldHVybiAKfQ==", + ruleType: "Ast", + language: "Javascript", + pattern: null, + elementChecked: "Assignment", + }, + ], + }, + { + id: 3, + name: "testing-ruleset-3", + rules: [], + }, +]; diff --git a/tests/language-support.test.js b/tests/language-support.test.js new file mode 100644 index 0000000..78d2483 --- /dev/null +++ b/tests/language-support.test.js @@ -0,0 +1,46 @@ +import { + LANGUAGE_JAVASCRIPT, + LANGUAGE_PYTHON, + LANGUAGE_TYPESCRIPT, +} from "../utils/constants"; +import { managePathsBySupportAndLanguage } from "../utils/rosie"; + +const notSupportedPaths = [ + "config.json", + "README.md", + "LICENSE", + "index.html", + "picture.png", +]; + +const pythonPaths = [ + "index.py3", + "/python.py3", + "/some-folder/nested/index.py", +]; + +const javascriptPaths = [ + "index.js", + "/javascript.jsx", + "/some-folder/nested/index.js", +]; + +const typescriptPaths = [ + "index.ts", + "/typescript.tsx", + "/some-folder/nested/index.ts", +]; + +test("path files are split correctly", async () => { + const files = managePathsBySupportAndLanguage([ + ...notSupportedPaths, + ...pythonPaths, + ...javascriptPaths, + ...typescriptPaths, + ]); + + expect(files.notSupported.length).toBe(5); + expect(files[LANGUAGE_PYTHON].length).toBe(3); + expect(files[LANGUAGE_JAVASCRIPT].length).toBe(3); + expect(files[LANGUAGE_TYPESCRIPT].length).toBe(3); +}); diff --git a/tests/ruleset-to-rules.test.js b/tests/ruleset-to-rules.test.js new file mode 100644 index 0000000..f4e11e4 --- /dev/null +++ b/tests/ruleset-to-rules.test.js @@ -0,0 +1,49 @@ +import { convertRulesetsToRules } from "../utils/rules"; +import { getRulesetsWithRulesMock } from "./fixtures/rulesetsMock"; + +test("can fetch rulesets when an API token is set", async () => { + expect( + convertRulesetsToRules( + getRulesetsWithRulesMock(["testing-ruleset-1", "testing-ruleset-2"]) + ) + ).toEqual([ + { + id: "testing-ruleset-1/testing-rule-11", + contentBase64: + "ZnVuY3Rpb24gdmlzaXQobm9kZSl7CiAgaWYoIW5vZGUpIHJldHVybiAKfQ==", + language: "javascript", + type: "ast", + entityChecked: "htmlelement", + pattern: null, + }, + { + id: "testing-ruleset-2/testing-rule-22", + contentBase64: + "ZnVuY3Rpb24gdmlzaXQobm9kZSl7CiAgaWYoIW5vZGUpIHJldHVybiAKfQ==", + language: "javascript", + type: "ast", + entityChecked: "assign", + pattern: null, + }, + ]); + + expect( + convertRulesetsToRules( + getRulesetsWithRulesMock(["testing-ruleset-1", "testing-ruleset-3"]) + ) + ).toEqual([ + { + id: "testing-ruleset-1/testing-rule-11", + contentBase64: + "ZnVuY3Rpb24gdmlzaXQobm9kZSl7CiAgaWYoIW5vZGUpIHJldHVybiAKfQ==", + language: "javascript", + type: "ast", + entityChecked: "htmlelement", + pattern: null, + }, + ]); + + expect( + convertRulesetsToRules(getRulesetsWithRulesMock(["testing-ruleset-3"])) + ).toEqual([]); +}); diff --git a/utils/rosie.js b/utils/rosie.js index 6b750e7..8fa44af 100644 --- a/utils/rosie.js +++ b/utils/rosie.js @@ -6,31 +6,41 @@ import { getRulesForRosiePerLanguage } from "./rules"; import { printEmptyLine, printFailure, printInfo, printSubItem } from "./print"; /** - * + * Used to filter out unsupported language files and + * combine similar language files together + * @param {string[]} paths + * @returns {{notSupported: string[], 'language': string[]}} + */ +export function managePathsBySupportAndLanguage(paths) { + return paths.reduce( + (acc, path) => { + const fileLanguage = getLanguageForFile(path); + if (!fileLanguage) { + acc.notSupported.push(path); + } else { + if (acc[fileLanguage]) { + acc[fileLanguage].push(path); + } else { + acc[fileLanguage] = [path]; + } + } + return acc; + }, + { + notSupported: [], + } + ); +} + +/** + * Run a Rosie check on the given paths against with the given rules * @param {string[]} paths * @param {RosieRule} rules */ export async function analyzeFiles(paths, rules) { try { // we won't analyze files for languages that aren't supported - const files = paths.reduce( - (acc, path) => { - const fileLanguage = getLanguageForFile(path); - if (!fileLanguage) { - acc.notSupported.push(path); - } else { - if (acc[fileLanguage]) { - acc[fileLanguage].push(path); - } else { - acc[fileLanguage] = [path]; - } - } - return acc; - }, - { - notSupported: [], - } - ); + const files = managePathsBySupportAndLanguage(paths); printEmptyLine(); // if there's are unsupported files we'll log that with a notice diff --git a/utils/rulesets.js b/utils/rulesets.js index ea0dabf..04e6c36 100644 --- a/utils/rulesets.js +++ b/utils/rulesets.js @@ -3,12 +3,8 @@ import { codigaApiFetch } from "./api"; import { ACTION_TOKEN_ADD, CODIGA_CONFIG_FILE } from "./constants"; import { getRootDirectory } from "./git"; import { GET_RULESETS_FOR_CLIENT } from "../graphql/queries"; -import { - printCommandSuggestion, - printFailure, - printInfo, - printSuggestion, -} from "./print"; +import { printCommandSuggestion, printFailure, printSuggestion } from "./print"; +import { getToken } from "./store"; /** * Gets an array of rulesets and their rules @@ -16,6 +12,9 @@ import { */ export async function getRulesetsWithRules(names) { try { + if (!getToken()) { + throw new Error("Not Authorized"); + } const resp = await codigaApiFetch(GET_RULESETS_FOR_CLIENT, { names }); const rulesetsWithRules = resp.ruleSetsForClient || []; return rulesetsWithRules;