"migrate" command for flipper-pkg tool

Summary: "migrate" command for easy migration of existing Flipper plugins to the specification version 2.

Reviewed By: passy

Differential Revision: D21253913

fbshipit-source-id: 9edb170fbaa10e9c3f670d5d68e69f4f6106c151
This commit is contained in:
Anton Nikolaev
2020-04-28 04:56:45 -07:00
committed by Facebook GitHub Bot
parent deb0daa7f3
commit 1cf3c30b7c
8 changed files with 362 additions and 5 deletions

View File

@@ -28,6 +28,7 @@ USAGE
* [`flipper-pkg help [COMMAND]`](#flipper-pkg-help-command)
* [`flipper-pkg init [DIRECTORY]`](#flipper-pkg-init-directory)
* [`flipper-pkg lint [DIRECTORY]`](#flipper-pkg-lint-directory)
* [`flipper-pkg migrate [DIRECTORY]`](#flipper-pkg-migrate-directory)
* [`flipper-pkg pack [DIRECTORY]`](#flipper-pkg-pack-directory)
## `flipper-pkg bundle [DIRECTORY]`
@@ -66,15 +67,15 @@ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3
## `flipper-pkg init [DIRECTORY]`
initializes Flipper desktop plugin template in the provided directory
initializes a Flipper desktop plugin template in the provided directory
```
USAGE
$ flipper-pkg init [DIRECTORY]
ARGUMENTS
DIRECTORY [default: .] Path to directory where plugin package template should be initialized. Defaults to the current
working directory.
DIRECTORY [default: .] Path to the directory where the plugin package template should be initialized. Defaults to the
current working directory.
EXAMPLE
$ flipper-pkg init path/to/plugin
@@ -99,6 +100,27 @@ EXAMPLE
_See code: [src/commands/lint.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/lint.ts)_
## `flipper-pkg migrate [DIRECTORY]`
migrates a Flipper desktop plugin to the latest version of specification
```
USAGE
$ flipper-pkg migrate [DIRECTORY]
ARGUMENTS
DIRECTORY [default: .] Path to the plugin directory. Defaults to the current working directory.
OPTIONS
--no-dependencies Do not add or change package dependencies during migration.
--no-scripts Do not add or change package scripts during migration.
EXAMPLE
$ flipper-pkg migrate path/to/plugin
```
_See code: [src/commands/migrate.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/migrate.ts)_
## `flipper-pkg pack [DIRECTORY]`
packs a plugin folder into a distributable archive

View File

@@ -23,6 +23,7 @@
"flipper-pkg-lib": "0.39.0",
"fs-extra": "^8.1.0",
"inquirer": "^7.1.0",
"lodash": "^4.17.15",
"recursive-readdir": "^2.2.2"
},
"devDependencies": {

View File

@@ -0,0 +1,144 @@
/**
* 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 runMigrate from '../utils/runMigrate';
import fs from 'fs-extra';
const packageJsonV1 = {
name: 'Fresco',
version: '1.0.0',
main: 'index.tsx',
license: 'MIT',
keywords: ['images'],
dependencies: {
flipper: 'latest',
},
scripts: {
prepack: 'yarn reset && yarn build',
},
title: 'Images',
icon: 'profile',
bugs: {
email: 'example@test.com',
},
};
const packageJsonV2 = {
$schema: 'https://fbflipper.com/schemas/plugin-package/v2.json',
name: 'flipper-plugin-network',
id: 'Network',
flipperBundlerEntry: 'index.tsx',
main: 'dist/index.js',
title: 'Network',
description:
'Use the Network inspector to inspect outgoing network traffic in your apps.',
icon: 'internet',
version: '1.0.0',
license: 'MIT',
keywords: ['network', 'flipper-plugin'],
scripts: {
prepack: 'yarn reset && yarn build',
},
bugs: {
email: 'example@test.com',
url: 'https://github.com/facebook/flipper',
},
};
let convertedPackageJsonString: string | undefined;
beforeEach(() => {
jest.mock('fs-extra', () => jest.fn());
fs.pathExists = jest.fn().mockResolvedValue(true);
fs.pathExistsSync = jest.fn().mockReturnValue(true);
fs.readJson = jest.fn().mockResolvedValue(packageJsonV1);
fs.readFile = jest
.fn()
.mockResolvedValue(new Buffer(JSON.stringify(packageJsonV1)));
convertedPackageJsonString = undefined;
fs.writeFile = jest.fn().mockImplementation(async (_path, content) => {
convertedPackageJsonString = content;
});
});
test('converts package.json and adds dependencies', async () => {
const error = await runMigrate('dir');
expect(error).toBeUndefined();
expect(convertedPackageJsonString).toMatchInlineSnapshot(`
"{
\\"$schema\\": \\"https://fbflipper.com/schemas/plugin-package/v2.json\\",
\\"name\\": \\"flipper-plugin-fresco\\",
\\"id\\": \\"Fresco\\",
\\"version\\": \\"1.0.0\\",
\\"main\\": \\"dist/bundle.js\\",
\\"flipperBundlerEntry\\": \\"index.tsx\\",
\\"license\\": \\"MIT\\",
\\"keywords\\": [
\\"flipper-plugin\\",
\\"images\\"
],
\\"peerDependencies\\": {
\\"flipper\\": \\"latest\\"
},
\\"devDependencies\\": {
\\"flipper\\": \\"latest\\",
\\"flipper-pkg\\": \\"latest\\"
},
\\"scripts\\": {
\\"prepack\\": \\"yarn reset && yarn build && flipper-pkg lint && flipper-pkg bundle\\"
},
\\"title\\": \\"Images\\",
\\"icon\\": \\"profile\\",
\\"bugs\\": {
\\"email\\": \\"example@test.com\\"
}
}"
`);
});
test('converts package.json without changing dependencies', async () => {
const error = await runMigrate('dir', {noDependencies: true});
expect(error).toBeUndefined();
expect(convertedPackageJsonString).toMatchInlineSnapshot(`
"{
\\"$schema\\": \\"https://fbflipper.com/schemas/plugin-package/v2.json\\",
\\"name\\": \\"flipper-plugin-fresco\\",
\\"id\\": \\"Fresco\\",
\\"version\\": \\"1.0.0\\",
\\"main\\": \\"dist/bundle.js\\",
\\"flipperBundlerEntry\\": \\"index.tsx\\",
\\"license\\": \\"MIT\\",
\\"keywords\\": [
\\"flipper-plugin\\",
\\"images\\"
],
\\"dependencies\\": {
\\"flipper\\": \\"latest\\"
},
\\"scripts\\": {
\\"prepack\\": \\"yarn reset && yarn build && flipper-pkg lint && flipper-pkg bundle\\"
},
\\"title\\": \\"Images\\",
\\"icon\\": \\"profile\\",
\\"bugs\\": {
\\"email\\": \\"example@test.com\\"
}
}"
`);
});
test('does not migrate already migrated packages', async () => {
fs.readJson = jest.fn().mockResolvedValue(packageJsonV2);
fs.readFile = jest
.fn()
.mockResolvedValue(new Buffer(JSON.stringify(packageJsonV2)));
const error = await runMigrate('dir');
expect(error).toBeUndefined();
expect(convertedPackageJsonString).toBeUndefined();
});

View File

@@ -0,0 +1,53 @@
/**
* 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 {Command, flags} from '@oclif/command';
import {args} from '@oclif/parser';
import runMigrate from '../utils/runMigrate';
import path from 'path';
export default class Migrate extends Command {
public static description =
'migrates a Flipper desktop plugin to the latest version of specification';
public static examples = [`$ flipper-pkg migrate path/to/plugin`];
public static flags = {
'no-dependencies': flags.boolean({
description:
'Do not add or change package dependencies during migration.',
default: false,
}),
'no-scripts': flags.boolean({
description: 'Do not add or change package scripts during migration.',
default: false,
}),
};
public static args: args.IArg[] = [
{
name: 'directory',
required: false,
default: '.',
description:
'Path to the plugin directory. Defaults to the current working directory.',
},
];
public async run() {
const {args, flags} = this.parse(Migrate);
const dir: string = path.resolve(process.cwd(), args.directory);
const noDependencies = flags['no-dependencies'];
const noScripts = flags['no-scripts'];
const error = await runMigrate(dir, {noDependencies, noScripts});
if (error) {
this.error(error);
}
}
}

View File

@@ -0,0 +1,129 @@
/**
* 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 {getPluginDetails} from 'flipper-pkg-lib';
import {kebabCase} from 'lodash';
export default async function (
dir: string,
options: {
noDependencies?: boolean;
noScripts?: boolean;
} = {},
): Promise<string | undefined> {
const {noDependencies, noScripts} = Object.assign(
{
noDependencies: false,
noScripts: false,
},
options,
);
if (!(await fs.pathExists(dir))) {
return `Directory not found: ${dir}`;
}
const packageJsonPath = path.join(dir, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
return `package.json not found: ${packageJsonPath}`;
}
console.log(`⚙️ Migrating Flipper plugin package in ${dir}`);
const packageJsonString = (await fs.readFile(packageJsonPath)).toString();
const packageJson = JSON.parse(packageJsonString);
const pluginDetails = await getPluginDetails(dir, packageJson);
if (pluginDetails.specVersion === 2) {
console.log(
`✅ Plugin is already defined according to the latest specification version.`,
);
return;
}
const name = pluginDetails.name.startsWith('flipper-plugin-')
? pluginDetails.name
: `flipper-plugin-${kebabCase(pluginDetails.name)}`;
const keys = Object.keys(packageJson);
packageJson.name = name;
packageJson.main = pluginDetails.main;
if (!packageJson.flipperBundlerEntry) {
const index = keys.indexOf('main');
keys.splice(index + 1, 0, 'flipperBundlerEntry');
}
packageJson.flipperBundlerEntry = pluginDetails.source;
if (!packageJson.id) {
const index = keys.indexOf('name');
keys.splice(index + 1, 0, 'id');
}
packageJson.id = pluginDetails.id;
if (!packageJson.$schema) {
keys.unshift('$schema');
}
packageJson.$schema = 'https://fbflipper.com/schemas/plugin-package/v2.json';
if (!packageJson.keywords) {
keys.push('keywords');
}
if (
!packageJson.keywords ||
!packageJson.keywords.includes('flipper-plugin')
) {
packageJson.keywords = ['flipper-plugin', ...(packageJson.keywords || [])];
}
if (!noDependencies) {
const dependenciesFieldIndex = keys.indexOf('dependencies');
if (packageJson.dependencies && packageJson.dependencies.flipper) {
delete packageJson.dependencies.flipper;
}
if (!packageJson.peerDependencies) {
if (dependenciesFieldIndex === -1) {
keys.push('peerDependencies');
} else {
if (Object.keys(packageJson.dependencies).length === 0) {
// If no other dependencies except 'flipper' then we need to remove 'dependencies' and add 'peerDependencies' instead
keys.splice(dependenciesFieldIndex, 1, 'peerDependencies');
} else {
// If there are other dependencies except 'flipper', then
keys.splice(dependenciesFieldIndex + 1, 0, 'peerDependencies');
}
}
}
packageJson.peerDependencies = {
...packageJson.peerDependencies,
flipper: 'latest',
};
if (!packageJson.devDependencies) {
const peerDependenciesFieldIndex = keys.indexOf('peerDependencies');
keys.splice(peerDependenciesFieldIndex + 1, 0, 'devDependencies');
}
packageJson.devDependencies = {
...packageJson.devDependencies,
flipper: 'latest',
'flipper-pkg': 'latest',
};
}
if (!noScripts) {
if (!packageJson.scripts) {
keys.push('scripts');
}
packageJson.scripts = {
...packageJson.scripts,
prepack:
(packageJson.scripts?.prepack
? packageJson.scripts!.prepack! + ' && '
: '') + 'flipper-pkg lint && flipper-pkg bundle',
};
}
const newPackageJson = keys.reduce<any>((result, key) => {
result[key] = packageJson[key];
return result;
}, {});
const newPackageJsonString = JSON.stringify(newPackageJson, undefined, 2);
await fs.writeFile(packageJsonPath, newPackageJsonString);
console.log(`✅ Plugin migrated to the latest specification version 2.`);
}