fix: gate public page download links before Portal release

Add a MindSpace public HTML checker for missing companion docx/PDF paths, wire it into runtime packaging and release-portal-runtime-prod.sh, and document the workflow for agents and ops.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-01 12:40:59 +08:00
parent 5a970e612e
commit b273bb7aa2
10 changed files with 368 additions and 3 deletions
+17
View File
@@ -184,6 +184,19 @@ async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir, sourceNodeModul
await visit(runtimeNodeModulesDir);
}
async function copyMindspacePublicLinkTools() {
console.log('==> 拷贝 MindSpace 公开页链接检查工具');
await fs.copyFile(
path.join(root, 'mindspace-public-links.mjs'),
path.join(runtimeRoot, 'mindspace-public-links.mjs'),
);
await fs.mkdir(path.join(runtimeRoot, 'scripts'), { recursive: true });
await fs.copyFile(
path.join(root, 'scripts', 'check-mindspace-public-links.mjs'),
path.join(runtimeRoot, 'scripts', 'check-mindspace-public-links.mjs'),
);
}
async function writeMetadata() {
const packageJson = JSON.parse(await fs.readFile(path.join(root, 'package.json'), 'utf8'));
const runtimeDependencies = Object.fromEntries(
@@ -272,6 +285,9 @@ async function writeMetadata() {
'Bundled alongside server.mjs (required for sandbox-fs MCP):',
' mindspace-sandbox-mcp.mjs (esbuild bundle; includes schedule-service deps)',
'',
'Post-deploy validation (scripts/check-mindspace-public-links.mjs):',
' Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.',
'',
`Runtime install mode: ${runtimeInstallMode}`,
`Bundle target: ${runtimeNodeTarget}`,
'',
@@ -302,6 +318,7 @@ async function main() {
if (runtimeInstallMode === 'bundle-node-modules' && !skipNodeModules) {
await copyNodeModules();
}
await copyMindspacePublicLinkTools();
await writeMetadata();
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'wechat-mp-menu.mjs'), 0o755);
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env node
/**
* Scan MindSpace public/*.html for broken relative links.
*
* Default (--downloads-only): attachment / download links only (release gate).
* --all-links: also check img/src and mindspace-cover assets.
*
* Usage:
* node scripts/check-mindspace-public-links.mjs
* node scripts/check-mindspace-public-links.mjs --user <uuid>
* node scripts/check-mindspace-public-links.mjs --root /path/to/MindSpace
* node scripts/check-mindspace-public-links.mjs --all-links
*/
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { scanPublicHtmlLinks } from '../mindspace-public-links.mjs';
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const DEFAULT_PUBLISH_ROOT = 'MindSpace';
function parseArgs(argv) {
let root = path.join(repoRoot, DEFAULT_PUBLISH_ROOT);
let userId = null;
let allLinks = false;
for (let i = 2; i < argv.length; i += 1) {
if (argv[i] === '--root' && argv[i + 1]) {
root = path.resolve(argv[i + 1]);
i += 1;
} else if (argv[i] === '--user' && argv[i + 1]) {
userId = argv[i + 1];
i += 1;
} else if (argv[i] === '--all-links') {
allLinks = true;
} else if (argv[i] === '--downloads-only') {
allLinks = false;
} else if (argv[i] === '--help' || argv[i] === '-h') {
console.log(
`Usage: node scripts/check-mindspace-public-links.mjs [--root ${DEFAULT_PUBLISH_ROOT}] [--user <uuid>] [--downloads-only|--all-links]`,
);
process.exit(0);
}
}
return { root, userId, downloadsOnly: !allLinks };
}
const { root, userId, downloadsOnly } = parseArgs(process.argv);
const issues = scanPublicHtmlLinks(root, { userId, downloadsOnly });
const modeLabel = downloadsOnly ? 'download/attachment links' : 'all relative links';
if (issues.length === 0) {
console.log(`OK: no missing ${modeLabel} under ${root}${userId ? ` (user ${userId})` : ''}`);
process.exit(0);
}
console.error(`Found ${issues.length} missing ${modeLabel} under ${root}:`);
for (const issue of issues) {
const relHtml = path.relative(root, issue.htmlPath);
console.error(`- ${relHtml}: ${issue.ref} (${issue.source})`);
}
process.exit(1);
+38 -1
View File
@@ -151,7 +151,7 @@ fi
verify_runtime_artifact() {
local missing=0
for required in server.mjs mindspace-sandbox-mcp.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/load-env.mjs scripts/wechat-mp-menu.mjs scripts/memind-portal-tunnel.sh; do
for required in server.mjs mindspace-sandbox-mcp.mjs mindspace-public-links.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/check-mindspace-public-links.mjs scripts/load-env.mjs scripts/wechat-mp-menu.mjs scripts/memind-portal-tunnel.sh; do
if [[ ! -e "${RUNTIME_ROOT}/${required}" ]]; then
echo "runtime 产物缺失: ${RUNTIME_ROOT}/${required}" >&2
missing=1
@@ -171,6 +171,29 @@ say "验证 MindSpace 发布与聊天 Finish 回归守卫"
npm run verify:mindspace-publish-guards:full
)
verify_mindspace_public_links() {
local target_root="${1:-${ROOT}/MindSpace}"
local label="${2:-本地 MindSpace}"
if [[ "${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}" == "1" ]]; then
say "跳过 ${label} 公开页链接检查(ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1"
return 0
fi
if [[ ! -d "${target_root}" ]]; then
say "跳过 ${label} 公开页链接检查(目录不存在: ${target_root}"
return 0
fi
say "检查 ${label} 公开页相对链接"
if ! node "${ROOT}/scripts/check-mindspace-public-links.mjs" --root "${target_root}" --downloads-only; then
echo "MindSpace 公开页存在缺失的相对下载/资源链接。" >&2
echo "修复 public/*.html 中的 href/src/cover 路径,或临时设置 ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1 跳过。" >&2
exit 1
fi
}
if [[ "${DRY_RUN}" -ne 1 ]]; then
verify_mindspace_public_links "${ROOT}/MindSpace" "本地"
fi
verify_remote_goosed_dependency() {
say "检查 103 goosed 依赖"
ssh -o BatchMode=yes "${HOST}" 'bash -s' <<'REMOTE'
@@ -500,6 +523,19 @@ if [[ "${portal_code}" != "200" ]]; then
exit 1
fi
if [[ "${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}" != "1" && -d "${APP_DIR}/MindSpace" && -f "${APP_DIR}/scripts/check-mindspace-public-links.mjs" ]]; then
say "检查 MindSpace 公开页相对链接"
node_bin="/opt/homebrew/opt/node@24/bin/node"
if [[ ! -x "${node_bin}" ]]; then
node_bin="$(command -v node)"
fi
if ! "${node_bin}" "${APP_DIR}/scripts/check-mindspace-public-links.mjs" --root "${APP_DIR}/MindSpace" --downloads-only; then
echo "MindSpace public link check failed: broken relative download/asset links under public/*.html" >&2
echo "Set ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1 only if you accept shipping with known broken links." >&2
exit 1
fi
fi
say "重启 m.tkmind.cn 反向隧道"
ensure_portal_tunnel
@@ -507,6 +543,7 @@ say "检查 live 目录中不再保留源码树"
allowed_live_mjs=(
"${APP_DIR}/server.mjs"
"${APP_DIR}/mindspace-sandbox-mcp.mjs"
"${APP_DIR}/mindspace-public-links.mjs"
)
extra_files=""
while IFS= read -r file; do