Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit eeec426

Browse files
feat: add ruleset-add command
1 parent e20730a commit eeec426

9 files changed

Lines changed: 263 additions & 1 deletion

File tree

graphql/queries.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ export const GET_RULESETS_FOR_CLIENT = gql`
2727
}
2828
}
2929
`;
30+
31+
export const GET_RULESET = gql`
32+
query getRuleset($name: String!) {
33+
ruleSet(name: $name) {
34+
id
35+
name
36+
}
37+
}
38+
`;

src/addRuleset.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { writeFile } from "fs/promises";
2+
import {
3+
ACTION_RULESET_ADD,
4+
ACTION_TOKEN_ADD,
5+
CODIGA_CONFIG_FILE,
6+
} from "../utils/constants";
7+
import { readFile, parseYamlFile } from "../utils/file";
8+
import { getGitDirectory } from "../utils/git";
9+
import {
10+
printCommandSuggestion,
11+
printEmptyLine,
12+
printFailure,
13+
printInfo,
14+
printSuggestion,
15+
} from "../utils/print";
16+
import { convertRulesetsToString, getRuleset } from "../utils/ruleset";
17+
18+
/**
19+
* Creates a codiga.yml file's content with the rulesets given
20+
* Note: because this overrides the file, you need to pass any old rulesets that should remain
21+
* @param {string[]} rulesets
22+
*/
23+
export async function createCodigaYml(codigaFileLocation, rulesets) {
24+
if (isTestMode) return;
25+
try {
26+
await writeFile(codigaFileLocation, convertRulesetsToString(rulesets), {
27+
encoding: "utf-8",
28+
});
29+
} catch (err) {
30+
// console.debug(err);
31+
printEmptyLine();
32+
printFailure(`We were unable to write to: ${codigaFileLocation}`);
33+
printSuggestion(
34+
" ↳ Please try again and contact us, if the issue persists:",
35+
"https://app.codiga.io/support"
36+
);
37+
printEmptyLine();
38+
process.exit(1);
39+
}
40+
}
41+
42+
/**
43+
* Handles adding a ruleset to a codiga.yml file
44+
* @param {string[]} rulesetNames
45+
*/
46+
export async function addRuleset(rulesetNames) {
47+
// TODO - change to a prompt when no rulesets are given
48+
const rulesetName = rulesetNames[0];
49+
if (!rulesetName) {
50+
printEmptyLine();
51+
printFailure("You need to specify a ruleset to add");
52+
printSuggestion(
53+
" ↳ You can search for rulesets here:",
54+
"https://app.codiga.io/hub/rulesets"
55+
);
56+
printCommandSuggestion(
57+
" ↳ Then follow this command structure:",
58+
`${ACTION_RULESET_ADD} <ruleset-name>`
59+
);
60+
printEmptyLine();
61+
process.exit(1);
62+
}
63+
64+
// Check if the ruleset exists before continuing onwards
65+
const ruleset = await getRuleset({ name: rulesetName });
66+
if (!ruleset) {
67+
printEmptyLine();
68+
printFailure(
69+
"That ruleset either doesn't exist or you lack the permissions to access it"
70+
);
71+
printCommandSuggestion(
72+
" ↳ Ensure you have a Codiga API token set with one of the following commands:",
73+
ACTION_TOKEN_ADD
74+
);
75+
printSuggestion(
76+
" ↳ You can find more rulesets here:",
77+
"https://app.codiga.io/hub/rulesets"
78+
);
79+
printEmptyLine();
80+
process.exit(1);
81+
}
82+
83+
/**
84+
* Get the `codiga.yml` file location and content.
85+
* We'll look in the git directory, if it exists.
86+
* Otherwise, we'll look in the current directory
87+
*/
88+
const gitDirectory = getGitDirectory();
89+
const dir = gitDirectory || process.cwd();
90+
const codigaFileLocation = `${dir}/${CODIGA_CONFIG_FILE}`;
91+
const codigaFileContent = readFile(codigaFileLocation);
92+
93+
/**
94+
* If we found a `codiga.yml` file, add the rule to it
95+
* If we don't find a `codiga.yml` file, create the file and add the rule to it
96+
*/
97+
if (codigaFileContent) {
98+
const parsedFile = parseYamlFile(codigaFileContent, codigaFileLocation);
99+
const codigaRulesets = parsedFile.rulesets;
100+
if (codigaRulesets.includes(rulesetName)) {
101+
printEmptyLine();
102+
printInfo(
103+
`The ruleset (${rulesetName}) already exists in your \`codiga.yml\``
104+
);
105+
printSuggestion;
106+
printEmptyLine();
107+
process.exit(1);
108+
} else {
109+
// adding the new ruleset to the file
110+
await createCodigaYml(codigaFileLocation, [
111+
...codigaRulesets,
112+
rulesetName,
113+
]);
114+
printSuggestion(
115+
`We added ${rulesetName} to your codiga.yml file:`,
116+
codigaFileLocation
117+
);
118+
printSuggestion(
119+
" ↳ Find more rulesets to add here:",
120+
"https://app.codiga.io/hub/rulesets"
121+
);
122+
}
123+
} else {
124+
// creating a new codiga.yml with the ruleset here
125+
await createCodigaYml(codigaFileLocation, [rulesetName]);
126+
printSuggestion(
127+
`No codiga.yml file found, so we created one and added ${rulesetName} to it:`,
128+
codigaFileLocation
129+
);
130+
printSuggestion(
131+
" ↳ Find more rulesets to add here:",
132+
"https://app.codiga.io/hub/rulesets"
133+
);
134+
}
135+
136+
process.exit(0);
137+
}

src/cli.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ACTION_TOKEN_ADD,
55
ACTION_TOKEN_CHECK,
66
ACTION_TOKEN_DELETE,
7+
ACTION_RULESET_ADD,
78
OPTION_LANGUAGE,
89
OPTION_LOCAL_SHA,
910
OPTION_REMOTE_SHA,
@@ -19,6 +20,7 @@ import {
1920
checkCodigaToken,
2021
deleteCodigaToken,
2122
} from "./codigaToken";
23+
import { addRuleset } from "./addRuleset";
2224

2325
/**
2426
* Parses the given command into a readable object to execute
@@ -50,6 +52,7 @@ function parseCommand(args) {
5052
},
5153
}
5254
)
55+
.command(ACTION_RULESET_ADD, "Add a ruleset to a `codiga.yml` file")
5356
.help(true).argv;
5457

5558
// format any actions into a single object with default values
@@ -58,6 +61,7 @@ function parseCommand(args) {
5861
[ACTION_TOKEN_CHECK]: yargV["_"].includes(ACTION_TOKEN_CHECK) || false,
5962
[ACTION_TOKEN_DELETE]: yargV["_"].includes(ACTION_TOKEN_DELETE) || false,
6063
[ACTION_GIT_PUSH_HOOK]: yargV["_"].includes(ACTION_GIT_PUSH_HOOK) || false,
64+
[ACTION_RULESET_ADD]: yargV["_"].includes(ACTION_RULESET_ADD) || false,
6165
};
6266

6367
// how many actions were detected in the command ran
@@ -90,10 +94,13 @@ function parseCommand(args) {
9094
[OPTION_LANGUAGE]: yargV[OPTION_LANGUAGE] || null,
9195
};
9296

97+
const parameters = yargV["_"].slice(1);
98+
9399
// the parsed/formatted result
94100
return {
95101
action: selectedAction,
96102
options,
103+
parameters,
97104
};
98105
}
99106

@@ -105,6 +112,7 @@ export async function cli(args) {
105112
[OPTION_REMOTE_SHA]: remoteSha,
106113
[OPTION_LANGUAGE]: language,
107114
},
115+
parameters,
108116
} = parseCommand(args);
109117

110118
switch (action) {
@@ -116,6 +124,8 @@ export async function cli(args) {
116124
return await deleteCodigaToken();
117125
case ACTION_GIT_PUSH_HOOK:
118126
return await checkPush(remoteSha, localSha);
127+
case ACTION_RULESET_ADD:
128+
return await addRuleset(parameters);
119129
default:
120130
return (() => {
121131
printEmptyLine();

tests/add-ruleset.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ACTION_RULESET_ADD } from "../utils/constants";
2+
import { executeCommand } from "./test-utils";
3+
4+
describe("codiga ruleset-add", () => {
5+
test("an invalid ruleset name should throw", async () => {
6+
// run the command
7+
await executeCommand([ACTION_RULESET_ADD, "invalid-ruleset"])
8+
.then((output) => {
9+
expect(output).toBeUndefined();
10+
})
11+
.catch(({ stdout }) => {
12+
expect(stdout).toMatch(
13+
/That ruleset either doesn't exist or you lack the permissions to access it/
14+
);
15+
});
16+
});
17+
18+
test("a ruleset that's already present should throw", async () => {
19+
// run the command
20+
await executeCommand([ACTION_RULESET_ADD, "great-ruleset"])
21+
.then((output) => {
22+
expect(output).toBeUndefined();
23+
})
24+
.catch(({ stdout }) => {
25+
console.log(stdout);
26+
expect(stdout).toMatch(/already exists in your/);
27+
});
28+
});
29+
});

tests/fixtures/codiga.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
rulesets:
22
- react-best-practices
3-
- jsx-a11y
3+
- jsx-a11y
4+
- great-ruleset
5+

tests/fixtures/rulesetMock.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const mockedRuleset1 = {
2+
id: 1,
3+
name: "test-ruleset",
4+
};
5+
6+
export const mockedRuleset2 = {
7+
id: 2,
8+
name: "great-ruleset",
9+
};
10+
11+
export const mockedRuleset3 = {
12+
id: 3,
13+
name: "fantastic-ruleset",
14+
};
15+
16+
export function getRulesetMock(rulesetName) {
17+
switch (rulesetName) {
18+
case "test-ruleset":
19+
return mockedRuleset1;
20+
case "great-ruleset":
21+
return mockedRuleset2;
22+
case "fantastic-ruleset":
23+
return mockedRuleset3;
24+
default:
25+
return null;
26+
}
27+
}

tests/yaml.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { parseYamlFile } from "../utils/file";
2+
import { convertRulesetsToString } from "../utils/ruleset";
23
import {
34
CODIGA_CONFIG_MISSING_RULESETS,
45
CODIGA_CONFIG_MISSING_RULESETS_LIST,
@@ -20,3 +21,28 @@ test("parse a valid file accurately", async () => {
2021
rulesets: ["react-best-practices", "jsx-a11y"],
2122
});
2223
});
24+
25+
test("ruleset is converted correctly", () => {
26+
const input = ["codiga-ruleset"];
27+
const output = "rulesets:\n" + " - codiga-ruleset\n";
28+
expect(convertRulesetsToString(input)).toBe(output);
29+
});
30+
31+
test("empty strings are removed", () => {
32+
const input = ["codiga-ruleset", "", "daniel-ruleset"];
33+
const output =
34+
"rulesets:\n" + " - codiga-ruleset\n" + " - daniel-ruleset\n";
35+
expect(convertRulesetsToString(input)).toBe(output);
36+
});
37+
38+
test("null values are removed", () => {
39+
const input = ["codiga-ruleset", "", null];
40+
const output = "rulesets:\n" + " - codiga-ruleset\n";
41+
expect(convertRulesetsToString(input)).toBe(output);
42+
});
43+
44+
test("undefined values are removed", () => {
45+
const input = ["codiga-ruleset", "", undefined];
46+
const output = "rulesets:\n" + " - codiga-ruleset\n";
47+
expect(convertRulesetsToString(input)).toBe(output);
48+
});

utils/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ACTION_TOKEN_ADD = "token-add";
66
export const ACTION_TOKEN_CHECK = "token-check";
77
export const ACTION_TOKEN_DELETE = "token-delete";
88
export const ACTION_GIT_PUSH_HOOK = "git-push-hook";
9+
export const ACTION_RULESET_ADD = "ruleset-add";
910

1011
// COMMAND OPTIONS
1112
export const OPTION_LANGUAGE = "language";

utils/ruleset.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { GET_RULESET } from "../graphql/queries";
2+
import { getRulesetMock } from "../tests/fixtures/rulesetMock";
3+
import { isTestMode } from "../tests/test-utils";
4+
import { codigaApiFetch } from "./api";
5+
6+
export async function getRuleset({ name }) {
7+
try {
8+
if (isTestMode) return getRulesetMock(name);
9+
const resp = await codigaApiFetch(GET_RULESET, { name });
10+
return resp.ruleSet;
11+
} catch (err) {
12+
return null;
13+
}
14+
}
15+
16+
export function convertRulesetsToString(rulesets) {
17+
return `rulesets:\n${rulesets
18+
.filter((x) => x)
19+
.map((ruleset) => ` - ${ruleset}\n`)
20+
.join("")}`;
21+
}

0 commit comments

Comments
 (0)