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:
Anton Nikolaev
2020-03-14 14:26:07 -07:00
committed by Facebook GitHub Bot
parent a60e6fee87
commit 85c13bb1f3
607 changed files with 103 additions and 142 deletions

View File

@@ -0,0 +1,3 @@
# Changelog
See [static/CHANGELOG.md](static/CHANGELOG.md)

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>CocoaAsyncSocketMac</string>
<key>CFBundleIdentifier</key>
<string>com.robbiehanson.CocoaAsyncSocketMac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>CocoaAsyncSocketMac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
</dict>
</plist>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>CocoaAsyncSocketMac</string>
<key>CFBundleIdentifier</key>
<string>com.robbiehanson.CocoaAsyncSocketMac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>CocoaAsyncSocketMac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
</dict>
</plist>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>FBPortForwarding-Mac</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.FBPortForwarding-Mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>FBPortForwarding-Mac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Facebook. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>FBPortForwarding-Mac</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.FBPortForwarding-Mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>FBPortForwarding-Mac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Facebook. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>peertalkMac</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.peertalkMac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>peertalkMac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Facebook. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>peertalkMac</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.peertalkMac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>peertalkMac</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Facebook. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16G29</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>PortForwardingMacApp</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.PortForwardingMacApp</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>PortForwardingMacApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A235</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0900</string>
<key>DTXcodeBuild</key>
<string>9A235</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013-present Facebook. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
APPL????

View File

@@ -0,0 +1,86 @@
/**
* 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
*/
// ==============
// Preload script
// ==============
const {remote, ipcRenderer} = require('electron');
const flipperState = {
mainWindowId: 0,
isClientInit: false,
plugins: null,
appName: 'JS App',
};
ipcRenderer.on('parent-window-id', (event, message) => {
flipperState.mainWindowId = message;
});
function initClient(plugins, appName) {
if (flipperState.isClientInit) {
return;
}
if (plugins) {
flipperState.plugins = plugins;
}
if (appName) {
flipperState.appName = appName;
}
if (flipperState.mainWindowId != 0) {
ipcRenderer.sendTo(
flipperState.mainWindowId,
'from-js-emulator-init-client',
{
command: 'initClient',
windowId: remote.getCurrentWebContents().id,
payload: {
plugins: flipperState.plugins,
appName: flipperState.appName,
},
},
);
flipperState.isClientInit = true;
}
}
window.FlipperWebviewBridge = {
registerPlugins: function(plugins) {
flipperState.plugins = plugins;
},
start: function(appName) {
flipperState.appName = appName;
initClient();
},
sendFlipperObject: function(plugin, method, data) {
initClient();
if (flipperState.mainWindowId != 0) {
ipcRenderer.sendTo(flipperState.mainWindowId, 'from-js-emulator', {
command: 'sendFlipperObject',
payload: {
api: plugin,
method: method,
params: data,
},
});
}
},
isFlipperSupported: true,
initClient: initClient,
};
ipcRenderer.on('message-to-plugin', (event, message) => {
const flipper = window.flipper;
if (!flipper) {
return;
}
const receiver = flipper.FlipperWebviewMessageReceiver.receive;
const {api, method, params} = message.params;
receiver(api, method, JSON.stringify(params));
});

View File

@@ -0,0 +1,20 @@
/**
* 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 {ipcRenderer} = require('electron');
global.sendToHost = message => {
ipcRenderer.sendToHost(message);
};
global.setupToReceiveHostMessage = callback => {
ipcRenderer.on('hostMessage', (event, message) => {
callback(message);
});
};

View File

@@ -0,0 +1,2 @@
{
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

16
desktop/static/anchor.svg Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="34px" height="15px" viewBox="0 0 34 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M15.7532039,0.628345697 C16.4715168,-0.209508324 17.6268814,-0.213619078 18.3523771,0.641069382 L25.4052507,8.94988484 C26.8378145,10.6375528 29.7993348,12.0206796 32.0012238,12.0390288 L33.999137,12.0556781 L0,12.0556781 L1.99762575,12.0390288 C4.20759939,12.0206096 7.16128831,10.6501222 8.60494516,8.96621291 L15.7532039,0.628345697 Z" id="path-1"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(-0.000000, 2.000000)">
<rect id="Rectangle-Copy" fill="#FFFFFF" x="0" y="11" width="34" height="1"></rect>
<g id="Combined-Shape" fill-rule="nonzero">
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
<path stroke="rgba(0,0,0,0.3)" stroke-width="1" d="M15.3736097,0.302910233 C16.2949805,-0.771794461 17.8118778,-0.76831469 18.7335645,0.317501454 L25.7864381,8.62631692 C27.1230664,10.2009657 29.9459429,11.521884 32.0053904,11.5390462 L34.0033036,11.5556955 L33.999137,12.5556781 L0,12.5556781 L-0.00416712734,11.5556955 L1.99345861,11.5390462 C4.06137191,11.521811 6.87731445,10.2131533 8.22535095,8.64077745 L15.3736097,0.30291024 Z"></path>
</g>
<rect id="Rectangle" fill="#FFFFFF" x="0" y="12" width="34" height="1"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,318 @@
/**
* 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 strict-local
*/
import path from 'path';
import fs from 'fs';
const Metro = require('metro');
import util from 'util';
import recursiveReaddir from 'recursive-readdir';
import expandTilde from 'expand-tilde';
import pMap from 'p-map';
import {homedir} from 'os';
import Watchman from './watchman';
const HOME_DIR = homedir();
const DEFAULT_COMPILE_OPTIONS: CompileOptions = {
force: false,
failSilently: true,
recompileOnChanges: true,
};
export type CompileOptions = {
force: boolean;
failSilently: boolean;
recompileOnChanges: boolean;
};
type DynamicCompileOptions = CompileOptions & {force: boolean};
export type PluginManifest = {
version: string;
name: string;
main?: string;
bundleMain?: string;
[key: string]: any;
};
type PluginInfo = {
rootDir: string;
name: string;
entry: string;
manifest: PluginManifest;
};
export type CompiledPluginInfo = PluginManifest & {out: string};
export default async function(
reloadCallback: (() => void) | null,
pluginPaths: string[],
pluginCache: string,
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
) {
options = Object.assign({}, DEFAULT_COMPILE_OPTIONS, options);
const plugins = pluginEntryPoints(pluginPaths);
if (!fs.existsSync(pluginCache)) {
fs.mkdirSync(pluginCache);
}
if (options.recompileOnChanges) {
await startWatchChanges(plugins, reloadCallback, pluginCache, options);
}
const compilations = pMap(
Object.values(plugins),
plugin => {
const dynamicOptions: DynamicCompileOptions = Object.assign(options, {
force: false,
});
return compilePlugin(plugin, pluginCache, dynamicOptions);
},
{concurrency: 4},
);
const dynamicPlugins = (await compilations).filter(
c => c !== null,
) as CompiledPluginInfo[];
console.log('✅ Compiled all plugins.');
return dynamicPlugins;
}
async function startWatchingPluginsUsingWatchman(
plugins: PluginInfo[],
onPluginChanged: (plugin: PluginInfo) => void,
) {
// Initializing a watchman for each folder containing plugins
const watchmanRootMap: {[key: string]: Watchman} = {};
await Promise.all(
plugins.map(async plugin => {
const watchmanRoot = path.resolve(plugin.rootDir, '..');
if (!watchmanRootMap[watchmanRoot]) {
watchmanRootMap[watchmanRoot] = new Watchman(watchmanRoot);
await watchmanRootMap[watchmanRoot].initialize();
}
}),
);
// Start watching plugins using the initialized watchmans
await Promise.all(
plugins.map(async plugin => {
const watchmanRoot = path.resolve(plugin.rootDir, '..');
const watchman = watchmanRootMap[watchmanRoot];
await watchman.startWatchFiles(
path.relative(watchmanRoot, plugin.rootDir),
() => onPluginChanged(plugin),
{
excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'],
},
);
}),
);
}
async function startWatchChanges(
plugins: {[key: string]: PluginInfo},
reloadCallback: (() => void) | null,
pluginCache: string,
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
) {
// eslint-disable-next-line no-console
console.log('🕵️‍ Watching for plugin changes');
const delayedCompilation: {[key: string]: NodeJS.Timeout | null} = {};
const kCompilationDelayMillis = 1000;
const onPluginChanged = (plugin: PluginInfo) => {
if (!delayedCompilation[plugin.name]) {
delayedCompilation[plugin.name] = setTimeout(() => {
delayedCompilation[plugin.name] = null;
// eslint-disable-next-line no-console
console.log(`🕵️‍ Detected changes in ${plugin.name}`);
const watchOptions = Object.assign(options, {force: true});
compilePlugin(plugin, pluginCache, watchOptions).then(
reloadCallback ?? (() => {}),
);
}, kCompilationDelayMillis);
}
};
const filteredPlugins = Object.values(plugins)
// no hot reloading for plugins in .flipper folder. This is to prevent
// Flipper from reloading, while we are doing changes on thirdparty plugins.
.filter(
plugin => !plugin.rootDir.startsWith(path.join(HOME_DIR, '.flipper')),
);
try {
await startWatchingPluginsUsingWatchman(filteredPlugins, onPluginChanged);
} catch (err) {
console.error(
'Failed to start watching plugin files using Watchman, continue without hot reloading',
err,
);
}
}
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;
};
function pluginEntryPoints(additionalPaths: string[] = []) {
const defaultPluginPath = path.join(HOME_DIR, '.flipper', 'node_modules');
const entryPoints = entryPointForPluginFolder(defaultPluginPath);
if (typeof additionalPaths === 'string') {
additionalPaths = [additionalPaths];
}
additionalPaths.forEach(additionalPath => {
const additionalPlugins = entryPointForPluginFolder(additionalPath);
Object.keys(additionalPlugins).forEach(key => {
entryPoints[key] = additionalPlugins[key];
});
});
return entryPoints;
}
function entryPointForPluginFolder(pluginPath: string) {
pluginPath = expandTilde(pluginPath);
if (!fs.existsSync(pluginPath)) {
return {};
}
return fs
.readdirSync(pluginPath)
.filter(name => fs.lstatSync(path.join(pluginPath, name)).isDirectory())
.filter(Boolean)
.map(name => {
let packageJSON;
try {
packageJSON = fs
.readFileSync(path.join(pluginPath, name, 'package.json'))
.toString();
} catch (e) {}
if (packageJSON) {
try {
const pkg = JSON.parse(packageJSON) as PluginManifest;
const plugin: PluginInfo = {
manifest: pkg,
name: pkg.name,
entry: path.join(pluginPath, name, pkg.main || 'index.js'),
rootDir: path.join(pluginPath, name),
};
return plugin;
} catch (e) {
console.error(
`Could not load plugin "${pluginPath}", because package.json is invalid.`,
);
console.error(e);
return null;
}
}
return null;
})
.filter(Boolean)
.reduce<{[key: string]: PluginInfo}>((acc, cv) => {
acc[cv!.name] = cv!;
return acc;
}, {});
}
async function mostRecentlyChanged(dir: string) {
const files = await util.promisify<string, string[]>(recursiveReaddir)(dir);
return files
.map(f => fs.lstatSync(f).ctime)
.reduce((a, b) => (a > b ? a : b), new Date(0));
}
async function compilePlugin(
pluginInfo: PluginInfo,
pluginCache: string,
options: DynamicCompileOptions,
): Promise<CompiledPluginInfo | null> {
const {rootDir, manifest, entry, name} = pluginInfo;
const bundleMain = manifest.bundleMain ?? path.join('dist', 'index.js');
const bundlePath = path.join(rootDir, bundleMain);
if (fs.existsSync(bundlePath)) {
// eslint-disable-next-line no-console
const out = path.join(rootDir, bundleMain);
console.log(`🥫 Using pre-built version of ${name}: ${out}...`);
return Object.assign({}, pluginInfo.manifest, {out});
} else {
const out = path.join(
pluginCache,
`${name}@${manifest.version || '0.0.0'}.js`,
);
const result = Object.assign({}, pluginInfo.manifest, {out});
const rootDirCtime = await mostRecentlyChanged(rootDir);
if (
!options.force &&
fs.existsSync(out) &&
rootDirCtime < fs.lstatSync(out).ctime
) {
// eslint-disable-next-line no-console
console.log(`🥫 Using cached version of ${name}...`);
return result;
} else {
console.log(`⚙️ Compiling ${name}...`); // eslint-disable-line no-console
try {
await Metro.runBuild(
{
reporter: {update: () => {}},
projectRoot: rootDir,
watchFolders: [__dirname, rootDir],
serializer: {
getRunModuleStatement: (moduleID: string) =>
`module.exports = global.__r(${moduleID}).default;`,
createModuleIdFactory,
},
transformer: {
babelTransformerPath: path.join(
__dirname,
'transforms',
'index.js',
),
},
resolver: {
sourceExts: ['tsx', 'ts', 'js'],
blacklistRE: /(\/|\\)(sonar|flipper|flipper-public)(\/|\\)(dist|doctor)(\/|\\)|(\.native\.js$)/,
},
},
{
entry: entry.replace(rootDir, '.'),
out,
dev: false,
sourceMap: true,
minify: false,
},
);
} catch (e) {
if (options.failSilently) {
console.error(
`❌ Plugin ${name} is ignored, because it could not be compiled.`,
);
console.error(e);
return null;
} else {
throw e;
}
}
return result;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,37 @@
/*
* 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.
*
*/
{
remote: {
process: {
env: {},
},
getCurrentWindow: function() {
return {
isFocused: function() {return true;},
on: function() {return true;}
};
},
app: {
getVersion: function() {return global.__VERSION__ || '1';},
getName: function() {return '';},
getAppPath: function() {return process.cwd();}
},
shell: {
openExternal: function() {}
},
Menu: {
buildFromTemplate: function() {
return {items: []}
},
setApplicationMenu: function() {}
}
},
ipcRenderer: {
on: function() {return true;}
},
}

View 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
*/
global.fetch = require('jest-fetch-mock');
require('immer').enableMapSet();

File diff suppressed because it is too large Load Diff

BIN
desktop/static/icon.icns Normal file

Binary file not shown.

BIN
desktop/static/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
desktop/static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

379
desktop/static/icons.json Normal file
View File

@@ -0,0 +1,379 @@
{
"accessibility": [
16
],
"app-dailies": [
12
],
"app-react": [
12
],
"apps": [
12
],
"arrow-right": [
12
],
"bell-null-outline": [
24
],
"bell-null": [
12
],
"bell": [
12
],
"bird": [
12
],
"borders": [
16
],
"box": [
12
],
"brush-paint": [
12
],
"bug": [
12
],
"building-city": [
12
],
"camcorder": [
12,
16
],
"camera": [
12,
16
],
"caution-octagon": [
16,
20
],
"caution-triangle": [
12,
16,
24
],
"caution": [
16
],
"checkmark": [
16
],
"chevron-down-outline": [
10
],
"chevron-down": [
12,
16,
8
],
"chevron-left": [
12,
16
],
"chevron-right": [
8,
12,
16
],
"chevron-up": [
12,
16,
8
],
"compose": [
12
],
"copy": [
12
],
"cross-circle": [
12,
16,
24
],
"cross": [
16
],
"dashboard-outline": [
24
],
"dashboard": [
12
],
"data-table": [
16
],
"desktop": [
12
],
"directions": [
12
],
"dots-3-circle-outline": [
16
],
"download": [
16
],
"face-unhappy-outline": [
24
],
"first-aid": [
12
],
"flash-default": [
12
],
"info-circle": [
12,
16,
24
],
"internet": [
12
],
"life-event-major": [
16
],
"magic-wand": [
12,
20
],
"magnifying-glass": [
16,
20
],
"messages": [
12
],
"minus-circle": [
12
],
"mobile-engagement": [
16
],
"mobile": [
12,
16,
32
],
"network": [
12
],
"news-feed": [
12
],
"pause": [
16
],
"posts": [
20
],
"power": [
16
],
"profile": [
12
],
"question-circle-outline": [
16
],
"question-circle": [
12,
20
],
"question": [
16
],
"refresh-left": [
16
],
"rocket": [
12,
20
],
"settings": [
12
],
"share-external": [
12,
16
],
"share": [
16
],
"star-outline": [
16,
24
],
"star-slash": [
16
],
"star": [
12,
16,
24
],
"stop-playback": [
12,
16
],
"stop": [
16
],
"target": [
12,
16
],
"thought-bubble": [
12
],
"tools": [
12,
20
],
"translate": [
12
],
"trash-outline": [
16
],
"trash": [
12,
16
],
"tree": [
12
],
"trending": [
12
],
"triangle-down": [
12
],
"triangle-right": [
12
],
"underline": [
12
],
"washing-machine": [
12
],
"watch-tv": [
12
],
"gears-two": [
16
],
"info-cursive": [
16
],
"on-this-day": [
12
],
"zoom-out": [
16
],
"zoom-in": [
16
],
"fast-forward": [
16
],
"draft-outline": [
16
],
"gradient": [
16
],
"crop": [
16
],
"play": [
16
],
"cross-outline": [
16
],
"messenger-code": [
12
],
"book": [
12
],
"list-arrow-up": [
12
],
"cat": [
12
],
"duplicate": [
12
],
"profile-circle-outline": [
16
],
"card-person": [
12
],
"pencil-outline": [
16
],
"code": [
12
],
"undo-outline": [
16
],
"checkmark-circle-outline": [
24
],
"target-outline": [
16,
24
],
"internet-outline": [
24,
32
],
"profile-outline": [
32
],
"app-react-outline": [
16
],
"send-outline": [
16
],
"paper-stack": [
12
],
"weather-cold": [
12
],
"mobile-cross": [
16
],
"database-arrow-left": [
12
],
"plus-circle-outline": [
16
],
"arrows-circle": [
12
],
"navicon": [
12
],
"paper-fold-text": [
16
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" ?><svg viewBox="3230 1250 120 120" xmlns="http://www.w3.org/2000/svg"><defs><style>
.cls-1 {
fill: #a4c639;
}
.cls-2 {
fill: #fff;
}
</style></defs><g data-name="Group 93" id="Group_93" transform="translate(3107 1147)"><circle class="cls-1" cx="60" cy="60" data-name="Ellipse 69" id="Ellipse_69" r="60" transform="translate(123 103)"/><g data-name="Group 92" id="Group_92" transform="translate(-109 75)"><rect class="cls-2" data-name="Rectangle 44" height="31.549" id="Rectangle_44" rx="5.03" transform="translate(257 73.52)" width="10.059"/><rect class="cls-2" data-name="Rectangle 45" height="31.549" id="Rectangle_45" rx="5.03" transform="translate(316.441 73.52)" width="10.059"/><path class="cls-2" d="M0,0H45.724a0,0,0,0,1,0,0V32.408a6,6,0,0,1-6,6H6a6,6,0,0,1-6-6V0A0,0,0,0,1,0,0Z" data-name="Rectangle 46" id="Rectangle_46" transform="translate(268.888 74.434)"/><path class="cls-2" d="M0,0H10.059a0,0,0,0,1,0,0V11.888a5.03,5.03,0,0,1-5.03,5.03h0A5.03,5.03,0,0,1,0,11.888V0A0,0,0,0,1,0,0Z" data-name="Rectangle 47" id="Rectangle_47" transform="translate(278.033 111.928)"/><path class="cls-2" d="M0,0H10.059a0,0,0,0,1,0,0V11.888a5.03,5.03,0,0,1-5.03,5.03h0A5.03,5.03,0,0,1,0,11.888V0A0,0,0,0,1,0,0Z" data-name="Rectangle 48" id="Rectangle_48" transform="translate(295.408 111.928)"/><path class="cls-2" d="M22.883.25c12.5,0,22.633,9.478,22.633,21.17H.25C.25,9.728,10.383.25,22.883.25Z" data-name="Path 206" id="Path_206" transform="translate(268.867 51.551)"/><circle class="cls-1" cx="2.058" cy="2.058" data-name="Ellipse 70" id="Ellipse_70" r="2.058" transform="translate(279.13 60.717)"/><circle class="cls-1" cx="2.058" cy="2.058" data-name="Ellipse 71" id="Ellipse_71" r="2.058" transform="translate(300.163 60.717)"/><rect class="cls-2" data-name="Rectangle 49" height="9.145" id="Rectangle_49" rx="0.686" transform="matrix(0.875, -0.485, 0.485, 0.875, 276.753, 47.665)" width="1.372"/><rect class="cls-2" data-name="Rectangle 50" height="9.145" id="Rectangle_50" rx="0.686" transform="translate(305.605 47) rotate(31)" width="1.372"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" ?><svg viewBox="2922 786 120 120" xmlns="http://www.w3.org/2000/svg"><defs><style>
.cls-1 {
fill: #c8c8c8;
}
.cls-2 {
fill: #fff;
}
</style></defs><g data-name="Group 32" id="Group_32" transform="translate(2602 291)"><circle class="cls-1" cx="60" cy="60" data-name="Ellipse 17" id="Ellipse_17" r="60" transform="translate(320 495)"/><g data-name="Group 31" id="Group_31" transform="translate(179 179.1)"><path class="cls-2" d="M623.222,94.309S609.4,79.17,610.2,61.817s12.939-22.045,12.939-22.045A16.749,16.749,0,0,1,631.5,38a22.864,22.864,0,0,1,7.143,1.594c3.615,1.454,4.507,1.9,7.494,1.895,3.533-.006,6.046-1.681,9.257-2.645a25.088,25.088,0,0,1,6.3-.844,18.273,18.273,0,0,1,6.249,1.594,25.033,25.033,0,0,1,8.1,6.552,17.35,17.35,0,0,0-8.8,16.556c.88,11.155,11.257,15.245,11.257,15.245A70.758,70.758,0,0,1,667.76,94.575a11.3,11.3,0,0,1-8.274,4.515c-2.1.127-4.489-.8-7.218-2.125a16.273,16.273,0,0,0-6.425-1.239,13.185,13.185,0,0,0-4.753.62c-2.112.8-7.026,3.065-9.418,2.745A14.019,14.019,0,0,1,623.222,94.309Z" data-name="Path 122" id="Path_122" transform="translate(-443.163 316.54)"/><path class="cls-2" d="M647.683,36.118s-1.239-6.107,3.983-12.48A18.153,18.153,0,0,1,663.969,17,18.339,18.339,0,0,1,659.9,30.188,14.914,14.914,0,0,1,647.683,36.118Z" data-name="Path 123" id="Path_123" transform="translate(-447.409 318.9)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-Copy-5" fill="#000000" x="3" y="13" width="14" height="2"></rect>
<path d="M2,16 L18,16 L18,4 L2,4 L2,16 Z M1,3 L19,3 L19,17 L1,17 L1,3 Z" fill="#000000" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 489 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-Copy" fill="#000000" x="3" y="5" width="2" height="10"></rect>
<path d="M2,16 L18,16 L18,4 L2,4 L2,16 Z M1,3 L19,3 L19,17 L1,17 L1,3 Z" fill="#000000" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect fill="#000000" x="15" y="5" width="2" height="10"></rect>
<path d="M2,16 L18,16 L18,4 L2,4 L2,16 Z M1,3 L19,3 L19,17 L1,17 L1,3 Z" fill="#000000" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -0,0 +1,96 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="graphiql/graphiql.css">
<link rel="stylesheet" href="vis/vis.min.css">
<title>Flipper</title>
<style>
#loading {
-webkit-app-region: drag;
z-index: 999999;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 50px;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #525252;
text-align: center;
}
.__infinity-dev-box-error {
background-color: #000;
font-family: monospace;
white-space: pre;
font-size: 16px;
}
</style>
</head>
<body>
<div id="root">
<div id="loading">
Loading...
</div>
</div>
<div class="__infinity-dev-box __infinity-dev-box-error" hidden>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
(function() {
global.electronRequire = window.require;
let suppressErrors = false;
const socket = io(location.origin);
socket.on('refresh', () => {
location.reload();
});
socket.on('hasErrors', (html) => {
openError(html);
suppressErrors = true;
});
function openError(text) {
if (suppressErrors) {
return;
}
const box = document.querySelector('.__infinity-dev-box-error');
box.removeAttribute('hidden');
box.textContent = text;
}
function init() {
const script = document.createElement('script');
script.src = window.process.env.BUNDLE_URL;
script.onerror = () => {
openError('Script failure. Check Chrome console for more info.');
};
window.addEventListener('flipper-store-ready', () => global.Flipper.init());
document.body.appendChild(script);
}
init();
})();
</script>
</body>
</html>

21
desktop/static/index.html Normal file
View File

@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="graphiql/graphiql.css">
<link rel="stylesheet" href="vis/vis.min.css">
<title>Flipper</title>
</head>
<body>
<div id="root"></div>
<script>
global.electronRequire = window.require;
</script>
<script>
window.addEventListener('flipper-store-ready', () => global.Flipper.init());
</script>
<script src="bundle.js"></script>
</body>
</html>

13
desktop/static/index.js Normal file
View File

@@ -0,0 +1,13 @@
/**
* 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
*/
global.electronRequire = require;
global.electronProcess = process;
require('./main.bundle.js');

View File

@@ -0,0 +1,99 @@
/**
* 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 os from 'os';
import fs from 'fs';
import path from 'path';
import {promisify} from 'util';
import {spawn} from 'child_process';
import xdg from 'xdg-basedir';
import mkdirp from 'mkdirp';
const isProduction = () =>
!/node_modules[\\/]electron[\\/]/.test(process.execPath);
const isLauncherInstalled = () => {
if (os.type() == 'Darwin') {
const receipt = 'com.facebook.flipper.launcher';
const plistLocation = '/Applications/Flipper.app/Contents/Info.plist';
return (
fs.existsSync(plistLocation) &&
fs.readFileSync(plistLocation).indexOf(receipt) > 0
);
}
return false;
};
const startLauncher = (argv: {file?: string; url?: string}) => {
const args = [];
if (argv.file) {
args.push('--file', argv.file);
}
if (argv.url) {
args.push('--url', argv.url);
}
if (os.type() == 'Darwin') {
spawn('open', ['/Applications/Flipper.app', '--args'].concat(args));
}
};
const checkIsCycle = async () => {
const dir = path.join(xdg.cache!, 'flipper');
const filePath = path.join(dir, 'last-launcher-run');
// This isn't monotonically increasing, so there's a change we get time drift
// between the checks, but the worst case here is that we do two roundtrips
// before this check works.
const rightNow = Date.now();
let backThen;
try {
backThen = parseInt(
(await promisify(fs.readFile)(filePath)).toString(),
10,
);
} catch (e) {
backThen = 0;
}
const delta = rightNow - backThen;
await mkdirp(dir);
await promisify(fs.writeFile)(filePath, rightNow);
// If the last startup was less than 5s ago, something's not okay.
return Math.abs(delta) < 5000;
};
/**
* Runs the launcher if required and returns a boolean based on whether
* it has. You should shut down this instance of the app in that case.
*/
export default async function delegateToLauncher(argv: {
launcher: boolean;
file?: string;
url?: string;
}) {
if (argv.launcher && isProduction() && isLauncherInstalled()) {
if (await checkIsCycle()) {
console.error(
'Launcher cycle detected. Not delegating even though I usually would.',
);
return false;
}
console.warn('Delegating to Flipper Launcher ...');
console.warn(
`You can disable this behavior by passing '--no-launcher' at startup.`,
);
startLauncher(argv);
return true;
}
return false;
}

330
desktop/static/main.ts Normal file
View File

@@ -0,0 +1,330 @@
/**
* 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 [s, ns] = process.hrtime();
let launchStartTime: number | undefined = s * 1e3 + ns / 1e6;
import {
app,
BrowserWindow,
ipcMain,
Notification,
globalShortcut,
} from 'electron';
import path from 'path';
import url from 'url';
import fs from 'fs';
import fixPath from 'fix-path';
import {exec} from 'child_process';
import compilePlugins from './compilePlugins';
import setup from './setup';
import delegateToLauncher from './launcher';
import expandTilde from 'expand-tilde';
import yargs from 'yargs';
const VERSION: string = (global as any).__VERSION__;
// Adds system PATH folders to process.env.PATH for MacOS production bundles.
fixPath();
// disable electron security warnings: https://github.com/electron/electron/blob/master/docs/tutorial/security.md#security-native-capabilities-and-your-responsibility
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
if (process.platform === 'darwin') {
// If we are running on macOS and the app is called Flipper, we add a comment
// with the old name, to make it findable via Spotlight using its old name.
const APP_NAME = 'Flipper.app';
const i = process.execPath.indexOf(`/${APP_NAME}/`);
if (i > -1) {
exec(
`osascript -e 'on run {f, c}' -e 'tell app "Finder" to set comment of (POSIX file f as alias) to c' -e end "${process.execPath.substr(
0,
i,
)}/${APP_NAME}" "sonar"`,
);
}
}
const argv = yargs
.usage('$0 [args]')
.options({
file: {
describe: 'Define a file to open on startup.',
type: 'string',
},
url: {
describe: 'Define a flipper:// URL to open on startup.',
type: 'string',
},
updater: {
default: true,
describe: 'Toggle the built-in update mechanism.',
type: 'boolean',
},
launcher: {
default: true,
describe: 'Toggle delegating to the update launcher on startup.',
type: 'boolean',
},
'launcher-msg': {
describe:
'[Internal] Used to provide a user message from the launcher to the user.',
type: 'string',
},
})
.version(VERSION)
.help()
.parse(process.argv.slice(1));
const {config, configPath, flipperDir} = setup(argv);
const skipLoadingEmbeddedPlugins = process.env.FLIPPER_NO_EMBEDDED_PLUGINS;
const pluginPaths = (config.pluginPaths ?? [])
.concat([
path.join(configPath, '..', 'thirdparty'),
...(skipLoadingEmbeddedPlugins
? []
: [
path.join(__dirname, '..', 'src', 'plugins'),
path.join(__dirname, '..', 'src', 'fb', 'plugins'),
]),
])
.map(expandTilde)
.filter(fs.existsSync);
process.env.CONFIG = JSON.stringify({
...config,
pluginPaths,
});
// possible reference to main app window
let win: BrowserWindow;
let appReady = false;
let pluginsCompiled = false;
let deeplinkURL: string | undefined = argv.url;
let filePath: string | undefined = argv.file;
// tracking
setInterval(() => {
if (win) {
win.webContents.send('trackUsage');
}
}, 60 * 1000);
compilePlugins(
() => {
if (win) {
win.reload();
}
},
pluginPaths,
path.join(flipperDir, 'plugins'),
).then(dynamicPlugins => {
ipcMain.on('get-dynamic-plugins', event => {
event.returnValue = dynamicPlugins;
});
pluginsCompiled = true;
tryCreateWindow();
});
// check if we already have an instance of this app open
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) {
win.restore();
}
win.focus();
}
});
// Create myWindow, load the rest of the app, etc...
app.on('ready', () => {});
}
// quit app once all windows are closed
app.on('window-all-closed', () => {
appReady = false;
app.quit();
});
app.on('will-finish-launching', () => {
// Protocol handler for osx
app.on('open-url', function(event, url) {
event.preventDefault();
deeplinkURL = url;
argv.url = url;
if (win) {
win.webContents.send('flipper-protocol-handler', deeplinkURL);
}
});
app.on('open-file', (event, path) => {
// When flipper app is running, and someone double clicks the import file, `componentDidMount` will not be called again and windows object will exist in that case. That's why calling `win.webContents.send('open-flipper-file', filePath);` again.
event.preventDefault();
filePath = path;
argv.file = path;
if (win) {
win.webContents.send('open-flipper-file', filePath);
filePath = undefined;
}
});
});
app.on('ready', () => {
// If we delegate to the launcher, shut down this instance of the app.
delegateToLauncher(argv).then((hasLauncherInvoked: boolean) => {
if (hasLauncherInvoked) {
app.quit();
return;
}
appReady = true;
app.commandLine.appendSwitch('scroll-bounce');
tryCreateWindow();
// if in development install the react devtools extension
if (process.env.NODE_ENV === 'development') {
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REDUX_DEVTOOLS,
} = require('electron-devtools-installer');
installExtension(REACT_DEVELOPER_TOOLS.id);
installExtension(REDUX_DEVTOOLS.id);
}
});
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
ipcMain.on('componentDidMount', _event => {
if (deeplinkURL) {
win.webContents.send('flipper-protocol-handler', deeplinkURL);
deeplinkURL = undefined;
}
if (filePath) {
// When flipper app is not running, the windows object might not exist in the callback of `open-file`, but after ``componentDidMount` it will definitely exist.
win.webContents.send('open-flipper-file', filePath);
filePath = undefined;
}
});
ipcMain.on('getLaunchTime', event => {
if (launchStartTime) {
event.sender.send('getLaunchTime', launchStartTime);
// set launchTime to null to only report it once, to prevents reporting wrong
// launch times for example after reloading the renderer process
launchStartTime = undefined;
}
});
ipcMain.on(
'sendNotification',
(e, {payload, pluginNotification, closeAfter}) => {
// notifications can only be sent when app is ready
if (appReady) {
const n = new Notification(payload);
// Forwarding notification events to renderer process
// https://electronjs.org/docs/api/notification#instance-events
['show', 'click', 'close', 'reply', 'action'].forEach(eventName => {
// TODO: refactor this to make typescript happy
// @ts-ignore
n.on(eventName, (event, ...args) => {
e.sender.send(
'notificationEvent',
eventName,
pluginNotification,
...args,
);
});
});
n.show();
if (closeAfter) {
setTimeout(() => {
n.close();
}, closeAfter);
}
}
},
);
// Define custom protocol handler. Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('flipper');
function tryCreateWindow() {
if (appReady && pluginsCompiled) {
win = new BrowserWindow({
show: false,
title: 'Flipper',
width: config.lastWindowPosition?.width || 1400,
height: config.lastWindowPosition?.height || 1000,
minWidth: 800,
minHeight: 600,
center: true,
titleBarStyle: 'hiddenInset',
vibrancy: 'sidebar',
webPreferences: {
backgroundThrottling: false,
webSecurity: false,
scrollBounce: true,
experimentalFeatures: true,
nodeIntegration: true,
webviewTag: true,
nativeWindowOpen: true,
},
});
win.once('ready-to-show', () => win.show());
win.once('close', () => {
if (process.env.NODE_ENV === 'development') {
// Removes as a default protocol for debug builds. Because even when the
// production application is installed, and one tries to deeplink through
// browser, it still looks for the debug one and tries to open electron
app.removeAsDefaultProtocolClient('flipper');
}
const [x, y] = win.getPosition();
const [width, height] = win.getSize();
// save window position and size
fs.writeFileSync(
configPath,
JSON.stringify({
...config,
lastWindowPosition: {
x,
y,
width,
height,
},
}),
);
});
if (
config.lastWindowPosition &&
config.lastWindowPosition.x &&
config.lastWindowPosition.y
) {
win.setPosition(config.lastWindowPosition.x, config.lastWindowPosition.y);
}
const entryUrl =
process.env.ELECTRON_URL ||
url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true,
});
win.loadURL(entryUrl);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,38 @@
{
"name": "flipper-static",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.8.3",
"@babel/generator": "^7.8.3",
"@babel/parser": "^7.8.3",
"@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",
"electron-devtools-installer": "^2.2.4",
"expand-tilde": "^2.0.2",
"fb-watchman": "^2.0.0",
"fix-path": "^3.0.0",
"mem": "^6.0.0",
"metro": "^0.58.0",
"mkdirp": "^1.0.0",
"p-map": "^4.0.0",
"recursive-readdir": "2.2.2",
"uuid": "^7.0.1",
"xdg-basedir": "^4.0.0",
"yargs": "^15.0.1"
},
"resolutions": {
"metro/temp": "0.9.0",
"ws": "7.2.0"
},
"devDependencies": {
"@babel/preset-env": "^7.8.3"
}
}

BIN
desktop/static/pattern.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

63
desktop/static/setup.ts Normal file
View 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 path from 'path';
import os from 'os';
import fs from 'fs';
export type Config = {
pluginPaths?: string[];
disabledPlugins?: string[];
lastWindowPosition?: {
x: number;
y: number;
width: number;
height: number;
};
updater?: boolean | undefined;
launcherMsg?: string | undefined;
updaterEnabled?: boolean;
launcherEnabled?: boolean;
};
export default function setup(argv: any) {
// ensure .flipper folder and config exist
const flipperDir = path.join(os.homedir(), '.flipper');
if (!fs.existsSync(flipperDir)) {
fs.mkdirSync(flipperDir);
}
const configPath = path.join(flipperDir, 'config.json');
let config: Config = {
pluginPaths: [],
disabledPlugins: [],
};
try {
config = {
...config,
...JSON.parse(fs.readFileSync(configPath).toString()),
};
} catch (e) {
// file not readable or not parsable, overwrite it with the new config
console.warn(`Failed to read ${configPath}: ${e}`);
console.info('Writing new default config.');
fs.writeFileSync(configPath, JSON.stringify(config));
}
// Non-persistent CLI arguments.
config = {
...config,
updaterEnabled: argv.updater,
launcherEnabled: argv.launcher,
launcherMsg: argv.launcherMsg,
};
return {config, configPath, flipperDir};
}

160
desktop/static/style.css Normal file
View File

@@ -0,0 +1,160 @@
/**
* 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.
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
*:active,
*:focus {
outline: none;
}
/**/
html,
body,
#root {
height: 100%;
width: 100%;
}
body {
font-family: system-ui;
font-size: 13px;
user-select: none;
-webkit-user-select: none;
cursor: default;
overflow: hidden;
}
* {
box-sizing: border-box;
}
#root {
overflow: hidden;
}

View File

@@ -0,0 +1,66 @@
/**
* 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 {transform} from '@babel/core';
import electronProcess from '../electron-process';
const babelOptions = {
ast: true,
plugins: [electronProcess],
filename: 'index.js',
};
test('transform "process.exit(0);"', () => {
const src = 'process.exit(0);';
const code = transform(src, babelOptions).code;
expect(code).toMatchInlineSnapshot(`"electronProcess.exit(0);"`);
});
test('transform "global.process.exit(0);"', () => {
const src = 'global.process.exit(0);';
const code = transform(src, babelOptions).code;
expect(code).toMatchInlineSnapshot(`"global.electronProcess.exit(0);"`);
});
test('transform "process.ENV.TEST = "true";"', () => {
const src = 'process.ENV.TEST = "true";';
const code = transform(src, babelOptions).code;
expect(code).toMatchInlineSnapshot(
`"electronProcess.ENV.TEST = \\"true\\";"`,
);
});
test('do not transform if process bound in an upper scope', () => {
const src = `
const process = {};
for (const i=0; i<10; i++) {
process.ENV[i] = i;
}
`;
const code = transform(src, babelOptions).code;
expect(code).toMatchInlineSnapshot(`
"const process = {};
for (const i = 0; i < 10; i++) {
process.ENV[i] = i;
}"
`);
});
test('do not transform if process bound to the current scope', () => {
const src = `
const process = {};
process.ENV.TEST = "true";
`;
const code = transform(src, babelOptions).code;
expect(code).toMatchInlineSnapshot(`
"const process = {};
process.ENV.TEST = \\"true\\";"
`);
});

View 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
*/
import {transform} from '@babel/core';
import electronStubs from '../electron-stubs';
const babelOptions = {
ast: true,
plugins: [electronStubs],
filename: 'index.js',
};
test('transform electron requires to inlined stubs', () => {
const src = 'require("electron")';
const transformed = transform(src, babelOptions).ast;
const body = transformed.program.body[0];
expect(body.type).toBe('ExpressionStatement');
expect(body.expression.properties.map(p => p.key.name)).toContain('remote');
});

View 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
*/
import {parse} from '@babel/parser';
import {transformFromAstSync} from '@babel/core';
import generate from '@babel/generator';
import flipperRequires from '../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");');
});

View 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'));
},
},
};
};

View File

@@ -0,0 +1,33 @@
/**
* 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 = function(babel, options) {
return {
name: 'change-process-to-electronProcess',
visitor: {
MemberExpression(path) {
if (
path.node.object.type === 'Identifier' &&
path.node.object.name === 'process' &&
!path.scope.hasBinding('process')
) {
path.node.object.name = 'electronProcess';
} else if (
path.node.object.type === 'MemberExpression' &&
path.node.object.object.type === 'Identifier' &&
path.node.object.object.name === 'global' &&
path.node.object.property.type === 'Identifier' &&
path.node.object.property.name === 'process'
) {
path.node.object.property.name = 'electronProcess';
}
},
},
};
};

View File

@@ -0,0 +1,37 @@
/**
* 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 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, options) {
return {
name: 'change-electron-to-electronRequire-in-main',
visitor: {
CallExpression(path) {
if (!isRequire(path.node)) {
return;
}
const source = path.node.arguments[0].value;
if (!source.startsWith('./')) {
path.node.callee.name = 'electronRequire';
}
},
},
};
};

View 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'));
}
},
},
};
};

View 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);
}
}
},
},
};
};

View 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/',
);
}
}
},
},
};
};

View 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'));
}
},
},
});

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
module.exports = function(babel) {
const t = babel.types;
return {
name: 'infinity-import-react',
visitor: {
Program: {
exit(path, state) {
if (state.get('NEEDS_REACT')) {
path.unshiftContainer('body', [
t.variableDeclaration('var', [
t.variableDeclarator(
t.identifier('React'),
t.callExpression(t.identifier('require'), [
t.stringLiteral('react'),
]),
),
]),
]);
}
},
},
ReferencedIdentifier(path, state) {
// mark react as needing to be imported
if (path.node.name === 'React' && !path.scope.getBinding('React')) {
state.set('NEEDS_REACT', true);
}
// replace Buffer with require('buffer')
if (path.node.name === 'Buffer' && !path.scope.getBinding('Buffer')) {
path.replaceWith(
t.memberExpression(
t.callExpression(t.identifier('require'), [
t.stringLiteral('buffer'),
]),
t.identifier('Buffer'),
),
);
}
},
},
};
};

View File

@@ -0,0 +1,157 @@
/**
* 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');
const staticDir = path.resolve(__dirname, '..');
function transform({filename, options, src}) {
const isPlugin =
options.projectRoot && !__dirname.startsWith(options.projectRoot);
const isMain =
options.projectRoot &&
options.projectRoot === staticDir &&
!options.isTestRunner;
const isTypeScript = filename.endsWith('.tsx') || filename.endsWith('.ts');
const presets = [
isMain
? [
require('../node_modules/@babel/preset-env'),
{targets: {electron: require('electron/package.json').version}},
]
: require('../node_modules/@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('../node_modules/@babel/plugin-transform-modules-commonjs'),
require('../node_modules/@babel/plugin-proposal-object-rest-spread'),
require('../node_modules/@babel/plugin-proposal-class-properties'),
require('../node_modules/@babel/plugin-transform-flow-strip-types'),
require('../node_modules/@babel/plugin-proposal-optional-chaining'),
require('../node_modules/@babel/plugin-proposal-nullish-coalescing-operator'),
require('./dynamic-requires.js'),
);
} else {
plugins.push(
require('../node_modules/@babel/plugin-transform-typescript'),
require('../node_modules/@babel/plugin-proposal-class-properties'),
require('../node_modules/@babel/plugin-transform-modules-commonjs'),
require('../node_modules/@babel/plugin-proposal-optional-chaining'),
require('../node_modules/@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 (process.env.BUILD_HEADLESS) {
plugins.push(require('./electron-stubs.js'));
}
if (!options.isTestRunner) {
if (isMain) {
// For the main Electron process ("static" folder), to avoid issues with
// native node modules, we prevent Metro from resolving any installed modules.
// Instead all of them are just resolved from "node_modules" as usual.
plugins.push(require('./electron-requires-main'));
// Metro bundler messes up "global.process", so we're changing all its occurrences to "global.electronProcess" instead.
// https://github.com/facebook/metro/blob/7e6b3114fc4a9b07a8c0dd3797b1e0c55a4c32ad/packages/metro/src/lib/getPreludeCode.js#L24
plugins.push(require('./electron-process'));
} else {
// 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'));
}
}
if (isPlugin) {
plugins.push(require('./flipper-requires.js'));
} else {
plugins.push(require('./import-react.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},
});
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

1
desktop/static/vis/vis.min.css vendored Normal file

File diff suppressed because one or more lines are too long

115
desktop/static/watchman.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* 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 {Client} from 'fb-watchman';
import {v4 as uuid} from 'uuid';
import path from 'path';
export default class Watchman {
constructor(private rootDir: string) {}
private client?: Client;
private watch?: any;
private relativeRoot?: string;
async initialize(): Promise<void> {
if (this.client) {
return;
}
this.client = new Client();
this.client.setMaxListeners(250);
await new Promise((resolve, reject) => {
const onError = (err: Error) => {
this.client!.removeAllListeners('error');
reject(err);
this.client!.end();
delete this.client;
};
this.client!.once('error', onError);
this.client!.capabilityCheck(
{optional: [], required: ['relative_root']},
error => {
if (error) {
onError(error);
return;
}
this.client!.command(
['watch-project', this.rootDir],
(error, resp) => {
if (error) {
onError(error);
return;
}
if ('warning' in resp) {
console.warn(resp.warning);
}
this.watch = resp.watch;
this.relativeRoot = resp.relative_path;
resolve();
},
);
},
);
});
}
async startWatchFiles(
relativeDir: string,
handler: (resp: any) => void,
options: {excludes: string[]},
): Promise<void> {
if (!this.watch) {
throw new Error(
'Watchman is not initialized, please call "initialize" function and wait for the returned promise completion before calling "startWatchFiles".',
);
}
options = Object.assign({excludes: []}, options);
return new Promise((resolve, reject) => {
this.client!.command(['clock', this.watch], (error, resp) => {
if (error) {
return reject(error);
}
try {
const {clock} = resp;
const sub = {
expression: [
'allof',
['not', ['type', 'd']],
...options!.excludes.map(e => ['not', ['match', e, 'wholename']]),
],
fields: ['name'],
since: clock,
relative_root: this.relativeRoot
? path.join(this.relativeRoot, relativeDir)
: relativeDir,
};
const id = uuid();
this.client!.command(['subscribe', this.watch, id, sub], error => {
if (error) {
return reject(error);
}
this.client!.on('subscription', resp => {
if (resp.subscription !== id || !resp.files) {
return;
}
handler(resp);
});
resolve();
});
} catch (err) {
reject(err);
}
});
});
}
}

3711
desktop/static/yarn.lock Normal file

File diff suppressed because it is too large Load Diff