UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

88 lines (86 loc) 3.87 kB
import { useLogger } from "../../../logger/index.js"; import { EXTENSIONS } from "../../../constants.js"; import { getExtensionsPath } from "../get-extensions-path.js"; import { getStorage } from "../../../storage/index.js"; import { mkdir, readFile, rm } from "node:fs/promises"; import { join } from "path"; import { useEnv } from "@directus/env"; import { ErrorCode, ServiceUnavailableError, isDirectusError } from "@directus/errors"; import { move, remove } from "fs-extra"; import { Readable } from "node:stream"; import { download } from "@directus/extensions-registry"; import { EXTENSION_PKG_KEY, ExtensionManifest } from "@directus/extensions"; import DriverLocal from "@directus/storage-driver-local"; import PQueue from "p-queue"; import { extract } from "tar"; //#region src/extensions/lib/installation/manager.ts const env = useEnv(); var InstallationManager = class { extensionPath = getExtensionsPath(); async install(versionId) { const logger = useLogger(); const tempDir = join(env["TEMP_PATH"], "marketplace", versionId); const tmpStorage = new DriverLocal({ root: tempDir }); try { await mkdir(tempDir, { recursive: true }); const options = {}; if (env["MARKETPLACE_REGISTRY"] && typeof env["MARKETPLACE_REGISTRY"] === "string") options.registry = env["MARKETPLACE_REGISTRY"]; let tarReadableStream; try { tarReadableStream = await download(versionId, env["MARKETPLACE_TRUST"] === "sandbox", options); } catch (error) { throw new ServiceUnavailableError({ service: "marketplace", reason: "Could not download the extension" }, { cause: error }); } if (!tarReadableStream) throw new Error(`No readable stream returned from download`); const tarStream = Readable.fromWeb(tarReadableStream); const tarPath = join(tempDir, `bin.tar.tgz`); await tmpStorage.write("bin.tar.tgz", tarStream); /** * NPM modules that are packed are always tarballed in a folder called "package" */ const extractedPath = "package"; await extract({ file: tarPath, cwd: tempDir }); const packageFile = JSON.parse(await readFile(join(tempDir, extractedPath, "package.json"), { encoding: "utf-8" })); if (!(await ExtensionManifest.parseAsync(packageFile))[EXTENSION_PKG_KEY]?.type) throw new Error(`Extension type not found in package.json`); if (env["EXTENSIONS_LOCATION"]) { const remoteDisk = (await getStorage()).location(env["EXTENSIONS_LOCATION"]); const queue = new PQueue({ concurrency: EXTENSIONS.STORAGE_MAX_CONCURRENCY }); for await (const filepath of tmpStorage.list(extractedPath)) { const readStream = await tmpStorage.read(filepath); const remotePath = join(env["EXTENSIONS_PATH"], ".registry", versionId, filepath.substring(7)); queue.add(() => remoteDisk.write(remotePath, readStream)); } await queue.onIdle(); } const dest = join(this.extensionPath, ".registry", versionId); await move(join(tempDir, extractedPath), dest, { overwrite: true }); } catch (err) { logger.warn(err); if (isDirectusError(err, ErrorCode.ServiceUnavailable)) throw err; throw new ServiceUnavailableError({ service: "extensions", reason: "Failed to extract the extension or write it to storage" }, { cause: err }); } finally { await rm(tempDir, { recursive: true }); } } async uninstall(folder) { if (env["EXTENSIONS_LOCATION"]) { const remoteDisk = (await getStorage()).location(env["EXTENSIONS_LOCATION"]); const queue = new PQueue({ concurrency: EXTENSIONS.STORAGE_MAX_CONCURRENCY }); const prefix = join(env["EXTENSIONS_PATH"], ".registry", folder); for await (const filepath of remoteDisk.list(prefix)) queue.add(() => remoteDisk.delete(filepath)); await queue.onIdle(); } await remove(join(this.extensionPath, ".registry", folder)); } }; //#endregion export { InstallationManager };