flipper-plugin-lib
Version:
Library containing common Flipper plugin installation utilities
216 lines • 11.4 kB
JavaScript
;
/**
* 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