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
3
desktop/static/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
See [static/CHANGELOG.md](static/CHANGELOG.md)
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
50
desktop/static/PortForwardingMacApp.app/Contents/Info.plist
Normal 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>
|
||||
BIN
desktop/static/PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp
Executable file
1
desktop/static/PortForwardingMacApp.app/Contents/PkgInfo
Normal file
@@ -0,0 +1 @@
|
||||
APPL????
|
||||
86
desktop/static/SupportJSClientPreload.js
Normal 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));
|
||||
});
|
||||
20
desktop/static/SupportRequestPreload.js
Normal 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);
|
||||
});
|
||||
};
|
||||
2
desktop/static/_watchmanconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
BIN
desktop/static/actions-artifacts.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
16
desktop/static/anchor.svg
Normal 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 |
318
desktop/static/compilePlugins.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
desktop/static/dmgBackground.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
desktop/static/document-icons/document.icns
Normal file
BIN
desktop/static/document-icons/document.ico
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
desktop/static/document-icons/document.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
37
desktop/static/electron-stubs.notjs
Normal 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;}
|
||||
},
|
||||
}
|
||||
12
desktop/static/globalTestSetup.js
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
|
||||
*/
|
||||
|
||||
global.fetch = require('jest-fetch-mock');
|
||||
|
||||
require('immer').enableMapSet();
|
||||
1746
desktop/static/graphiql/graphiql.css
Normal file
BIN
desktop/static/icon.icns
Normal file
BIN
desktop/static/icon.ico
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
desktop/static/icon.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
379
desktop/static/icons.json
Normal 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
|
||||
]
|
||||
}
|
||||
BIN
desktop/static/icons/accessibility.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
9
desktop/static/icons/android.svg
Normal 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 |
BIN
desktop/static/icons/componentkit-logo.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
9
desktop/static/icons/ios.svg
Normal 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 |
BIN
desktop/static/icons/litho-logo.png
Normal file
|
After Width: | Height: | Size: 812 B |
7
desktop/static/icons/sidebar_bottom.svg
Normal 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 |
7
desktop/static/icons/sidebar_left.svg
Normal 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 |
7
desktop/static/icons/sidebar_right.svg
Normal 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 |
96
desktop/static/index.dev.html
Normal 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
@@ -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
@@ -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');
|
||||
99
desktop/static/launcher.ts
Normal 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
@@ -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);
|
||||
}
|
||||
}
|
||||
BIN
desktop/static/native-modules/keytar-darwin.node
Executable file
BIN
desktop/static/native-modules/keytar-linux.node
Executable file
BIN
desktop/static/native-modules/keytar-win32.node
Normal file
38
desktop/static/package.json
Normal 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
|
After Width: | Height: | Size: 4.7 KiB |
63
desktop/static/setup.ts
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 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
@@ -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;
|
||||
}
|
||||
66
desktop/static/transforms/__tests__/electron-process.node.js
Normal 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\\";"
|
||||
`);
|
||||
});
|
||||
25
desktop/static/transforms/__tests__/electron-stubs.node.js
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
|
||||
*/
|
||||
|
||||
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');
|
||||
});
|
||||
72
desktop/static/transforms/__tests__/flipper-requires.node.js
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
|
||||
*/
|
||||
|
||||
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");');
|
||||
});
|
||||
34
desktop/static/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'));
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
33
desktop/static/transforms/electron-process.js
Normal 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';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
37
desktop/static/transforms/electron-requires-main.js
Normal 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';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
94
desktop/static/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/static/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/static/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/static/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'));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
53
desktop/static/transforms/import-react.js
Normal 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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
157
desktop/static/transforms/index.js
Normal 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},
|
||||
});
|
||||
},
|
||||
};
|
||||
BIN
desktop/static/uiperf/screenshot.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
1
desktop/static/vis/vis.min.css
vendored
Normal file
115
desktop/static/watchman.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||