Files
memind/scripts/backfill-plaza-workspace-thumbnails.mjs
T
john ed910a68ca
Memind CI / Test, build, and release guards (push) Has been cancelled
chore(plaza): add 103 backfill scripts for cover URLs and hero thumbnails
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 16:15:45 +08:00

110 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/**
* 强制重生成 Plaza 帖对应 workspace HTML 的缩略图(嵌入 hero 图)。
*
* Usage (103):
* node scripts/backfill-plaza-workspace-thumbnails.mjs --dry-run
* node scripts/backfill-plaza-workspace-thumbnails.mjs --apply
* node scripts/backfill-plaza-workspace-thumbnails.mjs --apply --batch-prefix plaza-batch-
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createDbPool } from '../db.mjs';
import { resolveMindSpaceUserPublishDir } from '../mindspace-runtime-config.mjs';
import { ensureWorkspaceHtmlThumbnail } from '../mindspace-workspace-thumbnails.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return;
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
const value = trimmed.slice(eq + 1).trim();
if (!process.env[key]) process.env[key] = value;
}
}
loadEnvFile(path.join(root, '.env'));
function parseArgs() {
const args = process.argv.slice(2);
let apply = false;
let batchPrefix = '';
for (let i = 0; i < args.length; i += 1) {
if (args[i] === '--apply') apply = true;
else if (args[i] === '--dry-run') apply = false;
else if (args[i] === '--batch-prefix' && args[i + 1]) batchPrefix = args[++i];
}
return { apply, batchPrefix };
}
const { apply, batchPrefix } = parseArgs();
const h5Root = process.env.H5_ROOT ?? path.join(root, 'users');
const pool = createDbPool();
const params = ['published'];
let batchFilter = '';
if (batchPrefix) {
batchFilter = ' AND p.workspace_relative_path LIKE ?';
params.push(`public/${batchPrefix}%`);
}
const [rows] = await pool.query(
`SELECT pp.id AS post_id, pp.title, pp.user_id, p.workspace_relative_path
FROM plaza_posts pp
JOIN h5_publish_records pr ON pr.id = pp.publication_id
JOIN h5_page_records p ON p.id = pr.page_id
WHERE pp.status = ?
AND p.workspace_relative_path LIKE 'public/%.html'
${batchFilter}
ORDER BY pp.published_at DESC, pp.id DESC`,
params,
);
console.log(`${apply ? '写入模式' : 'Dry-run'}:命中 ${rows.length} 条 workspace HTML 帖子。`);
let ok = 0;
let skipped = 0;
let failed = 0;
for (const row of rows) {
const relativePath = String(row.workspace_relative_path ?? '').trim();
if (!relativePath) {
skipped += 1;
continue;
}
const publishDir = resolveMindSpaceUserPublishDir(h5Root, { id: row.user_id });
const htmlPath = path.join(publishDir, relativePath);
if (!fs.existsSync(htmlPath)) {
console.warn(`- SKIP ${row.post_id} | ${row.title} | missing ${relativePath}`);
skipped += 1;
continue;
}
console.log(`- ${row.post_id} | ${row.title} | ${relativePath}`);
if (!apply) continue;
try {
await ensureWorkspaceHtmlThumbnail(publishDir, relativePath, null, {
title: row.title,
force: true,
});
ok += 1;
} catch (error) {
failed += 1;
console.warn(
` FAIL ${row.post_id}:`,
error instanceof Error ? error.message : error,
);
}
}
console.log(`完成:regenerated=${ok} skipped=${skipped} failed=${failed}`);
if (!apply) {
console.log('Dry-run 未写入;正式执行: node scripts/backfill-plaza-workspace-thumbnails.mjs --apply');
}
await pool.end();