UNPKG

flipper-plugin-lib

Version:

Library containing common Flipper plugin installation utilities

216 lines 11.4 kB
"use strict"; /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllInstalledPluginsInDir = exports.moveInstalledPluginsFromLegacyDir = exports.cleanupOldInstalledPluginVersions = exports.getInstalledPlugins = exports.getAllInstalledPluginVersions = exports.removePlugins = exports.removePlugin = exports.installPluginFromFileOrBuffer = exports.installPluginFromNpm = exports.getInstalledPlugin = void 0; // Heavy use of nested promises but without sacrificing error propagation. /* eslint-disable promise/no-nesting */ const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const util_1 = require("util"); const live_plugin_manager_1 = require("live-plugin-manager"); const decompress_1 = __importDefault(require("decompress")); const decompress_targz_1 = __importDefault(require("decompress-targz")); const decompress_unzip_1 = __importDefault(require("decompress-unzip")); const tmp_1 = __importDefault(require("tmp")); const getPluginDetails_1 = require("./getPluginDetails"); const pluginPaths_1 = require("./pluginPaths"); const p_filter_1 = __importDefault(require("p-filter")); const p_map_1 = __importDefault(require("p-map")); const semver_1 = __importDefault(require("semver")); const typeUtils_1 = require("./typeUtils"); function providePluginManagerNoDependencies() { return new live_plugin_manager_1.PluginManager({ ignoredDependencies: [/.*/] }); } async function installPluginFromTempDir(sourceDir) { const pluginDetails = await (0, getPluginDetails_1.getInstalledPluginDetails)(sourceDir); const { name, version } = pluginDetails; const backupDir = path_1.default.join(await (0, util_1.promisify)(tmp_1.default.dir)(), `${name}-${version}`); const destinationDir = (0, pluginPaths_1.getPluginVersionInstallationDir)(name, version); if (pluginDetails.specVersion == 1) { throw new Error(`Cannot install plugin ${pluginDetails.name} because it is packaged using the unsupported format v1. Please encourage the plugin author to update to v2, following the instructions on https://fbflipper.com/docs/extending/js-setup#migration-to-the-new-plugin-specification`); } try { // Moving the existing destination dir to backup if (await fs_extra_1.default.pathExists(destinationDir)) { await fs_extra_1.default.move(destinationDir, backupDir, { overwrite: true }); } await fs_extra_1.default.move(sourceDir, destinationDir); } catch (err) { // Restore previous version from backup if installation failed await fs_extra_1.default.remove(destinationDir); if (await fs_extra_1.default.pathExists(backupDir)) { await fs_extra_1.default.move(backupDir, destinationDir, { overwrite: true }); } throw err; } return await (0, getPluginDetails_1.getInstalledPluginDetails)(destinationDir); } async function getPluginRootDir(dir) { // npm packages are tar.gz archives containing folder 'package' inside const packageDir = path_1.default.join(dir, 'package'); const isNpmPackage = await fs_extra_1.default.pathExists(packageDir); // vsix packages are zip archives containing folder 'extension' inside const extensionDir = path_1.default.join(dir, 'extension'); const isVsix = await fs_extra_1.default.pathExists(extensionDir); if (!isNpmPackage && !isVsix) { throw new Error('Package format is invalid: directory "package" or "extensions" not found in the archive root'); } return isNpmPackage ? packageDir : extensionDir; } async function getInstalledPlugin(name, version) { const dir = (0, pluginPaths_1.getPluginVersionInstallationDir)(name, version); if (!(await fs_extra_1.default.pathExists(dir))) { return null; } return await (0, getPluginDetails_1.getInstalledPluginDetails)(dir); } exports.getInstalledPlugin = getInstalledPlugin; async function installPluginFromNpm(name) { const tmpDir = await (0, util_1.promisify)(tmp_1.default.dir)(); try { await fs_extra_1.default.ensureDir(tmpDir); const plugManNoDep = providePluginManagerNoDependencies(); plugManNoDep.options.pluginsPath = tmpDir; const pluginInfo = await plugManNoDep.install(name); return await installPluginFromTempDir(pluginInfo.location); } finally { await fs_extra_1.default.remove(tmpDir); } } exports.installPluginFromNpm = installPluginFromNpm; async function installPluginFromFileOrBuffer(packagePath) { const tmpDir = await (0, util_1.promisify)(tmp_1.default.dir)(); try { const files = await (0, decompress_1.default)(packagePath, tmpDir, { plugins: [(0, decompress_targz_1.default)(), (0, decompress_unzip_1.default)()], }); if (!files.length) { throw new Error('The package is not in tar.gz format or is empty'); } const pluginDir = await getPluginRootDir(tmpDir); return await installPluginFromTempDir(pluginDir); } finally { await fs_extra_1.default.remove(tmpDir); } } exports.installPluginFromFileOrBuffer = installPluginFromFileOrBuffer; async function removePlugin(name) { await fs_extra_1.default.remove((0, pluginPaths_1.getPluginInstallationDir)(name)); } exports.removePlugin = removePlugin; async function removePlugins(names) { await (0, p_map_1.default)(names, (name) => removePlugin(name)); } exports.removePlugins = removePlugins; async function getAllInstalledPluginVersions() { const pluginDirs = await getInstalledPluginVersionDirs(); const versionDirs = pluginDirs.map(([_, versionDirs]) => versionDirs).flat(); return await (0, p_map_1.default)(versionDirs, (versionDir) => (0, getPluginDetails_1.getInstalledPluginDetails)(versionDir).catch((err) => { console.error(`Failed to load plugin details from ${versionDir}`, err); return null; })).then((versionDetails) => versionDetails.filter(typeUtils_1.notNull)); } exports.getAllInstalledPluginVersions = getAllInstalledPluginVersions; async function getInstalledPlugins() { const versionDirs = await getInstalledPluginVersionDirs(); return (0, p_map_1.default)(versionDirs .filter(([_, versionDirs]) => versionDirs.length > 0) .map(([_, versionDirs]) => versionDirs[0]), (latestVersionDir) => (0, getPluginDetails_1.getInstalledPluginDetails)(latestVersionDir).catch((err) => { console.error(`Failed to load plugin from ${latestVersionDir}`, err); return null; })).then((plugins) => plugins.filter(typeUtils_1.notNull)); } exports.getInstalledPlugins = getInstalledPlugins; async function cleanupOldInstalledPluginVersions(maxNumberOfVersionsToKeep) { const versionDirs = await getInstalledPluginVersionDirs(); const versionDirsToDelete = versionDirs .map(([_, versionDirs]) => versionDirs.slice(maxNumberOfVersionsToKeep)) .flat(); await (0, p_map_1.default)(versionDirsToDelete, (versionDirToDelete) => fs_extra_1.default.remove(versionDirToDelete).catch(() => { })); } exports.cleanupOldInstalledPluginVersions = cleanupOldInstalledPluginVersions; // Before that we installed all plugins to "thirdparty" folder and only kept // a single version for each of them. Now we install plugins to "installed-plugins" // folder and keep multiple versions. This function checks if the legacy folder exists and // moves all the plugins installed there to the new folder. async function moveInstalledPluginsFromLegacyDir() { if (await fs_extra_1.default.pathExists(pluginPaths_1.legacyPluginInstallationDir)) { await fs_extra_1.default .readdir(pluginPaths_1.legacyPluginInstallationDir) .then((dirs) => dirs.map((dir) => path_1.default.join(pluginPaths_1.legacyPluginInstallationDir, dir))) .then((dirs) => (0, p_filter_1.default)(dirs, (dir) => fs_extra_1.default .lstat(dir) .then((lstat) => lstat.isDirectory()) .catch(() => false))) .then((dirs) => (0, p_map_1.default)(dirs, (dir) => (0, getPluginDetails_1.getInstalledPluginDetails)(dir).catch(async (err) => { console.error(`Failed to load plugin from ${dir} on moving legacy plugins. Removing it.`, err); await fs_extra_1.default.remove(dir); return null; }))) .then((plugins) => (0, p_map_1.default)(plugins.filter(typeUtils_1.notNull), (plugin) => fs_extra_1.default.move(plugin.dir, (0, pluginPaths_1.getPluginVersionInstallationDir)(plugin.name, plugin.version), { overwrite: true }))); await fs_extra_1.default.remove(pluginPaths_1.legacyPluginInstallationDir); } } exports.moveInstalledPluginsFromLegacyDir = moveInstalledPluginsFromLegacyDir; async function getInstalledPluginVersionDirs() { if (!(await fs_extra_1.default.pathExists(pluginPaths_1.pluginInstallationDir))) { return []; } return await fs_extra_1.default .readdir(pluginPaths_1.pluginInstallationDir) .then((dirs) => dirs.map((dir) => path_1.default.join(pluginPaths_1.pluginInstallationDir, dir))) .then((dirs) => (0, p_filter_1.default)(dirs, (dir) => fs_extra_1.default .lstat(dir) .then((lstat) => lstat.isDirectory()) .catch(() => false))) .then((dirs) => (0, p_map_1.default)(dirs, (dir) => fs_extra_1.default .readdir(dir) .then((versionDirs) => versionDirs.filter((d) => semver_1.default.valid(d))) .then((versionDirs) => versionDirs.sort((v1, v2) => semver_1.default.compare(v2, v1, true))) .then((versionDirs) => versionDirs.map((versionDir) => path_1.default.join(dir, versionDir))) .then((versionDirs) => (0, p_filter_1.default)(versionDirs, (versionDir) => fs_extra_1.default .lstat(versionDir) .then((lstat) => lstat.isDirectory()) .catch(() => false)))).then((allDirs) => allDirs.reduce((agg, versionDirs, i) => { agg.push([dirs[i], versionDirs]); return agg; }, []))); } async function getAllInstalledPluginsInDir(dir, recursive = false) { const plugins = []; if (!((await fs_extra_1.default.pathExists(dir)) && (await fs_extra_1.default.stat(dir)).isDirectory())) { console.log('defaultPlugins dir not found'); return plugins; } const items = await fs_extra_1.default.readdir(dir); await (0, p_map_1.default)(items, async (item) => { const fullPath = path_1.default.join(dir, item); if (await (0, getPluginDetails_1.isPluginDir)(fullPath)) { try { plugins.push(await (0, getPluginDetails_1.getInstalledPluginDetails)(fullPath)); } catch (err) { console.error(`Failed to load plugin from ${fullPath}`); } } else if (recursive) { plugins.push(...(await getAllInstalledPluginsInDir(fullPath, recursive))); } }); return plugins; } exports.getAllInstalledPluginsInDir = getAllInstalledPluginsInDir; //# sourceMappingURL=pluginInstaller.js.map