Basic support for extension bundles

This commit is contained in:
Eric Jizba 2019-04-22 09:24:38 -07:00
Родитель dbfa2fc5f5
Коммит 783cc3e89d
14 изменённых файлов: 101 добавлений и 35 удалений

Просмотреть файл

@ -68,7 +68,11 @@ export class FunctionConfig {
}
public get isHttpTrigger(): boolean {
return !!this.inBinding && !!this.inBinding.type && this.inBinding.type.toLowerCase() === 'httptrigger';
return !!this.inBinding && !!this.inBinding.type && /^http/i.test(this.inBinding.type);
}
public get isTimerTrigger(): boolean {
return !!this.inBinding && !!this.inBinding.type && /^timer/i.test(this.inBinding.type);
}
public get authLevel(): HttpAuthLevel {

Просмотреть файл

@ -6,12 +6,14 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import { Progress, Uri, window, workspace } from 'vscode';
import { AzureWizardExecuteStep, callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
import { localSettingsFileName } from '../../constants';
import { AzureWizardExecuteStep, callWithTelemetryAndErrorHandling, IActionContext, parseError } from 'vscode-azureextensionui';
import { hostFileName, localSettingsFileName, ProjectRuntime } from '../../constants';
import { ext } from '../../extensionVariables';
import { IHostJson } from '../../funcConfig/host';
import { validateAzureWebJobsStorage } from '../../LocalAppSettings';
import { localize } from '../../localize';
import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { writeFormattedJson } from '../../utils/fs';
import { nonNullProp } from '../../utils/nonNull';
import { getContainingWorkspace } from '../../utils/workspace';
import { IFunctionWizardContext } from './IFunctionWizardContext';
@ -53,6 +55,10 @@ export abstract class FunctionCreateStepBase<T extends IFunctionWizardContext> e
progress.report({ message: localize('creatingFunction', 'Creating new {0}...', template.name) });
const newFilePath: string = await this.executeCore(wizardContext);
if (wizardContext.runtime !== ProjectRuntime.v1 && !template.isHttpTrigger && !template.isTimerTrigger) {
await this.verifyExtensionBundle(wizardContext);
}
const cachedFunc: ICachedFunction = { projectPath: wizardContext.projectPath, newFilePath, isHttpTrigger: template.isHttpTrigger };
if (wizardContext.openBehavior) {
@ -65,6 +71,23 @@ export abstract class FunctionCreateStepBase<T extends IFunctionWizardContext> e
runPostFunctionCreateSteps(cachedFunc);
}
public async verifyExtensionBundle(wizardContext: T): Promise<void> {
const hostFilePath: string = path.join(wizardContext.projectPath, hostFileName);
try {
const hostJson: IHostJson = <IHostJson>await fse.readJSON(hostFilePath);
if (!hostJson.extensionBundle) {
// https://github.com/Microsoft/vscode-azurefunctions/issues/1202
hostJson.extensionBundle = {
id: 'Microsoft.Azure.Functions.ExtensionBundle',
version: '[1.*, 2.0.0)'
};
await writeFormattedJson(hostFilePath, hostJson);
}
} catch (error) {
throw new Error(localize('failedToParseHostJson', 'Failed to parse {0}: {1}', hostFileName, parseError(error).message));
}
}
public shouldExecute(wizardContext: T): boolean {
return !!wizardContext.functionTemplate;
}

Просмотреть файл

@ -8,6 +8,7 @@ import * as os from 'os';
import * as path from 'path';
import { Progress } from 'vscode';
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectRuntime, proxiesFileName } from '../../../constants';
import { IHostJson } from '../../../funcConfig/host';
import { ILocalAppSettings } from '../../../LocalAppSettings';
import { confirmOverwriteFile, writeFormattedJson } from "../../../utils/fs";
import { nonNullProp } from '../../../utils/nonNull';
@ -24,7 +25,7 @@ export class ScriptProjectCreateStep extends ProjectCreateStepBase {
const runtime: ProjectRuntime = nonNullProp(wizardContext, 'runtime');
const hostJsonPath: string = path.join(wizardContext.projectPath, hostFileName);
if (await confirmOverwriteFile(hostJsonPath)) {
const hostJson: object = this.getHostContent(runtime);
const hostJson: IHostJson = this.getHostContent(runtime);
await writeFormattedJson(hostJsonPath, hostJson);
}
@ -71,7 +72,7 @@ local.settings.json`));
}
}
private getHostContent(runtime: ProjectRuntime): object {
private getHostContent(runtime: ProjectRuntime): IHostJson {
if (runtime === ProjectRuntime.v2) {
if (this.supportsManagedDependencies) {
return {

Просмотреть файл

@ -36,7 +36,7 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
const vscodePath: string = path.join(wizardContext.workspacePath, '.vscode');
await fse.ensureDir(vscodePath);
await this.writeTasksJson(wizardContext, vscodePath, runtime);
await this.writeTasksJson(wizardContext, vscodePath);
await this.writeLaunchJson(wizardContext.workspaceFolder, vscodePath, runtime);
await this.writeSettingsJson(wizardContext.workspaceFolder, vscodePath, language, runtime);
await this.writeExtensionsJson(vscodePath, language);
@ -55,7 +55,7 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
}
protected abstract executeCore(wizardContext: IProjectWizardContext): Promise<void>;
protected abstract getTasks(runtime: ProjectRuntime): TaskDefinition[];
protected abstract getTasks(): TaskDefinition[];
protected getDebugConfiguration?(runtime: ProjectRuntime): DebugConfiguration;
protected getRecommendedExtensions?(language: ProjectLanguage): string[];
@ -71,8 +71,8 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
return path.posix.join(subDir, fsPath);
}
private async writeTasksJson(wizardContext: IProjectWizardContext, vscodePath: string, runtime: ProjectRuntime): Promise<void> {
const newTasks: TaskDefinition[] = this.getTasks(runtime);
private async writeTasksJson(wizardContext: IProjectWizardContext, vscodePath: string): Promise<void> {
const newTasks: TaskDefinition[] = this.getTasks();
for (const task of newTasks) {
// tslint:disable-next-line: strict-boolean-expressions no-unsafe-any
let cwd: string = (task.options && task.options.cwd) || '.';

Просмотреть файл

@ -7,7 +7,7 @@ import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { DebugConfiguration, TaskDefinition } from 'vscode';
import { extensionPrefix, extInstallCommand, extInstallTaskName, func, funcWatchProblemMatcher, gitignoreFileName, hostStartCommand, isWindows, localSettingsFileName, packTaskName, Platform, pythonVenvSetting } from "../../../constants";
import { extensionPrefix, extInstallCommand, func, funcWatchProblemMatcher, gitignoreFileName, hostStartCommand, isWindows, localSettingsFileName, packTaskName, Platform, pythonVenvSetting } from "../../../constants";
import { pythonDebugConfig } from '../../../debug/PythonDebugProvider';
import { azureWebJobsStorageKey, getLocalAppSettings, ILocalAppSettings } from '../../../LocalAppSettings';
import { writeFormattedJson } from '../../../utils/fs';
@ -21,6 +21,8 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep {
private _venvName: string | undefined;
protected async executeCore(wizardContext: IProjectWizardContext): Promise<void> {
await super.executeCore(wizardContext);
const zipPath: string = this.setDeploySubpath(wizardContext, `${path.basename(wizardContext.projectPath)}.zip`);
this._venvName = await getExistingVenv(wizardContext.projectPath);
@ -38,18 +40,28 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep {
}
protected getTasks(): TaskDefinition[] {
const pipInstallLabel: string = 'pipInstall';
const dependsOn: string | undefined = this.requiresFuncExtensionsInstall ? extInstallCommand : this._venvName ? pipInstallLabel : undefined;
const tasks: TaskDefinition[] = [
{
type: func,
command: hostStartCommand,
problemMatcher: funcWatchProblemMatcher,
isBackground: true,
dependsOn: extInstallTaskName
dependsOn
}
];
if (this._venvName) {
const pipInstallLabel: string = 'pipInstall';
if (this.requiresFuncExtensionsInstall) {
tasks.push({
type: func,
command: extInstallCommand,
dependsOn: pipInstallLabel,
problemMatcher: []
});
}
const venvSettingReference: string = `\${config:${extensionPrefix}.${pythonVenvSetting}}`;
function getPipInstallCommand(platform: NodeJS.Platform): string {
@ -57,12 +69,6 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep {
}
tasks.push(
{
type: func,
command: extInstallCommand,
dependsOn: pipInstallLabel,
problemMatcher: []
},
{
label: pipInstallLabel,
type: 'shell',

Просмотреть файл

@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as semver from 'semver';
import { TaskDefinition } from 'vscode';
import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand, ProjectRuntime } from '../../../constants';
import { getLocalFuncCoreToolsVersion } from '../../../funcCoreTools/getLocalFuncCoreToolsVersion';
import { IProjectWizardContext } from '../../createNewProject/IProjectWizardContext';
import { InitVSCodeStepBase } from './InitVSCodeStepBase';
@ -12,28 +14,41 @@ import { InitVSCodeStepBase } from './InitVSCodeStepBase';
* Base class for all projects based on a simple script (i.e. JavaScript, C# Script, Bash, etc.) that don't require compilation
*/
export class ScriptInitVSCodeStep extends InitVSCodeStepBase {
protected getTasks(runtime: ProjectRuntime): TaskDefinition[] {
protected requiresFuncExtensionsInstall: boolean = false;
protected getTasks(): TaskDefinition[] {
return [
{
type: func,
command: hostStartCommand,
problemMatcher: funcWatchProblemMatcher,
dependsOn: runtime === ProjectRuntime.v1 ? undefined : extInstallTaskName,
dependsOn: this.requiresFuncExtensionsInstall ? extInstallTaskName : undefined,
isBackground: true
}
];
}
protected async executeCore(wizardContext: IProjectWizardContext): Promise<void> {
// "func extensions install" task creates C# build artifacts that should be hidden
// See issue: https://github.com/Microsoft/vscode-azurefunctions/pull/699
this.settings.push({ prefix: 'files', key: 'exclude', value: { obj: true, bin: true } });
if (wizardContext.runtime === ProjectRuntime.v2) {
try {
const currentVersion: string | null = await getLocalFuncCoreToolsVersion();
// Starting after this version, projects can use extension bundle instead of running "func extensions install"
this.requiresFuncExtensionsInstall = !!currentVersion && semver.lte(currentVersion, '2.5.553');
} catch {
// use default of false
}
}
this.setDeploySubpath(wizardContext, '.');
if (!this.preDeployTask) {
if (wizardContext.runtime !== ProjectRuntime.v1) {
if (this.requiresFuncExtensionsInstall) {
// "func extensions install" task creates C# build artifacts that should be hidden
// See issue: https://github.com/Microsoft/vscode-azurefunctions/pull/699
this.settings.push({ prefix: 'files', key: 'exclude', value: { obj: true, bin: true } });
if (!this.preDeployTask) {
this.preDeployTask = extInstallTaskName;
}
}
this.setDeploySubpath(wizardContext, '.');
}
}

Просмотреть файл

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { TaskDefinition } from 'vscode';
import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand, ProjectRuntime } from '../../../constants';
import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand } from '../../../constants';
import { JavaScriptInitVSCodeStep } from "./JavaScriptInitVSCodeStep";
const npmPruneTaskLabel: string = 'npm prune';
@ -14,7 +14,7 @@ const npmBuildTaskLabel: string = 'npm build';
export class TypeScriptInitVSCodeStep extends JavaScriptInitVSCodeStep {
public readonly preDeployTask: string = npmPruneTaskLabel;
public getTasks(runtime: ProjectRuntime): TaskDefinition[] {
public getTasks(): TaskDefinition[] {
return [
{
type: func,
@ -27,7 +27,7 @@ export class TypeScriptInitVSCodeStep extends JavaScriptInitVSCodeStep {
type: 'shell',
label: npmBuildTaskLabel,
command: 'npm run build',
dependsOn: runtime === ProjectRuntime.v1 ? npmInstallTaskLabel : [extInstallTaskName, npmInstallTaskLabel],
dependsOn: this.requiresFuncExtensionsInstall ? [extInstallTaskName, npmInstallTaskLabel] : npmInstallTaskLabel,
problemMatcher: '$tsc'
},
{

1
src/funcConfig/README.md Normal file
Просмотреть файл

@ -0,0 +1 @@
This folder contains logic related to Azure Functions project files, like "local.settings.json", "host.json", and "function.json".

15
src/funcConfig/host.ts Normal file
Просмотреть файл

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IHostJson {
version?: string;
managedDependency?: {
enabled?: boolean;
};
extensionBundle?: {
id?: string;
version?: string;
};
}

Просмотреть файл

@ -18,6 +18,7 @@ export interface IFunctionTemplate {
defaultFunctionName: string;
language: string;
isHttpTrigger: boolean;
isTimerTrigger: boolean;
userPromptedSettings: IFunctionSetting[];
categories: TemplateCategory[];
}

Просмотреть файл

@ -54,7 +54,8 @@ function parseDotnetTemplate(rawTemplate: IRawTemplate): IFunctionTemplate {
}
return {
isHttpTrigger: rawTemplate.Name.toLowerCase().startsWith('http') || rawTemplate.Name.toLowerCase().endsWith('webhook'),
isHttpTrigger: /^http/i.test(rawTemplate.Name) || /webhook$/i.test(rawTemplate.Name),
isTimerTrigger: /^timer/i.test(rawTemplate.Name),
id: rawTemplate.Identity,
name: rawTemplate.Name,
defaultFunctionName: rawTemplate.DefaultName,

Просмотреть файл

@ -197,6 +197,7 @@ export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResou
return {
functionConfig: functionConfig,
isHttpTrigger: functionConfig.isHttpTrigger,
isTimerTrigger: functionConfig.isTimerTrigger,
id: rawTemplate.id,
name: getResourceValue(resources, rawTemplate.metadata.name),
defaultFunctionName: rawTemplate.metadata.defaultFunctionName,

Просмотреть файл

@ -0,0 +1 @@
This folder contains logic related to VS Code project files, like "settings.json", "tasks.json", and "launch.json".

Просмотреть файл

@ -13,8 +13,7 @@ export function getJavaScriptValidateOptions(): IValidateProjectOptions {
expectedSettings: {
projectLanguage: ProjectLanguage.JavaScript,
projectRuntime: ProjectRuntime.v2,
deploySubpath: '.',
preDeployTask: 'func: extensions install'
deploySubpath: '.'
},
expectedPaths: [
],
@ -135,7 +134,6 @@ export function getPythonValidateOptions(projectName: string, venvName: string):
'Attach to Python Functions'
],
expectedTasks: [
'extensions install',
'pipInstall',
'host start'
]
@ -196,8 +194,7 @@ export function getPowerShellValidateOptions(): IValidateProjectOptions {
expectedSettings: {
projectLanguage: ProjectLanguage.PowerShell,
projectRuntime: ProjectRuntime.v2,
deploySubpath: '.',
preDeployTask: 'func: extensions install'
deploySubpath: '.'
},
expectedPaths: [
'profile.ps1',