@cloudcreate/adsense-check 1.0.1 → 1.2.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-SES7MRFF.js +1748 -0
- package/dist/cli.js +319 -92
- package/dist/index.d.ts +48 -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,196 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
BrowserManager,
|
|
4
|
+
analyzeSiteTopic,
|
|
5
|
+
analyzeWithAI,
|
|
6
|
+
check,
|
|
7
|
+
computePageAiScore,
|
|
8
|
+
detectSiteType,
|
|
9
|
+
fetchPage,
|
|
10
|
+
getSupportedLangs,
|
|
11
|
+
isValidLang,
|
|
12
|
+
t
|
|
13
|
+
} from "./chunk-SES7MRFF.js";
|
|
5
14
|
|
|
6
15
|
// src/cli.ts
|
|
7
16
|
import "dotenv/config";
|
|
8
17
|
import { Command } from "commander";
|
|
9
18
|
import chalk2 from "chalk";
|
|
10
19
|
import { mkdirSync, writeFileSync } from "fs";
|
|
11
|
-
import { join
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
20
|
+
import { join } from "path";
|
|
13
21
|
|
|
14
22
|
// src/reporter.ts
|
|
15
23
|
import chalk from "chalk";
|
|
16
24
|
import figures from "figures";
|
|
17
|
-
var
|
|
25
|
+
var ICONS = {
|
|
18
26
|
pass: chalk.green(figures.tick),
|
|
19
27
|
warn: chalk.yellow(figures.warning),
|
|
20
28
|
fail: chalk.red(figures.cross),
|
|
21
29
|
skip: chalk.gray("-")
|
|
22
30
|
};
|
|
23
|
-
var
|
|
31
|
+
var LABELS = {
|
|
24
32
|
pass: chalk.green("PASS"),
|
|
25
33
|
warn: chalk.yellow("WARN"),
|
|
26
34
|
fail: chalk.red("FAIL"),
|
|
27
35
|
skip: chalk.gray("SKIP")
|
|
28
36
|
};
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
function renderBar(score, max, width = 20) {
|
|
38
|
+
const ratio = max > 0 ? score / max : 0;
|
|
39
|
+
const filled = Math.round(ratio * width);
|
|
40
|
+
const empty = width - filled;
|
|
41
|
+
const color = ratio >= 0.8 ? chalk.green : ratio >= 0.5 ? chalk.yellow : chalk.red;
|
|
42
|
+
return color("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
|
|
43
|
+
}
|
|
44
|
+
function categoryScore(cat) {
|
|
45
|
+
if (cat.items.length === 0) return 100;
|
|
46
|
+
const earned = cat.items.reduce((s, i) => {
|
|
47
|
+
if (i.status === "pass") return s + 100;
|
|
48
|
+
if (i.status === "warn") return s + 40;
|
|
49
|
+
return s;
|
|
50
|
+
}, 0);
|
|
51
|
+
return Math.round(earned / cat.items.length);
|
|
37
52
|
}
|
|
38
53
|
function renderTerminalReport(report) {
|
|
39
|
-
const
|
|
54
|
+
const lang = report.lang;
|
|
55
|
+
const typeKey = `detector.type.${report.siteType}`;
|
|
56
|
+
const typeLabel = t(typeKey, lang);
|
|
57
|
+
const confidenceLabel = report.siteTypeConfidence === "high" ? "" : ` (${report.siteTypeConfidence})`;
|
|
58
|
+
const lines = [
|
|
59
|
+
"",
|
|
60
|
+
chalk.bold.cyan(` ${t("report.title", lang)}`),
|
|
61
|
+
chalk.gray(` URL: ${report.url}`),
|
|
62
|
+
chalk.gray(` Time: ${report.timestamp}`),
|
|
63
|
+
chalk.gray(` Site type: ${typeLabel}${confidenceLabel}`)
|
|
64
|
+
];
|
|
65
|
+
if (report.siteTopic) {
|
|
66
|
+
lines.push(chalk.gray(` Topic: ${report.siteTopic.topic} \u2014 ${report.siteTopic.description}`));
|
|
67
|
+
}
|
|
68
|
+
if (report.samplingInfo) {
|
|
69
|
+
const s = report.samplingInfo;
|
|
70
|
+
const confColor = s.confidence === "high" ? chalk.green : s.confidence === "medium" ? chalk.yellow : chalk.red;
|
|
71
|
+
lines.push(chalk.gray(` Pages: ${s.totalDiscovered} total, ${s.recentCount} recent (6mo), ${s.sampledCount} sampled (${s.samplePct}%) ${confColor(s.confidence + " confidence")}`));
|
|
72
|
+
}
|
|
73
|
+
if (report.siteType === "unsupported") {
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(chalk.red.bold(` ${t("topic.unsupported_warning", lang, { type: report.siteTopic?.topic ?? "unknown" })}`));
|
|
76
|
+
}
|
|
40
77
|
lines.push("");
|
|
41
|
-
|
|
42
|
-
lines.push(chalk.
|
|
43
|
-
lines.push(chalk.gray(` Checked: ${report.timestamp}`));
|
|
78
|
+
const scoreColor = report.compositeScore >= 80 ? chalk.green.bold : report.compositeScore >= 50 ? chalk.yellow.bold : chalk.red.bold;
|
|
79
|
+
lines.push(chalk.bold(` ${t("report.composite_score", lang)}: `) + scoreColor(`${report.compositeScore}/100`));
|
|
44
80
|
lines.push("");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
const hardColor = report.hardStatus === "ready" ? chalk.green : report.hardStatus === "warn" ? chalk.yellow : chalk.red;
|
|
82
|
+
const hardLabel = report.hardStatus === "ready" ? "PASS" : report.hardStatus === "warn" ? "WARN" : "FAIL";
|
|
83
|
+
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)}`);
|
|
84
|
+
for (const cat of report.hardCategories) {
|
|
85
|
+
const catScore = categoryScore(cat);
|
|
86
|
+
const catIcon = cat.items.every((i) => i.status === "pass") ? ICONS.pass : cat.items.some((i) => i.status === "fail") ? ICONS.fail : ICONS.warn;
|
|
87
|
+
for (const item of cat.items) {
|
|
88
|
+
lines.push(` \u2502 ${ICONS[item.status]} ${chalk.bold(item.name.padEnd(16))} ${item.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const hardStatusKey = `report.hard.${report.hardStatus}`;
|
|
92
|
+
const hardWarnCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "warn").length;
|
|
93
|
+
const hardFailCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "fail").length;
|
|
94
|
+
const hardStatusMsg = report.hardStatus === "ready" ? t(hardStatusKey, lang) : t(hardStatusKey, lang, { count: report.hardStatus === "fail" ? hardFailCount : hardWarnCount });
|
|
95
|
+
lines.push(chalk.gray(` \u2502`));
|
|
96
|
+
lines.push(` \u2514\u2500 ${t("report.score", lang)}: ${hardStatusMsg}`);
|
|
97
|
+
lines.push("");
|
|
98
|
+
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")}`);
|
|
99
|
+
for (const cat of report.softCategories) {
|
|
100
|
+
const isAiCat = cat.name.includes("AI") || cat.name.includes("ai");
|
|
101
|
+
const score = isAiCat && report.siteAiScore > 0 ? report.siteAiScore : categoryScore(cat);
|
|
102
|
+
const bar = renderBar(score, 100);
|
|
103
|
+
const pct = `${score}%`;
|
|
104
|
+
lines.push(` \u2502 ${bar} ${pct.padStart(4)} ${cat.name}`);
|
|
105
|
+
}
|
|
106
|
+
if (report.warningPenalty > 0) {
|
|
107
|
+
lines.push(chalk.gray(` \u2502`));
|
|
108
|
+
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 })}`));
|
|
109
|
+
}
|
|
110
|
+
lines.push(chalk.gray(` \u2502`));
|
|
111
|
+
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);
|
|
112
|
+
const softContrib = Math.round(report.softScore * 0.6);
|
|
113
|
+
lines.push(chalk.gray(` \u2502 Hard ${Math.round(hardContrib)}% \xD7 0.4 + Soft ${report.softScore}% \xD7 0.6 - Penalty ${report.warningPenalty} = ${report.compositeScore}`));
|
|
114
|
+
if (report.siteAiScore > 0) {
|
|
115
|
+
lines.push(chalk.gray(` \u2502 AI Value Score: ${report.siteAiScore}/100 (geometric mean \xD7 page-type weights)`));
|
|
116
|
+
}
|
|
117
|
+
lines.push(chalk.gray(` \u2514\u2500`));
|
|
118
|
+
lines.push("");
|
|
119
|
+
if (report.categoryScores.length > 0) {
|
|
120
|
+
for (const cs of report.categoryScores) {
|
|
121
|
+
const isAiCat = cs.name.includes("AI") || cs.name.includes("ai");
|
|
122
|
+
const pct = isAiCat && report.siteAiScore > 0 ? report.siteAiScore : cs.maxScore > 0 ? Math.round(cs.score / cs.maxScore * 100) : 0;
|
|
123
|
+
const bar = renderBar(pct, 100);
|
|
124
|
+
lines.push(` ${bar} ${pct}% ${cs.name}`);
|
|
125
|
+
}
|
|
126
|
+
lines.push("");
|
|
127
|
+
}
|
|
128
|
+
for (const cat of report.categories) {
|
|
129
|
+
lines.push(chalk.bold(` ${cat.name}`));
|
|
130
|
+
for (const item of cat.items) {
|
|
131
|
+
lines.push(` ${ICONS[item.status]} [${LABELS[item.status]}] ${item.message}`);
|
|
132
|
+
if (item.detail) lines.push(chalk.gray(` ${item.detail}`));
|
|
54
133
|
}
|
|
55
134
|
lines.push("");
|
|
56
135
|
}
|
|
57
136
|
if (report.pages.length > 0) {
|
|
58
|
-
lines.push(chalk.bold("
|
|
59
|
-
lines.push(chalk.gray(` (${report.pages.length}
|
|
137
|
+
lines.push(chalk.bold(` ${t("report.page_details", lang)}`));
|
|
138
|
+
lines.push(chalk.gray(` (${t("report.pages", lang, { count: report.pages.length })})`));
|
|
60
139
|
lines.push("");
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
140
|
+
const problems = report.pages.filter((p) => p.contentStatus !== "pass" || p.issues.length > 0 || p.ai && p.ai.status !== "pass");
|
|
141
|
+
const ok = report.pages.filter((p) => p.contentStatus === "pass" && p.issues.length === 0 && (!p.ai || p.ai.status === "pass"));
|
|
142
|
+
for (const p of problems) renderPage(lines, p, lang);
|
|
143
|
+
if (ok.length > 0) lines.push(chalk.gray(` ${t("report.pages_ok", lang, { count: ok.length })}`));
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
lines.push(chalk.bold(` ${t("report.score", lang)}: `) + `${report.score}/${report.totalChecks}`);
|
|
147
|
+
if (report.hardStatus === "fail") {
|
|
148
|
+
lines.push(chalk.red.bold(` ${t("report.notready", lang, { count: hardFailCount })}`));
|
|
149
|
+
} else if (report.hardStatus === "warn") {
|
|
150
|
+
lines.push(chalk.yellow.bold(` ${t("report.hard.warn", lang, { count: hardWarnCount })}`));
|
|
151
|
+
} else if (report.warned > 0) {
|
|
152
|
+
lines.push(chalk.yellow.bold(` ${t("report.mostly", lang, { count: report.warned })}`));
|
|
153
|
+
} else {
|
|
154
|
+
lines.push(chalk.green.bold(` ${t("report.ready", lang)}`));
|
|
155
|
+
}
|
|
156
|
+
const hasAi = report.categories.some((c) => c.group === "soft" && (c.name.includes("AI") || c.name.includes("ai")));
|
|
157
|
+
if (!hasAi) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push(chalk.cyan(` \u{1F4A1} ${t("ai.suggest_enable", lang)}`));
|
|
74
160
|
}
|
|
75
|
-
lines.push(chalk.bold(" Score: ") + `${report.score}/${report.totalChecks}`);
|
|
76
|
-
lines.push(` Status: ${getStatusSummary(report)}`);
|
|
77
161
|
lines.push("");
|
|
78
162
|
return lines.join("\n");
|
|
79
163
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
164
|
+
var PAGE_TYPE_ICONS = {
|
|
165
|
+
homepage: chalk.cyan("*"),
|
|
166
|
+
content: chalk.green("A"),
|
|
167
|
+
game_detail: chalk.blue("G"),
|
|
168
|
+
required: chalk.yellow("!"),
|
|
169
|
+
listing: chalk.gray("L"),
|
|
170
|
+
utility: chalk.gray("#"),
|
|
171
|
+
unknown: chalk.gray("?")
|
|
172
|
+
};
|
|
173
|
+
function renderPage(lines, page, lang) {
|
|
174
|
+
const path = (() => {
|
|
175
|
+
try {
|
|
176
|
+
return new URL(page.url).pathname;
|
|
177
|
+
} catch {
|
|
178
|
+
return page.url;
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
83
181
|
const ratioColor = page.contentRatio >= 50 ? chalk.green : page.contentRatio >= 30 ? chalk.yellow : chalk.red;
|
|
84
|
-
|
|
182
|
+
const scoreColor = page.score >= 80 ? chalk.green : page.score >= 50 ? chalk.yellow : chalk.red;
|
|
183
|
+
const typeIcon = PAGE_TYPE_ICONS[page.pageType] || chalk.gray("?");
|
|
184
|
+
lines.push(` ${ICONS[page.contentStatus]} ${typeIcon} ${chalk.bold(path)} ${scoreColor(page.score + "/100")}`);
|
|
85
185
|
lines.push(chalk.gray(` ${page.title}`));
|
|
86
|
-
lines.push(`
|
|
87
|
-
for (const issue of page.issues) {
|
|
88
|
-
lines.push(chalk.yellow(` ! ${issue}`));
|
|
89
|
-
}
|
|
186
|
+
lines.push(` ${t("report.content_label", lang)} ${ratioColor(page.contentRatio + "%")} (${page.contentChars}/${page.totalChars})`);
|
|
187
|
+
for (const issue of page.issues) lines.push(chalk.yellow(` ! ${issue}`));
|
|
90
188
|
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
|
-
}
|
|
189
|
+
lines.push(` ${ICONS[page.ai.status]} AI: ${truncate(page.ai.assessment, 80)}`);
|
|
190
|
+
for (const s of page.ai.suggestions.slice(0, 2)) lines.push(chalk.gray(` -> ${truncate(s, 70)}`));
|
|
96
191
|
}
|
|
97
192
|
lines.push("");
|
|
98
193
|
}
|
|
99
|
-
function safePath(url) {
|
|
100
|
-
try {
|
|
101
|
-
return new URL(url).pathname;
|
|
102
|
-
} catch {
|
|
103
|
-
return url;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
194
|
function truncate(s, max) {
|
|
107
195
|
return s.length > max ? s.slice(0, max - 1) + "..." : s;
|
|
108
196
|
}
|
|
@@ -111,11 +199,10 @@ function renderJsonReport(report) {
|
|
|
111
199
|
}
|
|
112
200
|
|
|
113
201
|
// src/cli.ts
|
|
114
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
115
202
|
function formatTimestamp() {
|
|
116
203
|
const d = /* @__PURE__ */ new Date();
|
|
117
|
-
const
|
|
118
|
-
return `${d.getFullYear()}${
|
|
204
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
205
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
119
206
|
}
|
|
120
207
|
function getDomain(url) {
|
|
121
208
|
try {
|
|
@@ -125,53 +212,193 @@ function getDomain(url) {
|
|
|
125
212
|
}
|
|
126
213
|
}
|
|
127
214
|
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
|
|
215
|
+
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").option("--page <url>", "Analyze a single page value (AI four-dimension scoring)").action(async (url, opts) => {
|
|
129
216
|
try {
|
|
130
217
|
new URL(url);
|
|
131
218
|
} catch {
|
|
132
219
|
console.error(chalk2.red(`Error: Invalid URL "${url}"`));
|
|
133
220
|
process.exit(1);
|
|
134
221
|
}
|
|
135
|
-
if (!url.startsWith("http"))
|
|
136
|
-
|
|
222
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
223
|
+
const lang = isValidLang(opts.lang) ? opts.lang : "en";
|
|
224
|
+
const validTypes = ["content", "tool", "game"];
|
|
225
|
+
const siteType = validTypes.includes(opts.type) ? opts.type : void 0;
|
|
226
|
+
if (opts.detectOnly) {
|
|
227
|
+
const browser = new BrowserManager();
|
|
228
|
+
try {
|
|
229
|
+
process.stderr.write(chalk2.cyan(`\u25CF Detecting site type for ${url}...
|
|
230
|
+
`));
|
|
231
|
+
const page = await browser.newPage();
|
|
232
|
+
const data = await fetchPage(page, url, parseInt(opts.timeout, 10));
|
|
233
|
+
await page.close();
|
|
234
|
+
const domResult = detectSiteType([data.signals], data.navText + " " + data.footerText, siteType);
|
|
235
|
+
process.stderr.write(chalk2.gray(` DOM detection: ${domResult.type} (${domResult.confidence})
|
|
236
|
+
`));
|
|
237
|
+
const apiKey = opts.apiKey || process.env.AI_API_KEY;
|
|
238
|
+
if (opts.ai && apiKey) {
|
|
239
|
+
process.stderr.write(chalk2.gray(" AI: analyzing topic...\n"));
|
|
240
|
+
const topic = await analyzeSiteTopic(
|
|
241
|
+
{ title: data.title, text: data.text, navText: data.navText + " " + data.footerText },
|
|
242
|
+
lang,
|
|
243
|
+
apiKey
|
|
244
|
+
);
|
|
245
|
+
console.log(JSON.stringify({
|
|
246
|
+
domType: domResult.type,
|
|
247
|
+
domConfidence: domResult.confidence,
|
|
248
|
+
aiType: topic.type,
|
|
249
|
+
topic: topic.topic,
|
|
250
|
+
description: topic.description,
|
|
251
|
+
confidence: topic.confidence,
|
|
252
|
+
reasoning: topic.reasoning
|
|
253
|
+
}, null, 2));
|
|
254
|
+
} else {
|
|
255
|
+
console.log(JSON.stringify({
|
|
256
|
+
type: domResult.type,
|
|
257
|
+
confidence: domResult.confidence,
|
|
258
|
+
signals: domResult.signals
|
|
259
|
+
}, null, 2));
|
|
260
|
+
}
|
|
261
|
+
await browser.close();
|
|
262
|
+
process.exit(0);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
await browser.close();
|
|
265
|
+
console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
266
|
+
process.exit(2);
|
|
267
|
+
}
|
|
137
268
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
269
|
+
if (opts.page) {
|
|
270
|
+
const pageUrl = opts.page.startsWith("http") ? opts.page : "https://" + opts.page;
|
|
271
|
+
const apiKey = opts.apiKey || process.env.AI_API_KEY;
|
|
272
|
+
if (!apiKey) {
|
|
273
|
+
console.error(chalk2.red("Error: --page requires AI (--ai --api-key or AI_API_KEY env)"));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
const browser = new BrowserManager();
|
|
277
|
+
try {
|
|
278
|
+
process.stderr.write(chalk2.cyan(`\u25CF Analyzing page value: ${pageUrl}
|
|
279
|
+
`));
|
|
280
|
+
const pg = await browser.newPage();
|
|
281
|
+
const data = await fetchPage(pg, pageUrl, parseInt(opts.timeout, 10));
|
|
282
|
+
await pg.close();
|
|
283
|
+
process.stderr.write(chalk2.gray(" Detecting topic...\n"));
|
|
284
|
+
let siteTopic;
|
|
285
|
+
try {
|
|
286
|
+
siteTopic = await analyzeSiteTopic(
|
|
287
|
+
{ title: data.title, text: data.text, navText: data.navText + " " + data.footerText },
|
|
288
|
+
lang,
|
|
289
|
+
apiKey
|
|
290
|
+
);
|
|
291
|
+
process.stderr.write(chalk2.gray(` Topic: ${siteTopic.topic} (${siteTopic.type})
|
|
292
|
+
`));
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
process.stderr.write(chalk2.gray(" AI: analyzing page value...\n"));
|
|
296
|
+
const result = await analyzeWithAI(
|
|
297
|
+
[{ url: pageUrl, text: data.text }],
|
|
298
|
+
lang,
|
|
299
|
+
apiKey,
|
|
300
|
+
void 0,
|
|
301
|
+
siteTopic
|
|
302
|
+
);
|
|
303
|
+
const analysis = result.pageAnalyses[0];
|
|
304
|
+
if (!analysis) {
|
|
305
|
+
console.error(chalk2.red(" AI analysis returned no results"));
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
308
|
+
const score = computePageAiScore(analysis);
|
|
309
|
+
const v = analysis.valueScore ?? 5;
|
|
310
|
+
const o = analysis.originalityScore ?? 5;
|
|
311
|
+
const r = analysis.relevanceScore ?? 5;
|
|
312
|
+
const c = analysis.complianceScore ?? 5;
|
|
313
|
+
const dimColor = (s) => s >= 8 ? chalk2.green : s >= 5 ? chalk2.yellow : chalk2.red;
|
|
314
|
+
const scoreColor = score >= 70 ? chalk2.green : score >= 40 ? chalk2.yellow : chalk2.red;
|
|
315
|
+
const lines = [
|
|
316
|
+
"",
|
|
317
|
+
chalk2.bold.cyan(" Page Value Analysis"),
|
|
318
|
+
chalk2.gray(` URL: ${pageUrl}`),
|
|
319
|
+
chalk2.gray(` Title: ${data.title}`)
|
|
320
|
+
];
|
|
321
|
+
if (siteTopic) {
|
|
322
|
+
lines.push(chalk2.gray(` Site topic: ${siteTopic.topic} (${siteTopic.type})`));
|
|
323
|
+
}
|
|
324
|
+
lines.push("");
|
|
325
|
+
lines.push(` ${dimColor(v)("Value")} ${v}/10`);
|
|
326
|
+
lines.push(` ${dimColor(o)("Originality")} ${o}/10`);
|
|
327
|
+
lines.push(` ${dimColor(r)("Relevance")} ${r}/10`);
|
|
328
|
+
lines.push(` ${dimColor(c)("Compliance")} ${c}/10`);
|
|
329
|
+
lines.push("");
|
|
330
|
+
lines.push(chalk2.bold(` Overall: ${scoreColor(score + "/100")}`) + chalk2.gray(" (geometric mean)"));
|
|
331
|
+
lines.push("");
|
|
332
|
+
if (analysis.assessment) {
|
|
333
|
+
lines.push(chalk2.gray(` ${analysis.assessment}`));
|
|
334
|
+
lines.push("");
|
|
335
|
+
}
|
|
336
|
+
for (const s of analysis.suggestions) {
|
|
337
|
+
lines.push(chalk2.yellow(` -> ${s}`));
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
if (opts.json) {
|
|
341
|
+
console.log(JSON.stringify({
|
|
342
|
+
url: pageUrl,
|
|
343
|
+
title: data.title,
|
|
344
|
+
topic: siteTopic,
|
|
345
|
+
scores: { value: v, originality: o, relevance: r, compliance: c },
|
|
346
|
+
overall: score,
|
|
347
|
+
relevanceLabel: analysis.relevance,
|
|
348
|
+
assessment: analysis.assessment,
|
|
349
|
+
suggestions: analysis.suggestions
|
|
350
|
+
}, null, 2));
|
|
351
|
+
} else {
|
|
352
|
+
console.log(lines.join("\n"));
|
|
353
|
+
}
|
|
354
|
+
await browser.close();
|
|
355
|
+
process.exit(c <= 2 ? 1 : 0);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
await browser.close();
|
|
358
|
+
console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
359
|
+
process.exit(2);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
process.stderr.write(chalk2.cyan(`\u25CF Checking ${url}...
|
|
363
|
+
`));
|
|
143
364
|
try {
|
|
365
|
+
let lastProgress = "";
|
|
144
366
|
const report = await check({
|
|
145
367
|
url,
|
|
146
|
-
|
|
147
|
-
|
|
368
|
+
maxCrawl: parseInt(opts.maxCrawl, 10),
|
|
369
|
+
maxPages: parseInt(opts.pageLimit, 10),
|
|
370
|
+
maxContent: parseInt(opts.contentLimit, 10),
|
|
371
|
+
sampleMin: parseInt(opts.sampleMin, 10),
|
|
372
|
+
sampleRatio: parseFloat(opts.sampleRatio),
|
|
373
|
+
siteType,
|
|
374
|
+
skipAi: !opts.ai,
|
|
148
375
|
timeout: parseInt(opts.timeout, 10),
|
|
149
|
-
apiKey: opts.apiKey
|
|
376
|
+
apiKey: opts.apiKey,
|
|
377
|
+
lang,
|
|
378
|
+
onProgress: (msg) => {
|
|
379
|
+
lastProgress = msg;
|
|
380
|
+
const line = `\r${chalk2.cyan("\u25CF")} ${chalk2.gray(msg)}`;
|
|
381
|
+
process.stderr.write(line + " ".repeat(Math.max(0, 60 - msg.length)));
|
|
382
|
+
}
|
|
150
383
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.log(renderJsonReport(report));
|
|
155
|
-
} else {
|
|
156
|
-
console.log(renderTerminalReport(report));
|
|
157
|
-
}
|
|
384
|
+
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
385
|
+
if (opts.json) console.log(renderJsonReport(report));
|
|
386
|
+
else console.log(renderTerminalReport(report));
|
|
158
387
|
if (opts.save !== false) {
|
|
159
388
|
const ts = formatTimestamp();
|
|
160
389
|
const domain = getDomain(url);
|
|
161
390
|
const outDir = join(process.cwd(), opts.output);
|
|
162
391
|
try {
|
|
163
392
|
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)}`));
|
|
393
|
+
const path = join(outDir, `${domain}-${ts}.json`);
|
|
394
|
+
writeFileSync(path, renderJsonReport(report), "utf-8");
|
|
395
|
+
console.log(chalk2.gray(` ${t("report.saved", lang)}: ${path}`));
|
|
396
|
+
} catch {
|
|
169
397
|
}
|
|
170
398
|
}
|
|
171
399
|
process.exit(report.failed > 0 ? 1 : 0);
|
|
172
400
|
} catch (err) {
|
|
173
|
-
|
|
174
|
-
process.stderr.write("\r" + " ".repeat(60) + "\r");
|
|
401
|
+
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
175
402
|
console.error(chalk2.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
176
403
|
process.exit(2);
|
|
177
404
|
}
|
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,27 @@ 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;
|
|
76
|
+
siteAiScore: number;
|
|
37
77
|
}
|
|
38
78
|
interface CheckOptions {
|
|
39
79
|
url: string;
|
|
40
|
-
|
|
80
|
+
maxCrawl?: number;
|
|
81
|
+
maxPages?: number;
|
|
82
|
+
maxContent?: number;
|
|
83
|
+
sampleMin?: number;
|
|
84
|
+
sampleRatio?: number;
|
|
85
|
+
siteType?: SiteType;
|
|
41
86
|
skipAi?: boolean;
|
|
42
87
|
timeout?: number;
|
|
43
88
|
apiKey?: string;
|
|
89
|
+
lang?: Lang;
|
|
90
|
+
onProgress?: (message: string) => void;
|
|
44
91
|
}
|
|
45
92
|
|
|
46
93
|
declare function check(options: CheckOptions): Promise<CheckReport>;
|
package/dist/index.js
CHANGED