const fs = require("fs"); const path = require("path"); const { execFileSync } = require("child_process"); function now() { return new Date().toISOString(); } function verifyChecksum(filePath, checksum) { if (!checksum) { return; } const [alg, expected] = checksum.split(":", 2); if (!alg || !expected) { return; } const crypto = require("crypto"); const actual = crypto.createHash(alg).update(fs.readFileSync(filePath)).digest("hex"); if (actual !== expected) { throw new Error(`文件校验失败 ${filePath}`); } } function unpackArtifact(artifact, localPath, versionDir) { if (artifact.type !== "archive" || !artifact.unpack) { return; } const extractDir = path.resolve(path.dirname(localPath), "_extracted"); fs.mkdirSync(extractDir, { recursive: true }); const format = artifact.archive_format || "zip"; if (format === "zip") { if (process.platform === "win32") { const source = localPath.replace(/'/g, "''"); const target = extractDir.replace(/'/g, "''"); execFileSync("powershell.exe", ["-NoProfile", "-Command", `Expand-Archive -Path '${source}' -DestinationPath '${target}' -Force`], { stdio: "inherit" }); } else { execFileSync("unzip", ["-o", localPath, "-d", extractDir], { stdio: "inherit" }); } } else if (["tar", "tar.gz", "tgz", "tar.xz"].includes(format)) { execFileSync("tar", ["-xf", localPath, "-C", extractDir], { stdio: "inherit" }); } artifact.extracted_path = path.relative(versionDir, extractDir).replace(/\\/g, "/"); if (artifact.entry_point) { artifact.extracted_entry_point = path.posix.join(artifact.extracted_path, artifact.entry_point.replace(/\\/g, "/")); } } module.exports = async function install(remote, id) { const parsed = remote.parseProjectVersion(id || "kit"); const projectId = parsed.projectId; const group = parsed.group || "/modules"; const project = await remote.getProjectMetadata(projectId, group); const version = parsed.version || project.version; if (!version) { throw new Error(`远程模块没有可安装版本 ${projectId}`); } const versionMeta = await remote.getVersionMetadata(projectId, version, project.group || group); const artifacts = remote.getMatchingArtifacts(versionMeta); if (artifacts.length < 1) { throw new Error(`没有匹配当前平台的文件 ${projectId}@${version}`); } const root = remote.getLocalModuleRoot(projectId); const versionDir = path.resolve(root, "versions", version); fs.mkdirSync(versionDir, { recursive: true }); for (const artifact of artifacts) { const localPath = await remote.downloadArtifact(projectId, version, artifact, versionDir, project.group || group); verifyChecksum(localPath, artifact.checksum || (versionMeta.checksums && versionMeta.checksums[artifact.path])); unpackArtifact(artifact, localPath, versionDir); } const localVersionMeta = Object.assign({}, versionMeta, { installed_at: now(), artifacts }); fs.writeFileSync(path.resolve(versionDir, ".version.json"), JSON.stringify(localVersionMeta, null, 4), "utf-8"); const state = remote.getInstallState(projectId); const action = state.current_version ? "switch" : "install"; await remote.activate(projectId, version, action); $logger.info(`当前版本 ${projectId}@${version}`); return version; };