Files
memind/release-gate/public-url.mjs
T
john c122bd5664 feat(analytics): retire Rybbit and add SEO/GEO discovery tracking
Document Umami as the sole analytics platform, remove Rybbit proxy and
tracker code, and classify public page traffic with discovery_channel for
adm SEO/GEO dashboards.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 01:34:05 +08:00

102 lines
3.1 KiB
JavaScript

import fs from 'node:fs/promises';
import net from 'node:net';
import path from 'node:path';
const SCANNED_PUBLIC_EXTENSIONS = new Set(['.css', '.html']);
const ALLOWED_TKMIND_HOSTS = new Set([
'asr.tkmind.cn',
'go.tkmind.cn',
'm.tkmind.cn',
'plaza.tkmind.cn',
]);
function isPrivateIpv4(hostname) {
const parts = hostname.split('.').map(Number);
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) return false;
return parts[0] === 10
|| parts[0] === 127
|| (parts[0] === 169 && parts[1] === 254)
|| (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|| (parts[0] === 192 && parts[1] === 168);
}
export function classifyPublicUrl(rawUrl) {
let url;
try {
url = new URL(rawUrl);
} catch {
return 'invalid_absolute_url';
}
const hostname = url.hostname.toLowerCase();
if (
hostname === 'localhost'
|| hostname.endsWith('.localhost')
|| hostname === '::1'
|| isPrivateIpv4(hostname)
) {
return 'loopback_or_private_host';
}
if (hostname.endsWith('.tkmind.cn') && !ALLOWED_TKMIND_HOSTS.has(hostname)) {
return 'unknown_tkmind_public_host';
}
return null;
}
function extractPublicDocumentUrls(source, extension) {
const urls = [];
if (extension === '.html') {
const attributePattern = /\b(?:action|content|href|poster|src)\s*=\s*["'](https?:\/\/[^"'<> \t\r\n]+)["']/gi;
for (const match of source.matchAll(attributePattern)) urls.push(match[1]);
}
if (extension === '.css') {
const cssPattern = /url\(\s*["']?(https?:\/\/[^"') \t\r\n]+)["']?\s*\)/gi;
for (const match of source.matchAll(cssPattern)) urls.push(match[1]);
}
return urls;
}
async function walkPublicDocuments(root, current = root) {
const entries = await fs.readdir(current, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const absolute = path.join(current, entry.name);
if (entry.isDirectory()) files.push(...await walkPublicDocuments(root, absolute));
else if (entry.isFile() && SCANNED_PUBLIC_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
files.push(absolute);
}
}
return files;
}
export async function inspectRuntimePublicUrls(runtimeRoot) {
const distRoot = path.join(runtimeRoot, 'dist');
const findings = [];
for (const file of await walkPublicDocuments(distRoot)) {
const extension = path.extname(file).toLowerCase();
const source = await fs.readFile(file, 'utf8');
for (const rawUrl of extractPublicDocumentUrls(source, extension)) {
const reason = classifyPublicUrl(rawUrl);
if (reason) {
findings.push({
file: path.relative(runtimeRoot, file).split(path.sep).join('/'),
url: rawUrl,
reason,
});
}
}
}
const entryScript = await fs.readFile(
path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'),
'utf8',
);
if (!/H5_PUBLIC_BASE_URL[^\n]*https:\/\/m\.tkmind\.cn/.test(entryScript)) {
findings.push({
file: 'scripts/run-memind-portal-prod.sh',
url: null,
reason: 'missing_canonical_h5_public_base',
});
}
return findings;
}