Install pre-bundled packages 1/N

Summary: No functional changes in this diff. This is just plugin manager refactoring before implementing  new functionality for installing pre-bundled packages.

Reviewed By: passy

Differential Revision: D19832078

fbshipit-source-id: 56b7ff1c68b6beb4abb2941da607651268e5f71a
This commit is contained in:
Anton Nikolaev
2020-02-11 12:52:23 -08:00
committed by Facebook Github Bot
parent 463e8a7984
commit 71928fdf08

View File

@@ -9,6 +9,7 @@
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {promisify} from 'util';
import {homedir} from 'os'; import {homedir} from 'os';
import {PluginMap, PluginDefinition} from '../reducers/pluginManager'; import {PluginMap, PluginDefinition} from '../reducers/pluginManager';
import {PluginManager as PM} from 'live-plugin-manager'; import {PluginManager as PM} from 'live-plugin-manager';
@@ -20,23 +21,30 @@ import decompressTargz from 'decompress-targz';
import decompressUnzip from 'decompress-unzip'; import decompressUnzip from 'decompress-unzip';
import tmp from 'tmp'; import tmp from 'tmp';
const getTmpDir = promisify(tmp.dir) as () => Promise<string>;
const ALGOLIA_APPLICATION_ID = 'OFCNCOG2CU'; const ALGOLIA_APPLICATION_ID = 'OFCNCOG2CU';
const ALGOLIA_API_KEY = 'f54e21fa3a2a0160595bb058179bfb1e'; const ALGOLIA_API_KEY = 'f54e21fa3a2a0160595bb058179bfb1e';
export const PLUGIN_DIR = path.join(homedir(), '.flipper', 'thirdparty'); export const PLUGIN_DIR = path.join(homedir(), '.flipper', 'thirdparty');
// TODO(T57014856): The use should be constrained to just this module when the function providePluginManager(): PM {
// refactor is done.
export function providePluginManager(): PM {
return new PM({ return new PM({
ignoredDependencies: [/^flipper$/, /^react$/, /^react-dom$/, /^@types/], ignoredDependencies: [/^flipper$/, /^react$/, /^react-dom$/, /^@types\//],
}); });
} }
async function installPlugin( function providePluginManagerNoDependencies(): PM {
name: string, return new PM({ignoredDependencies: [/.*/]});
installFn: (pluginManager: PM) => Promise<void>, }
) {
async function installPlugin(pluginDir: string) {
const packageJSONPath = path.join(pluginDir, 'package.json');
const packageJSON = JSON.parse(
(await fs.readFile(packageJSONPath)).toString(),
);
const name = packageJSON.name;
await fs.ensureDir(PLUGIN_DIR); await fs.ensureDir(PLUGIN_DIR);
// create empty watchman config (required by metro's file watcher) // create empty watchman config (required by metro's file watcher)
await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}'); await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}');
@@ -45,28 +53,63 @@ async function installPlugin(
await fs.remove(destinationDir); await fs.remove(destinationDir);
const pluginManager = providePluginManager(); const pluginManager = providePluginManager();
// install the plugin and all it's dependencies into node_modules // install the plugin dependencies into node_modules
pluginManager.options.pluginsPath = path.join(destinationDir, 'node_modules'); const nodeModulesDir = path.join(destinationDir, 'node_modules');
await installFn(pluginManager); pluginManager.options.pluginsPath = nodeModulesDir;
const pluginInfo = await pluginManager.installFromPath(pluginDir);
// move the plugin itself out of the node_modules folder const itselfDir = path.join(nodeModulesDir, name);
const pluginDir = path.join(PLUGIN_DIR, name, 'node_modules', name);
const pluginFiles = await fs.readdir(pluginDir); // copying plugin files into the destination folder
const pluginFiles = await fs.readdir(itselfDir);
await Promise.all( await Promise.all(
pluginFiles.map(f => pluginFiles.map(f =>
fs.move(path.join(pluginDir, f), path.join(pluginDir, '..', '..', f)), fs.move(path.join(itselfDir, f), path.join(destinationDir, f)),
), ),
); );
// live-plugin-manager also installs plugin itself into the target dir, it's better remove it
await fs.remove(itselfDir);
return pluginInfo;
}
async function getPluginRootDir(dir: string) {
// npm packages are tar.gz archives containing folder 'package' inside
const packageDir = path.join(dir, 'package');
const isNpmPackage = await fs.pathExists(packageDir);
// vsix packages are zip archives containing folder 'extension' inside
const extensionDir = path.join(dir, 'extension');
const isVsix = await fs.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;
} }
export async function installPluginFromNpm(name: string) { export async function installPluginFromNpm(name: string) {
await installPlugin(name, pluginManager => const tmpDir = await getTmpDir();
pluginManager.install(name).then(() => {}), try {
); await fs.ensureDir(tmpDir);
const plugManNoDep = providePluginManagerNoDependencies();
plugManNoDep.options.pluginsPath = tmpDir;
await plugManNoDep.install(name);
const pluginDir = path.join(tmpDir, name);
return await installPlugin(pluginDir);
} finally {
if (await fs.pathExists(tmpDir)) {
await fs.remove(tmpDir);
}
}
} }
export async function installPluginFromFile(packagePath: string) { export async function installPluginFromFile(packagePath: string) {
const tmpDir = tmp.dirSync().name; const tmpDir = await getTmpDir();
try { try {
const files = await decompress(packagePath, tmpDir, { const files = await decompress(packagePath, tmpDir, {
plugins: [decompressTargz(), decompressUnzip()], plugins: [decompressTargz(), decompressUnzip()],
@@ -74,41 +117,11 @@ export async function installPluginFromFile(packagePath: string) {
if (!files.length) { if (!files.length) {
throw new Error('The package is not in tar.gz format or is empty'); throw new Error('The package is not in tar.gz format or is empty');
} }
const pluginDir = await getPluginRootDir(tmpDir);
// npm packages are tar.gz archives containing folder 'package' inside return await installPlugin(pluginDir);
const packageDir = path.join(tmpDir, 'package');
const isNpmPackage = await fs.pathExists(packageDir);
// vsix packages are zip archives containing folder 'extension' inside
const extensionDir = path.join(tmpDir, 'extension');
const isVsix = await fs.pathExists(extensionDir);
if (!isNpmPackage && !isVsix) {
throw new Error(
'Package format is invalid: directory "package" or "extensions" not found in the archive root',
);
}
const packageRoot = isNpmPackage ? packageDir : extensionDir;
// otherwise both npm and vsix are quite similar, so we can use the same logic for installing them
const packageJsonPath = path.join(packageRoot, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
throw new Error(
`Package format is invalid: file "${path.relative(
tmpDir,
packageJsonPath,
)}" not found`,
);
}
const packageJson = await fs.readJSON(packageJsonPath);
const name = packageJson.name as string;
await installPlugin(name, pluginManager =>
pluginManager.installFromPath(packageRoot).then(() => {}),
);
} finally { } finally {
if (fs.existsSync(tmpDir)) { if (await fs.pathExists(tmpDir)) {
fs.removeSync(tmpDir); await fs.remove(tmpDir);
} }
} }
} }