Remove remaining Node imports from core

Summary:
Removed remaining path / fs imports from Flipper core.

`expand-tide` needed replacement too, but noticed that it never actually rewrites paths since all use cases were already using absolute paths, so removed it instead.

Reviewed By: aigoncharov

Differential Revision: D33017654

fbshipit-source-id: e12f66ef68b5f9e4279411c94445a2fb87249e9a
This commit is contained in:
Michel Weststrate
2021-12-13 05:46:42 -08:00
committed by Facebook GitHub Bot
parent d95b15094f
commit accef856fc
18 changed files with 47 additions and 306 deletions

View File

@@ -30,7 +30,7 @@ export interface Device {
clearLogs(): Promise<void>;
sendMetroCommand(command: string): Promise<void>;
navigateToLocation(location: string): Promise<void>;
screenshot(): Promise<Buffer>;
screenshot(): Promise<Uint8Array>;
}
export type DevicePluginPredicate = (device: Device) => boolean;

View File

@@ -9,11 +9,11 @@
import {Button as AntButton, message} from 'antd';
import React, {useState, useEffect, useCallback} from 'react';
import path from 'path';
import {capture, getCaptureLocation, getFileName} from '../utils/screenshot';
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
import {useStore} from '../utils/useStore';
import {getRenderHostInstance} from '../RenderHost';
import {path} from 'flipper-plugin';
async function openFile(path: string) {
getRenderHostInstance().flipperServer.exec('open-file', path);

View File

@@ -10,8 +10,8 @@
import React, {Component} from 'react';
import BaseDevice from '../devices/BaseDevice';
import {Button, Glyph, colors} from '../ui';
import path from 'path';
import {getRenderHostInstance} from '../RenderHost';
import {path} from 'flipper-plugin';
type OwnProps = {
recordingFinished: (path: string | null) => void;

View File

@@ -16,11 +16,9 @@ import {
colors,
Glyph,
} from '../../ui';
import React, {useState} from 'react';
import {promises as fs} from 'fs';
import {theme} from 'flipper-plugin';
import React, {useState, useEffect} from 'react';
import {getFlipperLib, theme} from 'flipper-plugin';
import {getRenderHostInstance} from '../../RenderHost';
import {useEffect} from 'react';
export const ConfigFieldContainer = styled(FlexRow)({
paddingLeft: 10,
@@ -75,8 +73,8 @@ export function FilePathConfigField(props: {
useEffect(() => {
(async function () {
try {
const stat = await fs.stat(value);
setIsValid(props.isRegularFile !== stat.isDirectory());
const stat = await getFlipperLib().remoteServerContext.fs.stat(value);
setIsValid(props.isRegularFile !== stat.isDirectory);
} catch (_) {
setIsValid(false);
}
@@ -93,8 +91,9 @@ export function FilePathConfigField(props: {
onChange={(e) => {
setValue(e.target.value);
props.onChange(e.target.value);
fs.stat(e.target.value)
.then((stat) => stat.isDirectory())
getFlipperLib()
.remoteServerContext.fs.stat(e.target.value)
.then((stat) => stat.isDirectory)
.then((valid) => {
if (valid !== isValid) {
setIsValid(valid);

View File

@@ -27,7 +27,6 @@ export {PluginClient, Props, KeyboardActions} from './plugin';
export {default as Client} from './Client';
export {reportUsage} from 'flipper-common';
export {default as promiseTimeout} from './utils/promiseTimeout';
export {bufferToBlob} from './utils/screenshot';
export {getPluginKey} from './utils/pluginKey';
export {Notification, Idler} from 'flipper-plugin';
export {IdlerImpl} from './utils/Idler';
@@ -72,8 +71,6 @@ export {default as Checkbox} from './ui/components/Checkbox';
export {default as Orderable} from './ui/components/Orderable';
export {Component, PureComponent} from 'react';
export {default as ContextMenu} from './ui/components/ContextMenu';
export {FileListFiles} from './ui/components/FileList';
export {default as FileList} from './ui/components/FileList';
export {default as View} from './ui/components/View';
export {default as Sidebar} from './ui/components/Sidebar';
export {default as FlexBox} from './ui/components/FlexBox';

View File

@@ -199,14 +199,12 @@ export default class BaseDevice implements Device {
return this.flipperServer.exec('device-supports-screenshot', this.serial);
}
async screenshot(): Promise<Buffer> {
async screenshot(): Promise<Uint8Array> {
if (this.isArchived) {
return Buffer.from([]);
return new Uint8Array();
}
return Buffer.from(
Base64.toUint8Array(
await this.flipperServer.exec('device-take-screenshot', this.serial),
),
return Base64.toUint8Array(
await this.flipperServer.exec('device-take-screenshot', this.serial),
);
}

View File

@@ -16,13 +16,13 @@ import dispatcher, {
getLatestCompatibleVersionOfEachPlugin,
} from '../plugins';
import {BundledPluginDetails, InstalledPluginDetails} from 'flipper-common';
import path from 'path';
import {createRootReducer, State} from '../../reducers/index';
import {getLogger} from 'flipper-common';
import configureStore from 'redux-mock-store';
import TestPlugin from './TestPlugin';
import {_SandyPluginDefinition} from 'flipper-plugin';
import {getRenderHostInstance} from '../../RenderHost';
import path from 'path';
let loadDynamicPluginsMock: jest.Mock;

View File

@@ -1,217 +0,0 @@
/**
* 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 {Component} from 'react';
import path from 'path';
import fs from 'fs';
const EMPTY_MAP = new Map();
const EMPTY_FILE_LIST_STATE = {error: null, files: EMPTY_MAP};
export type FileListFileType = 'file' | 'folder';
export type FileListFile = {
name: string;
src: string;
type: FileListFileType;
size: number;
mtime: number;
atime: number;
ctime: number;
birthtime: number;
};
export type FileListFiles = Array<FileListFile>;
type FileListProps = {
/** Path to the folder */
src: string;
/** Content to be rendered in case of an error */
onError?: (err: Error) => React.ReactNode | null | undefined;
/** Content to be rendered while loading */
onLoad?: () => void;
/** Content to be rendered when the file list is loaded */
onFiles: (files: FileListFiles) => React.ReactNode;
};
type FileListState = {
files: Map<string, FileListFile>;
error: Error | null | undefined;
};
/**
* List the contents of a folder from the user's file system. The file system is watched for
* changes and this list will automatically update.
*/
export default class FileList extends Component<FileListProps, FileListState> {
constructor(props: FileListProps, context: Object) {
super(props, context);
this.state = EMPTY_FILE_LIST_STATE;
}
watcher: fs.FSWatcher | null | undefined;
fetchFile(src: string, name: string): Promise<FileListFile> {
return new Promise((resolve, reject) => {
const loc = path.join(src, name);
fs.lstat(loc, (err, stat) => {
if (err) {
reject(err);
} else {
const details: FileListFile = {
atime: Number(stat.atime),
birthtime:
typeof stat.birthtime === 'object' ? Number(stat.birthtime) : 0,
ctime: Number(stat.ctime),
mtime: Number(stat.mtime),
name,
size: stat.size,
src: loc,
type: stat.isDirectory() ? 'folder' : 'file',
};
resolve(details);
}
});
});
}
fetchFilesFromFolder(
originalSrc: string,
currentSrc: string,
callback: Function,
) {
const hasChangedDir = () => this.props.src !== originalSrc;
let filesSet: Map<string, FileListFile> = new Map();
fs.readdir(currentSrc, (err, files) => {
if (err) {
return callback(err, EMPTY_MAP);
}
let remainingPaths = files.length;
const next = () => {
if (hasChangedDir()) {
return callback(null, EMPTY_MAP);
}
if (!remainingPaths) {
return callback(null, filesSet);
}
const name = files.shift();
if (name) {
this.fetchFile(currentSrc, name)
.then((data) => {
filesSet.set(name, data);
if (data.type == 'folder') {
this.fetchFilesFromFolder(
originalSrc,
path.join(currentSrc, name),
function (err: Error, files: Map<string, FileListFile>) {
if (err) {
return callback(err, EMPTY_MAP);
}
filesSet = new Map([...filesSet, ...files]);
remainingPaths--;
if (!remainingPaths) {
return callback(null, filesSet);
}
},
);
} else {
remainingPaths--;
}
next();
})
.catch((err) => {
return callback(err, EMPTY_MAP);
});
}
};
next();
});
}
fetchFiles(callback?: Function) {
const {src} = this.props;
const setState = (data: FileListState) => {
if (!hasChangedDir()) {
this.setState(data);
}
};
const hasChangedDir = () => this.props.src !== src;
this.fetchFilesFromFolder(
src,
src,
function (err: Error, files: Map<string, FileListFile>) {
setState({error: err, files: files});
if (callback) {
callback();
}
},
);
}
UNSAFE_componentWillReceiveProps(nextProps: FileListProps) {
if (nextProps.src !== this.props.src) {
this.initialFetch(nextProps);
}
}
componentDidMount() {
this.initialFetch(this.props);
}
componentWillUnmount() {
this.removeWatcher();
}
initialFetch(props: FileListProps) {
this.removeWatcher();
fs.access(props.src, fs.constants.R_OK, (err) => {
if (err) {
this.setState({error: err, files: EMPTY_MAP});
return;
}
this.fetchFiles(props.onLoad);
this.watcher = fs.watch(props.src, () => {
this.fetchFiles();
});
this.watcher.on('error', (err) => {
this.setState({error: err, files: EMPTY_MAP});
this.removeWatcher();
});
});
}
removeWatcher() {
if (this.watcher) {
this.watcher.close();
}
}
render() {
const {error, files} = this.state;
const {onError, onFiles} = this.props;
if (error && onError) {
return onError(error);
} else {
return onFiles(Array.from(files.values()));
}
}
}

View File

@@ -62,9 +62,6 @@ export {Component, PureComponent} from 'react';
// context menus and dropdowns
export {default as ContextMenu} from './components/ContextMenu';
// file
export {FileListFile, FileListFiles} from './components/FileList';
export {default as FileList} from './components/FileList';
// utility elements
export {default as View} from './components/View';
export {default as Sidebar} from './components/Sidebar';

View File

@@ -8,7 +8,6 @@
*/
import * as React from 'react';
import path from 'path';
import {getLogger} from 'flipper-common';
import {Store, MiddlewareAPI} from '../reducers';
import {DeviceExport} from '../devices/BaseDevice';
@@ -20,7 +19,6 @@ import {pluginKey} from '../utils/pluginKey';
import {DevicePluginMap, ClientPluginMap} from '../plugin';
import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice';
import fs from 'fs-extra';
import {v4 as uuidv4} from 'uuid';
import {tryCatchReportPlatformFailures} from 'flipper-common';
import {TestIdler} from './Idler';
@@ -33,7 +31,7 @@ import {deconstructClientId} from 'flipper-common';
import {processMessageQueue} from './messageQueue';
import {getPluginTitle} from './pluginUtils';
import {capture} from './screenshot';
import {Dialog, Idler} from 'flipper-plugin';
import {Dialog, getFlipperLib, Idler, path} from 'flipper-plugin';
import {ClientQuery} from 'flipper-common';
import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl';
import ShareSheetExportFile from '../chrome/ShareSheetExportFile';
@@ -521,7 +519,10 @@ export const exportStoreToFile = (
}> => {
return exportStore(store, includeSupportDetails, idler, statusUpdate).then(
async ({serializedString, fetchMetaDataErrors}) => {
await fs.writeFile(exportFilePath, serializedString);
await getFlipperLib().remoteServerContext.fs.writeFile(
exportFilePath,
serializedString,
);
store.dispatch(resetSupportFormV2State());
return {fetchMetaDataErrors};
},
@@ -584,17 +585,17 @@ export function importDataToStore(source: string, data: string, store: Store) {
}
}
export const importFileToStore = (file: string, store: Store) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
console.error(
`[exportData] importFileToStore for file ${file} failed:`,
err,
);
return;
}
export const importFileToStore = async (file: string, store: Store) => {
try {
const data = await getFlipperLib().remoteServerContext.fs.readFile(file);
importDataToStore(file, data, store);
});
} catch (err) {
console.error(
`[exportData] importFileToStore for file ${file} failed:`,
err,
);
return;
}
};
export function canOpenDialog() {

View File

@@ -7,15 +7,14 @@
* @format
*/
import util from 'util';
import {exec as execImport} from 'child_process';
import {getFlipperLib} from 'flipper-plugin';
const cmd = 'klist --json';
const endWith = '@THEFACEBOOK.COM';
export async function isFBEmployee(): Promise<boolean> {
return util
.promisify(execImport)(cmd)
return getFlipperLib()
.remoteServerContext.childProcess.exec(cmd)
.then(
(stdobj: {stderr: string; stdout: string}) => {
const principal = String(JSON.parse(stdobj.stdout).principal);

View File

@@ -7,17 +7,15 @@
* @format
*/
import fs from 'fs';
import path from 'path';
import BaseDevice from '../devices/BaseDevice';
import {reportPlatformFailures} from 'flipper-common';
import expandTilde from 'expand-tilde';
import {getRenderHostInstance} from '../RenderHost';
import {getFlipperLib, path} from 'flipper-plugin';
export function getCaptureLocation() {
return expandTilde(
return (
getRenderHostInstance().serverConfig.processConfig.screenCapturePath ||
getRenderHostInstance().serverConfig.paths.desktopPath,
getRenderHostInstance().serverConfig.paths.desktopPath
);
}
@@ -34,33 +32,14 @@ export async function capture(device: BaseDevice): Promise<string> {
}
const pngPath = path.join(getCaptureLocation(), getFileName('png'));
return reportPlatformFailures(
device.screenshot().then((buffer) => writeBufferToFile(pngPath, buffer)),
// TODO: there is no reason to read the screenshot first, grab it over the websocket, than send it back
// again to write in a file, probably easier to change screenshot api to `device.screenshot(): path`
device
.screenshot()
.then((buffer) =>
getFlipperLib().remoteServerContext.fs.writeFileBinary(pngPath, buffer),
)
.then(() => pngPath),
'captureScreenshot',
);
}
/**
* Writes a buffer to a specified file path.
* Returns a Promise which resolves to the file path.
*/
export const writeBufferToFile = (
filePath: string,
buffer: Buffer,
): Promise<string> => {
return new Promise((resolve, reject) => {
fs.writeFile(filePath, buffer, (err) => {
if (err) {
reject(err);
} else {
resolve(filePath);
}
});
});
};
/**
* Creates a Blob from a Buffer
*/
export const bufferToBlob = (buffer: Buffer): Blob => {
return new Blob([buffer]);
};

View File

@@ -83,7 +83,6 @@
"@types/deep-equal": "^1.0.1",
"@types/detect-port": "^1.3.1",
"@types/electron-devtools-installer": "^2.2.0",
"@types/expand-tilde": "^2.0.0",
"@types/express": "^4.17.13",
"@types/fb-watchman": "^2.0.1",
"@types/form-data": "^2.2.1",
@@ -150,7 +149,6 @@
"eslint-plugin-react": "^7.27.1",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-rulesdir": "^0.2.1",
"expand-tilde": "^2.0.2",
"express": "^4.15.2",
"fb-watchman": "^2.0.1",
"flipper-babel-transformer": "0.0.0",

View File

@@ -13,7 +13,6 @@
"decompress": "^4.2.1",
"decompress-targz": "^4.1.1",
"decompress-unzip": "^4.0.1",
"expand-tilde": "^2.0.2",
"flipper-common": "^0.0.0",
"fs-extra": "^10.0.0",
"live-plugin-manager": "^0.17.0",

View File

@@ -9,7 +9,6 @@
import path from 'path';
import fs from 'fs-extra';
import expandTilde from 'expand-tilde';
import {getPluginSourceFolders} from './pluginPaths';
import pmap from 'p-map';
import pfilter from 'p-filter';
@@ -49,7 +48,6 @@ export async function getSourcePlugins(): Promise<InstalledPluginDetails[]> {
async function entryPointForPluginFolder(
pluginsDir: string,
): Promise<{[key: string]: InstalledPluginDetails}> {
pluginsDir = expandTilde(pluginsDir);
if (!(await fs.pathExists(pluginsDir))) {
return {};
}

View File

@@ -11,7 +11,6 @@ import path from 'path';
import {homedir} from 'os';
import fs from 'fs-extra';
import pFilter from 'p-filter';
import expandTilde from 'expand-tilde';
const flipperDataDir = path.join(homedir(), '.flipper');
@@ -44,7 +43,7 @@ export async function getPluginSourceFolders(): Promise<string[]> {
}
pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins', 'public'));
pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins', 'fb'));
return pFilter(pluginFolders.map(expandTilde), (p) => fs.pathExists(p));
return pFilter(pluginFolders, (p) => fs.pathExists(p));
}
export function getPluginInstallationDir(name: string) {

View File

@@ -8,7 +8,6 @@
* @flow strict-local
*/
import {bufferToBlob} from 'flipper';
import {RequiredParametersDialog} from './components';
import {
removeBookmarkFromDB,
@@ -73,14 +72,14 @@ export function plugin(client: PluginClient<Events, Methods>) {
draft.unshift(navigationEvent);
});
const screenshot: Buffer = await client.device.screenshot();
const screenshot = await client.device.screenshot();
if (screenshot.byteLength === 0) {
console.warn(
'[navigation] Could not retrieve valid screenshot from the device.',
);
return;
}
const blobURL = URL.createObjectURL(bufferToBlob(screenshot));
const blobURL = URL.createObjectURL(new Blob([screenshot.buffer]));
// this process is async, make sure we update the correct one..
const navigationEventIndex = navigationEvents
.get()

View File

@@ -2620,11 +2620,6 @@
resolved "https://registry.yarnpkg.com/@types/electron-devtools-installer/-/electron-devtools-installer-2.2.0.tgz#32ee4ebbe99b3daf9847a6d2097dc00b5de94f10"
integrity sha512-HJNxpaOXuykCK4rQ6FOMxAA0NLFYsf7FiPFGmab0iQmtVBHSAfxzy3MRFpLTTDDWbV0yD2YsHOQvdu8yCqtCfw==
"@types/expand-tilde@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/expand-tilde/-/expand-tilde-2.0.0.tgz#c01a706675b9d60931bf6a7dc7dfa45d63540c97"
integrity sha512-17h/6MRHoetV2QVUVnUfrmaFCXNIFJ3uDJmXlklX2xDtlEb1W0OXLgP+qwND2Ibg/PtQfQi0vx19KGuPayjLiw==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
version "4.17.19"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d"