Move desktop-related code to "desktop" subfolder (#872)
Summary: Pull Request resolved: https://github.com/facebook/flipper/pull/872 Move all the JS code related to desktop app to "desktop" subfolder. The structure of "desktop" folder: - `src` - JS code of Flipper desktop app executing in Electron Renderer (Chrome) process. This folder also contains all the Flipper plugins in subfolder "src/plugins". - `static` - JS code of Flipper desktop app bootstrapping executing in Electron Main (Node.js) process - `pkg` - Flipper packaging lib and CLI tool - `doctor` - Flipper diagnostics lib and CLI tool - `scripts` - Build scripts for Flipper desktop app - `headless` - Headless version of Flipper app - `headless-tests` - Integration tests running agains Flipper headless version Reviewed By: passy Differential Revision: D20249304 fbshipit-source-id: 9a51c63b51b92b758a02fc8ebf7d3d116770efe9
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a60e6fee87
commit
85c13bb1f3
14
desktop/.eslintignore
Normal file
14
desktop/.eslintignore
Normal file
@@ -0,0 +1,14 @@
|
||||
*.d.ts
|
||||
*.bundle.js
|
||||
src/fb/plugins/relaydevtools/relay-devtools/*
|
||||
latest
|
||||
resources
|
||||
templates
|
||||
node_modules
|
||||
flow-typed
|
||||
lib
|
||||
!.eslintrc.js
|
||||
dist
|
||||
website/build
|
||||
react-native/ReactNativeFlipperExample
|
||||
scripts/generate-changelog.js
|
||||
75
desktop/.eslintrc.js
Normal file
75
desktop/.eslintrc.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const fbjs = require('eslint-config-fbjs');
|
||||
|
||||
// enforces copyright header and @format directive to be present in every file
|
||||
const pattern = /^\*\r?\n[\S\s]*Facebook[\S\s]* \* @format\r?\n/;
|
||||
|
||||
const prettierConfig = {
|
||||
requirePragma: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: false,
|
||||
jsxBracketSameLine: true,
|
||||
parser: 'flow',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
root: true,
|
||||
extends: 'fbjs',
|
||||
plugins: [...fbjs.plugins, 'header', 'prettier', '@typescript-eslint'],
|
||||
rules: {
|
||||
// disable rules from eslint-config-fbjs
|
||||
'react/react-in-jsx-scope': 0, // not needed with our metro implementation
|
||||
'no-new': 0, // new keyword needed e.g. new Notification
|
||||
'no-catch-shadow': 0, // only relevant for IE8 and below
|
||||
'no-bitwise': 0, // bitwise operations needed in some places
|
||||
'consistent-return': 0,
|
||||
'no-var': 2,
|
||||
'prefer-const': [2, {destructuring: 'all'}],
|
||||
'prefer-spread': 1,
|
||||
'prefer-rest-params': 1,
|
||||
'max-len': 0, // lets prettier take care of this
|
||||
indent: 0, // lets prettier take care of this
|
||||
'no-console': 0, // we're setting window.console in App.js
|
||||
'no-multi-spaces': 2,
|
||||
'prefer-promise-reject-errors': 1,
|
||||
'no-throw-literal': 'error',
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-semi': 2,
|
||||
'no-unsafe-negation': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-rename': 2,
|
||||
|
||||
// additional rules for this project
|
||||
'header/header': [2, 'block', {pattern}],
|
||||
'prettier/prettier': [2, prettierConfig],
|
||||
'flowtype/object-type-delimiter': [0],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.tsx', '*.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
rules: {
|
||||
'prettier/prettier': [2, {...prettierConfig, parser: 'typescript'}],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
1,
|
||||
{
|
||||
ignoreRestSiblings: true,
|
||||
varsIgnorePattern: '^_',
|
||||
argsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
6
desktop/.npmignore
Normal file
6
desktop/.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
**
|
||||
!src/**/*
|
||||
__tests__
|
||||
.DS_Store
|
||||
src/plugins/**/*
|
||||
src/fb/**/*
|
||||
51
desktop/.vscode/launch.json
vendored
Normal file
51
desktop/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to Running Renderer",
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"port": 9222,
|
||||
"webRoot": "/",
|
||||
"url": "http://localhost:3000/index.dev.html"
|
||||
},
|
||||
{
|
||||
"name": "Attach to Running Main",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Current Jest Suite",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||
"args": ["--runInBand", "${relativeFile}"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Current Script",
|
||||
"args": ["${file}"],
|
||||
"env": {
|
||||
"TS_NODE_FILES": "true"
|
||||
},
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"runtimeArgs": [
|
||||
"--require",
|
||||
"ts-node/register"
|
||||
]
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Attach to All",
|
||||
"configurations": [
|
||||
"Attach to Running Main",
|
||||
"Attach to Running Renderer"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1
desktop/.yarnrc
Normal file
1
desktop/.yarnrc
Normal file
@@ -0,0 +1 @@
|
||||
--install.mutex network
|
||||
25
desktop/__mocks__/electron.tsx
Normal file
25
desktop/__mocks__/electron.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
remote: {
|
||||
process: {
|
||||
env: {},
|
||||
},
|
||||
app: {
|
||||
getPath: (path: string) => `/${path}`,
|
||||
getAppPath: process.cwd,
|
||||
getVersion: () => '0.9.99',
|
||||
relaunch: () => {},
|
||||
exit: () => {},
|
||||
},
|
||||
getCurrentWindow: () => ({isFocused: () => true}),
|
||||
},
|
||||
ipcRenderer: {},
|
||||
};
|
||||
2
desktop/doctor/.gitignore
vendored
Normal file
2
desktop/doctor/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/lib
|
||||
node_modules/
|
||||
1
desktop/doctor/.ignore
Symbolic link
1
desktop/doctor/.ignore
Symbolic link
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
8
desktop/doctor/README.md
Normal file
8
desktop/doctor/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Flipper Doctor
|
||||
|
||||
This package exists for running checks to diagnose and potentially fix issues affecting the operation of Flipper.
|
||||
It's designed to be primarily used programmatically but may also expose a CLI interface.
|
||||
|
||||
## Usage
|
||||
`cd doctor`
|
||||
`yarn run run`
|
||||
7
desktop/doctor/jestconfig.json
Normal file
7
desktop/doctor/jestconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
||||
48
desktop/doctor/package.json
Normal file
48
desktop/doctor/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "flipper-doctor",
|
||||
"version": "0.7.0",
|
||||
"description": "Utility for checking for issues with a flipper installation",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/fb-watchman": "2.0.0",
|
||||
"@types/jest": "^24.0.21",
|
||||
"@typescript-eslint/eslint-plugin": "^2.8.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-flowtype": "^4.5.2",
|
||||
"eslint-plugin-header": "^3.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.19.1",
|
||||
"ts-jest": "^24.1.0",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepare": "yarn run build",
|
||||
"prepublishOnly": "yarn test && yarn run lint",
|
||||
"preversion": "yarn run lint",
|
||||
"test": "jest --config jestconfig.json --passWithNoTests",
|
||||
"lint": "eslint -c ../.eslintrc.js src/**/* --ext .js,.ts && tsc --noemit",
|
||||
"fix": "eslint -c ../.eslintrc.js src/**/* --fix --ext .js,.ts",
|
||||
"run": "yarn run build && node lib/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"keywords": [
|
||||
"Flipper",
|
||||
"Doctor"
|
||||
],
|
||||
"author": "Facebook, Inc",
|
||||
"dependencies": {
|
||||
"@types/node": "^13.7.7",
|
||||
"envinfo": "^7.4.0",
|
||||
"fb-watchman": "^2.0.1"
|
||||
}
|
||||
}
|
||||
36
desktop/doctor/src/cli.ts
Normal file
36
desktop/doctor/src/cli.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 {getHealthchecks} from './index';
|
||||
import {getEnvInfo} from './environmentInfo';
|
||||
|
||||
(async () => {
|
||||
const environmentInfo = await getEnvInfo();
|
||||
console.log(JSON.stringify(environmentInfo));
|
||||
const healthchecks = getHealthchecks();
|
||||
const results = await Promise.all(
|
||||
Object.entries(healthchecks).map(async ([key, category]) => [
|
||||
key,
|
||||
category.isSkipped
|
||||
? category
|
||||
: {
|
||||
label: category.label,
|
||||
results: await Promise.all(
|
||||
category.healthchecks.map(async ({key, label, run}) => ({
|
||||
key,
|
||||
label,
|
||||
result: await run(environmentInfo),
|
||||
})),
|
||||
),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
})();
|
||||
48
desktop/doctor/src/environmentInfo.ts
Normal file
48
desktop/doctor/src/environmentInfo.ts
Normal 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 {run} from 'envinfo';
|
||||
|
||||
export type EnvironmentInfo = {
|
||||
SDKs: {
|
||||
'iOS SDK': {
|
||||
Platforms: string[];
|
||||
};
|
||||
'Android SDK':
|
||||
| {
|
||||
'API Levels': string[] | 'Not Found';
|
||||
'Build Tools': string[] | 'Not Found';
|
||||
'System Images': string[] | 'Not Found';
|
||||
'Android NDK': string | 'Not Found';
|
||||
}
|
||||
| 'Not Found';
|
||||
};
|
||||
IDEs: {
|
||||
Xcode: {
|
||||
version: string;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
async function retrieveAndParseEnvInfo(): Promise<any> {
|
||||
return JSON.parse(
|
||||
await run(
|
||||
{
|
||||
SDKs: ['iOS SDK'],
|
||||
IDEs: ['Xcode'],
|
||||
},
|
||||
{json: true, showNotFound: true},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getEnvInfo(): Promise<EnvironmentInfo> {
|
||||
return await retrieveAndParseEnvInfo();
|
||||
}
|
||||
10
desktop/doctor/src/globals.d.ts
vendored
Normal file
10
desktop/doctor/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
declare module 'envinfo';
|
||||
294
desktop/doctor/src/index.ts
Normal file
294
desktop/doctor/src/index.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* 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 {exec} from 'child_process';
|
||||
import {promisify} from 'util';
|
||||
import {EnvironmentInfo, getEnvInfo} from './environmentInfo';
|
||||
export {EnvironmentInfo, getEnvInfo} from './environmentInfo';
|
||||
import * as watchman from 'fb-watchman';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export type HealthcheckCategory = {
|
||||
label: string;
|
||||
isSkipped: false;
|
||||
isRequired: boolean;
|
||||
healthchecks: Healthcheck[];
|
||||
};
|
||||
|
||||
export type SkippedHealthcheckCategory = {
|
||||
label: string;
|
||||
isSkipped: true;
|
||||
skipReason: string;
|
||||
};
|
||||
|
||||
export type Healthchecks = {
|
||||
common: HealthcheckCategory | SkippedHealthcheckCategory;
|
||||
android: HealthcheckCategory | SkippedHealthcheckCategory;
|
||||
ios: HealthcheckCategory | SkippedHealthcheckCategory;
|
||||
};
|
||||
|
||||
export type Healthcheck = {
|
||||
key: string;
|
||||
label: string;
|
||||
isRequired?: boolean;
|
||||
run: (env: EnvironmentInfo) => Promise<HealthchecRunResult>;
|
||||
};
|
||||
|
||||
export type HealthchecRunResult = {
|
||||
hasProblem: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type CategoryResult = [
|
||||
string,
|
||||
{
|
||||
label: string;
|
||||
results: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
result: {hasProblem: boolean};
|
||||
}>;
|
||||
},
|
||||
];
|
||||
|
||||
export function getHealthchecks(): Healthchecks {
|
||||
return {
|
||||
common: {
|
||||
label: 'Common',
|
||||
isRequired: true,
|
||||
isSkipped: false,
|
||||
healthchecks: [
|
||||
{
|
||||
key: 'common.openssl',
|
||||
label: 'OpenSSL Installed',
|
||||
run: async (_: EnvironmentInfo) => {
|
||||
const result = await tryExecuteCommand('openssl version');
|
||||
const hasProblem = result.hasProblem;
|
||||
const message = hasProblem
|
||||
? `OpenSSL (https://wiki.openssl.org/index.php/Binaries) is not installed or not added to PATH. ${result.message}.`
|
||||
: `OpenSSL (https://wiki.openssl.org/index.php/Binaries) is installed and added to PATH. ${result.message}.`;
|
||||
return {
|
||||
hasProblem,
|
||||
message,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'common.watchman',
|
||||
label: 'Watchman Installed',
|
||||
run: async (_: EnvironmentInfo) => {
|
||||
const isAvailable = await isWatchmanAvailable();
|
||||
return {
|
||||
hasProblem: !isAvailable,
|
||||
message: isAvailable
|
||||
? 'Watchman file watching service (https://facebook.github.io/watchman/) is installed and added to PATH. Live reloading after changes during Flipper plugin development is enabled.'
|
||||
: 'Watchman file watching service (https://facebook.github.io/watchman/) is not installed or not added to PATH. Live reloading after changes during Flipper plugin development is disabled.',
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
android: {
|
||||
label: 'Android',
|
||||
isRequired: false,
|
||||
isSkipped: false,
|
||||
healthchecks: [
|
||||
{
|
||||
key: 'android.sdk',
|
||||
label: 'SDK Installed',
|
||||
isRequired: true,
|
||||
run: async (_: EnvironmentInfo) => {
|
||||
if (process.env.ANDROID_HOME) {
|
||||
const androidHome = process.env.ANDROID_HOME;
|
||||
if (!fs.existsSync(androidHome)) {
|
||||
return {
|
||||
hasProblem: true,
|
||||
message: `ANDROID_HOME points to a folder which does not exist: ${androidHome}.`,
|
||||
};
|
||||
}
|
||||
const platformToolsDir = path.join(androidHome, 'platform-tools');
|
||||
if (!fs.existsSync(path.join(androidHome, 'platform-tools'))) {
|
||||
return {
|
||||
hasProblem: true,
|
||||
message: `Android SDK Platform Tools not found at the expected location "${platformToolsDir}". Probably they are not installed.`,
|
||||
};
|
||||
}
|
||||
return await tryExecuteCommand(
|
||||
path.join(platformToolsDir, 'adb') + ' version',
|
||||
);
|
||||
}
|
||||
return await tryExecuteCommand('adb version');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ios: {
|
||||
label: 'iOS',
|
||||
...(process.platform === 'darwin'
|
||||
? {
|
||||
isRequired: false,
|
||||
isSkipped: false,
|
||||
healthchecks: [
|
||||
{
|
||||
key: 'ios.sdk',
|
||||
label: 'SDK Installed',
|
||||
isRequired: true,
|
||||
run: async (e: EnvironmentInfo) => {
|
||||
const hasProblem =
|
||||
!e.SDKs['iOS SDK'] ||
|
||||
!e.SDKs['iOS SDK'].Platforms ||
|
||||
!e.SDKs['iOS SDK'].Platforms.length;
|
||||
const message = hasProblem
|
||||
? 'iOS SDK is not installed. You can install it using Xcode (https://developer.apple.com/xcode/).'
|
||||
: `iOS SDK is installed for the following platforms: ${JSON.stringify(
|
||||
e.SDKs['iOS SDK'].Platforms,
|
||||
)}.`;
|
||||
return {
|
||||
hasProblem,
|
||||
message,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ios.xcode',
|
||||
label: 'XCode Installed',
|
||||
isRequired: true,
|
||||
run: async (e: EnvironmentInfo) => {
|
||||
const hasProblem = e.IDEs == null || e.IDEs.Xcode == null;
|
||||
const message = hasProblem
|
||||
? 'Xcode (https://developer.apple.com/xcode/) is not installed.'
|
||||
: `Xcode version ${e.IDEs.Xcode.version} is installed at "${e.IDEs.Xcode.path}".`;
|
||||
return {
|
||||
hasProblem,
|
||||
message,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ios.xcode-select',
|
||||
label: 'xcode-select set',
|
||||
isRequired: true,
|
||||
run: async (_: EnvironmentInfo) => {
|
||||
const result = await tryExecuteCommand('xcode-select -p');
|
||||
const hasProblem = result.hasProblem;
|
||||
const message = hasProblem
|
||||
? `Xcode version is not selected. You can select it using command "sudo xcode-select -switch <path/to/>Xcode.app". ${result.message}.`
|
||||
: `Xcode version is selected. ${result.message}.`;
|
||||
return {
|
||||
hasProblem,
|
||||
message,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ios.instruments',
|
||||
label: 'Instruments exists',
|
||||
isRequired: true,
|
||||
run: async (_: EnvironmentInfo) => {
|
||||
const result = await tryExecuteCommand('which instruments');
|
||||
const hasProblem = result.hasProblem;
|
||||
const message = hasProblem
|
||||
? `Instruments not found. Please try to re-install Xcode (https://developer.apple.com/xcode/). ${result.message}.`
|
||||
: `Instruments are installed. ${result.message}.`;
|
||||
return {
|
||||
hasProblem,
|
||||
message,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
isSkipped: true,
|
||||
skipReason: `Healthcheck is skipped, because iOS development is not supported on the current platform "${process.platform}".`,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function runHealthchecks(): Promise<
|
||||
Array<CategoryResult | SkippedHealthcheckCategory>
|
||||
> {
|
||||
const environmentInfo = await getEnvInfo();
|
||||
const healthchecks: Healthchecks = getHealthchecks();
|
||||
const results: Array<
|
||||
CategoryResult | SkippedHealthcheckCategory
|
||||
> = await Promise.all(
|
||||
Object.entries(healthchecks).map(async ([key, category]) => {
|
||||
if (category.isSkipped) {
|
||||
return category;
|
||||
}
|
||||
const categoryResult: CategoryResult = [
|
||||
key,
|
||||
{
|
||||
label: category.label,
|
||||
results: await Promise.all(
|
||||
category.healthchecks.map(
|
||||
async ({key, label, run, isRequired}) => ({
|
||||
key,
|
||||
label,
|
||||
isRequired: isRequired ?? true,
|
||||
result: await run(environmentInfo).catch(e => {
|
||||
console.error(e);
|
||||
// TODO Improve result type to be: OK | Problem(message, fix...)
|
||||
return {
|
||||
hasProblem: true,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
return categoryResult;
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}
|
||||
|
||||
async function tryExecuteCommand(
|
||||
command: string,
|
||||
): Promise<HealthchecRunResult> {
|
||||
try {
|
||||
const output = await promisify(exec)(command);
|
||||
return {
|
||||
hasProblem: false,
|
||||
message: `Command "${command}" successfully executed with output: ${output.stdout}`,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
hasProblem: true,
|
||||
message: `Command "${command}" failed to execute with output: ${err.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function isWatchmanAvailable(): Promise<boolean> {
|
||||
const client = new watchman.Client();
|
||||
return new Promise(resolve => {
|
||||
const complete = (result: boolean) => {
|
||||
resolve(result);
|
||||
client.removeAllListeners('error');
|
||||
client.end();
|
||||
};
|
||||
client.once('error', () => complete(false));
|
||||
client.capabilityCheck(
|
||||
{optional: [], required: ['relative_root']},
|
||||
error => {
|
||||
if (error) {
|
||||
complete(false);
|
||||
return;
|
||||
}
|
||||
complete(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
12
desktop/doctor/tsconfig.json
Normal file
12
desktop/doctor/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es7", "dom", "es2017"],
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
}
|
||||
3
desktop/doctor/tslint.json
Normal file
3
desktop/doctor/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"]
|
||||
}
|
||||
4335
desktop/doctor/yarn.lock
Normal file
4335
desktop/doctor/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
10
desktop/flow-typed/TsStub.js
vendored
Normal file
10
desktop/flow-typed/TsStub.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare module TsStub {
|
||||
declare module.exports: any;
|
||||
}
|
||||
281
desktop/flow-typed/flipper.js
vendored
Normal file
281
desktop/flow-typed/flipper.js
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// This has been taken from the old plugin.js and manually stripped of
|
||||
// implementation so only the types remain.
|
||||
// It's not generated using flowgen because the typescript def is slightly weaker
|
||||
// than the original flow one. (Persisted State generic param being used
|
||||
// in reducers etc.
|
||||
|
||||
import type {KeyboardActions} from './MenuBar.js';
|
||||
import type {App} from './App.js';
|
||||
import type {Logger} from './fb-interfaces/Logger.js';
|
||||
import type Client from './Client.js';
|
||||
import type {Store, MiddlewareAPI} from './reducers/index.js';
|
||||
import type {MetricType} from './utils/exportMetrics.tsx';
|
||||
import type {Node} from 'react';
|
||||
import type BaseDevice from './devices/BaseDevice.js';
|
||||
import type AndroidDevice from './devices/AndroidDevice';
|
||||
import type IOSDevice from './devices/IOSDevice';
|
||||
|
||||
declare module 'flipper' {
|
||||
// This function is intended to be called from outside of the plugin.
|
||||
// If you want to `call` from the plugin use, this.client.call
|
||||
declare function callClient(
|
||||
client: Client,
|
||||
id: string,
|
||||
): (string, ?Object) => Promise<Object>;
|
||||
|
||||
declare interface PluginClient {
|
||||
// eslint-disable-next-line
|
||||
send(method: string, params?: Object): void;
|
||||
// eslint-disable-next-line
|
||||
call(method: string, params?: Object): Promise<any>;
|
||||
// eslint-disable-next-line
|
||||
subscribe(method: string, callback: (params: any) => void): void;
|
||||
// eslint-disable-next-line
|
||||
supportsMethod(method: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
declare type PluginTarget = BaseDevice | Client;
|
||||
|
||||
declare type Notification = {|
|
||||
id: string,
|
||||
title: string,
|
||||
message: string | Node,
|
||||
severity: 'warning' | 'error',
|
||||
timestamp?: number,
|
||||
category?: string,
|
||||
action?: string,
|
||||
|};
|
||||
|
||||
declare type Props<T> = {
|
||||
logger: Logger,
|
||||
persistedState: T,
|
||||
setPersistedState: (state: $Shape<T>) => void,
|
||||
target: PluginTarget,
|
||||
deepLinkPayload: ?string,
|
||||
selectPlugin: (pluginID: string, deepLinkPayload: ?string) => boolean,
|
||||
isArchivedDevice: boolean,
|
||||
selectedApp: ?string,
|
||||
};
|
||||
|
||||
declare class FlipperBasePlugin<
|
||||
State = *,
|
||||
Actions = *,
|
||||
PersistedState = *,
|
||||
> extends React.Component<Props<PersistedState>, State> {
|
||||
static title: ?string;
|
||||
static id: string;
|
||||
static icon: ?string;
|
||||
static gatekeeper: ?string;
|
||||
static entry: ?string;
|
||||
static bugs: ?{
|
||||
email?: string,
|
||||
url?: string,
|
||||
};
|
||||
static keyboardActions: ?KeyboardActions;
|
||||
static screenshot: ?string;
|
||||
static defaultPersistedState: PersistedState;
|
||||
static persistedStateReducer: ?(
|
||||
persistedState: PersistedState,
|
||||
method: string,
|
||||
data: Object,
|
||||
) => $Shape<PersistedState>;
|
||||
static metricsReducer: ?(
|
||||
persistedState: PersistedState,
|
||||
) => Promise<MetricType>;
|
||||
static exportPersistedState: ?(
|
||||
callClient: (string, ?Object) => Promise<Object>,
|
||||
persistedState: ?PersistedState,
|
||||
store: ?MiddlewareAPI,
|
||||
) => Promise<?PersistedState>;
|
||||
static serializePersistedState: (
|
||||
persistedState: PersistedState,
|
||||
statusUpdate?: (msg: string) => void,
|
||||
idler?: Idler,
|
||||
) => Promise<string>;
|
||||
static deserializePersistedState: (
|
||||
serializedString: string,
|
||||
) => PersistedState;
|
||||
static getActiveNotifications: ?(
|
||||
persistedState: PersistedState,
|
||||
) => Array<Notification>;
|
||||
static onRegisterDevice: ?(
|
||||
store: Store,
|
||||
baseDevice: BaseDevice,
|
||||
setPersistedState: (
|
||||
pluginKey: string,
|
||||
newPluginState: ?PersistedState,
|
||||
) => void,
|
||||
) => void;
|
||||
// forbid instance properties that should be static
|
||||
title: empty;
|
||||
id: empty;
|
||||
persist: empty;
|
||||
icon: empty;
|
||||
keyboardActions: empty;
|
||||
screenshot: empty;
|
||||
|
||||
reducers: {
|
||||
[actionName: string]: (state: State, actionData: Object) => $Shape<State>,
|
||||
};
|
||||
app: App;
|
||||
onKeyboardAction: ?(action: string) => void;
|
||||
|
||||
toJSON(): string;
|
||||
|
||||
init(): void;
|
||||
teardown(): void;
|
||||
computeNotifications(props: Props<*>, state: State): Array<Notification>;
|
||||
// methods to be overridden by subclasses
|
||||
_init(): void;
|
||||
_teardown(): void;
|
||||
|
||||
dispatchAction(actionData: Actions): void;
|
||||
}
|
||||
|
||||
declare class FlipperDevicePlugin<
|
||||
S = *,
|
||||
A = *,
|
||||
P = *,
|
||||
> extends FlipperBasePlugin<S, A, P> {
|
||||
device: BaseDevice;
|
||||
|
||||
constructor(props: Props<P>): void;
|
||||
|
||||
_init(): void;
|
||||
|
||||
_teardown(): void;
|
||||
|
||||
static supportsDevice(device: BaseDevice): boolean;
|
||||
}
|
||||
|
||||
declare class FlipperPlugin<S = *, A = *, P = *> extends FlipperBasePlugin<
|
||||
S,
|
||||
A,
|
||||
P,
|
||||
> {
|
||||
constructor(props: Props<P>): void;
|
||||
|
||||
subscriptions: Array<{
|
||||
method: string,
|
||||
callback: Function,
|
||||
}>;
|
||||
|
||||
client: PluginClient;
|
||||
realClient: Client;
|
||||
|
||||
getDevice(): Promise<BaseDevice>;
|
||||
|
||||
getAndroidDevice(): AndroidDevice;
|
||||
|
||||
getIOSDevice(): IOSDevice;
|
||||
|
||||
_teardown(): void;
|
||||
|
||||
_init(): void;
|
||||
}
|
||||
|
||||
declare var AndroidDevice: any;
|
||||
declare var BaseDevice: any;
|
||||
declare var Block: any;
|
||||
declare var Box: any;
|
||||
declare var Button: any;
|
||||
declare var ButtonGroup: any;
|
||||
declare var Checkbox: any;
|
||||
declare var CodeBlock: any;
|
||||
declare var Component: any;
|
||||
declare var ContextMenu: any;
|
||||
declare var DataDescription: any;
|
||||
declare var DataInspector: any;
|
||||
declare var DetailSidebar: any;
|
||||
declare var Device: any;
|
||||
declare var DeviceLogEntry: any;
|
||||
declare var Element: any;
|
||||
declare var ElementID: any;
|
||||
declare var ElementSearchResultSet: any;
|
||||
declare var ElementsInspector: any;
|
||||
declare var ErrorBlock: any;
|
||||
declare var ErrorBlockContainer: any;
|
||||
declare var Filter: any;
|
||||
declare var FlexBox: any;
|
||||
declare var FlexCenter: any;
|
||||
declare var FlexColumn: any;
|
||||
declare var FlexRow: any;
|
||||
declare var FlipperBasePlugin: any;
|
||||
declare var FlipperPlugin: any;
|
||||
declare var GK: any;
|
||||
declare var Glyph: any;
|
||||
declare var Heading: any;
|
||||
declare var HorizontalRule: any;
|
||||
declare var Input: any;
|
||||
declare var Label: any;
|
||||
declare var Link: any;
|
||||
declare var LoadingIndicator: any;
|
||||
declare var LogLevel: any;
|
||||
declare var ManagedDataInspector: any;
|
||||
declare var ManagedTable: any;
|
||||
declare var ManagedTable_immutable: any;
|
||||
declare var MarkerTimeline: any;
|
||||
declare var MetricType: any;
|
||||
declare var MiddlewareAPI: any;
|
||||
declare var OS: any;
|
||||
declare var Panel: any;
|
||||
declare var PureComponent: any;
|
||||
declare var SearchBox: any;
|
||||
declare var SearchIcon: any;
|
||||
declare var SearchInput: any;
|
||||
declare var Searchable: any;
|
||||
declare var SearchableProps: any;
|
||||
declare var SearchableTable: any;
|
||||
declare var SearchableTable_immutable: any;
|
||||
declare var Select: any;
|
||||
declare var Sheet: any;
|
||||
declare var Sidebar: any;
|
||||
declare var SidebarExtensions: any;
|
||||
declare var Spacer: any;
|
||||
declare var StackTrace: any;
|
||||
declare var StatusIndicator: any;
|
||||
declare var Store: any;
|
||||
declare var Tab: any;
|
||||
declare var TableBodyRow: any;
|
||||
declare var TableColumnOrder: any;
|
||||
declare var TableColumnSizes: any;
|
||||
declare var TableColumns: any;
|
||||
declare var TableHighlightedRows: any;
|
||||
declare var TableRowSortOrder: any;
|
||||
declare var TableRows: any;
|
||||
declare var TableRows_immutable: any;
|
||||
declare var Tabs: any;
|
||||
declare var Text: any;
|
||||
declare var Textarea: any;
|
||||
declare var ToggleButton: any;
|
||||
declare var Toolbar: any;
|
||||
declare var Tooltip: any;
|
||||
declare var Value: any;
|
||||
declare var VerticalRule: any;
|
||||
declare var View: any;
|
||||
declare var bufferToBlob: any;
|
||||
declare var clipboard: any;
|
||||
declare var colors: any;
|
||||
declare var constants: any;
|
||||
declare var createPaste: any;
|
||||
declare var createTablePlugin: any;
|
||||
declare var getPersistedState: any;
|
||||
declare var getPluginKey: any;
|
||||
declare var getStringFromErrorLike: any;
|
||||
declare var graphQLQuery: any;
|
||||
declare var isProduction: any;
|
||||
declare var keyframes: any;
|
||||
declare var renderValue: any;
|
||||
declare var shouldParseAndroidLog: any;
|
||||
declare var styled: any;
|
||||
declare var textContent: any;
|
||||
}
|
||||
507
desktop/flow-typed/npm/adbkit-fb_vx.x.x.js
vendored
Normal file
507
desktop/flow-typed/npm/adbkit-fb_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,507 @@
|
||||
// flow-typed signature: 7849d9ce4b390afcfc917a08445a20eb
|
||||
// flow-typed version: <<STUB>>/adbkit-fb_v2.10.1/flow_v0.59.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'adbkit-fb'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'adbkit-fb' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'adbkit-fb/lib/adb' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/auth' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/client' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/forward' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getdevicepath' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getserialno' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getstate' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/listforwards' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/waitfordevice' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/clear' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/framebuffer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getfeatures' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getpackages' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getproperties' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/install' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/isinstalled' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/listreverses' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/local' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/log' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/logcat' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/monkey' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/reboot' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/remount' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/reverse' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/root' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/screencap' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/shell' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/startactivity' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/startservice' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/sync' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/tcp' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/tcpip' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/trackjdwp' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/uninstall' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/usb' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/connect' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/devices' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/deviceswithpaths' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/disconnect' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/kill' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/trackdevices' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/transport' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/command/host/version' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/connection' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/dump' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/framebuffer/rgbtransform' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/keycode' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/linetransform' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/parser' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/proc/stat' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/protocol' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/sync' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/sync/entry' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/sync/pulltransfer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/sync/pushtransfer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/sync/stats' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/packet' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/packetreader' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/rollingcounter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/server' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/service' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/servicemap' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/socket' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/tracker' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/adb/util' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'adbkit-fb/lib/cli' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'adbkit-fb/index' {
|
||||
declare module.exports: $Exports<'adbkit-fb'>;
|
||||
}
|
||||
declare module 'adbkit-fb/index.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/auth.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/auth'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/client.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/client'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/forward.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/forward'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getdevicepath.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getdevicepath'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getserialno.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getserialno'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/getstate.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getstate'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/listforwards.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/listforwards'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-serial/waitfordevice.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/waitfordevice'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/clear.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/clear'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/framebuffer.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/framebuffer'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getfeatures.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getfeatures'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getpackages.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getpackages'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/getproperties.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getproperties'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/install.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/install'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/isinstalled.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/isinstalled'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/listreverses.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/listreverses'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/local.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/local'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/log.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/log'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/logcat.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/logcat'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/monkey.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/monkey'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/reboot.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/reboot'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/remount.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/remount'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/reverse.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/reverse'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/root.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/root'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/screencap.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/screencap'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/shell.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/shell'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/startactivity.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/startactivity'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/startservice.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/startservice'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/sync.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/sync'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/tcp.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/tcp'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/tcpip.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/tcpip'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/trackjdwp.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/trackjdwp'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/uninstall.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/uninstall'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/usb.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/usb'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/connect.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/connect'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/devices.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/devices'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/deviceswithpaths.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/deviceswithpaths'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/disconnect.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/disconnect'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/kill.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/kill'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/trackdevices.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/trackdevices'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/transport.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/transport'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/command/host/version.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/version'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/connection.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/connection'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/dump.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/dump'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/framebuffer/rgbtransform.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/framebuffer/rgbtransform'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/keycode.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/keycode'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/linetransform.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/linetransform'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/parser.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/parser'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/proc/stat.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/proc/stat'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/protocol.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/protocol'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/sync.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/sync'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/sync/entry.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/entry'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/sync/pulltransfer.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/pulltransfer'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/sync/pushtransfer.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/pushtransfer'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/sync/stats.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/stats'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/packet.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/packet'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/packetreader.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/packetreader'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/rollingcounter.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/rollingcounter'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/server.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/server'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/service.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/service'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/servicemap.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/servicemap'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tcpusb/socket.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/socket'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/tracker.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/tracker'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/adb/util.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/adb/util'>;
|
||||
}
|
||||
declare module 'adbkit-fb/lib/cli.js' {
|
||||
declare module.exports: $Exports<'adbkit-fb/lib/cli'>;
|
||||
}
|
||||
32
desktop/flow-typed/npm/dateformat_vx.x.x.js
vendored
Normal file
32
desktop/flow-typed/npm/dateformat_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// flow-typed signature: 3d651fc79523c92efe95077d29cac82f
|
||||
// flow-typed version: <<STUB>>/dateformat_v3.0.3/flow_v0.102.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'dateformat'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'dateformat' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'dateformat/lib/dateformat' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'dateformat/lib/dateformat.js' {
|
||||
declare module.exports: $Exports<'dateformat/lib/dateformat'>;
|
||||
}
|
||||
4282
desktop/flow-typed/npm/electron-v5.0.2.js
vendored
Normal file
4282
desktop/flow-typed/npm/electron-v5.0.2.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
desktop/flow-typed/npm/electron-v8.0.1.js
vendored
Normal file
12
desktop/flow-typed/npm/electron-v8.0.1.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
* @flow
|
||||
*
|
||||
*/
|
||||
|
||||
declare module 'electron' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
18
desktop/flow-typed/npm/google-palette_vx.x.x.js
vendored
Normal file
18
desktop/flow-typed/npm/google-palette_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// flow-typed signature: 6187fdc45c9567c2e88b8d4399bc1300
|
||||
// flow-typed version: <<STUB>>/google-palette_vlatest/flow_v0.69.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'google-palette'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'google-palette' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
633
desktop/flow-typed/npm/graphql_vx.x.x.js
vendored
Normal file
633
desktop/flow-typed/npm/graphql_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
// flow-typed signature: 78a3b0630930ae254dcef08544af115d
|
||||
// flow-typed version: <<STUB>>/graphql_v^0.11.7/flow_v0.59.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'graphql'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'graphql' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'graphql/error/formatError' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/error/GraphQLError' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/error/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/error/locatedError' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/error/syntaxError' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/execution/execute' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/execution/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/execution/values' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/graphql' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/dedent' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/find' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/invariant' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/isInvalid' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/isNullish' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/keyMap' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/keyValMap' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/ObjMap' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/quotedOrList' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/jsutils/suggestionList' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/ast' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/kinds' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/lexer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/location' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/parser' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/printer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/source' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/language/visitor' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/subscription/asyncIteratorReject' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/subscription/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/subscription/mapAsyncIterator' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/subscription/subscribe' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/definition' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/directives' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/introspection' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/scalars' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/type/schema' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/assertValidName' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/astFromValue' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/buildASTSchema' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/buildClientSchema' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/concatAST' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/extendSchema' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/findBreakingChanges' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/findDeprecatedUsages' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/getOperationAST' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/introspectionQuery' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/isValidJSValue' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/isValidLiteralValue' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/schemaPrinter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/separateOperations' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/typeComparators' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/typeFromAST' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/TypeInfo' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/utilities/valueFromAST' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/ArgumentsOfCorrectType' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/DefaultValuesOfCorrectType' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/FieldsOnCorrectType' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/FragmentsOnCompositeTypes' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/KnownArgumentNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/KnownDirectives' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/KnownFragmentNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/KnownTypeNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/LoneAnonymousOperation' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/NoFragmentCycles' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/NoUndefinedVariables' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/NoUnusedFragments' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/NoUnusedVariables' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/OverlappingFieldsCanBeMerged' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/PossibleFragmentSpreads' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/ProvidedNonNullArguments' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/ScalarLeafs' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/SingleFieldSubscriptions' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueArgumentNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueDirectivesPerLocation' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueFragmentNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueInputFieldNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueOperationNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/UniqueVariableNames' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/VariablesAreInputTypes' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/rules/VariablesInAllowedPosition' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/specifiedRules' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'graphql/validation/validate' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'graphql/error/formatError.js' {
|
||||
declare module.exports: $Exports<'graphql/error/formatError'>;
|
||||
}
|
||||
declare module 'graphql/error/GraphQLError.js' {
|
||||
declare module.exports: $Exports<'graphql/error/GraphQLError'>;
|
||||
}
|
||||
declare module 'graphql/error/index.js' {
|
||||
declare module.exports: $Exports<'graphql/error/index'>;
|
||||
}
|
||||
declare module 'graphql/error/locatedError.js' {
|
||||
declare module.exports: $Exports<'graphql/error/locatedError'>;
|
||||
}
|
||||
declare module 'graphql/error/syntaxError.js' {
|
||||
declare module.exports: $Exports<'graphql/error/syntaxError'>;
|
||||
}
|
||||
declare module 'graphql/execution/execute.js' {
|
||||
declare module.exports: $Exports<'graphql/execution/execute'>;
|
||||
}
|
||||
declare module 'graphql/execution/index.js' {
|
||||
declare module.exports: $Exports<'graphql/execution/index'>;
|
||||
}
|
||||
declare module 'graphql/execution/values.js' {
|
||||
declare module.exports: $Exports<'graphql/execution/values'>;
|
||||
}
|
||||
declare module 'graphql/graphql.js' {
|
||||
declare module.exports: $Exports<'graphql/graphql'>;
|
||||
}
|
||||
declare module 'graphql/index' {
|
||||
declare module.exports: $Exports<'graphql'>;
|
||||
}
|
||||
declare module 'graphql/index.js' {
|
||||
declare module.exports: $Exports<'graphql'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/dedent.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/dedent'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/find.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/find'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/invariant.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/invariant'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/isInvalid.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/isInvalid'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/isNullish.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/isNullish'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/keyMap.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/keyMap'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/keyValMap.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/keyValMap'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/ObjMap.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/ObjMap'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/quotedOrList.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/quotedOrList'>;
|
||||
}
|
||||
declare module 'graphql/jsutils/suggestionList.js' {
|
||||
declare module.exports: $Exports<'graphql/jsutils/suggestionList'>;
|
||||
}
|
||||
declare module 'graphql/language/ast.js' {
|
||||
declare module.exports: $Exports<'graphql/language/ast'>;
|
||||
}
|
||||
declare module 'graphql/language/index.js' {
|
||||
declare module.exports: $Exports<'graphql/language/index'>;
|
||||
}
|
||||
declare module 'graphql/language/kinds.js' {
|
||||
declare module.exports: $Exports<'graphql/language/kinds'>;
|
||||
}
|
||||
declare module 'graphql/language/lexer.js' {
|
||||
declare module.exports: $Exports<'graphql/language/lexer'>;
|
||||
}
|
||||
declare module 'graphql/language/location.js' {
|
||||
declare module.exports: $Exports<'graphql/language/location'>;
|
||||
}
|
||||
declare module 'graphql/language/parser.js' {
|
||||
declare module.exports: $Exports<'graphql/language/parser'>;
|
||||
}
|
||||
declare module 'graphql/language/printer.js' {
|
||||
declare module.exports: $Exports<'graphql/language/printer'>;
|
||||
}
|
||||
declare module 'graphql/language/source.js' {
|
||||
declare module.exports: $Exports<'graphql/language/source'>;
|
||||
}
|
||||
declare module 'graphql/language/visitor.js' {
|
||||
declare module.exports: $Exports<'graphql/language/visitor'>;
|
||||
}
|
||||
declare module 'graphql/subscription/asyncIteratorReject.js' {
|
||||
declare module.exports: $Exports<'graphql/subscription/asyncIteratorReject'>;
|
||||
}
|
||||
declare module 'graphql/subscription/index.js' {
|
||||
declare module.exports: $Exports<'graphql/subscription/index'>;
|
||||
}
|
||||
declare module 'graphql/subscription/mapAsyncIterator.js' {
|
||||
declare module.exports: $Exports<'graphql/subscription/mapAsyncIterator'>;
|
||||
}
|
||||
declare module 'graphql/subscription/subscribe.js' {
|
||||
declare module.exports: $Exports<'graphql/subscription/subscribe'>;
|
||||
}
|
||||
declare module 'graphql/type/definition.js' {
|
||||
declare module.exports: $Exports<'graphql/type/definition'>;
|
||||
}
|
||||
declare module 'graphql/type/directives.js' {
|
||||
declare module.exports: $Exports<'graphql/type/directives'>;
|
||||
}
|
||||
declare module 'graphql/type/index.js' {
|
||||
declare module.exports: $Exports<'graphql/type/index'>;
|
||||
}
|
||||
declare module 'graphql/type/introspection.js' {
|
||||
declare module.exports: $Exports<'graphql/type/introspection'>;
|
||||
}
|
||||
declare module 'graphql/type/scalars.js' {
|
||||
declare module.exports: $Exports<'graphql/type/scalars'>;
|
||||
}
|
||||
declare module 'graphql/type/schema.js' {
|
||||
declare module.exports: $Exports<'graphql/type/schema'>;
|
||||
}
|
||||
declare module 'graphql/utilities/assertValidName.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/assertValidName'>;
|
||||
}
|
||||
declare module 'graphql/utilities/astFromValue.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/astFromValue'>;
|
||||
}
|
||||
declare module 'graphql/utilities/buildASTSchema.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/buildASTSchema'>;
|
||||
}
|
||||
declare module 'graphql/utilities/buildClientSchema.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/buildClientSchema'>;
|
||||
}
|
||||
declare module 'graphql/utilities/concatAST.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/concatAST'>;
|
||||
}
|
||||
declare module 'graphql/utilities/extendSchema.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/extendSchema'>;
|
||||
}
|
||||
declare module 'graphql/utilities/findBreakingChanges.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/findBreakingChanges'>;
|
||||
}
|
||||
declare module 'graphql/utilities/findDeprecatedUsages.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/findDeprecatedUsages'>;
|
||||
}
|
||||
declare module 'graphql/utilities/getOperationAST.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/getOperationAST'>;
|
||||
}
|
||||
declare module 'graphql/utilities/index.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/index'>;
|
||||
}
|
||||
declare module 'graphql/utilities/introspectionQuery.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/introspectionQuery'>;
|
||||
}
|
||||
declare module 'graphql/utilities/isValidJSValue.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/isValidJSValue'>;
|
||||
}
|
||||
declare module 'graphql/utilities/isValidLiteralValue.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/isValidLiteralValue'>;
|
||||
}
|
||||
declare module 'graphql/utilities/schemaPrinter.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/schemaPrinter'>;
|
||||
}
|
||||
declare module 'graphql/utilities/separateOperations.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/separateOperations'>;
|
||||
}
|
||||
declare module 'graphql/utilities/typeComparators.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/typeComparators'>;
|
||||
}
|
||||
declare module 'graphql/utilities/typeFromAST.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/typeFromAST'>;
|
||||
}
|
||||
declare module 'graphql/utilities/TypeInfo.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/TypeInfo'>;
|
||||
}
|
||||
declare module 'graphql/utilities/valueFromAST.js' {
|
||||
declare module.exports: $Exports<'graphql/utilities/valueFromAST'>;
|
||||
}
|
||||
declare module 'graphql/validation/index.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/index'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/ArgumentsOfCorrectType.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/ArgumentsOfCorrectType'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/DefaultValuesOfCorrectType.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/DefaultValuesOfCorrectType'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/FieldsOnCorrectType.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/FieldsOnCorrectType'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/FragmentsOnCompositeTypes.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/FragmentsOnCompositeTypes'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/KnownArgumentNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/KnownArgumentNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/KnownDirectives.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/KnownDirectives'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/KnownFragmentNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/KnownFragmentNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/KnownTypeNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/KnownTypeNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/LoneAnonymousOperation.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/LoneAnonymousOperation'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/NoFragmentCycles.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/NoFragmentCycles'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/NoUndefinedVariables.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/NoUndefinedVariables'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/NoUnusedFragments.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/NoUnusedFragments'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/NoUnusedVariables.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/NoUnusedVariables'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/OverlappingFieldsCanBeMerged.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/OverlappingFieldsCanBeMerged'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/PossibleFragmentSpreads.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/PossibleFragmentSpreads'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/ProvidedNonNullArguments.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/ProvidedNonNullArguments'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/ScalarLeafs.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/ScalarLeafs'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/SingleFieldSubscriptions.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/SingleFieldSubscriptions'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueArgumentNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueArgumentNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueDirectivesPerLocation.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueDirectivesPerLocation'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueFragmentNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueFragmentNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueInputFieldNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueInputFieldNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueOperationNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueOperationNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/UniqueVariableNames.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/UniqueVariableNames'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/VariablesAreInputTypes.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/VariablesAreInputTypes'>;
|
||||
}
|
||||
declare module 'graphql/validation/rules/VariablesInAllowedPosition.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/rules/VariablesInAllowedPosition'>;
|
||||
}
|
||||
declare module 'graphql/validation/specifiedRules.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/specifiedRules'>;
|
||||
}
|
||||
declare module 'graphql/validation/validate.js' {
|
||||
declare module.exports: $Exports<'graphql/validation/validate'>;
|
||||
}
|
||||
1248
desktop/flow-typed/npm/immutable_v4.x.x.js
vendored
Normal file
1248
desktop/flow-typed/npm/immutable_v4.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1185
desktop/flow-typed/npm/jest_v24.x.x.js
vendored
Normal file
1185
desktop/flow-typed/npm/jest_v24.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
33
desktop/flow-typed/npm/lodash.debounce_vx.x.x.js
vendored
Normal file
33
desktop/flow-typed/npm/lodash.debounce_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// flow-typed signature: 95241cb8305cb4cbda994a489dff963f
|
||||
// flow-typed version: <<STUB>>/lodash.debounce_v4.0.8/flow_v0.76.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'lodash.debounce'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'lodash.debounce' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
|
||||
|
||||
// Filename aliases
|
||||
declare module 'lodash.debounce/index' {
|
||||
declare module.exports: $Exports<'lodash.debounce'>;
|
||||
}
|
||||
declare module 'lodash.debounce/index.js' {
|
||||
declare module.exports: $Exports<'lodash.debounce'>;
|
||||
}
|
||||
19
desktop/flow-typed/npm/lodash.memoize_vx.x.x.js
vendored
Normal file
19
desktop/flow-typed/npm/lodash.memoize_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// flow-typed signature: 0eceddcdb50aa9bf578c375a12ef9aad
|
||||
// flow-typed version: <<STUB>>/lodash.memoize_v4.1.2/flow_v0.91.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'lodash.memoize'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'lodash.memoize' {
|
||||
declare function memoize<T>(fn: T, resolver?: Function): T;
|
||||
declare export default typeof memoize;
|
||||
}
|
||||
6083
desktop/flow-typed/npm/lodash_v4.x.x.js
vendored
Normal file
6083
desktop/flow-typed/npm/lodash_v4.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
291
desktop/flow-typed/npm/needle_vx.x.x.js
vendored
Normal file
291
desktop/flow-typed/npm/needle_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
// flow-typed signature: 290caff3b97efa4471b18ff001038a13
|
||||
// flow-typed version: <<STUB>>/needle_v2.2.1/flow_v0.69.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'needle'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'needle' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'needle/examples/deflated-stream' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/digest-auth' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/download-to-file' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/multipart-stream' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/parsed-stream' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/parsed-stream2' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/stream-events' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/stream-to-file' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/examples/upload-image' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/auth' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/cookies' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/decoder' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/multipart' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/needle' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/parsers' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/lib/querystring' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/basic_auth_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/compression_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/cookies_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/decoder_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/errors_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/headers_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/helpers' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/long_string_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/output_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/parsing_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/post_data_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/proxy_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/querystring_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/redirect_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/redirect_with_timeout' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/request_stream_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/response_stream_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/socket_pool_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/url_spec' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/utils/formidable' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/utils/proxy' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'needle/test/utils/test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'needle/examples/deflated-stream.js' {
|
||||
declare module.exports: $Exports<'needle/examples/deflated-stream'>;
|
||||
}
|
||||
declare module 'needle/examples/digest-auth.js' {
|
||||
declare module.exports: $Exports<'needle/examples/digest-auth'>;
|
||||
}
|
||||
declare module 'needle/examples/download-to-file.js' {
|
||||
declare module.exports: $Exports<'needle/examples/download-to-file'>;
|
||||
}
|
||||
declare module 'needle/examples/multipart-stream.js' {
|
||||
declare module.exports: $Exports<'needle/examples/multipart-stream'>;
|
||||
}
|
||||
declare module 'needle/examples/parsed-stream.js' {
|
||||
declare module.exports: $Exports<'needle/examples/parsed-stream'>;
|
||||
}
|
||||
declare module 'needle/examples/parsed-stream2.js' {
|
||||
declare module.exports: $Exports<'needle/examples/parsed-stream2'>;
|
||||
}
|
||||
declare module 'needle/examples/stream-events.js' {
|
||||
declare module.exports: $Exports<'needle/examples/stream-events'>;
|
||||
}
|
||||
declare module 'needle/examples/stream-to-file.js' {
|
||||
declare module.exports: $Exports<'needle/examples/stream-to-file'>;
|
||||
}
|
||||
declare module 'needle/examples/upload-image.js' {
|
||||
declare module.exports: $Exports<'needle/examples/upload-image'>;
|
||||
}
|
||||
declare module 'needle/lib/auth.js' {
|
||||
declare module.exports: $Exports<'needle/lib/auth'>;
|
||||
}
|
||||
declare module 'needle/lib/cookies.js' {
|
||||
declare module.exports: $Exports<'needle/lib/cookies'>;
|
||||
}
|
||||
declare module 'needle/lib/decoder.js' {
|
||||
declare module.exports: $Exports<'needle/lib/decoder'>;
|
||||
}
|
||||
declare module 'needle/lib/multipart.js' {
|
||||
declare module.exports: $Exports<'needle/lib/multipart'>;
|
||||
}
|
||||
declare module 'needle/lib/needle.js' {
|
||||
declare module.exports: $Exports<'needle/lib/needle'>;
|
||||
}
|
||||
declare module 'needle/lib/parsers.js' {
|
||||
declare module.exports: $Exports<'needle/lib/parsers'>;
|
||||
}
|
||||
declare module 'needle/lib/querystring.js' {
|
||||
declare module.exports: $Exports<'needle/lib/querystring'>;
|
||||
}
|
||||
declare module 'needle/test/basic_auth_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/basic_auth_spec'>;
|
||||
}
|
||||
declare module 'needle/test/compression_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/compression_spec'>;
|
||||
}
|
||||
declare module 'needle/test/cookies_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/cookies_spec'>;
|
||||
}
|
||||
declare module 'needle/test/decoder_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/decoder_spec'>;
|
||||
}
|
||||
declare module 'needle/test/errors_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/errors_spec'>;
|
||||
}
|
||||
declare module 'needle/test/headers_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/headers_spec'>;
|
||||
}
|
||||
declare module 'needle/test/helpers.js' {
|
||||
declare module.exports: $Exports<'needle/test/helpers'>;
|
||||
}
|
||||
declare module 'needle/test/long_string_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/long_string_spec'>;
|
||||
}
|
||||
declare module 'needle/test/output_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/output_spec'>;
|
||||
}
|
||||
declare module 'needle/test/parsing_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/parsing_spec'>;
|
||||
}
|
||||
declare module 'needle/test/post_data_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/post_data_spec'>;
|
||||
}
|
||||
declare module 'needle/test/proxy_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/proxy_spec'>;
|
||||
}
|
||||
declare module 'needle/test/querystring_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/querystring_spec'>;
|
||||
}
|
||||
declare module 'needle/test/redirect_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/redirect_spec'>;
|
||||
}
|
||||
declare module 'needle/test/redirect_with_timeout.js' {
|
||||
declare module.exports: $Exports<'needle/test/redirect_with_timeout'>;
|
||||
}
|
||||
declare module 'needle/test/request_stream_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/request_stream_spec'>;
|
||||
}
|
||||
declare module 'needle/test/response_stream_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/response_stream_spec'>;
|
||||
}
|
||||
declare module 'needle/test/socket_pool_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/socket_pool_spec'>;
|
||||
}
|
||||
declare module 'needle/test/url_spec.js' {
|
||||
declare module.exports: $Exports<'needle/test/url_spec'>;
|
||||
}
|
||||
declare module 'needle/test/utils/formidable.js' {
|
||||
declare module.exports: $Exports<'needle/test/utils/formidable'>;
|
||||
}
|
||||
declare module 'needle/test/utils/proxy.js' {
|
||||
declare module.exports: $Exports<'needle/test/utils/proxy'>;
|
||||
}
|
||||
declare module 'needle/test/utils/test.js' {
|
||||
declare module.exports: $Exports<'needle/test/utils/test'>;
|
||||
}
|
||||
18
desktop/flow-typed/npm/react-chartjs-2_vx.x.x.js
vendored
Normal file
18
desktop/flow-typed/npm/react-chartjs-2_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// flow-typed signature: 033193531f49f251b11350a57a58eb12
|
||||
// flow-typed version: <<STUB>>/react-chartjs-2_vlatest/flow_v0.69.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'react-chartjs-2'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'react-chartjs-2' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
179
desktop/flow-typed/npm/react-d3-tree_vx.x.x.js
vendored
Normal file
179
desktop/flow-typed/npm/react-d3-tree_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// flow-typed signature: 32f203474854b8a1d378914b9461a972
|
||||
// flow-typed version: <<STUB>>/react-d3-tree_v1.12.0/flow_v0.86.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'react-d3-tree'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'react-d3-tree' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'react-d3-tree/jest/mocks/cssModule' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/jest/mocks/image' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/jest/polyfills/raf' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/jest/setup' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/lib/react-d3-tree.min' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/scripts/generateMarkdown' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Link/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Link/tests/index.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/ForeignObjectElement' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/SvgTextElement' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/tests/ForeignObjectElement.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/tests/index.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Node/tests/SvgTextElement.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Tree/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Tree/NodeWrapper' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Tree/tests/index.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Tree/tests/mockData' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/Tree/tests/NodeWrapper.test' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/src/util/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-d3-tree/webpack.config' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'react-d3-tree/jest/mocks/cssModule.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/jest/mocks/cssModule'>;
|
||||
}
|
||||
declare module 'react-d3-tree/jest/mocks/image.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/jest/mocks/image'>;
|
||||
}
|
||||
declare module 'react-d3-tree/jest/polyfills/raf.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/jest/polyfills/raf'>;
|
||||
}
|
||||
declare module 'react-d3-tree/jest/setup.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/jest/setup'>;
|
||||
}
|
||||
declare module 'react-d3-tree/lib/react-d3-tree.min.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/lib/react-d3-tree.min'>;
|
||||
}
|
||||
declare module 'react-d3-tree/scripts/generateMarkdown.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/scripts/generateMarkdown'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/index.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/index'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Link/index.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Link/index'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Link/tests/index.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Link/tests/index.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/ForeignObjectElement.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/ForeignObjectElement'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/index.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/index'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/SvgTextElement.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/SvgTextElement'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/tests/ForeignObjectElement.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/tests/ForeignObjectElement.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/tests/index.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/tests/index.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Node/tests/SvgTextElement.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Node/tests/SvgTextElement.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Tree/index.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Tree/index'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Tree/NodeWrapper.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Tree/NodeWrapper'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Tree/tests/index.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Tree/tests/index.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Tree/tests/mockData.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Tree/tests/mockData'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/Tree/tests/NodeWrapper.test.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/Tree/tests/NodeWrapper.test'>;
|
||||
}
|
||||
declare module 'react-d3-tree/src/util/index.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/src/util/index'>;
|
||||
}
|
||||
declare module 'react-d3-tree/webpack.config.js' {
|
||||
declare module.exports: $Exports<'react-d3-tree/webpack.config'>;
|
||||
}
|
||||
49
desktop/flow-typed/npm/react-stepper-horizontal_vx.x.x.js
vendored
Normal file
49
desktop/flow-typed/npm/react-stepper-horizontal_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// flow-typed signature: f8556e312538a69cb4b93dca5618b82a
|
||||
// flow-typed version: <<STUB>>/react-stepper-horizontal_v1.0.11/flow_v0.102.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'react-stepper-horizontal'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'react-stepper-horizontal' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'react-stepper-horizontal/lib' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-stepper-horizontal/lib/Step' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-stepper-horizontal/lib/Stepper' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'react-stepper-horizontal/lib/index' {
|
||||
declare module.exports: $Exports<'react-stepper-horizontal/lib'>;
|
||||
}
|
||||
declare module 'react-stepper-horizontal/lib/index.js' {
|
||||
declare module.exports: $Exports<'react-stepper-horizontal/lib'>;
|
||||
}
|
||||
declare module 'react-stepper-horizontal/lib/Step.js' {
|
||||
declare module.exports: $Exports<'react-stepper-horizontal/lib/Step'>;
|
||||
}
|
||||
declare module 'react-stepper-horizontal/lib/Stepper.js' {
|
||||
declare module.exports: $Exports<'react-stepper-horizontal/lib/Stepper'>;
|
||||
}
|
||||
1376
desktop/flow-typed/npm/react-virtualized_vx.x.x.js
vendored
Normal file
1376
desktop/flow-typed/npm/react-virtualized_vx.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
46
desktop/flow-typed/npm/redux-mock-store_vx.x.x.js
vendored
Normal file
46
desktop/flow-typed/npm/redux-mock-store_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// flow-typed signature: 0ce56690a743910f5934d0ffc0f26d61
|
||||
// flow-typed version: <<STUB>>/redux-mock-store_v1.5.3/flow_v0.76.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'redux-mock-store'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'redux-mock-store' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'redux-mock-store/lib/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'redux-mock-store/test/index' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'redux-mock-store/test/mock/middleware' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'redux-mock-store/lib/index.js' {
|
||||
declare module.exports: $Exports<'redux-mock-store/lib/index'>;
|
||||
}
|
||||
declare module 'redux-mock-store/test/index.js' {
|
||||
declare module.exports: $Exports<'redux-mock-store/test/index'>;
|
||||
}
|
||||
declare module 'redux-mock-store/test/mock/middleware.js' {
|
||||
declare module.exports: $Exports<'redux-mock-store/test/mock/middleware'>;
|
||||
}
|
||||
193
desktop/flow-typed/npm/sql-formatter_vx.x.x.js
vendored
Normal file
193
desktop/flow-typed/npm/sql-formatter_vx.x.x.js
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
// flow-typed signature: 21bf70a7f73a77579f4cc048adda0919
|
||||
// flow-typed version: <<STUB>>/sql-formatter_v2.3.3/flow_v0.102.0
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'sql-formatter'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'sql-formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'sql-formatter/dist/sql-formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/dist/sql-formatter.min' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/Formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/Indentation' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/InlineBlock' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/Params' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/Tokenizer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/core/tokenTypes' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/languages/Db2Formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/languages/N1qlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/languages/PlSqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/languages/StandardSqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/lib/sqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/Formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/Indentation' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/InlineBlock' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/Params' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/Tokenizer' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/core/tokenTypes' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/languages/Db2Formatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/languages/N1qlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/languages/PlSqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/languages/StandardSqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'sql-formatter/src/sqlFormatter' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'sql-formatter/dist/sql-formatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/dist/sql-formatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/dist/sql-formatter.min.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/dist/sql-formatter.min'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/Formatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/Formatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/Indentation.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/Indentation'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/InlineBlock.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/InlineBlock'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/Params.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/Params'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/Tokenizer.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/Tokenizer'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/core/tokenTypes.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/core/tokenTypes'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/languages/Db2Formatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/languages/Db2Formatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/languages/N1qlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/languages/N1qlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/languages/PlSqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/languages/PlSqlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/languages/StandardSqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/languages/StandardSqlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/lib/sqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/lib/sqlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/Formatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/Formatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/Indentation.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/Indentation'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/InlineBlock.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/InlineBlock'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/Params.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/Params'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/Tokenizer.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/Tokenizer'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/core/tokenTypes.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/core/tokenTypes'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/languages/Db2Formatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/languages/Db2Formatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/languages/N1qlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/languages/N1qlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/languages/PlSqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/languages/PlSqlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/languages/StandardSqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/languages/StandardSqlFormatter'>;
|
||||
}
|
||||
declare module 'sql-formatter/src/sqlFormatter.js' {
|
||||
declare module.exports: $Exports<'sql-formatter/src/sqlFormatter'>;
|
||||
}
|
||||
17
desktop/flow-typed/npm/unicode-substring.js
vendored
Normal file
17
desktop/flow-typed/npm/unicode-substring.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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
|
||||
* @flow
|
||||
*/
|
||||
|
||||
declare module 'unicode-substring' {
|
||||
declare export default function unicodeSubstring(
|
||||
baseString: string,
|
||||
start: number,
|
||||
end: number,
|
||||
): string;
|
||||
}
|
||||
10
desktop/flow-typed/react.js
vendored
Normal file
10
desktop/flow-typed/react.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
declare var React: $Exports<'react'>;
|
||||
3
desktop/headless-tests/.babelrc
Normal file
3
desktop/headless-tests/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-flow"]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
241
desktop/headless-tests/__tests__/headlessIntegrationTests.js
Normal file
241
desktop/headless-tests/__tests__/headlessIntegrationTests.js
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 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 {spawn} from 'child_process';
|
||||
import memoize from 'lodash.memoize';
|
||||
// $FlowFixMe
|
||||
import stringify from 'canonical-json';
|
||||
|
||||
const TEST_TIMEOUT_MS = 30 * 1000;
|
||||
|
||||
const layoutPathsToExcludeFromSnapshots = [
|
||||
'id',
|
||||
'children.*',
|
||||
'extraInfo.linkedNode',
|
||||
'data.View.*.value',
|
||||
'data.View.*.*.value',
|
||||
'data.View.*.*.*.value',
|
||||
'data.Drawable.*.value',
|
||||
'data.LithoView.mountbounds',
|
||||
'data.Layout.height',
|
||||
'data.Layout.width',
|
||||
'data.*.typeface',
|
||||
];
|
||||
|
||||
const params = {
|
||||
bin: process.env.FLIPPER_PATH || '/tmp/flipper-macos',
|
||||
securePort: process.env.SECURE_PORT || '8088',
|
||||
insecurePort: process.env.INSECURE_PORT || '8089',
|
||||
device: process.env.DEVICE,
|
||||
};
|
||||
|
||||
if (!params.device) {
|
||||
console.warn(
|
||||
'No device specified. Test may fail if more than one is present.',
|
||||
);
|
||||
}
|
||||
|
||||
const basicArgs = [
|
||||
'-v',
|
||||
...(params.device ? ['--device', params.device] : []),
|
||||
'--secure-port',
|
||||
params.securePort,
|
||||
'--insecure-port',
|
||||
params.insecurePort,
|
||||
];
|
||||
|
||||
const runHeadless = memoize((args: Array<string>): Promise<{
|
||||
output: Object,
|
||||
stderr: string,
|
||||
}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stdoutChunks = [];
|
||||
const stderrChunks = [];
|
||||
console.info(`Running ${params.bin} ${args.join(' ')}`);
|
||||
const process = spawn(params.bin, args, {});
|
||||
process.stdout.setEncoding('utf8');
|
||||
process.stdout.on('data', chunk => {
|
||||
stdoutChunks.push(chunk);
|
||||
});
|
||||
process.stderr.on('data', chunk => {
|
||||
stderrChunks.push(chunk);
|
||||
});
|
||||
process.stdout.on('end', chunk => {
|
||||
const stdout = stdoutChunks.join('');
|
||||
const stderr = stderrChunks.join('');
|
||||
try {
|
||||
console.log(stderr);
|
||||
resolve({output: JSON.parse(stdout), stderr: stderr});
|
||||
} catch (e) {
|
||||
console.warn(stderr);
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to parse headless output as JSON (${e.message}): ${stdout}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
process.kill('SIGINT');
|
||||
}, 20000);
|
||||
});
|
||||
});
|
||||
|
||||
function getPluginState(app: string, plugin: string): Promise<string> {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
const pluginStates = result.output.store.pluginStates;
|
||||
for (const pluginId of Object.keys(pluginStates)) {
|
||||
const matches = /([^#]+)#([^#]+)#([^#]+)#([^#]+)#([^#]+)/.exec(pluginId);
|
||||
if (
|
||||
matches &&
|
||||
matches.length === 6 &&
|
||||
matches[1] === app &&
|
||||
matches[5] === plugin
|
||||
) {
|
||||
const id = matches[0];
|
||||
return pluginStates[id];
|
||||
}
|
||||
}
|
||||
throw new Error(`No matching plugin state for ${app}, ${plugin}`);
|
||||
});
|
||||
}
|
||||
|
||||
test(
|
||||
'Flipper app appears in exported clients',
|
||||
() => {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
expect(result.output.clients.map(c => c.query.app)).toContain('Flipper');
|
||||
});
|
||||
},
|
||||
TEST_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
test(
|
||||
'Output includes fileVersion',
|
||||
() => {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
expect(result.output.fileVersion).toMatch(/[0-9]+\.[0-9]+\.[0-9]+/);
|
||||
});
|
||||
},
|
||||
TEST_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
test(
|
||||
'Output includes device',
|
||||
() => {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
expect(result.output.device).toBeTruthy();
|
||||
});
|
||||
},
|
||||
TEST_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
test(
|
||||
'Output includes flipperReleaseRevision',
|
||||
() => {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
expect(result.output.flipperReleaseRevision).toBeTruthy();
|
||||
});
|
||||
},
|
||||
TEST_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
test(
|
||||
'Output includes store',
|
||||
() => {
|
||||
return runHeadless(basicArgs).then(result => {
|
||||
expect(result.output.store).toBeTruthy();
|
||||
});
|
||||
},
|
||||
TEST_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
function stripUnstableLayoutAttributes(node: Object): Object {
|
||||
let newNode = node;
|
||||
for (const path of layoutPathsToExcludeFromSnapshots) {
|
||||
const parts = path.split('.');
|
||||
newNode = stripNode(newNode, parts);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
function stripNode(node: any, path: Array<string>) {
|
||||
if (path.length === 0) {
|
||||
return 'PLACEHOLDER';
|
||||
}
|
||||
if (path[0] === '*') {
|
||||
if (Array.isArray(node)) {
|
||||
return node.map(e => stripNode(e, path.slice(1)));
|
||||
}
|
||||
return Object.entries(node).reduce((acc, [key, val]) => {
|
||||
acc[key] = stripNode(val, path.slice(1));
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (!node[path[0]]) {
|
||||
return node;
|
||||
}
|
||||
return {...node, [path[0]]: stripNode(node[path[0]], path.slice(1))};
|
||||
}
|
||||
|
||||
test('test layout snapshot stripping', () => {
|
||||
const beforeStripping = {
|
||||
my: {
|
||||
test: {
|
||||
id: 4,
|
||||
node: 7,
|
||||
something: 9,
|
||||
list: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
id: 2,
|
||||
children: [1, 2, 3],
|
||||
extraInfo: {
|
||||
linkedNode: 55,
|
||||
somethingElse: 44,
|
||||
},
|
||||
data: {View: {bounds: {something: {value: 4}}}},
|
||||
other: 8,
|
||||
};
|
||||
const afterStripping = stripUnstableLayoutAttributes(beforeStripping);
|
||||
expect(afterStripping).toEqual({
|
||||
my: {
|
||||
test: {
|
||||
id: 4,
|
||||
node: 7,
|
||||
something: 9,
|
||||
list: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
id: 'PLACEHOLDER',
|
||||
children: ['PLACEHOLDER', 'PLACEHOLDER', 'PLACEHOLDER'],
|
||||
extraInfo: {
|
||||
linkedNode: 'PLACEHOLDER',
|
||||
somethingElse: 44,
|
||||
},
|
||||
data: {View: {bounds: {something: {value: 'PLACEHOLDER'}}}},
|
||||
other: 8,
|
||||
});
|
||||
});
|
||||
|
||||
test('Sample app layout hierarchy matches snapshot', () => {
|
||||
return getPluginState('Flipper', 'Inspector').then(result => {
|
||||
const state = JSON.parse(result);
|
||||
expect(state.rootAXElement).toBe('com.facebook.flipper.sample');
|
||||
expect(state.rootElement).toBe('com.facebook.flipper.sample');
|
||||
const canonicalizedElements = Object.values(state.elements)
|
||||
.map(e => {
|
||||
const stableizedElements = stripUnstableLayoutAttributes(e);
|
||||
return stringify(stableizedElements);
|
||||
})
|
||||
.sort();
|
||||
expect(canonicalizedElements).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
25
desktop/headless-tests/package.json
Normal file
25
desktop/headless-tests/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "headless-tests",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:debug": "node --inspect node_modules/.bin/jest --runInBand"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.jsx?$": "babel-jest"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^24.7.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/preset-flow": "^7.8.3",
|
||||
"babel-jest": "^24.8.0",
|
||||
"canonical-json": "^0.0.4",
|
||||
"lodash.memoize": "^4.1.2"
|
||||
}
|
||||
}
|
||||
3961
desktop/headless-tests/yarn.lock
Normal file
3961
desktop/headless-tests/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
394
desktop/headless/index.tsx
Normal file
394
desktop/headless/index.tsx
Normal file
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 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 {createStore, Dispatch, Middleware, MiddlewareAPI} from 'redux';
|
||||
import {applyMiddleware} from 'redux';
|
||||
import yargs, {Argv} from 'yargs';
|
||||
import dispatcher from '../src/dispatcher/index';
|
||||
import reducers, {Actions, State} from '../src/reducers/index';
|
||||
import {init as initLogger} from '../src/fb-stubs/Logger';
|
||||
import {exportStore} from '../src/utils/exportData';
|
||||
import {
|
||||
exportMetricsWithoutTrace,
|
||||
exportMetricsFromTrace,
|
||||
} from '../src/utils/exportMetrics';
|
||||
import {listDevices} from '../src/utils/listDevices';
|
||||
import setup from '../static/setup';
|
||||
import {getPersistentPlugins, pluginsClassMap} from '../src/utils/pluginUtils';
|
||||
import {serialize} from '../src/utils/serialization';
|
||||
import {getStringFromErrorLike} from '../src/utils/index';
|
||||
import AndroidDevice from '../src/devices/AndroidDevice';
|
||||
import {Store} from 'flipper';
|
||||
|
||||
type Action = {exit: boolean; result?: string};
|
||||
|
||||
type UserArguments = {
|
||||
securePort: string;
|
||||
insecurePort: string;
|
||||
dev: boolean;
|
||||
exit: 'sigint' | 'disconnect';
|
||||
verbose: boolean;
|
||||
metrics: string;
|
||||
listDevices: boolean;
|
||||
device: string;
|
||||
listPlugins: boolean;
|
||||
selectPlugins: Array<string>;
|
||||
};
|
||||
|
||||
(yargs as Argv<UserArguments>)
|
||||
.usage('$0 [args]')
|
||||
.command<UserArguments>(
|
||||
'*',
|
||||
'Start a headless Flipper instance',
|
||||
(yargs: Argv<UserArguments>) => {
|
||||
yargs.option('secure-port', {
|
||||
default: '8088',
|
||||
describe: 'Secure port the Flipper server should run on.',
|
||||
type: 'string',
|
||||
});
|
||||
yargs.option('insecure-port', {
|
||||
default: '8089',
|
||||
describe: 'Insecure port the Flipper server should run on.',
|
||||
type: 'string',
|
||||
});
|
||||
yargs.option('dev', {
|
||||
default: false,
|
||||
describe:
|
||||
'Enable redux-devtools. Tries to connect to devtools running on port 8181',
|
||||
type: 'boolean',
|
||||
});
|
||||
yargs.option('exit', {
|
||||
describe: 'Controls when to exit and dump the store to stdout.',
|
||||
choices: ['sigint', 'disconnect'],
|
||||
default: 'sigint',
|
||||
});
|
||||
yargs.option('v', {
|
||||
alias: 'verbose',
|
||||
default: false,
|
||||
describe: 'Enable verbose logging',
|
||||
type: 'boolean',
|
||||
});
|
||||
yargs.option('metrics', {
|
||||
default: undefined,
|
||||
describe: 'Will export metrics instead of data when flipper terminates',
|
||||
type: 'string',
|
||||
});
|
||||
yargs.option('list-devices', {
|
||||
default: false,
|
||||
describe: 'Will print the list of devices in the terminal',
|
||||
type: 'boolean',
|
||||
});
|
||||
yargs.option('list-plugins', {
|
||||
default: false,
|
||||
describe: 'Will print the list of supported plugins in the terminal',
|
||||
type: 'boolean',
|
||||
});
|
||||
yargs.option('select-plugins', {
|
||||
default: [],
|
||||
describe:
|
||||
'The data/metrics would be exported only for the selected plugins',
|
||||
type: 'array',
|
||||
});
|
||||
yargs.option('device', {
|
||||
default: undefined,
|
||||
describe:
|
||||
'The identifier passed will be matched against the udid of the available devices and the matched device would be selected',
|
||||
type: 'string',
|
||||
});
|
||||
return yargs;
|
||||
},
|
||||
startFlipper,
|
||||
)
|
||||
.version(global.__VERSION__)
|
||||
.help().argv; // http://yargs.js.org/docs/#api-argv
|
||||
|
||||
function shouldExportMetric(metrics: string): boolean {
|
||||
if (!metrics) {
|
||||
return process.argv.includes('--metrics');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function outputAndExit(output: string | null | undefined): void {
|
||||
output = output || '';
|
||||
console.log(`Finished. Outputting ${output.length} characters.`);
|
||||
process.stdout.write(output, () => {
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function errorAndExit(error: any): void {
|
||||
process.stderr.write(getStringFromErrorLike(error), () => {
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
async function earlyExitActions(
|
||||
exitClosures: Array<(userArguments: UserArguments) => Promise<Action>>,
|
||||
userArguments: UserArguments,
|
||||
_originalConsole?: typeof global.console,
|
||||
): Promise<void> {
|
||||
for (const exitAction of exitClosures) {
|
||||
try {
|
||||
const action = await exitAction(userArguments);
|
||||
if (action.exit) {
|
||||
outputAndExit(action.result);
|
||||
}
|
||||
} catch (e) {
|
||||
errorAndExit(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function exitActions(
|
||||
exitClosures: Array<
|
||||
(userArguments: UserArguments, store: Store) => Promise<Action>
|
||||
>,
|
||||
userArguments: UserArguments,
|
||||
store: Store,
|
||||
): Promise<void> {
|
||||
const {metrics, exit} = userArguments;
|
||||
for (const exitAction of exitClosures) {
|
||||
try {
|
||||
const action = await exitAction(userArguments, store);
|
||||
if (action.exit) {
|
||||
outputAndExit(action.result);
|
||||
}
|
||||
} catch (e) {
|
||||
errorAndExit(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (exit == 'sigint') {
|
||||
process.on('SIGINT', async () => {
|
||||
try {
|
||||
if (shouldExportMetric(metrics) && !metrics) {
|
||||
const state = store.getState();
|
||||
const payload = await exportMetricsWithoutTrace(
|
||||
store,
|
||||
state.pluginStates,
|
||||
);
|
||||
outputAndExit(payload);
|
||||
} else {
|
||||
const {serializedString, errorArray} = await exportStore(store);
|
||||
errorArray.forEach(console.error);
|
||||
outputAndExit(serializedString);
|
||||
}
|
||||
} catch (e) {
|
||||
errorAndExit(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function storeModifyingActions(
|
||||
storeModifyingClosures: Array<
|
||||
(userArguments: UserArguments, store: Store) => Promise<Action>
|
||||
>,
|
||||
userArguments: UserArguments,
|
||||
store: Store,
|
||||
): Promise<void> {
|
||||
for (const closure of storeModifyingClosures) {
|
||||
try {
|
||||
const action = await closure(userArguments, store);
|
||||
if (action.exit) {
|
||||
outputAndExit(action.result);
|
||||
}
|
||||
} catch (e) {
|
||||
errorAndExit(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function startFlipper(userArguments: UserArguments) {
|
||||
const {verbose, metrics, exit, insecurePort, securePort} = userArguments;
|
||||
console.error(`
|
||||
_____ _ _
|
||||
| __| |_|___ ___ ___ ___
|
||||
| __| | | . | . | -_| _|
|
||||
|__| |_|_| _| _|___|_| v${global.__VERSION__}
|
||||
|_| |_|
|
||||
`);
|
||||
|
||||
// redirect all logging to stderr
|
||||
const overriddenMethods = ['debug', 'info', 'log', 'warn', 'error'];
|
||||
for (const method of overriddenMethods) {
|
||||
(global.console as {[key: string]: any})[method] =
|
||||
method === 'error' || verbose ? global.console.error : () => {};
|
||||
}
|
||||
|
||||
// Polyfills
|
||||
global.WebSocket = require('ws'); // used for redux devtools
|
||||
global.fetch = require('node-fetch/lib/index');
|
||||
|
||||
process.env.BUNDLED_PLUGIN_PATH =
|
||||
process.env.BUNDLED_PLUGIN_PATH ||
|
||||
path.join(path.dirname(process.execPath), 'plugins');
|
||||
|
||||
process.env.FLIPPER_PORTS = `${insecurePort},${securePort}`;
|
||||
|
||||
// needs to be required after WebSocket polyfill is loaded
|
||||
const devToolsEnhancer = require('remote-redux-devtools');
|
||||
|
||||
const headlessMiddleware: Middleware<{}, State, any> = (
|
||||
store: MiddlewareAPI<Dispatch<Actions>, State>,
|
||||
) => (next: Dispatch<Actions>) => (action: Actions) => {
|
||||
if (exit == 'disconnect' && action.type == 'CLIENT_REMOVED') {
|
||||
// TODO(T42325892): Investigate why the export stalls without exiting the
|
||||
// current eventloop task here.
|
||||
setTimeout(() => {
|
||||
if (shouldExportMetric(metrics) && !metrics) {
|
||||
const state = store.getState();
|
||||
exportMetricsWithoutTrace(store as Store, state.pluginStates)
|
||||
.then((payload: string | null) => {
|
||||
outputAndExit(payload || '');
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
errorAndExit(e);
|
||||
});
|
||||
} else {
|
||||
exportStore(store)
|
||||
.then(({serializedString}) => {
|
||||
outputAndExit(serializedString);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
errorAndExit(e);
|
||||
});
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
|
||||
setup({});
|
||||
const store = createStore<State, Actions, {}, {}>(
|
||||
reducers,
|
||||
devToolsEnhancer.composeWithDevTools(applyMiddleware(headlessMiddleware)),
|
||||
);
|
||||
const logger = initLogger(store, {isHeadless: true});
|
||||
|
||||
const earlyExitClosures: Array<(
|
||||
userArguments: UserArguments,
|
||||
) => Promise<Action>> = [
|
||||
async (userArguments: UserArguments) => {
|
||||
if (userArguments.listDevices) {
|
||||
const devices = await listDevices(store);
|
||||
const mapped = devices.map(device => {
|
||||
return {
|
||||
os: device.os,
|
||||
title: device.title,
|
||||
deviceType: device.deviceType,
|
||||
serial: device.serial,
|
||||
};
|
||||
});
|
||||
return {exit: true, result: await serialize(mapped)};
|
||||
}
|
||||
return Promise.resolve({exit: false});
|
||||
},
|
||||
];
|
||||
|
||||
await earlyExitActions(earlyExitClosures, userArguments);
|
||||
|
||||
const cleanupDispatchers = dispatcher(store, logger);
|
||||
|
||||
const storeModifyingClosures: Array<(
|
||||
userArguments: UserArguments,
|
||||
store: Store,
|
||||
) => Promise<Action>> = [
|
||||
async (userArguments: UserArguments, store: Store) => {
|
||||
const {device: selectedDeviceID} = userArguments;
|
||||
if (selectedDeviceID) {
|
||||
const devices = await listDevices(store);
|
||||
const matchedDevice = devices.find(
|
||||
device => device.serial === selectedDeviceID,
|
||||
);
|
||||
if (matchedDevice) {
|
||||
if (matchedDevice instanceof AndroidDevice) {
|
||||
const ports = store.getState().application.serverPorts;
|
||||
matchedDevice.reverse([ports.secure, ports.insecure]);
|
||||
}
|
||||
matchedDevice.loadDevicePlugins(
|
||||
store.getState().plugins.devicePlugins,
|
||||
);
|
||||
store.dispatch({
|
||||
type: 'REGISTER_DEVICE',
|
||||
payload: matchedDevice,
|
||||
});
|
||||
store.dispatch({
|
||||
type: 'SELECT_DEVICE',
|
||||
payload: matchedDevice,
|
||||
});
|
||||
return {exit: false};
|
||||
}
|
||||
throw new Error(`No device matching the serial ${selectedDeviceID}`);
|
||||
}
|
||||
return {
|
||||
exit: false,
|
||||
};
|
||||
},
|
||||
async (userArguments: UserArguments, store: Store) => {
|
||||
const {selectPlugins} = userArguments;
|
||||
const selectedPlugins = selectPlugins.filter(selectPlugin => {
|
||||
return selectPlugin != undefined;
|
||||
});
|
||||
if (selectedPlugins) {
|
||||
store.dispatch({
|
||||
type: 'SELECTED_PLUGINS',
|
||||
payload: selectedPlugins,
|
||||
});
|
||||
}
|
||||
return {
|
||||
exit: false,
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
const exitActionClosures: Array<(
|
||||
userArguments: UserArguments,
|
||||
store: Store,
|
||||
) => Promise<Action>> = [
|
||||
async (userArguments: UserArguments, store: Store) => {
|
||||
const {listPlugins} = userArguments;
|
||||
if (listPlugins) {
|
||||
return Promise.resolve({
|
||||
exit: true,
|
||||
result: await serialize(
|
||||
getPersistentPlugins(store.getState().plugins),
|
||||
),
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
exit: false,
|
||||
});
|
||||
},
|
||||
async (userArguments: UserArguments, store: Store) => {
|
||||
const {metrics} = userArguments;
|
||||
if (shouldExportMetric(metrics) && metrics && metrics.length > 0) {
|
||||
try {
|
||||
const payload = await exportMetricsFromTrace(
|
||||
metrics,
|
||||
pluginsClassMap(store.getState().plugins),
|
||||
store.getState().plugins.selectedPlugins,
|
||||
);
|
||||
return {exit: true, result: payload ? payload.toString() : ''};
|
||||
} catch (error) {
|
||||
return {exit: true, result: error};
|
||||
}
|
||||
}
|
||||
return Promise.resolve({exit: false});
|
||||
},
|
||||
];
|
||||
|
||||
await storeModifyingActions(storeModifyingClosures, userArguments, store);
|
||||
|
||||
await exitActions(exitActionClosures, userArguments, store);
|
||||
|
||||
await cleanupDispatchers();
|
||||
}
|
||||
259
desktop/package.json
Normal file
259
desktop/package.json
Normal file
@@ -0,0 +1,259 @@
|
||||
{
|
||||
"name": "flipper",
|
||||
"version": "0.33.1",
|
||||
"versionDate": "2018-4-12",
|
||||
"description": "Mobile development tool",
|
||||
"productName": "Flipper",
|
||||
"author": "Facebook Inc",
|
||||
"main": "src/index.tsx",
|
||||
"icon": "icon.png",
|
||||
"category": "facebook-intern",
|
||||
"privileged": true,
|
||||
"build": {
|
||||
"appId": "flipper",
|
||||
"productName": "Flipper",
|
||||
"artifactName": "Flipper-${os}.${ext}",
|
||||
"protocols": {
|
||||
"name": "flipper",
|
||||
"schemes": [
|
||||
"flipper"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools",
|
||||
"extendInfo": {
|
||||
"NSUserNotificationAlertStyle": "alert"
|
||||
}
|
||||
},
|
||||
"dmg": {
|
||||
"background": "dmgBackground.png",
|
||||
"icon": "icon.icns",
|
||||
"iconSize": 155,
|
||||
"window": {
|
||||
"width": 660,
|
||||
"height": 400
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 123,
|
||||
"y": 172
|
||||
},
|
||||
{
|
||||
"x": 539,
|
||||
"y": 168,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"publisherName": "Facebook, Inc.",
|
||||
"sign": null
|
||||
},
|
||||
"asar": false,
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": [
|
||||
".flipper"
|
||||
],
|
||||
"name": "Flipper Data",
|
||||
"role": "Viewer",
|
||||
"icon": "document-icons/document.icns"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"@jest-runner/electron/electron": "8.0.1",
|
||||
"adbkit/adbkit-logcat": "2",
|
||||
"@types/react": "16.9.17",
|
||||
"@types/react-dom": "16.9.4"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.*__tests__/.*\\.tsx?$": "ts-jest",
|
||||
"\\.(js|tsx?)$": "<rootDir>/static/transforms/index.js"
|
||||
},
|
||||
"setupFiles": [
|
||||
"<rootDir>/static/globalTestSetup.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^flipper$": "<rootDir>/src/index.tsx"
|
||||
},
|
||||
"clearMocks": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest-runner/electron": "^2.0.2",
|
||||
"@testing-library/react": "^9.3.0",
|
||||
"@types/algoliasearch": "^3.30.19",
|
||||
"@types/babel-code-frame": "^6.20.2",
|
||||
"@types/decompress": "4.2.3",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/detect-port": "^1.1.0",
|
||||
"@types/expand-tilde": "^2.0.0",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/fb-watchman": "^2.0.0",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/invariant": "^2.2.31",
|
||||
"@types/jest": "^25.1.0",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.isequal": "^4.5.5",
|
||||
"@types/mkdirp": "^1.0.0",
|
||||
"@types/node": "^12.12.20",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"@types/react-redux": "^7.1.5",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.1",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/redux-persist": "^4.3.1",
|
||||
"@types/requestidlecallback": "^0.3.1",
|
||||
"@types/rsocket-core": "^0.0.5",
|
||||
"@types/socket.io": "^2.1.4",
|
||||
"@types/testing-library__react": "^9.1.2",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/uuid": "^7.0.0",
|
||||
"@types/ws": "^7.2.0",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
||||
"@typescript-eslint/parser": "^2.19.2",
|
||||
"babel-code-frame": "^6.26.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"electron": "8.0.1",
|
||||
"electron-builder": "^22.3.2",
|
||||
"eslint": "^6.7.0",
|
||||
"eslint-config-fbjs": "^3.1.1",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-flowtype": "^4.3.0",
|
||||
"eslint-plugin-header": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"eslint-plugin-relay": "^1.4.1",
|
||||
"flow-bin": "^0.120.1",
|
||||
"glob": "^7.1.2",
|
||||
"jest": "^25.1.0",
|
||||
"jest-fetch-mock": "^3.0.0",
|
||||
"prettier": "^1.19.1",
|
||||
"react-async": "^10.0.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"redux-mock-store": "^1.5.3",
|
||||
"ts-jest": "^25.1.0",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.22",
|
||||
"@emotion/styled": "^10.0.23",
|
||||
"@iarna/toml": "^2.2.3",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/promise-retry": "^1.1.3",
|
||||
"@types/react-color": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.9.1",
|
||||
"@types/react-transition-group": "^4.2.2",
|
||||
"@types/react-virtualized": "^9.21.7",
|
||||
"@types/redux-devtools-extension": "^2.13.2",
|
||||
"@types/redux-mock-store": "^1.0.1",
|
||||
"@types/rsocket-tcp-server": "^0.0.2",
|
||||
"@types/which": "^1.3.2",
|
||||
"JSONStream": "^1.3.1",
|
||||
"adbkit": "^2.11.1",
|
||||
"adbkit-logcat": "^2.0.1",
|
||||
"algoliasearch": "^4.0.0",
|
||||
"ansi-to-html": "^0.6.3",
|
||||
"async-mutex": "^0.1.3",
|
||||
"chalk": "^3.0.0",
|
||||
"codemirror": "^5.25.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"dashify": "^2.0.0",
|
||||
"decompress": "^4.2.0",
|
||||
"decompress-targz": "^4.1.1",
|
||||
"decompress-unzip": "^4.0.1",
|
||||
"deep-equal": "^2.0.1",
|
||||
"detect-port": "^1.1.1",
|
||||
"emotion": "^10.0.23",
|
||||
"expand-tilde": "^2.0.2",
|
||||
"express": "^4.15.2",
|
||||
"fb-watchman": "^2.0.0",
|
||||
"flipper-doctor": "^0.7.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"immer": "^6.0.0",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"invariant": "^2.2.2",
|
||||
"line-replace": "^1.0.2",
|
||||
"live-plugin-manager": "^0.14.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mkdirp": "^1.0.0",
|
||||
"node-fetch": "^2.3.0",
|
||||
"npm-api": "^1.0.0",
|
||||
"open": "^7.0.0",
|
||||
"openssl-wrapper": "^0.3.4",
|
||||
"p-map": "^4.0.0",
|
||||
"patch-package": "^6.2.0",
|
||||
"pkg": "^4.4.1",
|
||||
"promise-retry": "^1.1.1",
|
||||
"promisify-child-process": "^3.1.3",
|
||||
"prop-types": "^15.6.0",
|
||||
"query-string": "^6.10.1",
|
||||
"react": "16.13.0",
|
||||
"react-color": "^2.11.7",
|
||||
"react-debounce-render": "^6.0.0",
|
||||
"react-devtools-core": "^4.0.6",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-markdown": "^4.2.2",
|
||||
"react-player": "^1.15.2",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-test-renderer": "^16.13.0",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.3.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"remote-redux-devtools": "^0.5.16",
|
||||
"rsocket-core": "^0.0.19",
|
||||
"rsocket-tcp-server": "^0.0.19",
|
||||
"socket.io": "^2.0.4",
|
||||
"string-natural-compare": "^3.0.0",
|
||||
"tmp": "0.0.33",
|
||||
"uuid": "^7.0.1",
|
||||
"websocket": "^1.0.31",
|
||||
"which": "^2.0.1",
|
||||
"ws": "^7.2.0",
|
||||
"xdg-basedir": "^4.0.0",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^15.0.1",
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
"tmp",
|
||||
"flipper-doctor"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/prepare-watchman-config.js && yarn config set ignore-engines",
|
||||
"postinstall": "cross-env TS_NODE_FILES=true node --require ts-node/register scripts/yarn-install.ts && patch-package",
|
||||
"rm-dist": "rimraf dist",
|
||||
"rm-modules": "rimraf node_modules static/node_modules",
|
||||
"rm-temp": "rimraf $TMPDIR/jest* $TMPDIR/react-native-packager*",
|
||||
"rm-bundle": "rimraf static/main.bundle.*",
|
||||
"reset": "yarn rm-dist && yarn rm-temp && yarn cache clean && yarn rm-bundle && yarn rm-modules",
|
||||
"start": "cross-env NODE_ENV=development TS_NODE_FILES=true node --require ts-node/register scripts/start-dev-server.ts --inspect=9229",
|
||||
"start:break": "cross-env NODE_ENV=development TS_NODE_FILES=true node --require ts-node/register scripts/start-dev-server.ts --inspect-brk=9229",
|
||||
"start:no-embedded-plugins": "cross-env NODE_ENV=development cross-env FLIPPER_NO_EMBEDDED_PLUGINS=true TS_NODE_FILES=true node --require ts-node/register scripts/start-dev-server.ts",
|
||||
"build": "yarn rm-dist && cross-env NODE_ENV=production TS_NODE_FILES=true node --require ts-node/register scripts/build-release.ts $@",
|
||||
"build-headless": "yarn rm-dist && mkdir dist && cross-env NODE_ENV=production TS_NODE_FILES=true node --require ts-node/register scripts/build-headless.ts $@",
|
||||
"fix": "eslint . --fix --ext .js,.ts,.tsx",
|
||||
"test": "jest --testPathPattern=\"node\\.(js|tsx)$\" --no-cache",
|
||||
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",
|
||||
"test-electron": "jest --testPathPattern=\"electron\\.(js|tsx)$\" --testEnvironment=@jest-runner/electron/environment --runner=@jest-runner/electron --no-cache",
|
||||
"test-with-device": "USE_ELECTRON_STUBS=1 jest --testPathPattern=\"device\\.(js|tsx)$\" --detectOpenHandles --no-cache",
|
||||
"tsc": "tsc --noemit",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx && flow check && yarn tsc",
|
||||
"everything": "yarn reset && yarn install && yarn lint && yarn test && yarn test-electron && yarn build --mac --win --linux && yarn build-headless --mac --linux && yarn start"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"7zip-bin-mac": "^1.0.1"
|
||||
}
|
||||
}
|
||||
47
desktop/patches/adbkit+2.11.1.patch
Normal file
47
desktop/patches/adbkit+2.11.1.patch
Normal file
@@ -0,0 +1,47 @@
|
||||
diff --git a/node_modules/adbkit/lib/adb/parser.js b/node_modules/adbkit/lib/adb/parser.js
|
||||
index 1b66bda..c41037d 100644
|
||||
--- a/node_modules/adbkit/lib/adb/parser.js
|
||||
+++ b/node_modules/adbkit/lib/adb/parser.js
|
||||
@@ -52,7 +52,7 @@ Parser = (function() {
|
||||
|
||||
Parser.prototype.readAll = function() {
|
||||
var all, endListener, errorListener, resolver, tryRead;
|
||||
- all = new Buffer(0);
|
||||
+ all = Buffer.alloc(0);
|
||||
resolver = Promise.defer();
|
||||
tryRead = (function(_this) {
|
||||
return function() {
|
||||
@@ -108,7 +108,7 @@ Parser = (function() {
|
||||
return resolver.reject(new Parser.PrematureEOFError(howMany));
|
||||
}
|
||||
} else {
|
||||
- return resolver.resolve(new Buffer(0));
|
||||
+ return resolver.resolve(Buffer.alloc(0));
|
||||
}
|
||||
};
|
||||
})(this);
|
||||
@@ -196,7 +196,7 @@ Parser = (function() {
|
||||
|
||||
Parser.prototype.readUntil = function(code) {
|
||||
var read, skipped;
|
||||
- skipped = new Buffer(0);
|
||||
+ skipped = Buffer.alloc(0);
|
||||
read = (function(_this) {
|
||||
return function() {
|
||||
return _this.readBytes(1).then(function(chunk) {
|
||||
diff --git a/node_modules/adbkit/lib/adb/protocol.js b/node_modules/adbkit/lib/adb/protocol.js
|
||||
index d1377d0..2edd7ba 100644
|
||||
--- a/node_modules/adbkit/lib/adb/protocol.js
|
||||
+++ b/node_modules/adbkit/lib/adb/protocol.js
|
||||
@@ -33,9 +33,9 @@ Protocol = (function() {
|
||||
|
||||
Protocol.encodeData = function(data) {
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
- data = new Buffer(data);
|
||||
+ data = Buffer.from(data);
|
||||
}
|
||||
- return Buffer.concat([new Buffer(Protocol.encodeLength(data.length)), data]);
|
||||
+ return Buffer.concat([Buffer.from(Protocol.encodeLength(data.length)), data]);
|
||||
};
|
||||
|
||||
return Protocol;
|
||||
13
desktop/patches/adbkit-logcat+2.0.1.patch
Normal file
13
desktop/patches/adbkit-logcat+2.0.1.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/node_modules/adbkit-logcat/lib/logcat/parser/binary.js b/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
|
||||
index 12fa8ec..e122933 100644
|
||||
--- a/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
|
||||
+++ b/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
|
||||
@@ -10,7 +10,7 @@ const HEADER_SIZE_MAX = 100
|
||||
class Binary extends EventEmitter {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
- this.buffer = new Buffer(0)
|
||||
+ this.buffer = Buffer.alloc(0)
|
||||
}
|
||||
|
||||
parse(chunk) {
|
||||
51
desktop/patches/jsonparse+1.3.1.patch
Normal file
51
desktop/patches/jsonparse+1.3.1.patch
Normal file
@@ -0,0 +1,51 @@
|
||||
diff --git a/node_modules/jsonparse/jsonparse.js b/node_modules/jsonparse/jsonparse.js
|
||||
index 3991060..83f1458 100644
|
||||
--- a/node_modules/jsonparse/jsonparse.js
|
||||
+++ b/node_modules/jsonparse/jsonparse.js
|
||||
@@ -56,7 +56,7 @@ function Parser() {
|
||||
this.value = undefined;
|
||||
|
||||
this.string = undefined; // string data
|
||||
- this.stringBuffer = Buffer.alloc ? Buffer.alloc(STRING_BUFFER_SIZE) : new Buffer(STRING_BUFFER_SIZE);
|
||||
+ this.stringBuffer = Buffer.alloc(STRING_BUFFER_SIZE);
|
||||
this.stringBufferOffset = 0;
|
||||
this.unicode = undefined; // unicode escapes
|
||||
this.highSurrogate = undefined;
|
||||
@@ -67,7 +67,7 @@ function Parser() {
|
||||
this.state = VALUE;
|
||||
this.bytes_remaining = 0; // number of bytes remaining in multi byte utf8 char to read after split boundary
|
||||
this.bytes_in_sequence = 0; // bytes in multi byte utf8 char to read
|
||||
- this.temp_buffs = { "2": new Buffer(2), "3": new Buffer(3), "4": new Buffer(4) }; // for rebuilding chars split before boundary is reached
|
||||
+ this.temp_buffs = { "2": Buffer.alloc(2), "3": Buffer.alloc(3), "4": Buffer.alloc(4) }; // for rebuilding chars split before boundary is reached
|
||||
|
||||
// Stream offset
|
||||
this.offset = -1;
|
||||
@@ -125,7 +125,7 @@ proto.appendStringBuf = function (buf, start, end) {
|
||||
this.stringBufferOffset += size;
|
||||
};
|
||||
proto.write = function (buffer) {
|
||||
- if (typeof buffer === "string") buffer = new Buffer(buffer);
|
||||
+ if (typeof buffer === "string") buffer = Buffer.from(buffer);
|
||||
var n;
|
||||
for (var i = 0, l = buffer.length; i < l; i++) {
|
||||
if (this.tState === START){
|
||||
@@ -221,16 +221,16 @@ proto.write = function (buffer) {
|
||||
var intVal = parseInt(this.unicode, 16);
|
||||
this.unicode = undefined;
|
||||
if (this.highSurrogate !== undefined && intVal >= 0xDC00 && intVal < (0xDFFF + 1)) { //<56320,57343> - lowSurrogate
|
||||
- this.appendStringBuf(new Buffer(String.fromCharCode(this.highSurrogate, intVal)));
|
||||
+ this.appendStringBuf(Buffer.from(String.fromCharCode(this.highSurrogate, intVal)));
|
||||
this.highSurrogate = undefined;
|
||||
} else if (this.highSurrogate === undefined && intVal >= 0xD800 && intVal < (0xDBFF + 1)) { //<55296,56319> - highSurrogate
|
||||
this.highSurrogate = intVal;
|
||||
} else {
|
||||
if (this.highSurrogate !== undefined) {
|
||||
- this.appendStringBuf(new Buffer(String.fromCharCode(this.highSurrogate)));
|
||||
+ this.appendStringBuf(Buffer.from(String.fromCharCode(this.highSurrogate)));
|
||||
this.highSurrogate = undefined;
|
||||
}
|
||||
- this.appendStringBuf(new Buffer(String.fromCharCode(intVal)));
|
||||
+ this.appendStringBuf(Buffer.from(String.fromCharCode(intVal)));
|
||||
}
|
||||
this.tState = STRING1;
|
||||
}
|
||||
1
desktop/pkg/.eslintignore
Normal file
1
desktop/pkg/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
/lib
|
||||
2
desktop/pkg/.gitignore
vendored
Normal file
2
desktop/pkg/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/lib
|
||||
/node_modules/
|
||||
21
desktop/pkg/LICENSE
Normal file
21
desktop/pkg/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
70
desktop/pkg/README.md
Normal file
70
desktop/pkg/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# flipper-pkg
|
||||
|
||||
`flipper-pkg` is a **work-in-progress** tool for bundling and publishing
|
||||
Flipper plugins.
|
||||
|
||||
<!-- toc -->
|
||||
* [Usage](#usage)
|
||||
* [Commands](#commands)
|
||||
<!-- tocstop -->
|
||||
# Usage
|
||||
<!-- usage -->
|
||||
```sh-session
|
||||
$ npm install -g mycli
|
||||
$ mycli COMMAND
|
||||
running command...
|
||||
$ mycli (-v|--version|version)
|
||||
mycli/0.0.0 darwin-x64 node-v12.14.0
|
||||
$ mycli --help [COMMAND]
|
||||
USAGE
|
||||
$ mycli COMMAND
|
||||
...
|
||||
```
|
||||
<!-- usagestop -->
|
||||
# Commands
|
||||
<!-- commands -->
|
||||
* [`mycli hello [FILE]`](#mycli-hello-file)
|
||||
* [`mycli help [COMMAND]`](#mycli-help-command)
|
||||
|
||||
## `mycli hello [FILE]`
|
||||
|
||||
describe the command here
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ mycli hello [FILE]
|
||||
|
||||
OPTIONS
|
||||
-f, --force
|
||||
-h, --help show CLI help
|
||||
-n, --name=name name to print
|
||||
|
||||
EXAMPLE
|
||||
$ mycli hello
|
||||
hello world from ./src/hello.ts!
|
||||
```
|
||||
|
||||
_See code: [src/commands/hello.ts](https://github.com/passy/mycli/blob/v0.0.0/src/commands/hello.ts)_
|
||||
|
||||
## `mycli help [COMMAND]`
|
||||
|
||||
display help for mycli
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ mycli help [COMMAND]
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND command to show help for
|
||||
|
||||
OPTIONS
|
||||
--all see all commands in CLI
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_
|
||||
<!-- commandsstop -->
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
14
desktop/pkg/bin/run
Executable file
14
desktop/pkg/bin/run
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
require('@oclif/command').run()
|
||||
.then(require('@oclif/command/flush'))
|
||||
.catch(require('@oclif/errors/handle'))
|
||||
8
desktop/pkg/bin/run.cmd
Normal file
8
desktop/pkg/bin/run.cmd
Normal file
@@ -0,0 +1,8 @@
|
||||
@REM Copyright (c) Facebook, Inc. and its affiliates.
|
||||
@REM
|
||||
@REM This source code is licensed under the MIT license found in the
|
||||
@REM LICENSE file in the root directory of this source tree.
|
||||
|
||||
@echo off
|
||||
|
||||
node "%~dp0run" %*
|
||||
11
desktop/pkg/jestconfig.json
Normal file
11
desktop/pkg/jestconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
"\/node_modules\/",
|
||||
"\/lib\/"
|
||||
],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
||||
79
desktop/pkg/package.json
Normal file
79
desktop/pkg/package.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "flipper-pkg",
|
||||
"version": "0.0.0",
|
||||
"description": "Utility for building and publishing Flipper plugins",
|
||||
"repository": "facebook/flipper",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"flipper-pkg": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/facebook/flipper/issues",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
"@babel/generator": "^7.8.6",
|
||||
"@babel/parser": "^7.8.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
|
||||
"@babel/plugin-transform-typescript": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@oclif/command": "^1",
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/plugin-help": "^2",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/node": "^13.7.5",
|
||||
"cli-ux": "^5.4.5",
|
||||
"fs-extra": "^8.1.0",
|
||||
"metro": "^0.58.0",
|
||||
"inquirer": "^7.0.5",
|
||||
"tslib": "^1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1",
|
||||
"@types/jest": "^24.0.21",
|
||||
"globby": "^10",
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.19.1",
|
||||
"ts-jest": "^24.1.0",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
|
||||
"prepare": "yarn run build",
|
||||
"prepublishOnly": "yarn test && yarn run lint",
|
||||
"preversion": "yarn run lint",
|
||||
"test": "jest --config jestconfig.json",
|
||||
"run": "bin/run",
|
||||
"version": "oclif-dev readme && git add README.md"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
"/npm-shrinkwrap.json",
|
||||
"/oclif.manifest.json",
|
||||
"lib/**/*"
|
||||
],
|
||||
"homepage": "https://github.com/facebook/flipper",
|
||||
"keywords": [
|
||||
"Flipper"
|
||||
],
|
||||
"author": "Facebook, Inc",
|
||||
"oclif": {
|
||||
"commands": "./lib/commands",
|
||||
"bin": "flipper-pkg",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
desktop/pkg/src/__tests__/index.ts
Normal file
12
desktop/pkg/src/__tests__/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
127
desktop/pkg/src/commands/bundle.ts
Normal file
127
desktop/pkg/src/commands/bundle.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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 {promises as fs} from 'fs';
|
||||
import {mkdirp, pathExists, readJSON, ensureDir} from 'fs-extra';
|
||||
import * as inquirer from 'inquirer';
|
||||
import * as path from 'path';
|
||||
import * as yarn from '../utils/yarn';
|
||||
import cli from 'cli-ux';
|
||||
import runBuild from '../utils/runBuild';
|
||||
|
||||
async function deriveOutputFileName(inputDirectory: string): Promise<string> {
|
||||
const packageJson = await readJSON(path.join(inputDirectory, 'package.json'));
|
||||
return `${packageJson.name || ''}-${packageJson.version}.tgz`;
|
||||
}
|
||||
|
||||
export default class Bundle extends Command {
|
||||
public static description =
|
||||
'bundle a plugin folder into a distributable archive';
|
||||
|
||||
public static examples = [`$ flipper-pkg bundle path/to/plugin`];
|
||||
|
||||
public static flags = {
|
||||
output: flags.string({
|
||||
char: 'o',
|
||||
default: '.',
|
||||
description:
|
||||
"Where to output the bundle, file or directory. Defaults to '.'.",
|
||||
}),
|
||||
};
|
||||
|
||||
public static args = [{name: 'directory', required: true}];
|
||||
|
||||
public async run() {
|
||||
const {args, flags: parsedFlags} = this.parse(Bundle);
|
||||
|
||||
const stat = await fs.lstat(args.directory);
|
||||
if (!stat.isDirectory()) {
|
||||
this.error(`Plugin source ${args.directory} is not a directory.`);
|
||||
}
|
||||
|
||||
let output;
|
||||
if (await pathExists(parsedFlags.output)) {
|
||||
const outputStat = await fs.lstat(parsedFlags.output);
|
||||
if (outputStat.isDirectory()) {
|
||||
output = path.resolve(
|
||||
path.join(
|
||||
parsedFlags.output,
|
||||
await deriveOutputFileName(args.directory),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
output = parsedFlags.output;
|
||||
}
|
||||
} else {
|
||||
let dir;
|
||||
let file = null;
|
||||
// Treat this as a
|
||||
if (parsedFlags.output.slice(-1) === '/') {
|
||||
dir = parsedFlags.output;
|
||||
} else {
|
||||
dir = path.dirname(parsedFlags.output);
|
||||
file = path.basename(parsedFlags.output);
|
||||
}
|
||||
|
||||
if (!(await pathExists(dir))) {
|
||||
const answer: {confirm: boolean} = await inquirer.prompt({
|
||||
default: true,
|
||||
message: `Output directory '${dir}' doesn't exist. Create it?`,
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
});
|
||||
|
||||
if (answer.confirm) {
|
||||
mkdirp(dir);
|
||||
} else {
|
||||
this.error(`Output directory ${dir} not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (file === null) {
|
||||
file = await deriveOutputFileName(args.directory);
|
||||
}
|
||||
output = path.join(dir, file);
|
||||
}
|
||||
|
||||
const inputDirectory = path.resolve(args.directory);
|
||||
const outputFile = path.resolve(output);
|
||||
|
||||
this.log(`⚙️ Bundling ${inputDirectory} to ${outputFile}...`);
|
||||
|
||||
cli.action.start('Installing dependencies');
|
||||
await yarn.install(inputDirectory);
|
||||
cli.action.stop();
|
||||
|
||||
cli.action.start('Reading package.json');
|
||||
const packageJson = await readJSON(
|
||||
path.join(inputDirectory, 'package.json'),
|
||||
);
|
||||
const entry =
|
||||
packageJson.main ??
|
||||
((await pathExists(path.join(inputDirectory, 'index.tsx')))
|
||||
? 'index.tsx'
|
||||
: 'index.jsx');
|
||||
const bundleMain = packageJson.bundleMain ?? path.join('dist', 'index.js');
|
||||
const out = path.resolve(inputDirectory, bundleMain);
|
||||
cli.action.stop(`done. Entry: ${entry}. Bundle main: ${bundleMain}.`);
|
||||
|
||||
cli.action.start(`Compiling`);
|
||||
await ensureDir(path.dirname(out));
|
||||
await runBuild(inputDirectory, entry, out);
|
||||
cli.action.stop();
|
||||
|
||||
cli.action.start(`Packing to ${outputFile}`);
|
||||
await yarn.pack(inputDirectory, outputFile);
|
||||
cli.action.stop();
|
||||
|
||||
this.log(`✅ Bundled ${inputDirectory} to ${outputFile}`);
|
||||
}
|
||||
}
|
||||
12
desktop/pkg/src/index.ts
Normal file
12
desktop/pkg/src/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export {run} from '@oclif/command';
|
||||
export const PKG = 'flipper-pkg';
|
||||
export {default as runBuild} from './utils/runBuild';
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const {parse} = require('@babel/parser');
|
||||
const {transformFromAstSync} = require('@babel/core');
|
||||
const {default: generate} = require('@babel/generator');
|
||||
|
||||
const flipperRequires = require('../flipper-requires');
|
||||
|
||||
const babelOptions = {
|
||||
ast: true,
|
||||
plugins: [flipperRequires],
|
||||
filename: 'index.js',
|
||||
};
|
||||
|
||||
test('transform react requires to global object', () => {
|
||||
const src = 'require("react")';
|
||||
const ast = parse(src);
|
||||
const transformed = transformFromAstSync(ast, src, babelOptions).ast;
|
||||
const {code} = generate(transformed);
|
||||
expect(code).toBe('global.React;');
|
||||
});
|
||||
|
||||
test('transform react-dom requires to global object', () => {
|
||||
const src = 'require("react-dom")';
|
||||
const ast = parse(src);
|
||||
const transformed = transformFromAstSync(ast, src, babelOptions).ast;
|
||||
const {code} = generate(transformed);
|
||||
expect(code).toBe('global.ReactDOM;');
|
||||
});
|
||||
|
||||
test('transform flipper requires to global object', () => {
|
||||
const src = 'require("flipper")';
|
||||
const ast = parse(src);
|
||||
const transformed = transformFromAstSync(ast, src, babelOptions).ast;
|
||||
const {code} = generate(transformed);
|
||||
expect(code).toBe('global.Flipper;');
|
||||
});
|
||||
|
||||
test('transform React identifier to global.React', () => {
|
||||
const src = 'React;';
|
||||
const ast = parse(src);
|
||||
const transformed = transformFromAstSync(ast, src, babelOptions).ast;
|
||||
const {code} = generate(transformed);
|
||||
expect(code).toBe('global.React;');
|
||||
});
|
||||
|
||||
test.skip('throw error when requiring outside the plugin', () => {
|
||||
const src = 'require("../test.js")';
|
||||
const ast = parse(src);
|
||||
expect(() => {
|
||||
transformFromAstSync(ast, src, babelOptions);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('allow requiring from parent folder as long as we stay in plugin folder', () => {
|
||||
const src = 'require("../test.js")';
|
||||
const ast = parse(src);
|
||||
const transformed = transformFromAstSync(ast, src, {
|
||||
...babelOptions,
|
||||
root: '/path/to/plugin',
|
||||
filename: '/path/to/plugin/subfolder/index.js',
|
||||
}).ast;
|
||||
const {code} = generate(transformed);
|
||||
expect(code).toBe('require("../test.js");');
|
||||
});
|
||||
34
desktop/pkg/src/transforms/dynamic-requires.js
Normal file
34
desktop/pkg/src/transforms/dynamic-requires.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
function isDynamicRequire(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'require' &&
|
||||
(node.arguments.length !== 1 || node.arguments[0].type !== 'StringLiteral')
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function(babel) {
|
||||
const t = babel.types;
|
||||
|
||||
return {
|
||||
name: 'replace-dynamic-requires',
|
||||
visitor: {
|
||||
CallExpression(path) {
|
||||
if (!isDynamicRequire(path.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
path.replaceWith(t.identifier('triggerDynamicRequireError'));
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
94
desktop/pkg/src/transforms/electron-requires.js
Normal file
94
desktop/pkg/src/transforms/electron-requires.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const BUILTINS = [
|
||||
'electron',
|
||||
'buffer',
|
||||
'child_process',
|
||||
'crypto',
|
||||
'dgram',
|
||||
'dns',
|
||||
'fs',
|
||||
'http',
|
||||
'https',
|
||||
'net',
|
||||
'os',
|
||||
'readline',
|
||||
'stream',
|
||||
'string_decoder',
|
||||
'tls',
|
||||
'tty',
|
||||
'zlib',
|
||||
'constants',
|
||||
'events',
|
||||
'url',
|
||||
'assert',
|
||||
'util',
|
||||
'path',
|
||||
'perf_hooks',
|
||||
'punycode',
|
||||
'querystring',
|
||||
'cluster',
|
||||
'console',
|
||||
'module',
|
||||
'process',
|
||||
'vm',
|
||||
'domain',
|
||||
'v8',
|
||||
'repl',
|
||||
'timers',
|
||||
'node-fetch',
|
||||
];
|
||||
|
||||
const IGNORED_MODULES = [
|
||||
'bufferutil',
|
||||
'utf-8-validate',
|
||||
'spawn-sync',
|
||||
'./src/logcat',
|
||||
'./src/monkey',
|
||||
'./src/adb',
|
||||
];
|
||||
|
||||
function isRequire(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'require' &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments[0].type === 'StringLiteral'
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function(babel) {
|
||||
const t = babel.types;
|
||||
|
||||
return {
|
||||
name: 'infinity-import-react',
|
||||
visitor: {
|
||||
CallExpression(path) {
|
||||
if (!isRequire(path.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const source = path.node.arguments[0].value;
|
||||
|
||||
if (
|
||||
BUILTINS.includes(source) ||
|
||||
BUILTINS.some(moduleName => source.startsWith(`${moduleName}/`))
|
||||
) {
|
||||
path.node.callee.name = 'electronRequire';
|
||||
}
|
||||
|
||||
if (IGNORED_MODULES.includes(source)) {
|
||||
path.replaceWith(t.identifier('triggerReferenceError'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
35
desktop/pkg/src/transforms/electron-stubs.js
Normal file
35
desktop/pkg/src/transforms/electron-stubs.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const babylon = require('@babel/parser');
|
||||
const fs = require('fs');
|
||||
|
||||
const electronStubs = babylon.parseExpression(
|
||||
fs.readFileSync('static/electron-stubs.notjs').toString(),
|
||||
);
|
||||
|
||||
module.exports = function(babel) {
|
||||
return {
|
||||
name: 'replace-electron-requires-with-stubs',
|
||||
visitor: {
|
||||
CallExpression(path) {
|
||||
if (
|
||||
path.node.type === 'CallExpression' &&
|
||||
path.node.callee.type === 'Identifier' &&
|
||||
path.node.callee.name === 'require' &&
|
||||
path.node.arguments.length > 0
|
||||
) {
|
||||
if (path.node.arguments[0].value === 'electron') {
|
||||
path.replaceWith(electronStubs);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
47
desktop/pkg/src/transforms/fb-stubs.js
Normal file
47
desktop/pkg/src/transforms/fb-stubs.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const replaceFBStubs = fs.existsSync(
|
||||
path.join(__dirname, '..', '..', 'src', 'fb'),
|
||||
);
|
||||
|
||||
const requireFromFolder = (folder, path) =>
|
||||
new RegExp(folder + '/[A-Za-z0-9.-_]+(.js)?$', 'g').test(path);
|
||||
|
||||
module.exports = function(babel) {
|
||||
return {
|
||||
name: 'replace-dynamic-requires',
|
||||
visitor: {
|
||||
CallExpression(path) {
|
||||
if (
|
||||
replaceFBStubs &&
|
||||
path.node.type === 'CallExpression' &&
|
||||
path.node.callee.type === 'Identifier' &&
|
||||
path.node.callee.name === 'require' &&
|
||||
path.node.arguments.length > 0
|
||||
) {
|
||||
if (requireFromFolder('fb', path.node.arguments[0].value)) {
|
||||
throw new Error(
|
||||
'Do not require directly from fb/, but rather from fb-stubs/ to not break flow-typing and make sure stubs are up-to-date.',
|
||||
);
|
||||
} else if (
|
||||
requireFromFolder('fb-stubs', path.node.arguments[0].value)
|
||||
) {
|
||||
path.node.arguments[0].value = path.node.arguments[0].value.replace(
|
||||
'/fb-stubs/',
|
||||
'/fb/',
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
81
desktop/pkg/src/transforms/flipper-requires.js
Normal file
81
desktop/pkg/src/transforms/flipper-requires.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const {resolve, dirname} = require('path');
|
||||
|
||||
// do not apply this transform for these paths
|
||||
const EXCLUDE_PATHS = [
|
||||
'/node_modules/react-devtools-core/',
|
||||
'relay-devtools/DevtoolsUI',
|
||||
];
|
||||
|
||||
function isExcludedPath(path) {
|
||||
for (const epath of EXCLUDE_PATHS) {
|
||||
if (path.indexOf(epath) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} // $FlowFixMe
|
||||
module.exports = ({types: t}) => ({
|
||||
visitor: {
|
||||
// $FlowFixMe
|
||||
CallExpression(path, state) {
|
||||
if (isExcludedPath(state.file.opts.filename)) {
|
||||
return;
|
||||
}
|
||||
const node = path.node;
|
||||
const args = node.arguments || [];
|
||||
|
||||
if (
|
||||
node.callee.name === 'require' &&
|
||||
args.length === 1 &&
|
||||
t.isStringLiteral(args[0])
|
||||
) {
|
||||
if (args[0].value === 'flipper') {
|
||||
path.replaceWith(t.identifier('global.Flipper'));
|
||||
} else if (args[0].value === 'react') {
|
||||
path.replaceWith(t.identifier('global.React'));
|
||||
} else if (args[0].value === 'react-dom') {
|
||||
path.replaceWith(t.identifier('global.ReactDOM'));
|
||||
} else if (args[0].value === 'adbkit') {
|
||||
path.replaceWith(t.identifier('global.adbkit'));
|
||||
} else if (
|
||||
// require a file not a pacakge
|
||||
args[0].value.indexOf('/') > -1 &&
|
||||
// in the plugin itself and not inside one of its dependencies
|
||||
state.file.opts.filename.indexOf('node_modules') === -1 &&
|
||||
// the resolved path for this file is outside the plugins root
|
||||
!resolve(dirname(state.file.opts.filename), args[0].value).startsWith(
|
||||
state.file.opts.root,
|
||||
) &&
|
||||
!resolve(dirname(state.file.opts.filename), args[0].value).indexOf(
|
||||
'/static/',
|
||||
) < 0
|
||||
) {
|
||||
throw new Error(
|
||||
`Plugins cannot require files from outside their folder. Attempted to require ${resolve(
|
||||
dirname(state.file.opts.filename),
|
||||
args[0].value,
|
||||
)} which isn't inside ${state.file.opts.root}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Identifier(path, state) {
|
||||
if (
|
||||
path.node.name === 'React' &&
|
||||
path.parentPath.node.id !== path.node &&
|
||||
!isExcludedPath(state.file.opts.filename)
|
||||
) {
|
||||
path.replaceWith(t.identifier('global.React'));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
124
desktop/pkg/src/transforms/index.js
Normal file
124
desktop/pkg/src/transforms/index.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const generate = require('@babel/generator').default;
|
||||
const babylon = require('@babel/parser');
|
||||
const babel = require('@babel/core');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function transform({filename, options, src}) {
|
||||
const isTypeScript = filename.endsWith('.tsx') || filename.endsWith('.ts');
|
||||
const presets = [require('@babel/preset-react')];
|
||||
|
||||
const ast = babylon.parse(src, {
|
||||
filename,
|
||||
plugins: isTypeScript
|
||||
? [
|
||||
'jsx',
|
||||
'typescript',
|
||||
'classProperties',
|
||||
'optionalChaining',
|
||||
'nullishCoalescingOperator',
|
||||
]
|
||||
: [
|
||||
'jsx',
|
||||
['flow', {all: true}],
|
||||
'classProperties',
|
||||
'objectRestSpread',
|
||||
'optionalChaining',
|
||||
'nullishCoalescingOperator',
|
||||
],
|
||||
sourceType: 'module',
|
||||
});
|
||||
|
||||
// run babel
|
||||
const plugins = [];
|
||||
|
||||
if (!isTypeScript) {
|
||||
plugins.push(
|
||||
require('@babel/plugin-transform-modules-commonjs'),
|
||||
require('@babel/plugin-proposal-object-rest-spread'),
|
||||
require('@babel/plugin-proposal-class-properties'),
|
||||
require('@babel/plugin-transform-flow-strip-types'),
|
||||
require('@babel/plugin-proposal-optional-chaining'),
|
||||
require('@babel/plugin-proposal-nullish-coalescing-operator'),
|
||||
require('./dynamic-requires.js'),
|
||||
);
|
||||
} else {
|
||||
plugins.push(
|
||||
require('@babel/plugin-transform-typescript'),
|
||||
require('@babel/plugin-proposal-class-properties'),
|
||||
require('@babel/plugin-transform-modules-commonjs'),
|
||||
require('@babel/plugin-proposal-optional-chaining'),
|
||||
require('@babel/plugin-proposal-nullish-coalescing-operator'),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.resolve(path.dirname(path.dirname(__dirname)), 'src', 'fb'),
|
||||
)
|
||||
) {
|
||||
plugins.push(require('./fb-stubs.js'));
|
||||
}
|
||||
if (!options.isTestRunner) {
|
||||
// Replacing require statements with electronRequire to prevent metro from
|
||||
// resolving them. electronRequire are resolved during runtime by electron.
|
||||
// As the tests are not bundled by metro and run in @jest-runner/electron,
|
||||
// electron imports are working out of the box.
|
||||
plugins.push(require('./electron-requires'));
|
||||
}
|
||||
plugins.push(require('./flipper-requires.js'));
|
||||
const transformed = babel.transformFromAst(ast, src, {
|
||||
ast: true,
|
||||
babelrc: !filename.includes('node_modules'),
|
||||
code: false,
|
||||
comments: false,
|
||||
compact: false,
|
||||
root: options.projectRoot,
|
||||
filename,
|
||||
plugins,
|
||||
presets,
|
||||
sourceMaps: true,
|
||||
retainLines: !!options.isTestRunner,
|
||||
});
|
||||
const result = generate(
|
||||
transformed.ast,
|
||||
{
|
||||
filename,
|
||||
sourceFileName: filename,
|
||||
sourceMaps: true,
|
||||
retainLines: !!options.isTestRunner,
|
||||
},
|
||||
src,
|
||||
);
|
||||
return {
|
||||
ast: transformed.ast,
|
||||
code: result.code,
|
||||
filename,
|
||||
map: result.map,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
transform,
|
||||
// Disable caching of babel transforms all together. We haven't found a good
|
||||
// way to cache our transforms, as they rely on side effects like env vars or
|
||||
// the existence of folders in the file system.
|
||||
getCacheKey: () => Math.random().toString(36),
|
||||
process(src, filename, config, options) {
|
||||
return transform({
|
||||
src,
|
||||
filename,
|
||||
config,
|
||||
options: {...options, isTestRunner: true},
|
||||
});
|
||||
},
|
||||
};
|
||||
72
desktop/pkg/src/utils/runBuild.ts
Normal file
72
desktop/pkg/src/utils/runBuild.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const Metro = require('metro'); // no typings :(
|
||||
import * as path from 'path';
|
||||
|
||||
function hash(string: string) {
|
||||
let hash = 0;
|
||||
if (string.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
let chr;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
chr = string.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
const fileToIdMap = new Map();
|
||||
const createModuleIdFactory = () => (filePath: string) => {
|
||||
if (filePath === '__prelude__') {
|
||||
return 0;
|
||||
}
|
||||
let id = fileToIdMap.get(filePath);
|
||||
if (typeof id !== 'number') {
|
||||
id = hash(filePath);
|
||||
fileToIdMap.set(filePath, id);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
export default async function runBuild(
|
||||
inputDirectory: string,
|
||||
entry: string,
|
||||
out: string,
|
||||
) {
|
||||
await Metro.runBuild(
|
||||
{
|
||||
reporter: {update: () => {}},
|
||||
projectRoot: inputDirectory,
|
||||
watchFolders: [inputDirectory, path.resolve(__dirname, '..', '..')],
|
||||
serializer: {
|
||||
getRunModuleStatement: (moduleID: string) =>
|
||||
`module.exports = global.__r(${moduleID}).default;`,
|
||||
createModuleIdFactory,
|
||||
},
|
||||
transformer: {
|
||||
babelTransformerPath: path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'transforms',
|
||||
'index.js',
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
dev: false,
|
||||
minify: false,
|
||||
resetCache: true,
|
||||
sourceMap: true,
|
||||
entry,
|
||||
out,
|
||||
},
|
||||
);
|
||||
}
|
||||
36
desktop/pkg/src/utils/yarn.ts
Normal file
36
desktop/pkg/src/utils/yarn.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 * as util from 'util';
|
||||
import {exec as execImport} from 'child_process';
|
||||
const exec = util.promisify(execImport);
|
||||
|
||||
const WINDOWS = /^win/.test(process.platform);
|
||||
const YARN_PATH = 'yarn' + (WINDOWS ? '.cmd' : '');
|
||||
|
||||
export async function install(pkgDir: string) {
|
||||
const {stderr} = await exec(YARN_PATH, {
|
||||
cwd: pkgDir,
|
||||
});
|
||||
if (stderr) {
|
||||
console.warn(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
export async function pack(pkgDir: string, out: string) {
|
||||
const {stderr} = await exec(
|
||||
[YARN_PATH, 'pack', '--filename', out].join(' '),
|
||||
{
|
||||
cwd: pkgDir,
|
||||
},
|
||||
);
|
||||
if (stderr) {
|
||||
console.warn(stderr);
|
||||
}
|
||||
}
|
||||
16
desktop/pkg/tsconfig.json
Normal file
16
desktop/pkg/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"importHelpers": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
7
desktop/pkg/tslint.json
Normal file
7
desktop/pkg/tslint.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"interface-name": false,
|
||||
"variable-name": false
|
||||
}
|
||||
}
|
||||
5742
desktop/pkg/yarn.lock
Normal file
5742
desktop/pkg/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
121
desktop/scripts/build-headless.ts
Normal file
121
desktop/scripts/build-headless.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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';
|
||||
import path from 'path';
|
||||
import lineReplace from 'line-replace';
|
||||
import yazl from 'yazl';
|
||||
const {exec: createBinary} = require('pkg');
|
||||
import {
|
||||
buildFolder,
|
||||
compile,
|
||||
compileDefaultPlugins,
|
||||
getVersionNumber,
|
||||
genMercurialRevision,
|
||||
} from './build-utils';
|
||||
|
||||
const PLUGINS_FOLDER_NAME = 'plugins';
|
||||
|
||||
function preludeBundle(
|
||||
dir: string,
|
||||
versionNumber: string,
|
||||
buildRevision: string | null,
|
||||
) {
|
||||
const revisionStr =
|
||||
buildRevision == null ? '' : `global.__REVISION__="${buildRevision}";`;
|
||||
return new Promise(resolve =>
|
||||
lineReplace({
|
||||
file: path.join(dir, 'bundle.js'),
|
||||
line: 1,
|
||||
text: `var __DEV__=false; global.electronRequire = require; global.performance = require("perf_hooks").performance;global.__VERSION__="${versionNumber}";${revisionStr}`,
|
||||
addNewLine: true,
|
||||
callback: resolve,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function createZip(buildDir: string, distDir: string, targets: string[]) {
|
||||
return new Promise(resolve => {
|
||||
const zip = new yazl.ZipFile();
|
||||
|
||||
// add binaries for each target
|
||||
targets.forEach(target => {
|
||||
const binary = `flipper-${target === 'mac' ? 'macos' : target}`;
|
||||
zip.addFile(path.join(buildDir, binary), binary);
|
||||
});
|
||||
|
||||
// add plugins
|
||||
const pluginDir = path.join(buildDir, PLUGINS_FOLDER_NAME);
|
||||
fs.readdirSync(pluginDir).forEach(file => {
|
||||
zip.addFile(
|
||||
path.join(pluginDir, file),
|
||||
path.join(PLUGINS_FOLDER_NAME, file),
|
||||
);
|
||||
});
|
||||
|
||||
// write zip file
|
||||
zip.outputStream
|
||||
.pipe(fs.createWriteStream(path.join(distDir, 'Flipper-headless.zip')))
|
||||
.on('close', resolve);
|
||||
zip.end();
|
||||
});
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const targets: {mac?: string; linux?: string; win?: string} = {};
|
||||
let platformPostfix: string = '';
|
||||
if (process.argv.indexOf('--mac') > -1) {
|
||||
targets.mac = 'node10-macos-x64';
|
||||
platformPostfix = '-macos';
|
||||
}
|
||||
if (process.argv.indexOf('--linux') > -1) {
|
||||
targets.linux = 'node10-linux-x64';
|
||||
platformPostfix = '-linux';
|
||||
}
|
||||
if (process.argv.indexOf('--win') > -1) {
|
||||
targets.win = 'node10-win-x64';
|
||||
platformPostfix = '-win';
|
||||
}
|
||||
const length = Object.keys(targets).length;
|
||||
if (length === 0) {
|
||||
throw new Error('No targets specified. eg. --mac, --win, or --linux');
|
||||
} else if (length > 1) {
|
||||
// platformPostfix is automatically added by pkg
|
||||
platformPostfix = '';
|
||||
}
|
||||
// Compiling all plugins takes a long time. Use this flag for quicker
|
||||
// developement iteration by not including any plugins.
|
||||
const skipPlugins = process.argv.indexOf('--no-plugins') > -1;
|
||||
|
||||
process.env.BUILD_HEADLESS = 'true';
|
||||
const buildDir = await buildFolder();
|
||||
const distDir = path.join(__dirname, '..', '..', 'dist');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Created build directory', buildDir);
|
||||
await compile(buildDir, path.join(__dirname, '..', 'headless', 'index.tsx'));
|
||||
const versionNumber = getVersionNumber();
|
||||
const buildRevision = await genMercurialRevision();
|
||||
await preludeBundle(buildDir, versionNumber, buildRevision);
|
||||
await compileDefaultPlugins(
|
||||
path.join(buildDir, PLUGINS_FOLDER_NAME),
|
||||
skipPlugins,
|
||||
);
|
||||
await createBinary([
|
||||
path.join(buildDir, 'bundle.js'),
|
||||
'--output',
|
||||
path.join(buildDir, `flipper${platformPostfix}`),
|
||||
'--targets',
|
||||
Object.values(targets).join(','),
|
||||
'--debug',
|
||||
]);
|
||||
await createZip(buildDir, distDir, Object.keys(targets));
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('✨ Done');
|
||||
process.exit();
|
||||
})();
|
||||
194
desktop/scripts/build-release.ts
Executable file
194
desktop/scripts/build-release.ts
Executable file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 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 {Platform, Arch, ElectronDownloadOptions, build} from 'electron-builder';
|
||||
import {spawn} from 'promisify-child-process';
|
||||
import {
|
||||
buildFolder,
|
||||
compile,
|
||||
compileMain,
|
||||
die,
|
||||
compileDefaultPlugins,
|
||||
getVersionNumber,
|
||||
genMercurialRevision,
|
||||
} from './build-utils';
|
||||
import fetch from 'node-fetch';
|
||||
import {getIcons, buildLocalIconPath, getIconURL} from '../src/utils/icons';
|
||||
|
||||
function generateManifest(versionNumber: string) {
|
||||
const filePath = path.join(__dirname, '..', '..', 'dist');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
fs.mkdirSync(filePath);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '..', '..', 'dist', 'manifest.json'),
|
||||
JSON.stringify({
|
||||
package: 'com.facebook.sonar',
|
||||
version_name: versionNumber,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function modifyPackageManifest(
|
||||
buildFolder: string,
|
||||
versionNumber: string,
|
||||
hgRevision: string | null,
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Creating package.json manifest');
|
||||
const manifest = require('../package.json');
|
||||
const manifestStatic = require('../static/package.json');
|
||||
|
||||
// The manifest's dependencies are bundled with the final app by
|
||||
// electron-builder. We want to bundle the dependencies from the static-folder
|
||||
// because all dependencies from the root-folder are already bundled by metro.
|
||||
manifest.dependencies = manifestStatic.dependencies;
|
||||
manifest.main = 'index.js';
|
||||
manifest.version = versionNumber;
|
||||
if (hgRevision != null) {
|
||||
manifest.revision = hgRevision;
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(buildFolder, 'package.json'),
|
||||
JSON.stringify(manifest, null, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
async function buildDist(buildFolder: string) {
|
||||
const targetsRaw: Map<Platform, Map<Arch, string[]>>[] = [];
|
||||
const postBuildCallbacks: (() => void)[] = [];
|
||||
|
||||
if (process.argv.indexOf('--mac') > -1) {
|
||||
targetsRaw.push(Platform.MAC.createTarget(['dir']));
|
||||
// You can build mac apps on Linux but can't build dmgs, so we separate those.
|
||||
if (process.argv.indexOf('--mac-dmg') > -1) {
|
||||
targetsRaw.push(Platform.MAC.createTarget(['dmg']));
|
||||
}
|
||||
postBuildCallbacks.push(() =>
|
||||
spawn('zip', ['-qyr9', '../Flipper-mac.zip', 'Flipper.app'], {
|
||||
cwd: path.join(__dirname, '..', '..', 'dist', 'mac'),
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (process.argv.indexOf('--linux') > -1) {
|
||||
targetsRaw.push(Platform.LINUX.createTarget(['zip']));
|
||||
}
|
||||
if (process.argv.indexOf('--win') > -1) {
|
||||
targetsRaw.push(Platform.WINDOWS.createTarget(['zip']));
|
||||
}
|
||||
if (!targetsRaw.length) {
|
||||
throw new Error('No targets specified. eg. --mac, --win, or --linux');
|
||||
}
|
||||
|
||||
// merge all target maps into a single map
|
||||
let targetsMerged: [Platform, Map<Arch, string[]>][] = [];
|
||||
for (const target of targetsRaw) {
|
||||
targetsMerged = targetsMerged.concat(Array.from(target));
|
||||
}
|
||||
const targets = new Map(targetsMerged);
|
||||
|
||||
const electronDownloadOptions: ElectronDownloadOptions = {};
|
||||
if (process.env.electron_config_cache) {
|
||||
electronDownloadOptions.cache = process.env.electron_config_cache;
|
||||
}
|
||||
|
||||
try {
|
||||
await build({
|
||||
publish: 'never',
|
||||
config: {
|
||||
appId: `com.facebook.sonar`,
|
||||
directories: {
|
||||
buildResources: path.join(__dirname, '..', 'static'),
|
||||
output: path.join(__dirname, '..', '..', 'dist'),
|
||||
},
|
||||
electronDownload: electronDownloadOptions,
|
||||
npmRebuild: false,
|
||||
},
|
||||
projectDir: buildFolder,
|
||||
targets,
|
||||
});
|
||||
return await Promise.all(postBuildCallbacks.map(p => p()));
|
||||
} catch (err) {
|
||||
return die(err);
|
||||
}
|
||||
}
|
||||
|
||||
function copyStaticFolder(buildFolder: string) {
|
||||
fs.copySync(path.join(__dirname, '..', 'static'), buildFolder, {
|
||||
dereference: true,
|
||||
});
|
||||
}
|
||||
|
||||
function downloadIcons(buildFolder: string) {
|
||||
const iconURLs = Object.entries(getIcons()).reduce<
|
||||
{
|
||||
name: string;
|
||||
size: number;
|
||||
density: number;
|
||||
}[]
|
||||
>((acc, [name, sizes]) => {
|
||||
acc.push(
|
||||
// get icons in @1x and @2x
|
||||
...sizes.map(size => ({name, size, density: 1})),
|
||||
...sizes.map(size => ({name, size, density: 2})),
|
||||
);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return Promise.all(
|
||||
iconURLs.map(({name, size, density}) => {
|
||||
const url = getIconURL(name, size, density);
|
||||
return fetch(url)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
`Could not download the icon ${name} from ${url}: got status ${
|
||||
res.status
|
||||
}`,
|
||||
);
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.then(
|
||||
res =>
|
||||
new Promise((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(
|
||||
path.join(buildFolder, buildLocalIconPath(name, size, density)),
|
||||
);
|
||||
res.body.pipe(fileStream);
|
||||
res.body.on('error', reject);
|
||||
fileStream.on('finish', resolve);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const dir = await buildFolder();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Created build directory', dir);
|
||||
await compileMain({dev: false});
|
||||
copyStaticFolder(dir);
|
||||
await downloadIcons(dir);
|
||||
await compileDefaultPlugins(path.join(dir, 'defaultPlugins'));
|
||||
await compile(dir, path.join(__dirname, '..', 'src', 'init.tsx'));
|
||||
const versionNumber = getVersionNumber();
|
||||
const hgRevision = await genMercurialRevision();
|
||||
modifyPackageManifest(dir, versionNumber, hgRevision);
|
||||
generateManifest(versionNumber);
|
||||
await buildDist(dir);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('✨ Done');
|
||||
process.exit();
|
||||
})();
|
||||
183
desktop/scripts/build-utils.ts
Normal file
183
desktop/scripts/build-utils.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const Metro = require('../static/node_modules/metro');
|
||||
import compilePlugins from '../static/compilePlugins';
|
||||
import util from 'util';
|
||||
import tmp from 'tmp';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import {spawn} from 'promisify-child-process';
|
||||
import recursiveReaddir from 'recursive-readdir';
|
||||
|
||||
async function mostRecentlyChanged(
|
||||
dir: string,
|
||||
ignores: string[],
|
||||
): Promise<Date> {
|
||||
const files = await util.promisify<string, string[], string[]>(
|
||||
recursiveReaddir,
|
||||
)(dir, ignores);
|
||||
return files
|
||||
.map(f => fs.lstatSync(f).ctime)
|
||||
.reduce((a, b) => (a > b ? a : b), new Date(0));
|
||||
}
|
||||
|
||||
export function die(err: Error) {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export function compileDefaultPlugins(
|
||||
defaultPluginDir: string,
|
||||
skipAll: boolean = false,
|
||||
) {
|
||||
return compilePlugins(
|
||||
null,
|
||||
skipAll
|
||||
? []
|
||||
: [
|
||||
path.join(__dirname, '..', 'src', 'plugins'),
|
||||
path.join(__dirname, '..', 'src', 'fb', 'plugins'),
|
||||
],
|
||||
defaultPluginDir,
|
||||
{force: true, failSilently: false, recompileOnChanges: false},
|
||||
)
|
||||
.then(defaultPlugins =>
|
||||
fs.writeFileSync(
|
||||
path.join(defaultPluginDir, 'index.json'),
|
||||
JSON.stringify(
|
||||
defaultPlugins.map(({entry, rootDir, out, ...plugin}) => ({
|
||||
...plugin,
|
||||
out: path.parse(out).base,
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.catch(die);
|
||||
}
|
||||
|
||||
export function compile(buildFolder: string, entry: string) {
|
||||
console.log(`⚙️ Compiling renderer bundle...`);
|
||||
const projectRoots = path.join(__dirname, '..');
|
||||
return Metro.runBuild(
|
||||
{
|
||||
reporter: {update: () => {}},
|
||||
projectRoot: projectRoots,
|
||||
watchFolders: [projectRoots],
|
||||
serializer: {},
|
||||
transformer: {
|
||||
babelTransformerPath: path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'static',
|
||||
'transforms',
|
||||
'index.js',
|
||||
),
|
||||
},
|
||||
resolver: {
|
||||
blacklistRE: /(\/|\\)(sonar|flipper|flipper-public)(\/|\\)(desktop)(\/|\\)(dist|doctor)(\/|\\)|(\.native\.js$)/,
|
||||
},
|
||||
},
|
||||
{
|
||||
dev: false,
|
||||
minify: false,
|
||||
resetCache: true,
|
||||
sourceMap: true,
|
||||
entry,
|
||||
out: path.join(buildFolder, 'bundle.js'),
|
||||
},
|
||||
)
|
||||
.then(() => console.log('✅ Compiled renderer bundle.'))
|
||||
.catch(die);
|
||||
}
|
||||
|
||||
export async function compileMain({dev}: {dev: boolean}) {
|
||||
const staticDir = path.resolve(__dirname, '..', 'static');
|
||||
const out = path.join(staticDir, 'main.bundle.js');
|
||||
// check if main needs to be compiled
|
||||
if (await fs.pathExists(out)) {
|
||||
const staticDirCtime = await mostRecentlyChanged(staticDir, ['*.bundle.*']);
|
||||
const bundleCtime = (await fs.lstat(out)).ctime;
|
||||
if (staticDirCtime < bundleCtime) {
|
||||
console.log(`🥫 Using cached version of main bundle...`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log(`⚙️ Compiling main bundle...`);
|
||||
try {
|
||||
const config = Object.assign({}, await Metro.loadConfig(), {
|
||||
reporter: {update: () => {}},
|
||||
projectRoot: staticDir,
|
||||
watchFolders: [staticDir],
|
||||
transformer: {
|
||||
babelTransformerPath: path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'static',
|
||||
'transforms',
|
||||
'index.js',
|
||||
),
|
||||
},
|
||||
resolver: {
|
||||
sourceExts: ['tsx', 'ts', 'js'],
|
||||
blacklistRE: /(\/|\\)(sonar|flipper|flipper-public)(\/|\\)(desktop)(\/|\\)(dist|doctor)(\/|\\)|(\.native\.js$)/,
|
||||
},
|
||||
});
|
||||
await Metro.runBuild(config, {
|
||||
platform: 'web',
|
||||
entry: path.join(staticDir, 'main.ts'),
|
||||
out,
|
||||
dev,
|
||||
minify: false,
|
||||
sourceMap: true,
|
||||
resetCache: true,
|
||||
});
|
||||
console.log('✅ Compiled main bundle.');
|
||||
} catch (err) {
|
||||
die(err);
|
||||
}
|
||||
}
|
||||
export function buildFolder(): Promise<string> {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Creating build directory');
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
tmp.dir({prefix: 'flipper-build-'}, (err, buildFolder) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buildFolder);
|
||||
}
|
||||
});
|
||||
}).catch(e => {
|
||||
die(e);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
export function getVersionNumber() {
|
||||
let {version} = require('../package.json');
|
||||
const buildNumber = process.argv.join(' ').match(/--version=(\d+)/);
|
||||
if (buildNumber && buildNumber.length > 0) {
|
||||
version = [...version.split('.').slice(0, 2), buildNumber[1]].join('.');
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
// Asynchronously determine current mercurial revision as string or `null` in case of any error.
|
||||
export function genMercurialRevision(): Promise<string | null> {
|
||||
return spawn('hg', ['log', '-r', '.', '-T', '{node}'], {encoding: 'utf8'})
|
||||
.then(
|
||||
res =>
|
||||
(res &&
|
||||
(typeof res.stdout === 'string'
|
||||
? res.stdout
|
||||
: res.stdout?.toString())) ||
|
||||
null,
|
||||
)
|
||||
.catch(() => null);
|
||||
}
|
||||
76
desktop/scripts/generate-changelog.js
Executable file
76
desktop/scripts/generate-changelog.js
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @noformat
|
||||
*/
|
||||
|
||||
/**
|
||||
* WARNING: this file should be able to run on node v6.16.0, which is used at SandCastle.
|
||||
* Please run `nvm use 6.16.0` before testing changes in this file!
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
|
||||
const desktopRoot = path.resolve(__dirname, '..');
|
||||
const root = path.resolve(desktopRoot, '..');
|
||||
|
||||
const version = JSON.parse(fs.readFileSync(path.join(desktopRoot, 'package.json'), 'utf8')).version;
|
||||
|
||||
const now = new Date();
|
||||
const date = `${now.getDate()}/${now.getMonth() + 1}/${now.getFullYear()}`;
|
||||
const newlineMarker = '__NEWLINE_MARKER__';
|
||||
const fChangelog = path.resolve(root, 'CHANGELOG.md');
|
||||
|
||||
const lastCommit = cp
|
||||
.execSync(`hg log --limit 1 --template '{node}'`, {cwd: root})
|
||||
.toString();
|
||||
const firstCommit = cp
|
||||
.execSync(
|
||||
`hg log --limit 1 --template '{node}' --keyword 'Flipper Release: v'`,
|
||||
{cwd: root}
|
||||
)
|
||||
.toString();
|
||||
|
||||
console.log(
|
||||
`Generating changelog for version ${version} based on ${firstCommit}..${lastCommit}`
|
||||
);
|
||||
|
||||
// # get all commit summaries since last release | find all changelog entries, but make sure there is only one line per commit by temporarily replacing newlines
|
||||
const hgLogCommand = `hg log -r "${firstCommit}::${lastCommit} and file('../*')" --template "{phabdiff} - {sub('\n','${newlineMarker}', desc)}\n"`;
|
||||
const hgLog = cp.execSync(hgLogCommand, {cwd: __dirname}).toString();
|
||||
|
||||
const diffRe = /^D\d+/;
|
||||
const changeLogLineRe = /(^changelog:\s*?)(.*?)$/i;
|
||||
|
||||
let contents = `# ${version} (${date})\n\n`;
|
||||
let changes = 0;
|
||||
|
||||
hgLog
|
||||
.split('\n')
|
||||
.filter(line => diffRe.test(line))
|
||||
.forEach(line => {
|
||||
// Grab the diff nr from every line in the output
|
||||
const diff = line.trim().match(diffRe)[0];
|
||||
// unfold the lines generated by hg log again
|
||||
line.split(newlineMarker).forEach(diffline => {
|
||||
// if a line starts with changelog:, grab the rest of the text and add it to the changelog
|
||||
const match = diffline.match(changeLogLineRe);
|
||||
if (match) {
|
||||
changes++;
|
||||
contents += ` * ${diff} - ${match[2]}\n`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!changes) {
|
||||
console.log('No diffs with changelog items found in this release');
|
||||
} else {
|
||||
contents += '\n\n' + fs.readFileSync(fChangelog, 'utf8');
|
||||
fs.writeFileSync(fChangelog, contents);
|
||||
}
|
||||
65
desktop/scripts/metro-transform.js
Normal file
65
desktop/scripts/metro-transform.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const generate = require('babel-generator').default;
|
||||
const babylon = require('babylon');
|
||||
const babel = require('babel-core');
|
||||
const metro = require('metro');
|
||||
|
||||
exports.transform = function({
|
||||
filename,
|
||||
options,
|
||||
src,
|
||||
plugins: defaultPlugins,
|
||||
}) {
|
||||
const presets = [];
|
||||
|
||||
let ast = babylon.parse(src, {
|
||||
filename,
|
||||
plugins: ['jsx', 'flow', 'classProperties', 'objectRestSpread'],
|
||||
sourceType: filename.includes('node_modules') ? 'script' : 'module',
|
||||
});
|
||||
|
||||
// run babel
|
||||
const plugins = [
|
||||
...defaultPlugins,
|
||||
require('./babel-plugins/electron-requires.js'),
|
||||
require('./babel-plugins/dynamic-requires.js'),
|
||||
];
|
||||
if (!filename.includes('node_modules')) {
|
||||
plugins.unshift(require('babel-plugin-transform-es2015-modules-commonjs'));
|
||||
}
|
||||
ast = babel.transformFromAst(ast, src, {
|
||||
babelrc: !filename.includes('node_modules'),
|
||||
code: false,
|
||||
comments: false,
|
||||
compact: false,
|
||||
filename,
|
||||
plugins,
|
||||
presets,
|
||||
sourceMaps: true,
|
||||
}).ast;
|
||||
|
||||
const result = generate(
|
||||
ast,
|
||||
{
|
||||
filename,
|
||||
sourceFileName: filename,
|
||||
sourceMaps: true,
|
||||
},
|
||||
src,
|
||||
);
|
||||
|
||||
return {
|
||||
ast,
|
||||
code: result.code,
|
||||
filename,
|
||||
map: result.rawMappings.map(metro.sourceMaps.compactMapping),
|
||||
};
|
||||
};
|
||||
31
desktop/scripts/prepare-watchman-config.js
Normal file
31
desktop/scripts/prepare-watchman-config.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const {exists: existsImport, copyFile} = require('fs');
|
||||
const exists = util.promisify(existsImport);
|
||||
|
||||
const desktopRootDir = path.resolve(__dirname, '..');
|
||||
const rootDir = path.resolve(desktopRootDir, '..');
|
||||
const hasGit = exists(path.join(rootDir, '.git'));
|
||||
|
||||
async function prepareWatchmanConfig(dir) {
|
||||
const hasWatchmanConfig = exists(path.join(dir, '.watchmanconfig'));
|
||||
if ((await hasGit) && !(await hasWatchmanConfig)) {
|
||||
console.log(`Creating .watchmanconfig in ${dir}`);
|
||||
await util.promisify(copyFile)(
|
||||
path.join(dir, '_watchmanconfig'),
|
||||
path.join(dir, '.watchmanconfig'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
prepareWatchmanConfig(rootDir);
|
||||
prepareWatchmanConfig(path.join(desktopRootDir, 'static'));
|
||||
265
desktop/scripts/start-dev-server.ts
Normal file
265
desktop/scripts/start-dev-server.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const electronBinary: string = require('electron') as any;
|
||||
import codeFrame from 'babel-code-frame';
|
||||
import socketIo from 'socket.io';
|
||||
import express, {Express} from 'express';
|
||||
import detect from 'detect-port';
|
||||
import child from 'child_process';
|
||||
import AnsiToHtmlConverter from 'ansi-to-html';
|
||||
import chalk from 'chalk';
|
||||
import http from 'http';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {compileMain} from './build-utils';
|
||||
import Watchman from '../static/watchman';
|
||||
const Metro = require('../static/node_modules/metro');
|
||||
const MetroResolver = require('../static/node_modules/metro-resolver');
|
||||
|
||||
const ansiToHtmlConverter = new AnsiToHtmlConverter();
|
||||
|
||||
const DEFAULT_PORT = (process.env.PORT || 3000) as number;
|
||||
const STATIC_DIR = path.join(__dirname, '..', 'static');
|
||||
|
||||
let shutdownElectron: (() => void) | undefined = undefined;
|
||||
|
||||
function launchElectron({
|
||||
devServerURL,
|
||||
bundleURL,
|
||||
electronURL,
|
||||
}: {
|
||||
devServerURL: string;
|
||||
bundleURL: string;
|
||||
electronURL: string;
|
||||
}) {
|
||||
const args = [
|
||||
path.join(STATIC_DIR, 'index.js'),
|
||||
'--remote-debugging-port=9222',
|
||||
...process.argv,
|
||||
];
|
||||
|
||||
const proc = child.spawn(electronBinary, args, {
|
||||
cwd: STATIC_DIR,
|
||||
env: {
|
||||
...process.env,
|
||||
SONAR_ROOT: process.cwd(),
|
||||
BUNDLE_URL: bundleURL,
|
||||
ELECTRON_URL: electronURL,
|
||||
DEV_SERVER_URL: devServerURL,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
const electronCloseListener = () => {
|
||||
process.exit();
|
||||
};
|
||||
|
||||
const processExitListener = () => {
|
||||
proc.kill();
|
||||
};
|
||||
|
||||
proc.on('close', electronCloseListener);
|
||||
process.on('exit', processExitListener);
|
||||
|
||||
return () => {
|
||||
proc.off('close', electronCloseListener);
|
||||
process.off('exit', processExitListener);
|
||||
proc.kill();
|
||||
};
|
||||
}
|
||||
|
||||
function startMetroServer(app: Express) {
|
||||
const projectRoot = path.join(__dirname, '..');
|
||||
return Metro.runMetro({
|
||||
projectRoot,
|
||||
watchFolders: [projectRoot],
|
||||
transformer: {
|
||||
babelTransformerPath: path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'static',
|
||||
'transforms',
|
||||
'index.js',
|
||||
),
|
||||
},
|
||||
resolver: {
|
||||
blacklistRE: /(\/|\\)(sonar|flipper|flipper-public)(\/|\\)(desktop)(\/|\\)(dist|doctor)(\/|\\)|(\.native\.js$)/,
|
||||
resolveRequest: (context: any, moduleName: string, platform: string) => {
|
||||
if (moduleName.startsWith('./localhost:3000')) {
|
||||
moduleName = moduleName.replace('./localhost:3000', '.');
|
||||
}
|
||||
return MetroResolver.resolve(
|
||||
{...context, resolveRequest: null},
|
||||
moduleName,
|
||||
platform,
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: true,
|
||||
}).then((metroBundlerServer: any) => {
|
||||
app.use(metroBundlerServer.processRequest.bind(metroBundlerServer));
|
||||
});
|
||||
}
|
||||
|
||||
function startAssetServer(
|
||||
port: number,
|
||||
): Promise<{app: Express; server: http.Server}> {
|
||||
const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (knownErrors[req.url] != null) {
|
||||
delete knownErrors[req.url];
|
||||
outputScreen();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
||||
res.header('Expires', '-1');
|
||||
res.header('Pragma', 'no-cache');
|
||||
next();
|
||||
});
|
||||
|
||||
app.post('/_restartElectron', (req, res) => {
|
||||
if (shutdownElectron) {
|
||||
shutdownElectron();
|
||||
}
|
||||
shutdownElectron = launchElectron({
|
||||
devServerURL: `http://localhost:${port}`,
|
||||
bundleURL: `http://localhost:${port}/src/init.bundle?dev=true&platform=web&minify=false&excludeSource=false`,
|
||||
electronURL: `http://localhost:${port}/index.dev.html`,
|
||||
});
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
fs.readFile(path.join(STATIC_DIR, 'index.dev.html'), (err, content) => {
|
||||
res.end(content);
|
||||
});
|
||||
});
|
||||
|
||||
app.use(express.static(STATIC_DIR));
|
||||
|
||||
app.use(function(err: any, req: any, res: any, _next: any) {
|
||||
knownErrors[req.url] = err;
|
||||
outputScreen();
|
||||
res.status(500).send('Something broke, check the console!');
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
return new Promise(resolve => {
|
||||
server.listen(port, 'localhost', () => resolve({app, server}));
|
||||
});
|
||||
}
|
||||
|
||||
async function addWebsocket(server: http.Server) {
|
||||
const io = socketIo(server);
|
||||
|
||||
// notify connected clients that there's errors in the console
|
||||
io.on('connection', client => {
|
||||
if (hasErrors()) {
|
||||
client.emit('hasErrors', ansiToHtmlConverter.toHtml(buildErrorScreen()));
|
||||
}
|
||||
});
|
||||
|
||||
// refresh the app on changes to the src folder
|
||||
// this can be removed once metroServer notifies us about file changes
|
||||
try {
|
||||
const watchman = new Watchman(path.resolve(__dirname, '..', 'src'));
|
||||
await watchman.initialize();
|
||||
await watchman.startWatchFiles(
|
||||
'',
|
||||
() => {
|
||||
io.emit('refresh');
|
||||
},
|
||||
{
|
||||
excludes: [
|
||||
'**/__tests__/**/*',
|
||||
'**/node_modules/**/*',
|
||||
'**/.*',
|
||||
'plugins/**/*', // plugin changes are tracked separately, so exlcuding them here to avoid double reloading.
|
||||
],
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Failed to start watching for changes using Watchman, continue without hot reloading',
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
return io;
|
||||
}
|
||||
|
||||
const knownErrors: {[key: string]: any} = {};
|
||||
|
||||
function hasErrors() {
|
||||
return Object.keys(knownErrors).length > 0;
|
||||
}
|
||||
|
||||
function buildErrorScreen() {
|
||||
const lines = [
|
||||
chalk.red(`✖ Found ${Object.keys(knownErrors).length} errors`),
|
||||
'',
|
||||
];
|
||||
|
||||
for (const url in knownErrors) {
|
||||
const err = knownErrors[url];
|
||||
|
||||
if (err.filename != null && err.lineNumber != null && err.column != null) {
|
||||
lines.push(chalk.inverse(err.filename));
|
||||
lines.push();
|
||||
lines.push(err.message);
|
||||
lines.push(
|
||||
codeFrame(
|
||||
fs.readFileSync(err.filename, 'utf8'),
|
||||
err.lineNumber,
|
||||
err.column,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
lines.push(err.stack);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function outputScreen(socket?: socketIo.Server) {
|
||||
// output screen
|
||||
if (hasErrors()) {
|
||||
const errorScreen = buildErrorScreen();
|
||||
console.error(errorScreen);
|
||||
|
||||
// notify live clients of errors
|
||||
socket?.emit('hasErrors', ansiToHtmlConverter.toHtml(errorScreen));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(chalk.green('✔ No known errors'));
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const port = await detect(DEFAULT_PORT);
|
||||
const {app, server} = await startAssetServer(port);
|
||||
const socket = await addWebsocket(server);
|
||||
await startMetroServer(app);
|
||||
outputScreen(socket);
|
||||
await compileMain({dev: true});
|
||||
shutdownElectron = launchElectron({
|
||||
devServerURL: `http://localhost:${port}`,
|
||||
bundleURL: `http://localhost:${port}/src/init.bundle`,
|
||||
electronURL: `http://localhost:${port}/index.dev.html`,
|
||||
});
|
||||
})();
|
||||
65
desktop/scripts/yarn-install.ts
Normal file
65
desktop/scripts/yarn-install.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 util from 'util';
|
||||
import globImport from 'glob';
|
||||
import {exec as execImport} from 'child_process';
|
||||
const glob = util.promisify(globImport);
|
||||
const exec = util.promisify(execImport);
|
||||
const PACKAGES = [
|
||||
'headless-tests',
|
||||
'static',
|
||||
'src/plugins/*',
|
||||
'src/fb/plugins/*',
|
||||
'src/fb/plugins/layout/*',
|
||||
];
|
||||
const WINDOWS = /^win/.test(process.platform);
|
||||
const YARN_PATH =
|
||||
process.argv.length > 2
|
||||
? path.join(__dirname, process.argv[2])
|
||||
: 'yarn' + (WINDOWS ? '.cmd' : '');
|
||||
|
||||
Promise.all(
|
||||
PACKAGES.map(pattern =>
|
||||
glob(path.join(__dirname, '..', pattern, 'package.json')),
|
||||
),
|
||||
)
|
||||
.then(async packages => {
|
||||
const flattenPackages = packages.reduce((acc, cv) => acc.concat(cv), []);
|
||||
console.log(
|
||||
`Installing dependencies for ${flattenPackages.length} plugins`,
|
||||
);
|
||||
for (const pkg of flattenPackages) {
|
||||
const {stderr} = await exec(
|
||||
// This script is itself executed by yarn (as postinstall script),
|
||||
// therefore another yarn instance is running, while we are trying to
|
||||
// install the plugin dependencies. We are setting a different port
|
||||
// for the mutex of this yarn instance to make sure, it is not blocked
|
||||
// by the yarn instance which is executing this script. Otherwise this
|
||||
// will cause a deadlock.
|
||||
[YARN_PATH, '--mutex', 'network:30330'].join(' '),
|
||||
{
|
||||
cwd: pkg.replace('/package.json', ''),
|
||||
},
|
||||
);
|
||||
if (stderr) {
|
||||
console.warn(stderr);
|
||||
} else {
|
||||
console.log(`Installed dependencies for ${pkg}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
.then(() => console.log('📦 Installed all plugin dependencies!'))
|
||||
.catch(err => {
|
||||
console.error('❌ Installing plugin dependencies failed.');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
22
desktop/src/.eslintrc.js
Normal file
22
desktop/src/.eslintrc.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// Eslint rules cascade. Rules defined here are merged with the rules
|
||||
// defined in the parent directory.
|
||||
|
||||
// Somewhat ironically, this particular file is not actually transformed by Babel
|
||||
// and can't have ES6 imports/exports.
|
||||
// eslint-disable-line import/no-commonjs
|
||||
module.exports = {
|
||||
plugins: ['import'],
|
||||
rules: {
|
||||
// Import rules from https://www.npmjs.com/package/eslint-plugin-import
|
||||
'import/no-commonjs': 1,
|
||||
},
|
||||
};
|
||||
173
desktop/src/App.tsx
Normal file
173
desktop/src/App.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
import {FlexColumn, FlexRow} from 'flipper';
|
||||
import {connect} from 'react-redux';
|
||||
import TitleBar from './chrome/TitleBar';
|
||||
import MainSidebar2 from './chrome/mainsidebar/MainSidebar2';
|
||||
import BugReporterDialog from './chrome/BugReporterDialog';
|
||||
import ErrorBar from './chrome/ErrorBar';
|
||||
import DoctorBar from './chrome/DoctorBar';
|
||||
import ShareSheetExportUrl from './chrome/ShareSheetExportUrl';
|
||||
import SignInSheet from './chrome/SignInSheet';
|
||||
import ExportDataPluginSheet from './chrome/ExportDataPluginSheet';
|
||||
import ShareSheetExportFile from './chrome/ShareSheetExportFile';
|
||||
import JSEmulatorLauncherSheet from './chrome/JSEmulatorLauncherSheet';
|
||||
import PluginContainer from './PluginContainer';
|
||||
import Sheet from './chrome/Sheet';
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
import {
|
||||
ActiveSheet,
|
||||
ShareType,
|
||||
ACTIVE_SHEET_BUG_REPORTER,
|
||||
ACTIVE_SHEET_PLUGINS,
|
||||
ACTIVE_SHEET_SHARE_DATA,
|
||||
ACTIVE_SHEET_SIGN_IN,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
ACTIVE_SHEET_DOCTOR,
|
||||
ACTIVE_SHEET_SHARE_DATA_IN_FILE,
|
||||
ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT,
|
||||
ACTIVE_SHEET_PLUGIN_SHEET,
|
||||
ACTIVE_SHEET_JS_EMULATOR_LAUNCHER,
|
||||
} from './reducers/application';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import BugReporter from './fb-stubs/BugReporter';
|
||||
import {State as Store} from './reducers/index';
|
||||
import {StaticView, FlipperError} from './reducers/connections';
|
||||
import PluginManager from './chrome/plugin-manager/PluginManager';
|
||||
import StatusBar from './chrome/StatusBar';
|
||||
import SettingsSheet from './chrome/SettingsSheet';
|
||||
import DoctorSheet from './chrome/DoctorSheet';
|
||||
|
||||
const version = remote.app.getVersion();
|
||||
|
||||
type OwnProps = {
|
||||
logger: Logger;
|
||||
bugReporter: BugReporter;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
leftSidebarVisible: boolean;
|
||||
errors: FlipperError[];
|
||||
activeSheet: ActiveSheet;
|
||||
share: ShareType | null;
|
||||
staticView: StaticView;
|
||||
};
|
||||
|
||||
type Props = StateFromProps & OwnProps;
|
||||
|
||||
export class App extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
// track time since launch
|
||||
const [s, ns] = process.hrtime();
|
||||
const launchEndTime = s * 1e3 + ns / 1e6;
|
||||
ipcRenderer.on('getLaunchTime', (_: any, launchStartTime: number) => {
|
||||
this.props.logger.track(
|
||||
'performance',
|
||||
'launchTime',
|
||||
launchEndTime - launchStartTime,
|
||||
);
|
||||
});
|
||||
ipcRenderer.send('getLaunchTime');
|
||||
ipcRenderer.send('componentDidMount');
|
||||
}
|
||||
|
||||
getSheet = (onHide: () => any) => {
|
||||
const {activeSheet} = this.props;
|
||||
switch (activeSheet) {
|
||||
case ACTIVE_SHEET_BUG_REPORTER:
|
||||
return (
|
||||
<BugReporterDialog
|
||||
bugReporter={this.props.bugReporter}
|
||||
onHide={onHide}
|
||||
/>
|
||||
);
|
||||
case ACTIVE_SHEET_PLUGINS:
|
||||
return <PluginManager onHide={onHide} />;
|
||||
case ACTIVE_SHEET_SIGN_IN:
|
||||
return <SignInSheet onHide={onHide} />;
|
||||
case ACTIVE_SHEET_SETTINGS:
|
||||
return <SettingsSheet platform={process.platform} onHide={onHide} />;
|
||||
case ACTIVE_SHEET_DOCTOR:
|
||||
return <DoctorSheet onHide={onHide} />;
|
||||
case ACTIVE_SHEET_SELECT_PLUGINS_TO_EXPORT:
|
||||
return <ExportDataPluginSheet onHide={onHide} />;
|
||||
case ACTIVE_SHEET_SHARE_DATA:
|
||||
return (
|
||||
<ShareSheetExportUrl
|
||||
onHide={onHide}
|
||||
logger={this.props.logger}
|
||||
closeOnFinish={
|
||||
this.props.share != null && this.props.share.closeOnFinish
|
||||
}
|
||||
/>
|
||||
);
|
||||
case ACTIVE_SHEET_SHARE_DATA_IN_FILE:
|
||||
return this.props.share && this.props.share.type === 'file' ? (
|
||||
<ShareSheetExportFile
|
||||
onHide={onHide}
|
||||
file={this.props.share.file}
|
||||
logger={this.props.logger}
|
||||
/>
|
||||
) : (
|
||||
(() => {
|
||||
console.error('No file provided when calling share sheet.');
|
||||
return null;
|
||||
})()
|
||||
);
|
||||
case ACTIVE_SHEET_PLUGIN_SHEET:
|
||||
// Currently unused.
|
||||
return null;
|
||||
case ACTIVE_SHEET_JS_EMULATOR_LAUNCHER:
|
||||
return <JSEmulatorLauncherSheet onHide={onHide} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlexColumn grow={true}>
|
||||
<TitleBar version={version} />
|
||||
<DoctorBar />
|
||||
<ErrorBar />
|
||||
<Sheet>{this.getSheet}</Sheet>
|
||||
<FlexRow grow={true}>
|
||||
{this.props.leftSidebarVisible && <MainSidebar2 />}
|
||||
{this.props.staticView != null ? (
|
||||
React.createElement(this.props.staticView, {
|
||||
logger: this.props.logger,
|
||||
})
|
||||
) : (
|
||||
<PluginContainer logger={this.props.logger} />
|
||||
)}
|
||||
<div
|
||||
id="flipper-out-of-contents-container"
|
||||
style={{width: '100%', display: 'none'}}
|
||||
/>
|
||||
</FlexRow>
|
||||
<StatusBar />
|
||||
</FlexColumn>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, {}, OwnProps, Store>(
|
||||
({
|
||||
application: {leftSidebarVisible, activeSheet, share},
|
||||
connections: {errors, staticView},
|
||||
}) => ({
|
||||
leftSidebarVisible,
|
||||
activeSheet,
|
||||
share: share,
|
||||
errors,
|
||||
staticView,
|
||||
}),
|
||||
)(App);
|
||||
599
desktop/src/Client.tsx
Normal file
599
desktop/src/Client.tsx
Normal file
@@ -0,0 +1,599 @@
|
||||
/**
|
||||
* 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 {FlipperPlugin, FlipperDevicePlugin} from './plugin';
|
||||
import BaseDevice, {OS} from './devices/BaseDevice';
|
||||
import {App} from './App.js';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import {Store} from './reducers/index';
|
||||
import {setPluginState} from './reducers/pluginStates';
|
||||
import {Payload, ConnectionStatus} from 'rsocket-types';
|
||||
import {Flowable, Single} from 'rsocket-flowable';
|
||||
import {performance} from 'perf_hooks';
|
||||
import {reportPlatformFailures, reportPluginFailures} from './utils/metrics';
|
||||
import {notNull} from './utils/typeUtils';
|
||||
import {default as isProduction} from './utils/isProduction';
|
||||
import {registerPlugins} from './reducers/plugins';
|
||||
import createTableNativePlugin from './plugins/TableNativePlugin';
|
||||
import EventEmitter from 'events';
|
||||
import invariant from 'invariant';
|
||||
import {flipperRecorderAddEvent} from './utils/pluginStateRecorder';
|
||||
import {getPluginKey} from './utils/pluginUtils';
|
||||
import {
|
||||
processMessageImmediately,
|
||||
processMessageLater,
|
||||
} from './utils/messageQueue';
|
||||
import GK from './fb-stubs/GK';
|
||||
|
||||
type Plugins = Array<string>;
|
||||
|
||||
export type ClientQuery = {
|
||||
app: string;
|
||||
os: OS;
|
||||
device: string;
|
||||
device_id: string;
|
||||
sdk_version?: number;
|
||||
};
|
||||
|
||||
export type ClientExport = {
|
||||
id: string;
|
||||
query: ClientQuery;
|
||||
};
|
||||
|
||||
type ErrorType = {message: string; stacktrace: string; name: string};
|
||||
type Params = {
|
||||
api: string;
|
||||
method: string;
|
||||
params?: Object;
|
||||
};
|
||||
type RequestMetadata = {method: string; id: number; params: Params | undefined};
|
||||
|
||||
const handleError = (store: Store, device: BaseDevice, error: ErrorType) => {
|
||||
if (isProduction()) {
|
||||
return;
|
||||
}
|
||||
const crashReporterPlugin = store
|
||||
.getState()
|
||||
.plugins.devicePlugins.get('CrashReporter');
|
||||
if (!crashReporterPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pluginKey = getPluginKey(null, device, 'CrashReporter');
|
||||
|
||||
const persistedState = {
|
||||
...crashReporterPlugin.defaultPersistedState,
|
||||
...store.getState().pluginStates[pluginKey],
|
||||
};
|
||||
const isCrashReport: boolean = Boolean(error.name || error.message);
|
||||
const payload = isCrashReport
|
||||
? {
|
||||
name: error.name,
|
||||
reason: error.message,
|
||||
callstack: error.stacktrace,
|
||||
}
|
||||
: {
|
||||
name: 'Plugin Error',
|
||||
reason: JSON.stringify(error),
|
||||
};
|
||||
|
||||
const newPluginState =
|
||||
crashReporterPlugin.persistedStateReducer == null
|
||||
? persistedState
|
||||
: crashReporterPlugin.persistedStateReducer(
|
||||
persistedState,
|
||||
'flipper-crash-report',
|
||||
payload,
|
||||
);
|
||||
if (persistedState !== newPluginState) {
|
||||
store.dispatch(
|
||||
setPluginState({
|
||||
pluginKey,
|
||||
state: newPluginState,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export interface FlipperClientConnection<D, M> {
|
||||
connectionStatus(): Flowable<ConnectionStatus>;
|
||||
close(): void;
|
||||
fireAndForget(payload: Payload<D, M>): void;
|
||||
requestResponse(payload: Payload<D, M>): Single<Payload<D, M>>;
|
||||
}
|
||||
|
||||
export default class Client extends EventEmitter {
|
||||
app: App | undefined;
|
||||
connected: boolean;
|
||||
id: string;
|
||||
query: ClientQuery;
|
||||
sdkVersion: number;
|
||||
messageIdCounter: number;
|
||||
plugins: Plugins;
|
||||
connection: FlipperClientConnection<any, any> | null | undefined;
|
||||
store: Store;
|
||||
activePlugins: Set<string>;
|
||||
device: Promise<BaseDevice>;
|
||||
_deviceResolve: (device: BaseDevice) => void = _ => {};
|
||||
_deviceSet: false | BaseDevice = false;
|
||||
logger: Logger;
|
||||
lastSeenDeviceList: Array<BaseDevice>;
|
||||
broadcastCallbacks: Map<string, Map<string, Set<Function>>>;
|
||||
rIC: any;
|
||||
|
||||
requestCallbacks: Map<
|
||||
number,
|
||||
{
|
||||
resolve: (data: any) => void;
|
||||
reject: (err: Error) => void;
|
||||
metadata: RequestMetadata;
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
}
|
||||
>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
query: ClientQuery,
|
||||
conn: FlipperClientConnection<any, any> | null | undefined,
|
||||
logger: Logger,
|
||||
store: Store,
|
||||
plugins?: Plugins | null | undefined,
|
||||
device?: BaseDevice,
|
||||
) {
|
||||
super();
|
||||
this.connected = true;
|
||||
this.plugins = plugins ? plugins : [];
|
||||
this.connection = conn;
|
||||
this.id = id;
|
||||
this.query = query;
|
||||
this.sdkVersion = query.sdk_version || 0;
|
||||
this.messageIdCounter = 0;
|
||||
this.logger = logger;
|
||||
this.store = store;
|
||||
this.broadcastCallbacks = new Map();
|
||||
this.requestCallbacks = new Map();
|
||||
this.activePlugins = new Set();
|
||||
this.lastSeenDeviceList = [];
|
||||
|
||||
this.device = device
|
||||
? Promise.resolve(device)
|
||||
: new Promise((resolve, _reject) => {
|
||||
this._deviceResolve = resolve;
|
||||
});
|
||||
if (device) {
|
||||
this._deviceSet = device;
|
||||
}
|
||||
|
||||
const client = this;
|
||||
// node.js doesn't support requestIdleCallback
|
||||
this.rIC =
|
||||
typeof window === 'undefined' || !window.requestIdleCallback
|
||||
? (cb: Function, _: any) => {
|
||||
cb();
|
||||
}
|
||||
: window.requestIdleCallback.bind(window);
|
||||
|
||||
if (conn) {
|
||||
conn.connectionStatus().subscribe({
|
||||
onNext(payload) {
|
||||
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
|
||||
client.connected = false;
|
||||
}
|
||||
},
|
||||
onSubscribe(subscription) {
|
||||
subscription.request(Number.MAX_SAFE_INTEGER);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* All clients should have a corresponding Device in the store.
|
||||
However, clients can connect before a device is registered, so wait a
|
||||
while for the device to be registered if it isn't already. */
|
||||
setMatchingDevice(): void {
|
||||
if (this._deviceSet) {
|
||||
return;
|
||||
}
|
||||
reportPlatformFailures(
|
||||
new Promise<BaseDevice>((resolve, reject) => {
|
||||
let unsubscribe: () => void = () => {};
|
||||
|
||||
const device = this.store
|
||||
.getState()
|
||||
.connections.devices.find(
|
||||
device => device.serial === this.query.device_id,
|
||||
);
|
||||
if (device) {
|
||||
resolve(device);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
unsubscribe();
|
||||
const error = `Timed out waiting for device for client ${this.id}`;
|
||||
console.error(error);
|
||||
reject(error);
|
||||
}, 5000);
|
||||
unsubscribe = this.store.subscribe(() => {
|
||||
const newDeviceList = this.store.getState().connections.devices;
|
||||
if (newDeviceList === this.lastSeenDeviceList) {
|
||||
return;
|
||||
}
|
||||
this.lastSeenDeviceList = this.store.getState().connections.devices;
|
||||
const matchingDevice = newDeviceList.find(
|
||||
device => device.serial === this.query.device_id,
|
||||
);
|
||||
if (matchingDevice) {
|
||||
clearTimeout(timeout);
|
||||
resolve(matchingDevice);
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
}),
|
||||
'client-setMatchingDevice',
|
||||
).then(device => {
|
||||
this._deviceSet = device;
|
||||
this._deviceResolve(device);
|
||||
});
|
||||
}
|
||||
|
||||
supportsPlugin(Plugin: typeof FlipperPlugin): boolean {
|
||||
return this.plugins.includes(Plugin.id);
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.setMatchingDevice();
|
||||
await this.getPlugins();
|
||||
}
|
||||
|
||||
// get the supported plugins
|
||||
async getPlugins(): Promise<Plugins> {
|
||||
const plugins = await this.rawCall<{plugins: Plugins}>(
|
||||
'getPlugins',
|
||||
false,
|
||||
).then(data => data.plugins);
|
||||
this.plugins = plugins;
|
||||
const nativeplugins = plugins
|
||||
.map(plugin => /_nativeplugin_([^_]+)_([^_]+)/.exec(plugin))
|
||||
.filter(notNull)
|
||||
.map(([id, type, title]) => {
|
||||
// TODO put this in another component, and make the "types" registerable
|
||||
switch (type) {
|
||||
case 'Table':
|
||||
return createTableNativePlugin(id, title);
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
this.store.dispatch(registerPlugins(nativeplugins as any));
|
||||
return plugins;
|
||||
}
|
||||
|
||||
// get the plugins, and update the UI
|
||||
async refreshPlugins() {
|
||||
await this.getPlugins();
|
||||
this.emit('plugins-change');
|
||||
}
|
||||
|
||||
async deviceSerial(): Promise<string> {
|
||||
try {
|
||||
const device = await this.device;
|
||||
if (!device) {
|
||||
console.error('Using "" for deviceId device is not ready');
|
||||
return '';
|
||||
}
|
||||
return device.serial;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Using "" for deviceId because client has no matching device',
|
||||
);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(msg: string) {
|
||||
if (typeof msg !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
let rawData;
|
||||
try {
|
||||
rawData = JSON.parse(msg);
|
||||
} catch (err) {
|
||||
console.error(`Invalid JSON: ${msg}`, 'clientMessage');
|
||||
return;
|
||||
}
|
||||
|
||||
const data: {
|
||||
id?: number;
|
||||
method?: string;
|
||||
params?: Params;
|
||||
success?: Object;
|
||||
error?: ErrorType;
|
||||
} = rawData;
|
||||
|
||||
const {id, method} = data;
|
||||
|
||||
if (id == null) {
|
||||
const {error} = data;
|
||||
if (error != null) {
|
||||
console.error(
|
||||
`Error received from device ${
|
||||
method ? `when calling ${method}` : ''
|
||||
}: ${error.message} + \nDevice Stack Trace: ${error.stacktrace}`,
|
||||
'deviceError',
|
||||
);
|
||||
this.device.then(device => handleError(this.store, device, error));
|
||||
} else if (method === 'refreshPlugins') {
|
||||
this.refreshPlugins();
|
||||
} else if (method === 'execute') {
|
||||
invariant(data.params, 'expected params');
|
||||
const params: Params = data.params;
|
||||
|
||||
const device = this.getDeviceSync();
|
||||
if (device) {
|
||||
const persistingPlugin:
|
||||
| typeof FlipperPlugin
|
||||
| typeof FlipperDevicePlugin
|
||||
| undefined =
|
||||
this.store.getState().plugins.clientPlugins.get(params.api) ||
|
||||
this.store.getState().plugins.devicePlugins.get(params.api);
|
||||
|
||||
if (persistingPlugin && persistingPlugin.persistedStateReducer) {
|
||||
const pluginKey = getPluginKey(this.id, device, params.api);
|
||||
flipperRecorderAddEvent(pluginKey, params.method, params.params);
|
||||
if (GK.get('flipper_event_queue')) {
|
||||
processMessageLater(
|
||||
this.store,
|
||||
pluginKey,
|
||||
persistingPlugin,
|
||||
params,
|
||||
);
|
||||
} else {
|
||||
processMessageImmediately(
|
||||
this.store,
|
||||
pluginKey,
|
||||
persistingPlugin,
|
||||
params,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`Received a message for plugin ${params.api}.${params.method}, which will be ignored because the device has not connected yet`,
|
||||
);
|
||||
}
|
||||
const apiCallbacks = this.broadcastCallbacks.get(params.api);
|
||||
if (!apiCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const methodCallbacks = apiCallbacks.get(params.method);
|
||||
if (methodCallbacks) {
|
||||
for (const callback of methodCallbacks) {
|
||||
callback(params.params);
|
||||
}
|
||||
}
|
||||
}
|
||||
return; // method === 'execute'
|
||||
}
|
||||
|
||||
if (this.sdkVersion < 1) {
|
||||
const callbacks = this.requestCallbacks.get(id);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
this.requestCallbacks.delete(id);
|
||||
this.finishTimingRequestResponse(callbacks.metadata);
|
||||
this.onResponse(data, callbacks.resolve, callbacks.reject);
|
||||
}
|
||||
}
|
||||
|
||||
onResponse(
|
||||
data: {
|
||||
success?: Object;
|
||||
error?: ErrorType;
|
||||
},
|
||||
resolve: ((a: any) => any) | undefined,
|
||||
reject: (error: ErrorType) => any,
|
||||
) {
|
||||
if (data.success) {
|
||||
resolve && resolve(data.success);
|
||||
} else if (data.error) {
|
||||
reject(data.error);
|
||||
const {error} = data;
|
||||
if (error) {
|
||||
this.device.then(device => handleError(this.store, device, error));
|
||||
}
|
||||
} else {
|
||||
// ???
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): ClientExport {
|
||||
return {id: this.id, query: this.query};
|
||||
}
|
||||
|
||||
subscribe(api: string, method: string, callback: (params: Object) => void) {
|
||||
let apiCallbacks = this.broadcastCallbacks.get(api);
|
||||
if (!apiCallbacks) {
|
||||
apiCallbacks = new Map();
|
||||
this.broadcastCallbacks.set(api, apiCallbacks);
|
||||
}
|
||||
|
||||
let methodCallbacks = apiCallbacks.get(method);
|
||||
if (!methodCallbacks) {
|
||||
methodCallbacks = new Set();
|
||||
apiCallbacks.set(method, methodCallbacks);
|
||||
}
|
||||
methodCallbacks.add(callback);
|
||||
}
|
||||
|
||||
unsubscribe(api: string, method: string, callback: Function) {
|
||||
const apiCallbacks = this.broadcastCallbacks.get(api);
|
||||
if (!apiCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const methodCallbacks = apiCallbacks.get(method);
|
||||
if (!methodCallbacks) {
|
||||
return;
|
||||
}
|
||||
methodCallbacks.delete(callback);
|
||||
}
|
||||
|
||||
rawCall<T>(method: string, fromPlugin: boolean, params?: Params): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.messageIdCounter++;
|
||||
const metadata: RequestMetadata = {
|
||||
method,
|
||||
id,
|
||||
params,
|
||||
};
|
||||
|
||||
if (this.sdkVersion < 1) {
|
||||
this.requestCallbacks.set(id, {reject, resolve, metadata});
|
||||
}
|
||||
|
||||
const data = {
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
|
||||
const plugin = params ? params.api : undefined;
|
||||
|
||||
console.debug(data, 'message:call');
|
||||
|
||||
if (this.sdkVersion < 1) {
|
||||
this.startTimingRequestResponse({method, id, params});
|
||||
if (this.connection) {
|
||||
this.connection.fireAndForget({data: JSON.stringify(data)});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const mark = this.getPerformanceMark(metadata);
|
||||
performance.mark(mark);
|
||||
if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) {
|
||||
this.connection &&
|
||||
this.connection
|
||||
.requestResponse({data: JSON.stringify(data)})
|
||||
.subscribe({
|
||||
onComplete: payload => {
|
||||
if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) {
|
||||
const logEventName = this.getLogEventName(data);
|
||||
this.logger.trackTimeSince(mark, logEventName);
|
||||
const response: {
|
||||
success?: Object;
|
||||
error?: ErrorType;
|
||||
} = JSON.parse(payload.data);
|
||||
this.onResponse(response, resolve, reject);
|
||||
}
|
||||
},
|
||||
// Open fresco then layout and you get errors because responses come back after deinit.
|
||||
onError: e => {
|
||||
if (this.isAcceptingMessagesFromPlugin(plugin)) {
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDeviceSync(): BaseDevice | undefined {
|
||||
return this._deviceSet || undefined;
|
||||
}
|
||||
|
||||
startTimingRequestResponse(data: RequestMetadata) {
|
||||
performance.mark(this.getPerformanceMark(data));
|
||||
}
|
||||
|
||||
finishTimingRequestResponse(data: RequestMetadata) {
|
||||
const mark = this.getPerformanceMark(data);
|
||||
const logEventName = this.getLogEventName(data);
|
||||
this.logger.trackTimeSince(mark, logEventName);
|
||||
}
|
||||
|
||||
isAcceptingMessagesFromPlugin(plugin: string | null | undefined) {
|
||||
return this.connection && (!plugin || this.activePlugins.has(plugin));
|
||||
}
|
||||
|
||||
getPerformanceMark(data: RequestMetadata): string {
|
||||
const {method, id} = data;
|
||||
return `request_response_${method}_${id}`;
|
||||
}
|
||||
|
||||
getLogEventName(data: RequestMetadata): string {
|
||||
const {method, params} = data;
|
||||
return params && params.api && params.method
|
||||
? `request_response_${method}_${params.api}_${params.method}`
|
||||
: `request_response_${method}`;
|
||||
}
|
||||
|
||||
initPlugin(pluginId: string) {
|
||||
this.activePlugins.add(pluginId);
|
||||
this.rawSend('init', {plugin: pluginId});
|
||||
}
|
||||
|
||||
deinitPlugin(pluginId: string) {
|
||||
this.activePlugins.delete(pluginId);
|
||||
this.rawSend('deinit', {plugin: pluginId});
|
||||
}
|
||||
|
||||
rawSend(method: string, params?: Object): void {
|
||||
const data = {
|
||||
method,
|
||||
params,
|
||||
};
|
||||
console.debug(data, 'message:send');
|
||||
if (this.connection) {
|
||||
this.connection.fireAndForget({data: JSON.stringify(data)});
|
||||
}
|
||||
}
|
||||
|
||||
call(
|
||||
api: string,
|
||||
method: string,
|
||||
fromPlugin: boolean,
|
||||
params?: Object,
|
||||
): Promise<Object> {
|
||||
return reportPluginFailures(
|
||||
this.rawCall('execute', fromPlugin, {api, method, params}),
|
||||
`Call-${method}`,
|
||||
api,
|
||||
);
|
||||
}
|
||||
|
||||
send(api: string, method: string, params?: Object): void {
|
||||
if (!isProduction()) {
|
||||
console.warn(
|
||||
`${api}:${method ||
|
||||
''} client.send() is deprecated. Please use call() instead so you can handle errors.`,
|
||||
);
|
||||
}
|
||||
return this.rawSend('execute', {api, method, params});
|
||||
}
|
||||
|
||||
async supportsMethod(api: string, method: string): Promise<boolean> {
|
||||
if (this.sdkVersion < 2) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
const response = await this.rawCall<{
|
||||
isSupported: boolean;
|
||||
}>('isMethodSupported', true, {
|
||||
api,
|
||||
method,
|
||||
});
|
||||
return response.isSupported;
|
||||
}
|
||||
}
|
||||
446
desktop/src/MenuBar.tsx
Normal file
446
desktop/src/MenuBar.tsx
Normal file
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* 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 {FlipperPlugin, FlipperDevicePlugin} from './plugin';
|
||||
import {
|
||||
showOpenDialog,
|
||||
startFileExport,
|
||||
startLinkExport,
|
||||
} from './utils/exportData';
|
||||
import {
|
||||
setActiveSheet,
|
||||
ACTIVE_SHEET_PLUGINS,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
} from './reducers/application';
|
||||
import {setStaticView} from './reducers/connections';
|
||||
import SupportRequestFormV2 from './fb-stubs/SupportRequestFormV2';
|
||||
import {Store} from './reducers/';
|
||||
import electron, {MenuItemConstructorOptions} from 'electron';
|
||||
import {notNull} from './utils/typeUtils';
|
||||
import constants from './fb-stubs/constants';
|
||||
|
||||
export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste';
|
||||
export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help';
|
||||
|
||||
export type KeyboardAction = {
|
||||
action: string;
|
||||
label: string;
|
||||
accelerator?: string;
|
||||
topLevelMenu: TopLevelMenu;
|
||||
};
|
||||
|
||||
const defaultKeyboardActions: Array<KeyboardAction> = [
|
||||
{
|
||||
label: 'Clear',
|
||||
accelerator: 'CmdOrCtrl+K',
|
||||
topLevelMenu: 'View',
|
||||
action: 'clear',
|
||||
},
|
||||
{
|
||||
label: 'Go To Bottom',
|
||||
accelerator: 'CmdOrCtrl+B',
|
||||
topLevelMenu: 'View',
|
||||
action: 'goToBottom',
|
||||
},
|
||||
{
|
||||
label: 'Create Paste',
|
||||
topLevelMenu: 'Edit',
|
||||
action: 'createPaste',
|
||||
},
|
||||
];
|
||||
|
||||
export type KeyboardActions = Array<DefaultKeyboardAction | KeyboardAction>;
|
||||
|
||||
const menuItems: Map<string, electron.MenuItem> = new Map();
|
||||
|
||||
let pluginActionHandler: ((action: string) => void) | null;
|
||||
function actionHandler(action: string) {
|
||||
if (pluginActionHandler) {
|
||||
pluginActionHandler(action);
|
||||
} else {
|
||||
console.warn(`Unhandled keyboard action "${action}".`);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupMenuBar(
|
||||
plugins: Array<typeof FlipperPlugin | typeof FlipperDevicePlugin>,
|
||||
store: Store,
|
||||
) {
|
||||
const template = getTemplate(
|
||||
electron.remote.app,
|
||||
electron.remote.shell,
|
||||
store,
|
||||
);
|
||||
// collect all keyboard actions from all plugins
|
||||
const registeredActions: Set<KeyboardAction> = new Set(
|
||||
plugins
|
||||
.map(plugin => plugin.keyboardActions || [])
|
||||
.reduce((acc: KeyboardActions, cv) => acc.concat(cv), [])
|
||||
.map((action: DefaultKeyboardAction | KeyboardAction) =>
|
||||
typeof action === 'string'
|
||||
? defaultKeyboardActions.find(a => a.action === action)
|
||||
: action,
|
||||
)
|
||||
.filter(notNull),
|
||||
);
|
||||
|
||||
// add keyboard actions to
|
||||
registeredActions.forEach(keyboardAction => {
|
||||
if (keyboardAction != null) {
|
||||
appendMenuItem(template, actionHandler, keyboardAction);
|
||||
}
|
||||
});
|
||||
|
||||
// create actual menu instance
|
||||
const applicationMenu = electron.remote.Menu.buildFromTemplate(template);
|
||||
|
||||
// add menu items to map, so we can modify them easily later
|
||||
registeredActions.forEach(keyboardAction => {
|
||||
if (keyboardAction != null) {
|
||||
const {topLevelMenu, label, action} = keyboardAction;
|
||||
const menu = applicationMenu.items.find(
|
||||
menuItem => menuItem.label === topLevelMenu,
|
||||
);
|
||||
if (menu && menu.submenu) {
|
||||
const menuItem = menu.submenu.items.find(
|
||||
menuItem => menuItem.label === label,
|
||||
);
|
||||
menuItem && menuItems.set(action, menuItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update menubar
|
||||
electron.remote.Menu.setApplicationMenu(applicationMenu);
|
||||
}
|
||||
|
||||
function appendMenuItem(
|
||||
template: Array<MenuItemConstructorOptions>,
|
||||
actionHandler: (action: string) => void,
|
||||
item: KeyboardAction,
|
||||
) {
|
||||
const keyboardAction = item;
|
||||
if (keyboardAction == null) {
|
||||
return;
|
||||
}
|
||||
const itemIndex = template.findIndex(
|
||||
menu => menu.label === keyboardAction.topLevelMenu,
|
||||
);
|
||||
if (itemIndex > -1 && template[itemIndex].submenu != null) {
|
||||
(template[itemIndex].submenu as MenuItemConstructorOptions[]).push({
|
||||
click: () => actionHandler(keyboardAction.action),
|
||||
label: keyboardAction.label,
|
||||
accelerator: keyboardAction.accelerator,
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function activateMenuItems(
|
||||
activePlugin:
|
||||
| FlipperPlugin<any, any, any>
|
||||
| FlipperDevicePlugin<any, any, any>,
|
||||
) {
|
||||
// disable all keyboard actions
|
||||
for (const item of menuItems) {
|
||||
item[1].enabled = false;
|
||||
}
|
||||
|
||||
// set plugin action handler
|
||||
if (activePlugin.onKeyboardAction) {
|
||||
pluginActionHandler = activePlugin.onKeyboardAction;
|
||||
}
|
||||
|
||||
// enable keyboard actions for the current plugin
|
||||
if (activePlugin.constructor.keyboardActions != null) {
|
||||
(activePlugin.constructor.keyboardActions || []).forEach(keyboardAction => {
|
||||
const action =
|
||||
typeof keyboardAction === 'string'
|
||||
? keyboardAction
|
||||
: keyboardAction.action;
|
||||
const item = menuItems.get(action);
|
||||
if (item != null) {
|
||||
item.enabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set the application menu again to make sure it updates
|
||||
electron.remote.Menu.setApplicationMenu(
|
||||
electron.remote.Menu.getApplicationMenu(),
|
||||
);
|
||||
}
|
||||
|
||||
function getTemplate(
|
||||
app: electron.App,
|
||||
shell: electron.Shell,
|
||||
store: Store,
|
||||
): Array<MenuItemConstructorOptions> {
|
||||
const exportSubmenu = [
|
||||
{
|
||||
label: 'File...',
|
||||
accelerator: 'CommandOrControl+E',
|
||||
click: () => startFileExport(store.dispatch),
|
||||
},
|
||||
];
|
||||
if (constants.ENABLE_SHAREABLE_LINK) {
|
||||
exportSubmenu.push({
|
||||
label: 'Shareable Link',
|
||||
accelerator: 'CommandOrControl+Shift+E',
|
||||
click: () => startLinkExport(store.dispatch),
|
||||
});
|
||||
}
|
||||
const fileSubmenu: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'Preferences',
|
||||
accelerator: 'Cmd+,',
|
||||
click: () => store.dispatch(setActiveSheet(ACTIVE_SHEET_SETTINGS)),
|
||||
},
|
||||
{
|
||||
label: 'Import Flipper File...',
|
||||
accelerator: 'CommandOrControl+O',
|
||||
click: function() {
|
||||
showOpenDialog(store);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
submenu: exportSubmenu,
|
||||
},
|
||||
];
|
||||
const supportRequestSubmenu = [
|
||||
{
|
||||
label: 'Create...',
|
||||
click: function() {
|
||||
// Dispatch an action to open the export screen of Support Request form
|
||||
store.dispatch(setStaticView(SupportRequestFormV2));
|
||||
},
|
||||
},
|
||||
];
|
||||
fileSubmenu.push({
|
||||
label: 'Support Requests',
|
||||
submenu: supportRequestSubmenu,
|
||||
});
|
||||
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: fileSubmenu,
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Undo',
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
role: 'undo',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
role: 'redo',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
role: 'cut',
|
||||
},
|
||||
{
|
||||
label: 'Copy',
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
role: 'copy',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
role: 'paste',
|
||||
},
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'CmdOrCtrl+A',
|
||||
role: 'selectAll',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click: function(
|
||||
_,
|
||||
focusedWindow: electron.BrowserWindow | undefined,
|
||||
) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: (function() {
|
||||
if (process.platform === 'darwin') {
|
||||
return 'Ctrl+Command+F';
|
||||
} else {
|
||||
return 'F11';
|
||||
}
|
||||
})(),
|
||||
click: function(
|
||||
_,
|
||||
focusedWindow: electron.BrowserWindow | undefined,
|
||||
) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Manage Plugins...',
|
||||
click: function() {
|
||||
store.dispatch(setActiveSheet(ACTIVE_SHEET_PLUGINS));
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: (function() {
|
||||
if (process.platform === 'darwin') {
|
||||
return 'Alt+Command+I';
|
||||
} else {
|
||||
return 'Ctrl+Shift+I';
|
||||
}
|
||||
})(),
|
||||
click: function(
|
||||
_,
|
||||
focusedWindow: electron.BrowserWindow | undefined,
|
||||
) {
|
||||
if (focusedWindow) {
|
||||
// @ts-ignore: https://github.com/electron/electron/issues/7832
|
||||
focusedWindow.toggleDevTools();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize',
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Getting started',
|
||||
click: function() {
|
||||
shell.openExternal(
|
||||
'https://fbflipper.com/docs/getting-started.html',
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Create plugins',
|
||||
click: function() {
|
||||
shell.openExternal(
|
||||
'https://fbflipper.com/docs/tutorial/intro.html',
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Report problems',
|
||||
click: function() {
|
||||
shell.openExternal('https://github.com/facebook/flipper/issues');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const name = app.name;
|
||||
template.unshift({
|
||||
label: name,
|
||||
submenu: [
|
||||
{
|
||||
label: 'About ' + name,
|
||||
role: 'about',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Services',
|
||||
role: 'services',
|
||||
submenu: [],
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Hide ' + name,
|
||||
accelerator: 'Command+H',
|
||||
role: 'hide',
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
role: 'hideOthers',
|
||||
},
|
||||
{
|
||||
label: 'Show All',
|
||||
role: 'unhide',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: function() {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const windowMenu = template.find(function(m) {
|
||||
return m.role === 'window';
|
||||
});
|
||||
if (windowMenu) {
|
||||
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
role: 'front',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
581
desktop/src/NotificationsHub.tsx
Normal file
581
desktop/src/NotificationsHub.tsx
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* 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 {SearchableProps, FlipperBasePlugin, FlipperPlugin} from 'flipper';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import {
|
||||
Searchable,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
FlexBox,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Glyph,
|
||||
ContextMenu,
|
||||
styled,
|
||||
colors,
|
||||
} from 'flipper';
|
||||
import {FlipperDevicePlugin} from './plugin';
|
||||
import {connect} from 'react-redux';
|
||||
import React, {Component, Fragment} from 'react';
|
||||
import {clipboard} from 'electron';
|
||||
import {
|
||||
PluginNotification,
|
||||
updatePluginBlacklist,
|
||||
updateCategoryBlacklist,
|
||||
} from './reducers/notifications';
|
||||
import {selectPlugin} from './reducers/connections';
|
||||
import {State as StoreState} from './reducers/index';
|
||||
import textContent from './utils/textContent';
|
||||
import createPaste from './fb-stubs/createPaste';
|
||||
import {getPluginTitle} from './utils/pluginUtils';
|
||||
|
||||
type OwnProps = {
|
||||
onClear: () => void;
|
||||
selectedID: string | null | undefined;
|
||||
logger: Logger;
|
||||
} & Partial<SearchableProps>;
|
||||
|
||||
type StateFromProps = {
|
||||
activeNotifications: Array<PluginNotification>;
|
||||
invalidatedNotifications: Array<PluginNotification>;
|
||||
blacklistedPlugins: Array<string>;
|
||||
blacklistedCategories: Array<string>;
|
||||
devicePlugins: Map<string, typeof FlipperDevicePlugin>;
|
||||
clientPlugins: Map<string, typeof FlipperPlugin>;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
selectPlugin: (payload: {
|
||||
selectedPlugin: string | null;
|
||||
selectedApp: string | null;
|
||||
deepLinkPayload: string | null;
|
||||
}) => any;
|
||||
updatePluginBlacklist: (blacklist: Array<string>) => any;
|
||||
updateCategoryBlacklist: (blacklist: Array<string>) => any;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
|
||||
type State = {
|
||||
selectedNotification: string | null | undefined;
|
||||
};
|
||||
|
||||
const Content = styled(FlexColumn)({
|
||||
padding: '0 10px',
|
||||
backgroundColor: colors.light02,
|
||||
overflow: 'scroll',
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
const Heading = styled(FlexBox)({
|
||||
display: 'block',
|
||||
alignItems: 'center',
|
||||
marginTop: 15,
|
||||
marginBottom: 5,
|
||||
color: colors.macOSSidebarSectionTitle,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
const NoContent = styled(FlexColumn)({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
flexGrow: 1,
|
||||
fontWeight: 500,
|
||||
lineHeight: 2.5,
|
||||
color: colors.light30,
|
||||
});
|
||||
|
||||
class NotificationsTable extends Component<Props & SearchableProps, State> {
|
||||
contextMenuItems = [{label: 'Clear all', click: this.props.onClear}];
|
||||
state: State = {
|
||||
selectedNotification: this.props.selectedID,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: Props & SearchableProps) {
|
||||
if (this.props.filters.length !== prevProps.filters.length) {
|
||||
this.props.updatePluginBlacklist(
|
||||
this.props.filters
|
||||
.filter(f => f.type === 'exclude' && f.key.toLowerCase() === 'plugin')
|
||||
.map(f => String(f.value)),
|
||||
);
|
||||
|
||||
this.props.updateCategoryBlacklist(
|
||||
this.props.filters
|
||||
.filter(
|
||||
f => f.type === 'exclude' && f.key.toLowerCase() === 'category',
|
||||
)
|
||||
.map(f => String(f.value)),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.props.selectedID &&
|
||||
prevProps.selectedID !== this.props.selectedID
|
||||
) {
|
||||
this.setState({
|
||||
selectedNotification: this.props.selectedID,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onHidePlugin = (pluginId: string) => {
|
||||
// add filter to searchbar
|
||||
this.props.addFilter({
|
||||
value: pluginId,
|
||||
type: 'exclude',
|
||||
key: 'plugin',
|
||||
});
|
||||
this.props.updatePluginBlacklist(
|
||||
this.props.blacklistedPlugins.concat(pluginId),
|
||||
);
|
||||
};
|
||||
|
||||
onHideCategory = (category: string) => {
|
||||
// add filter to searchbar
|
||||
this.props.addFilter({
|
||||
value: category,
|
||||
type: 'exclude',
|
||||
key: 'category',
|
||||
});
|
||||
this.props.updatePluginBlacklist(
|
||||
this.props.blacklistedCategories.concat(category),
|
||||
);
|
||||
};
|
||||
|
||||
getFilter = (): ((n: PluginNotification) => boolean) => (
|
||||
n: PluginNotification,
|
||||
) => {
|
||||
const searchTerm = this.props.searchTerm.toLowerCase();
|
||||
|
||||
// filter plugins
|
||||
const blacklistedPlugins = new Set(
|
||||
this.props.blacklistedPlugins.map(p => p.toLowerCase()),
|
||||
);
|
||||
if (blacklistedPlugins.has(n.pluginId.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter categories
|
||||
const {category} = n.notification;
|
||||
if (category) {
|
||||
const blacklistedCategories = new Set(
|
||||
this.props.blacklistedCategories.map(p => p.toLowerCase()),
|
||||
);
|
||||
if (blacklistedCategories.has(category.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchTerm.length === 0) {
|
||||
return true;
|
||||
} else if (n.notification.title.toLowerCase().indexOf(searchTerm) > -1) {
|
||||
return true;
|
||||
} else if (
|
||||
typeof n.notification.message === 'string' &&
|
||||
n.notification.message.toLowerCase().indexOf(searchTerm) > -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getPlugin = (id: string) =>
|
||||
this.props.clientPlugins.get(id) || this.props.devicePlugins.get(id);
|
||||
|
||||
render() {
|
||||
const activeNotifications = this.props.activeNotifications
|
||||
.filter(this.getFilter())
|
||||
.map((n: PluginNotification) => {
|
||||
const {category} = n.notification;
|
||||
|
||||
return (
|
||||
<NotificationItem
|
||||
key={n.notification.id}
|
||||
{...n}
|
||||
plugin={this.getPlugin(n.pluginId)}
|
||||
isSelected={this.state.selectedNotification === n.notification.id}
|
||||
onHighlight={() =>
|
||||
this.setState({selectedNotification: n.notification.id})
|
||||
}
|
||||
onClear={this.props.onClear}
|
||||
onHidePlugin={() => this.onHidePlugin(n.pluginId)}
|
||||
onHideCategory={
|
||||
category ? () => this.onHideCategory(category) : undefined
|
||||
}
|
||||
selectPlugin={this.props.selectPlugin}
|
||||
logger={this.props.logger}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.reverse();
|
||||
|
||||
const invalidatedNotifications = this.props.invalidatedNotifications
|
||||
.filter(this.getFilter())
|
||||
.map((n: PluginNotification) => (
|
||||
<NotificationItem
|
||||
key={n.notification.id}
|
||||
{...n}
|
||||
plugin={this.getPlugin(n.pluginId)}
|
||||
onClear={this.props.onClear}
|
||||
inactive
|
||||
/>
|
||||
))
|
||||
.reverse();
|
||||
|
||||
return (
|
||||
<ContextMenu items={this.contextMenuItems} component={Content}>
|
||||
{activeNotifications.length > 0 && (
|
||||
<Fragment>
|
||||
<Heading>Active notifications</Heading>
|
||||
<FlexColumn shrink={false}>{activeNotifications}</FlexColumn>
|
||||
</Fragment>
|
||||
)}
|
||||
{invalidatedNotifications.length > 0 && (
|
||||
<Fragment>
|
||||
<Heading>Past notifications</Heading>
|
||||
<FlexColumn shrink={false}>{invalidatedNotifications}</FlexColumn>
|
||||
</Fragment>
|
||||
)}
|
||||
{activeNotifications.length + invalidatedNotifications.length === 0 && (
|
||||
<NoContent>
|
||||
<Glyph
|
||||
name="bell-null"
|
||||
size={24}
|
||||
variant="outline"
|
||||
color={colors.light30}
|
||||
/>
|
||||
No Notifications
|
||||
</NoContent>
|
||||
)}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ConnectedNotificationsTable = connect<
|
||||
StateFromProps,
|
||||
DispatchFromProps,
|
||||
OwnProps,
|
||||
StoreState
|
||||
>(
|
||||
({
|
||||
notifications: {
|
||||
activeNotifications,
|
||||
invalidatedNotifications,
|
||||
blacklistedPlugins,
|
||||
blacklistedCategories,
|
||||
},
|
||||
plugins: {devicePlugins, clientPlugins},
|
||||
}) => ({
|
||||
activeNotifications,
|
||||
invalidatedNotifications,
|
||||
blacklistedPlugins,
|
||||
blacklistedCategories,
|
||||
devicePlugins,
|
||||
clientPlugins,
|
||||
}),
|
||||
{
|
||||
updatePluginBlacklist,
|
||||
updateCategoryBlacklist,
|
||||
selectPlugin,
|
||||
},
|
||||
)(Searchable(NotificationsTable));
|
||||
|
||||
const shadow = (
|
||||
props: {isSelected?: boolean; inactive?: boolean},
|
||||
_hover?: boolean,
|
||||
) => {
|
||||
if (props.inactive) {
|
||||
return `inset 0 0 0 1px ${colors.light10}`;
|
||||
}
|
||||
const shadow = ['1px 1px 5px rgba(0,0,0,0.1)'];
|
||||
if (props.isSelected) {
|
||||
shadow.push(`inset 0 0 0 2px ${colors.macOSTitleBarIconSelected}`);
|
||||
}
|
||||
|
||||
return shadow.join(',');
|
||||
};
|
||||
|
||||
const SEVERITY_COLOR_MAP = {
|
||||
warning: colors.yellow,
|
||||
error: colors.red,
|
||||
};
|
||||
|
||||
type NotificationBoxProps = {
|
||||
inactive?: boolean;
|
||||
isSelected?: boolean;
|
||||
severity: keyof typeof SEVERITY_COLOR_MAP;
|
||||
};
|
||||
|
||||
const NotificationBox = styled(FlexRow)<NotificationBoxProps>(props => ({
|
||||
backgroundColor: props.inactive ? 'transparent' : colors.white,
|
||||
opacity: props.inactive ? 0.5 : 1,
|
||||
alignItems: 'flex-start',
|
||||
borderRadius: 5,
|
||||
padding: 10,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
marginBottom: 10,
|
||||
boxShadow: shadow(props),
|
||||
'::before': {
|
||||
content: '""',
|
||||
display: !props.inactive && !props.isSelected ? 'block' : 'none',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 3,
|
||||
backgroundColor: SEVERITY_COLOR_MAP[props.severity] || colors.info,
|
||||
},
|
||||
':hover': {
|
||||
boxShadow: shadow(props, true),
|
||||
'& > *': {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const Title = styled.div({
|
||||
minWidth: 150,
|
||||
color: colors.light80,
|
||||
flexShrink: 0,
|
||||
marginBottom: 6,
|
||||
fontWeight: 500,
|
||||
lineHeight: 1,
|
||||
fontSize: '1.1em',
|
||||
});
|
||||
|
||||
const NotificationContent = styled(FlexColumn)<{isSelected?: boolean}>(
|
||||
props => ({
|
||||
marginLeft: 6,
|
||||
marginRight: 10,
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
maxHeight: props.isSelected ? 'none' : 56,
|
||||
lineHeight: 1.4,
|
||||
color: props.isSelected ? colors.light50 : colors.light30,
|
||||
}),
|
||||
);
|
||||
|
||||
const Actions = styled(FlexRow)({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
color: colors.light20,
|
||||
marginTop: 12,
|
||||
borderTop: `1px solid ${colors.light05}`,
|
||||
paddingTop: 8,
|
||||
});
|
||||
|
||||
const NotificationButton = styled.div({
|
||||
border: `1px solid ${colors.light20}`,
|
||||
color: colors.light50,
|
||||
borderRadius: 4,
|
||||
textAlign: 'center',
|
||||
padding: 4,
|
||||
width: 80,
|
||||
marginBottom: 4,
|
||||
opacity: 0,
|
||||
transition: '0.15s opacity',
|
||||
'[data-role="notification"]:hover &': {
|
||||
opacity: 0.5,
|
||||
},
|
||||
':last-child': {
|
||||
marginBottom: 0,
|
||||
},
|
||||
'[data-role="notification"] &:hover': {
|
||||
opacity: 1,
|
||||
},
|
||||
});
|
||||
|
||||
type ItemProps = {
|
||||
onHighlight?: () => any;
|
||||
onHidePlugin?: () => any;
|
||||
onHideCategory?: () => any;
|
||||
onClear?: () => any;
|
||||
isSelected?: boolean;
|
||||
inactive?: boolean;
|
||||
selectPlugin?: (payload: {
|
||||
selectedPlugin: string | null;
|
||||
selectedApp: string | null;
|
||||
deepLinkPayload: string | null;
|
||||
}) => any;
|
||||
logger?: Logger;
|
||||
plugin: typeof FlipperBasePlugin | null | undefined;
|
||||
};
|
||||
|
||||
type ItemState = {
|
||||
reportedNotHelpful: boolean;
|
||||
};
|
||||
|
||||
class NotificationItem extends Component<
|
||||
ItemProps & PluginNotification,
|
||||
ItemState
|
||||
> {
|
||||
constructor(props: ItemProps & PluginNotification) {
|
||||
super(props);
|
||||
const items = [];
|
||||
if (props.onHidePlugin && props.plugin) {
|
||||
items.push({
|
||||
label: `Hide ${getPluginTitle(props.plugin)} plugin`,
|
||||
click: this.props.onHidePlugin,
|
||||
});
|
||||
}
|
||||
if (props.onHideCategory) {
|
||||
items.push({
|
||||
label: 'Hide Similar',
|
||||
click: this.props.onHideCategory,
|
||||
});
|
||||
}
|
||||
items.push(
|
||||
{label: 'Copy', click: this.copy},
|
||||
{label: 'Create Paste', click: this.createPaste},
|
||||
);
|
||||
|
||||
this.contextMenuItems = items;
|
||||
}
|
||||
|
||||
state = {reportedNotHelpful: false};
|
||||
contextMenuItems: Array<{label: string; click: (() => any) | undefined}>;
|
||||
deepLinkButton = React.createRef();
|
||||
|
||||
createPaste = () => {
|
||||
createPaste(this.getContent());
|
||||
};
|
||||
|
||||
copy = () => clipboard.writeText(this.getContent());
|
||||
|
||||
getContent = (): string =>
|
||||
[
|
||||
this.props.notification.timestamp,
|
||||
`[${this.props.notification.severity}] ${this.props.notification.title}`,
|
||||
this.props.notification.action,
|
||||
this.props.notification.category,
|
||||
textContent(this.props.notification.message),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
openDeeplink = () => {
|
||||
const {notification, pluginId, client} = this.props;
|
||||
if (this.props.selectPlugin && notification.action) {
|
||||
this.props.selectPlugin({
|
||||
selectedPlugin: pluginId,
|
||||
selectedApp: client,
|
||||
deepLinkPayload: notification.action,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
reportNotUseful = (e: React.MouseEvent<any>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.props.logger) {
|
||||
this.props.logger.track(
|
||||
'usage',
|
||||
'notification-not-useful',
|
||||
this.props.notification,
|
||||
);
|
||||
}
|
||||
this.setState({reportedNotHelpful: true});
|
||||
};
|
||||
|
||||
onHide = (e: React.MouseEvent<any>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.props.onHideCategory) {
|
||||
this.props.onHideCategory();
|
||||
} else if (this.props.onHidePlugin) {
|
||||
this.props.onHidePlugin();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
notification,
|
||||
isSelected,
|
||||
inactive,
|
||||
onHidePlugin,
|
||||
onHideCategory,
|
||||
plugin,
|
||||
} = this.props;
|
||||
const {action} = notification;
|
||||
|
||||
return (
|
||||
<ContextMenu<React.ComponentProps<typeof NotificationBox>>
|
||||
data-role="notification"
|
||||
component={NotificationBox}
|
||||
severity={notification.severity}
|
||||
onClick={this.props.onHighlight}
|
||||
isSelected={isSelected}
|
||||
inactive={inactive}
|
||||
items={this.contextMenuItems}>
|
||||
<Glyph name={(plugin ? plugin.icon : 'bell') || 'bell'} size={12} />
|
||||
<NotificationContent isSelected={isSelected}>
|
||||
<Title>{notification.title}</Title>
|
||||
{notification.message}
|
||||
{!inactive &&
|
||||
isSelected &&
|
||||
plugin &&
|
||||
(action || onHidePlugin || onHideCategory) && (
|
||||
<Actions>
|
||||
<FlexRow>
|
||||
{action && (
|
||||
<Button onClick={this.openDeeplink}>
|
||||
Open in {getPluginTitle(plugin)}
|
||||
</Button>
|
||||
)}
|
||||
<ButtonGroup>
|
||||
{onHideCategory && (
|
||||
<Button onClick={onHideCategory}>Hide similar</Button>
|
||||
)}
|
||||
{onHidePlugin && (
|
||||
<Button onClick={onHidePlugin}>
|
||||
Hide {getPluginTitle(plugin)}
|
||||
</Button>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</FlexRow>
|
||||
<span>
|
||||
{notification.timestamp
|
||||
? new Date(notification.timestamp).toTimeString()
|
||||
: ''}
|
||||
</span>
|
||||
</Actions>
|
||||
)}
|
||||
</NotificationContent>
|
||||
{action && !inactive && !isSelected && (
|
||||
<FlexColumn style={{alignSelf: 'center'}}>
|
||||
{action && (
|
||||
<NotificationButton onClick={this.openDeeplink}>
|
||||
Open
|
||||
</NotificationButton>
|
||||
)}
|
||||
{this.state.reportedNotHelpful ? (
|
||||
<NotificationButton onClick={this.onHide}>
|
||||
Hide
|
||||
</NotificationButton>
|
||||
) : (
|
||||
<NotificationButton onClick={this.reportNotUseful}>
|
||||
Not helpful
|
||||
</NotificationButton>
|
||||
)}
|
||||
</FlexColumn>
|
||||
)}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
457
desktop/src/PluginContainer.tsx
Normal file
457
desktop/src/PluginContainer.tsx
Normal file
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* 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 {
|
||||
FlipperPlugin,
|
||||
FlipperDevicePlugin,
|
||||
Props as PluginProps,
|
||||
} from './plugin';
|
||||
import {Logger} from './fb-interfaces/Logger';
|
||||
import BaseDevice from './devices/BaseDevice';
|
||||
import {pluginKey as getPluginKey} from './reducers/pluginStates';
|
||||
import Client from './Client';
|
||||
import {
|
||||
ErrorBoundary,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
colors,
|
||||
styled,
|
||||
ArchivedDevice,
|
||||
Glyph,
|
||||
Label,
|
||||
VBox,
|
||||
View,
|
||||
} from 'flipper';
|
||||
import {
|
||||
StaticView,
|
||||
setStaticView,
|
||||
pluginIsStarred,
|
||||
starPlugin,
|
||||
} from './reducers/connections';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {connect, ReactReduxContext} from 'react-redux';
|
||||
import {setPluginState} from './reducers/pluginStates';
|
||||
import {selectPlugin} from './reducers/connections';
|
||||
import {State as Store, MiddlewareAPI} from './reducers/index';
|
||||
import {activateMenuItems} from './MenuBar';
|
||||
import {Message} from './reducers/pluginMessageQueue';
|
||||
import {Idler} from './utils/Idler';
|
||||
import {processMessageQueue} from './utils/messageQueue';
|
||||
import {ToggleButton, SmallText} from './ui';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
width: 0,
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
backgroundColor: colors.white,
|
||||
});
|
||||
|
||||
export const SidebarContainer = styled(FlexRow)({
|
||||
backgroundColor: colors.light02,
|
||||
height: '100%',
|
||||
overflow: 'scroll',
|
||||
});
|
||||
|
||||
const Waiting = styled(FlexColumn)({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
background: colors.light02,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
function ProgressBar({progress}: {progress: number}) {
|
||||
return (
|
||||
<ProgressBarContainer>
|
||||
<ProgressBarBar progress={progress} />
|
||||
</ProgressBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const ProgressBarContainer = styled.div({
|
||||
border: `1px solid ${colors.cyan}`,
|
||||
borderRadius: 4,
|
||||
width: 300,
|
||||
});
|
||||
|
||||
const ProgressBarBar = styled.div<{progress: number}>(({progress}) => ({
|
||||
background: colors.cyan,
|
||||
width: `${Math.min(100, Math.round(progress * 100))}%`,
|
||||
height: 8,
|
||||
}));
|
||||
|
||||
type OwnProps = {
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
pluginState: Object;
|
||||
activePlugin: typeof FlipperPlugin | typeof FlipperDevicePlugin | null;
|
||||
target: Client | BaseDevice | null;
|
||||
pluginKey: string | null;
|
||||
deepLinkPayload: string | null;
|
||||
selectedApp: string | null;
|
||||
isArchivedDevice: boolean;
|
||||
pendingMessages: Message[] | undefined;
|
||||
pluginIsEnabled: boolean;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
selectPlugin: (payload: {
|
||||
selectedPlugin: string | null;
|
||||
selectedApp?: string | null;
|
||||
deepLinkPayload: string | null;
|
||||
}) => any;
|
||||
setPluginState: (payload: {pluginKey: string; state: any}) => void;
|
||||
setStaticView: (payload: StaticView) => void;
|
||||
starPlugin: typeof starPlugin;
|
||||
};
|
||||
|
||||
type Props = StateFromProps & DispatchFromProps & OwnProps;
|
||||
|
||||
type State = {
|
||||
progress: {current: number; total: number};
|
||||
};
|
||||
|
||||
class PluginContainer extends PureComponent<Props, State> {
|
||||
static contextType = ReactReduxContext;
|
||||
|
||||
plugin:
|
||||
| FlipperPlugin<any, any, any>
|
||||
| FlipperDevicePlugin<any, any, any>
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
refChanged = (
|
||||
ref:
|
||||
| FlipperPlugin<any, any, any>
|
||||
| FlipperDevicePlugin<any, any, any>
|
||||
| null
|
||||
| undefined,
|
||||
) => {
|
||||
if (this.plugin) {
|
||||
this.plugin._teardown();
|
||||
this.plugin = null;
|
||||
}
|
||||
if (ref && this.props.target) {
|
||||
activateMenuItems(ref);
|
||||
ref._init();
|
||||
this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`);
|
||||
this.plugin = ref;
|
||||
}
|
||||
};
|
||||
|
||||
idler?: Idler;
|
||||
pluginBeingProcessed: string | null = null;
|
||||
|
||||
state = {progress: {current: 0, total: 0}};
|
||||
|
||||
get store(): MiddlewareAPI {
|
||||
return this.context.store;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.plugin) {
|
||||
this.plugin._teardown();
|
||||
this.plugin = null;
|
||||
}
|
||||
this.cancelCurrentQueue();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.processMessageQueue();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.processMessageQueue();
|
||||
}
|
||||
|
||||
processMessageQueue() {
|
||||
const {
|
||||
pluginKey,
|
||||
pendingMessages,
|
||||
activePlugin,
|
||||
pluginIsEnabled,
|
||||
} = this.props;
|
||||
if (pluginKey !== this.pluginBeingProcessed) {
|
||||
this.pluginBeingProcessed = pluginKey;
|
||||
this.cancelCurrentQueue();
|
||||
this.setState({progress: {current: 0, total: 0}});
|
||||
if (
|
||||
pluginIsEnabled &&
|
||||
activePlugin &&
|
||||
activePlugin.persistedStateReducer &&
|
||||
pluginKey &&
|
||||
pendingMessages?.length
|
||||
) {
|
||||
const start = Date.now();
|
||||
this.idler = new Idler();
|
||||
processMessageQueue(
|
||||
activePlugin,
|
||||
pluginKey,
|
||||
this.store,
|
||||
progress => {
|
||||
this.setState({progress});
|
||||
},
|
||||
this.idler,
|
||||
).then(completed => {
|
||||
const duration = Date.now() - start;
|
||||
this.props.logger.track(
|
||||
'duration',
|
||||
'queue-processing-before-plugin-open',
|
||||
{
|
||||
completed,
|
||||
duration,
|
||||
},
|
||||
activePlugin.id,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelCurrentQueue() {
|
||||
if (this.idler && !this.idler.isCancelled()) {
|
||||
this.idler.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activePlugin,
|
||||
pluginKey,
|
||||
target,
|
||||
pendingMessages,
|
||||
pluginIsEnabled,
|
||||
} = this.props;
|
||||
if (!activePlugin || !target || !pluginKey) {
|
||||
console.warn(`No selected plugin. Rendering empty!`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!pluginIsEnabled) {
|
||||
return this.renderPluginEnabler();
|
||||
}
|
||||
if (!pendingMessages || pendingMessages.length === 0) {
|
||||
return this.renderPlugin();
|
||||
}
|
||||
return this.renderPluginLoader();
|
||||
}
|
||||
|
||||
renderPluginEnabler() {
|
||||
const activePlugin = this.props.activePlugin!;
|
||||
return (
|
||||
<View grow>
|
||||
<Waiting>
|
||||
<VBox>
|
||||
<FlexRow>
|
||||
<Label
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
color: colors.light30,
|
||||
textTransform: 'uppercase',
|
||||
}}>
|
||||
{activePlugin.title}
|
||||
</Label>
|
||||
</FlexRow>
|
||||
</VBox>
|
||||
<VBox>
|
||||
<ToggleButton
|
||||
toggled={false}
|
||||
onClick={() => {
|
||||
this.props.starPlugin({
|
||||
selectedPlugin: activePlugin.id,
|
||||
selectedApp: (this.props.target as Client).query.app,
|
||||
});
|
||||
}}
|
||||
large
|
||||
/>
|
||||
</VBox>
|
||||
<VBox>
|
||||
<SmallText>Click to enable this plugin</SmallText>
|
||||
</VBox>
|
||||
</Waiting>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderPluginLoader() {
|
||||
return (
|
||||
<View grow>
|
||||
<Waiting>
|
||||
<VBox>
|
||||
<Glyph
|
||||
name="dashboard"
|
||||
variant="outline"
|
||||
size={24}
|
||||
color={colors.light30}
|
||||
/>
|
||||
</VBox>
|
||||
<VBox>
|
||||
<Label>
|
||||
Processing {this.state.progress.total} events for{' '}
|
||||
{this.props.activePlugin?.id ?? 'plugin'}
|
||||
</Label>
|
||||
</VBox>
|
||||
<VBox>
|
||||
<ProgressBar
|
||||
progress={this.state.progress.current / this.state.progress.total}
|
||||
/>
|
||||
</VBox>
|
||||
</Waiting>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderPlugin() {
|
||||
const {
|
||||
pluginState,
|
||||
setPluginState,
|
||||
activePlugin,
|
||||
pluginKey,
|
||||
target,
|
||||
isArchivedDevice,
|
||||
selectedApp,
|
||||
} = this.props;
|
||||
if (!activePlugin || !target || !pluginKey) {
|
||||
console.warn(`No selected plugin. Rendering empty!`);
|
||||
return null;
|
||||
}
|
||||
const props: PluginProps<Object> & {
|
||||
key: string;
|
||||
ref: (
|
||||
ref:
|
||||
| FlipperPlugin<any, any, any>
|
||||
| FlipperDevicePlugin<any, any, any>
|
||||
| null
|
||||
| undefined,
|
||||
) => void;
|
||||
} = {
|
||||
key: pluginKey,
|
||||
logger: this.props.logger,
|
||||
selectedApp,
|
||||
persistedState: activePlugin.defaultPersistedState
|
||||
? {
|
||||
...activePlugin.defaultPersistedState,
|
||||
...pluginState,
|
||||
}
|
||||
: pluginState,
|
||||
setStaticView: (payload: StaticView) => this.props.setStaticView(payload),
|
||||
setPersistedState: state => setPluginState({pluginKey, state}),
|
||||
target,
|
||||
deepLinkPayload: this.props.deepLinkPayload,
|
||||
selectPlugin: (pluginID: string, deepLinkPayload: string | null) => {
|
||||
const {target} = this.props;
|
||||
// check if plugin will be available
|
||||
if (
|
||||
target instanceof Client &&
|
||||
target.plugins.some(p => p === pluginID)
|
||||
) {
|
||||
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
|
||||
return true;
|
||||
} else if (target instanceof BaseDevice) {
|
||||
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
ref: this.refChanged,
|
||||
isArchivedDevice,
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Container key="plugin">
|
||||
<ErrorBoundary
|
||||
heading={`Plugin "${activePlugin.title ||
|
||||
'Unknown'}" encountered an error during render`}>
|
||||
{React.createElement(activePlugin, props)}
|
||||
</ErrorBoundary>
|
||||
</Container>
|
||||
<SidebarContainer id="detailsSidebar" />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({
|
||||
connections: {
|
||||
selectedPlugin,
|
||||
selectedDevice,
|
||||
selectedApp,
|
||||
clients,
|
||||
deepLinkPayload,
|
||||
userStarredPlugins,
|
||||
},
|
||||
pluginStates,
|
||||
plugins: {devicePlugins, clientPlugins},
|
||||
pluginMessageQueue,
|
||||
}) => {
|
||||
let pluginKey = null;
|
||||
let target = null;
|
||||
let activePlugin:
|
||||
| typeof FlipperDevicePlugin
|
||||
| typeof FlipperPlugin
|
||||
| null = null;
|
||||
let pluginIsEnabled = false;
|
||||
|
||||
if (selectedPlugin) {
|
||||
activePlugin = devicePlugins.get(selectedPlugin) || null;
|
||||
target = selectedDevice;
|
||||
if (selectedDevice && activePlugin) {
|
||||
pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id);
|
||||
pluginIsEnabled = true;
|
||||
} else {
|
||||
target =
|
||||
clients.find((client: Client) => client.id === selectedApp) || null;
|
||||
activePlugin = clientPlugins.get(selectedPlugin) || null;
|
||||
if (activePlugin && target) {
|
||||
pluginKey = getPluginKey(target.id, activePlugin.id);
|
||||
pluginIsEnabled = pluginIsStarred(
|
||||
userStarredPlugins,
|
||||
selectedApp,
|
||||
activePlugin.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const isArchivedDevice = !selectedDevice
|
||||
? false
|
||||
: selectedDevice instanceof ArchivedDevice;
|
||||
if (isArchivedDevice) {
|
||||
pluginIsEnabled = true;
|
||||
}
|
||||
|
||||
const pendingMessages = pluginKey
|
||||
? pluginMessageQueue[pluginKey]
|
||||
: undefined;
|
||||
|
||||
const s: StateFromProps = {
|
||||
pluginState: pluginStates[pluginKey as string],
|
||||
activePlugin: activePlugin,
|
||||
target,
|
||||
deepLinkPayload,
|
||||
pluginKey,
|
||||
isArchivedDevice,
|
||||
selectedApp: selectedApp || null,
|
||||
pendingMessages,
|
||||
pluginIsEnabled,
|
||||
};
|
||||
return s;
|
||||
},
|
||||
{
|
||||
setPluginState,
|
||||
selectPlugin,
|
||||
setStaticView,
|
||||
starPlugin,
|
||||
},
|
||||
)(PluginContainer);
|
||||
14
desktop/src/UninitializedClient.tsx
Normal file
14
desktop/src/UninitializedClient.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export type UninitializedClient = {
|
||||
os: string;
|
||||
deviceName: string;
|
||||
appName: string;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`can create a Fake flipper 1`] = `
|
||||
Object {
|
||||
"androidEmulators": Array [],
|
||||
"clients": Array [
|
||||
Object {
|
||||
"id": "TestApp#Android#MockAndroidDevice#serial",
|
||||
"query": Object {
|
||||
"app": "TestApp",
|
||||
"device": "MockAndroidDevice",
|
||||
"device_id": "serial",
|
||||
"os": "Android",
|
||||
},
|
||||
},
|
||||
],
|
||||
"deepLinkPayload": null,
|
||||
"devices": Array [
|
||||
Object {
|
||||
"deviceType": "physical",
|
||||
"logs": Array [],
|
||||
"os": "Android",
|
||||
"serial": "serial",
|
||||
"title": "MockAndroidDevice",
|
||||
},
|
||||
],
|
||||
"errors": Array [],
|
||||
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||
"selectedDevice": Object {
|
||||
"deviceType": "physical",
|
||||
"logs": Array [],
|
||||
"os": "Android",
|
||||
"serial": "serial",
|
||||
"title": "MockAndroidDevice",
|
||||
},
|
||||
"selectedPlugin": "TestPlugin",
|
||||
"staticView": null,
|
||||
"uninitializedClients": Array [],
|
||||
"userPreferredApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||
"userPreferredDevice": "MockAndroidDevice",
|
||||
"userPreferredPlugin": "TestPlugin",
|
||||
"userStarredPlugins": Object {
|
||||
"TestApp": Array [
|
||||
"TestPlugin",
|
||||
],
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`can create a Fake flipper 2`] = `
|
||||
Object {
|
||||
"clientPlugins": Map {
|
||||
"TestPlugin" => [Function],
|
||||
},
|
||||
"devicePlugins": Map {},
|
||||
"disabledPlugins": Array [],
|
||||
"failedPlugins": Array [],
|
||||
"gatekeepedPlugins": Array [],
|
||||
"selectedPlugins": Array [],
|
||||
}
|
||||
`;
|
||||
63
desktop/src/__tests__/createMockFlipperWithPlugin.node.tsx
Normal file
63
desktop/src/__tests__/createMockFlipperWithPlugin.node.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 {createMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin';
|
||||
import {FlipperPlugin} from '../plugin';
|
||||
|
||||
interface PersistedState {
|
||||
count: 1;
|
||||
}
|
||||
|
||||
class TestPlugin extends FlipperPlugin<any, any, any> {
|
||||
static id = 'TestPlugin';
|
||||
|
||||
static defaultPersistedState = {
|
||||
count: 0,
|
||||
};
|
||||
|
||||
static persistedStateReducer(
|
||||
persistedState: PersistedState,
|
||||
method: string,
|
||||
_payload: {},
|
||||
) {
|
||||
if (method === 'inc') {
|
||||
return Object.assign({}, persistedState, {
|
||||
count: persistedState.count + 1,
|
||||
});
|
||||
}
|
||||
return persistedState;
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
test('can create a Fake flipper', async () => {
|
||||
await createMockFlipperWithPlugin(
|
||||
TestPlugin,
|
||||
async ({client, device, store, sendMessage}) => {
|
||||
expect(client).toBeTruthy();
|
||||
expect(device).toBeTruthy();
|
||||
expect(store).toBeTruthy();
|
||||
expect(sendMessage).toBeTruthy();
|
||||
expect(client.plugins.includes(TestPlugin.id)).toBe(true);
|
||||
expect(store.getState().connections).toMatchSnapshot();
|
||||
expect(store.getState().plugins).toMatchSnapshot();
|
||||
sendMessage('inc', {});
|
||||
expect(store.getState().pluginStates).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
|
||||
"count": 1,
|
||||
},
|
||||
}
|
||||
`);
|
||||
},
|
||||
);
|
||||
});
|
||||
80
desktop/src/__tests__/createTablePlugin.node.tsx
Normal file
80
desktop/src/__tests__/createTablePlugin.node.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 {createTablePlugin} from '../createTablePlugin';
|
||||
import {FlipperPlugin} from '../plugin';
|
||||
import {List, Map as ImmutableMap} from 'immutable';
|
||||
import {TableRows_immutable} from '../ui/components/table/types.js';
|
||||
|
||||
const PROPS = {
|
||||
method: 'method',
|
||||
resetMethod: 'resetMethod',
|
||||
columns: {},
|
||||
columnSizes: {},
|
||||
renderSidebar: () => {},
|
||||
buildRow: () => {},
|
||||
};
|
||||
|
||||
type PersistedState<T> = {
|
||||
rows: TableRows_immutable;
|
||||
datas: ImmutableMap<string, T>;
|
||||
};
|
||||
|
||||
type RowData = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
test('createTablePlugin returns FlipperPlugin', () => {
|
||||
const tablePlugin = createTablePlugin({...PROPS});
|
||||
expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin);
|
||||
});
|
||||
|
||||
test('persistedStateReducer is resetting data', () => {
|
||||
const resetMethod = 'resetMethod';
|
||||
const tablePlugin = createTablePlugin<RowData>({...PROPS, resetMethod});
|
||||
|
||||
const ps: PersistedState<RowData> = {
|
||||
datas: ImmutableMap({'1': {id: '1'}}),
|
||||
rows: List([
|
||||
{
|
||||
key: '1',
|
||||
columns: {
|
||||
id: {
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
const {rows, datas} = tablePlugin.persistedStateReducer(ps, resetMethod, {
|
||||
id: '0',
|
||||
});
|
||||
|
||||
expect(datas!.toJSON()).toEqual({});
|
||||
expect(rows!.size).toBe(0);
|
||||
});
|
||||
|
||||
test('persistedStateReducer is adding data', () => {
|
||||
const method = 'method';
|
||||
const tablePlugin = createTablePlugin({...PROPS, method});
|
||||
const id = '1';
|
||||
|
||||
const {
|
||||
rows,
|
||||
datas,
|
||||
} = tablePlugin.persistedStateReducer(
|
||||
tablePlugin.defaultPersistedState,
|
||||
method,
|
||||
{id},
|
||||
);
|
||||
|
||||
expect(rows!.size).toBe(1);
|
||||
expect([...datas!.keys()]).toEqual([id]);
|
||||
});
|
||||
105
desktop/src/chrome/AutoUpdateVersion.tsx
Normal file
105
desktop/src/chrome/AutoUpdateVersion.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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 {FlexRow, colors, LoadingIndicator, Glyph, styled} from 'flipper';
|
||||
import {remote} from 'electron';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import React, {Component} from 'react';
|
||||
import config from '../fb-stubs/config';
|
||||
|
||||
const Container = styled(FlexRow)({
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
type State = {
|
||||
updater:
|
||||
| 'error'
|
||||
| 'checking-for-update'
|
||||
| 'update-available'
|
||||
| 'update-not-available'
|
||||
| 'update-downloaded';
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
version: string;
|
||||
};
|
||||
|
||||
export default class AutoUpdateVersion extends Component<Props, State> {
|
||||
state: State = {
|
||||
updater: 'update-not-available',
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (isProduction()) {
|
||||
// this will fail, if the app is not code signed
|
||||
try {
|
||||
remote.autoUpdater.setFeedURL({
|
||||
url: `${config.updateServer}?version=${this.props.version}`,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
remote.autoUpdater.on('update-downloaded', () => {
|
||||
this.setState({updater: 'update-downloaded'});
|
||||
|
||||
const notification = new Notification('Update available', {
|
||||
body: 'Restart Flipper to update to the latest version.',
|
||||
requireInteraction: true,
|
||||
});
|
||||
notification.onclick = remote.autoUpdater.quitAndInstall;
|
||||
});
|
||||
|
||||
remote.autoUpdater.on('error', error => {
|
||||
this.setState({updater: 'error', error: error.toString()});
|
||||
});
|
||||
|
||||
remote.autoUpdater.on('checking-for-update', () => {
|
||||
this.setState({updater: 'checking-for-update'});
|
||||
});
|
||||
|
||||
remote.autoUpdater.on('update-available', () => {
|
||||
this.setState({updater: 'update-available'});
|
||||
});
|
||||
|
||||
remote.autoUpdater.on('update-not-available', () => {
|
||||
this.setState({updater: 'update-not-available'});
|
||||
});
|
||||
|
||||
remote.autoUpdater.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
{this.state.updater === 'update-available' && (
|
||||
<span title="Downloading new version">
|
||||
<LoadingIndicator size={16} />
|
||||
</span>
|
||||
)}
|
||||
{this.state.updater === 'error' && (
|
||||
<span title={`Error fetching update: ${this.state.error || ''}`}>
|
||||
<Glyph color={colors.light30} name="caution-triangle" />
|
||||
</span>
|
||||
)}
|
||||
{this.state.updater === 'update-downloaded' && (
|
||||
<span
|
||||
tabIndex={-1}
|
||||
role="button"
|
||||
title="Update available. Restart Flipper."
|
||||
onClick={remote.autoUpdater.quitAndInstall}>
|
||||
<Glyph color={colors.light30} name="breaking-news" />
|
||||
</span>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
323
desktop/src/chrome/BugReporterDialog.tsx
Normal file
323
desktop/src/chrome/BugReporterDialog.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* 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 BugReporter from '../fb-stubs/BugReporter';
|
||||
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin';
|
||||
import React, {Fragment, Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
Button,
|
||||
colors,
|
||||
Link,
|
||||
Input,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
FlexCenter,
|
||||
Textarea,
|
||||
Text,
|
||||
Glyph,
|
||||
styled,
|
||||
} from 'flipper';
|
||||
import {State as Store} from '../reducers';
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 10,
|
||||
width: 400,
|
||||
height: 300,
|
||||
});
|
||||
|
||||
const Icon = styled(Glyph)({
|
||||
marginRight: 8,
|
||||
marginLeft: 3,
|
||||
});
|
||||
|
||||
const Center = styled(Text)({
|
||||
textAlign: 'center',
|
||||
lineHeight: '130%',
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
});
|
||||
|
||||
const Title = styled.div({
|
||||
fontWeight: 500,
|
||||
marginTop: 8,
|
||||
marginLeft: 2,
|
||||
marginBottom: 8,
|
||||
});
|
||||
|
||||
const textareaStyle = {
|
||||
margin: 0,
|
||||
marginBottom: 10,
|
||||
};
|
||||
|
||||
const TitleInput = styled(Input)({
|
||||
...textareaStyle,
|
||||
height: 30,
|
||||
});
|
||||
|
||||
const DescriptionTextarea = styled(Textarea)({
|
||||
...textareaStyle,
|
||||
flexGrow: 1,
|
||||
});
|
||||
|
||||
const SubmitButtonContainer = styled.div({
|
||||
marginLeft: 'auto',
|
||||
});
|
||||
|
||||
const Footer = styled(FlexRow)({
|
||||
lineHeight: '24px',
|
||||
});
|
||||
|
||||
const CloseDoneButton = styled(Button)({
|
||||
marginTop: 20,
|
||||
marginLeft: 'auto !important',
|
||||
marginRight: 'auto',
|
||||
});
|
||||
|
||||
const InfoBox = styled(FlexRow)({
|
||||
marginBottom: 20,
|
||||
lineHeight: '130%',
|
||||
});
|
||||
|
||||
type State = {
|
||||
description: string;
|
||||
title: string;
|
||||
submitting: boolean;
|
||||
success: number | null | undefined;
|
||||
error: string | null | undefined;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
bugReporter: BugReporter;
|
||||
onHide: () => any;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {};
|
||||
|
||||
type StateFromProps = {
|
||||
activePlugin:
|
||||
| typeof FlipperPlugin
|
||||
| typeof FlipperDevicePlugin
|
||||
| null
|
||||
| undefined;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
class BugReporterDialog extends Component<Props, State> {
|
||||
state = {
|
||||
description: '',
|
||||
title: '',
|
||||
submitting: false,
|
||||
success: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
titleRef?: HTMLElement | null;
|
||||
descriptionRef?: HTMLElement | null;
|
||||
|
||||
onDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
this.setState({description: e.target.value});
|
||||
};
|
||||
|
||||
onTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({title: e.target.value});
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
// validate fields
|
||||
const {title, description} = this.state;
|
||||
if (!title) {
|
||||
this.setState({
|
||||
error: 'Title required.',
|
||||
});
|
||||
if (this.titleRef) {
|
||||
this.titleRef.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!description) {
|
||||
this.setState({
|
||||
error: 'Description required.',
|
||||
});
|
||||
if (this.descriptionRef) {
|
||||
this.descriptionRef.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
error: null,
|
||||
submitting: true,
|
||||
},
|
||||
() => {
|
||||
// this will be called before the next repaint
|
||||
requestAnimationFrame(() => {
|
||||
// we have to call this again to ensure a repaint has actually happened
|
||||
// as requestAnimationFrame is called BEFORE a repaint, not after which
|
||||
// means we have to queue up twice to actually ensure a repaint has
|
||||
// happened
|
||||
requestAnimationFrame(() => {
|
||||
this.props.bugReporter
|
||||
.report(title, description)
|
||||
.then((id: number) => {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
success: id,
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
error: err.message,
|
||||
submitting: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
setTitleRef = (ref: HTMLElement | null) => {
|
||||
this.titleRef = ref;
|
||||
};
|
||||
|
||||
setDescriptionRef = (ref: HTMLElement | null) => {
|
||||
this.descriptionRef = ref;
|
||||
};
|
||||
|
||||
onCancel = () => {
|
||||
this.setState({
|
||||
error: null,
|
||||
title: '',
|
||||
description: '',
|
||||
});
|
||||
this.props.onHide();
|
||||
};
|
||||
|
||||
render() {
|
||||
let content;
|
||||
const {title, success, error, description, submitting} = this.state;
|
||||
const {activePlugin} = this.props;
|
||||
|
||||
if (success) {
|
||||
content = (
|
||||
<FlexCenter grow={true}>
|
||||
<FlexColumn>
|
||||
<Center>
|
||||
<Glyph
|
||||
name="checkmark-circle"
|
||||
size={24}
|
||||
variant="outline"
|
||||
color={colors.light30}
|
||||
/>
|
||||
<br />
|
||||
<Title>Bug Report created</Title>
|
||||
The bug report{' '}
|
||||
<Link
|
||||
href={`https://our.intern.facebook.com/intern/bug/${success}`}>
|
||||
{success}
|
||||
</Link>{' '}
|
||||
was successfully created. Thank you for your help making Flipper
|
||||
better!
|
||||
</Center>
|
||||
<CloseDoneButton onClick={this.onCancel} compact type="primary">
|
||||
Close
|
||||
</CloseDoneButton>
|
||||
</FlexColumn>
|
||||
</FlexCenter>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Fragment>
|
||||
<Title>Report a bug in Flipper</Title>
|
||||
<TitleInput
|
||||
placeholder="Title"
|
||||
value={title}
|
||||
ref={this.setTitleRef}
|
||||
onChange={this.onTitleChange}
|
||||
disabled={submitting}
|
||||
/>
|
||||
|
||||
<DescriptionTextarea
|
||||
placeholder="Describe your problem in as much detail as possible."
|
||||
value={description}
|
||||
ref={this.setDescriptionRef}
|
||||
onChange={this.onDescriptionChange}
|
||||
disabled={submitting}
|
||||
/>
|
||||
{activePlugin && activePlugin.bugs && (
|
||||
<InfoBox>
|
||||
<Icon color={colors.light50} name="info-circle" />
|
||||
<span>
|
||||
If your bug is related to the{' '}
|
||||
<strong>
|
||||
{(activePlugin && activePlugin.title) || activePlugin.id}{' '}
|
||||
plugin
|
||||
</strong>
|
||||
{activePlugin && activePlugin.bugs && activePlugin.bugs.url && (
|
||||
<span>
|
||||
, you might find useful information about it here:{' '}
|
||||
<Link href={activePlugin.bugs.url || ''}>
|
||||
{activePlugin.bugs.url}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
{activePlugin && activePlugin.bugs && activePlugin.bugs.email && (
|
||||
<span>
|
||||
, you might also want contact{' '}
|
||||
<Link href={'mailto:' + String(activePlugin.bugs.email)}>
|
||||
{activePlugin.bugs.email}
|
||||
</Link>
|
||||
, the author/oncall of this plugin, directly
|
||||
</span>
|
||||
)}
|
||||
.
|
||||
</span>
|
||||
</InfoBox>
|
||||
)}
|
||||
|
||||
<Footer>
|
||||
{error != null && <Text color={colors.red}>{error}</Text>}
|
||||
<SubmitButtonContainer>
|
||||
<Button
|
||||
onClick={this.onCancel}
|
||||
disabled={submitting}
|
||||
compact
|
||||
padded>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={this.onSubmit}
|
||||
disabled={submitting}
|
||||
compact
|
||||
padded>
|
||||
Submit Report
|
||||
</Button>
|
||||
</SubmitButtonContainer>
|
||||
</Footer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <Container>{content}</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({
|
||||
plugins: {devicePlugins, clientPlugins},
|
||||
connections: {selectedPlugin},
|
||||
}) => ({
|
||||
activePlugin: selectedPlugin
|
||||
? devicePlugins.get(selectedPlugin) || clientPlugins.get(selectedPlugin)
|
||||
: null,
|
||||
}),
|
||||
)(BugReporterDialog);
|
||||
39
desktop/src/chrome/CancellableExportStatus.tsx
Normal file
39
desktop/src/chrome/CancellableExportStatus.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 {FlexRow, Button} from '../ui/index';
|
||||
import {styled, LoadingIndicator, Text} from 'flipper';
|
||||
import React, {Component} from 'react';
|
||||
import {colors} from '../ui/components/colors';
|
||||
|
||||
const Wrapper = styled(FlexRow)({
|
||||
color: colors.light50,
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
});
|
||||
|
||||
type Props = {
|
||||
msg: string;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
export default class CancellableExportStatus extends Component<Props> {
|
||||
render() {
|
||||
const {msg, onCancel} = this.props;
|
||||
return (
|
||||
<Wrapper>
|
||||
<LoadingIndicator size={16} />
|
||||
|
||||
<Text>{msg}</Text>
|
||||
|
||||
<Button onClick={onCancel}> Cancel </Button>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
77
desktop/src/chrome/DetailSidebar.tsx
Normal file
77
desktop/src/chrome/DetailSidebar.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Sidebar from '../ui/components/Sidebar';
|
||||
import {connect} from 'react-redux';
|
||||
import {toggleRightSidebarAvailable} from '../reducers/application';
|
||||
|
||||
type OwnProps = {
|
||||
children: any;
|
||||
width?: number;
|
||||
minWidth?: number;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
rightSidebarVisible: boolean;
|
||||
rightSidebarAvailable: boolean;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
toggleRightSidebarAvailable: (visible?: boolean) => any;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
class DetailSidebar extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.updateSidebarAvailablility();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateSidebarAvailablility();
|
||||
}
|
||||
|
||||
updateSidebarAvailablility() {
|
||||
const available = Boolean(this.props.children);
|
||||
if (available !== this.props.rightSidebarAvailable) {
|
||||
this.props.toggleRightSidebarAvailable(available);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const domNode = document.getElementById('detailsSidebar');
|
||||
return (
|
||||
this.props.children &&
|
||||
this.props.rightSidebarVisible &&
|
||||
domNode &&
|
||||
ReactDOM.createPortal(
|
||||
<Sidebar
|
||||
minWidth={this.props.minWidth}
|
||||
width={this.props.width || 300}
|
||||
position="right">
|
||||
{this.props.children}
|
||||
</Sidebar>,
|
||||
domNode,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: TS_MIGRATION
|
||||
type Store = any;
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({application: {rightSidebarVisible, rightSidebarAvailable}}) => ({
|
||||
rightSidebarVisible,
|
||||
rightSidebarAvailable,
|
||||
}),
|
||||
{
|
||||
toggleRightSidebarAvailable,
|
||||
},
|
||||
)(DetailSidebar);
|
||||
217
desktop/src/chrome/DevicesButton.tsx
Normal file
217
desktop/src/chrome/DevicesButton.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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 {Button, styled} from 'flipper';
|
||||
import {connect, ReactReduxContext} from 'react-redux';
|
||||
import {spawn} from 'child_process';
|
||||
import {dirname} from 'path';
|
||||
import {selectDevice, preferDevice} from '../reducers/connections';
|
||||
import {
|
||||
setActiveSheet,
|
||||
ActiveSheet,
|
||||
ACTIVE_SHEET_JS_EMULATOR_LAUNCHER,
|
||||
} from '../reducers/application';
|
||||
import {default as which} from 'which';
|
||||
import {showOpenDialog} from '../utils/exportData';
|
||||
import BaseDevice from '../devices/BaseDevice';
|
||||
import React, {Component} from 'react';
|
||||
import {State} from '../reducers';
|
||||
import GK from '../fb-stubs/GK';
|
||||
|
||||
type StateFromProps = {
|
||||
selectedDevice: BaseDevice | null | undefined;
|
||||
androidEmulators: Array<string>;
|
||||
devices: Array<BaseDevice>;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
selectDevice: (device: BaseDevice) => void;
|
||||
preferDevice: (device: string) => void;
|
||||
setActiveSheet: (sheet: ActiveSheet) => void;
|
||||
};
|
||||
|
||||
type OwnProps = {};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
const DropdownButton = styled(Button)({
|
||||
fontSize: 11,
|
||||
});
|
||||
|
||||
class DevicesButton extends Component<Props> {
|
||||
launchEmulator = (name: string) => {
|
||||
// On Linux, you must run the emulator from the directory it's in because
|
||||
// reasons ...
|
||||
which('emulator')
|
||||
.then(emulatorPath => {
|
||||
if (emulatorPath) {
|
||||
const child = spawn(emulatorPath, [`@${name}`], {
|
||||
detached: true,
|
||||
cwd: dirname(emulatorPath),
|
||||
});
|
||||
child.stderr.on('data', data => {
|
||||
console.error(`Android emulator error: ${data}`);
|
||||
});
|
||||
child.on('error', console.error);
|
||||
} else {
|
||||
throw new Error('Could not get emulator path');
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
this.props.preferDevice(name);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
devices,
|
||||
androidEmulators,
|
||||
selectedDevice,
|
||||
selectDevice,
|
||||
} = this.props;
|
||||
let buttonLabel = 'No device selected';
|
||||
let icon = 'minus-circle';
|
||||
|
||||
if (selectedDevice && selectedDevice.isArchived) {
|
||||
buttonLabel = `${selectedDevice.displayTitle() || 'Unknown device'}`;
|
||||
icon = 'box';
|
||||
} else if (selectedDevice && selectedDevice.deviceType === 'physical') {
|
||||
buttonLabel = selectedDevice.displayTitle() || 'Unknown device';
|
||||
icon = 'mobile';
|
||||
} else if (selectedDevice && selectedDevice.deviceType === 'emulator') {
|
||||
buttonLabel = selectedDevice.displayTitle() || 'Unknown emulator';
|
||||
icon = 'desktop';
|
||||
}
|
||||
|
||||
const dropdown: any[] = [];
|
||||
|
||||
// Physical devices
|
||||
const connectedDevices = [
|
||||
{
|
||||
label: 'Connected Devices',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter(device => device.deviceType === 'physical')
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: `📱 ${device.displayTitle()}`,
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (connectedDevices.length > 1) {
|
||||
dropdown.push(...connectedDevices);
|
||||
}
|
||||
// Emulators
|
||||
const runningEmulators = [
|
||||
{
|
||||
label: 'Running Emulators',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter(device => device.deviceType === 'emulator')
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: device.displayTitle(),
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (runningEmulators.length > 1) {
|
||||
dropdown.push(...runningEmulators);
|
||||
}
|
||||
// Archived
|
||||
const importedFiles = [
|
||||
{
|
||||
label: 'Disconnected Devices',
|
||||
enabled: false,
|
||||
},
|
||||
...devices
|
||||
.filter(device => device.isArchived)
|
||||
.map((device: BaseDevice) => ({
|
||||
click: () => selectDevice(device),
|
||||
checked: device === selectedDevice,
|
||||
label: `📦 ${device.displayTitle()}`,
|
||||
type: 'checkbox',
|
||||
})),
|
||||
];
|
||||
if (importedFiles.length > 1) {
|
||||
dropdown.push(...importedFiles);
|
||||
}
|
||||
// Launch JS emulator
|
||||
if (GK.get('flipper_js_client_emulator')) {
|
||||
dropdown.push(
|
||||
{type: 'separator' as 'separator'},
|
||||
{
|
||||
label: 'Launch JS Web App',
|
||||
click: () =>
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_JS_EMULATOR_LAUNCHER),
|
||||
},
|
||||
);
|
||||
}
|
||||
// Launch Android emulators
|
||||
if (androidEmulators.length > 0) {
|
||||
const emulators = Array.from(androidEmulators)
|
||||
.filter(
|
||||
(name: string) =>
|
||||
devices.findIndex(
|
||||
(device: BaseDevice) =>
|
||||
device.title === name && !device.isArchived,
|
||||
) === -1,
|
||||
)
|
||||
.map((name: string) => ({
|
||||
label: name,
|
||||
click: () => this.launchEmulator(name),
|
||||
}));
|
||||
|
||||
if (emulators.length > 0) {
|
||||
dropdown.push(
|
||||
{type: 'separator' as 'separator'},
|
||||
{
|
||||
label: 'Launch Android emulators',
|
||||
enabled: false,
|
||||
},
|
||||
...emulators,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dropdown.length > 0) {
|
||||
dropdown.push({type: 'separator' as 'separator'});
|
||||
}
|
||||
return (
|
||||
<ReactReduxContext.Consumer>
|
||||
{({store}) => {
|
||||
dropdown.push({
|
||||
label: 'Open File...',
|
||||
click: () => {
|
||||
showOpenDialog(store);
|
||||
},
|
||||
});
|
||||
return (
|
||||
<DropdownButton compact={true} icon={icon} dropdown={dropdown}>
|
||||
{buttonLabel}
|
||||
</DropdownButton>
|
||||
);
|
||||
}}
|
||||
</ReactReduxContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, State>(
|
||||
({connections: {devices, androidEmulators, selectedDevice}}) => ({
|
||||
devices,
|
||||
androidEmulators,
|
||||
selectedDevice,
|
||||
}),
|
||||
{
|
||||
selectDevice,
|
||||
preferDevice,
|
||||
setActiveSheet,
|
||||
},
|
||||
)(DevicesButton);
|
||||
189
desktop/src/chrome/DoctorBar.tsx
Normal file
189
desktop/src/chrome/DoctorBar.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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 {styled, colors} from 'flipper';
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
setActiveSheet,
|
||||
ActiveSheet,
|
||||
ACTIVE_SHEET_DOCTOR,
|
||||
ACTIVE_SHEET_SETTINGS,
|
||||
} from '../reducers/application';
|
||||
import {State as Store} from '../reducers/index';
|
||||
import {ButtonGroup, Button} from 'flipper';
|
||||
import {FlexColumn, FlexRow} from 'flipper';
|
||||
import runHealthchecks, {
|
||||
HealthcheckSettings,
|
||||
HealthcheckEventsHandler,
|
||||
} from '../utils/runHealthchecks';
|
||||
import {
|
||||
updateHealthcheckResult,
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
HealthcheckReport,
|
||||
HealthcheckResult,
|
||||
} from '../reducers/healthchecks';
|
||||
|
||||
import {reportUsage} from '../utils/metrics';
|
||||
|
||||
type StateFromProps = {
|
||||
healthcheckReport: HealthcheckReport;
|
||||
} & HealthcheckSettings;
|
||||
|
||||
type DispatchFromProps = {
|
||||
setActiveSheet: (payload: ActiveSheet) => void;
|
||||
} & HealthcheckEventsHandler;
|
||||
|
||||
type State = {visible: boolean; message: string; showSettingsButton: boolean};
|
||||
|
||||
type Props = DispatchFromProps & StateFromProps;
|
||||
class DoctorBar extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
message: '',
|
||||
showSettingsButton: false,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.showMessageIfChecksFailed();
|
||||
}
|
||||
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||
const failedCategories = Object.values(
|
||||
props.healthcheckReport.categories,
|
||||
).filter(cat => hasProblems(cat.result));
|
||||
if (failedCategories.length == 1) {
|
||||
const failedCat = failedCategories[0];
|
||||
if (failedCat.key === 'ios' || failedCat.key === 'android') {
|
||||
return {
|
||||
...state,
|
||||
message: `Doctor has discovered problems with your ${failedCat.label} setup. If you are not interested in ${failedCat.label} development you can disable it in Settings.`,
|
||||
showSettingsButton: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (failedCategories.length) {
|
||||
return {
|
||||
...state,
|
||||
message: 'Doctor has discovered problems with your installation.',
|
||||
showSettingsButton: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async showMessageIfChecksFailed() {
|
||||
await runHealthchecks(this.props);
|
||||
const result = this.props.healthcheckReport.result;
|
||||
if (hasProblems(result)) {
|
||||
if (result.isAcknowledged) {
|
||||
reportUsage('doctor:warning:suppressed');
|
||||
} else {
|
||||
this.setVisible(true);
|
||||
reportUsage('doctor:warning:shown');
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
this.state.visible && (
|
||||
<Container>
|
||||
<WarningContainer>
|
||||
<FlexRow style={{flexDirection: 'row-reverse'}}>
|
||||
<ButtonSection>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => {
|
||||
reportUsage('doctor:report:opened:fromWarningBar');
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_DOCTOR);
|
||||
this.setVisible(false);
|
||||
}}>
|
||||
Show Problems
|
||||
</Button>
|
||||
{this.state.showSettingsButton && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
reportUsage('settings:opened:fromWarningBar');
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_SETTINGS);
|
||||
}}>
|
||||
Show Settings
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => this.setVisible(false)}>
|
||||
Dismiss
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonSection>
|
||||
<FlexColumn style={{flexGrow: 1}}>
|
||||
{this.state.message}
|
||||
</FlexColumn>
|
||||
</FlexRow>
|
||||
</WarningContainer>
|
||||
</Container>
|
||||
)
|
||||
);
|
||||
}
|
||||
setVisible(visible: boolean) {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
...prevState,
|
||||
visible,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, {}, Store>(
|
||||
({
|
||||
settingsState: {enableAndroid, enableIOS},
|
||||
healthchecks: {healthcheckReport},
|
||||
}) => ({
|
||||
enableAndroid,
|
||||
enableIOS,
|
||||
healthcheckReport,
|
||||
}),
|
||||
{
|
||||
setActiveSheet,
|
||||
updateHealthcheckResult,
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
},
|
||||
)(DoctorBar);
|
||||
|
||||
const Container = styled.div({
|
||||
boxShadow: '2px 2px 2px #ccc',
|
||||
userSelect: 'text',
|
||||
});
|
||||
|
||||
const WarningContainer = styled.div({
|
||||
backgroundColor: colors.orange,
|
||||
color: '#fff',
|
||||
maxHeight: '600px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
transition: 'max-height 0.3s ease',
|
||||
'&.collapsed': {
|
||||
maxHeight: '0px',
|
||||
},
|
||||
padding: '4px 12px',
|
||||
borderBottom: '1px solid ' + colors.orangeDark3,
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '28px',
|
||||
});
|
||||
|
||||
const ButtonSection = styled(FlexColumn)({
|
||||
marginLeft: '8px',
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
});
|
||||
|
||||
function hasProblems(result: HealthcheckResult) {
|
||||
return result.status === 'WARNING' || result.status === 'FAILED';
|
||||
}
|
||||
422
desktop/src/chrome/DoctorSheet.tsx
Normal file
422
desktop/src/chrome/DoctorSheet.tsx
Normal file
@@ -0,0 +1,422 @@
|
||||
/**
|
||||
* 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 {
|
||||
FlexColumn,
|
||||
styled,
|
||||
Text,
|
||||
FlexRow,
|
||||
Glyph,
|
||||
LoadingIndicator,
|
||||
colors,
|
||||
Spacer,
|
||||
Button,
|
||||
FlexBox,
|
||||
Checkbox,
|
||||
} from 'flipper';
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {State as Store} from '../reducers';
|
||||
import {
|
||||
HealthcheckResult,
|
||||
HealthcheckReportCategory,
|
||||
HealthcheckReport,
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
updateHealthcheckResult,
|
||||
acknowledgeProblems,
|
||||
resetAcknowledgedProblems,
|
||||
} from '../reducers/healthchecks';
|
||||
import runHealthchecks, {
|
||||
HealthcheckSettings,
|
||||
HealthcheckEventsHandler,
|
||||
} from '../utils/runHealthchecks';
|
||||
import {shell} from 'electron';
|
||||
import {reportUsage} from '../utils/metrics';
|
||||
|
||||
type StateFromProps = {
|
||||
healthcheckReport: HealthcheckReport;
|
||||
} & HealthcheckSettings;
|
||||
|
||||
type DispatchFromProps = {
|
||||
acknowledgeProblems: () => void;
|
||||
resetAcknowledgedProblems: () => void;
|
||||
} & HealthcheckEventsHandler;
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
padding: 20,
|
||||
width: 600,
|
||||
});
|
||||
|
||||
const HealthcheckDisplayContainer = styled(FlexRow)({
|
||||
alignItems: 'center',
|
||||
marginBottom: 5,
|
||||
});
|
||||
|
||||
const HealthcheckListContainer = styled(FlexColumn)({
|
||||
marginBottom: 20,
|
||||
width: 300,
|
||||
});
|
||||
|
||||
const Title = styled(Text)({
|
||||
marginBottom: 18,
|
||||
marginRight: 10,
|
||||
fontWeight: 100,
|
||||
fontSize: '40px',
|
||||
});
|
||||
|
||||
const CategoryContainer = styled(FlexColumn)({
|
||||
marginBottom: 5,
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
});
|
||||
|
||||
const SideContainer = styled(FlexBox)({
|
||||
marginBottom: 20,
|
||||
padding: 20,
|
||||
backgroundColor: colors.highlightBackground,
|
||||
border: '1px solid #b3b3b3',
|
||||
width: 250,
|
||||
});
|
||||
|
||||
const SideContainerText = styled(Text)({
|
||||
display: 'block',
|
||||
wordWrap: 'break-word',
|
||||
overflow: 'auto',
|
||||
});
|
||||
|
||||
const HealthcheckLabel = styled(Text)({
|
||||
paddingLeft: 5,
|
||||
});
|
||||
|
||||
const SkipReasonLabel = styled(Text)({
|
||||
paddingLeft: 21,
|
||||
fontStyle: 'italic',
|
||||
});
|
||||
|
||||
const CenteredContainer = styled.label({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
type OwnProps = {
|
||||
onHide: () => void;
|
||||
};
|
||||
|
||||
function CenteredCheckbox(props: {
|
||||
checked: boolean;
|
||||
text: string;
|
||||
onChange: (checked: boolean) => void;
|
||||
}) {
|
||||
const {checked, onChange, text} = props;
|
||||
return (
|
||||
<CenteredContainer>
|
||||
<Checkbox checked={checked} onChange={onChange} />
|
||||
{text}
|
||||
</CenteredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function HealthcheckIcon(props: {checkResult: HealthcheckResult}) {
|
||||
const {checkResult: check} = props;
|
||||
switch (props.checkResult.status) {
|
||||
case 'IN_PROGRESS':
|
||||
return <LoadingIndicator size={16} title={props.checkResult.message} />;
|
||||
case 'SKIPPED':
|
||||
return (
|
||||
<Glyph
|
||||
size={16}
|
||||
name={'question'}
|
||||
color={colors.grey}
|
||||
title={props.checkResult.message}
|
||||
/>
|
||||
);
|
||||
case 'SUCCESS':
|
||||
return (
|
||||
<Glyph
|
||||
size={16}
|
||||
name={'checkmark'}
|
||||
color={colors.green}
|
||||
title={props.checkResult.message}
|
||||
/>
|
||||
);
|
||||
case 'FAILED':
|
||||
return (
|
||||
<Glyph
|
||||
size={16}
|
||||
name={'cross'}
|
||||
color={colors.red}
|
||||
title={props.checkResult.message}
|
||||
variant={check.isAcknowledged ? 'outline' : 'filled'}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Glyph
|
||||
size={16}
|
||||
name={'caution'}
|
||||
color={colors.yellow}
|
||||
title={props.checkResult.message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function HealthcheckDisplay(props: {
|
||||
label: string;
|
||||
result: HealthcheckResult;
|
||||
selected?: boolean;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<FlexColumn shrink>
|
||||
<HealthcheckDisplayContainer shrink title={props.result.message}>
|
||||
<HealthcheckIcon checkResult={props.result} />
|
||||
<HealthcheckLabel
|
||||
bold={props.selected}
|
||||
underline={!!props.onClick}
|
||||
cursor={props.onClick && 'pointer'}
|
||||
onClick={props.onClick}>
|
||||
{props.label}
|
||||
</HealthcheckLabel>
|
||||
</HealthcheckDisplayContainer>
|
||||
</FlexColumn>
|
||||
);
|
||||
}
|
||||
|
||||
function SideMessageDisplay(props: {children: React.ReactNode}) {
|
||||
return <SideContainerText selectable>{props.children}</SideContainerText>;
|
||||
}
|
||||
|
||||
function ResultMessage(props: {result: HealthcheckResult}) {
|
||||
if (status === 'IN_PROGRESS') {
|
||||
return <p>Doctor is running healthchecks...</p>;
|
||||
} else if (hasProblems(props.result)) {
|
||||
return (
|
||||
<p>
|
||||
Doctor has discovered problems with your installation. Please click to
|
||||
an item to get its details.
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<p>
|
||||
All good! Doctor has not discovered any issues with your installation.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function hasProblems(result: HealthcheckResult) {
|
||||
const {status} = result;
|
||||
return status === 'FAILED' || status === 'WARNING';
|
||||
}
|
||||
|
||||
function hasNewProblems(result: HealthcheckResult) {
|
||||
return hasProblems(result) && !result.isAcknowledged;
|
||||
}
|
||||
|
||||
export type State = {
|
||||
acknowledgeCheckboxVisible: boolean;
|
||||
acknowledgeOnClose?: boolean;
|
||||
selectedCheckKey?: string;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
class DoctorSheet extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
acknowledgeCheckboxVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
reportUsage('doctor:report:opened');
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||
if (
|
||||
!state.acknowledgeCheckboxVisible &&
|
||||
hasProblems(props.healthcheckReport.result)
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
acknowledgeCheckboxVisible: true,
|
||||
acknowledgeOnClose:
|
||||
state.acknowledgeOnClose === undefined
|
||||
? !hasNewProblems(props.healthcheckReport.result)
|
||||
: state.acknowledgeOnClose,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
state.acknowledgeCheckboxVisible &&
|
||||
!hasProblems(props.healthcheckReport.result)
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
acknowledgeCheckboxVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.state.acknowledgeOnClose) {
|
||||
if (hasNewProblems(this.props.healthcheckReport.result)) {
|
||||
reportUsage('doctor:report:closed:newProblems:acknowledged');
|
||||
}
|
||||
reportUsage('doctor:report:closed:acknowleged');
|
||||
this.props.acknowledgeProblems();
|
||||
} else {
|
||||
if (hasNewProblems(this.props.healthcheckReport.result)) {
|
||||
reportUsage('doctor:report:closed:newProblems:notAcknowledged');
|
||||
}
|
||||
reportUsage('doctor:report:closed:notAcknowledged');
|
||||
this.props.resetAcknowledgedProblems();
|
||||
}
|
||||
}
|
||||
|
||||
onAcknowledgeOnCloseChanged(acknowledge: boolean): void {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
...prevState,
|
||||
acknowledgeOnClose: acknowledge,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
openHelpUrl(helpUrl?: string): void {
|
||||
helpUrl && shell.openExternal(helpUrl);
|
||||
}
|
||||
|
||||
async runHealthchecks(): Promise<void> {
|
||||
await runHealthchecks(this.props);
|
||||
}
|
||||
|
||||
getCheckMessage(checkKey: string): string {
|
||||
for (const cat of Object.values(this.props.healthcheckReport.categories)) {
|
||||
const check = Object.values(cat.checks).find(chk => chk.key === checkKey);
|
||||
if (check) {
|
||||
return check.result.message || '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Doctor</Title>
|
||||
<FlexRow>
|
||||
<HealthcheckListContainer>
|
||||
{Object.values(this.props.healthcheckReport.categories).map(
|
||||
(category: HealthcheckReportCategory) => {
|
||||
return (
|
||||
<CategoryContainer key={category.key}>
|
||||
<HealthcheckDisplay
|
||||
label={category.label}
|
||||
result={category.result}
|
||||
/>
|
||||
{category.result.status !== 'SKIPPED' && (
|
||||
<CategoryContainer>
|
||||
{Object.values(category.checks).map(check => (
|
||||
<HealthcheckDisplay
|
||||
key={check.key}
|
||||
selected={check.key === this.state.selectedCheckKey}
|
||||
label={check.label}
|
||||
result={check.result}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
...this.state,
|
||||
selectedCheckKey:
|
||||
this.state.selectedCheckKey === check.key
|
||||
? undefined
|
||||
: check.key,
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</CategoryContainer>
|
||||
)}
|
||||
{category.result.status === 'SKIPPED' && (
|
||||
<CategoryContainer>
|
||||
<SkipReasonLabel>
|
||||
{category.result.message}
|
||||
</SkipReasonLabel>
|
||||
</CategoryContainer>
|
||||
)}
|
||||
</CategoryContainer>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</HealthcheckListContainer>
|
||||
<Spacer />
|
||||
<SideContainer shrink>
|
||||
<SideMessageDisplay>
|
||||
<SideContainerText selectable>
|
||||
{this.state.selectedCheckKey && (
|
||||
<p>{this.getCheckMessage(this.state.selectedCheckKey)}</p>
|
||||
)}
|
||||
{!this.state.selectedCheckKey && (
|
||||
<ResultMessage result={this.props.healthcheckReport.result} />
|
||||
)}
|
||||
</SideContainerText>
|
||||
</SideMessageDisplay>
|
||||
</SideContainer>
|
||||
</FlexRow>
|
||||
<FlexRow>
|
||||
<Spacer />
|
||||
{this.state.acknowledgeCheckboxVisible && (
|
||||
<CenteredCheckbox
|
||||
checked={!!this.state.acknowledgeOnClose}
|
||||
onChange={this.onAcknowledgeOnCloseChanged.bind(this)}
|
||||
text={
|
||||
'Do not show warning about these problems on Flipper startup'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Button compact padded onClick={this.props.onHide}>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
this.props.healthcheckReport.result.status === 'IN_PROGRESS'
|
||||
}
|
||||
type="primary"
|
||||
compact
|
||||
padded
|
||||
onClick={() => this.runHealthchecks()}>
|
||||
Re-run
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({
|
||||
healthchecks: {healthcheckReport},
|
||||
settingsState: {enableAndroid, enableIOS},
|
||||
}) => ({
|
||||
healthcheckReport,
|
||||
enableAndroid,
|
||||
enableIOS,
|
||||
}),
|
||||
{
|
||||
startHealthchecks,
|
||||
finishHealthchecks,
|
||||
updateHealthcheckResult,
|
||||
acknowledgeProblems,
|
||||
resetAcknowledgedProblems,
|
||||
},
|
||||
)(DoctorSheet);
|
||||
200
desktop/src/chrome/ErrorBar.tsx
Normal file
200
desktop/src/chrome/ErrorBar.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* 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 {styled, colors, Glyph} from 'flipper';
|
||||
import React, {useState, memo} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {FlipperError, dismissError} from '../reducers/connections';
|
||||
import {State as Store} from '../reducers/index';
|
||||
import {ErrorBlock, ButtonGroup, Button} from 'flipper';
|
||||
import {FlexColumn, FlexRow} from 'flipper';
|
||||
|
||||
type StateFromProps = {
|
||||
errors: FlipperError[];
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
dismissError: typeof dismissError;
|
||||
};
|
||||
|
||||
type Props = DispatchFromProps & StateFromProps;
|
||||
|
||||
const ErrorBar = memo(function ErrorBar(props: Props) {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
if (!props.errors.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorCount = props.errors.reduce(
|
||||
(sum, error) => sum + (error.occurrences || 1),
|
||||
0,
|
||||
);
|
||||
|
||||
const urgentErrors = props.errors.filter(e => e.urgent);
|
||||
|
||||
return (
|
||||
<ErrorBarContainer>
|
||||
<ErrorRows
|
||||
className={collapsed && urgentErrors.length === 0 ? 'collapsed' : ''}>
|
||||
{(collapsed ? urgentErrors : props.errors).map((error, index) => (
|
||||
<ErrorTile
|
||||
onDismiss={() => props.dismissError(index)}
|
||||
key={index}
|
||||
error={error}
|
||||
/>
|
||||
))}
|
||||
</ErrorRows>
|
||||
<DismissAllErrors
|
||||
onClick={() => setCollapsed(c => !c)}
|
||||
title="Show / hide errors">
|
||||
<Glyph
|
||||
color={colors.white}
|
||||
size={8}
|
||||
name={collapsed ? 'chevron-down' : 'chevron-up'}
|
||||
style={{marginRight: 4}}
|
||||
/>
|
||||
{collapsed && errorCount}
|
||||
</DismissAllErrors>
|
||||
</ErrorBarContainer>
|
||||
);
|
||||
});
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, {}, Store>(
|
||||
({connections: {errors}}) => ({
|
||||
errors,
|
||||
}),
|
||||
{
|
||||
dismissError,
|
||||
},
|
||||
)(ErrorBar);
|
||||
|
||||
function ErrorTile({
|
||||
onDismiss,
|
||||
error,
|
||||
}: {
|
||||
onDismiss: () => void;
|
||||
error: FlipperError;
|
||||
}) {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
return (
|
||||
<ErrorRow className={`${error.urgent ? 'urgent' : ''}`}>
|
||||
<FlexRow style={{flexDirection: 'row-reverse'}}>
|
||||
<ButtonSection>
|
||||
<ButtonGroup>
|
||||
{(error.details || error.error) && (
|
||||
<Button
|
||||
onClick={() => setCollapsed(s => !s)}
|
||||
icon={collapsed ? `chevron-down` : 'chevron-up'}
|
||||
iconSize={12}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={onDismiss} icon="cross-circle" iconSize={12} />
|
||||
</ButtonGroup>
|
||||
</ButtonSection>
|
||||
{error.occurrences! > 1 && (
|
||||
<ErrorCounter title="Nr of times this error occurred">
|
||||
{error.occurrences}
|
||||
</ErrorCounter>
|
||||
)}
|
||||
<FlexColumn
|
||||
style={
|
||||
collapsed
|
||||
? {overflow: 'hidden', whiteSpace: 'nowrap', flexGrow: 1}
|
||||
: {flexGrow: 1}
|
||||
}
|
||||
title={error.message}>
|
||||
{error.message}
|
||||
</FlexColumn>
|
||||
</FlexRow>
|
||||
{!collapsed && (
|
||||
<FlexRow>
|
||||
<ErrorDetails>
|
||||
{error.details}
|
||||
{error.error && <ErrorBlock error={error.error} />}
|
||||
</ErrorDetails>
|
||||
</FlexRow>
|
||||
)}
|
||||
</ErrorRow>
|
||||
);
|
||||
}
|
||||
|
||||
const ErrorBarContainer = styled.div({
|
||||
boxShadow: '2px 2px 2px #ccc',
|
||||
userSelect: 'text',
|
||||
});
|
||||
|
||||
const DismissAllErrors = styled.div({
|
||||
boxShadow: '2px 2px 2px #ccc',
|
||||
backgroundColor: colors.cherryDark3,
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
borderBottomLeftRadius: '4px',
|
||||
borderBottomRightRadius: '4px',
|
||||
position: 'absolute',
|
||||
width: '48px',
|
||||
height: '16px',
|
||||
zIndex: 2,
|
||||
right: '20px',
|
||||
fontSize: '6pt',
|
||||
lineHeight: '16px',
|
||||
cursor: 'pointer',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ErrorDetails = styled.div({
|
||||
width: '100%',
|
||||
marginTop: 4,
|
||||
});
|
||||
|
||||
const ErrorRows = styled.div({
|
||||
color: '#fff',
|
||||
maxHeight: '600px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
transition: 'max-height 0.3s ease',
|
||||
borderBottom: '1px solid #b3b3b3',
|
||||
'&.collapsed': {
|
||||
maxHeight: '0px',
|
||||
borderBottom: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const ErrorRow = styled.div({
|
||||
padding: '4px 12px',
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '28px',
|
||||
backgroundColor: colors.yellowTint,
|
||||
color: colors.yellow,
|
||||
'&.urgent': {
|
||||
backgroundColor: colors.redTint,
|
||||
color: colors.red,
|
||||
},
|
||||
});
|
||||
|
||||
const ButtonSection = styled(FlexColumn)({
|
||||
marginLeft: '8px',
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
});
|
||||
|
||||
const ErrorCounter = styled(FlexColumn)({
|
||||
border: `1px solid ${colors.light20}`,
|
||||
color: colors.light20,
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 20,
|
||||
marginTop: 4,
|
||||
lineHeight: '18px',
|
||||
textAlign: 'center',
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
marginLeft: '8px',
|
||||
fontSize: '10px',
|
||||
});
|
||||
164
desktop/src/chrome/ExportDataPluginSheet.tsx
Normal file
164
desktop/src/chrome/ExportDataPluginSheet.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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 {connect} from 'react-redux';
|
||||
import React, {Component} from 'react';
|
||||
import {ShareType} from '../reducers/application';
|
||||
import {State as PluginState} from '../reducers/plugins';
|
||||
import {State as PluginStatesState} from '../reducers/pluginStates';
|
||||
import {State as Store} from '../reducers';
|
||||
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
||||
import {ActiveSheet} from '../reducers/application';
|
||||
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
|
||||
import {getActivePersistentPlugins} from '../utils/pluginUtils';
|
||||
import {
|
||||
ACTIVE_SHEET_SHARE_DATA,
|
||||
setActiveSheet as getActiveSheetAction,
|
||||
setExportDataToFileActiveSheet as getExportDataToFileActiveSheetAction,
|
||||
} from '../reducers/application';
|
||||
import ListView from './ListView';
|
||||
import {Dispatch, Action} from 'redux';
|
||||
import {unsetShare} from '../reducers/application';
|
||||
import {FlexColumn, styled} from 'flipper';
|
||||
import Client from '../Client';
|
||||
|
||||
type OwnProps = {
|
||||
onHide: () => void;
|
||||
};
|
||||
|
||||
type StateFromProps = {
|
||||
share: ShareType | null;
|
||||
plugins: PluginState;
|
||||
pluginStates: PluginStatesState;
|
||||
pluginMessageQueue: PluginMessageQueueState;
|
||||
selectedClient: Client | undefined;
|
||||
};
|
||||
|
||||
type DispatchFromProps = {
|
||||
setSelectedPlugins: (payload: Array<string>) => void;
|
||||
setActiveSheet: (payload: ActiveSheet) => void;
|
||||
setExportDataToFileActiveSheet: (payload: {
|
||||
file: string;
|
||||
closeOnFinish: boolean;
|
||||
}) => void;
|
||||
unsetShare: () => void;
|
||||
};
|
||||
|
||||
type Props = OwnProps & StateFromProps & DispatchFromProps;
|
||||
|
||||
const Container = styled(FlexColumn)({
|
||||
width: 700,
|
||||
maxHeight: 700,
|
||||
padding: 8,
|
||||
});
|
||||
|
||||
type State = {
|
||||
availablePluginsToExport: Array<{id: string; label: string}>;
|
||||
};
|
||||
|
||||
class ExportDataPluginSheet extends Component<Props, State> {
|
||||
state: State = {availablePluginsToExport: []};
|
||||
static getDerivedStateFromProps(props: Props, _state: State): State {
|
||||
const {plugins, pluginStates, pluginMessageQueue, selectedClient} = props;
|
||||
const availablePluginsToExport = getActivePersistentPlugins(
|
||||
pluginStates,
|
||||
pluginMessageQueue,
|
||||
plugins,
|
||||
selectedClient,
|
||||
);
|
||||
return {
|
||||
availablePluginsToExport,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onHide} = this.props;
|
||||
const onHideWithUnsettingShare = () => {
|
||||
this.props.unsetShare();
|
||||
onHide();
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<ListView
|
||||
type="multiple"
|
||||
title="Select the plugins for which you want to export the data"
|
||||
onSubmit={() => {
|
||||
const {share} = this.props;
|
||||
if (!share) {
|
||||
console.error(
|
||||
'applications.share is undefined, whereas it was expected to be defined',
|
||||
);
|
||||
} else {
|
||||
switch (share.type) {
|
||||
case 'link':
|
||||
this.props.setActiveSheet(ACTIVE_SHEET_SHARE_DATA);
|
||||
break;
|
||||
case 'file': {
|
||||
const file = share.file;
|
||||
if (file) {
|
||||
this.props.setExportDataToFileActiveSheet({
|
||||
file,
|
||||
closeOnFinish: true,
|
||||
});
|
||||
} else {
|
||||
console.error('share.file is undefined');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={selectedArray => {
|
||||
this.props.setSelectedPlugins(selectedArray);
|
||||
}}
|
||||
elements={this.state.availablePluginsToExport}
|
||||
selectedElements={new Set(this.props.plugins.selectedPlugins)}
|
||||
onHide={onHideWithUnsettingShare}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||
({
|
||||
application: {share},
|
||||
plugins,
|
||||
pluginStates,
|
||||
pluginMessageQueue,
|
||||
connections: {selectedApp, clients},
|
||||
}) => {
|
||||
const selectedClient = clients.find(o => {
|
||||
return o.id === selectedApp;
|
||||
});
|
||||
return {
|
||||
share,
|
||||
plugins,
|
||||
pluginStates,
|
||||
pluginMessageQueue,
|
||||
selectedClient,
|
||||
};
|
||||
},
|
||||
(dispatch: Dispatch<Action<any>>) => ({
|
||||
setSelectedPlugins: (plugins: Array<string>) => {
|
||||
dispatch(actionForSelectedPlugins(plugins));
|
||||
},
|
||||
setActiveSheet: (payload: ActiveSheet) => {
|
||||
dispatch(getActiveSheetAction(payload));
|
||||
},
|
||||
setExportDataToFileActiveSheet: (payload: {
|
||||
file: string;
|
||||
closeOnFinish: boolean;
|
||||
}) => {
|
||||
dispatch(getExportDataToFileActiveSheetAction(payload));
|
||||
},
|
||||
unsetShare: () => {
|
||||
dispatch(unsetShare());
|
||||
},
|
||||
}),
|
||||
)(ExportDataPluginSheet);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user