import { AdapterHandlerOptions, AdapterInfo, } from "@/core/plugin-handler/types"; import fs from "fs-extra"; import search, { Result } from "libnpmsearch"; import path from "path"; import got from "got"; import spwan from "cross-spawn"; /** * 系统插件管理器 * @class AdapterHandler */ class AdapterHandler { // 插件安装地址 public baseDir: string; // 插件源地址 readonly registry: string; /** * Creates an instance of AdapterHandler. * @param {AdapterHandlerOptions} options * @memberof AdapterHandler */ constructor(options: AdapterHandlerOptions) { // 初始化插件存放 if (!fs.existsSync(options.baseDir)) { fs.mkdirsSync(options.baseDir); fs.writeFileSync( `${options.baseDir}/package.json`, '{"dependencies":{}}' ); } this.baseDir = options.baseDir; this.registry = options.registry || "https://registry.npm.taobao.org"; } /** * 获取插件信息 * @param {string} adapter 插件名称 * @param {string} adapterPath 插件指定路径 * @memberof PluginHandler */ async getAdapterInfo( adapter: string, adapterPath: string ): Promise { let adapterInfo: AdapterInfo; const infoPath = adapterPath || path.resolve(this.baseDir, "node_modules", adapter, "plugin.json"); // 从本地获取 if (await fs.pathExists(infoPath)) { adapterInfo = JSON.parse( fs.readFileSync(infoPath, "utf-8") ) as AdapterInfo; } else { // 本地没有从远程获取 const resp = await got.get( `https://cdn.jsdelivr.net/npm/${adapter}/plugin.json` ); // Todo 校验合法性 adapterInfo = JSON.parse(resp.body) as AdapterInfo; } return adapterInfo; } // 安装并启动插件 async install(adapters: Array) { const installCmd = "install"; // 安装 await this.execCommand(installCmd, adapters); } /** * 从 npm 搜索插件 * 传入 streamFunc 可以流式处理 * @param {string} adapter 插件名称 * @param {(data: Result) => void} [streamFunc] 流式处理钩子 * @memberof AdapterHandler */ async search(adapter: string, streamFunc?: (data: Result) => void) { return await new Promise((resolve, reject) => { const result: Result[] = []; const stream = search.stream(adapter); stream.on("data", (data: Result) => { result.push(data); if (streamFunc !== undefined) streamFunc(data); }); stream.on("end", () => { resolve(result); }); stream.on("error", (e: any) => { reject(e); }); }); } /** * 更新指定插件 * @param {...string[]} adapters 插件名称 * @memberof AdapterHandler */ async update(...adapters: string[]) { await this.execCommand("update", adapters); } /** * 卸载指定插件 * @param {...string[]} adapters 插件名称 * @memberof AdapterHandler */ async uninstall(adapters: string[]) { // 卸载插件 await this.execCommand("uninstall", adapters); } /** * 列出所有已安装插件 * @memberof AdapterHandler */ async list() { const installInfo = JSON.parse( await fs.readFile(`${this.baseDir}/package.json`, "utf-8") ); const adapters: string[] = []; for (const adapter in installInfo.dependencies) { adapters.push(adapter); } return adapters; } /** * 运行包管理器 * @memberof AdapterHandler */ private async execCommand(cmd: string, modules: string[]): Promise { return new Promise((resolve: any, reject: any) => { let args: string[] = [cmd] .concat(modules) .concat("--color=always") .concat("--save"); if (cmd !== "uninstall") args = args.concat(`--registry=${this.registry}`); const npm = spwan("npm", args, { cwd: this.baseDir, }); let output = ""; npm.stdout .on("data", (data: string) => { output += data; // 获取输出日志 }) .pipe(process.stdout); npm.stderr .on("data", (data: string) => { output += data; // 获取报错日志 }) .pipe(process.stderr); npm.on("close", (code: number) => { if (!code) { resolve({ code: 0, data: output }); // 如果没有报错就输出正常日志 } else { reject({ code: code, data: output }); // 如果报错就输出报错日志 } }); }); } } export default AdapterHandler;