Support plugins that contain a scope in their name (#1427)

Summary: Pull Request resolved: https://github.com/facebook/flipper/pull/1427

Reviewed By: mweststrate

Differential Revision: D22868784

fbshipit-source-id: c332e5b05e3fccb74cf5fdcdecf15b8f2e8c5006
This commit is contained in:
Anton Nikolaev
2020-08-04 10:50:45 -07:00
committed by Facebook GitHub Bot
parent 14e6b1078d
commit 7e84c8e880
3 changed files with 36 additions and 12 deletions

View File

@@ -8,7 +8,7 @@
"description": "The name of the package. Must start with \"flipper-plugin-\" prefix.", "description": "The name of the package. Must start with \"flipper-plugin-\" prefix.",
"type": "string", "type": "string",
"maxLength": 214, "maxLength": 214,
"pattern": "^flipper-plugin-[a-z0-9-._~]*$", "pattern": "^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?flipper-plugin-[a-z0-9-._~]*$",
"errorMessage": "should start with \"flipper-plugin-\", e.g. \"flipper-plugin-example\"" "errorMessage": "should start with \"flipper-plugin-\", e.g. \"flipper-plugin-example\""
}, },
"id": { "id": {

View File

@@ -48,6 +48,15 @@ test('valid package json', async () => {
expect(result).toBe(null); expect(result).toBe(null);
}); });
test('valid scoped package json', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.name = '@test/flipper-plugin-package';
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toBe(null);
});
test('$schema field is required', async () => { test('$schema field is required', async () => {
const testPackageJson = Object.assign({}, validPackageJson); const testPackageJson = Object.assign({}, validPackageJson);
delete testPackageJson.$schema; delete testPackageJson.$schema;

View File

@@ -45,11 +45,21 @@ function getPluginPendingInstallationDir(
} }
function getPluginPendingInstallationsDir(name: string): string { function getPluginPendingInstallationsDir(name: string): string {
return path.join(pluginPendingInstallationDir, name); return path.join(
pluginPendingInstallationDir,
replaceInvalidPathSegmentCharacters(name),
);
} }
function getPluginInstallationDir(name: string): string { function getPluginInstallationDir(name: string): string {
return path.join(pluginInstallationDir, name); return path.join(
pluginInstallationDir,
replaceInvalidPathSegmentCharacters(name),
);
}
function replaceInvalidPathSegmentCharacters(name: string) {
return name.replace('/', '__');
} }
async function installPluginFromTempDir( async function installPluginFromTempDir(
@@ -145,7 +155,10 @@ export async function installPluginFromNpm(name: string) {
const plugManNoDep = providePluginManagerNoDependencies(); const plugManNoDep = providePluginManagerNoDependencies();
plugManNoDep.options.pluginsPath = tmpDir; plugManNoDep.options.pluginsPath = tmpDir;
await plugManNoDep.install(name); await plugManNoDep.install(name);
const pluginTempDir = path.join(tmpDir, name); const pluginTempDir = path.join(
tmpDir,
replaceInvalidPathSegmentCharacters(name),
);
await installPluginFromTempDir(pluginTempDir); await installPluginFromTempDir(pluginTempDir);
} finally { } finally {
await fs.remove(tmpDir); await fs.remove(tmpDir);
@@ -178,14 +191,15 @@ export async function getInstalledPlugins(): Promise<PluginMap> {
const dirs = await fs.readdir(pluginInstallationDir); const dirs = await fs.readdir(pluginInstallationDir);
const plugins = await Promise.all<[string, PluginDetails]>( const plugins = await Promise.all<[string, PluginDetails]>(
dirs.map( dirs.map(
(name) => (dirName) =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
const pluginDir = path.join(pluginInstallationDir, name); const pluginDir = path.join(pluginInstallationDir, dirName);
if (!(await fs.lstat(pluginDir)).isDirectory()) { if (!(await fs.lstat(pluginDir)).isDirectory()) {
return resolve(undefined); return resolve(undefined);
} }
try { try {
resolve([name, await getPluginDetails(pluginDir)]); const details = await getPluginDetails(pluginDir);
resolve([details.name, details]);
} catch (e) { } catch (e) {
reject(e); reject(e);
} }
@@ -203,24 +217,25 @@ export async function getPendingInstallationPlugins(): Promise<PluginMap> {
const dirs = await fs.readdir(pluginPendingInstallationDir); const dirs = await fs.readdir(pluginPendingInstallationDir);
const plugins = await Promise.all<[string, PluginDetails]>( const plugins = await Promise.all<[string, PluginDetails]>(
dirs.map( dirs.map(
(name) => (dirName) =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
const versions = ( const versions = (
await fs.readdir(path.join(pluginPendingInstallationDir, name)) await fs.readdir(path.join(pluginPendingInstallationDir, dirName))
).sort((v1, v2) => semver.compare(v2, v1, true)); ).sort((v1, v2) => semver.compare(v2, v1, true));
if (versions.length === 0) { if (versions.length === 0) {
return resolve(undefined); return resolve(undefined);
} }
const pluginDir = path.join( const pluginDir = path.join(
pluginPendingInstallationDir, pluginPendingInstallationDir,
name, dirName,
versions[0], versions[0],
); );
if (!(await fs.lstat(pluginDir)).isDirectory()) { if (!(await fs.lstat(pluginDir)).isDirectory()) {
return resolve(undefined); return resolve(undefined);
} }
try { try {
resolve([name, await getPluginDetails(pluginDir)]); const details = await getPluginDetails(pluginDir);
resolve([details.name, details]);
} catch (e) { } catch (e) {
reject(e); reject(e);
} }
@@ -244,7 +259,7 @@ export async function getPendingAndInstalledPlugins(): Promise<PluginMap> {
} }
export async function removePlugin(name: string): Promise<void> { export async function removePlugin(name: string): Promise<void> {
await fs.remove(path.join(pluginInstallationDir, name)); await fs.remove(getPluginInstallationDir(name));
} }
export async function finishPendingPluginInstallations() { export async function finishPendingPluginInstallations() {