feat: initial commit - massive-cloud point cloud processing system

This commit is contained in:
2026-05-27 09:51:42 +00:00
commit 16811100d1
391 changed files with 78338 additions and 0 deletions

105
server/plugins/registry.ts Normal file
View File

@@ -0,0 +1,105 @@
/**
* 算法插件注册表
* ------------------------------------------------------------
* 启动时扫描 `server/plugins/manifests/*.json`,把每个 JSON 解析为
* `PluginManifest`。注册表是内存只读单例;增减算法只需添加 / 删除
* manifest 文件并重启进程(无需改 TS)。
*
* 路径解析:
* - 开发态:本文件位于 `server/plugins/registry.ts`,manifests 直接在同级
* `manifests/` 目录。
* - 生产态:esbuild 把 `server/_core/index.ts` 打到 `dist/index.js`,本文件
* 被打包到 dist/index.js 里。为了让 fs 能找到 manifests,Dockerfile 在
* runner 阶段额外把 `server/plugins/` 拷贝到 `dist/plugins/`,并通过
* 环境变量 `PLUGIN_DIR` 覆盖。
*/
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'node:url';
import type { PluginManifest } from './types.js';
let _cache: Map<string, PluginManifest> | null = null;
/** 返回 manifests / adapters / fixtures 的根目录。 */
export function pluginDir(): string {
if (process.env.PLUGIN_DIR) return process.env.PLUGIN_DIR;
const here = path.dirname(fileURLToPath(import.meta.url));
// 生产态 here === /app/dist;开发态 here === server/plugins
const candidates = [
path.join(here), // server/plugins(开发)
path.join(here, 'plugins'), // dist/plugins(生产 — Dockerfile 拷贝)
path.join(here, '..', 'server', 'plugins'),
];
for (const c of candidates) {
if (fs.existsSync(path.join(c, 'manifests'))) return c;
}
// 兜底——开发态相对仓库根
return path.resolve(here, '..', '..', 'server', 'plugins');
}
export function manifestsDir(): string {
return path.join(pluginDir(), 'manifests');
}
export function adaptersDir(): string {
return path.join(pluginDir(), 'adapters');
}
export function fixturesDir(): string {
return path.join(pluginDir(), 'fixtures');
}
/** 从 `manifests/*.json` 重新加载注册表。 */
export function reloadPlugins(): Map<string, PluginManifest> {
const dir = manifestsDir();
const result = new Map<string, PluginManifest>();
if (!fs.existsSync(dir)) {
console.warn('[PluginRegistry] manifests dir not found:', dir);
_cache = result;
return result;
}
for (const file of fs.readdirSync(dir)) {
if (!file.endsWith('.json')) continue;
const full = path.join(dir, file);
try {
const m = JSON.parse(fs.readFileSync(full, 'utf-8')) as PluginManifest;
if (!m.id || !m.runtime?.kind) {
console.warn('[PluginRegistry] skip invalid manifest', file);
continue;
}
if (result.has(m.id)) {
console.warn('[PluginRegistry] duplicate plugin id:', m.id);
}
result.set(m.id, m);
} catch (e) {
console.error('[PluginRegistry] failed to parse', file, e);
}
}
_cache = result;
const ids: string[] = [];
result.forEach((_v, k) => ids.push(k));
console.log(`[PluginRegistry] loaded ${result.size} plugin(s): ${ids.join(', ')}`);
return result;
}
export function listPlugins(): PluginManifest[] {
if (!_cache) reloadPlugins();
const arr: PluginManifest[] = [];
_cache!.forEach((m) => arr.push(m));
return arr;
}
export function getPlugin(id: string): PluginManifest | undefined {
if (!_cache) reloadPlugins();
return _cache!.get(id);
}
/**
* Force a full reload from disk and return the new list.
* Called by the /api/plugins/reload endpoint so operators can hot-swap
* manifests without restarting the server.
*/
export function forceReload(): PluginManifest[] {
_cache = null;
return listPlugins();
}