#!/usr/bin/env node /** * One-shot 103 production repairs from 2026-08-16 inspection. * Dry-run by default; pass --apply to mutate database rows. */ import process from 'node:process'; import { reconcileStuckStaticPageDeliveryContracts } from '../scheduled-task-executor.mjs'; 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 loadPool() { const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) { throw new Error('缺少 DATABASE_URL'); } return import('mysql2/promise').then(({ default: mysql }) => mysql.createPool({ uri: databaseUrl, connectionLimit: 2 })); } 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], ); let reconcileSummary = []; if (apply) { reconcileSummary = await reconcileStuckStaticPageDeliveryContracts({ pool, h5Root, limit: 50, logger: console, }); } else { const [stuck] = await pool.query( `SELECT user_id, workspace_relative_path, data_mode, status FROM h5_page_delivery_contracts WHERE status = 'preparing' AND data_mode = 'static' ORDER BY updated_at ASC LIMIT 50`, ); reconcileSummary = stuck; } 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); });