@cloudcreate/adsense-check 1.0.1 → 1.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.
- package/README.md +97 -77
- package/dist/chunk-GW4SHZYX.js +1699 -0
- package/dist/cli.js +219 -92
- package/dist/index.d.ts +47 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-V2YZ36NU.js +0 -1015
package/dist/cli.js
CHANGED
|
@@ -1,108 +1,189 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
BrowserManager,
|
|
4
|
+
analyzeSiteTopic,
|
|
5
|
+
check,
|
|
6
|
+
detectSiteType,
|
|
7
|
+
fetchPage,
|
|
8
|
+
getSupportedLangs,
|
|
9
|
+
isValidLang,
|
|
10
|
+
t
|
|
11
|
+
} from "./chunk-GW4SHZYX.js";
|
|
5
12
|
|
|
6
13
|
// src/cli.ts
|
|
7
14
|
import "dotenv/config";
|
|
8
15
|
import { Command } from "commander";
|
|
9
16
|
import chalk2 from "chalk";
|
|
10
17
|
import { mkdirSync, writeFileSync } from "fs";
|
|
11
|
-
import { join
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
18
|
+
import { join } from "path";
|
|
13
19
|
|
|
14
20
|
// src/reporter.ts
|
|
15
21
|
import chalk from "chalk";
|
|
16
22
|
import figures from "figures";
|
|
17
|
-
var
|
|
23
|
+
var ICONS = {
|
|
18
24
|
pass: chalk.green(figures.tick),
|
|
19
25
|
warn: chalk.yellow(figures.warning),
|
|
20
26
|
fail: chalk.red(figures.cross),
|
|
21
27
|
skip: chalk.gray("-")
|
|
22
28
|
};
|
|
23
|
-
var
|
|
29
|
+
var LABELS = {
|
|
24
30
|
pass: chalk.green("PASS"),
|
|
25
31
|
warn: chalk.yellow("WARN"),
|
|
26
32
|
fail: chalk.red("FAIL"),
|
|
27
33
|
skip: chalk.gray("SKIP")
|
|
28
34
|
};
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
function renderBar(score, max, width = 20) {
|
|
36
|
+
const ratio = max > 0 ? score / max : 0;
|
|
37
|
+
const filled = Math.round(ratio * width);
|
|
38
|
+
const empty = width - filled;
|
|
39
|
+
const color = ratio >= 0.8 ? chalk.green : ratio >= 0.5 ? chalk.yellow : chalk.red;
|
|
40
|
+
return color("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
|
|
41
|
+
}
|
|
42
|
+
function categoryScore(cat) {
|
|
43
|
+
if (cat.items.length === 0) return 100;
|
|
44
|
+
const earned = cat.items.reduce((s, i) => {
|
|
45
|
+
if (i.status === "pass") return s + 100;
|
|
46
|
+
if (i.status === "warn") return s + 40;
|
|
47
|
+
return s;
|
|
48
|
+
}, 0);
|
|
49
|
+
return Math.round(earned / cat.items.length);
|
|
37
50
|
}
|
|
38
51
|
function renderTerminalReport(report) {
|
|
39
|
-
const
|
|
52
|
+
const lang = report.lang;
|
|
53
|
+
const typeKey = `detector.type.${report.siteType}`;
|
|
54
|
+
const typeLabel = t(typeKey, lang);
|
|
55
|
+
const confidenceLabel = report.siteTypeConfidence === "high" ? "" : ` (${report.siteTypeConfidence})`;
|
|
56
|
+
const lines = [
|
|
57
|
+
"",
|
|
58
|
+
chalk.bold.cyan(` ${t("report.title", lang)}`),
|
|
59
|
+
chalk.gray(` URL: ${report.url}`),
|
|
60
|
+
chalk.gray(` Time: ${report.timestamp}`),
|
|
61
|
+
chalk.gray(` Site type: ${typeLabel}${confidenceLabel}`)
|
|
62
|
+
];
|
|
63
|
+
if (report.siteTopic) {
|
|
64
|
+
lines.push(chalk.gray(` Topic: ${report.siteTopic.topic} \u2014 ${report.siteTopic.description}`));
|
|
65
|
+
}
|
|
66
|
+
if (report.samplingInfo) {
|
|
67
|
+
const s = report.samplingInfo;
|
|
68
|
+
const confColor = s.confidence === "high" ? chalk.green : s.confidence === "medium" ? chalk.yellow : chalk.red;
|
|
69
|
+
lines.push(chalk.gray(` Pages: ${s.totalDiscovered} total, ${s.recentCount} recent (6mo), ${s.sampledCount} sampled (${s.samplePct}%) ${confColor(s.confidence + " confidence")}`));
|
|
70
|
+
}
|
|
71
|
+
if (report.siteType === "unsupported") {
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push(chalk.red.bold(` ${t("topic.unsupported_warning", lang, { type: report.siteTopic?.topic ?? "unknown" })}`));
|
|
74
|
+
}
|
|
40
75
|
lines.push("");
|
|
41
|
-
|
|
42
|
-
lines.push(chalk.
|
|
43
|
-
lines.push(chalk.gray(` Checked: ${report.timestamp}`));
|
|
76
|
+
const scoreColor = report.compositeScore >= 80 ? chalk.green.bold : report.compositeScore >= 50 ? chalk.yellow.bold : chalk.red.bold;
|
|
77
|
+
lines.push(chalk.bold(` ${t("report.composite_score", lang)}: `) + scoreColor(`${report.compositeScore}/100`));
|
|
44
78
|
lines.push("");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
const hardColor = report.hardStatus === "ready" ? chalk.green : report.hardStatus === "warn" ? chalk.yellow : chalk.red;
|
|
80
|
+
const hardLabel = report.hardStatus === "ready" ? "PASS" : report.hardStatus === "warn" ? "WARN" : "FAIL";
|
|
81
|
+
lines.push(chalk.bold(` \u250C\u2500 ${t("report.hard_requirements", lang)} `) + chalk.gray("\u2500".repeat(Math.max(0, 40 - t("report.hard_requirements", lang).length))) + ` ${hardColor.bold(hardLabel)}`);
|
|
82
|
+
for (const cat of report.hardCategories) {
|
|
83
|
+
const catScore = categoryScore(cat);
|
|
84
|
+
const catIcon = cat.items.every((i) => i.status === "pass") ? ICONS.pass : cat.items.some((i) => i.status === "fail") ? ICONS.fail : ICONS.warn;
|
|
85
|
+
for (const item of cat.items) {
|
|
86
|
+
lines.push(` \u2502 ${ICONS[item.status]} ${chalk.bold(item.name.padEnd(16))} ${item.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const hardStatusKey = `report.hard.${report.hardStatus}`;
|
|
90
|
+
const hardWarnCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "warn").length;
|
|
91
|
+
const hardFailCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "fail").length;
|
|
92
|
+
const hardStatusMsg = report.hardStatus === "ready" ? t(hardStatusKey, lang) : t(hardStatusKey, lang, { count: report.hardStatus === "fail" ? hardFailCount : hardWarnCount });
|
|
93
|
+
lines.push(chalk.gray(` \u2502`));
|
|
94
|
+
lines.push(` \u2514\u2500 ${t("report.score", lang)}: ${hardStatusMsg}`);
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push(chalk.bold(` \u250C\u2500 ${t("report.soft_scoring", lang)} `) + chalk.gray("\u2500".repeat(Math.max(0, 40 - t("report.soft_scoring", lang).length))) + ` ${scoreColor(report.softScore + "/100")}`);
|
|
97
|
+
for (const cat of report.softCategories) {
|
|
98
|
+
const score = categoryScore(cat);
|
|
99
|
+
const bar = renderBar(score, 100);
|
|
100
|
+
const pct = `${score}%`;
|
|
101
|
+
lines.push(` \u2502 ${bar} ${pct.padStart(4)} ${cat.name}`);
|
|
102
|
+
}
|
|
103
|
+
if (report.warningPenalty > 0) {
|
|
104
|
+
lines.push(chalk.gray(` \u2502`));
|
|
105
|
+
lines.push(chalk.yellow(` \u2502 \u26A0 ${t("report.warning_ratio", lang, { count: report.warned, total: report.totalChecks, pct: Math.round(report.warningRatio * 100) })} \u2192 ${t("report.warning_penalty", lang, { points: report.warningPenalty })}`));
|
|
106
|
+
}
|
|
107
|
+
lines.push(chalk.gray(` \u2502`));
|
|
108
|
+
const hardContrib = Math.round(report.hardStatus === "ready" ? 100 * 0.4 : report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "pass").length / Math.max(1, report.hardCategories.flatMap((c) => c.items).length) * 100 * 0.4);
|
|
109
|
+
const softContrib = Math.round(report.softScore * 0.6);
|
|
110
|
+
lines.push(chalk.gray(` \u2502 Hard ${Math.round(hardContrib)}% \xD7 0.4 + Soft ${report.softScore}% \xD7 0.6 - Penalty ${report.warningPenalty} = ${report.compositeScore}`));
|
|
111
|
+
lines.push(chalk.gray(` \u2514\u2500`));
|
|
112
|
+
lines.push("");
|
|
113
|
+
if (report.categoryScores.length > 0) {
|
|
114
|
+
for (const cs of report.categoryScores) {
|
|
115
|
+
const bar = renderBar(cs.score, cs.maxScore);
|
|
116
|
+
const pct = cs.maxScore > 0 ? Math.round(cs.score / cs.maxScore * 100) : 0;
|
|
117
|
+
lines.push(` ${bar} ${pct}% ${cs.name}`);
|
|
118
|
+
}
|
|
119
|
+
lines.push("");
|
|
120
|
+
}
|
|
121
|
+
for (const cat of report.categories) {
|
|
122
|
+
lines.push(chalk.bold(` ${cat.name}`));
|
|
123
|
+
for (const item of cat.items) {
|
|
124
|
+
lines.push(` ${ICONS[item.status]} [${LABELS[item.status]}] ${item.message}`);
|
|
125
|
+
if (item.detail) lines.push(chalk.gray(` ${item.detail}`));
|
|
54
126
|
}
|
|
55
127
|
lines.push("");
|
|
56
128
|
}
|
|
57
129
|
if (report.pages.length > 0) {
|
|
58
|
-
lines.push(chalk.bold("
|
|
59
|
-
lines.push(chalk.gray(` (${report.pages.length}
|
|
130
|
+
lines.push(chalk.bold(` ${t("report.page_details", lang)}`));
|
|
131
|
+
lines.push(chalk.gray(` (${t("report.pages", lang, { count: report.pages.length })})`));
|
|
60
132
|
lines.push("");
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
133
|
+
const problems = report.pages.filter((p) => p.contentStatus !== "pass" || p.issues.length > 0 || p.ai && p.ai.status !== "pass");
|
|
134
|
+
const ok = report.pages.filter((p) => p.contentStatus === "pass" && p.issues.length === 0 && (!p.ai || p.ai.status === "pass"));
|
|
135
|
+
for (const p of problems) renderPage(lines, p, lang);
|
|
136
|
+
if (ok.length > 0) lines.push(chalk.gray(` ${t("report.pages_ok", lang, { count: ok.length })}`));
|
|
137
|
+
lines.push("");
|
|
138
|
+
}
|
|
139
|
+
lines.push(chalk.bold(` ${t("report.score", lang)}: `) + `${report.score}/${report.totalChecks}`);
|
|
140
|
+
if (report.hardStatus === "fail") {
|
|
141
|
+
lines.push(chalk.red.bold(` ${t("report.notready", lang, { count: hardFailCount })}`));
|
|
142
|
+
} else if (report.hardStatus === "warn") {
|
|
143
|
+
lines.push(chalk.yellow.bold(` ${t("report.hard.warn", lang, { count: hardWarnCount })}`));
|
|
144
|
+
} else if (report.warned > 0) {
|
|
145
|
+
lines.push(chalk.yellow.bold(` ${t("report.mostly", lang, { count: report.warned })}`));
|
|
146
|
+
} else {
|
|
147
|
+
lines.push(chalk.green.bold(` ${t("report.ready", lang)}`));
|
|
148
|
+
}
|
|
149
|
+
const hasAi = report.categories.some((c) => c.group === "soft" && c.name.includes("AI"));
|
|
150
|
+
if (!hasAi) {
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push(chalk.cyan(` \u{1F4A1} ${t("ai.suggest_enable", lang)}`));
|
|
74
153
|
}
|
|
75
|
-
lines.push(chalk.bold(" Score: ") + `${report.score}/${report.totalChecks}`);
|
|
76
|
-
lines.push(` Status: ${getStatusSummary(report)}`);
|
|
77
154
|
lines.push("");
|
|
78
155
|
return lines.join("\n");
|
|
79
156
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
157
|
+
var PAGE_TYPE_ICONS = {
|
|
158
|
+
homepage: chalk.cyan("*"),
|
|
159
|
+
content: chalk.green("A"),
|
|
160
|
+
game_detail: chalk.blue("G"),
|
|
161
|
+
required: chalk.yellow("!"),
|
|
162
|
+
listing: chalk.gray("L"),
|
|
163
|
+
utility: chalk.gray("#"),
|
|
164
|
+
unknown: chalk.gray("?")
|
|
165
|
+
};
|
|
166
|
+
function renderPage(lines, page, lang) {
|
|
167
|
+
const path = (() => {
|
|
168
|
+
try {
|
|
169
|
+
return new URL(page.url).pathname;
|
|
170
|
+
} catch {
|
|
171
|
+
return page.url;
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
83
174
|
const ratioColor = page.contentRatio >= 50 ? chalk.green : page.contentRatio >= 30 ? chalk.yellow : chalk.red;
|
|
84
|
-
|
|
175
|
+
const scoreColor = page.score >= 80 ? chalk.green : page.score >= 50 ? chalk.yellow : chalk.red;
|
|
176
|
+
const typeIcon = PAGE_TYPE_ICONS[page.pageType] || chalk.gray("?");
|
|
177
|
+
lines.push(` ${ICONS[page.contentStatus]} ${typeIcon} ${chalk.bold(path)} ${scoreColor(page.score + "/100")}`);
|
|
85
178
|
lines.push(chalk.gray(` ${page.title}`));
|
|
86
|
-
lines.push(`
|
|
87
|
-
for (const issue of page.issues) {
|
|
88
|
-
lines.push(chalk.yellow(` ! ${issue}`));
|
|
89
|
-
}
|
|
179
|
+
lines.push(` ${t("report.content_label", lang)} ${ratioColor(page.contentRatio + "%")} (${page.contentChars}/${page.totalChars})`);
|
|
180
|
+
for (const issue of page.issues) lines.push(chalk.yellow(` ! ${issue}`));
|
|
90
181
|
if (page.ai) {
|
|
91
|
-
|
|
92
|
-
lines.push(`
|
|
93
|
-
for (const s of page.ai.suggestions.slice(0, 2)) {
|
|
94
|
-
lines.push(chalk.gray(` \u2192 ${truncate(s, 70)}`));
|
|
95
|
-
}
|
|
182
|
+
lines.push(` ${ICONS[page.ai.status]} AI: ${truncate(page.ai.assessment, 80)}`);
|
|
183
|
+
for (const s of page.ai.suggestions.slice(0, 2)) lines.push(chalk.gray(` -> ${truncate(s, 70)}`));
|
|
96
184
|
}
|
|
97
185
|
lines.push("");
|
|
98
186
|
}
|
|
99
|
-
function safePath(url) {
|
|
100
|
-
try {
|
|
101
|
-
return new URL(url).pathname;
|
|
102
|
-
} catch {
|
|
103
|
-
return url;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
187
|
function truncate(s, max) {
|
|
107
188
|
return s.length > max ? s.slice(0, max - 1) + "..." : s;
|
|
108
189
|
}
|
|
@@ -111,11 +192,10 @@ function renderJsonReport(report) {
|
|
|
111
192
|
}
|
|
112
193
|
|
|
113
194
|
// src/cli.ts
|
|
114
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
115
195
|
function formatTimestamp() {
|
|
116
196
|
const d = /* @__PURE__ */ new Date();
|
|
117
|
-
const
|
|
118
|
-
return `${d.getFullYear()}${
|
|
197
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
198
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
119
199
|
}
|
|
120
200
|
function getDomain(url) {
|
|
121
201
|
try {
|
|
@@ -125,53 +205,100 @@ function getDomain(url) {
|
|
|
125
205
|
}
|
|
126
206
|
}
|
|
127
207
|
var program = new Command();
|
|
128
|
-
program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output
|
|
208
|
+
program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output JSON to stdout").option("-n, --max-crawl <number>", "Total page crawl limit (Phase 1 + 2)", "50").option("-m, --page-limit <number>", "Max structural pages to crawl (Phase 1)", "50").option("-c, --content-limit <number>", "Max content pages to crawl (Phase 2)", "20").option("--sample-min <number>", "Min content pages to sample", "20").option("--sample-ratio <ratio>", "Content page sampling ratio (0-1)", "0.2").option("--ai", "Enable AI content quality analysis", false).option("-t, --timeout <ms>", "Page load timeout", "30000").option("--api-key <key>", "AI API key").option("-o, --output <dir>", "Report output directory", "tmp").option("--no-save", "Skip auto-saving report").option("-l, --lang <lang>", `Output language (${getSupportedLangs().join("|")})`, "en").option("--type <type>", "Force site type (content|tool|game), skip auto-detection").option("--detect-only", "Only detect site type/topic, skip full check").action(async (url, opts) => {
|
|
129
209
|
try {
|
|
130
210
|
new URL(url);
|
|
131
211
|
} catch {
|
|
132
212
|
console.error(chalk2.red(`Error: Invalid URL "${url}"`));
|
|
133
213
|
process.exit(1);
|
|
134
214
|
}
|
|
135
|
-
if (!url.startsWith("http"))
|
|
136
|
-
|
|
215
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
216
|
+
const lang = isValidLang(opts.lang) ? opts.lang : "en";
|
|
217
|
+
const validTypes = ["content", "tool", "game"];
|
|
218
|
+
const siteType = validTypes.includes(opts.type) ? opts.type : void 0;
|
|
219
|
+
if (opts.detectOnly) {
|
|
220
|
+
const browser = new BrowserManager();
|
|
221
|
+
try {
|
|
222
|
+
process.stderr.write(chalk2.cyan(`\u25CF Detecting site type for ${url}...
|
|
223
|
+
`));
|
|
224
|
+
const page = await browser.newPage();
|
|
225
|
+
const data = await fetchPage(page, url, parseInt(opts.timeout, 10));
|
|
226
|
+
await page.close();
|
|
227
|
+
const domResult = detectSiteType([data.signals], data.navText + " " + data.footerText, siteType);
|
|
228
|
+
process.stderr.write(chalk2.gray(` DOM detection: ${domResult.type} (${domResult.confidence})
|
|
229
|
+
`));
|
|
230
|
+
const apiKey = opts.apiKey || process.env.AI_API_KEY;
|
|
231
|
+
if (opts.ai && apiKey) {
|
|
232
|
+
process.stderr.write(chalk2.gray(" AI: analyzing topic...\n"));
|
|
233
|
+
const topic = await analyzeSiteTopic(
|
|
234
|
+
{ title: data.title, text: data.text, navText: data.navText + " " + data.footerText },
|
|
235
|
+
lang,
|
|
236
|
+
apiKey
|
|
237
|
+
);
|
|
238
|
+
console.log(JSON.stringify({
|
|
239
|
+
domType: domResult.type,
|
|
240
|
+
domConfidence: domResult.confidence,
|
|
241
|
+
aiType: topic.type,
|
|
242
|
+
topic: topic.topic,
|
|
243
|
+
description: topic.description,
|
|
244
|
+
confidence: topic.confidence,
|
|
245
|
+
reasoning: topic.reasoning
|
|
246
|
+
}, null, 2));
|
|
247
|
+
} else {
|
|
248
|
+
console.log(JSON.stringify({
|
|
249
|
+
type: domResult.type,
|
|
250
|
+
confidence: domResult.confidence,
|
|
251
|
+
signals: domResult.signals
|
|
252
|
+
}, null, 2));
|
|
253
|
+
}
|
|
254
|
+
await browser.close();
|
|
255
|
+
process.exit(0);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
await browser.close();
|
|
258
|
+
console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
259
|
+
process.exit(2);
|
|
260
|
+
}
|
|
137
261
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const interval = setInterval(() => {
|
|
141
|
-
process.stderr.write(`\r${chalk2.cyan(spinner[frame++ % spinner.length])} Checking ${url}...`);
|
|
142
|
-
}, 80);
|
|
262
|
+
process.stderr.write(chalk2.cyan(`\u25CF Checking ${url}...
|
|
263
|
+
`));
|
|
143
264
|
try {
|
|
265
|
+
let lastProgress = "";
|
|
144
266
|
const report = await check({
|
|
145
267
|
url,
|
|
146
|
-
|
|
147
|
-
|
|
268
|
+
maxCrawl: parseInt(opts.maxCrawl, 10),
|
|
269
|
+
maxPages: parseInt(opts.pageLimit, 10),
|
|
270
|
+
maxContent: parseInt(opts.contentLimit, 10),
|
|
271
|
+
sampleMin: parseInt(opts.sampleMin, 10),
|
|
272
|
+
sampleRatio: parseFloat(opts.sampleRatio),
|
|
273
|
+
siteType,
|
|
274
|
+
skipAi: !opts.ai,
|
|
148
275
|
timeout: parseInt(opts.timeout, 10),
|
|
149
|
-
apiKey: opts.apiKey
|
|
276
|
+
apiKey: opts.apiKey,
|
|
277
|
+
lang,
|
|
278
|
+
onProgress: (msg) => {
|
|
279
|
+
lastProgress = msg;
|
|
280
|
+
const line = `\r${chalk2.cyan("\u25CF")} ${chalk2.gray(msg)}`;
|
|
281
|
+
process.stderr.write(line + " ".repeat(Math.max(0, 60 - msg.length)));
|
|
282
|
+
}
|
|
150
283
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.log(renderJsonReport(report));
|
|
155
|
-
} else {
|
|
156
|
-
console.log(renderTerminalReport(report));
|
|
157
|
-
}
|
|
284
|
+
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
285
|
+
if (opts.json) console.log(renderJsonReport(report));
|
|
286
|
+
else console.log(renderTerminalReport(report));
|
|
158
287
|
if (opts.save !== false) {
|
|
159
288
|
const ts = formatTimestamp();
|
|
160
289
|
const domain = getDomain(url);
|
|
161
290
|
const outDir = join(process.cwd(), opts.output);
|
|
162
291
|
try {
|
|
163
292
|
mkdirSync(outDir, { recursive: true });
|
|
164
|
-
const
|
|
165
|
-
writeFileSync(
|
|
166
|
-
console.log(chalk2.gray(`
|
|
167
|
-
} catch
|
|
168
|
-
console.error(chalk2.yellow(` Warning: Failed to save report: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`));
|
|
293
|
+
const path = join(outDir, `${domain}-${ts}.json`);
|
|
294
|
+
writeFileSync(path, renderJsonReport(report), "utf-8");
|
|
295
|
+
console.log(chalk2.gray(` ${t("report.saved", lang)}: ${path}`));
|
|
296
|
+
} catch {
|
|
169
297
|
}
|
|
170
298
|
}
|
|
171
299
|
process.exit(report.failed > 0 ? 1 : 0);
|
|
172
300
|
} catch (err) {
|
|
173
|
-
|
|
174
|
-
process.stderr.write("\r" + " ".repeat(60) + "\r");
|
|
301
|
+
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
175
302
|
console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
176
303
|
process.exit(2);
|
|
177
304
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,65 @@
|
|
|
1
1
|
type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
|
|
2
|
+
type Lang = string;
|
|
3
|
+
type SiteType = 'content' | 'tool' | 'game' | 'unsupported';
|
|
4
|
+
type PageType = 'homepage' | 'content' | 'game_detail' | 'required' | 'listing' | 'utility' | 'unknown';
|
|
2
5
|
interface CheckItem {
|
|
3
6
|
name: string;
|
|
4
7
|
status: CheckStatus;
|
|
5
8
|
message: string;
|
|
6
9
|
detail?: string;
|
|
7
10
|
}
|
|
11
|
+
type CheckGroup = 'hard' | 'soft';
|
|
8
12
|
interface CheckCategory {
|
|
9
13
|
name: string;
|
|
10
14
|
items: CheckItem[];
|
|
15
|
+
group?: CheckGroup;
|
|
11
16
|
}
|
|
12
17
|
interface PageDetail {
|
|
13
18
|
url: string;
|
|
14
19
|
title: string;
|
|
20
|
+
pageType: PageType;
|
|
15
21
|
totalChars: number;
|
|
16
22
|
contentChars: number;
|
|
17
23
|
contentRatio: number;
|
|
18
24
|
contentStatus: CheckStatus;
|
|
19
25
|
issues: string[];
|
|
26
|
+
score: number;
|
|
27
|
+
relevance?: 'relevant' | 'tangential' | 'off-topic';
|
|
20
28
|
ai?: {
|
|
21
29
|
status: CheckStatus;
|
|
22
30
|
assessment: string;
|
|
23
31
|
suggestions: string[];
|
|
24
32
|
};
|
|
25
33
|
}
|
|
34
|
+
interface SiteTopic {
|
|
35
|
+
type: SiteType;
|
|
36
|
+
topic: string;
|
|
37
|
+
description: string;
|
|
38
|
+
confidence: 'high' | 'medium' | 'low';
|
|
39
|
+
reasoning: string;
|
|
40
|
+
}
|
|
41
|
+
interface CategoryScore {
|
|
42
|
+
name: string;
|
|
43
|
+
score: number;
|
|
44
|
+
maxScore: number;
|
|
45
|
+
}
|
|
26
46
|
interface CheckReport {
|
|
27
47
|
url: string;
|
|
28
48
|
timestamp: string;
|
|
49
|
+
lang: Lang;
|
|
50
|
+
siteType: SiteType;
|
|
51
|
+
siteTypeConfidence: 'high' | 'medium' | 'low';
|
|
52
|
+
siteTopic?: SiteTopic;
|
|
53
|
+
samplingInfo?: {
|
|
54
|
+
totalDiscovered: number;
|
|
55
|
+
recentCount: number;
|
|
56
|
+
sampledCount: number;
|
|
57
|
+
samplePct: number;
|
|
58
|
+
confidence: 'high' | 'medium' | 'low';
|
|
59
|
+
};
|
|
29
60
|
categories: CheckCategory[];
|
|
61
|
+
hardCategories: CheckCategory[];
|
|
62
|
+
softCategories: CheckCategory[];
|
|
30
63
|
score: number;
|
|
31
64
|
totalChecks: number;
|
|
32
65
|
passed: number;
|
|
@@ -34,13 +67,26 @@ interface CheckReport {
|
|
|
34
67
|
failed: number;
|
|
35
68
|
skipped: number;
|
|
36
69
|
pages: PageDetail[];
|
|
70
|
+
compositeScore: number;
|
|
71
|
+
categoryScores: CategoryScore[];
|
|
72
|
+
hardStatus: 'ready' | 'warn' | 'fail';
|
|
73
|
+
softScore: number;
|
|
74
|
+
warningRatio: number;
|
|
75
|
+
warningPenalty: number;
|
|
37
76
|
}
|
|
38
77
|
interface CheckOptions {
|
|
39
78
|
url: string;
|
|
40
|
-
|
|
79
|
+
maxCrawl?: number;
|
|
80
|
+
maxPages?: number;
|
|
81
|
+
maxContent?: number;
|
|
82
|
+
sampleMin?: number;
|
|
83
|
+
sampleRatio?: number;
|
|
84
|
+
siteType?: SiteType;
|
|
41
85
|
skipAi?: boolean;
|
|
42
86
|
timeout?: number;
|
|
43
87
|
apiKey?: string;
|
|
88
|
+
lang?: Lang;
|
|
89
|
+
onProgress?: (message: string) => void;
|
|
44
90
|
}
|
|
45
91
|
|
|
46
92
|
declare function check(options: CheckOptions): Promise<CheckReport>;
|
package/dist/index.js
CHANGED