#!/usr/bin/env node const SOURCE_URL = (process.env.XIAOSHU_READING_PROMPT_SOURCE_URL || 'https://server.fmode.cn/parse').replace(/\/$/, ''); const SOURCE_APP_ID = process.env.XIAOSHU_READING_PROMPT_SOURCE_APP_ID || 'ncloudmaster'; const TARGET_URL = (process.env.XIAOSHU_PARSE_URL || 'https://server.xiaoshu.pro/parse').replace(/\/$/, ''); const TARGET_APP_ID = process.env.XIAOSHU_PARSE_APP_ID || '7pIbDBJmKx_main'; const MASTER_KEY = process.env.XIAOSHU_MASTER_KEY || ''; const commit = process.argv.includes('--commit'); const CODES = [ 'english-reading-article-tpl', 'english-reading-article-supply-tpl', 'english-reading-question-json-tpl', ]; if (commit && !MASTER_KEY) throw new Error('缺少 XIAOSHU_MASTER_KEY,不会写入生产数据'); async function parseRequest(url, headers, init = {}) { const response = await fetch(url, { ...init, headers: { ...headers, ...(init.headers || {}) } }); const text = await response.text(); let payload; try { payload = text ? JSON.parse(text) : {}; } catch { throw new Error(`Parse 返回了无效 JSON(${response.status})`); } if (!response.ok || payload.error) throw new Error(payload.error || `Parse 请求失败(${response.status})`); return payload; } function classQuery(baseUrl, where) { return `${baseUrl}/classes/PromptTemplate?where=${encodeURIComponent(JSON.stringify(where))}&limit=100`; } const sourceHeaders = { 'X-Parse-Application-Id': SOURCE_APP_ID }; const targetHeaders = { 'X-Parse-Application-Id': TARGET_APP_ID, ...(MASTER_KEY ? { 'X-Parse-Master-Key': MASTER_KEY } : {}), 'Content-Type': 'application/json', }; const sourcePayload = await parseRequest(classQuery(SOURCE_URL, { code: { $in: CODES } }), sourceHeaders); const sourceRows = Array.isArray(sourcePayload.results) ? sourcePayload.results : []; const sourceByCode = new Map(sourceRows.map((row) => [String(row.code || ''), row])); const missingSource = CODES.filter((code) => !sourceByCode.has(code)); if (missingSource.length) throw new Error(`原系统缺少阅读模板:${missingSource.join(', ')}`); let targetRows = []; try { const targetPayload = await parseRequest(classQuery(TARGET_URL, { code: { $in: CODES } }), targetHeaders); targetRows = Array.isArray(targetPayload.results) ? targetPayload.results : []; } catch (error) { if (!commit) throw error; targetRows = []; } const targetByCode = new Map(targetRows.map((row) => [String(row.code || ''), row])); const summary = { source: sourceRows.length, targetBefore: targetRows.length, create: 0, update: 0, unchanged: 0, committed: commit }; for (const code of CODES) { const source = sourceByCode.get(code); const target = targetByCode.get(code); const value = { code, name: String(source.name || code), template: String(source.template || ''), ...(source.output === undefined ? {} : { output: source.output }), }; const unchanged = target && String(target.name || '') === value.name && String(target.template || '') === value.template && JSON.stringify(target.output ?? null) === JSON.stringify(value.output ?? null); if (unchanged) { summary.unchanged += 1; continue; } if (target) summary.update += 1; else summary.create += 1; if (!commit) continue; const url = target ? `${TARGET_URL}/classes/PromptTemplate/${encodeURIComponent(target.objectId)}` : `${TARGET_URL}/classes/PromptTemplate`; await parseRequest(url, targetHeaders, { method: target ? 'PUT' : 'POST', body: JSON.stringify(value) }); } console.log(JSON.stringify(summary, null, 2));