reactjsquality-check911 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,9 +33,14 @@ program
33
33
  .description("Scan components, pages, and services — rebuild architecture.md")
34
34
  .action(() => require("../commands/scan")());
35
35
 
36
+ program
37
+ .command("audit")
38
+ .description("Run npm audit for high/critical CVEs (only if vulnerabilities check is enabled)")
39
+ .action(() => require("../commands/audit")());
40
+
36
41
  program
37
42
  .command("hooks")
38
- .description("Install pre-commit (ESLint + coverage) and pre-push (Playwright) git hooks")
43
+ .description("Install pre-commit (ESLint + coverage) and pre-push (Playwright + audit) git hooks")
39
44
  .action(() => require("../commands/hooks")());
40
45
 
41
46
  program.parse(process.argv);
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ const { execSync } = require("child_process");
4
+ const fs = require("fs");
5
+
6
+ function loadConfig() {
7
+ try { return JSON.parse(fs.readFileSync(".reactjs-quality-agent.json", "utf8")); } catch (_) { return null; }
8
+ }
9
+
10
+ module.exports = function audit() {
11
+ const config = loadConfig();
12
+ if (!config) {
13
+ console.log("No .reactjs-quality-agent.json found — skipping audit.");
14
+ return;
15
+ }
16
+ if (!config.checks || !config.checks.vulnerabilities) {
17
+ return; // not opted in — silent skip
18
+ }
19
+
20
+ process.stdout.write("Running npm audit... ");
21
+ try {
22
+ let auditOutput = "";
23
+ let hasVulns = false;
24
+ try {
25
+ execSync("npm audit --audit-level=high --json", {
26
+ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"]
27
+ });
28
+ } catch (e) {
29
+ // npm audit exits 1 when vulnerabilities found — stdout still has JSON
30
+ auditOutput = e.stdout || "{}";
31
+ hasVulns = true;
32
+ }
33
+
34
+ if (!hasVulns) {
35
+ console.log("✅");
36
+ return;
37
+ }
38
+
39
+ let high = 0, critical = 0;
40
+ try {
41
+ const data = JSON.parse(auditOutput);
42
+ if (data.metadata && data.metadata.vulnerabilities) {
43
+ // npm v7+
44
+ high = data.metadata.vulnerabilities.high || 0;
45
+ critical = data.metadata.vulnerabilities.critical || 0;
46
+ } else if (data.vulnerabilities) {
47
+ // npm v6
48
+ for (const v of Object.values(data.vulnerabilities)) {
49
+ if (v.severity === "critical") critical++;
50
+ else if (v.severity === "high") high++;
51
+ }
52
+ }
53
+ } catch (_) {}
54
+
55
+ console.log("❌");
56
+ console.log(` ${critical} critical, ${high} high severity vulnerabilities in dependencies`);
57
+ console.log(" Fix: npm audit fix — auto-fix compatible updates");
58
+ console.log(" npm audit fix --force — force upgrades (may include breaking changes)");
59
+ console.log(" npm audit — see full vulnerability details");
60
+ process.exit(1);
61
+ } catch (e) {
62
+ console.log("⚠️ npm audit unavailable:", e.message.slice(0, 80));
63
+ }
64
+ };
@@ -4,7 +4,7 @@ const { execSync } = require("child_process");
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
 
7
- const THRESHOLD = 80;
7
+ const THRESHOLD = 95;
8
8
  const CONFIG_FILE = ".reactjs-quality-agent.json";
9
9
 
10
10
  function loadConfig() {
package/commands/hooks.js CHANGED
@@ -27,7 +27,7 @@ reactjsquality-check911 coverage
27
27
 
28
28
  const PRE_PUSH = `#!/bin/sh
29
29
  # reactjsquality-check911 — pre-push hook
30
- # Runs Playwright smoke tests before every push
30
+ # Runs Playwright smoke tests and npm audit before every push
31
31
 
32
32
  set -e
33
33
 
@@ -43,6 +43,7 @@ if [ ! -f "$CONFIG" ]; then
43
43
  fi
44
44
 
45
45
  reactjsquality-check911 playwright
46
+ reactjsquality-check911 audit
46
47
  `;
47
48
 
48
49
  module.exports = function hooks() {
@@ -66,9 +67,12 @@ module.exports = function hooks() {
66
67
  console.log(`
67
68
  Hooks installed. From now on:
68
69
 
69
- git commit → ESLint errors on changed chunks block the commit
70
- Jest coverage < 80% on changed files blocks the commit
70
+ git commit → ESLint errors block the commit (changed chunks only)
71
+ Code Smells (SonarJS) block the commit [if enabled]
72
+ Security violations block the commit [if enabled]
73
+ Jest coverage < 80% blocks the commit [if enabled]
71
74
 
72
- git push → Playwright smoke tests must all pass before push is allowed
75
+ git push → Playwright smoke tests must pass [if enabled]
76
+ npm audit for high/critical CVEs [if enabled]
73
77
  `);
74
78
  };
package/commands/init.js CHANGED
@@ -15,9 +15,11 @@ const FRAMEWORKS = [
15
15
  ];
16
16
 
17
17
  const CHECKS = [
18
- { id: "eslint", label: "ESLint — code style and error rules on staged chunks" },
19
- { id: "coverage", label: "Coverage Jest line coverage (80% threshold per changed file)" },
20
- { id: "playwright", label: "Playwrightsmoke tests run before every git push" }
18
+ { id: "eslint", label: "ESLint — code style and error rules on staged chunks" },
19
+ { id: "codesmells", label: "Code Smells SonarJS: cognitive complexity, duplicate code, dead code" },
20
+ { id: "vulnerabilities", label: "Vulnerabilitieseslint-security: eval/injection/unsafe regex + npm audit CVE scan on push" },
21
+ { id: "coverage", label: "Coverage — Jest line coverage (95% threshold per changed file)" },
22
+ { id: "playwright", label: "Playwright — E2E smoke tests run before every git push" }
21
23
  ];
22
24
 
23
25
  const COPILOT_INSTRUCTIONS = {
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const { execSync } = require("child_process");
4
- const fs = require("fs");
5
- const path = require("path");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const PKG_ROOT = path.join(__dirname, "..");
6
7
 
7
8
  const CONFIG_FILE = ".reactjs-quality-agent.json";
8
9
 
@@ -96,6 +97,58 @@ function runEslint(staged, changedRanges) {
96
97
  }
97
98
  }
98
99
 
100
+ function runWithPlugin(staged, changedRanges, configFile, label) {
101
+ process.stdout.write(`Running ${label}...`.padEnd(30));
102
+ const eslintJs = path.join(PKG_ROOT, "node_modules", "eslint", "bin", "eslint.js");
103
+ const configPath = path.join(PKG_ROOT, "config", configFile);
104
+
105
+ if (!fs.existsSync(eslintJs)) {
106
+ console.log("⚠️ (ESLint not in package — reinstall reactjsquality-check911)");
107
+ return { passed: true, summary: `⚠️ ${label}: ESLint not found` };
108
+ }
109
+
110
+ try {
111
+ const fileArgs = staged.map(f => `"${f}"`).join(" ");
112
+ let output = "";
113
+ try {
114
+ output = execSync(
115
+ `node "${eslintJs}" ${fileArgs} --format json --no-eslintrc --resolve-plugins-relative-to "${PKG_ROOT}" --config "${configPath}"`,
116
+ { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
117
+ );
118
+ } catch (e) {
119
+ output = e.stdout || "[]";
120
+ }
121
+
122
+ const data = JSON.parse(output || "[]");
123
+ const viols = [];
124
+ const errors = [];
125
+
126
+ for (const file of data) {
127
+ const ranges = findRanges(file.filePath, changedRanges);
128
+ if (!ranges) continue;
129
+ for (const msg of file.messages) {
130
+ if (!inChangedRange(msg.line, ranges)) continue;
131
+ const lbl = msg.severity === 2 ? "error" : "warn";
132
+ const entry = ` [${label}:${msg.ruleId || "?"}] ${path.basename(file.filePath)}:${msg.line} — ${msg.message} (${lbl})`;
133
+ viols.push(entry);
134
+ if (msg.severity === 2) errors.push(entry);
135
+ }
136
+ }
137
+
138
+ if (errors.length) {
139
+ console.log("❌");
140
+ viols.forEach(v => console.log(v));
141
+ return { passed: false, summary: `❌ ${label}: ${errors.length} issue(s) in changed lines` };
142
+ }
143
+ console.log("✅");
144
+ if (viols.length) viols.forEach(v => console.log(v));
145
+ return { passed: true, summary: `✅ ${label} passed` };
146
+ } catch (e) {
147
+ console.log("⚠️");
148
+ return { passed: true, summary: `⚠️ ${label}: ${e.message.slice(0, 80)}` };
149
+ }
150
+ }
151
+
99
152
  function runTypeScript(staged) {
100
153
  const tsFiles = staged.filter(f => f.endsWith(".ts") || f.endsWith(".tsx"));
101
154
  process.stdout.write("Running TypeScript... ");
@@ -141,6 +194,18 @@ module.exports = function quality() {
141
194
  if (!r.passed) failed = true;
142
195
  }
143
196
 
197
+ if (config.checks.codesmells) {
198
+ const r = runWithPlugin(staged, changedRanges, "sonarjs.eslintrc.json", "Code Smells");
199
+ results.push(r.summary);
200
+ if (!r.passed) failed = true;
201
+ }
202
+
203
+ if (config.checks.vulnerabilities) {
204
+ const r = runWithPlugin(staged, changedRanges, "security.eslintrc.json", "Security");
205
+ results.push(r.summary);
206
+ if (!r.passed) failed = true;
207
+ }
208
+
144
209
  const r2 = runTypeScript(staged);
145
210
  results.push(r2.summary);
146
211
  if (!r2.passed) failed = true;
@@ -0,0 +1,16 @@
1
+ {
2
+ "env": { "browser": true, "es2022": true, "node": true },
3
+ "parserOptions": { "ecmaVersion": 2022, "sourceType": "module" },
4
+ "plugins": ["security"],
5
+ "rules": {
6
+ "security/detect-eval-with-expression": "error",
7
+ "security/detect-non-literal-regexp": "warn",
8
+ "security/detect-non-literal-require": "warn",
9
+ "security/detect-object-injection": "warn",
10
+ "security/detect-possible-timing-attacks": "warn",
11
+ "security/detect-pseudoRandomBytes": "error",
12
+ "security/detect-unsafe-regex": "error",
13
+ "security/detect-no-csrf-before-method-override": "error",
14
+ "security/detect-disable-mustache-escape": "error"
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "env": { "browser": true, "es2022": true, "node": true },
3
+ "parserOptions": { "ecmaVersion": 2022, "sourceType": "module" },
4
+ "plugins": ["sonarjs"],
5
+ "rules": {
6
+ "sonarjs/cognitive-complexity": ["error", 15],
7
+ "sonarjs/no-duplicate-string": ["warn", { "threshold": 3 }],
8
+ "sonarjs/no-identical-functions": "error",
9
+ "sonarjs/no-empty-collection": "error",
10
+ "sonarjs/no-unused-collection": "error",
11
+ "sonarjs/no-redundant-boolean": "error",
12
+ "sonarjs/no-small-switch": "warn",
13
+ "sonarjs/prefer-immediate-return": "warn",
14
+ "sonarjs/no-nested-switch": "warn"
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactjsquality-check911",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "GitHub Copilot agent for React/JS projects — enforces ESLint, Jest coverage, and Playwright tests on every commit and push",
5
5
  "bin": {
6
6
  "reactjsquality-check911": "./bin/reactjsquality-check911.js"
@@ -39,6 +39,9 @@
39
39
  ],
40
40
  "type": "commonjs",
41
41
  "dependencies": {
42
- "commander": "^15.0.0"
42
+ "commander": "^15.0.0",
43
+ "eslint": "^8.57.0",
44
+ "eslint-plugin-sonarjs": "^0.25.0",
45
+ "eslint-plugin-security": "^3.0.0"
43
46
  }
44
47
  }