Install plugin from package file

Summary:
Adding a way to install plugins directly from package files. This is required for testing after packaging format changes.

Stage 3: Implemented installation of plugins directly from package file.

Reviewed By: jknoxville

Differential Revision: D19765619

fbshipit-source-id: 57f36c87d3cf5d4e1c9a1f8f9f9f32b14a18bc8b
This commit is contained in:
Anton Nikolaev
2020-02-06 09:41:44 -08:00
committed by Facebook Github Bot
parent 984cdbfb67
commit b9e7f5d6d1
8 changed files with 457 additions and 81 deletions

View File

@@ -15,10 +15,15 @@ import {PluginManager as PM} from 'live-plugin-manager';
import {default as algoliasearch, SearchIndex} from 'algoliasearch';
import NpmApi, {Package} from 'npm-api';
import semver from 'semver';
import decompress from 'decompress';
import decompressTargz from 'decompress-targz';
import tmp from 'tmp';
const ALGOLIA_APPLICATION_ID = 'OFCNCOG2CU';
const ALGOLIA_API_KEY = 'f54e21fa3a2a0160595bb058179bfb1e';
export const PLUGIN_DIR = path.join(homedir(), '.flipper', 'thirdparty');
// TODO(T57014856): The use should be constrained to just this module when the
// refactor is done.
export function providePluginManager(): PM {
@@ -27,13 +32,77 @@ export function providePluginManager(): PM {
});
}
async function installPlugin(
name: string,
installFn: (pluginManager: PM) => Promise<void>,
) {
await fs.ensureDir(PLUGIN_DIR);
// create empty watchman config (required by metro's file watcher)
await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}');
const destinationDir = path.join(PLUGIN_DIR, name);
// Clean up existing destination files.
await fs.remove(destinationDir);
const pluginManager = providePluginManager();
// install the plugin and all it's dependencies into node_modules
pluginManager.options.pluginsPath = path.join(destinationDir, 'node_modules');
await installFn(pluginManager);
// move the plugin itself out of the node_modules folder
const pluginDir = path.join(PLUGIN_DIR, name, 'node_modules', name);
const pluginFiles = await fs.readdir(pluginDir);
await Promise.all(
pluginFiles.map(f =>
fs.move(path.join(pluginDir, f), path.join(pluginDir, '..', '..', f)),
),
);
}
export async function installPluginFromNpm(name: string) {
await installPlugin(name, pluginManager =>
pluginManager.install(name).then(() => {}),
);
}
export async function installPluginFromFile(packagePath: string) {
const tmpDir = tmp.dirSync().name;
try {
const files = await decompress(packagePath, tmpDir, {
plugins: [decompressTargz()],
});
if (!files.length) {
throw new Error('The package is not in tar.gz format or is empty');
}
const packageDir = path.join(tmpDir, 'package');
if (!(await fs.pathExists(packageDir))) {
throw new Error(
'Package format is invalid: directory "package" not found',
);
}
const packageJsonPath = path.join(packageDir, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
throw new Error(
'Package format is invalid: file "package/package.json" not found',
);
}
const packageJson = await fs.readJSON(packageJsonPath);
const name = packageJson.name as string;
await installPlugin(name, pluginManager =>
pluginManager.installFromPath(packageDir).then(() => {}),
);
} finally {
if (fs.existsSync(tmpDir)) {
fs.removeSync(tmpDir);
}
}
}
// TODO(T57014856): This should be private, too.
export function provideSearchIndex(): SearchIndex {
const client = algoliasearch(ALGOLIA_APPLICATION_ID, ALGOLIA_API_KEY);
return client.initIndex('npm-search');
}
export const PLUGIN_DIR = path.join(homedir(), '.flipper', 'thirdparty');
export async function readInstalledPlugins(): Promise<PluginMap> {
const pluginDirExists = await fs.pathExists(PLUGIN_DIR);