Summary: To be used in the following diff and because it's super side effect heavy, I didn't bother to implement tests for it. Reviewed By: jknoxville Differential Revision: D18479311 fbshipit-source-id: 5cbcae0ebf2de4558b993325e50f6a2da1ed3ea7
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import path from 'path';
|
|
import fs from 'fs-extra';
|
|
import {homedir} from 'os';
|
|
import {PluginMap, PluginDefinition} from '../reducers/pluginManager';
|
|
import {PluginManager as PM} from 'live-plugin-manager';
|
|
import algoliasearch from 'algoliasearch';
|
|
import NpmApi, {Package} from 'npm-api';
|
|
import semver from 'semver';
|
|
|
|
const ALGOLIA_APPLICATION_ID = 'OFCNCOG2CU';
|
|
const ALGOLIA_API_KEY = 'f54e21fa3a2a0160595bb058179bfb1e';
|
|
|
|
// TODO(T57014856): The use should be constrained to just this module when the
|
|
// refactor is done.
|
|
export function providePluginManager(): PM {
|
|
return new PM({
|
|
ignoredDependencies: ['flipper', 'react', 'react-dom', '@types/*'],
|
|
});
|
|
}
|
|
|
|
// TODO(T57014856): This should be private, too.
|
|
export function provideSearchIndex(): algoliasearch.Index {
|
|
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);
|
|
|
|
if (!pluginDirExists) {
|
|
return new Map();
|
|
}
|
|
const dirs = await fs.readdir(PLUGIN_DIR);
|
|
const plugins = await Promise.all<[string, PluginDefinition]>(
|
|
dirs.map(
|
|
name =>
|
|
new Promise(async (resolve, reject) => {
|
|
if (!(await fs.lstat(path.join(PLUGIN_DIR, name))).isDirectory()) {
|
|
return resolve(undefined);
|
|
}
|
|
|
|
const packageJSON = await fs.readFile(
|
|
path.join(PLUGIN_DIR, name, 'package.json'),
|
|
);
|
|
|
|
try {
|
|
resolve([name, JSON.parse(packageJSON.toString())]);
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
}),
|
|
),
|
|
);
|
|
return new Map(plugins.filter(Boolean));
|
|
}
|
|
|
|
export type UpdateResult =
|
|
| {kind: 'up-to-date'}
|
|
| {kind: 'error'; error: Error}
|
|
| {kind: 'update-available'; version: string};
|
|
|
|
export async function findPluginUpdates(
|
|
currentPlugins: PluginMap,
|
|
): Promise<[string, UpdateResult][]> {
|
|
const npm = new NpmApi();
|
|
|
|
return Promise.all(
|
|
Array.from(currentPlugins.values()).map(
|
|
async (
|
|
currentPlugin: PluginDefinition,
|
|
): Promise<[string, UpdateResult]> =>
|
|
npm
|
|
.repo(currentPlugin.name)
|
|
.package()
|
|
.then((pkg: Package): [string, UpdateResult] => {
|
|
if (semver.lt(currentPlugin.version, pkg.version)) {
|
|
return [
|
|
currentPlugin.name,
|
|
{kind: 'update-available', version: pkg.version},
|
|
];
|
|
} else {
|
|
return [currentPlugin.name, {kind: 'up-to-date'}];
|
|
}
|
|
})
|
|
.catch(err => [currentPlugin.name, {kind: 'error', error: err}]),
|
|
),
|
|
);
|
|
}
|