-
Notifications
You must be signed in to change notification settings - Fork 138
Update debug config for node projects due to breaking changes #478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# Breaking changes to JavaScript Debug Configuration | ||
|
||
The debug configuration for v2 of the Azure Functions runtime [has changed](https://github.com/Azure/azure-functions-core-tools/issues/521) and old configurations will likely fail with the error "Cannot connect to runtime process, timeout after 10000 ms". You will automatically be prompted to update your configuration when you open a project. However, you can manually update your project with one of the following options: | ||
|
||
The first option is to add `languageWorkers:node:arguments` to your `runFunctionsHost` task in `.vscode\tasks.json` as seen below: | ||
|
||
```json | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "Run Functions Host", | ||
"identifier": "runFunctionsHost", | ||
"type": "shell", | ||
"command": "func host start", | ||
"options": { | ||
"env": { | ||
"languageWorkers:node:arguments": "--inspect=5858" | ||
} | ||
}, | ||
"isBackground": true, | ||
"presentation": { | ||
"reveal": "always" | ||
}, | ||
"problemMatcher": [ | ||
{ | ||
"owner": "azureFunctions", | ||
"pattern": [ | ||
{ | ||
"regexp": "\\b\\B", | ||
"file": 1, | ||
"location": 2, | ||
"message": 3 | ||
} | ||
], | ||
"background": { | ||
"activeOnStart": true, | ||
"beginsPattern": "^.*Stopping host.*", | ||
"endsPattern": "^.*Job host started.*" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
The second option requires two steps: | ||
|
||
1. Pass the `--language-worker` parameter to `func host start` in your `runFunctionsHost` task in `.vscode\tasks.json` as seen below: | ||
```json | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "Run Functions Host", | ||
"identifier": "runFunctionsHost", | ||
"type": "shell", | ||
"command": "func host start --language-worker -- \"--inspect=5858\"", | ||
"isBackground": true, | ||
"presentation": { | ||
"reveal": "always" | ||
}, | ||
"problemMatcher": [ | ||
{ | ||
"owner": "azureFunctions", | ||
"pattern": [ | ||
{ | ||
"regexp": "\\b\\B", | ||
"file": 1, | ||
"location": 2, | ||
"message": 3 | ||
} | ||
], | ||
"background": { | ||
"activeOnStart": true, | ||
"beginsPattern": "^.*Stopping host.*", | ||
"endsPattern": "^.*Job host started.*" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
1. Add the `FUNCTIONS_WORKER_RUNTIME` setting to your `local.settings.json`: | ||
|
||
```json | ||
{ | ||
"IsEncrypted": false, | ||
"Values": { | ||
"AzureWebJobsStorage": "", | ||
"FUNCTIONS_WORKER_RUNTIME": "node" | ||
} | ||
} | ||
``` | ||
|
||
The recommended debug configuration going forward has not been finalized. It will likely be similar to Option 2, but without a change to `local.settings.json` (since that file is not tracked by git). See [here](https://github.com/Azure/azure-functions-host/issues/3120) for more information. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
export interface ITasksJson { | ||
tasks: ITask[]; | ||
} | ||
|
||
export interface ITask { | ||
identifier: string; | ||
options?: ITaskOptions; | ||
} | ||
|
||
export interface ITaskOptions { | ||
env?: { | ||
[key: string]: string; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,15 @@ | |
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { TemplateFilter } from "../../constants"; | ||
import { ProjectRuntime, TemplateFilter } from "../../constants"; | ||
import { localize } from "../../localize"; | ||
import { funcHostTaskId } from "./IProjectCreator"; | ||
import { funcHostProblemMatcher, funcHostTaskId, funcHostTaskLabel } from "./IProjectCreator"; | ||
import { ITaskOptions } from "./ITasksJson"; | ||
import { ScriptProjectCreatorBase } from './ScriptProjectCreatorBase'; | ||
|
||
export const funcNodeDebugArgs: string = '--inspect=5858'; | ||
export const funcNodeDebugEnvVar: string = 'languageWorkers:node:arguments'; | ||
|
||
export class JavaScriptProjectCreator extends ScriptProjectCreatorBase { | ||
public readonly templateFilter: TemplateFilter = TemplateFilter.Verified; | ||
|
||
|
@@ -28,4 +32,33 @@ export class JavaScriptProjectCreator extends ScriptProjectCreatorBase { | |
] | ||
}; | ||
} | ||
|
||
public getTasksJson(runtime: string): {} { | ||
let options: ITaskOptions | undefined; | ||
if (runtime !== ProjectRuntime.one) { | ||
options = {}; | ||
options.env = {}; | ||
options.env[funcNodeDebugEnvVar] = funcNodeDebugArgs; | ||
} | ||
|
||
return { | ||
version: '2.0.0', | ||
tasks: [ | ||
{ | ||
label: funcHostTaskLabel, | ||
identifier: funcHostTaskId, | ||
type: 'shell', | ||
command: 'func host start', | ||
options: options, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this can return
is that okay? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. 'undefined' values are excluded when stringifying JSON, which is what we want in this case |
||
isBackground: true, | ||
presentation: { | ||
reveal: 'always' | ||
}, | ||
problemMatcher: [ | ||
funcHostProblemMatcher | ||
] | ||
} | ||
] | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,16 @@ import opn = require("opn"); | |
import * as path from 'path'; | ||
import * as vscode from 'vscode'; | ||
import { DialogResponses, IActionContext, IAzureUserInput } from 'vscode-azureextensionui'; | ||
import { gitignoreFileName, hostFileName, localSettingsFileName, projectLanguageSetting, projectRuntimeSetting } from '../../constants'; | ||
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, tasksFileName, vscodeFolderName } from '../../constants'; | ||
import { ext } from '../../extensionVariables'; | ||
import { tryGetLocalRuntimeVersion } from '../../funcCoreTools/tryGetLocalRuntimeVersion'; | ||
import { localize } from '../../localize'; | ||
import { getFuncExtensionSetting, updateGlobalSetting } from '../../ProjectSettings'; | ||
import { getFuncExtensionSetting, updateGlobalSetting, updateWorkspaceSetting } from '../../ProjectSettings'; | ||
import * as fsUtil from '../../utils/fs'; | ||
import { initProjectForVSCode } from './initProjectForVSCode'; | ||
import { funcHostTaskId } from './IProjectCreator'; | ||
import { ITask, ITasksJson } from './ITasksJson'; | ||
import { funcNodeDebugArgs, funcNodeDebugEnvVar } from './JavaScriptProjectCreator'; | ||
|
||
export async function validateFunctionProjects(actionContext: IActionContext, ui: IAzureUserInput, outputChannel: vscode.OutputChannel, folders: vscode.WorkspaceFolder[] | undefined): Promise<void> { | ||
actionContext.suppressTelemetry = true; | ||
|
@@ -24,6 +30,8 @@ export async function validateFunctionProjects(actionContext: IActionContext, ui | |
|
||
if (isInitializedProject(folderPath)) { | ||
actionContext.properties.isInitialized = 'true'; | ||
actionContext.suppressErrorDisplay = true; // Swallow errors when verifying debug config. No point in showing an error if we can't understand the project anyways | ||
await verifyDebugConfigIsValid(folderPath, actionContext); | ||
} else { | ||
actionContext.properties.isInitialized = 'false'; | ||
if (await promptToInitializeProject(ui, folderPath)) { | ||
|
@@ -68,3 +76,67 @@ function isInitializedProject(folderPath: string): boolean { | |
const runtime: string | undefined = getFuncExtensionSetting(projectRuntimeSetting, folderPath); | ||
return !!language && !!runtime; | ||
} | ||
|
||
/** | ||
* JavaScript debugging in the func cli had breaking changes in v2.0.1-beta.30. This verifies users are up-to-date with the latest working debug config. | ||
* See https://aka.ms/AA1vrxa for more info | ||
*/ | ||
async function verifyDebugConfigIsValid(folderPath: string, actionContext: IActionContext): Promise<void> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to guard against this function throwing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point |
||
const language: string | undefined = getFuncExtensionSetting(projectLanguageSetting, folderPath); | ||
if (language === ProjectLanguage.JavaScript) { | ||
const localProjectRuntime: ProjectRuntime | undefined = await tryGetLocalRuntimeVersion(); | ||
if (localProjectRuntime === ProjectRuntime.beta) { | ||
const tasksJsonPath: string = path.join(folderPath, vscodeFolderName, tasksFileName); | ||
const rawTasksData: string = (await fse.readFile(tasksJsonPath)).toString(); | ||
|
||
if (!rawTasksData.includes(funcNodeDebugEnvVar)) { | ||
const tasksContent: ITasksJson = <ITasksJson>JSON.parse(rawTasksData); | ||
|
||
const funcTask: ITask | undefined = tasksContent.tasks.find((t: ITask) => t.identifier === funcHostTaskId); | ||
if (funcTask) { | ||
actionContext.properties.debugConfigValid = 'false'; | ||
|
||
if (await promptToUpdateDebugConfiguration(folderPath)) { | ||
// tslint:disable-next-line:strict-boolean-expressions | ||
funcTask.options = funcTask.options || {}; | ||
// tslint:disable-next-line:strict-boolean-expressions | ||
funcTask.options.env = funcTask.options.env || {}; | ||
funcTask.options.env[funcNodeDebugEnvVar] = funcNodeDebugArgs; | ||
await fsUtil.writeFormattedJson(tasksJsonPath, tasksContent); | ||
|
||
actionContext.properties.updatedDebugConfig = 'true'; | ||
|
||
const viewFile: vscode.MessageItem = { title: 'View file' }; | ||
const result: vscode.MessageItem | undefined = await vscode.window.showInformationMessage(localize('tasksUpdated', 'Your "tasks.json" file has been updated.'), viewFile); | ||
if (result === viewFile) { | ||
await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(vscode.Uri.file(tasksJsonPath))); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
async function promptToUpdateDebugConfiguration(fsPath: string): Promise<boolean> { | ||
const settingKey: string = 'showDebugConfigWarning'; | ||
if (getFuncExtensionSetting<boolean>(settingKey)) { | ||
const updateConfig: vscode.MessageItem = { title: localize('updateTasks', 'Update tasks.json') }; | ||
const message: string = localize('uninitializedWarning', 'Your debug configuration is out of date and may not work with the latest version of the Azure Functions Core Tools.'); | ||
let result: vscode.MessageItem; | ||
do { | ||
result = await ext.ui.showWarningMessage(message, updateConfig, DialogResponses.dontWarnAgain, DialogResponses.learnMore); | ||
if (result === DialogResponses.dontWarnAgain) { | ||
await updateWorkspaceSetting(settingKey, false, fsPath); | ||
} else if (result === DialogResponses.learnMore) { | ||
// don't wait to re-show dialog | ||
// tslint:disable-next-line:no-floating-promises | ||
opn('https://aka.ms/AA1vrxa'); | ||
} else { | ||
return true; | ||
} | ||
} while (result === DialogResponses.learnMore); | ||
} | ||
|
||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this mention JavaScript?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think so. C# wasn't affected. Python isn't supported by this extension yet. And these instructions don't work for Java (The partner team is handling Java support and I think they fixed everything directly in Maven. In any case, we can always change this after the fact to include Java since the aka.ms link will point directly to master)