lintpage 0.1.0

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/cli.js');
package/dist/cli.d.mts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/cli.ts
26
+ var import_cac = __toESM(require("cac"));
27
+ var import_chalk2 = __toESM(require("chalk"));
28
+ var import_core = require("@lintpage/core");
29
+ var import_pro = require("@lintpage/pro");
30
+
31
+ // src/formatter.ts
32
+ var import_chalk = __toESM(require("chalk"));
33
+ var SEVERITY_ICON = {
34
+ CRITICAL: import_chalk.default.red.bold("\u2717"),
35
+ WARNING: import_chalk.default.yellow("\u26A0"),
36
+ INFO: import_chalk.default.blue("\u2139")
37
+ };
38
+ var SEVERITY_LABEL = {
39
+ CRITICAL: import_chalk.default.red.bold("CRITICAL"),
40
+ WARNING: import_chalk.default.yellow("WARNING"),
41
+ INFO: import_chalk.default.blue("INFO")
42
+ };
43
+ function groupByCategory(results) {
44
+ const groups = {};
45
+ for (const r of results) {
46
+ (groups[r.category] ??= []).push(r);
47
+ }
48
+ return groups;
49
+ }
50
+ function formatText(result, quiet) {
51
+ const lines = [];
52
+ const statusColor = result.overallStatus === "PASS" ? import_chalk.default.green : import_chalk.default.red;
53
+ lines.push("");
54
+ lines.push(
55
+ `${import_chalk.default.bold("LintPage")} audit for ${import_chalk.default.underline(result.url)}`
56
+ );
57
+ lines.push(
58
+ `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`
59
+ );
60
+ lines.push("");
61
+ if (result.results.length === 0) {
62
+ lines.push(import_chalk.default.green(" No issues found!"));
63
+ lines.push("");
64
+ return lines.join("\n");
65
+ }
66
+ if (quiet) {
67
+ const critical = result.results.filter((r) => r.severity === "CRITICAL").length;
68
+ const warnings = result.results.filter((r) => r.severity === "WARNING").length;
69
+ const info = result.results.filter((r) => r.severity === "INFO").length;
70
+ lines.push(
71
+ ` ${import_chalk.default.red.bold(`${critical} critical`)} ${import_chalk.default.yellow(`${warnings} warnings`)} ${import_chalk.default.blue(`${info} info`)}`
72
+ );
73
+ lines.push("");
74
+ return lines.join("\n");
75
+ }
76
+ for (const [category, scores] of Object.entries(result.categoryScores)) {
77
+ const pct = scores.max > 0 ? Math.round(scores.score / scores.max * 100) : 100;
78
+ const color = pct >= 80 ? import_chalk.default.green : pct >= 50 ? import_chalk.default.yellow : import_chalk.default.red;
79
+ lines.push(
80
+ ` ${import_chalk.default.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${import_chalk.default.dim(`(${pct}%)`)}`
81
+ );
82
+ }
83
+ lines.push("");
84
+ const grouped = groupByCategory(result.results);
85
+ for (const [category, issues] of Object.entries(grouped)) {
86
+ lines.push(import_chalk.default.bold.underline(` ${category}`));
87
+ for (const issue of issues) {
88
+ lines.push(
89
+ ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`
90
+ );
91
+ if (issue.fix) {
92
+ lines.push(` ${import_chalk.default.dim(issue.fix)}`);
93
+ }
94
+ }
95
+ lines.push("");
96
+ }
97
+ return lines.join("\n");
98
+ }
99
+ function formatJson(result) {
100
+ return JSON.stringify(result, null, 2);
101
+ }
102
+ var GH_LEVEL = {
103
+ CRITICAL: "error",
104
+ WARNING: "warning",
105
+ INFO: "notice"
106
+ };
107
+ function formatGithub(result) {
108
+ const lines = [];
109
+ for (const issue of result.results) {
110
+ const level = GH_LEVEL[issue.severity];
111
+ const title = `[${issue.ruleId}] ${issue.ruleName}`;
112
+ lines.push(`::${level} title=${title}::${issue.message}`);
113
+ }
114
+ const statusEmoji = result.overallStatus === "PASS" ? "\u2705" : "\u274C";
115
+ lines.push(
116
+ `::notice title=LintPage Score::${statusEmoji} ${result.url} \u2014 Score: ${result.score}/100 (${result.overallStatus})`
117
+ );
118
+ return lines.join("\n");
119
+ }
120
+ function format(result, fmt, quiet) {
121
+ switch (fmt) {
122
+ case "json":
123
+ return formatJson(result);
124
+ case "github":
125
+ return formatGithub(result);
126
+ case "text":
127
+ default:
128
+ return formatText(result, quiet);
129
+ }
130
+ }
131
+
132
+ // src/reporter.ts
133
+ async function reportResults(result, options) {
134
+ const url = options.apiUrl.replace(/\/$/, "") + "/api/v1/reports";
135
+ const response = await fetch(url, {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ Authorization: `Bearer ${options.token}`
140
+ },
141
+ body: JSON.stringify(result)
142
+ });
143
+ if (!response.ok) {
144
+ const body = await response.text().catch(() => "");
145
+ throw new Error(
146
+ `Failed to report results: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
147
+ );
148
+ }
149
+ }
150
+
151
+ // src/cli.ts
152
+ var import_fs = require("fs");
153
+ var import_path = require("path");
154
+ var cli = (0, import_cac.default)("lintpage");
155
+ var EXIT_PASS = 0;
156
+ var EXIT_FAIL = 1;
157
+ var EXIT_ERROR = 2;
158
+ cli.command("<url>", "Run an audit on a URL").option("--format <format>", "Output format: text, json, github", {
159
+ default: "text"
160
+ }).option("--fail-on-warning", "Exit 1 on warnings (not just criticals)").option("--report", "Send results to dashboard API").option("--quiet", "Minimal output (counts only)").option("--pro <key>", "Enable pro rules with license key").action(async (url, options) => {
161
+ const fmt = options.format || "text";
162
+ const failOnWarning = Boolean(options.failOnWarning);
163
+ const report = Boolean(options.report);
164
+ const quiet = Boolean(options.quiet);
165
+ const proKey = options.pro || process.env.LINTPAGE_PRO_KEY;
166
+ try {
167
+ let targetUrl = url;
168
+ if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
169
+ targetUrl = `https://${targetUrl}`;
170
+ }
171
+ const config = (0, import_core.loadConfig)() ?? void 0;
172
+ const plugins = [];
173
+ if (proKey) {
174
+ const licenseResult = await (0, import_pro.validateLicense)(proKey);
175
+ if (!licenseResult.valid) {
176
+ console.error(
177
+ import_chalk2.default.red(`Invalid license key: ${licenseResult.error}`)
178
+ );
179
+ process.exit(EXIT_ERROR);
180
+ }
181
+ plugins.push((0, import_pro.loadProPlugin)());
182
+ if (!quiet && fmt === "text") {
183
+ console.log(import_chalk2.default.dim("Pro rules enabled"));
184
+ }
185
+ }
186
+ if (!quiet && fmt === "text") {
187
+ console.log(import_chalk2.default.dim(`Auditing ${targetUrl}...`));
188
+ }
189
+ const result = await (0, import_core.auditPage)(targetUrl, { plugins, config });
190
+ console.log(format(result, fmt, quiet));
191
+ if (report) {
192
+ const apiUrl = process.env.LINTPAGE_API_URL || "https://lintpage.com";
193
+ const token = process.env.LINTPAGE_API_TOKEN || proKey;
194
+ if (!token) {
195
+ console.error(
196
+ import_chalk2.default.red(
197
+ "Missing API token for --report. Set LINTPAGE_API_TOKEN or LINTPAGE_PRO_KEY, or pass --pro <key>."
198
+ )
199
+ );
200
+ process.exit(EXIT_ERROR);
201
+ }
202
+ try {
203
+ await reportResults(result, { apiUrl, token });
204
+ if (!quiet && fmt === "text") {
205
+ console.log(import_chalk2.default.green("Results reported to dashboard"));
206
+ }
207
+ } catch (err) {
208
+ console.error(
209
+ import_chalk2.default.red(
210
+ `Failed to report: ${err instanceof Error ? err.message : err}`
211
+ )
212
+ );
213
+ process.exit(EXIT_ERROR);
214
+ }
215
+ }
216
+ const hasCritical = result.results.some(
217
+ (r) => r.severity === "CRITICAL"
218
+ );
219
+ const hasWarning = result.results.some(
220
+ (r) => r.severity === "WARNING"
221
+ );
222
+ if (hasCritical) {
223
+ process.exit(EXIT_FAIL);
224
+ }
225
+ if (failOnWarning && hasWarning) {
226
+ process.exit(EXIT_FAIL);
227
+ }
228
+ process.exit(EXIT_PASS);
229
+ } catch (err) {
230
+ if (fmt === "json") {
231
+ console.error(
232
+ JSON.stringify({
233
+ error: err instanceof Error ? err.message : String(err)
234
+ })
235
+ );
236
+ } else {
237
+ console.error(
238
+ import_chalk2.default.red(
239
+ `Error: ${err instanceof Error ? err.message : String(err)}`
240
+ )
241
+ );
242
+ }
243
+ process.exit(EXIT_ERROR);
244
+ }
245
+ });
246
+ cli.command("init", "Generate a .lintpagerc.json config file").action(() => {
247
+ const configPath = (0, import_path.join)(process.cwd(), ".lintpagerc.json");
248
+ if ((0, import_fs.existsSync)(configPath)) {
249
+ console.log(import_chalk2.default.yellow(".lintpagerc.json already exists"));
250
+ process.exit(EXIT_PASS);
251
+ }
252
+ const config = (0, import_core.createDefaultConfig)();
253
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n");
254
+ console.log(import_chalk2.default.green("Created .lintpagerc.json"));
255
+ process.exit(EXIT_PASS);
256
+ });
257
+ cli.help();
258
+ var pkgPath = (0, import_path.join)(__dirname, "..", "package.json");
259
+ var pkgJson = (0, import_fs.existsSync)(pkgPath) ? JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8")) : { version: "0.0.0" };
260
+ cli.version(pkgJson.version);
261
+ cli.parse();
262
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/formatter.ts","../src/reporter.ts"],"sourcesContent":["import cac from 'cac';\nimport chalk from 'chalk';\nimport {\n auditPage,\n loadConfig,\n createDefaultConfig,\n type RulePlugin,\n} from '@lintpage/core';\nimport { loadProPlugin, validateLicense } from '@lintpage/pro';\nimport { format, type Format } from './formatter';\nimport { reportResults } from './reporter';\nimport { writeFileSync, readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\n\nconst cli = cac('lintpage');\n\n// Exit codes\nconst EXIT_PASS = 0;\nconst EXIT_FAIL = 1;\nconst EXIT_ERROR = 2;\n\ncli\n .command('<url>', 'Run an audit on a URL')\n .option('--format <format>', 'Output format: text, json, github', {\n default: 'text',\n })\n .option('--fail-on-warning', 'Exit 1 on warnings (not just criticals)')\n .option('--report', 'Send results to dashboard API')\n .option('--quiet', 'Minimal output (counts only)')\n .option('--pro <key>', 'Enable pro rules with license key')\n .action(async (url: string, options: Record<string, unknown>) => {\n const fmt = (options.format as Format) || 'text';\n const failOnWarning = Boolean(options.failOnWarning);\n const report = Boolean(options.report);\n const quiet = Boolean(options.quiet);\n const proKey = (options.pro as string | undefined) || process.env.LINTPAGE_PRO_KEY;\n\n try {\n // Normalize URL\n let targetUrl = url;\n if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {\n targetUrl = `https://${targetUrl}`;\n }\n\n // Load config\n const config = loadConfig() ?? undefined;\n\n // Load plugins\n const plugins: RulePlugin[] = [];\n if (proKey) {\n const licenseResult = await validateLicense(proKey);\n if (!licenseResult.valid) {\n console.error(\n chalk.red(`Invalid license key: ${licenseResult.error}`)\n );\n process.exit(EXIT_ERROR);\n }\n plugins.push(loadProPlugin());\n if (!quiet && fmt === 'text') {\n console.log(chalk.dim('Pro rules enabled'));\n }\n }\n\n // Run audit\n if (!quiet && fmt === 'text') {\n console.log(chalk.dim(`Auditing ${targetUrl}...`));\n }\n\n const result = await auditPage(targetUrl, { plugins, config });\n\n // Output\n console.log(format(result, fmt, quiet));\n\n // Report to dashboard\n if (report) {\n const apiUrl =\n process.env.LINTPAGE_API_URL || 'https://lintpage.com';\n const token = process.env.LINTPAGE_API_TOKEN || proKey;\n if (!token) {\n console.error(\n chalk.red(\n 'Missing API token for --report. Set LINTPAGE_API_TOKEN or LINTPAGE_PRO_KEY, or pass --pro <key>.'\n )\n );\n process.exit(EXIT_ERROR);\n }\n try {\n await reportResults(result, { apiUrl, token });\n if (!quiet && fmt === 'text') {\n console.log(chalk.green('Results reported to dashboard'));\n }\n } catch (err) {\n console.error(\n chalk.red(\n `Failed to report: ${err instanceof Error ? err.message : err}`\n )\n );\n process.exit(EXIT_ERROR);\n }\n }\n\n // Exit code\n const hasCritical = result.results.some(\n (r) => r.severity === 'CRITICAL'\n );\n const hasWarning = result.results.some(\n (r) => r.severity === 'WARNING'\n );\n\n if (hasCritical) {\n process.exit(EXIT_FAIL);\n }\n if (failOnWarning && hasWarning) {\n process.exit(EXIT_FAIL);\n }\n process.exit(EXIT_PASS);\n } catch (err) {\n if (fmt === 'json') {\n console.error(\n JSON.stringify({\n error: err instanceof Error ? err.message : String(err),\n })\n );\n } else {\n console.error(\n chalk.red(\n `Error: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n process.exit(EXIT_ERROR);\n }\n });\n\ncli\n .command('init', 'Generate a .lintpagerc.json config file')\n .action(() => {\n const configPath = join(process.cwd(), '.lintpagerc.json');\n if (existsSync(configPath)) {\n console.log(chalk.yellow('.lintpagerc.json already exists'));\n process.exit(EXIT_PASS);\n }\n const config = createDefaultConfig();\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n console.log(chalk.green('Created .lintpagerc.json'));\n process.exit(EXIT_PASS);\n });\n\ncli.help();\n\n// Read version dynamically from package.json\nconst pkgPath = join(__dirname, '..', 'package.json');\nconst pkgJson = existsSync(pkgPath)\n ? JSON.parse(readFileSync(pkgPath, 'utf-8'))\n : { version: '0.0.0' };\ncli.version(pkgJson.version);\n\ncli.parse();\n","import chalk from 'chalk';\nimport type { PageResult, RuleResult, Severity } from '@lintpage/core';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nconst SEVERITY_LABEL: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('CRITICAL'),\n WARNING: chalk.yellow('WARNING'),\n INFO: chalk.blue('INFO'),\n};\n\nfunction groupByCategory(results: RuleResult[]): Record<string, RuleResult[]> {\n const groups: Record<string, RuleResult[]> = {};\n for (const r of results) {\n (groups[r.category] ??= []).push(r);\n }\n return groups;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n\n const statusColor = result.overallStatus === 'PASS' ? chalk.green : chalk.red;\n lines.push('');\n lines.push(\n `${chalk.bold('LintPage')} audit for ${chalk.underline(result.url)}`\n );\n lines.push(\n `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`\n );\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found!'));\n lines.push('');\n return lines.join('\\n');\n }\n\n if (quiet) {\n const critical = result.results.filter((r) => r.severity === 'CRITICAL').length;\n const warnings = result.results.filter((r) => r.severity === 'WARNING').length;\n const info = result.results.filter((r) => r.severity === 'INFO').length;\n lines.push(\n ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`\n );\n lines.push('');\n return lines.join('\\n');\n }\n\n // Category scores\n for (const [category, scores] of Object.entries(result.categoryScores)) {\n const pct = scores.max > 0 ? Math.round((scores.score / scores.max) * 100) : 100;\n const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;\n lines.push(\n ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`\n );\n }\n lines.push('');\n\n // Issues grouped by category\n const grouped = groupByCategory(result.results);\n for (const [category, issues] of Object.entries(grouped)) {\n lines.push(chalk.bold.underline(` ${category}`));\n for (const issue of issues) {\n lines.push(\n ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`\n );\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = `[${issue.ruleId}] ${issue.ruleName}`;\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${issue.message}`);\n }\n\n // Summary annotation\n const statusEmoji = result.overallStatus === 'PASS' ? '✅' : '❌';\n lines.push(\n `::notice title=LintPage Score::${statusEmoji} ${result.url} — Score: ${result.score}/100 (${result.overallStatus})`\n );\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n","import type { PageResult } from '@lintpage/core';\n\nexport interface ReportOptions {\n apiUrl: string;\n token: string;\n}\n\nexport async function reportResults(\n result: PageResult,\n options: ReportOptions\n): Promise<void> {\n const url = options.apiUrl.replace(/\\/$/, '') + '/api/v1/reports';\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.token}`,\n },\n body: JSON.stringify(result),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Failed to report results: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`\n );\n }\n}\n"],"mappings":"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","names":["import_chalk","chalk","cac","chalk"]}
package/dist/cli.mjs ADDED
@@ -0,0 +1,242 @@
1
+ // src/cli.ts
2
+ import cac from "cac";
3
+ import chalk2 from "chalk";
4
+ import {
5
+ auditPage,
6
+ loadConfig,
7
+ createDefaultConfig
8
+ } from "@lintpage/core";
9
+ import { loadProPlugin, validateLicense } from "@lintpage/pro";
10
+
11
+ // src/formatter.ts
12
+ import chalk from "chalk";
13
+ var SEVERITY_ICON = {
14
+ CRITICAL: chalk.red.bold("\u2717"),
15
+ WARNING: chalk.yellow("\u26A0"),
16
+ INFO: chalk.blue("\u2139")
17
+ };
18
+ var SEVERITY_LABEL = {
19
+ CRITICAL: chalk.red.bold("CRITICAL"),
20
+ WARNING: chalk.yellow("WARNING"),
21
+ INFO: chalk.blue("INFO")
22
+ };
23
+ function groupByCategory(results) {
24
+ const groups = {};
25
+ for (const r of results) {
26
+ (groups[r.category] ??= []).push(r);
27
+ }
28
+ return groups;
29
+ }
30
+ function formatText(result, quiet) {
31
+ const lines = [];
32
+ const statusColor = result.overallStatus === "PASS" ? chalk.green : chalk.red;
33
+ lines.push("");
34
+ lines.push(
35
+ `${chalk.bold("LintPage")} audit for ${chalk.underline(result.url)}`
36
+ );
37
+ lines.push(
38
+ `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`
39
+ );
40
+ lines.push("");
41
+ if (result.results.length === 0) {
42
+ lines.push(chalk.green(" No issues found!"));
43
+ lines.push("");
44
+ return lines.join("\n");
45
+ }
46
+ if (quiet) {
47
+ const critical = result.results.filter((r) => r.severity === "CRITICAL").length;
48
+ const warnings = result.results.filter((r) => r.severity === "WARNING").length;
49
+ const info = result.results.filter((r) => r.severity === "INFO").length;
50
+ lines.push(
51
+ ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`
52
+ );
53
+ lines.push("");
54
+ return lines.join("\n");
55
+ }
56
+ for (const [category, scores] of Object.entries(result.categoryScores)) {
57
+ const pct = scores.max > 0 ? Math.round(scores.score / scores.max * 100) : 100;
58
+ const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
59
+ lines.push(
60
+ ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`
61
+ );
62
+ }
63
+ lines.push("");
64
+ const grouped = groupByCategory(result.results);
65
+ for (const [category, issues] of Object.entries(grouped)) {
66
+ lines.push(chalk.bold.underline(` ${category}`));
67
+ for (const issue of issues) {
68
+ lines.push(
69
+ ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`
70
+ );
71
+ if (issue.fix) {
72
+ lines.push(` ${chalk.dim(issue.fix)}`);
73
+ }
74
+ }
75
+ lines.push("");
76
+ }
77
+ return lines.join("\n");
78
+ }
79
+ function formatJson(result) {
80
+ return JSON.stringify(result, null, 2);
81
+ }
82
+ var GH_LEVEL = {
83
+ CRITICAL: "error",
84
+ WARNING: "warning",
85
+ INFO: "notice"
86
+ };
87
+ function formatGithub(result) {
88
+ const lines = [];
89
+ for (const issue of result.results) {
90
+ const level = GH_LEVEL[issue.severity];
91
+ const title = `[${issue.ruleId}] ${issue.ruleName}`;
92
+ lines.push(`::${level} title=${title}::${issue.message}`);
93
+ }
94
+ const statusEmoji = result.overallStatus === "PASS" ? "\u2705" : "\u274C";
95
+ lines.push(
96
+ `::notice title=LintPage Score::${statusEmoji} ${result.url} \u2014 Score: ${result.score}/100 (${result.overallStatus})`
97
+ );
98
+ return lines.join("\n");
99
+ }
100
+ function format(result, fmt, quiet) {
101
+ switch (fmt) {
102
+ case "json":
103
+ return formatJson(result);
104
+ case "github":
105
+ return formatGithub(result);
106
+ case "text":
107
+ default:
108
+ return formatText(result, quiet);
109
+ }
110
+ }
111
+
112
+ // src/reporter.ts
113
+ async function reportResults(result, options) {
114
+ const url = options.apiUrl.replace(/\/$/, "") + "/api/v1/reports";
115
+ const response = await fetch(url, {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ Authorization: `Bearer ${options.token}`
120
+ },
121
+ body: JSON.stringify(result)
122
+ });
123
+ if (!response.ok) {
124
+ const body = await response.text().catch(() => "");
125
+ throw new Error(
126
+ `Failed to report results: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
127
+ );
128
+ }
129
+ }
130
+
131
+ // src/cli.ts
132
+ import { writeFileSync, readFileSync, existsSync } from "fs";
133
+ import { join } from "path";
134
+ var cli = cac("lintpage");
135
+ var EXIT_PASS = 0;
136
+ var EXIT_FAIL = 1;
137
+ var EXIT_ERROR = 2;
138
+ cli.command("<url>", "Run an audit on a URL").option("--format <format>", "Output format: text, json, github", {
139
+ default: "text"
140
+ }).option("--fail-on-warning", "Exit 1 on warnings (not just criticals)").option("--report", "Send results to dashboard API").option("--quiet", "Minimal output (counts only)").option("--pro <key>", "Enable pro rules with license key").action(async (url, options) => {
141
+ const fmt = options.format || "text";
142
+ const failOnWarning = Boolean(options.failOnWarning);
143
+ const report = Boolean(options.report);
144
+ const quiet = Boolean(options.quiet);
145
+ const proKey = options.pro || process.env.LINTPAGE_PRO_KEY;
146
+ try {
147
+ let targetUrl = url;
148
+ if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
149
+ targetUrl = `https://${targetUrl}`;
150
+ }
151
+ const config = loadConfig() ?? void 0;
152
+ const plugins = [];
153
+ if (proKey) {
154
+ const licenseResult = await validateLicense(proKey);
155
+ if (!licenseResult.valid) {
156
+ console.error(
157
+ chalk2.red(`Invalid license key: ${licenseResult.error}`)
158
+ );
159
+ process.exit(EXIT_ERROR);
160
+ }
161
+ plugins.push(loadProPlugin());
162
+ if (!quiet && fmt === "text") {
163
+ console.log(chalk2.dim("Pro rules enabled"));
164
+ }
165
+ }
166
+ if (!quiet && fmt === "text") {
167
+ console.log(chalk2.dim(`Auditing ${targetUrl}...`));
168
+ }
169
+ const result = await auditPage(targetUrl, { plugins, config });
170
+ console.log(format(result, fmt, quiet));
171
+ if (report) {
172
+ const apiUrl = process.env.LINTPAGE_API_URL || "https://lintpage.com";
173
+ const token = process.env.LINTPAGE_API_TOKEN || proKey;
174
+ if (!token) {
175
+ console.error(
176
+ chalk2.red(
177
+ "Missing API token for --report. Set LINTPAGE_API_TOKEN or LINTPAGE_PRO_KEY, or pass --pro <key>."
178
+ )
179
+ );
180
+ process.exit(EXIT_ERROR);
181
+ }
182
+ try {
183
+ await reportResults(result, { apiUrl, token });
184
+ if (!quiet && fmt === "text") {
185
+ console.log(chalk2.green("Results reported to dashboard"));
186
+ }
187
+ } catch (err) {
188
+ console.error(
189
+ chalk2.red(
190
+ `Failed to report: ${err instanceof Error ? err.message : err}`
191
+ )
192
+ );
193
+ process.exit(EXIT_ERROR);
194
+ }
195
+ }
196
+ const hasCritical = result.results.some(
197
+ (r) => r.severity === "CRITICAL"
198
+ );
199
+ const hasWarning = result.results.some(
200
+ (r) => r.severity === "WARNING"
201
+ );
202
+ if (hasCritical) {
203
+ process.exit(EXIT_FAIL);
204
+ }
205
+ if (failOnWarning && hasWarning) {
206
+ process.exit(EXIT_FAIL);
207
+ }
208
+ process.exit(EXIT_PASS);
209
+ } catch (err) {
210
+ if (fmt === "json") {
211
+ console.error(
212
+ JSON.stringify({
213
+ error: err instanceof Error ? err.message : String(err)
214
+ })
215
+ );
216
+ } else {
217
+ console.error(
218
+ chalk2.red(
219
+ `Error: ${err instanceof Error ? err.message : String(err)}`
220
+ )
221
+ );
222
+ }
223
+ process.exit(EXIT_ERROR);
224
+ }
225
+ });
226
+ cli.command("init", "Generate a .lintpagerc.json config file").action(() => {
227
+ const configPath = join(process.cwd(), ".lintpagerc.json");
228
+ if (existsSync(configPath)) {
229
+ console.log(chalk2.yellow(".lintpagerc.json already exists"));
230
+ process.exit(EXIT_PASS);
231
+ }
232
+ const config = createDefaultConfig();
233
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
234
+ console.log(chalk2.green("Created .lintpagerc.json"));
235
+ process.exit(EXIT_PASS);
236
+ });
237
+ cli.help();
238
+ var pkgPath = join(__dirname, "..", "package.json");
239
+ var pkgJson = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, "utf-8")) : { version: "0.0.0" };
240
+ cli.version(pkgJson.version);
241
+ cli.parse();
242
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/formatter.ts","../src/reporter.ts"],"sourcesContent":["import cac from 'cac';\nimport chalk from 'chalk';\nimport {\n auditPage,\n loadConfig,\n createDefaultConfig,\n type RulePlugin,\n} from '@lintpage/core';\nimport { loadProPlugin, validateLicense } from '@lintpage/pro';\nimport { format, type Format } from './formatter';\nimport { reportResults } from './reporter';\nimport { writeFileSync, readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\n\nconst cli = cac('lintpage');\n\n// Exit codes\nconst EXIT_PASS = 0;\nconst EXIT_FAIL = 1;\nconst EXIT_ERROR = 2;\n\ncli\n .command('<url>', 'Run an audit on a URL')\n .option('--format <format>', 'Output format: text, json, github', {\n default: 'text',\n })\n .option('--fail-on-warning', 'Exit 1 on warnings (not just criticals)')\n .option('--report', 'Send results to dashboard API')\n .option('--quiet', 'Minimal output (counts only)')\n .option('--pro <key>', 'Enable pro rules with license key')\n .action(async (url: string, options: Record<string, unknown>) => {\n const fmt = (options.format as Format) || 'text';\n const failOnWarning = Boolean(options.failOnWarning);\n const report = Boolean(options.report);\n const quiet = Boolean(options.quiet);\n const proKey = (options.pro as string | undefined) || process.env.LINTPAGE_PRO_KEY;\n\n try {\n // Normalize URL\n let targetUrl = url;\n if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {\n targetUrl = `https://${targetUrl}`;\n }\n\n // Load config\n const config = loadConfig() ?? undefined;\n\n // Load plugins\n const plugins: RulePlugin[] = [];\n if (proKey) {\n const licenseResult = await validateLicense(proKey);\n if (!licenseResult.valid) {\n console.error(\n chalk.red(`Invalid license key: ${licenseResult.error}`)\n );\n process.exit(EXIT_ERROR);\n }\n plugins.push(loadProPlugin());\n if (!quiet && fmt === 'text') {\n console.log(chalk.dim('Pro rules enabled'));\n }\n }\n\n // Run audit\n if (!quiet && fmt === 'text') {\n console.log(chalk.dim(`Auditing ${targetUrl}...`));\n }\n\n const result = await auditPage(targetUrl, { plugins, config });\n\n // Output\n console.log(format(result, fmt, quiet));\n\n // Report to dashboard\n if (report) {\n const apiUrl =\n process.env.LINTPAGE_API_URL || 'https://lintpage.com';\n const token = process.env.LINTPAGE_API_TOKEN || proKey;\n if (!token) {\n console.error(\n chalk.red(\n 'Missing API token for --report. Set LINTPAGE_API_TOKEN or LINTPAGE_PRO_KEY, or pass --pro <key>.'\n )\n );\n process.exit(EXIT_ERROR);\n }\n try {\n await reportResults(result, { apiUrl, token });\n if (!quiet && fmt === 'text') {\n console.log(chalk.green('Results reported to dashboard'));\n }\n } catch (err) {\n console.error(\n chalk.red(\n `Failed to report: ${err instanceof Error ? err.message : err}`\n )\n );\n process.exit(EXIT_ERROR);\n }\n }\n\n // Exit code\n const hasCritical = result.results.some(\n (r) => r.severity === 'CRITICAL'\n );\n const hasWarning = result.results.some(\n (r) => r.severity === 'WARNING'\n );\n\n if (hasCritical) {\n process.exit(EXIT_FAIL);\n }\n if (failOnWarning && hasWarning) {\n process.exit(EXIT_FAIL);\n }\n process.exit(EXIT_PASS);\n } catch (err) {\n if (fmt === 'json') {\n console.error(\n JSON.stringify({\n error: err instanceof Error ? err.message : String(err),\n })\n );\n } else {\n console.error(\n chalk.red(\n `Error: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n }\n process.exit(EXIT_ERROR);\n }\n });\n\ncli\n .command('init', 'Generate a .lintpagerc.json config file')\n .action(() => {\n const configPath = join(process.cwd(), '.lintpagerc.json');\n if (existsSync(configPath)) {\n console.log(chalk.yellow('.lintpagerc.json already exists'));\n process.exit(EXIT_PASS);\n }\n const config = createDefaultConfig();\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n console.log(chalk.green('Created .lintpagerc.json'));\n process.exit(EXIT_PASS);\n });\n\ncli.help();\n\n// Read version dynamically from package.json\nconst pkgPath = join(__dirname, '..', 'package.json');\nconst pkgJson = existsSync(pkgPath)\n ? JSON.parse(readFileSync(pkgPath, 'utf-8'))\n : { version: '0.0.0' };\ncli.version(pkgJson.version);\n\ncli.parse();\n","import chalk from 'chalk';\nimport type { PageResult, RuleResult, Severity } from '@lintpage/core';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nconst SEVERITY_LABEL: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('CRITICAL'),\n WARNING: chalk.yellow('WARNING'),\n INFO: chalk.blue('INFO'),\n};\n\nfunction groupByCategory(results: RuleResult[]): Record<string, RuleResult[]> {\n const groups: Record<string, RuleResult[]> = {};\n for (const r of results) {\n (groups[r.category] ??= []).push(r);\n }\n return groups;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n\n const statusColor = result.overallStatus === 'PASS' ? chalk.green : chalk.red;\n lines.push('');\n lines.push(\n `${chalk.bold('LintPage')} audit for ${chalk.underline(result.url)}`\n );\n lines.push(\n `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`\n );\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found!'));\n lines.push('');\n return lines.join('\\n');\n }\n\n if (quiet) {\n const critical = result.results.filter((r) => r.severity === 'CRITICAL').length;\n const warnings = result.results.filter((r) => r.severity === 'WARNING').length;\n const info = result.results.filter((r) => r.severity === 'INFO').length;\n lines.push(\n ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`\n );\n lines.push('');\n return lines.join('\\n');\n }\n\n // Category scores\n for (const [category, scores] of Object.entries(result.categoryScores)) {\n const pct = scores.max > 0 ? Math.round((scores.score / scores.max) * 100) : 100;\n const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;\n lines.push(\n ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`\n );\n }\n lines.push('');\n\n // Issues grouped by category\n const grouped = groupByCategory(result.results);\n for (const [category, issues] of Object.entries(grouped)) {\n lines.push(chalk.bold.underline(` ${category}`));\n for (const issue of issues) {\n lines.push(\n ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`\n );\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = `[${issue.ruleId}] ${issue.ruleName}`;\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${issue.message}`);\n }\n\n // Summary annotation\n const statusEmoji = result.overallStatus === 'PASS' ? '✅' : '❌';\n lines.push(\n `::notice title=LintPage Score::${statusEmoji} ${result.url} — Score: ${result.score}/100 (${result.overallStatus})`\n );\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n","import type { PageResult } from '@lintpage/core';\n\nexport interface ReportOptions {\n apiUrl: string;\n token: string;\n}\n\nexport async function reportResults(\n result: PageResult,\n options: ReportOptions\n): Promise<void> {\n const url = options.apiUrl.replace(/\\/$/, '') + '/api/v1/reports';\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.token}`,\n },\n body: JSON.stringify(result),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Failed to report results: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`\n );\n }\n}\n"],"mappings":";AAAA,OAAO,SAAS;AAChB,OAAOA,YAAW;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,eAAe,uBAAuB;;;ACR/C,OAAO,WAAW;AAOlB,IAAM,gBAA0C;AAAA,EAC9C,UAAU,MAAM,IAAI,KAAK,QAAG;AAAA,EAC5B,SAAS,MAAM,OAAO,QAAG;AAAA,EACzB,MAAM,MAAM,KAAK,QAAG;AACtB;AAEA,IAAM,iBAA2C;AAAA,EAC/C,UAAU,MAAM,IAAI,KAAK,UAAU;AAAA,EACnC,SAAS,MAAM,OAAO,SAAS;AAAA,EAC/B,MAAM,MAAM,KAAK,MAAM;AACzB;AAEA,SAAS,gBAAgB,SAAqD;AAC5E,QAAM,SAAuC,CAAC;AAC9C,aAAW,KAAK,SAAS;AACvB,KAAC,OAAO,EAAE,QAAQ,MAAM,CAAC,GAAG,KAAK,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,WAAW,QAAoB,OAAwB;AACrE,QAAM,QAAkB,CAAC;AAEzB,QAAM,cAAc,OAAO,kBAAkB,SAAS,MAAM,QAAQ,MAAM;AAC1E,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ,GAAG,MAAM,KAAK,UAAU,CAAC,cAAc,MAAM,UAAU,OAAO,GAAG,CAAC;AAAA,EACpE;AACA,QAAM;AAAA,IACJ,UAAU,YAAY,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,iBAAiB,YAAY,KAAK,OAAO,aAAa,CAAC;AAAA,EACzG;AACA,QAAM,KAAK,EAAE;AAEb,MAAI,OAAO,QAAQ,WAAW,GAAG;AAC/B,UAAM,KAAK,MAAM,MAAM,oBAAoB,CAAC;AAC5C,UAAM,KAAK,EAAE;AACb,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,MAAI,OAAO;AACT,UAAM,WAAW,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,UAAU,EAAE;AACzE,UAAM,WAAW,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,EAAE;AACxE,UAAM,OAAO,OAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AACjE,UAAM;AAAA,MACJ,KAAK,MAAM,IAAI,KAAK,GAAG,QAAQ,WAAW,CAAC,KAAK,MAAM,OAAO,GAAG,QAAQ,WAAW,CAAC,KAAK,MAAM,KAAK,GAAG,IAAI,OAAO,CAAC;AAAA,IACrH;AACA,UAAM,KAAK,EAAE;AACb,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAGA,aAAW,CAAC,UAAU,MAAM,KAAK,OAAO,QAAQ,OAAO,cAAc,GAAG;AACtE,UAAM,MAAM,OAAO,MAAM,IAAI,KAAK,MAAO,OAAO,QAAQ,OAAO,MAAO,GAAG,IAAI;AAC7E,UAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,SAAS,MAAM;AACzE,UAAM;AAAA,MACJ,KAAK,MAAM,KAAK,SAAS,OAAO,EAAE,CAAC,CAAC,IAAI,MAAM,GAAG,OAAO,KAAK,IAAI,OAAO,GAAG,EAAE,CAAC,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,CAAC;AAAA,IAC1G;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AAGb,QAAM,UAAU,gBAAgB,OAAO,OAAO;AAC9C,aAAW,CAAC,UAAU,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UAAM,KAAK,MAAM,KAAK,UAAU,KAAK,QAAQ,EAAE,CAAC;AAChD,eAAW,SAAS,QAAQ;AAC1B,YAAM;AAAA,QACJ,OAAO,cAAc,MAAM,QAAQ,CAAC,KAAK,eAAe,MAAM,QAAQ,EAAE,OAAO,EAAE,CAAC,IAAI,MAAM,OAAO;AAAA,MACrG;AACA,UAAI,MAAM,KAAK;AACb,cAAM,KAAK,UAAU,MAAM,IAAI,MAAM,GAAG,CAAC,EAAE;AAAA,MAC7C;AAAA,IACF;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIO,SAAS,WAAW,QAA4B;AACrD,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;AAIA,IAAM,WAAqC;AAAA,EACzC,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AACR;AAEO,SAAS,aAAa,QAA4B;AACvD,QAAM,QAAkB,CAAC;AAEzB,aAAW,SAAS,OAAO,SAAS;AAClC,UAAM,QAAQ,SAAS,MAAM,QAAQ;AACrC,UAAM,QAAQ,IAAI,MAAM,MAAM,KAAK,MAAM,QAAQ;AAEjD,UAAM,KAAK,KAAK,KAAK,UAAU,KAAK,KAAK,MAAM,OAAO,EAAE;AAAA,EAC1D;AAGA,QAAM,cAAc,OAAO,kBAAkB,SAAS,WAAM;AAC5D,QAAM;AAAA,IACJ,kCAAkC,WAAW,IAAI,OAAO,GAAG,kBAAa,OAAO,KAAK,SAAS,OAAO,aAAa;AAAA,EACnH;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIO,SAAS,OACd,QACA,KACA,OACQ;AACR,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,WAAW,MAAM;AAAA,IAC1B,KAAK;AACH,aAAO,aAAa,MAAM;AAAA,IAC5B,KAAK;AAAA,IACL;AACE,aAAO,WAAW,QAAQ,KAAK;AAAA,EACnC;AACF;;;AC/HA,eAAsB,cACpB,QACA,SACe;AACf,QAAM,MAAM,QAAQ,OAAO,QAAQ,OAAO,EAAE,IAAI;AAEhD,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,QAAQ,KAAK;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,UAAU,MAAM;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,UAAM,IAAI;AAAA,MACR,6BAA6B,SAAS,MAAM,IAAI,SAAS,UAAU,GAAG,OAAO,WAAM,IAAI,KAAK,EAAE;AAAA,IAChG;AAAA,EACF;AACF;;;AFjBA,SAAS,eAAe,cAAc,kBAAkB;AACxD,SAAS,YAAY;AAErB,IAAM,MAAM,IAAI,UAAU;AAG1B,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,aAAa;AAEnB,IACG,QAAQ,SAAS,uBAAuB,EACxC,OAAO,qBAAqB,qCAAqC;AAAA,EAChE,SAAS;AACX,CAAC,EACA,OAAO,qBAAqB,yCAAyC,EACrE,OAAO,YAAY,+BAA+B,EAClD,OAAO,WAAW,8BAA8B,EAChD,OAAO,eAAe,mCAAmC,EACzD,OAAO,OAAO,KAAa,YAAqC;AAC/D,QAAM,MAAO,QAAQ,UAAqB;AAC1C,QAAM,gBAAgB,QAAQ,QAAQ,aAAa;AACnD,QAAM,SAAS,QAAQ,QAAQ,MAAM;AACrC,QAAM,QAAQ,QAAQ,QAAQ,KAAK;AACnC,QAAM,SAAU,QAAQ,OAA8B,QAAQ,IAAI;AAElE,MAAI;AAEF,QAAI,YAAY;AAChB,QAAI,CAAC,UAAU,WAAW,SAAS,KAAK,CAAC,UAAU,WAAW,UAAU,GAAG;AACzE,kBAAY,WAAW,SAAS;AAAA,IAClC;AAGA,UAAM,SAAS,WAAW,KAAK;AAG/B,UAAM,UAAwB,CAAC;AAC/B,QAAI,QAAQ;AACV,YAAM,gBAAgB,MAAM,gBAAgB,MAAM;AAClD,UAAI,CAAC,cAAc,OAAO;AACxB,gBAAQ;AAAA,UACNC,OAAM,IAAI,wBAAwB,cAAc,KAAK,EAAE;AAAA,QACzD;AACA,gBAAQ,KAAK,UAAU;AAAA,MACzB;AACA,cAAQ,KAAK,cAAc,CAAC;AAC5B,UAAI,CAAC,SAAS,QAAQ,QAAQ;AAC5B,gBAAQ,IAAIA,OAAM,IAAI,mBAAmB,CAAC;AAAA,MAC5C;AAAA,IACF;AAGA,QAAI,CAAC,SAAS,QAAQ,QAAQ;AAC5B,cAAQ,IAAIA,OAAM,IAAI,YAAY,SAAS,KAAK,CAAC;AAAA,IACnD;AAEA,UAAM,SAAS,MAAM,UAAU,WAAW,EAAE,SAAS,OAAO,CAAC;AAG7D,YAAQ,IAAI,OAAO,QAAQ,KAAK,KAAK,CAAC;AAGtC,QAAI,QAAQ;AACV,YAAM,SACJ,QAAQ,IAAI,oBAAoB;AAClC,YAAM,QAAQ,QAAQ,IAAI,sBAAsB;AAChD,UAAI,CAAC,OAAO;AACV,gBAAQ;AAAA,UACNA,OAAM;AAAA,YACJ;AAAA,UACF;AAAA,QACF;AACA,gBAAQ,KAAK,UAAU;AAAA,MACzB;AACA,UAAI;AACF,cAAM,cAAc,QAAQ,EAAE,QAAQ,MAAM,CAAC;AAC7C,YAAI,CAAC,SAAS,QAAQ,QAAQ;AAC5B,kBAAQ,IAAIA,OAAM,MAAM,+BAA+B,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACNA,OAAM;AAAA,YACJ,qBAAqB,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,UAC/D;AAAA,QACF;AACA,gBAAQ,KAAK,UAAU;AAAA,MACzB;AAAA,IACF;AAGA,UAAM,cAAc,OAAO,QAAQ;AAAA,MACjC,CAAC,MAAM,EAAE,aAAa;AAAA,IACxB;AACA,UAAM,aAAa,OAAO,QAAQ;AAAA,MAChC,CAAC,MAAM,EAAE,aAAa;AAAA,IACxB;AAEA,QAAI,aAAa;AACf,cAAQ,KAAK,SAAS;AAAA,IACxB;AACA,QAAI,iBAAiB,YAAY;AAC/B,cAAQ,KAAK,SAAS;AAAA,IACxB;AACA,YAAQ,KAAK,SAAS;AAAA,EACxB,SAAS,KAAK;AACZ,QAAI,QAAQ,QAAQ;AAClB,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACNA,OAAM;AAAA,UACJ,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,UAAU;AAAA,EACzB;AACF,CAAC;AAEH,IACG,QAAQ,QAAQ,yCAAyC,EACzD,OAAO,MAAM;AACZ,QAAM,aAAa,KAAK,QAAQ,IAAI,GAAG,kBAAkB;AACzD,MAAI,WAAW,UAAU,GAAG;AAC1B,YAAQ,IAAIA,OAAM,OAAO,iCAAiC,CAAC;AAC3D,YAAQ,KAAK,SAAS;AAAA,EACxB;AACA,QAAM,SAAS,oBAAoB;AACnC,gBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAChE,UAAQ,IAAIA,OAAM,MAAM,0BAA0B,CAAC;AACnD,UAAQ,KAAK,SAAS;AACxB,CAAC;AAEH,IAAI,KAAK;AAGT,IAAM,UAAU,KAAK,WAAW,MAAM,cAAc;AACpD,IAAM,UAAU,WAAW,OAAO,IAC9B,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC,IACzC,EAAE,SAAS,QAAQ;AACvB,IAAI,QAAQ,QAAQ,OAAO;AAE3B,IAAI,MAAM;","names":["chalk","chalk"]}
@@ -0,0 +1,15 @@
1
+ import { PageResult } from '@lintpage/core';
2
+
3
+ type Format = 'text' | 'json' | 'github';
4
+ declare function formatText(result: PageResult, quiet: boolean): string;
5
+ declare function formatJson(result: PageResult): string;
6
+ declare function formatGithub(result: PageResult): string;
7
+ declare function format(result: PageResult, fmt: Format, quiet: boolean): string;
8
+
9
+ interface ReportOptions {
10
+ apiUrl: string;
11
+ token: string;
12
+ }
13
+ declare function reportResults(result: PageResult, options: ReportOptions): Promise<void>;
14
+
15
+ export { type Format, type ReportOptions, format, formatGithub, formatJson, formatText, reportResults };
@@ -0,0 +1,15 @@
1
+ import { PageResult } from '@lintpage/core';
2
+
3
+ type Format = 'text' | 'json' | 'github';
4
+ declare function formatText(result: PageResult, quiet: boolean): string;
5
+ declare function formatJson(result: PageResult): string;
6
+ declare function formatGithub(result: PageResult): string;
7
+ declare function format(result: PageResult, fmt: Format, quiet: boolean): string;
8
+
9
+ interface ReportOptions {
10
+ apiUrl: string;
11
+ token: string;
12
+ }
13
+ declare function reportResults(result: PageResult, options: ReportOptions): Promise<void>;
14
+
15
+ export { type Format, type ReportOptions, format, formatGithub, formatJson, formatText, reportResults };
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ format: () => format,
34
+ formatGithub: () => formatGithub,
35
+ formatJson: () => formatJson,
36
+ formatText: () => formatText,
37
+ reportResults: () => reportResults
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/formatter.ts
42
+ var import_chalk = __toESM(require("chalk"));
43
+ var SEVERITY_ICON = {
44
+ CRITICAL: import_chalk.default.red.bold("\u2717"),
45
+ WARNING: import_chalk.default.yellow("\u26A0"),
46
+ INFO: import_chalk.default.blue("\u2139")
47
+ };
48
+ var SEVERITY_LABEL = {
49
+ CRITICAL: import_chalk.default.red.bold("CRITICAL"),
50
+ WARNING: import_chalk.default.yellow("WARNING"),
51
+ INFO: import_chalk.default.blue("INFO")
52
+ };
53
+ function groupByCategory(results) {
54
+ const groups = {};
55
+ for (const r of results) {
56
+ (groups[r.category] ??= []).push(r);
57
+ }
58
+ return groups;
59
+ }
60
+ function formatText(result, quiet) {
61
+ const lines = [];
62
+ const statusColor = result.overallStatus === "PASS" ? import_chalk.default.green : import_chalk.default.red;
63
+ lines.push("");
64
+ lines.push(
65
+ `${import_chalk.default.bold("LintPage")} audit for ${import_chalk.default.underline(result.url)}`
66
+ );
67
+ lines.push(
68
+ `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`
69
+ );
70
+ lines.push("");
71
+ if (result.results.length === 0) {
72
+ lines.push(import_chalk.default.green(" No issues found!"));
73
+ lines.push("");
74
+ return lines.join("\n");
75
+ }
76
+ if (quiet) {
77
+ const critical = result.results.filter((r) => r.severity === "CRITICAL").length;
78
+ const warnings = result.results.filter((r) => r.severity === "WARNING").length;
79
+ const info = result.results.filter((r) => r.severity === "INFO").length;
80
+ lines.push(
81
+ ` ${import_chalk.default.red.bold(`${critical} critical`)} ${import_chalk.default.yellow(`${warnings} warnings`)} ${import_chalk.default.blue(`${info} info`)}`
82
+ );
83
+ lines.push("");
84
+ return lines.join("\n");
85
+ }
86
+ for (const [category, scores] of Object.entries(result.categoryScores)) {
87
+ const pct = scores.max > 0 ? Math.round(scores.score / scores.max * 100) : 100;
88
+ const color = pct >= 80 ? import_chalk.default.green : pct >= 50 ? import_chalk.default.yellow : import_chalk.default.red;
89
+ lines.push(
90
+ ` ${import_chalk.default.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${import_chalk.default.dim(`(${pct}%)`)}`
91
+ );
92
+ }
93
+ lines.push("");
94
+ const grouped = groupByCategory(result.results);
95
+ for (const [category, issues] of Object.entries(grouped)) {
96
+ lines.push(import_chalk.default.bold.underline(` ${category}`));
97
+ for (const issue of issues) {
98
+ lines.push(
99
+ ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`
100
+ );
101
+ if (issue.fix) {
102
+ lines.push(` ${import_chalk.default.dim(issue.fix)}`);
103
+ }
104
+ }
105
+ lines.push("");
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+ function formatJson(result) {
110
+ return JSON.stringify(result, null, 2);
111
+ }
112
+ var GH_LEVEL = {
113
+ CRITICAL: "error",
114
+ WARNING: "warning",
115
+ INFO: "notice"
116
+ };
117
+ function formatGithub(result) {
118
+ const lines = [];
119
+ for (const issue of result.results) {
120
+ const level = GH_LEVEL[issue.severity];
121
+ const title = `[${issue.ruleId}] ${issue.ruleName}`;
122
+ lines.push(`::${level} title=${title}::${issue.message}`);
123
+ }
124
+ const statusEmoji = result.overallStatus === "PASS" ? "\u2705" : "\u274C";
125
+ lines.push(
126
+ `::notice title=LintPage Score::${statusEmoji} ${result.url} \u2014 Score: ${result.score}/100 (${result.overallStatus})`
127
+ );
128
+ return lines.join("\n");
129
+ }
130
+ function format(result, fmt, quiet) {
131
+ switch (fmt) {
132
+ case "json":
133
+ return formatJson(result);
134
+ case "github":
135
+ return formatGithub(result);
136
+ case "text":
137
+ default:
138
+ return formatText(result, quiet);
139
+ }
140
+ }
141
+
142
+ // src/reporter.ts
143
+ async function reportResults(result, options) {
144
+ const url = options.apiUrl.replace(/\/$/, "") + "/api/v1/reports";
145
+ const response = await fetch(url, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ Authorization: `Bearer ${options.token}`
150
+ },
151
+ body: JSON.stringify(result)
152
+ });
153
+ if (!response.ok) {
154
+ const body = await response.text().catch(() => "");
155
+ throw new Error(
156
+ `Failed to report results: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
157
+ );
158
+ }
159
+ }
160
+ // Annotate the CommonJS export names for ESM import in node:
161
+ 0 && (module.exports = {
162
+ format,
163
+ formatGithub,
164
+ formatJson,
165
+ formatText,
166
+ reportResults
167
+ });
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/formatter.ts","../src/reporter.ts"],"sourcesContent":["export { format, formatText, formatJson, formatGithub, type Format } from './formatter';\nexport { reportResults, type ReportOptions } from './reporter';\n","import chalk from 'chalk';\nimport type { PageResult, RuleResult, Severity } from '@lintpage/core';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nconst SEVERITY_LABEL: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('CRITICAL'),\n WARNING: chalk.yellow('WARNING'),\n INFO: chalk.blue('INFO'),\n};\n\nfunction groupByCategory(results: RuleResult[]): Record<string, RuleResult[]> {\n const groups: Record<string, RuleResult[]> = {};\n for (const r of results) {\n (groups[r.category] ??= []).push(r);\n }\n return groups;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n\n const statusColor = result.overallStatus === 'PASS' ? chalk.green : chalk.red;\n lines.push('');\n lines.push(\n `${chalk.bold('LintPage')} audit for ${chalk.underline(result.url)}`\n );\n lines.push(\n `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`\n );\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found!'));\n lines.push('');\n return lines.join('\\n');\n }\n\n if (quiet) {\n const critical = result.results.filter((r) => r.severity === 'CRITICAL').length;\n const warnings = result.results.filter((r) => r.severity === 'WARNING').length;\n const info = result.results.filter((r) => r.severity === 'INFO').length;\n lines.push(\n ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`\n );\n lines.push('');\n return lines.join('\\n');\n }\n\n // Category scores\n for (const [category, scores] of Object.entries(result.categoryScores)) {\n const pct = scores.max > 0 ? Math.round((scores.score / scores.max) * 100) : 100;\n const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;\n lines.push(\n ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`\n );\n }\n lines.push('');\n\n // Issues grouped by category\n const grouped = groupByCategory(result.results);\n for (const [category, issues] of Object.entries(grouped)) {\n lines.push(chalk.bold.underline(` ${category}`));\n for (const issue of issues) {\n lines.push(\n ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`\n );\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = `[${issue.ruleId}] ${issue.ruleName}`;\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${issue.message}`);\n }\n\n // Summary annotation\n const statusEmoji = result.overallStatus === 'PASS' ? '✅' : '❌';\n lines.push(\n `::notice title=LintPage Score::${statusEmoji} ${result.url} — Score: ${result.score}/100 (${result.overallStatus})`\n );\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n","import type { PageResult } from '@lintpage/core';\n\nexport interface ReportOptions {\n apiUrl: string;\n token: string;\n}\n\nexport async function reportResults(\n result: PageResult,\n options: ReportOptions\n): Promise<void> {\n const url = options.apiUrl.replace(/\\/$/, '') + '/api/v1/reports';\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.token}`,\n },\n body: JSON.stringify(result),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Failed to report results: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`\n );\n }\n}\n"],"mappings":"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","names":["chalk"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,127 @@
1
+ // src/formatter.ts
2
+ import chalk from "chalk";
3
+ var SEVERITY_ICON = {
4
+ CRITICAL: chalk.red.bold("\u2717"),
5
+ WARNING: chalk.yellow("\u26A0"),
6
+ INFO: chalk.blue("\u2139")
7
+ };
8
+ var SEVERITY_LABEL = {
9
+ CRITICAL: chalk.red.bold("CRITICAL"),
10
+ WARNING: chalk.yellow("WARNING"),
11
+ INFO: chalk.blue("INFO")
12
+ };
13
+ function groupByCategory(results) {
14
+ const groups = {};
15
+ for (const r of results) {
16
+ (groups[r.category] ??= []).push(r);
17
+ }
18
+ return groups;
19
+ }
20
+ function formatText(result, quiet) {
21
+ const lines = [];
22
+ const statusColor = result.overallStatus === "PASS" ? chalk.green : chalk.red;
23
+ lines.push("");
24
+ lines.push(
25
+ `${chalk.bold("LintPage")} audit for ${chalk.underline(result.url)}`
26
+ );
27
+ lines.push(
28
+ `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`
29
+ );
30
+ lines.push("");
31
+ if (result.results.length === 0) {
32
+ lines.push(chalk.green(" No issues found!"));
33
+ lines.push("");
34
+ return lines.join("\n");
35
+ }
36
+ if (quiet) {
37
+ const critical = result.results.filter((r) => r.severity === "CRITICAL").length;
38
+ const warnings = result.results.filter((r) => r.severity === "WARNING").length;
39
+ const info = result.results.filter((r) => r.severity === "INFO").length;
40
+ lines.push(
41
+ ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`
42
+ );
43
+ lines.push("");
44
+ return lines.join("\n");
45
+ }
46
+ for (const [category, scores] of Object.entries(result.categoryScores)) {
47
+ const pct = scores.max > 0 ? Math.round(scores.score / scores.max * 100) : 100;
48
+ const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
49
+ lines.push(
50
+ ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`
51
+ );
52
+ }
53
+ lines.push("");
54
+ const grouped = groupByCategory(result.results);
55
+ for (const [category, issues] of Object.entries(grouped)) {
56
+ lines.push(chalk.bold.underline(` ${category}`));
57
+ for (const issue of issues) {
58
+ lines.push(
59
+ ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`
60
+ );
61
+ if (issue.fix) {
62
+ lines.push(` ${chalk.dim(issue.fix)}`);
63
+ }
64
+ }
65
+ lines.push("");
66
+ }
67
+ return lines.join("\n");
68
+ }
69
+ function formatJson(result) {
70
+ return JSON.stringify(result, null, 2);
71
+ }
72
+ var GH_LEVEL = {
73
+ CRITICAL: "error",
74
+ WARNING: "warning",
75
+ INFO: "notice"
76
+ };
77
+ function formatGithub(result) {
78
+ const lines = [];
79
+ for (const issue of result.results) {
80
+ const level = GH_LEVEL[issue.severity];
81
+ const title = `[${issue.ruleId}] ${issue.ruleName}`;
82
+ lines.push(`::${level} title=${title}::${issue.message}`);
83
+ }
84
+ const statusEmoji = result.overallStatus === "PASS" ? "\u2705" : "\u274C";
85
+ lines.push(
86
+ `::notice title=LintPage Score::${statusEmoji} ${result.url} \u2014 Score: ${result.score}/100 (${result.overallStatus})`
87
+ );
88
+ return lines.join("\n");
89
+ }
90
+ function format(result, fmt, quiet) {
91
+ switch (fmt) {
92
+ case "json":
93
+ return formatJson(result);
94
+ case "github":
95
+ return formatGithub(result);
96
+ case "text":
97
+ default:
98
+ return formatText(result, quiet);
99
+ }
100
+ }
101
+
102
+ // src/reporter.ts
103
+ async function reportResults(result, options) {
104
+ const url = options.apiUrl.replace(/\/$/, "") + "/api/v1/reports";
105
+ const response = await fetch(url, {
106
+ method: "POST",
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Authorization: `Bearer ${options.token}`
110
+ },
111
+ body: JSON.stringify(result)
112
+ });
113
+ if (!response.ok) {
114
+ const body = await response.text().catch(() => "");
115
+ throw new Error(
116
+ `Failed to report results: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`
117
+ );
118
+ }
119
+ }
120
+ export {
121
+ format,
122
+ formatGithub,
123
+ formatJson,
124
+ formatText,
125
+ reportResults
126
+ };
127
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/formatter.ts","../src/reporter.ts"],"sourcesContent":["import chalk from 'chalk';\nimport type { PageResult, RuleResult, Severity } from '@lintpage/core';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nconst SEVERITY_LABEL: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('CRITICAL'),\n WARNING: chalk.yellow('WARNING'),\n INFO: chalk.blue('INFO'),\n};\n\nfunction groupByCategory(results: RuleResult[]): Record<string, RuleResult[]> {\n const groups: Record<string, RuleResult[]> = {};\n for (const r of results) {\n (groups[r.category] ??= []).push(r);\n }\n return groups;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n\n const statusColor = result.overallStatus === 'PASS' ? chalk.green : chalk.red;\n lines.push('');\n lines.push(\n `${chalk.bold('LintPage')} audit for ${chalk.underline(result.url)}`\n );\n lines.push(\n `Score: ${statusColor.bold(String(result.score))}/100 Status: ${statusColor.bold(result.overallStatus)}`\n );\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found!'));\n lines.push('');\n return lines.join('\\n');\n }\n\n if (quiet) {\n const critical = result.results.filter((r) => r.severity === 'CRITICAL').length;\n const warnings = result.results.filter((r) => r.severity === 'WARNING').length;\n const info = result.results.filter((r) => r.severity === 'INFO').length;\n lines.push(\n ` ${chalk.red.bold(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.blue(`${info} info`)}`\n );\n lines.push('');\n return lines.join('\\n');\n }\n\n // Category scores\n for (const [category, scores] of Object.entries(result.categoryScores)) {\n const pct = scores.max > 0 ? Math.round((scores.score / scores.max) * 100) : 100;\n const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;\n lines.push(\n ` ${chalk.bold(category.padEnd(16))} ${color(`${scores.score}/${scores.max}`)} ${chalk.dim(`(${pct}%)`)}`\n );\n }\n lines.push('');\n\n // Issues grouped by category\n const grouped = groupByCategory(result.results);\n for (const [category, issues] of Object.entries(grouped)) {\n lines.push(chalk.bold.underline(` ${category}`));\n for (const issue of issues) {\n lines.push(\n ` ${SEVERITY_ICON[issue.severity]} ${SEVERITY_LABEL[issue.severity].padEnd(20)} ${issue.message}`\n );\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = `[${issue.ruleId}] ${issue.ruleName}`;\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${issue.message}`);\n }\n\n // Summary annotation\n const statusEmoji = result.overallStatus === 'PASS' ? '✅' : '❌';\n lines.push(\n `::notice title=LintPage Score::${statusEmoji} ${result.url} — Score: ${result.score}/100 (${result.overallStatus})`\n );\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n","import type { PageResult } from '@lintpage/core';\n\nexport interface ReportOptions {\n apiUrl: string;\n token: string;\n}\n\nexport async function reportResults(\n result: PageResult,\n options: ReportOptions\n): Promise<void> {\n const url = options.apiUrl.replace(/\\/$/, '') + '/api/v1/reports';\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${options.token}`,\n },\n body: JSON.stringify(result),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Failed to report results: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`\n );\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "lintpage",
3
+ "version": "0.1.0",
4
+ "description": "CLI for LintPage — audit website SEO, accessibility, and performance",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "lintpage": "./bin/lintpage.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.mts",
14
+ "default": "./dist/index.mjs"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "bin"
25
+ ],
26
+ "dependencies": {
27
+ "cac": "^6.7.14",
28
+ "chalk": "^5.4.1",
29
+ "@lintpage/pro": "0.1.0",
30
+ "@lintpage/core": "0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "tsup": "^8.4.0",
34
+ "typescript": "^5",
35
+ "vitest": "^4.0.18",
36
+ "@lintpage/tsconfig": "0.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "typecheck": "tsc --noEmit",
43
+ "clean": "rm -rf dist"
44
+ }
45
+ }