Skip to content

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

Merged
merged 5 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

## 0.10.0 - 2018-07-19
## 0.10.0 - 2018-07-24

### Added

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

### Changed

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

Expand Down
99 changes: 99 additions & 0 deletions docs/debugConfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Breaking changes to JavaScript Debug Configuration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this mention JavaScript?

Copy link
Contributor Author

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)


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.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@
"description": "%azFunc.showProjectWarningDescription%",
"default": true
},
"azureFunctions.showDebugConfigWarning": {
"type": "boolean",
"description": "%azFunc.showDebugConfigWarningDescription%",
"default": true
},
"azureFunctions.pickProcessTimeout": {
"type": "integer",
"description": "%azFunc.pickProcessTimeoutDescription%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"azFunc.showCoreToolsWarningDescription": "Show a warning if your installed version of Azure Functions Core Tools is out-of-date.",
"azFunc.show64BitWarningDescription": "Show a warning to install a 64-bit version of the Azure Functions Core Tools when you create a .NET Framework project.",
"azFunc.showProjectWarningDescription": "Show a warning when an Azure Functions project was detected that has not been initialized for use in VS Code.",
"azFunc.showDebugConfigWarningDescription": "Show a warning when an Azure Functions project was detected that has an out-of-date debug configuration.",
"azFunc.startStreamingLogs": "Start Streaming Logs",
"azFunc.stopStreamingLogs": "Stop Streaming Logs",
"azFunc.enableRemoteDebugging": "Enable remote debugging, an experimental feature that only supports Java-based Functions Apps.",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/createNewProject/IProjectCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export abstract class ProjectCreatorBase {
* Add all project files not included in the '.vscode' folder
*/
public abstract addNonVSCodeFiles(): Promise<void>;
public abstract getTasksJson(): {} | Promise<{}>;
public abstract getTasksJson(runtime: string): {} | Promise<{}>;
public getRecommendedExtensions(): string[] {
return ['ms-azuretools.vscode-azurefunctions'];
}
Expand Down
19 changes: 19 additions & 0 deletions src/commands/createNewProject/ITasksJson.ts
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;
};
}
37 changes: 35 additions & 2 deletions src/commands/createNewProject/JavaScriptProjectCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this can return

options: undefined

is that okay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
]
}
]
};
}
}
8 changes: 1 addition & 7 deletions src/commands/createNewProject/ScriptProjectCreatorBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectRuntime, TemplateFilter } from '../../constants';
import { tryGetLocalRuntimeVersion } from '../../funcCoreTools/tryGetLocalRuntimeVersion';
import { ILocalAppSettings } from '../../LocalAppSettings';
import { confirmOverwriteFile } from "../../utils/fs";
import * as fsUtil from '../../utils/fs';
Expand Down Expand Up @@ -47,12 +46,7 @@ export class ScriptProjectCreatorBase extends ProjectCreatorBase {
public readonly templateFilter: TemplateFilter = TemplateFilter.All;
public readonly functionsWorkerRuntime: string | undefined;

public async getRuntime(): Promise<ProjectRuntime> {
// tslint:disable-next-line:strict-boolean-expressions
return await tryGetLocalRuntimeVersion() || ScriptProjectCreatorBase.defaultRuntime;
}

public getTasksJson(): {} {
public getTasksJson(_runtime: string): {} {
return {
version: '2.0.0',
tasks: [
Expand Down
6 changes: 3 additions & 3 deletions src/commands/createNewProject/initProjectForVSCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export async function initProjectForVSCode(telemetryProperties: TelemetryPropert
const vscodePath: string = path.join(functionAppPath, '.vscode');
await fse.ensureDir(vscodePath);
outputChannel.appendLine(localize('writingDebugConfig', 'Writing project debug configuration...'));
await writeDebugConfiguration(projectCreator, vscodePath, ui);
await writeDebugConfiguration(projectCreator, vscodePath, ui, runtime);
outputChannel.appendLine(localize('writingSettings', 'Writing project settings...'));
await writeVSCodeSettings(projectCreator, vscodePath, runtime, language, templateFilter, ui);
outputChannel.appendLine(localize('writingRecommendations', 'Writing extension recommendations...'));
Expand All @@ -68,10 +68,10 @@ export async function initProjectForVSCode(telemetryProperties: TelemetryPropert
return projectCreator;
}

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

const launchJson: {} | undefined = projectCreator.getLaunchJson();
Expand Down
76 changes: 74 additions & 2 deletions src/commands/createNewProject/validateFunctionProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)) {
Expand Down Expand Up @@ -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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to guard against this function throwing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export enum Platform {
export const hostFileName: string = 'host.json';
export const localSettingsFileName: string = 'local.settings.json';
export const proxiesFileName: string = 'proxies.json';
export const tasksFileName: string = 'tasks.json';
export const vscodeFolderName: string = '.vscode';
export const gitignoreFileName: string = '.gitignore';

export enum PackageManager {
Expand Down