Skip to content

Commit b56a2b0

Browse files
authored
Update debug config for node projects due to breaking changes (#478)
1 parent d009cf1 commit b56a2b0

11 files changed

Lines changed: 242 additions & 16 deletions

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
All notable changes to the "azurefunctions" extension will be documented in this file.
66

7-
## 0.10.0 - 2018-07-19
7+
## 0.10.0 - 2018-07-24
88

99
### Added
1010

@@ -16,6 +16,7 @@ All notable changes to the "azurefunctions" extension will be documented in this
1616

1717
### Changed
1818

19+
- Debug config for JavaScript functions has changed. See https://aka.ms/AA1vrxa for more info
1920
- New C# projects will deploy the result of a 'dotnet publish' rather than deploying the build output
2021
- Azure Function Apps created through VS Code will automatically match the runtime from your local machine rather than always using v1
2122

docs/debugConfig.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Breaking changes to JavaScript Debug Configuration
2+
3+
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:
4+
5+
The first option is to add `languageWorkers:node:arguments` to your `runFunctionsHost` task in `.vscode\tasks.json` as seen below:
6+
7+
```json
8+
{
9+
"version": "2.0.0",
10+
"tasks": [
11+
{
12+
"label": "Run Functions Host",
13+
"identifier": "runFunctionsHost",
14+
"type": "shell",
15+
"command": "func host start",
16+
"options": {
17+
"env": {
18+
"languageWorkers:node:arguments": "--inspect=5858"
19+
}
20+
},
21+
"isBackground": true,
22+
"presentation": {
23+
"reveal": "always"
24+
},
25+
"problemMatcher": [
26+
{
27+
"owner": "azureFunctions",
28+
"pattern": [
29+
{
30+
"regexp": "\\b\\B",
31+
"file": 1,
32+
"location": 2,
33+
"message": 3
34+
}
35+
],
36+
"background": {
37+
"activeOnStart": true,
38+
"beginsPattern": "^.*Stopping host.*",
39+
"endsPattern": "^.*Job host started.*"
40+
}
41+
}
42+
]
43+
}
44+
]
45+
}
46+
```
47+
48+
The second option requires two steps:
49+
50+
1. Pass the `--language-worker` parameter to `func host start` in your `runFunctionsHost` task in `.vscode\tasks.json` as seen below:
51+
```json
52+
{
53+
"version": "2.0.0",
54+
"tasks": [
55+
{
56+
"label": "Run Functions Host",
57+
"identifier": "runFunctionsHost",
58+
"type": "shell",
59+
"command": "func host start --language-worker -- \"--inspect=5858\"",
60+
"isBackground": true,
61+
"presentation": {
62+
"reveal": "always"
63+
},
64+
"problemMatcher": [
65+
{
66+
"owner": "azureFunctions",
67+
"pattern": [
68+
{
69+
"regexp": "\\b\\B",
70+
"file": 1,
71+
"location": 2,
72+
"message": 3
73+
}
74+
],
75+
"background": {
76+
"activeOnStart": true,
77+
"beginsPattern": "^.*Stopping host.*",
78+
"endsPattern": "^.*Job host started.*"
79+
}
80+
}
81+
]
82+
}
83+
]
84+
}
85+
```
86+
87+
1. Add the `FUNCTIONS_WORKER_RUNTIME` setting to your `local.settings.json`:
88+
89+
```json
90+
{
91+
"IsEncrypted": false,
92+
"Values": {
93+
"AzureWebJobsStorage": "",
94+
"FUNCTIONS_WORKER_RUNTIME": "node"
95+
}
96+
}
97+
```
98+
99+
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.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,11 @@
564564
"description": "%azFunc.showProjectWarningDescription%",
565565
"default": true
566566
},
567+
"azureFunctions.showDebugConfigWarning": {
568+
"type": "boolean",
569+
"description": "%azFunc.showDebugConfigWarningDescription%",
570+
"default": true
571+
},
567572
"azureFunctions.pickProcessTimeout": {
568573
"type": "integer",
569574
"description": "%azFunc.pickProcessTimeoutDescription%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"azFunc.showCoreToolsWarningDescription": "Show a warning if your installed version of Azure Functions Core Tools is out-of-date.",
3434
"azFunc.show64BitWarningDescription": "Show a warning to install a 64-bit version of the Azure Functions Core Tools when you create a .NET Framework project.",
3535
"azFunc.showProjectWarningDescription": "Show a warning when an Azure Functions project was detected that has not been initialized for use in VS Code.",
36+
"azFunc.showDebugConfigWarningDescription": "Show a warning when an Azure Functions project was detected that has an out-of-date debug configuration.",
3637
"azFunc.startStreamingLogs": "Start Streaming Logs",
3738
"azFunc.stopStreamingLogs": "Stop Streaming Logs",
3839
"azFunc.enableRemoteDebugging": "Enable remote debugging, an experimental feature that only supports Java-based Functions Apps.",

src/commands/createNewProject/IProjectCreator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export abstract class ProjectCreatorBase {
4040
* Add all project files not included in the '.vscode' folder
4141
*/
4242
public abstract addNonVSCodeFiles(): Promise<void>;
43-
public abstract getTasksJson(): {} | Promise<{}>;
43+
public abstract getTasksJson(runtime: string): {} | Promise<{}>;
4444
public getRecommendedExtensions(): string[] {
4545
return ['ms-azuretools.vscode-azurefunctions'];
4646
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export interface ITasksJson {
7+
tasks: ITask[];
8+
}
9+
10+
export interface ITask {
11+
identifier: string;
12+
options?: ITaskOptions;
13+
}
14+
15+
export interface ITaskOptions {
16+
env?: {
17+
[key: string]: string;
18+
};
19+
}

src/commands/createNewProject/JavaScriptProjectCreator.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { TemplateFilter } from "../../constants";
6+
import { ProjectRuntime, TemplateFilter } from "../../constants";
77
import { localize } from "../../localize";
8-
import { funcHostTaskId } from "./IProjectCreator";
8+
import { funcHostProblemMatcher, funcHostTaskId, funcHostTaskLabel } from "./IProjectCreator";
9+
import { ITaskOptions } from "./ITasksJson";
910
import { ScriptProjectCreatorBase } from './ScriptProjectCreatorBase';
1011

12+
export const funcNodeDebugArgs: string = '--inspect=5858';
13+
export const funcNodeDebugEnvVar: string = 'languageWorkers:node:arguments';
14+
1115
export class JavaScriptProjectCreator extends ScriptProjectCreatorBase {
1216
public readonly templateFilter: TemplateFilter = TemplateFilter.Verified;
1317

@@ -28,4 +32,33 @@ export class JavaScriptProjectCreator extends ScriptProjectCreatorBase {
2832
]
2933
};
3034
}
35+
36+
public getTasksJson(runtime: string): {} {
37+
let options: ITaskOptions | undefined;
38+
if (runtime !== ProjectRuntime.one) {
39+
options = {};
40+
options.env = {};
41+
options.env[funcNodeDebugEnvVar] = funcNodeDebugArgs;
42+
}
43+
44+
return {
45+
version: '2.0.0',
46+
tasks: [
47+
{
48+
label: funcHostTaskLabel,
49+
identifier: funcHostTaskId,
50+
type: 'shell',
51+
command: 'func host start',
52+
options: options,
53+
isBackground: true,
54+
presentation: {
55+
reveal: 'always'
56+
},
57+
problemMatcher: [
58+
funcHostProblemMatcher
59+
]
60+
}
61+
]
62+
};
63+
}
3164
}

src/commands/createNewProject/ScriptProjectCreatorBase.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import * as fse from 'fs-extra';
77
import * as path from 'path';
88
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectRuntime, TemplateFilter } from '../../constants';
9-
import { tryGetLocalRuntimeVersion } from '../../funcCoreTools/tryGetLocalRuntimeVersion';
109
import { ILocalAppSettings } from '../../LocalAppSettings';
1110
import { confirmOverwriteFile } from "../../utils/fs";
1211
import * as fsUtil from '../../utils/fs';
@@ -47,12 +46,7 @@ export class ScriptProjectCreatorBase extends ProjectCreatorBase {
4746
public readonly templateFilter: TemplateFilter = TemplateFilter.All;
4847
public readonly functionsWorkerRuntime: string | undefined;
4948

50-
public async getRuntime(): Promise<ProjectRuntime> {
51-
// tslint:disable-next-line:strict-boolean-expressions
52-
return await tryGetLocalRuntimeVersion() || ScriptProjectCreatorBase.defaultRuntime;
53-
}
54-
55-
public getTasksJson(): {} {
49+
public getTasksJson(_runtime: string): {} {
5650
return {
5751
version: '2.0.0',
5852
tasks: [

src/commands/createNewProject/initProjectForVSCode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export async function initProjectForVSCode(telemetryProperties: TelemetryPropert
4949
const vscodePath: string = path.join(functionAppPath, '.vscode');
5050
await fse.ensureDir(vscodePath);
5151
outputChannel.appendLine(localize('writingDebugConfig', 'Writing project debug configuration...'));
52-
await writeDebugConfiguration(projectCreator, vscodePath, ui);
52+
await writeDebugConfiguration(projectCreator, vscodePath, ui, runtime);
5353
outputChannel.appendLine(localize('writingSettings', 'Writing project settings...'));
5454
await writeVSCodeSettings(projectCreator, vscodePath, runtime, language, templateFilter, ui);
5555
outputChannel.appendLine(localize('writingRecommendations', 'Writing extension recommendations...'));
@@ -68,10 +68,10 @@ export async function initProjectForVSCode(telemetryProperties: TelemetryPropert
6868
return projectCreator;
6969
}
7070

71-
async function writeDebugConfiguration(projectCreator: ProjectCreatorBase, vscodePath: string, ui: IAzureUserInput): Promise<void> {
71+
async function writeDebugConfiguration(projectCreator: ProjectCreatorBase, vscodePath: string, ui: IAzureUserInput, runtime: string): Promise<void> {
7272
const tasksJsonPath: string = path.join(vscodePath, 'tasks.json');
7373
if (await confirmOverwriteFile(tasksJsonPath, ui)) {
74-
await fsUtil.writeFormattedJson(tasksJsonPath, await projectCreator.getTasksJson());
74+
await fsUtil.writeFormattedJson(tasksJsonPath, await projectCreator.getTasksJson(runtime));
7575
}
7676

7777
const launchJson: {} | undefined = projectCreator.getLaunchJson();

src/commands/createNewProject/validateFunctionProjects.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import opn = require("opn");
99
import * as path from 'path';
1010
import * as vscode from 'vscode';
1111
import { DialogResponses, IActionContext, IAzureUserInput } from 'vscode-azureextensionui';
12-
import { gitignoreFileName, hostFileName, localSettingsFileName, projectLanguageSetting, projectRuntimeSetting } from '../../constants';
12+
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, tasksFileName, vscodeFolderName } from '../../constants';
13+
import { ext } from '../../extensionVariables';
14+
import { tryGetLocalRuntimeVersion } from '../../funcCoreTools/tryGetLocalRuntimeVersion';
1315
import { localize } from '../../localize';
14-
import { getFuncExtensionSetting, updateGlobalSetting } from '../../ProjectSettings';
16+
import { getFuncExtensionSetting, updateGlobalSetting, updateWorkspaceSetting } from '../../ProjectSettings';
17+
import * as fsUtil from '../../utils/fs';
1518
import { initProjectForVSCode } from './initProjectForVSCode';
19+
import { funcHostTaskId } from './IProjectCreator';
20+
import { ITask, ITasksJson } from './ITasksJson';
21+
import { funcNodeDebugArgs, funcNodeDebugEnvVar } from './JavaScriptProjectCreator';
1622

1723
export async function validateFunctionProjects(actionContext: IActionContext, ui: IAzureUserInput, outputChannel: vscode.OutputChannel, folders: vscode.WorkspaceFolder[] | undefined): Promise<void> {
1824
actionContext.suppressTelemetry = true;
@@ -24,6 +30,8 @@ export async function validateFunctionProjects(actionContext: IActionContext, ui
2430

2531
if (isInitializedProject(folderPath)) {
2632
actionContext.properties.isInitialized = 'true';
33+
actionContext.suppressErrorDisplay = true; // Swallow errors when verifying debug config. No point in showing an error if we can't understand the project anyways
34+
await verifyDebugConfigIsValid(folderPath, actionContext);
2735
} else {
2836
actionContext.properties.isInitialized = 'false';
2937
if (await promptToInitializeProject(ui, folderPath)) {
@@ -68,3 +76,67 @@ function isInitializedProject(folderPath: string): boolean {
6876
const runtime: string | undefined = getFuncExtensionSetting(projectRuntimeSetting, folderPath);
6977
return !!language && !!runtime;
7078
}
79+
80+
/**
81+
* 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.
82+
* See https://aka.ms/AA1vrxa for more info
83+
*/
84+
async function verifyDebugConfigIsValid(folderPath: string, actionContext: IActionContext): Promise<void> {
85+
const language: string | undefined = getFuncExtensionSetting(projectLanguageSetting, folderPath);
86+
if (language === ProjectLanguage.JavaScript) {
87+
const localProjectRuntime: ProjectRuntime | undefined = await tryGetLocalRuntimeVersion();
88+
if (localProjectRuntime === ProjectRuntime.beta) {
89+
const tasksJsonPath: string = path.join(folderPath, vscodeFolderName, tasksFileName);
90+
const rawTasksData: string = (await fse.readFile(tasksJsonPath)).toString();
91+
92+
if (!rawTasksData.includes(funcNodeDebugEnvVar)) {
93+
const tasksContent: ITasksJson = <ITasksJson>JSON.parse(rawTasksData);
94+
95+
const funcTask: ITask | undefined = tasksContent.tasks.find((t: ITask) => t.identifier === funcHostTaskId);
96+
if (funcTask) {
97+
actionContext.properties.debugConfigValid = 'false';
98+
99+
if (await promptToUpdateDebugConfiguration(folderPath)) {
100+
// tslint:disable-next-line:strict-boolean-expressions
101+
funcTask.options = funcTask.options || {};
102+
// tslint:disable-next-line:strict-boolean-expressions
103+
funcTask.options.env = funcTask.options.env || {};
104+
funcTask.options.env[funcNodeDebugEnvVar] = funcNodeDebugArgs;
105+
await fsUtil.writeFormattedJson(tasksJsonPath, tasksContent);
106+
107+
actionContext.properties.updatedDebugConfig = 'true';
108+
109+
const viewFile: vscode.MessageItem = { title: 'View file' };
110+
const result: vscode.MessageItem | undefined = await vscode.window.showInformationMessage(localize('tasksUpdated', 'Your "tasks.json" file has been updated.'), viewFile);
111+
if (result === viewFile) {
112+
await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(vscode.Uri.file(tasksJsonPath)));
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}
119+
}
120+
121+
async function promptToUpdateDebugConfiguration(fsPath: string): Promise<boolean> {
122+
const settingKey: string = 'showDebugConfigWarning';
123+
if (getFuncExtensionSetting<boolean>(settingKey)) {
124+
const updateConfig: vscode.MessageItem = { title: localize('updateTasks', 'Update tasks.json') };
125+
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.');
126+
let result: vscode.MessageItem;
127+
do {
128+
result = await ext.ui.showWarningMessage(message, updateConfig, DialogResponses.dontWarnAgain, DialogResponses.learnMore);
129+
if (result === DialogResponses.dontWarnAgain) {
130+
await updateWorkspaceSetting(settingKey, false, fsPath);
131+
} else if (result === DialogResponses.learnMore) {
132+
// don't wait to re-show dialog
133+
// tslint:disable-next-line:no-floating-promises
134+
opn('https://aka.ms/AA1vrxa');
135+
} else {
136+
return true;
137+
}
138+
} while (result === DialogResponses.learnMore);
139+
}
140+
141+
return false;
142+
}

0 commit comments

Comments
 (0)