frontend-guardian 0.1.15 → 0.1.17

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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/cli.js +67 -4
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -9,6 +9,7 @@ npx frontend-guardian . # scan current dir
9
9
  npx frontend-guardian ./src # scan ./src
10
10
  npx frontend-guardian . --fail-on-score 70 # exit 1 if score < 70 (for CI)
11
11
  npx frontend-guardian . --json # output JSON
12
+ npx frontend-guardian . --suggest # AI fix suggestions (hosted API or GEMINI_API_KEY)
12
13
  npx frontend-guardian init # add .github/workflows/frontend-guardian.yml to repo
13
14
  ```
14
15
 
@@ -23,10 +24,12 @@ node packages/cli/dist/cli.js .
23
24
 
24
25
  ## Publish to npm
25
26
 
27
+ From repo root:
28
+
26
29
  ```bash
27
30
  pnpm run build:packages
28
31
  cd packages/core && npm publish --access public
29
- cd ../cli && npm publish --access public
32
+ cd packages/cli && npm publish --access public
30
33
  ```
31
34
 
32
35
  ## Output
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import * as path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import chalk from "chalk";
6
6
  import ora from "ora";
7
+ import { GoogleGenAI } from "@google/genai";
7
8
  import { scanFiles, scanZip } from "@justinmoto/frontend-guardian-core";
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
  const EXT = [".js", ".jsx", ".ts", ".tsx", ".css"];
@@ -321,14 +322,17 @@ function parseArgs() {
321
322
  const argv = process.argv.slice(2);
322
323
  const pathArg = argv[0];
323
324
  if (!pathArg || pathArg.startsWith("-")) {
324
- return { path: "", json: false, failOnScore: null };
325
+ return { path: "", json: false, failOnScore: null, suggest: false };
325
326
  }
326
327
  let json = false;
327
328
  let failOnScore = null;
329
+ let suggest = false;
328
330
  for (let i = 1; i < argv.length; i++) {
329
331
  const a = argv[i];
330
332
  if (a === "--json")
331
333
  json = true;
334
+ else if (a === "--suggest")
335
+ suggest = true;
332
336
  else if (a === "--fail-on-score" && argv[i + 1] != null) {
333
337
  const n = parseInt(argv[i + 1], 10);
334
338
  if (!Number.isNaN(n) && n >= 0 && n <= 100)
@@ -341,7 +345,29 @@ function parseArgs() {
341
345
  failOnScore = n;
342
346
  }
343
347
  }
344
- return { path: pathArg, json, failOnScore };
348
+ return { path: pathArg, json, failOnScore, suggest };
349
+ }
350
+ const DEFAULT_SUGGEST_API = "https://frontend-guardian.vercel.app/api/suggest";
351
+ async function getGeminiSuggestion(apiKey, issueTitle, detected) {
352
+ const ai = new GoogleGenAI({ apiKey });
353
+ const prompt = `Frontend Guardian found this issue: "${issueTitle}". Detected: ${detected.slice(0, 500)}. Give a brief concrete fix suggestion in 1-2 sentences. No preamble.`;
354
+ const res = await ai.models.generateContent({ model: "gemini-1.5-flash", contents: prompt });
355
+ const text = res.text;
356
+ return (text ?? "").trim();
357
+ }
358
+ async function getSuggestFromBackend(issueTitle, detected) {
359
+ const url = process.env.FRONTEND_GUARDIAN_SUGGEST_URL || DEFAULT_SUGGEST_API;
360
+ const res = await fetch(url, {
361
+ method: "POST",
362
+ headers: { "Content-Type": "application/json" },
363
+ body: JSON.stringify({ issueTitle, detected: detected.slice(0, 500) }),
364
+ });
365
+ if (!res.ok) {
366
+ const err = await res.json().catch(() => ({}));
367
+ throw new Error(err?.error || `API ${res.status}`);
368
+ }
369
+ const data = (await res.json());
370
+ return (data.suggestion ?? "").trim();
345
371
  }
346
372
  async function main() {
347
373
  const argv = process.argv.slice(2);
@@ -349,14 +375,15 @@ async function main() {
349
375
  initWorkflow(process.cwd());
350
376
  return;
351
377
  }
352
- const { path: pathArg, json: outputJson, failOnScore } = parseArgs();
378
+ const { path: pathArg, json: outputJson, failOnScore, suggest: wantSuggest } = parseArgs();
353
379
  if (!pathArg) {
354
380
  console.error("Usage: frontend-guardian <path> [options]");
355
381
  console.error(" frontend-guardian init # add GitHub Actions workflow");
356
382
  console.error(" npx frontend-guardian .");
357
383
  console.error(" npx frontend-guardian . --fail-on-score 70");
358
384
  console.error(" npx frontend-guardian . --json");
359
- console.error("Options: --json Output result as JSON. --fail-on-score <0-100> Exit 1 if score below threshold.");
385
+ console.error(" npx frontend-guardian . --suggest # AI fix suggestions (uses hosted API or your GEMINI_API_KEY)");
386
+ console.error("Options: --json Output as JSON. --fail-on-score <0-100> Exit 1 if below. --suggest Gemini AI suggestions.");
360
387
  process.exit(1);
361
388
  }
362
389
  const resolved = path.resolve(process.cwd(), pathArg);
@@ -414,6 +441,42 @@ async function main() {
414
441
  else {
415
442
  printResult(projectName, result);
416
443
  }
444
+ if (wantSuggest && result.warnings.length + result.duplicates.length > 0) {
445
+ const apiKey = process.env.GEMINI_API_KEY;
446
+ const byTitle = new Map();
447
+ for (const w of result.warnings) {
448
+ const { title, detected } = issueType(w);
449
+ const key = title;
450
+ if (!byTitle.has(key))
451
+ byTitle.set(key, detected);
452
+ else
453
+ byTitle.set(key, byTitle.get(key) + "; " + detected);
454
+ }
455
+ for (const d of result.duplicates) {
456
+ const key = "Duplicate Components";
457
+ const detected = d.files.join(", ");
458
+ if (!byTitle.has(key))
459
+ byTitle.set(key, detected);
460
+ else
461
+ byTitle.set(key, byTitle.get(key) + "; " + detected);
462
+ }
463
+ console.log(chalk.cyan("\n 🤖 AI suggestions\n"));
464
+ const spinner = ora("Getting suggestions...").start();
465
+ try {
466
+ for (const [title, detected] of byTitle.entries()) {
467
+ const suggestion = apiKey
468
+ ? await getGeminiSuggestion(apiKey, title, detected)
469
+ : await getSuggestFromBackend(title, detected);
470
+ spinner.stop();
471
+ console.log(" " + chalk.bold(title) + "\n " + chalk.gray(suggestion) + "\n");
472
+ spinner.start("Getting suggestions...");
473
+ }
474
+ spinner.succeed("Done.");
475
+ }
476
+ catch {
477
+ spinner.fail("AI suggestions not available at the moment.");
478
+ }
479
+ }
417
480
  if (failOnScore != null && result.score < failOnScore) {
418
481
  const msg = `Score ${result.score} is below required ${failOnScore}. Failing.`;
419
482
  if (outputJson)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-guardian",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Scan frontend projects for Tailwind & component consistency (Phase 1 CLI)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "build": "tsc"
12
12
  },
13
13
  "dependencies": {
14
+ "@google/genai": "^1.0.0",
14
15
  "@justinmoto/frontend-guardian-core": "^0.1.15",
15
16
  "chalk": "^5.3.0",
16
17
  "ora": "^8.0.1"