67d529c81e
Prevent Portal .env from overriding MindSpace storage/users roots, and set launchd env plus runtime docs to use /Users/john/MindSpace paths. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
5.9 KiB
JavaScript
177 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const root = path.join(__dirname, '..');
|
|
const runtimeRoot = path.join(root, '.runtime', 'mindspace-service');
|
|
const serviceSourceRoot = path.join(root, 'mindspace-service');
|
|
const nodeModulesDir = path.join(root, 'node_modules');
|
|
const skipNodeModules = process.argv.includes('--skip-node-modules');
|
|
|
|
const excludeTopLevel = new Set([
|
|
'.git',
|
|
'.claude',
|
|
'.cursor',
|
|
'.local',
|
|
'.runtime',
|
|
'node_modules',
|
|
'dist',
|
|
'MindSpace',
|
|
'data',
|
|
'users',
|
|
'logs',
|
|
]);
|
|
|
|
async function exists(targetPath) {
|
|
try {
|
|
await fs.access(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function remove(targetPath) {
|
|
await fs.rm(targetPath, { recursive: true, force: true });
|
|
}
|
|
|
|
async function copyDir(source, target) {
|
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
await fs.cp(source, target, { recursive: true, dereference: false });
|
|
}
|
|
|
|
function shouldCopyMemindSource(source) {
|
|
const relative = path.relative(root, source);
|
|
if (!relative) return true;
|
|
const parts = relative.split(path.sep);
|
|
const top = parts[0];
|
|
const last = parts[parts.length - 1];
|
|
if (excludeTopLevel.has(top)) return false;
|
|
if (last.endsWith('.pid')) return false;
|
|
if (last === '.DS_Store' || last === '.env' || last === '.env.local') return false;
|
|
if (top === 'public' && parts[1] === 'plaza-covers') return false;
|
|
return true;
|
|
}
|
|
|
|
async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir, sourceNodeModulesDir) {
|
|
console.log('==> 重写 node_modules 顶层符号链接为 runtime 内相对路径');
|
|
const localNodeModulesRoot = `${sourceNodeModulesDir}${path.sep}`;
|
|
|
|
async function visit(dir) {
|
|
const entries = await fs.readdir(dir);
|
|
for (const entryName of entries) {
|
|
const fullPath = path.join(dir, entryName);
|
|
const stats = await fs.lstat(fullPath);
|
|
if (stats.isSymbolicLink()) {
|
|
const linkTarget = await fs.readlink(fullPath);
|
|
const normalizedTarget = path.normalize(linkTarget);
|
|
if (!normalizedTarget.startsWith(localNodeModulesRoot)) continue;
|
|
const relativeInsideNodeModules = normalizedTarget.slice(localNodeModulesRoot.length);
|
|
const runtimeTarget = path.join(runtimeNodeModulesDir, relativeInsideNodeModules);
|
|
const replacement = path.relative(path.dirname(fullPath), runtimeTarget);
|
|
await fs.unlink(fullPath);
|
|
await fs.symlink(replacement, fullPath);
|
|
continue;
|
|
}
|
|
if (stats.isDirectory()) {
|
|
await visit(fullPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
await visit(runtimeNodeModulesDir);
|
|
}
|
|
|
|
async function copyServiceSource() {
|
|
console.log('==> 拷贝 MindSpace service 源文件');
|
|
await remove(runtimeRoot);
|
|
await fs.mkdir(runtimeRoot, { recursive: true });
|
|
await copyDir(serviceSourceRoot, runtimeRoot);
|
|
await remove(path.join(runtimeRoot, 'mindspace-rpc-server.test.mjs'));
|
|
}
|
|
|
|
async function copyMemindSourceSnapshot() {
|
|
console.log('==> 拷贝 Memind 受控源码快照');
|
|
const targetRoot = path.join(runtimeRoot, 'memind-source');
|
|
await fs.mkdir(targetRoot, { recursive: true });
|
|
const entries = await fs.readdir(root);
|
|
for (const entry of entries) {
|
|
if (excludeTopLevel.has(entry)) continue;
|
|
if (entry.endsWith('.pid')) continue;
|
|
if (entry === '.DS_Store' || entry === '.env' || entry === '.env.local') continue;
|
|
const sourcePath = path.join(root, entry);
|
|
if (!shouldCopyMemindSource(sourcePath)) continue;
|
|
await fs.cp(sourcePath, path.join(targetRoot, entry), {
|
|
recursive: true,
|
|
filter: shouldCopyMemindSource,
|
|
});
|
|
}
|
|
}
|
|
|
|
async function copyNodeModules() {
|
|
if (skipNodeModules) return;
|
|
if (!(await exists(nodeModulesDir))) {
|
|
throw new Error(`缺少 node_modules 目录: ${nodeModulesDir}`);
|
|
}
|
|
const resolvedNodeModulesDir = await fs.realpath(nodeModulesDir);
|
|
console.log('==> 拷贝生产运行依赖 node_modules');
|
|
await copyDir(resolvedNodeModulesDir, path.join(runtimeRoot, 'node_modules'));
|
|
await rewriteNodeModulesSymlinks(path.join(runtimeRoot, 'node_modules'), resolvedNodeModulesDir);
|
|
}
|
|
|
|
async function writeMetadata() {
|
|
const head = await fs.readFile(path.join(root, '.git', 'HEAD'), 'utf8').catch(() => 'unknown');
|
|
const runbook = [
|
|
'MindSpace service runtime artifact',
|
|
'',
|
|
'Production runtime target:',
|
|
' /Users/john/MindSpace',
|
|
'Standalone persisted data root:',
|
|
' /Users/john/MindSpace/data/mindspace',
|
|
'Standalone users root:',
|
|
' /Users/john/MindSpace/users',
|
|
'Shared Portal/H5 root:',
|
|
' /Users/john/Project/Memind',
|
|
'',
|
|
'This artifact contains:',
|
|
' server.mjs',
|
|
' mindspace-rpc-server.mjs',
|
|
' mindspace-service-bootstrap.mjs',
|
|
' scripts/run-mindspace-prod.sh',
|
|
' memind-source/ (controlled source snapshot, no runtime data)',
|
|
' node_modules/ (runtime dependencies)',
|
|
'',
|
|
'This artifact must not contain:',
|
|
' MindSpace/ runtime data',
|
|
' data/mindspace/ runtime data',
|
|
' users/ runtime data',
|
|
' .env / .env.local',
|
|
'',
|
|
'The production service may read shared Portal secrets from /Users/john/Project/Memind/.env,',
|
|
'but standalone MindSpace data paths must stay under /Users/john/MindSpace.',
|
|
'',
|
|
`Git head ref: ${head.trim()}`,
|
|
].join('\n');
|
|
await fs.writeFile(path.join(runtimeRoot, 'RUNBOOK.txt'), runbook, 'utf8');
|
|
}
|
|
|
|
async function main() {
|
|
if (!(await exists(serviceSourceRoot))) {
|
|
throw new Error(`缺少 MindSpace service 源目录: ${serviceSourceRoot}`);
|
|
}
|
|
await copyServiceSource();
|
|
await copyMemindSourceSnapshot();
|
|
await copyNodeModules();
|
|
await writeMetadata();
|
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-mindspace-prod.sh'), 0o755);
|
|
console.log('');
|
|
console.log(`MindSpace service runtime 已生成: ${runtimeRoot}`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exit(1);
|
|
});
|