diff --git a/desktop/pkg/README.md b/desktop/pkg/README.md index fdd534e4b..a7e022a2c 100644 --- a/desktop/pkg/README.md +++ b/desktop/pkg/README.md @@ -26,6 +26,7 @@ USAGE * [`flipper-pkg bundle [DIRECTORY]`](#flipper-pkg-bundle-directory) * [`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 pack [DIRECTORY]`](#flipper-pkg-pack-directory) @@ -63,6 +64,24 @@ OPTIONS _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_ +## `flipper-pkg init [DIRECTORY]` + +initializes 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. + +EXAMPLE + $ flipper-pkg init path/to/plugin +``` + +_See code: [src/commands/init.ts](https://github.com/facebook/flipper/blob/v0.39.0/src/commands/init.ts)_ + ## `flipper-pkg lint [DIRECTORY]` validates a plugin package directory diff --git a/desktop/pkg/package.json b/desktop/pkg/package.json index c813fe198..2d496a8db 100644 --- a/desktop/pkg/package.json +++ b/desktop/pkg/package.json @@ -22,7 +22,8 @@ "cli-ux": "^5.4.5", "flipper-pkg-lib": "0.39.0", "fs-extra": "^8.1.0", - "inquirer": "^7.0.5" + "inquirer": "^7.1.0", + "recursive-readdir": "^2.2.2" }, "devDependencies": { "@oclif/dev-cli": "^1", diff --git a/desktop/pkg/src/commands/init.ts b/desktop/pkg/src/commands/init.ts new file mode 100644 index 000000000..373da47a0 --- /dev/null +++ b/desktop/pkg/src/commands/init.ts @@ -0,0 +1,91 @@ +/** + * 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 fs from 'fs-extra'; +import recursiveReaddirImport from 'recursive-readdir'; +import {promisify} from 'util'; +import inquirer from 'inquirer'; +const recursiveReaddir = promisify(recursiveReaddirImport); + +const templateDir = path.resolve(__dirname, '..', '..', 'templates', 'plugin'); +const templateExt = '.template'; + +export default class Init extends Command { + public static description = + 'initializes a Flipper desktop plugin template in the provided directory'; + + public static examples = [`$ flipper-pkg init path/to/plugin`]; + + public static args: args.IArg[] = [ + { + name: 'directory', + required: false, + default: '.', + description: + 'Path to the directory where the plugin package template should be initialized. Defaults to the current working directory.', + }, + ]; + + public async run() { + const {args} = this.parse(Init); + const outputDirectory: string = path.resolve(process.cwd(), args.directory); + console.log( + `⚙️ Initializing Flipper desktop template in ${outputDirectory}`, + ); + const defaultID = path.basename(outputDirectory); + const idQuestion: inquirer.QuestionCollection = [ + { + type: 'input', + name: 'id', + message: + 'ID (must match native plugin ID, e.g. returned by getId() in Android plugin):', + default: defaultID, + }, + ]; + const id: string = (await inquirer.prompt(idQuestion)).id; + const titleQuestion: inquirer.QuestionCollection = [ + { + type: 'input', + name: 'title', + message: 'Title (will be shown in the Flipper main sidebar):', + default: id, + }, + ]; + const title: string = (await inquirer.prompt(titleQuestion)).title; + const packageNameSuffix = id.toLowerCase().replace(' ', '-'); + const templateItems = await recursiveReaddir(templateDir); + for (const item of templateItems) { + const lstat = await fs.lstat(item); + if (lstat.isFile()) { + const file = path.relative(templateDir, item); + const dir = path.dirname(file); + const newDir = path.join(outputDirectory, dir); + const newFile = file.endsWith('.template') + ? path.join( + outputDirectory, + file.substring(0, file.length - templateExt.length), + ) + : path.join(outputDirectory, file); + await fs.ensureDir(newDir); + const content = (await fs.readFile(item)) + .toString() + .replace('{{id}}', id) + .replace('{{title}}', title) + .replace('{{package_name_suffix}}', packageNameSuffix); + await fs.writeFile(newFile, content); + } + } + console.log( + `✅ Plugin template initialized. Package name: flipper-plugin-${packageNameSuffix}.`, + ); + } +} diff --git a/desktop/pkg/templates/plugin/package.json.template b/desktop/pkg/templates/plugin/package.json.template new file mode 100644 index 000000000..3e5861526 --- /dev/null +++ b/desktop/pkg/templates/plugin/package.json.template @@ -0,0 +1,25 @@ +{ + "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json", + "name": "flipper-plugin-{{package_name_suffix}}", + "id": "{{id}}", + "version": "1.0.0", + "main": "dist/bundle.js", + "flipperBundlerEntry": "src/index.tsx", + "license": "MIT", + "keywords": [ + "flipper-plugin" + ], + "icon": "apps", + "title": "{{title}}", + "scripts": { + "lint": "flipper-pkg lint", + "prepack": "flipper-pkg lint && flipper-pkg bundle" + }, + "peerDependencies": { + "flipper": "latest" + }, + "devDependencies": { + "flipper": "latest", + "flipper-pkg": "latest" + } +} diff --git a/desktop/pkg/templates/plugin/src/index.tsx.template b/desktop/pkg/templates/plugin/src/index.tsx.template new file mode 100644 index 000000000..cf1abed5e --- /dev/null +++ b/desktop/pkg/templates/plugin/src/index.tsx.template @@ -0,0 +1,47 @@ +import React from 'react'; +import {FlipperPlugin, FlexColumn, KeyboardActions} from 'flipper'; + +type State = {}; + +type Data = {}; + +type PersistedState = { + data: Array; +}; + +export default class extends FlipperPlugin { + static keyboardActions: KeyboardActions = ['clear']; + + static defaultPersistedState: PersistedState = { + data: [], + }; + + static persistedStateReducer = ( + persistedState: PersistedState, + method: string, + data: Data, + ): PersistedState => { + return { + ...persistedState, + data: persistedState.data.concat([data]), + }; + }; + + state = {}; + + onKeyboardAction = (action: string) => { + if (action === 'clear') { + this.props.setPersistedState({data: []}); + } + }; + + render() { + return ( + + {this.props.persistedState.data.map((d) => ( +
{d}
+ ))} +
+ ); + } +} diff --git a/desktop/static/package.json b/desktop/static/package.json index 4dcd1cd80..c41e24e2a 100644 --- a/desktop/static/package.json +++ b/desktop/static/package.json @@ -9,8 +9,9 @@ "expand-tilde": "^2.0.2", "fb-watchman": "^2.0.0", "fix-path": "^3.0.0", - "fs-extra": "^8.1.0", "flipper-pkg-lib": "0.39.0", + "fs-extra": "^8.1.0", + "ignore": "^5.1.4", "mem": "^6.0.0", "mkdirp": "^1.0.0", "p-filter": "^2.1.0", @@ -19,7 +20,6 @@ "uuid": "^7.0.1", "ws": "^7.2.3", "xdg-basedir": "^4.0.0", - "ignore": "^5.1.4", "yargs": "^15.3.1" } } diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 8b4b7a3ec..50c89dc3a 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -6243,7 +6243,7 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^7.0.0, inquirer@^7.0.5: +inquirer@^7.0.0, inquirer@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== diff --git a/docs/extending/jssetup.mdx b/docs/extending/jssetup.mdx index 0a3edf6fa..ad3335700 100644 --- a/docs/extending/jssetup.mdx +++ b/docs/extending/jssetup.mdx @@ -36,19 +36,28 @@ Plugin File example structure: ## Plugin Definition +### flipper-pkg + +CLI tool `flipper-pkg` helps to initialize, validate, and package Flipper desktop plugins. + +The tool is published to npm and can be installed as a `devDependency` for the plugin package, or as a global CLI tool: +``` +yarn global add flipper-pkg +``` +or +``` +npm install flipper-pkg --global +``` + +### Package Format + All Flipper Desktop plugins must be self-contained in a directory. This directory must contain at a minimum package.json and entry source file, e.g.: * package.json * src/index.tsx -The best way to initialize a JS plugin is to create a directory, and run `yarn init` inside it. By convention, the `name` of a Flipper plugin package should start with `flipper-plugin-`, e.g. `flipper-plugin-myplugin`. +The best way to initialize a JS plugin is to create a directory, and run `flipper-pkg init` inside it ("flipper-pkg" should be installed globally before that). It will ask few questions and initialize the plugin for you. -Make sure that the `id` field in your package.json is the same as the identifier of the client plugin, e.g. if your Java plugin returns `myplugin` from its `getId()` method, the `id` field in your `package.json` should also be `myplugin`. - -Flipper has [tooling for transpiling and bundling](#transpiling-and-bundling) which allows writing plugins in plain ES6 JavaScript, [Flow](https://flow.org/) or [TypeScript](https://www.typescriptlang.org/) but we recommend you use **TypeScript** for the best development experience. We also recommend you use the file extension `.tsx` when using TypeScript which adds support for inline React expressions. - -After `yarn init` finishes, create an `src/index.tsx` file which will be the entry point to your plugin. An example `package.json` file could look like this: - -Example `package.json`: +After `flipper-pkg init` finished, you should have files `package.json` and `src/index.tsx` files in the directory. The first file is the plugin package manifest and the second is the entry point to your plugin. An example `package.json` file could look like this: ``` { "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json", @@ -84,7 +93,7 @@ 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. -- `id` Used as the plugin native identifier and **must match the mobile plugin identifier**. +- `id` Used as the plugin native identifier and **must match the mobile plugin identifier**, e.g. returned by `getId` method of your Java plugin. - `main` Points to the plugin bundle which will be loaded by Flipper. The "flipper-pkg" utility uses this field to determine output location during plugin bundling. @@ -115,6 +124,10 @@ export default class extends FlipperPlugin { 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. +### Transpilation + +Flipper has [tooling for transpiling and bundling](#transpiling-and-bundling) which allows writing plugins in plain ES6 JavaScript, [Flow](https://flow.org/) or [TypeScript](https://www.typescriptlang.org/) but we recommend you use **TypeScript** for the best development experience. We also recommend you use the file extension `.tsx` when using TypeScript which adds support for inline React expressions. + ### npm dependencies If you need any dependencies in your plugin, you can install them using `yarn add`. diff --git a/docs/tutorial/js-setup.mdx b/docs/tutorial/js-setup.mdx index b69080be6..51837e2fa 100644 --- a/docs/tutorial/js-setup.mdx +++ b/docs/tutorial/js-setup.mdx @@ -29,20 +29,31 @@ Your file will then look something like this: } ``` +## Installing flipper-pkg + +`flipper-pkg` tool helps to define, validate and package Flipper desktop plugins. You can install it globally using: +``` +yarn global add flipper-pkg +``` +or +``` +npm install flipper-pkg --global +``` + ## Creating the Plugin Package -With the loading part out of the way, we can create the new plugin. For that, first -create a new folder inside the custom plugins directory. Then use `yarn init` (`npm init` if that's more your style) -to initialise a new JavaScript package: +With the loading part out of the way, we can create the new plugin. For that, first create a new folder inside the custom plugins directory. Then use `flpper-pkg init` to initialise a new Flipper desktop plugin package: ```bash $ cd ~/Flipper/custom-plugins/ $ mkdir sea-mammals $ cd sea-mammals -$ yarn init +$ flipper-pkg init ``` -Open the `package.json` and edit it. There are a few important things: +The tool will ask you to provide "id" and "title" for your plugin. Use "sea-mammals" as "id" and "Sea Mammals" as "title". After that the tool will create two files in the directory: `package.json` and `src/index.tsx`. + +Open the `package.json` to check the fields: 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"