2021-12-03 17:54:58 +08:00

176 lines
4.5 KiB
TypeScript

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<AdapterInfo> {
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<string>) {
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<Result[]>((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<string> {
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;