Extract environment config initialisation to server-core

Summary: This diff makes most stuff that is read from the `os` package, and version info etc available from the `serverConfig` object, so that flipper-ui-core no longer needs the `os` package.

Reviewed By: passy

Differential Revision: D32694848

fbshipit-source-id: 93af1e95d898da9aaf351a6970b5a7652ee835c8
This commit is contained in:
Michel Weststrate
2021-12-08 04:25:28 -08:00
committed by Facebook GitHub Bot
parent de59bbedd2
commit 2a4fe77404
22 changed files with 199 additions and 135 deletions

View File

@@ -20,6 +20,7 @@ import type {RenderHost} from 'flipper-ui-core';
import os from 'os';
import {
FlipperServerImpl,
getEnvironmentInfo,
getGatekeepers,
loadLauncherSettings,
loadProcessConfig,
@@ -30,6 +31,7 @@ import {
getLogger,
isTest,
Logger,
parseEnvironmentVariables,
setLoggerInstance,
Settings,
} from 'flipper-common';
@@ -61,9 +63,10 @@ async function start() {
const app = remote.app;
const execPath = process.execPath || remote.process.execPath;
const appPath = app.getAppPath();
const staticPath = getStaticDir();
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
const env = process.env;
const environmentInfo = await getEnvironmentInfo(staticPath, isProduction);
const logger = createDelegatedLogger();
setLoggerInstance(logger);
@@ -80,14 +83,15 @@ async function start() {
const flipperServer = new FlipperServerImpl(
{
env,
gatekeepers: getGatekeepers(),
isProduction,
environmentInfo,
env: parseEnvironmentVariables(env),
// TODO: make userame parameterizable
gatekeepers: getGatekeepers(environmentInfo.os.unixname),
paths: {
appPath,
homePath: app.getPath('home'),
execPath,
staticPath: getStaticDir(),
staticPath,
tempPath: app.getPath('temp'),
desktopPath: app.getPath('desktop'),
},

View File

@@ -16,7 +16,12 @@ import {
OS as PluginOS,
UpdatablePluginDetails,
} from './PluginDetails';
import {LauncherSettings, ProcessConfig, Settings} from './settings';
import {
EnvironmentInfo,
LauncherSettings,
ProcessConfig,
Settings,
} from './settings';
// In the future, this file would deserve it's own package, as it doesn't really relate to plugins.
// Since flipper-plugin however is currently shared among server, client and defines a lot of base types, leaving it here for now.
@@ -177,13 +182,38 @@ export type FlipperServerCommands = {
'plugins-remove-plugins': (names: string[]) => Promise<void>;
};
export type ENVIRONMENT_VARIABLES =
| 'NODE_ENV'
| 'DEV_SERVER_URL'
| 'CONFIG'
| 'FLIPPER_ENABLED_PLUGINS'
| 'FB_ONDEMAND'
| 'FLIPPER_INTERNGRAPH_URL';
/**
* White listed environment variables that can be used in Flipper UI / plugins
*/
const environmentVariables = {
NODE_ENV: 1,
DEV_SERVER_URL: 1,
CONFIG: 1,
FLIPPER_ENABLED_PLUGINS: 1,
FB_ONDEMAND: 1,
FLIPPER_INTERNGRAPH_URL: 1,
JEST_WORKER_ID: 1,
FLIPPER_DOCS_BASE_URL: 1,
FLIPPER_NO_PLUGIN_AUTO_UPDATE: 1,
FLIPPER_NO_PLUGIN_MARKETPLACE: 1,
HOME: 1,
METRO_PORT_ENV_VAR: 1,
} as const;
export type ENVIRONMENT_VARIABLES = keyof typeof environmentVariables;
/**
* Grab all environment variables from a process.env object, without leaking variables which usage isn't declared in ENVIRONMENT_VARIABLES
*/
export function parseEnvironmentVariables(
baseEnv: any,
): Partial<Record<ENVIRONMENT_VARIABLES, string>> {
const result: any = {};
Object.keys(environmentVariables).forEach((k) => {
result[k] = baseEnv[k];
});
return result;
}
type ENVIRONMENT_PATHS =
| 'appPath'
@@ -194,7 +224,6 @@ type ENVIRONMENT_PATHS =
| 'desktopPath';
export type FlipperServerConfig = {
isProduction: boolean;
gatekeepers: Record<string, boolean>;
env: Partial<Record<ENVIRONMENT_VARIABLES, string>>;
paths: Record<ENVIRONMENT_PATHS, string>;
@@ -202,6 +231,7 @@ export type FlipperServerConfig = {
launcherSettings: LauncherSettings;
processConfig: ProcessConfig;
validWebSocketOrigins: string[];
environmentInfo: EnvironmentInfo;
};
export interface FlipperServer {

View File

@@ -56,7 +56,7 @@ export type LauncherSettings = {
// Settings that primarily only apply to Eelectron atm
// TODO: further separte between flipper-ui config and Electron config
export type ProcessConfig = {
disabledPlugins: Set<string>;
disabledPlugins: string[];
lastWindowPosition: {
x: number;
y: number;
@@ -68,3 +68,21 @@ export type ProcessConfig = {
// Controls whether to delegate to the launcher if present.
launcherEnabled: boolean;
};
export type EnvironmentInfo = {
processId: number;
isProduction: boolean;
releaseChannel: ReleaseChannel;
flipperReleaseRevision?: string;
appVersion: string;
os: {
arch: string;
platform: string;
unixname: string;
};
versions: {
electron?: string;
node: string;
platform: string;
};
};

View File

@@ -12,6 +12,7 @@ import os from 'os';
import yargs from 'yargs';
import {
FlipperServerImpl,
getEnvironmentInfo,
loadLauncherSettings,
loadProcessConfig,
loadSettings,
@@ -21,6 +22,7 @@ import {
Logger,
DeviceDescription,
setLoggerInstance,
parseEnvironmentVariables,
} from 'flipper-common';
import path from 'path';
import {stdout} from 'process';
@@ -54,6 +56,7 @@ const argv = yargs
async function start(deviceTitle: string, appName: string, pluginId: string) {
return new Promise(async (_resolve, reject) => {
const staticPath = path.resolve(__dirname, '..', '..', 'static');
let device: DeviceDescription | undefined;
let deviceResolver: () => void;
const devicePromise: Promise<void> = new Promise((resolve) => {
@@ -68,15 +71,16 @@ async function start(deviceTitle: string, appName: string, pluginId: string) {
console.debug = () => {};
console.info = console.error;
const environmentInfo = await getEnvironmentInfo(staticPath, false);
// TODO: initialise FB user manager to be able to do certificate exchange
const server = new FlipperServerImpl(
{
env: process.env,
environmentInfo,
env: parseEnvironmentVariables(process.env),
gatekeepers: {},
isProduction: false,
paths: {
staticPath: path.resolve(__dirname, '..', '..', 'static'),
staticPath,
tempPath: os.tmpdir(),
appPath: `/dev/null`,
homePath: `/dev/null`,

View File

@@ -30,7 +30,7 @@ export function loadDistilleryGK(
}
export default class GK {
static init() {}
static init(_username: string) {}
static get(id: GKID): boolean {
if (process.env.NODE_ENV === 'test' && id === TEST_PASSING_GK) {

View File

@@ -11,16 +11,17 @@ export {FlipperServerImpl} from './FlipperServerImpl';
export {loadSettings} from './utils/settings';
export {loadLauncherSettings} from './utils/launcherSettings';
export {loadProcessConfig} from './utils/processConfig';
export {getEnvironmentInfo} from './utils/environmentInfo';
import GKImplementation from './fb-stubs/GK';
export {setupPrefetcher} from './fb-stubs/Prefetcher';
let loaded = false;
export function getGatekeepers(): Record<string, boolean> {
export function getGatekeepers(username: string): Record<string, boolean> {
if (!loaded) {
// this starts fetching gatekeepers, note that they will only be available on next restart!
GKImplementation.init();
GKImplementation.init(username);
loaded = true;
}
return GKImplementation.allGKs();

View File

@@ -21,7 +21,7 @@ test('config is decoded from env', () => {
});
expect(config).toEqual({
disabledPlugins: new Set(['pluginA', 'pluginB', 'pluginC']),
disabledPlugins: ['pluginA', 'pluginB', 'pluginC'],
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
launcherMsg: 'wubba lubba dub dub',
screenCapturePath: '/my/screenshot/path',
@@ -31,7 +31,7 @@ test('config is decoded from env', () => {
test('config is decoded from env with defaults', () => {
expect(loadProcessConfig({CONFIG: '{}'})).toEqual({
disabledPlugins: new Set([]),
disabledPlugins: [],
lastWindowPosition: undefined,
launcherMsg: undefined,
screenCapturePath: undefined,

View File

@@ -0,0 +1,57 @@
/**
* 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-extra';
import path from 'path';
import {EnvironmentInfo, ReleaseChannel} from 'flipper-common';
export async function getEnvironmentInfo(
staticPath: string,
isProduction: boolean,
): Promise<EnvironmentInfo> {
const packageJson = await fs.readJSON(
path.resolve(staticPath, 'package.json'),
);
const releaseChannel: ReleaseChannel =
process.env.FLIPPER_RELEASE_CHANNEL === 'insiders'
? ReleaseChannel.INSIDERS
: process.env.FLIPPER_RELEASE_CHANNEL === 'stable'
? ReleaseChannel.STABLE
: packageJson.releaseChannel === 'insiders'
? ReleaseChannel.INSIDERS
: ReleaseChannel.STABLE;
// This is provided as part of the bundling process for headless.
const flipperReleaseRevision =
(global as any).__REVISION__ ?? packageJson.revision;
const appVersion =
process.env.FLIPPER_FORCE_VERSION ??
(isProduction ? packageJson.version : '0.0.0');
return {
processId: process.pid,
isProduction,
releaseChannel,
flipperReleaseRevision,
appVersion,
os: {
arch: process.arch,
platform: process.platform,
unixname: os.userInfo().username,
},
versions: {
electron: process.versions.electron,
node: process.versions.node,
platform: os.release(),
},
};
}

View File

@@ -12,7 +12,7 @@ import {ProcessConfig} from 'flipper-common';
export function loadProcessConfig(env: NodeJS.ProcessEnv): ProcessConfig {
const json = JSON.parse(env.CONFIG || '{}');
return {
disabledPlugins: new Set<string>(json.disabledPlugins || []),
disabledPlugins: json.disabledPlugins || [],
lastWindowPosition: json.lastWindowPosition,
launcherMsg: json.launcherMsg,
screenCapturePath: json.screenCapturePath,

View File

@@ -9,13 +9,13 @@
import chalk from 'chalk';
import path from 'path';
import {startFlipperServer} from './startFlipperServer';
import {startBaseServer} from './startBaseServer';
import {startSocketServer} from './startSocketServer';
// TODO: currently flipper-server is only suitable for development,
// needs to be come independently runnable, prebundled, distributed, etc!
// in future require conditionally
import {startWebServerDev} from './startWebServerDev';
import {startFlipperServer} from './startFlipperServer';
import {startBaseServer} from './startBaseServer';
import {startSocketServer} from './startSocketServer';
const PORT = 52342;
const rootDir = path.resolve(__dirname, '..', '..');

View File

@@ -14,9 +14,10 @@ import {
loadLauncherSettings,
loadProcessConfig,
loadSettings,
getEnvironmentInfo,
} from 'flipper-server-core';
import {
ENVIRONMENT_VARIABLES,
parseEnvironmentVariables,
isTest,
Logger,
setLoggerInstance,
@@ -26,7 +27,7 @@ import fs from 'fs';
export async function startFlipperServer(
rootDir: string,
staticDir: string,
staticPath: string,
): Promise<FlipperServerImpl> {
if (os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
@@ -57,7 +58,7 @@ export async function startFlipperServer(
try {
if (!isTest()) {
keytar = require(path.join(
staticDir,
staticPath,
'native-modules',
`keytar-${process.platform}.node`,
));
@@ -66,20 +67,19 @@ export async function startFlipperServer(
console.error('Failed to load keytar:', e);
}
const envVars: Partial<Record<ENVIRONMENT_VARIABLES, string | undefined>> =
Object.fromEntries(
([] as ENVIRONMENT_VARIABLES[]).map((v) => [v, process.env[v]] as const),
);
const environmentInfo = await getEnvironmentInfo(staticPath, isProduction);
const flipperServer = new FlipperServerImpl(
{
env: envVars,
gatekeepers: getGatekeepers(),
isProduction,
environmentInfo,
env: parseEnvironmentVariables(process.env),
// TODO: make userame parameterizable
gatekeepers: getGatekeepers(environmentInfo.os.unixname),
paths: {
appPath,
homePath: os.homedir(),
execPath,
staticPath: staticDir,
staticPath: staticPath,
tempPath: os.tmpdir(),
desktopPath: desktopPath,
},

View File

@@ -8,15 +8,13 @@
*/
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
import {getRenderHostInstance} from 'flipper-ui-core';
import {getRenderHostInstance, RenderHost} from 'flipper-ui-core';
export function initializeRenderHost(
flipperServer: FlipperServer,
flipperServerConfig: FlipperServerConfig,
) {
window.FlipperRenderHostInstance = {
processId: 0,
isProduction: window.flipperConfig.debug !== true,
readTextFromClipboard() {
// TODO:
return undefined;
@@ -72,7 +70,7 @@ export function initializeRenderHost(
// eslint-disable-next-line no-eval
return eval(source);
},
};
} as RenderHost;
}
function getDefaultPluginsIndex() {

View File

@@ -44,8 +44,6 @@ type ChildProcessEvents = {
* Utilities provided by the render host, e.g. Electron, the Browser, etc
*/
export interface RenderHost {
readonly processId: number;
readonly isProduction: boolean;
readTextFromClipboard(): string | undefined;
writeTextToClipboard(text: string): void;
/**

View File

@@ -77,7 +77,7 @@ test('checkDisabled', () => {
try {
hostConfig.processConfig = {
...orig,
disabledPlugins: new Set([disabledPlugin]),
disabledPlugins: [disabledPlugin],
};
const disabled = checkDisabled([]);

View File

@@ -210,7 +210,7 @@ export const checkDisabled = (
config.env.FLIPPER_ENABLED_PLUGINS.split(','),
);
}
disabledList = config.processConfig.disabledPlugins;
disabledList = new Set(config.processConfig.disabledPlugins);
} catch (e) {
console.error('Failed to compute enabled/disabled plugins', e);
}

View File

@@ -95,7 +95,8 @@ export default (store: Store, logger: Logger) => {
const oldExitData = loadExitData();
if (oldExitData) {
const isReload = renderHost.processId === oldExitData.pid;
const isReload =
renderHost.serverConfig.environmentInfo.processId === oldExitData.pid;
const timeSinceLastStartup =
Date.now() - parseInt(oldExitData.lastSeen, 10);
// console.log(isReload ? 'reload' : 'restart', oldExitData);
@@ -370,7 +371,7 @@ export function persistExitData(
? deconstructClientId(state.selectedAppId).app
: '',
cleanExit,
pid: getRenderHostInstance().processId,
pid: getRenderHostInstance().serverConfig.environmentInfo.processId,
};
window.localStorage.setItem(
flipperExitDataKey,

View File

@@ -1,15 +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 {readCurrentRevision} from '../packageMetadata';
test('readCurrentRevision does not return something meaningful in dev mode', async () => {
const ret = await readCurrentRevision();
expect(ret).toBeUndefined();
});

View File

@@ -23,7 +23,6 @@ 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 {readCurrentRevision} from './packageMetadata';
import {tryCatchReportPlatformFailures} from 'flipper-common';
import {TestIdler} from './Idler';
import {setStaticView} from '../reducers/connections';
@@ -243,7 +242,8 @@ async function addSaltToDeviceSerial({
}
return {...notif, client: notif.client.replace(serial, newSerial)};
});
const revision: string | undefined = await readCurrentRevision();
const revision: string | undefined =
getRenderHostInstance().serverConfig.environmentInfo.flipperReleaseRevision;
return {
fileVersion: getAppVersion() || 'unknown',
flipperReleaseRevision: revision,

View File

@@ -10,13 +10,10 @@
// Use of sync methods is cached.
/* eslint-disable node/no-sync */
import os from 'os';
import isProduction from './isProduction';
import fs from 'fs-extra';
import {getStaticPath} from './pathUtils';
import type {State, Store} from '../reducers/index';
import {sideEffect} from './sideEffect';
import {Logger, isTest, deconstructClientId} from 'flipper-common';
import {Logger, deconstructClientId} from 'flipper-common';
import {getRenderHostInstance} from '../RenderHost';
type PlatformInfo = {
arch: string;
@@ -83,15 +80,13 @@ export default (store: Store, _logger: Logger) => {
*/
export function getInfo(): Info {
if (!platformInfo) {
const envInfo = getRenderHostInstance().serverConfig.environmentInfo;
platformInfo = {
arch: process.arch,
platform: process.platform,
unixname: os.userInfo().username,
versions: {
electron: process.versions.electron,
node: process.versions.node,
platform: os.release(),
},
arch: envInfo.os.arch,
platform: envInfo.os.platform,
unixname: envInfo.os.unixname,
versions: envInfo.versions,
};
}
return {
@@ -100,18 +95,8 @@ export function getInfo(): Info {
};
}
let APP_VERSION: string | undefined;
export function getAppVersion(): string {
return (APP_VERSION =
APP_VERSION ??
process.env.FLIPPER_FORCE_VERSION ??
(isTest()
? '0.0.0'
: (isProduction()
? fs.readJsonSync(getStaticPath('package.json'), {
throws: false,
})?.version
: require('../../package.json').version) ?? '0.0.0'));
return getRenderHostInstance().serverConfig.environmentInfo.appVersion;
}
export function stringifyInfo(info: Info): string {

View File

@@ -10,5 +10,5 @@
import {getRenderHostInstance} from '../RenderHost';
export default function isProduction() {
return getRenderHostInstance().isProduction;
return getRenderHostInstance().serverConfig.environmentInfo.isProduction;
}

View File

@@ -1,33 +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 lodash from 'lodash';
import path from 'path';
import fs from 'fs';
import {promisify} from 'util';
import {getRenderHostInstance} from '../RenderHost';
const getPackageJSON = async () => {
const base = getRenderHostInstance().serverConfig.paths.appPath;
const content = await promisify(fs.readFile)(
path.join(base, 'package.json'),
'utf-8',
);
return JSON.parse(content);
};
export const readCurrentRevision: () => Promise<string | undefined> =
lodash.memoize(async () => {
// This is provided as part of the bundling process for headless.
if (global.__REVISION__) {
return global.__REVISION__;
}
const json = await getPackageJSON();
return json.revision;
});

View File

@@ -11,16 +11,19 @@
import {cleanup} from '@testing-library/react';
import {resolve} from 'path';
import {tmpdir} from 'os';
import os from 'os';
window.FlipperRenderHostInstance = createStubRenderHost();
require('../flipper-ui-core/src/fb-stubs/Logger').init(undefined, {
isTest: true,
});
import {TestUtils} from 'flipper-plugin';
import {FlipperServerConfig, ReleaseChannel, Tristate} from 'flipper-common';
import {
FlipperServerConfig,
ReleaseChannel,
Tristate,
parseEnvironmentVariables,
} from 'flipper-common';
// Only import the type!
import type {RenderHost} from 'flipper-ui-core';
const test = global.test;
@@ -92,12 +95,27 @@ Object.defineProperty(window, 'matchMedia', {
function createStubRenderHost(): RenderHost {
const rootPath = resolve(__dirname, '..');
const stubConfig: FlipperServerConfig = {
env: {...process.env},
environmentInfo: {
processId: process.pid,
appVersion: '0.0.0',
isProduction: false,
releaseChannel: ReleaseChannel.DEFAULT,
flipperReleaseRevision: '000',
os: {
arch: process.arch,
platform: process.platform,
unixname: os.userInfo().username,
},
versions: {
node: process.versions.node,
platform: os.release(),
},
},
env: parseEnvironmentVariables(process.env),
gatekeepers: {
TEST_PASSING_GK: true,
TEST_FAILING_GK: false,
},
isProduction: false,
launcherSettings: {
ignoreLocalPin: false,
releaseChannel: ReleaseChannel.DEFAULT,
@@ -108,10 +126,10 @@ function createStubRenderHost(): RenderHost {
execPath: process.execPath,
homePath: `/dev/null`,
staticPath: resolve(rootPath, 'static'),
tempPath: tmpdir(),
tempPath: os.tmpdir(),
},
processConfig: {
disabledPlugins: new Set(),
disabledPlugins: [],
lastWindowPosition: null,
launcherEnabled: false,
launcherMsg: null,
@@ -135,8 +153,6 @@ function createStubRenderHost(): RenderHost {
};
return {
processId: -1,
isProduction: false,
readTextFromClipboard() {
return '';
},