Files
memind/scripts/repair-production-inspection-103-standalone.mjs
T
john 0a7b2df5fe
Memind CI / Test, build, and release guards (push) Successful in 3m8s
docs(release): close production-inspection-fixes branch and add standalone repair script
Register branch disposition after 103 deploy/repair, and add a lightweight
103 repair script that avoids heavy runtime module imports.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 08:59:19 +08:00

146 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Standalone 103 production repair (no heavy module imports).
* Dry-run by default; pass --apply to mutate database rows.
*/
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
const TANG_USER_ID = 'a70ff537-8908-486e-9b6c-042e07cc25db';
const TASKS_TO_CANCEL = [
'781a77e5-a787-4f37-b864-e21d7e918855',
'9df3f89a-3a06-434d-8b4c-17b1aeb8911f',
'7f953d5f-6fc3-4191-b52a-bffcef48e77f',
'9b537fe8-eea2-4e39-8bcb-e8f169986327',
];
function normalizeDeliveryRelativePath(value) {
const raw = String(value ?? '').trim().replace(/^\/+/, '');
if (!raw) return '';
return raw.startsWith('public/') ? raw : `public/${raw}`;
}
async function loadPool() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) throw new Error('缺少 DATABASE_URL');
const { default: mysql } = await import('mysql2/promise');
return mysql.createPool({ uri: databaseUrl, connectionLimit: 2 });
}
async function markContractFailed(pool, { userId, relativePath, reason }) {
const now = Date.now();
await pool.query(
`UPDATE h5_page_delivery_contracts
SET status = 'failed', updated_at = ?, last_error = ?
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
[now, reason, userId, relativePath],
);
}
async function markContractReady(pool, { userId, relativePath }) {
const now = Date.now();
await pool.query(
`UPDATE h5_page_delivery_contracts
SET status = 'ready', updated_at = ?, last_error = NULL
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
[now, userId, relativePath],
);
}
async function reconcileStuckContracts(pool, h5Root, apply, limit = 50) {
const [rows] = await pool.query(
`SELECT user_id, workspace_relative_path
FROM h5_page_delivery_contracts
WHERE status = 'preparing' AND data_mode = 'static'
ORDER BY updated_at ASC
LIMIT ?`,
[limit],
);
const summary = [];
for (const row of rows ?? []) {
const relativePath = normalizeDeliveryRelativePath(row.workspace_relative_path);
if (!relativePath) continue;
const filePath = path.join(h5Root, 'MindSpace', row.user_id, relativePath);
const exists = fs.existsSync(filePath);
const action = exists ? 'ready' : 'failed';
summary.push({ userId: row.user_id, relativePath, fileExists: exists, action: apply ? action : `would_${action}` });
if (apply) {
if (exists) {
await markContractReady(pool, { userId: row.user_id, relativePath });
} else {
await markContractFailed(pool, {
userId: row.user_id,
relativePath,
reason: 'reconcile: static html missing',
});
}
}
}
return summary;
}
async function main() {
const apply = process.argv.includes('--apply');
const h5Root = process.env.H5_ROOT || '/Users/john/Project/Memind';
const pool = await loadPool();
const now = Date.now();
const actions = [];
for (const taskId of TASKS_TO_CANCEL) {
const [rows] = await pool.query(
`SELECT id, user_id, title, status, hour, minute
FROM h5_scheduled_tasks WHERE id = ? LIMIT 1`,
[taskId],
);
const row = rows?.[0];
if (!row) {
actions.push({ taskId, action: 'skip', reason: 'not_found' });
continue;
}
if (row.status === 'cancelled') {
actions.push({ taskId, action: 'skip', reason: 'already_cancelled', title: row.title });
continue;
}
actions.push({
taskId,
action: apply ? 'cancel' : 'would_cancel',
title: row.title,
status: row.status,
schedule: `${row.hour ?? '?'}:${String(row.minute ?? 0).padStart(2, '0')}`,
});
if (apply) {
await pool.query(
`UPDATE h5_scheduled_tasks
SET status = 'cancelled', updated_at = ?, last_error = NULL
WHERE id = ? AND user_id = ?`,
[now, taskId, TANG_USER_ID],
);
}
}
const [activeWeather] = await pool.query(
`SELECT id, title, hour, minute, status, last_run_at
FROM h5_scheduled_tasks
WHERE user_id = ? AND title LIKE '%天气预报%' AND status = 'active'
ORDER BY hour, created_at`,
[TANG_USER_ID],
);
const reconcileSummary = await reconcileStuckContracts(pool, h5Root, apply);
console.log(JSON.stringify({
mode: apply ? 'apply' : 'dry-run',
cancelledTasks: actions,
remainingActiveWeatherTasks: activeWeather,
deliveryContracts: reconcileSummary,
}, null, 2));
await pool.end();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});