diff --git a/lib/flowMain.js b/lib/flowMain.js index 91e4b55..da456b6 100644 --- a/lib/flowMain.js +++ b/lib/flowMain.js @@ -19,6 +19,8 @@ import {DeclarationSupport} from './flowDeclaration'; import {setupDiagnostics} from './flowDiagnostics'; import {checkNode, checkFlow, isFlowEnabled} from './utils' +import {clearWorkspaceCaches} from './pkg/flow-base/lib/FlowHelpers' + import {setupLogging} from "./flowLogging" type Context = { @@ -36,9 +38,11 @@ export function activate(context: Context): void { return } global.vscode = vscode + setupLogging() checkNode() checkFlow() + // Language features languages.forEach(lang => { context.subscriptions.push(vscode.languages.registerHoverProvider(lang, new HoverSupport)); @@ -50,3 +54,7 @@ export function activate(context: Context): void { // Diagnostics setupDiagnostics(context.subscriptions); } + +vscode.workspace.onDidChangeConfiguration(params => { + clearWorkspaceCaches(); +}); diff --git a/lib/pkg/flow-base/lib/FlowHelpers.js b/lib/pkg/flow-base/lib/FlowHelpers.js index 6ff0928..a22951f 100644 --- a/lib/pkg/flow-base/lib/FlowHelpers.js +++ b/lib/pkg/flow-base/lib/FlowHelpers.js @@ -124,8 +124,14 @@ function isOptional(param: string): boolean { return lastChar === '?'; } +function clearWorkspaceCaches() { + flowPathCache.reset(); + flowConfigDirCache.reset(); + global.cachedPathToFlowBin = undefined; +} + async function isFlowInstalled(): Promise { - const flowPath = getPathToFlow(); + const flowPath = await getPathToFlow(); if (!flowPathCache.has(flowPath)) { flowPathCache.set(flowPath, await canFindFlow(flowPath)); } @@ -144,12 +150,41 @@ async function canFindFlow(flowPath: string): Promise { } /** - * @return The path to Flow on the user's machine. It is recommended not to cache the result of this - * function in case the user updates his or her preferences in Atom, in which case the return - * value will be stale. + * @return The path to Flow on the user's machine. First using the the user's + * config, then looking into the node_modules for the project. + * + * It is cached, so it is expected that changing the users settings will + * trigger a call to `clearWorkspaceCaches`. + */ + +async function getPathToFlow(): Promise { + if (global.cachedPathToFlowBin) { return global.cachedPathToFlowBin } + + const config = global.vscode.workspace.getConfiguration('flow'); + const shouldUseNodeModule = config.get('useNPMPackagedFlow'); + + if (shouldUseNodeModule && await canFindFlow(nodeModuleFlowLocation())){ + global.cachedPathToFlowBin = nodeModuleFlowLocation(); + return global.cachedPathToFlowBin + } + + const userPath = config.get('pathToFlow'); + if (await canFindFlow(userPath)) { + global.cachedPathToFlowBin = userPath + return global.cachedPathToFlowBin + } + + // Final fallback, mainly so we complete the promise implmentation + return "flow" +} + +/** + * @returnThe potential path to Flow on the user's machine if they are using NPM/Yarn to manage + * their installs of flow. */ -function getPathToFlow(): string { - return global.vscode.workspace.getConfiguration('flow').get('pathToFlow') +function nodeModuleFlowLocation(): string { + const flowBin = process.platform === 'win32' ? 'flow.cmd' : 'flow' + return `${global.vscode.workspace.rootPath}/node_modules/.bin/${flowBin}` } function getStopFlowOnExit(): boolean { @@ -192,4 +227,5 @@ module.exports = { processAutocompleteItem, groupParamNames, flowCoordsToAtomCoords, + clearWorkspaceCaches }; diff --git a/lib/pkg/flow-base/lib/FlowProcess.js b/lib/pkg/flow-base/lib/FlowProcess.js index 0dcc5dd..4b01a93 100644 --- a/lib/pkg/flow-base/lib/FlowProcess.js +++ b/lib/pkg/flow-base/lib/FlowProcess.js @@ -139,7 +139,8 @@ export class FlowProcess { // don't want to log because it just means the server is busy and we don't want to wait. if (!couldRetry && !suppressErrors) { // not sure what happened, but we'll let the caller deal with it - logger.error(`Flow failed: flow ${args.join(' ')}. Error: ${JSON.stringify(e)}`); + const pathToFlow = await getPathToFlow(); + logger.error(`Flow failed: ${pathToFlow} ${args.join(' ')}. Error: ${JSON.stringify(e)}`); } throw e; } @@ -153,7 +154,7 @@ export class FlowProcess { /** Starts a Flow server in the current root */ async _startFlowServer(): Promise { - const pathToFlow = getPathToFlow(); + const pathToFlow = await getPathToFlow(); // `flow server` will start a server in the foreground. asyncExecute // will not resolve the promise until the process exits, which in this // case is never. We need to use spawn directly to get access to the @@ -342,7 +343,7 @@ export class FlowProcess { ...args, '--from', 'nuclide', ]; - const pathToFlow = getPathToFlow(); + const pathToFlow = await getPathToFlow(); const ret = await asyncExecute(pathToFlow, args, options); if (ret.exitCode !== 0) { // TODO: bubble up the exit code via return value instead diff --git a/lib/utils/util.js b/lib/utils/util.js index 7318040..e444cfb 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -2,14 +2,11 @@ import spawn from 'cross-spawn'; import {window, workspace} from 'vscode' +import {getPathToFlow} from "../pkg/flow-base/lib/FlowHelpers" const NODE_NOT_FOUND = '[Flow] Cannot find node in PATH. The simpliest way to resolve it is install node globally' const FLOW_NOT_FOUND = '[Flow] Cannot find flow in PATH. Try to install it by npm install flow-bin -g' -function getPathToFlow(): string { - return workspace.getConfiguration('flow').get('pathToFlow') -} - export function isFlowEnabled() { return workspace.getConfiguration('flow').get('enabled') } @@ -36,9 +33,10 @@ export function checkNode() { } } -export function checkFlow() { +export async function checkFlow() { + const path = await getPathToFlow() try { - const check = spawn(process.platform === 'win32' ? 'where' : 'which', [getPathToFlow()]) + const check = spawn(process.platform === 'win32' ? 'where' : 'which', [path]) let flowOutput = "", flowOutputError = "" diff --git a/package.json b/package.json index 85cae6f..a068741 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,11 @@ "type": "boolean", "default": true, "description": "Stop Flow on Exit" + }, + "flow.useNPMPackagedFlow": { + "type": "boolean", + "default": false, + "description": "Support using flow through your node_modules folder, WARNING: Checking this box is a security risk. When you open a project we will immediately run code contained within it." } } } @@ -77,4 +82,4 @@ "bugs": { "url": "https://github.com/flowtype/flow-for-vscode/issues" } -} +} \ No newline at end of file