diff --git a/desktop/pkg/package.json b/desktop/pkg/package.json index 2a2196bf9..00abe2579 100644 --- a/desktop/pkg/package.json +++ b/desktop/pkg/package.json @@ -47,7 +47,8 @@ "postpack": "rimraf oclif.manifest.json", "prepack": "yarn reset && yarn build && oclif-dev manifest && oclif-dev readme", "run": "yarn build && bin/run", - "version": "oclif-dev readme && hg add README.md" + "version": "oclif-dev readme && hg add README.md", + "test": "yarn jest" }, "engines": { "node": ">=8.0.0" diff --git a/desktop/pkg/src/__tests__/runInit.node.ts b/desktop/pkg/src/__tests__/runInit.node.ts new file mode 100644 index 000000000..38597e2ed --- /dev/null +++ b/desktop/pkg/src/__tests__/runInit.node.ts @@ -0,0 +1,141 @@ +/** + * 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 fse from 'fs-extra'; + +import {initTemplate} from '../commands/init'; + +let files: Record = {}; + +beforeEach(() => { + function ensureDir() { + // no implementation + } + function writeFile(name: string, contents: string) { + files[name] = contents; + } + + files = {}; + jest.mock('fs-extra', () => jest.fn()); + // @ts-ignore + fse.ensureDir = ensureDir; + // @ts-ignore + fse.writeFile = writeFile; +}); + +afterEach(() => { + // @ts-ignore + // fse.ensureDir.mockRestore(); + // @ts-ignore + // fs.writeFile.mockRestore(); +}); + +test('It generates the correct files', async () => { + await initTemplate('my weird Package %name. etc', 'Nice title', '/dev/null'); + expect(files).toMatchInlineSnapshot(` + Object { + "/dev/null/.gitignore": "node_modules + dist/ + ", + "/dev/null/package.json": "{ + \\"$schema\\": \\"https://fbflipper.com/schemas/plugin-package/v2.json\\", + \\"name\\": \\"flipper-plugin-my-weird-package-name-etc\\", + \\"id\\": \\"my weird Package %name. etc\\", + \\"version\\": \\"1.0.0\\", + \\"main\\": \\"dist/bundle.js\\", + \\"flipperBundlerEntry\\": \\"src/index.tsx\\", + \\"license\\": \\"MIT\\", + \\"keywords\\": [ + \\"flipper-plugin\\" + ], + \\"icon\\": \\"apps\\", + \\"title\\": \\"Nice title\\", + \\"scripts\\": { + \\"lint\\": \\"flipper-pkg lint\\", + \\"prepack\\": \\"flipper-pkg lint && flipper-pkg bundle\\", + \\"build\\": \\"flipper-pkg bundle\\", + \\"watch\\": \\"flipper-pkg bundle --watch\\" + }, + \\"peerDependencies\\": { + \\"flipper\\": \\"latest\\" + }, + \\"devDependencies\\": { + \\"@types/react\\": \\"latest\\", + \\"@types/react-dom\\": \\"latest\\", + \\"flipper\\": \\"latest\\", + \\"flipper-pkg\\": \\"latest\\" + } + } + ", + "/dev/null/src/index.tsx": "import React from 'react'; + import {FlipperPlugin, View, 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) => ( +
{JSON.stringify(d, null, 2)}
+ ))} +
+ ) + } + } + ", + "/dev/null/tsconfig.json": "{ + \\"compilerOptions\\": { + \\"target\\": \\"ES2017\\", + \\"module\\": \\"ES6\\", + \\"jsx\\": \\"react\\", + \\"sourceMap\\": true, + \\"noEmit\\": true, + \\"strict\\": true, + \\"moduleResolution\\": \\"node\\", + \\"esModuleInterop\\": true, + \\"forceConsistentCasingInFileNames\\": true + }, + \\"files\\": [\\"src/index.tsx\\"] + } + ", + } + `); +}); diff --git a/desktop/pkg/src/commands/init.ts b/desktop/pkg/src/commands/init.ts index 16cdbb323..45897003a 100644 --- a/desktop/pkg/src/commands/init.ts +++ b/desktop/pkg/src/commands/init.ts @@ -56,52 +56,61 @@ export default class Init extends Command { }, ]; const pluginDirectory: string = path.resolve(process.cwd(), args.directory); - const title: string = (await inquirer.prompt(titleQuestion)).title; - const packageNameSuffix = id.toLowerCase().replace(' ', '-'); - const templateItems = await recursiveReaddir(templateDir); - const outputDirectory = path.join( - pluginDirectory, - 'flipper-plugin-' + packageNameSuffix, - ); + const packageName = getPackageNameFromId(id); + const outputDirectory = path.join(pluginDirectory, packageName); if (fs.existsSync(outputDirectory)) { console.error(`Directory '${outputDirectory}' already exists`); process.exit(1); } - await fs.ensureDir(outputDirectory); - console.log( `⚙️ Initializing Flipper desktop template in ${outputDirectory}`, ); + await fs.ensureDir(outputDirectory); + initTemplate(id, title, outputDirectory); - 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(`⚙️ Installing dependencies`); spawnSync('yarn', ['install'], {cwd: outputDirectory, stdio: [0, 1, 2]}); + console.log( - `✅ Plugin directory initialized. Package name: flipper-plugin-${packageNameSuffix}.`, - ); - console.log( - ` Run 'cd flipper-plugin-${packageNameSuffix} && yarn watch' to get started!`, + `✅ Plugin directory initialized. Package name: ${packageName}.`, ); + console.log(` Run 'cd ${packageName} && yarn watch' to get started!`); + } +} + +function getPackageNameFromId(id: string): string { + return 'flipper-plugin-' + id.toLowerCase().replace(/[^a-zA-Z0-9\-_]+/g, '-'); +} + +export async function initTemplate( + id: string, + title: string, + outputDirectory: string, +) { + const packageName = getPackageNameFromId(id); + 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(templateExt) + ? 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}}', packageName); + await fs.writeFile(newFile, content); + } } } diff --git a/desktop/pkg/templates/plugin/package.json.template b/desktop/pkg/templates/plugin/package.json.template index 987519ce7..84f5ddcb6 100644 --- a/desktop/pkg/templates/plugin/package.json.template +++ b/desktop/pkg/templates/plugin/package.json.template @@ -1,6 +1,6 @@ { "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json", - "name": "flipper-plugin-{{package_name_suffix}}", + "name": "{{package_name}}", "id": "{{id}}", "version": "1.0.0", "main": "dist/bundle.js",