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:
@@ -173,6 +173,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 runtimePackageJson = {
|
||||
@@ -233,6 +246,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.',
|
||||
'',
|
||||
'Key runtime differences must stay in .env, not in the artifact:',
|
||||
' DATABASE_URL / MYSQL_*',
|
||||
' H5_PUBLIC_BASE_URL',
|
||||
@@ -254,6 +270,7 @@ async function main() {
|
||||
await bundleServer();
|
||||
await bundleSandboxMcp();
|
||||
await copyNodeModules();
|
||||
await copyMindspacePublicLinkTools();
|
||||
await writeMetadata();
|
||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'), 0o755);
|
||||
console.log('');
|
||||
|
||||
@@ -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);
|
||||
@@ -139,7 +139,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; 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; do
|
||||
if [[ ! -e "${RUNTIME_ROOT}/${required}" ]]; then
|
||||
echo "runtime 产物缺失: ${RUNTIME_ROOT}/${required}" >&2
|
||||
missing=1
|
||||
@@ -153,6 +153,29 @@ verify_runtime_artifact() {
|
||||
|
||||
verify_runtime_artifact
|
||||
|
||||
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'
|
||||
@@ -435,10 +458,24 @@ 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 "检查 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
|
||||
|
||||
Reference in New Issue
Block a user