"lint" command for flipper-pkg tool

Summary:
Implemented json schema for flipper plugin package.json and used it for validation in "flipper-pkg lint" command.

Nice thing about json schema is that it not only allows to validate json, but also can be referenced using "$schema" property in json so IDEs like VSCode can find it and use for code completion, validation and to show properties documentation. I'm going to deploy the schema as a part of documentation website so it can be referenced as https://fbflipper.com/schemas/plugin-package/v2.json.

Also the "$schema" field can be used instead of "specVersion" to determine the specification according to which the plugin is defined. E.g., if specification version 3 would be created, it will be described in schema https://fbflipper.com/schemas/plugin-package/v3.json, etc.

Reviewed By: passy

Differential Revision: D21228294

fbshipit-source-id: f21351e584ef936a7d6b314436448489691f83a6
This commit is contained in:
Anton Nikolaev
2020-04-27 17:31:39 -07:00
committed by Facebook GitHub Bot
parent 01f8d80402
commit 21c574ac80
24 changed files with 1062 additions and 84 deletions

View File

@@ -5,7 +5,8 @@
"rootDir": "src",
"allowJs": true,
"esModuleInterop": true,
"composite": true
"composite": true,
"incremental": true
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]

View File

@@ -41,7 +41,7 @@ test('getPluginDetailsV1', async () => {
test('getPluginDetailsV2', async () => {
const pluginV2 = {
specVersion: 2,
$schema: 'https://fbflipper.com/schemas/plugin-package/v2.json',
name: 'flipper-plugin-test',
title: 'Test',
version: '3.0.1',
@@ -72,7 +72,7 @@ test('getPluginDetailsV2', async () => {
test('id used as title if the latter omited', async () => {
const pluginV2 = {
specVersion: 2,
$schema: 'https://fbflipper.com/schemas/plugin-package/v2.json',
name: 'flipper-plugin-test',
id: 'test',
version: '3.0.1',
@@ -103,7 +103,7 @@ test('id used as title if the latter omited', async () => {
test('name without "flipper-plugin-" prefix is used as title if the latter omited', async () => {
const pluginV2 = {
specVersion: 2,
$schema: 'https://fbflipper.com/schemas/plugin-package/v2.json',
name: 'flipper-plugin-test',
version: '3.0.1',
main: 'dist/bundle.js',

View File

@@ -17,9 +17,12 @@ export default async function (
): Promise<PluginDetails> {
packageJson =
packageJson || (await fs.readJson(path.join(pluginDir, 'package.json')));
const specVersion = !packageJson.specVersion
? 1
: (packageJson.specVersion as number);
const specVersion =
packageJson.$schema &&
packageJson.$schema ===
'https://fbflipper.com/schemas/plugin-package/v2.json'
? 2
: 1;
switch (specVersion) {
case 1:
return await getPluginDetailsV1(pluginDir, packageJson);

View File

@@ -5,7 +5,8 @@
"rootDir": "src",
"allowJs": true,
"esModuleInterop": true,
"composite": true
"composite": true,
"incremental": true
},
"references": [{"path": "../babel-transformer"}],
"include": ["src"],

View File

@@ -15,7 +15,7 @@ $ npm install -g flipper-pkg
$ flipper-pkg COMMAND
running command...
$ flipper-pkg (-v|--version|version)
flipper-pkg/0.37.0 darwin-x64 node-v12.15.0
flipper-pkg/0.39.0 darwin-x64 node-v12.15.0
$ flipper-pkg --help [COMMAND]
USAGE
$ flipper-pkg COMMAND
@@ -26,6 +26,7 @@ USAGE
<!-- commands -->
* [`flipper-pkg bundle [DIRECTORY]`](#flipper-pkg-bundle-directory)
* [`flipper-pkg help [COMMAND]`](#flipper-pkg-help-command)
* [`flipper-pkg lint [DIRECTORY]`](#flipper-pkg-lint-directory)
* [`flipper-pkg pack [DIRECTORY]`](#flipper-pkg-pack-directory)
## `flipper-pkg bundle [DIRECTORY]`
@@ -40,10 +41,10 @@ ARGUMENTS
DIRECTORY [default: .] Path to plugin package directory for bundling. Defaults to the current working directory.
EXAMPLE
$ flipper-pkg bundle optional/path/to/directory
$ flipper-pkg bundle path/to/plugin
```
_See code: [src/commands/bundle.ts](https://github.com/facebook/flipper/blob/v0.37.0/src/commands/bundle.ts)_
_See code: [src/commands/bundle.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/bundle.ts)_
## `flipper-pkg help [COMMAND]`
@@ -62,6 +63,23 @@ OPTIONS
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_
## `flipper-pkg lint [DIRECTORY]`
validates a plugin package directory
```
USAGE
$ flipper-pkg lint [DIRECTORY]
ARGUMENTS
DIRECTORY [default: .] Path to plugin package directory for linting. Defaults to the current working directory.
EXAMPLE
$ flipper-pkg lint path/to/plugin
```
_See code: [src/commands/lint.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/lint.ts)_
## `flipper-pkg pack [DIRECTORY]`
packs a plugin folder into a distributable archive
@@ -81,7 +99,7 @@ EXAMPLE
$ flipper-pkg pack path/to/plugin
```
_See code: [src/commands/pack.ts](https://github.com/facebook/flipper/blob/v0.37.0/src/commands/pack.ts)_
_See code: [src/commands/pack.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/pack.ts)_
<!-- commandsstop -->

View File

@@ -17,9 +17,11 @@
"@oclif/parser": "^3",
"@oclif/plugin-help": "^2",
"@oclif/plugin-warn-if-update-available": "^1.7.0",
"ajv": "^6.12.2",
"ajv-errors": "^1.0.1",
"cli-ux": "^5.4.5",
"fs-extra": "^8.1.0",
"flipper-pkg-lib": "0.39.0",
"fs-extra": "^8.1.0",
"inquirer": "^7.0.5"
},
"devDependencies": {

View File

@@ -0,0 +1,525 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "JSON schema for NPM package.json files",
"definitions": {
"person": {
"description": "A person who has been involved in creating or maintaining this package",
"type": [
"object",
"string"
],
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"email": {
"type": "string",
"format": "email"
}
}
},
"bundledDependency": {
"description": "Array of package names that will be bundled when publishing the package.",
"type": "array",
"items": {
"type": "string"
}
},
"dependency": {
"description": "Dependencies are specified with a simple hash of package name to version range. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or git URL.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"scriptsInstallAfter": {
"description": "Run AFTER the package is installed",
"type": "string"
},
"scriptsPublishAfter": {
"description": "Run AFTER the package is published",
"type": "string"
},
"scriptsRestart": {
"description": "Run by the 'npm restart' command. Note: 'npm restart' will run the stop and start scripts if no restart script is provided.",
"type": "string"
},
"scriptsStart": {
"description": "Run by the 'npm start' command",
"type": "string"
},
"scriptsStop": {
"description": "Run by the 'npm stop' command",
"type": "string"
},
"scriptsTest": {
"description": "Run by the 'npm test' command",
"type": "string"
},
"scriptsUninstallBefore": {
"description": "Run BEFORE the package is uninstalled",
"type": "string"
},
"scriptsVersionBefore": {
"description": "Run BEFORE bump the package version",
"type": "string"
},
"coreProperties": {
"type": "object",
"patternProperties": {
"^_": {
"description": "Any property starting with _ is valid.",
"additionalProperties": true,
"additionalItems": true,
"tsType": "any"
}
},
"properties": {
"name": {
"description": "The name of the package.",
"type": "string",
"maxLength": 214,
"minLength": 1,
"pattern": "^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"
},
"version": {
"description": "Version must be parseable by node-semver, which is bundled with npm as a dependency.",
"type": "string"
},
"description": {
"description": "This helps people discover your package, as it's listed in 'npm search'.",
"type": "string"
},
"keywords": {
"description": "This helps people discover your package as it's listed in 'npm search'.",
"type": "array",
"items": {
"type": "string"
}
},
"homepage": {
"description": "The url to the project homepage.",
"type": "string"
},
"bugs": {
"description": "The url to your project's issue tracker and / or the email address to which issues should be reported. These are helpful for people who encounter issues with your package.",
"type": [
"object",
"string"
],
"properties": {
"url": {
"type": "string",
"description": "The url to your project's issue tracker.",
"format": "uri"
},
"email": {
"type": "string",
"description": "The email address to which issues should be reported.",
"format": "email"
}
}
},
"license": {
"type": "string",
"description": "You should specify a license for your package so that people know how they are permitted to use it, and any restrictions you're placing on it."
},
"licenses": {
"description": "DEPRECATED: Instead, use SPDX expressions, like this: { \"license\": \"ISC\" } or { \"license\": \"(MIT OR Apache-2.0)\" } see: 'https://docs.npmjs.com/files/package.json#license'",
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
}
}
}
},
"author": {
"$ref": "#/definitions/person"
},
"contributors": {
"description": "A list of people who contributed to this package.",
"type": "array",
"items": {
"$ref": "#/definitions/person"
}
},
"maintainers": {
"description": "A list of people who maintains this package.",
"type": "array",
"items": {
"$ref": "#/definitions/person"
}
},
"files": {
"description": "The 'files' field is an array of files to include in your project. If you name a folder in the array, then it will also include the files inside that folder.",
"type": "array",
"items": {
"type": "string"
}
},
"main": {
"description": "The main field is a module ID that is the primary entry point to your program.",
"type": "string"
},
"bin": {
"type": [
"string",
"object"
],
"additionalProperties": {
"type": "string"
}
},
"type": {
"description": "The type field defines how .js and extensionless files should be treated within a particular package.json files package scope. Supported values: \"commonjs\" (default) or \"module\".",
"type": "string"
},
"types": {
"description": "Set the types property to point to your bundled declaration file",
"type": "string"
},
"typings": {
"description": "Note that the \"typings\" field is synonymous with \"types\", and could be used as well.",
"type": "string"
},
"man": {
"type": [
"array",
"string"
],
"description": "Specify either a single file or an array of filenames to put in place for the man program to find.",
"items": {
"type": "string"
}
},
"directories": {
"type": "object",
"properties": {
"bin": {
"description": "If you specify a 'bin' directory, then all the files in that folder will be used as the 'bin' hash.",
"type": "string"
},
"doc": {
"description": "Put markdown files in here. Eventually, these will be displayed nicely, maybe, someday.",
"type": "string"
},
"example": {
"description": "Put example scripts in here. Someday, it might be exposed in some clever way.",
"type": "string"
},
"lib": {
"description": "Tell people where the bulk of your library is. Nothing special is done with the lib folder in any way, but it's useful meta info.",
"type": "string"
},
"man": {
"description": "A folder that is full of man pages. Sugar to generate a 'man' array by walking the folder.",
"type": "string"
},
"test": {
"type": "string"
}
}
},
"repository": {
"description": "Specify the place where your code lives. This is helpful for people who want to contribute.",
"type": [
"object",
"string"
],
"properties": {
"type": {
"type": "string"
},
"url": {
"type": "string"
},
"directory": {
"type": "string"
}
}
},
"scripts": {
"description": "The 'scripts' member is an object hash of script commands that are run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point.",
"type": "object",
"properties": {
"prepublish": {
"type": "string",
"description": "Run BEFORE the package is published (Also run on local npm install without any arguments)"
},
"prepare": {
"type": "string",
"description": "Run both BEFORE the package is packed and published, and on local npm install without any arguments. This is run AFTER prepublish, but BEFORE prepublishOnly"
},
"prepublishOnly": {
"type": "string",
"description": "Run BEFORE the package is prepared and packed, ONLY on npm publish"
},
"prepack": {
"type": "string",
"description": "run BEFORE a tarball is packed (on npm pack, npm publish, and when installing git dependencies)"
},
"postpack": {
"type": "string",
"description": "Run AFTER the tarball has been generated and moved to its final destination."
},
"publish": {
"$ref": "#/definitions/scriptsPublishAfter"
},
"postpublish": {
"$ref": "#/definitions/scriptsPublishAfter"
},
"preinstall": {
"type": "string",
"description": "Run BEFORE the package is installed"
},
"install": {
"$ref": "#/definitions/scriptsInstallAfter"
},
"postinstall": {
"$ref": "#/definitions/scriptsInstallAfter"
},
"preuninstall": {
"$ref": "#/definitions/scriptsUninstallBefore"
},
"uninstall": {
"$ref": "#/definitions/scriptsUninstallBefore"
},
"postuninstall": {
"type": "string",
"description": "Run AFTER the package is uninstalled"
},
"preversion": {
"$ref": "#/definitions/scriptsVersionBefore"
},
"version": {
"$ref": "#/definitions/scriptsVersionBefore"
},
"postversion": {
"type": "string",
"description": "Run AFTER bump the package version"
},
"pretest": {
"$ref": "#/definitions/scriptsTest"
},
"test": {
"$ref": "#/definitions/scriptsTest"
},
"posttest": {
"$ref": "#/definitions/scriptsTest"
},
"prestop": {
"$ref": "#/definitions/scriptsStop"
},
"stop": {
"$ref": "#/definitions/scriptsStop"
},
"poststop": {
"$ref": "#/definitions/scriptsStop"
},
"prestart": {
"$ref": "#/definitions/scriptsStart"
},
"start": {
"$ref": "#/definitions/scriptsStart"
},
"poststart": {
"$ref": "#/definitions/scriptsStart"
},
"prerestart": {
"$ref": "#/definitions/scriptsRestart"
},
"restart": {
"$ref": "#/definitions/scriptsRestart"
},
"postrestart": {
"$ref": "#/definitions/scriptsRestart"
}
},
"additionalProperties": {
"type": "string",
"tsType": "string | undefined"
}
},
"config": {
"description": "A 'config' hash can be used to set configuration parameters used in package scripts that persist across upgrades.",
"type": "object",
"additionalProperties": true
},
"dependencies": {
"$ref": "#/definitions/dependency"
},
"devDependencies": {
"$ref": "#/definitions/dependency"
},
"optionalDependencies": {
"$ref": "#/definitions/dependency"
},
"peerDependencies": {
"$ref": "#/definitions/dependency"
},
"resolutions": {
"$ref": "#/definitions/dependency"
},
"engines": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"engineStrict": {
"type": "boolean"
},
"os": {
"description": "You can specify which operating systems your module will run on",
"type": "array",
"items": {
"type": "string"
}
},
"cpu": {
"description": "If your code only runs on certain cpu architectures, you can specify which ones.",
"type": "array",
"items": {
"type": "string"
}
},
"preferGlobal": {
"type": "boolean",
"description": "DEPRECATED: This option used to trigger an npm warning, but it will no longer warn. It is purely there for informational purposes. It is now recommended that you install any binaries as local devDependencies wherever possible."
},
"private": {
"type": "boolean",
"description": "If set to true, then npm will refuse to publish it."
},
"publishConfig": {
"type": "object",
"additionalProperties": true
},
"dist": {
"type": "object",
"properties": {
"shasum": {
"type": "string"
},
"tarball": {
"type": "string"
}
}
},
"readme": {
"type": "string"
},
"module": {
"description": "An ECMAScript module ID that is the primary entry point to your program.",
"type": "string"
},
"esnext": {
"description": "A module ID with untranspiled code that is the primary entry point to your program.",
"type": [
"string",
"object"
],
"properties": {
"main": {
"type": "string"
},
"browser": {
"type": "string"
}
},
"additionalProperties": {
"type": "string"
}
},
"workspaces": {
"description": "To configure your yarn workspaces, please note private should be set to true to use yarn workspaces",
"anyof": [
{
"type": "array",
"description": "your workspace folders also takes a glob",
"items": "string"
},
{
"type": "object",
"properties": {
"packages": {
"type": "array",
"description": "your workspace folder's also takes a glob",
"items": "string"
},
"nohoist": {
"type": "array",
"description": "nohoist your npm packages",
"items": "string"
}
}
}
]
}
}
},
"jspmDefinition": {
"properties": {
"jspm": {
"$ref": "#/definitions/coreProperties"
}
}
}
},
"allOf": [
{
"$ref": "#/definitions/coreProperties"
},
{
"$ref": "#/definitions/jspmDefinition"
},
{
"anyOf": [
{
"properties": {
"bundleDependencies": {
"$ref": "#/definitions/bundledDependency"
}
},
"not": {
"properties": {
"bundledDependencies": {}
},
"required": [
"bundledDependencies"
]
}
},
{
"properties": {
"bundledDependencies": {
"$ref": "#/definitions/bundledDependency"
}
},
"not": {
"properties": {
"bundleDependencies": {}
},
"required": [
"bundleDependencies"
]
}
}
]
}
]
}

View File

@@ -0,0 +1,52 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "https://schemastore.azurewebsites.net/schemas/json/package.json"}
],
"properties": {
"name": {
"description": "The name of the package. Must start with \"flipper-plugin-\" prefix.",
"type": "string",
"maxLength": 214,
"pattern": "^flipper-plugin-[a-z0-9-._~]*$",
"errorMessage": "should start with \"flipper-plugin-\", e.g. \"flipper-plugin-example\""
},
"id": {
"type": "string",
"description": "Used as the plugin native identifier and must match the mobile plugin identifier. Also shown in the Flipper main sidebar if \"title\" property is omitted."
},
"flipperBundlerEntry": {
"type": "string",
"filePathExists": true,
"description": "Points to the source entry point which will be used for the plugin code bundling. \"flipper-pkg\" takes the path specified in this property as source, transpiles and bundles it, and saves the output to the path specified in property \"main\"."
},
"title": {
"type": "string",
"description": "Shown in the Flipper main sidebar as the human-readable name of the plugin. If omitted, \"id\" is used instead."
},
"icon": {
"type": "string",
"description": "Determines the plugin icon which is displayed in the main sidebar. The list of available icons is static for now and can be found in https://github.com/facebook/flipper/blob/master/desktop/static/icons.json."
},
"keywords": {
"description": "This helps people discover your package as it's listed in 'npm search'. To make the plugin discoverable in Flipper, the property must contain \"flipper-plugin\" keyword.",
"type": "array",
"items": {
"type": "string"
},
"contains": {
"type": "string",
"pattern": "flipper-plugin"
},
"errorMessage": "should contain keyword \"flipper-plugin\""
}
},
"required": [
"name",
"version",
"id",
"main",
"flipperBundlerEntry",
"keywords"
]
}

View File

@@ -1,12 +0,0 @@
/**
* 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
*/
test('tests are working', () => {
expect(true).toBeTruthy();
});

View File

@@ -0,0 +1,151 @@
/**
* 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 runLint from '../utils/runLint';
import fs from 'fs-extra';
const validPackageJson = {
$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'],
bugs: {
email: 'oncall+flipper@xmail.facebook.com',
url: 'https://fb.workplace.com/groups/flippersupport/',
},
};
beforeEach(() => {
jest.mock('fs-extra', () => jest.fn());
fs.pathExists = jest.fn().mockResolvedValue(true);
fs.pathExistsSync = jest.fn().mockReturnValue(true);
fs.lstatSync = jest.fn().mockReturnValue({
isFile: function () {
return true;
},
});
});
test('valid package json', async () => {
const json = JSON.stringify(validPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toBe(null);
});
test('$schema field is required', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
delete testPackageJson.$schema;
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
". should have required property \\"$schema\\" pointing to a supported schema URI, e.g.:
{
\\"$schema\\": \\"https://fbflipper.com/schemas/plugin-package/v2.json\\",
\\"name\\": \\"flipper-plugin-example\\",
...
}",
]
`);
});
test('supported schema is required', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.$schema =
'https://fbflipper.com/schemas/plugin-package/v1.json';
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
".$schema should point to a supported schema. Currently supported schemas:
- https://fbflipper.com/schemas/plugin-package/v2.json",
]
`);
});
test('name is required', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
delete testPackageJson.name;
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
". should have required property 'name'",
]
`);
});
test('name must start with "flipper-plugin-"', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.name = 'test-plugin';
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
"/name should start with \\"flipper-plugin-\\", e.g. \\"flipper-plugin-example\\"",
]
`);
});
test('keywords must contain "flipper-plugin"', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.keywords = ['flipper', 'network'];
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
"/keywords should contain keyword \\"flipper-plugin\\"",
]
`);
});
test('flippeBundlerEntry must point to an existing file', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.flipperBundlerEntry = 'unexisting/file';
fs.pathExistsSync = jest
.fn()
.mockImplementation((path) => !path.includes('unexisting/file'));
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
"/flipperBundlerEntry should point to a valid file",
]
`);
});
test('multiple validation errors reported', async () => {
const testPackageJson = Object.assign({}, validPackageJson);
testPackageJson.keywords = ['flipper'];
delete testPackageJson.flipperBundlerEntry;
const json = JSON.stringify(testPackageJson);
fs.readFile = jest.fn().mockResolvedValue(new Buffer(json));
const result = await runLint('dir');
expect(result).toMatchInlineSnapshot(`
Array [
". should have required property 'flipperBundlerEntry'",
"/keywords should contain keyword \\"flipper-plugin\\"",
]
`);
});

View File

@@ -16,7 +16,7 @@ import {runBuild, getPluginDetails} from 'flipper-pkg-lib';
export default class Bundle extends Command {
public static description = 'transpiles and bundles plugin';
public static examples = [`$ flipper-pkg bundle optional/path/to/directory`];
public static examples = [`$ flipper-pkg bundle path/to/plugin`];
public static args: args.IArg[] = [
{

View File

@@ -0,0 +1,48 @@
/**
* 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} from '@oclif/command';
import {args} from '@oclif/parser';
import path from 'path';
import runLint from '../utils/runLint';
export default class Lint extends Command {
public static description = 'validates a plugin package directory';
public static examples = [`$ flipper-pkg lint path/to/plugin`];
public static args: args.IArg[] = [
{
name: 'directory',
required: false,
default: '.',
description:
'Path to plugin package directory for linting. Defaults to the current working directory.',
},
];
public async run() {
const {args} = this.parse(Lint);
const inputDirectory: string = path.resolve(process.cwd(), args.directory);
try {
console.log(`⚙️ Validating ${inputDirectory}`);
const errors = await runLint(inputDirectory);
if (errors) {
this.error(
`Plugin package definition is invalid. See https://fbflipper.com/docs/extending/js-setup.html#plugin-definition for details.\n${errors.join(
'\n',
)}`,
);
}
} catch (error) {
this.error(error);
}
console.log('✅ Plugin package definition is valid');
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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 fs from 'fs-extra';
import path from 'path';
import Ajv from 'ajv';
import filePathExists from './validation/filePathExists';
const pluginPackageJsonSchemaUrl =
'https://fbflipper.com/schemas/plugin-package/v2.json';
const packageJsonSchemaUrl =
'https://schemastore.azurewebsites.net/schemas/json/package.json';
const schemasDir = path.resolve(__dirname, '..', '..', 'schemas');
const packageJsonSchemaPath = path.join(schemasDir, 'package.json');
const pluginPackageJsonSchemaPath = path.join(
schemasDir,
'plugin-package-v2.json',
);
export default async function runLint(
inputDirectory: string,
): Promise<null | string[]> {
const packageJsonPath = path.join(inputDirectory, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
return [
`package.json not found in plugin source directory ${inputDirectory}.`,
];
}
const packageJsonString = (await fs.readFile(packageJsonPath)).toString();
const packageJson = JSON.parse(packageJsonString);
if (!packageJson.$schema) {
return [
[
`. should have required property "$schema" pointing to a supported schema URI, e.g.:`,
`{`,
` "$schema": "${pluginPackageJsonSchemaUrl}",`,
` "name": "flipper-plugin-example",`,
` ...`,
`}`,
].join('\n'),
];
}
if (packageJson.$schema != pluginPackageJsonSchemaUrl) {
return [
[
`.$schema should point to a supported schema. Currently supported schemas:`,
`- ${pluginPackageJsonSchemaUrl}`,
].join('\n'),
];
}
const packageJsonSchema = await fs.readJson(packageJsonSchemaPath);
const pluginPackageJsonSchema = await fs.readJson(
pluginPackageJsonSchemaPath,
);
const ajv = new Ajv({
allErrors: true,
loadSchema,
schemaId: 'auto',
meta: true,
jsonPointers: true,
});
require('ajv-errors')(ajv);
ajv
.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'))
.addSchema(packageJsonSchema, packageJsonSchemaUrl)
.addSchema(pluginPackageJsonSchema, pluginPackageJsonSchemaUrl)
.addKeyword('filePathExists', filePathExists(inputDirectory));
const validate = await ajv.compileAsync(pluginPackageJsonSchema);
const valid = await validate(packageJson);
if (!valid) {
return validate.errors
? validate.errors.map(
(error) =>
`${error.dataPath === '' ? '.' : error.dataPath} ${
error.message || 'unspecified error'
}`,
)
: [];
}
return null;
}
async function loadSchema(_uri: string) {
return false;
}

View File

@@ -0,0 +1,43 @@
/**
* 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 ajv from 'ajv';
import path from 'path';
import fs from 'fs-extra';
export default function (inputDirectory: string) {
const filePathExists: ajv.KeywordDefinition = {
errors: true,
compile: function validatePathExists(schema: any) {
function filePathExistsValidator(value: any, dataPath: any) {
const it = filePathExistsValidator as ajv.SchemaValidateFunction;
if (!schema) {
return true;
}
it.errors = [];
const tpl = {
keyword: 'filePathExists',
dataPath,
schemaPath: '',
params: [],
};
const fullPath = path.resolve(inputDirectory, value);
if (!fs.pathExistsSync(fullPath) || !fs.lstatSync(fullPath).isFile()) {
it.errors.push({
...tpl,
message: `should point to a valid file`,
});
}
return it.errors.length === 0;
}
return filePathExistsValidator;
},
};
return filePathExists;
}

View File

@@ -4,7 +4,8 @@
"outDir": "lib",
"rootDir": "src",
"allowJs": true,
"esModuleInterop": true
"esModuleInterop": true,
"incremental": true
},
"references": [{"path": "../pkg-lib"}],
"include": ["src"],

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-network",
"id": "Network",
"specVersion": 2,
"flipperBundlerEntry": "index.tsx",
"main": "dist/index.js",
"title": "Network",

View File

@@ -1,8 +1,8 @@
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-sea-mammals",
"id": "sea-mammals",
"private": true,
"specVersion": 2,
"version": "2.0.0",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
@@ -17,7 +17,8 @@
"email": "realpassy@fb.com"
},
"scripts": {
"prepack": "flipper-pkg bundle"
"lint": "flipper-pkg lint",
"prepack": "flipper-pkg lint && flipper-pkg bundle"
},
"peerDependencies": {
"flipper": "0.39.0"

View File

@@ -1606,19 +1606,7 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
"@oclif/command@^1", "@oclif/command@^1.5.1", "@oclif/command@^1.5.13":
version "1.5.19"
resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.19.tgz#13f472450eb83bd6c6871a164c03eadb5e1a07ed"
integrity sha512-6+iaCMh/JXJaB2QWikqvGE9//wLEVYYwZd5sud8aLoLKog1Q75naZh2vlGVtg5Mq/NqpqGQvdIjJb3Bm+64AUQ==
dependencies:
"@oclif/config" "^1"
"@oclif/errors" "^1.2.2"
"@oclif/parser" "^3.8.3"
"@oclif/plugin-help" "^2"
debug "^4.1.1"
semver "^5.6.0"
"@oclif/command@^1.5.10":
"@oclif/command@^1", "@oclif/command@^1.5.1", "@oclif/command@^1.5.10", "@oclif/command@^1.5.13":
version "1.5.20"
resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.20.tgz#bb0693586d7d66a457c49b719e394c02ff0169a7"
integrity sha512-lzst5RU/STfoutJJv4TLE/cm1WtW3xy6Aqvqy3r1lPsGdNifgbEq4dCOYyc/ZEuhV/IStQLDFTnAlqTdolkz1Q==
@@ -1630,17 +1618,7 @@
debug "^4.1.1"
semver "^5.6.0"
"@oclif/config@^1", "@oclif/config@^1.12.12":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.14.0.tgz#0af93facd5c5087f804489f1603c4f3bc0c45014"
integrity sha512-KsOP/mx9lzTah+EtGqLUXN3PDL0J3zb9/dTneFyiUK2K6T7vFEGhV6OasmqTh4uMZHGYTGrNPV8x/Yw6qZNL6A==
dependencies:
"@oclif/errors" "^1.0.0"
"@oclif/parser" "^3.8.0"
debug "^4.1.1"
tslib "^1.9.3"
"@oclif/config@^1.12.8":
"@oclif/config@^1", "@oclif/config@^1.12.12", "@oclif/config@^1.12.8":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.15.1.tgz#39950c70811ab82d75bb3cdb33679ed0a4c21c57"
integrity sha512-GdyHpEZuWlfU8GSaZoiywtfVBsPcfYn1KuSLT1JTfvZGpPG6vShcGr24YZ3HG2jXUFlIuAqDcYlTzOrqOdTPNQ==
@@ -2522,17 +2500,22 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-errors@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
ajv-keywords@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5:
version "6.12.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
dependencies:
fast-deep-equal "^2.0.1"
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
@@ -5285,10 +5268,10 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-deep-equal@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
fast-diff@^1.1.2:
version "1.2.0"

View File

@@ -51,9 +51,9 @@ After `yarn init` finishes, create an `src/index.tsx` file which will be the ent
Example `package.json`:
```
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-myplugin",
"id": "myplugin",
"specVersion": 2,
"version": "1.0.0",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
@@ -65,12 +65,14 @@ Example `package.json`:
"email": "you@example.com"
},
"scripts": {
"prepack": "flipper-pkg bundle"
"lint": "flipper-pkg lint",
"prepack": "flipper-pkg lint && flipper-pkg bundle"
}
"dependencies": {
"peerDependencies": {
"flipper": "latest"
},
"devDependencies": {
"flipper": "latest",
"flipper-pkg": "latest"
}
}
@@ -78,9 +80,9 @@ Example `package.json`:
Important attributes of `package.json`:
- `name` Npm package name. Should start with `flipper-plugin-` by convention, so Flipper plugins can be easily found on npm.
- `$schema` must contain URI identifying scheme according to which the plugin is defined. Currently, Flipper supports plugins defined by the specification version 2 (https://fbflipper.com/schemas/plugin-package/v2.json), while version 1 is being deprecated.
- `specVersion` Version of the Flipper plugin specification. Currently, Flipper supports plugins defined using version 2 of the specification which is described in the current section.
- `name` Npm package name. Should start with `flipper-plugin-` by convention, so Flipper plugins can be easily found on npm.
- `id` Used as the plugin native identifier and **must match the mobile plugin identifier**.
@@ -109,6 +111,10 @@ export default class extends FlipperPlugin {
}
```
### Validation
Plugin definition can be validated using command `flipper-pkg lint`. The command shows all the mismatches which should be fixed to make plugin definition valid.
### npm dependencies
If you need any dependencies in your plugin, you can install them using `yarn add`.

View File

@@ -18,9 +18,9 @@ A valid example `package.json` could look like this:
```json
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-sea-mammals",
"id": "sea-mammals",
"specVersion": 2,
"version": "2.0.0",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
@@ -30,7 +30,7 @@ A valid example `package.json` could look like this:
"title": "Sea Mammals",
"category": "Example Plugin",
"scripts": {
"prepack": "flipper-pkg bundle"
"prepack": "flipper-pkg lint && flipper-pkg bundle"
},
"dependencies": {
"flipper": "latest"

View File

@@ -43,19 +43,21 @@ $ yarn init
```
Open the `package.json` and edit it. There are a few important things:
1) "name" must start with "flipper-plugin-"
2) "keywords" must contain "flipper-plugin"
3) "id" must be the same as used on native side, e.g. returned by getId() method in Android plugin. In our case that is "sea-mammals".
4) "specVersion" must contain the version of the specification according to which the plugin is defined. Currently, Flipper supports plugins defined by the specification version 2, while version 1 is being deprecated.
5) "title" and "icon" are optional fields specifying the plugin item appearance in the Flipper sidebar.
1) "$schema" must contain URI identifying scheme according to which the plugin is defined. Currently, Flipper supports plugins defined by the specification version 2 (https://fbflipper.com/schemas/plugin-package/v2.json), while version 1 is being deprecated.
2) "name" must start with "flipper-plugin-"
3) "keywords" must contain "flipper-plugin"
4) "id" must be the same as used on native side, e.g. returned by getId() method in Android plugin. In our case that is "sea-mammals".
5) "flipperBundlerEntry" must point to the source entry point which will be used by "flipper-pkg" to produce the plugin bundle.
6) "main" must point to the place where the produced bundle will be written.
7) "title" and "icon" are optional fields specifying the plugin item appearance in the Flipper sidebar.
For instance:
```json
{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-sea-mammals",
"id": "sea-mammals",
"specVersion": 2,
"version": "2.0.0",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
@@ -65,7 +67,8 @@ For instance:
"title": "Sea Mammals",
"category": "Example Plugin",
"scripts": {
"prepack": "flipper-pkg bundle"
"lint": "flipper-pkg lint",
"prepack": "flipper-pkg lint && flipper-pkg bundle"
},
"peerDependencies": {
"flipper": "latest"
@@ -78,4 +81,6 @@ For instance:
```
*See [package.json](https://github.com/facebook/flipper/blob/master/desktop/plugins/seamammals/package.json)*
To ensure there are no errors in the defined plugin, install packages (using `yarn install` or `npm install`) and execute script `lint` (`yarn lint` or `npm run lint`) which shows all the mismatches that should be fixed to make the plugin definition valid.
Now that our package has been set up, we are ready to build a UI for our plugin. Either by using a standardized table-based plugin, or by creating a custom UI.

1
website/.gitignore vendored
View File

@@ -2,6 +2,7 @@ node_modules
.DS_Store
lib/core/metadata.js
lib/core/MetadataBlog.js
static/schemas
website/translated_docs
build/
website/yarn.lock

View File

@@ -1,7 +1,8 @@
{
"scripts": {
"start": "yarn generate-uidocs && docusaurus start",
"build": "yarn generate-uidocs && docusaurus build",
"copy-schema": "fcli ensure static/schemas/plugin-package && fcli copy ../desktop/pkg/schemas/plugin-package-v2.json static/schemas/plugin-package/v2.json -o",
"start": "yarn copy-schema && yarn generate-uidocs && docusaurus start",
"build": "yarn copy-schema && yarn generate-uidocs && docusaurus build",
"publish-gh-pages": "docusaurus deploy",
"write-translations": "docusaurus write-translations",
"version": "docusaurus version",
@@ -13,14 +14,14 @@
"@docusaurus/core": "^2.0.0-alpha.50",
"@docusaurus/preset-classic": "^2.0.0-alpha.50",
"docblock-parser": "^1.0.0",
"file-cli": "^1.2.0",
"glob": "^7.1.3",
"react-docgen": "^5.2.1",
"classnames": "^2.2.6",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"dependencies": {
},
"dependencies": {},
"resolutions": {
"minimist": "1.2.3",
"kind-of": "6.0.3"

View File

@@ -2558,6 +2558,15 @@ cliui@^4.0.0:
strip-ansi "^4.0.0"
wrap-ansi "^2.0.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
coa@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
@@ -3888,6 +3897,14 @@ figures@^3.0.0:
dependencies:
escape-string-regexp "^1.0.5"
file-cli@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/file-cli/-/file-cli-1.2.0.tgz#d57f37f80e87c6f63f959893fd130c0c378ec61d"
integrity sha512-IfYljsbNFCgvg7zwSWNeLYqG87hcMiBGU6EEvdmgVPZ1HX5UdjeR4/tKP5a+99rxKhEDt4+KNLruuvc8p7RDJQ==
dependencies:
fs-extra "^8.1.0"
yargs "^15.1.0"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -3951,7 +3968,7 @@ find-cache-dir@^3.0.0, find-cache-dir@^3.2.0:
make-dir "^3.0.2"
pkg-dir "^4.1.0"
find-up@4.1.0, find-up@^4.0.0:
find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
@@ -4115,6 +4132,11 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -8014,6 +8036,11 @@ require-main-filename@^1.0.1:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@@ -8655,7 +8682,7 @@ string-width@^2.0.0, string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string-width@^4.1.0:
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
@@ -9607,7 +9634,7 @@ wrap-ansi@^2.0.0:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi@^6.0.0:
wrap-ansi@^6.0.0, wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
@@ -9668,6 +9695,14 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^18.1.1:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
@@ -9686,6 +9721,23 @@ yargs@12.0.5:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
yargs@^15.1.0:
version "15.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.1"
zepto@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"