fix(page-data): harden structural delivery and Portal base URL
Reject /api/page-data/ legacy passthrough, verify live API before send, and resolve delivery links to Portal (8081) instead of Vite (5173). Add local repair/verify scripts for daily-register Page Data flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 修复 john 用户「每日待办登记」Page Data 交付:
|
||||
* - 前台页改为 public + insertRow
|
||||
* - 后台页 password + listRows/updateRow/deleteRow
|
||||
*
|
||||
* Usage: node scripts/repair-daily-todo-local.mjs
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { bindWorkspaceHtmlForPageData } from '../page-data-workspace-bind.mjs';
|
||||
import { resolveMindSpaceStorageRoot } from '../mindspace-runtime-config.mjs';
|
||||
import { readPageAccessPolicy, writePageAccessPolicy } from '../page-data-policy-store.mjs';
|
||||
|
||||
import { resolvePageDataDeliveryBaseUrl } from '../user-publish.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'));
|
||||
loadEnvFile(path.join(root, '../../.env.local'));
|
||||
|
||||
const JOHN_USER_ID = '1c99b83b-0454-474f-a5d2-129d34506a32';
|
||||
const WORKSPACE_ROOT = path.join(root, 'MindSpace', JOHN_USER_ID);
|
||||
const ADMIN_PASSWORD = 'adminadmin';
|
||||
const DATASET = 'daily_todo';
|
||||
const LOCAL_BASE = process.env.MEMIND_PAGE_DATA_DELIVERY_BASE_URL || resolvePageDataDeliveryBaseUrl();
|
||||
|
||||
const FORM_POLICY = {
|
||||
accessMode: 'public',
|
||||
datasets: {
|
||||
[DATASET]: {
|
||||
insert: true,
|
||||
read: false,
|
||||
columns: {
|
||||
insert: ['name', 'date', 'todo_items', 'priority', 'status', 'remark'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const ADMIN_POLICY = {
|
||||
accessMode: 'password',
|
||||
datasets: {
|
||||
[DATASET]: {
|
||||
insert: false,
|
||||
read: true,
|
||||
update: true,
|
||||
softDelete: true,
|
||||
columns: {
|
||||
read: ['id', 'name', 'date', 'todo_items', 'priority', 'status', 'remark', 'created_at', 'updated_at'],
|
||||
update: ['name', 'date', 'todo_items', 'priority', 'status', 'remark', 'updated_at'],
|
||||
soft_delete: ['id'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async function bindPages(pool, h5Root, storageRoot) {
|
||||
const pages = [
|
||||
{
|
||||
relativePath: 'public/daily-todo.html',
|
||||
accessMode: 'public',
|
||||
password: null,
|
||||
policy: FORM_POLICY,
|
||||
},
|
||||
{
|
||||
relativePath: 'public/daily-todo-admin.html',
|
||||
accessMode: 'password',
|
||||
password: ADMIN_PASSWORD,
|
||||
policy: ADMIN_POLICY,
|
||||
},
|
||||
];
|
||||
|
||||
const bound = [];
|
||||
for (const page of pages) {
|
||||
const result = await bindWorkspaceHtmlForPageData({
|
||||
pool,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
userId: JOHN_USER_ID,
|
||||
workspaceRoot: WORKSPACE_ROOT,
|
||||
relativePath: page.relativePath,
|
||||
accessMode: page.accessMode,
|
||||
password: page.password,
|
||||
pageDataPolicy: page.policy,
|
||||
});
|
||||
bound.push({
|
||||
relativePath: page.relativePath,
|
||||
pageId: result.pageId,
|
||||
workspaceUrl: result.workspaceUrl,
|
||||
publicationAccessMode: result.publicationAccessMode,
|
||||
});
|
||||
}
|
||||
return bound;
|
||||
}
|
||||
|
||||
function patchAdminPolicySoftDelete(pageId) {
|
||||
const policy = readPageAccessPolicy(WORKSPACE_ROOT, pageId);
|
||||
if (!policy?.datasets?.[DATASET]) return policy;
|
||||
policy.datasets[DATASET].update = true;
|
||||
policy.datasets[DATASET].softDelete = true;
|
||||
policy.datasets[DATASET].hardDelete = false;
|
||||
policy.datasets[DATASET].columns.update = ADMIN_POLICY.datasets[DATASET].columns.update;
|
||||
return writePageAccessPolicy(WORKSPACE_ROOT, policy);
|
||||
}
|
||||
|
||||
async function smokeInsert(pageId) {
|
||||
const res = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data/${DATASET}/rows`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: '本地验证',
|
||||
date: '2026-07-13',
|
||||
todo_items: '测试待办',
|
||||
priority: '普通',
|
||||
status: '待完成',
|
||||
remark: 'repair script',
|
||||
}),
|
||||
});
|
||||
const body = await res.json().catch(() => ({}));
|
||||
return { status: res.status, body };
|
||||
}
|
||||
|
||||
async function smokeAdminList(pageId) {
|
||||
const authRes = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data-auth`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ password: ADMIN_PASSWORD }),
|
||||
});
|
||||
const authBody = await authRes.json().catch(() => ({}));
|
||||
const token = authBody?.data?.token;
|
||||
if (!token) return { status: authRes.status, body: authBody, listStatus: 0 };
|
||||
|
||||
const listRes = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data/${DATASET}?limit=5`, {
|
||||
headers: { Accept: 'application/json', 'x-page-data-token': token },
|
||||
});
|
||||
const listBody = await listRes.json().catch(() => ({}));
|
||||
return { status: authRes.status, listStatus: listRes.status, rows: listBody?.data?.rows?.length ?? 0 };
|
||||
}
|
||||
|
||||
const pool = createDbPool();
|
||||
const h5Root = root;
|
||||
const storageRoot = resolveMindSpaceStorageRoot(h5Root);
|
||||
|
||||
console.log('=== 每日待办登记 · 本地修复 ===\n');
|
||||
|
||||
const bound = await bindPages(pool, h5Root, storageRoot);
|
||||
console.log('✔ 页面已重新绑定\n');
|
||||
for (const item of bound) {
|
||||
console.log(` ${item.relativePath}`);
|
||||
console.log(` pageId: ${item.pageId}`);
|
||||
console.log(` access: ${item.publicationAccessMode}`);
|
||||
console.log(` url: ${item.workspaceUrl}\n`);
|
||||
}
|
||||
|
||||
const adminPageId = bound.find((item) => item.relativePath === 'public/daily-todo-admin.html')?.pageId;
|
||||
if (adminPageId) {
|
||||
patchAdminPolicySoftDelete(adminPageId);
|
||||
console.log('✔ 后台策略已启用 update + softDelete\n');
|
||||
}
|
||||
|
||||
const registerPageId = bound.find((item) => item.relativePath === 'public/daily-todo.html')?.pageId;
|
||||
const insert = registerPageId ? await smokeInsert(registerPageId) : { status: 0 };
|
||||
const admin = adminPageId ? await smokeAdminList(adminPageId) : { listStatus: 0 };
|
||||
|
||||
console.log('【insert 冒烟】', JSON.stringify(insert, null, 2));
|
||||
console.log('【admin 冒烟】', JSON.stringify(admin, null, 2));
|
||||
|
||||
console.log('\n=== 正确访问链接(Portal 8081)===');
|
||||
for (const item of bound) {
|
||||
console.log(` ${item.relativePath}: ${item.workspaceUrl}`);
|
||||
}
|
||||
console.log(` 后台管理密码: ${ADMIN_PASSWORD}`);
|
||||
console.log('\n注意:Agent 交付链接必须用 Portal(8081),不要用 Vite UI(5173)。');
|
||||
|
||||
const passed = insert.status === 201 && admin.listStatus === 200;
|
||||
console.log(`\n${passed ? '✔ 修复完成,链路可用' : '✘ 仍有问题,请检查上方输出'}`);
|
||||
|
||||
await pool.end();
|
||||
process.exit(passed ? 0 : 1);
|
||||
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 本地验证「每日登记事项」Page Data 交付链路:
|
||||
* 1. 绑定前守卫应拦截(与生产 WeChat 一致)
|
||||
* 2. 补 dataset + bind 后守卫应放行
|
||||
* 3. insert API 冒烟
|
||||
*
|
||||
* Usage: node scripts/verify-daily-register-local.mjs
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { bindWorkspaceHtmlForPageData } from '../page-data-workspace-bind.mjs';
|
||||
import { resolveMindSpaceStorageRoot } from '../mindspace-runtime-config.mjs';
|
||||
import { ensureRegisteredDatasetFromHtml } from '../page-data-workspace-ensure.mjs';
|
||||
import {
|
||||
resolvePageDataCollectOutcomeAsync,
|
||||
evaluatePageDataFinishGuardAsync,
|
||||
} from '../mindspace-page-data-finish-guard.mjs';
|
||||
import { createPageService } from '../mindspace-pages.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'));
|
||||
loadEnvFile(path.join(root, '../../.env.local'));
|
||||
|
||||
const USER_MSG =
|
||||
'帮我做一个"每日登记事项":填写姓名、年龄、最喜欢的阅读类型;需要一个填写页面和一个后台查看页面。页面做好后请把两个链接都发给我。';
|
||||
const JOHN_USER_ID = '1c99b83b-0454-474f-a5d2-129d34506a32';
|
||||
const WORKSPACE_ROOT = path.join(root, 'MindSpace', JOHN_USER_ID);
|
||||
const ADMIN_PASSWORD = '88888888';
|
||||
const DATASET = 'daily_register';
|
||||
const LOCAL_BASE = process.env.MEMIND_PUBLIC_BASE_URL || 'http://127.0.0.1:8081';
|
||||
const PROD_OWNER = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
||||
|
||||
const TOOL_MESSAGES = [
|
||||
{
|
||||
content: [{
|
||||
type: 'toolRequest',
|
||||
toolCall: { value: { name: 'sandbox-fs__write_file', arguments: { path: 'public/daily-register.html' } } },
|
||||
}],
|
||||
},
|
||||
{
|
||||
content: [{
|
||||
type: 'toolRequest',
|
||||
toolCall: { value: { name: 'sandbox-fs__write_file', arguments: { path: 'public/daily-admin.html' } } },
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
function patchAdminHtmlForPageData(html) {
|
||||
if (/listRows\s*\(\s*['"]daily_register['"]/.test(html)) return html;
|
||||
const scriptStart = html.indexOf('<script>\n const PASSWORD');
|
||||
const scriptEnd = html.indexOf('</script>', scriptStart);
|
||||
if (scriptStart < 0 || scriptEnd < 0) {
|
||||
throw new Error('daily-admin.html 结构不符合预期,无法注入 Page Data 脚本');
|
||||
}
|
||||
const replacement = `<script src="/assets/page-data-client.js"></script>
|
||||
<script>
|
||||
const PASSWORD = '${ADMIN_PASSWORD}';
|
||||
var client = MindSpacePageData.createClient({ apiBase: '/api' });
|
||||
|
||||
function loadRecords() {
|
||||
return client.listRows('daily_register');
|
||||
}
|
||||
|
||||
function login() {
|
||||
const pwd = document.getElementById('passwordInput').value;
|
||||
if (pwd === PASSWORD) {
|
||||
document.getElementById('loginSection').style.display = 'none';
|
||||
document.getElementById('adminSection').style.display = 'block';
|
||||
document.getElementById('loginError').style.display = 'none';
|
||||
renderRecords();
|
||||
sessionStorage.setItem('admin_logged_in', 'true');
|
||||
} else {
|
||||
document.getElementById('loginError').style.display = 'block';
|
||||
document.getElementById('passwordInput').value = '';
|
||||
document.getElementById('passwordInput').focus();
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
document.getElementById('loginSection').style.display = 'block';
|
||||
document.getElementById('adminSection').style.display = 'none';
|
||||
document.getElementById('passwordInput').value = '';
|
||||
sessionStorage.removeItem('admin_logged_in');
|
||||
}
|
||||
|
||||
function renderRecords() {
|
||||
loadRecords().then(function(records) {
|
||||
const tbody = document.getElementById('recordsBody');
|
||||
const noData = document.getElementById('noData');
|
||||
const totalCount = document.getElementById('totalCount');
|
||||
totalCount.textContent = records.length;
|
||||
if (!records.length) {
|
||||
tbody.innerHTML = '';
|
||||
noData.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
noData.style.display = 'none';
|
||||
tbody.innerHTML = records.map(function(r, i) {
|
||||
var idx = records.length - i;
|
||||
var type = r.reading_type || r.readingType || '';
|
||||
return '<tr><td>' + idx + '</td><td><strong>' + escapeHtml(r.name || '') + '</strong></td><td>' + (r.age || '') + '</td><td>' + escapeHtml(type) + '</td><td>' + escapeHtml(r.created_at || r.createdAt || '') + '</td></tr>';
|
||||
}).join('');
|
||||
}).catch(function(err) {
|
||||
alert('加载失败:' + (err.message || err));
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
alert('后台暂不支持批量删除,请在数据库或管理工具中清理。');
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (sessionStorage.getItem('admin_logged_in') === 'true') {
|
||||
document.getElementById('loginSection').style.display = 'none';
|
||||
document.getElementById('adminSection').style.display = 'block';
|
||||
renderRecords();
|
||||
}
|
||||
})();`;
|
||||
return `${html.slice(0, scriptStart)}${replacement}${html.slice(scriptEnd + '</script>'.length)}`;
|
||||
}
|
||||
|
||||
async function ensureProdHtmlLocally() {
|
||||
const publicDir = path.join(WORKSPACE_ROOT, 'public');
|
||||
fs.mkdirSync(publicDir, { recursive: true });
|
||||
for (const name of ['daily-register.html', 'daily-admin.html']) {
|
||||
const target = path.join(publicDir, name);
|
||||
const url = `https://m.tkmind.cn/MindSpace/${PROD_OWNER}/public/${name}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`拉取生产 HTML 失败 ${name}: ${res.status}`);
|
||||
let html = await res.text();
|
||||
if (name === 'daily-admin.html') html = patchAdminHtmlForPageData(html);
|
||||
fs.writeFileSync(target, html, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
async function assessGuard(pool, pageService, label) {
|
||||
const guard = await evaluatePageDataFinishGuardAsync({
|
||||
publishDir: WORKSPACE_ROOT,
|
||||
agentText: USER_MSG,
|
||||
messages: TOOL_MESSAGES,
|
||||
pool,
|
||||
userId: JOHN_USER_ID,
|
||||
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
||||
});
|
||||
const outcome = await resolvePageDataCollectOutcomeAsync({
|
||||
reply: { text: '问卷和后台已创建,可以提交。', messages: TOOL_MESSAGES },
|
||||
intent: { agentText: USER_MSG },
|
||||
publishDir: WORKSPACE_ROOT,
|
||||
pool,
|
||||
userId: JOHN_USER_ID,
|
||||
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
||||
apiBase: LOCAL_BASE,
|
||||
});
|
||||
return {
|
||||
label,
|
||||
needsRepair: guard.needsRepair,
|
||||
unboundFiles: guard.unboundFiles.map((file) => file.relativePath),
|
||||
htmlIssues: guard.htmlIssues,
|
||||
outcomeAction: outcome.action,
|
||||
outcomeReason: outcome.reason,
|
||||
};
|
||||
}
|
||||
|
||||
async function bindPages(pool, h5Root, storageRoot) {
|
||||
const registerHtml = fs.readFileSync(path.join(WORKSPACE_ROOT, 'public/daily-register.html'), 'utf8');
|
||||
await ensureRegisteredDatasetFromHtml({
|
||||
workspaceRoot: WORKSPACE_ROOT,
|
||||
userId: JOHN_USER_ID,
|
||||
html: registerHtml,
|
||||
datasetName: DATASET,
|
||||
});
|
||||
|
||||
const pages = [
|
||||
{
|
||||
relativePath: 'public/daily-register.html',
|
||||
accessMode: 'public',
|
||||
password: null,
|
||||
policy: {
|
||||
accessMode: 'public',
|
||||
datasets: {
|
||||
[DATASET]: {
|
||||
insert: true,
|
||||
read: false,
|
||||
columns: { insert: ['name', 'age', 'reading_type'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
relativePath: 'public/daily-admin.html',
|
||||
accessMode: 'password',
|
||||
password: ADMIN_PASSWORD,
|
||||
policy: {
|
||||
accessMode: 'password',
|
||||
datasets: {
|
||||
[DATASET]: {
|
||||
insert: false,
|
||||
read: true,
|
||||
columns: { read: ['id', 'name', 'age', 'reading_type', 'created_at'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const bound = [];
|
||||
for (const page of pages) {
|
||||
const result = await bindWorkspaceHtmlForPageData({
|
||||
pool,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
userId: JOHN_USER_ID,
|
||||
workspaceRoot: WORKSPACE_ROOT,
|
||||
relativePath: page.relativePath,
|
||||
accessMode: page.accessMode,
|
||||
password: page.password,
|
||||
pageDataPolicy: page.policy,
|
||||
});
|
||||
bound.push({
|
||||
relativePath: page.relativePath,
|
||||
pageId: result.pageId,
|
||||
workspaceUrl: result.workspaceUrl,
|
||||
publicationUrl: result.publicationUrl,
|
||||
});
|
||||
}
|
||||
return bound;
|
||||
}
|
||||
|
||||
async function smokeInsert(pageId) {
|
||||
const res = await fetch(`${LOCAL_BASE}/api/public/pages/${pageId}/data/${DATASET}/rows`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: '本地验证',
|
||||
age: 28,
|
||||
reading_type: '科学技术',
|
||||
}),
|
||||
});
|
||||
const body = await res.json().catch(() => ({}));
|
||||
return { status: res.status, body };
|
||||
}
|
||||
|
||||
const pool = createDbPool();
|
||||
const h5Root = root;
|
||||
const storageRoot = resolveMindSpaceStorageRoot(h5Root);
|
||||
const pageService = createPageService(pool, { h5Root, storageRoot });
|
||||
|
||||
console.log('=== 每日登记事项 · 本地验证 ===\n');
|
||||
await ensureProdHtmlLocally();
|
||||
console.log('✔ 已同步生产 HTML 到本地 john 工作区(后台页已改为 Page Data listRows)\n');
|
||||
|
||||
const before = await assessGuard(pool, pageService, '绑定前');
|
||||
console.log('【绑定前】', JSON.stringify(before, null, 2));
|
||||
|
||||
const bound = await bindPages(pool, h5Root, storageRoot);
|
||||
console.log('\n✔ 绑定完成');
|
||||
for (const item of bound) {
|
||||
console.log(` ${item.relativePath}`);
|
||||
console.log(` pageId: ${item.pageId}`);
|
||||
console.log(` url: ${item.workspaceUrl}`);
|
||||
}
|
||||
|
||||
const after = await assessGuard(pool, pageService, '绑定后');
|
||||
console.log('\n【绑定后】', JSON.stringify(after, null, 2));
|
||||
|
||||
const registerPageId = bound.find((item) => item.relativePath === 'public/daily-register.html')?.pageId;
|
||||
const insert = registerPageId ? await smokeInsert(registerPageId) : { status: 0, body: { error: 'missing pageId' } };
|
||||
console.log('\n【insert 冒烟】', JSON.stringify(insert, null, 2));
|
||||
|
||||
const passed =
|
||||
before.outcomeAction !== 'send' &&
|
||||
after.outcomeAction !== 'fail' &&
|
||||
insert.status === 201;
|
||||
|
||||
console.log('\n=== 补充:Agent 未 load page-data-collect 技能时 ===');
|
||||
console.log('绑定完成后守卫仍可能报 incomplete_delivery(要求 Agent 走正式技能链路)');
|
||||
console.log('若消息含 load_skill(page-data-collect) + bind 工具,守卫 outcome = send');
|
||||
|
||||
console.log('\n=== 本地可访问链接(已 bind) ===');
|
||||
for (const item of bound) {
|
||||
console.log(` ${item.relativePath}: ${item.workspaceUrl.replace('5173', '8081')}`);
|
||||
}
|
||||
console.log(` 后台密码: ${ADMIN_PASSWORD}`);
|
||||
|
||||
console.log('\n=== 汇总 ===');
|
||||
console.log(`绑定前守卫: ${before.outcomeAction} (${before.outcomeReason ?? 'n/a'})`);
|
||||
console.log(`绑定后守卫: ${after.outcomeAction} (${after.outcomeReason ?? 'n/a'})`);
|
||||
console.log(`insert API: HTTP ${insert.status}`);
|
||||
console.log(passed ? '✔ 本地验证通过:问题可复现且修复后交付完整' : '✘ 本地验证未完全通过,请检查上方输出');
|
||||
|
||||
await pool.end();
|
||||
process.exit(passed ? 0 : 1);
|
||||
Reference in New Issue
Block a user