Better error handling/loading of templates (#1509)
This commit is contained in:
Родитель
ba8a0d9af3
Коммит
e6536a5e61
|
@ -26,9 +26,9 @@ export * from './src/constants';
|
|||
export * from './src/extensionVariables';
|
||||
export * from './src/funcConfig/function';
|
||||
export * from './src/vsCodeConfig/settings';
|
||||
export * from './src/templates/CentralTemplateProvider';
|
||||
export * from './src/templates/IFunctionTemplate';
|
||||
export * from './src/templates/ScriptTemplateRetriever';
|
||||
export * from './src/templates/TemplateProvider';
|
||||
export * from './src/templates/script/getScriptResourcesPath';
|
||||
export * from './src/tree/AzureAccountTreeItemWithProjects';
|
||||
export * from './src/utils/fs';
|
||||
export * from './src/utils/delay';
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureWizardPromptStep, IAzureQuickPickItem, IWizardOptions } from 'vscode-azureextensionui';
|
||||
import { ProjectLanguage, ProjectRuntime } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { localize } from '../../localize';
|
||||
import { IBindingTemplate } from '../../templates/IBindingTemplate';
|
||||
|
@ -17,7 +18,7 @@ export class BindingListStep extends AzureWizardPromptStep<IBindingWizardContext
|
|||
public async prompt(context: IBindingWizardContext): Promise<void> {
|
||||
const direction: string = nonNullProp(context, 'bindingDirection');
|
||||
const placeHolder: string = localize('selectBinding', 'Select binding with direction "{0}"', direction);
|
||||
context.bindingTemplate = (await ext.ui.showQuickPick(this.getPicks(direction), { placeHolder })).data;
|
||||
context.bindingTemplate = (await ext.ui.showQuickPick(this.getPicks(context, direction), { placeHolder })).data;
|
||||
}
|
||||
|
||||
public shouldPrompt(context: IBindingWizardContext): boolean {
|
||||
|
@ -35,13 +36,13 @@ export class BindingListStep extends AzureWizardPromptStep<IBindingWizardContext
|
|||
}
|
||||
}
|
||||
|
||||
private async getPicks(direction: string): Promise<IAzureQuickPickItem<IBindingTemplate>[]> {
|
||||
// wait for template provider task to signal that bindings have been defined
|
||||
await ext.templateProviderTask;
|
||||
|
||||
const bindings: IBindingTemplate[] = ext.scriptBindings
|
||||
private async getPicks(context: IBindingWizardContext, direction: string): Promise<IAzureQuickPickItem<IBindingTemplate>[]> {
|
||||
const language: ProjectLanguage = nonNullProp(context, 'language');
|
||||
const runtime: ProjectRuntime = nonNullProp(context, 'runtime');
|
||||
const templates: IBindingTemplate[] = await ext.templateProvider.getBindingTemplates(context, language, runtime);
|
||||
return templates
|
||||
.filter(b => b.direction.toLowerCase() === direction.toLowerCase())
|
||||
.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
return bindings.map(b => { return { label: b.displayName, data: b }; });
|
||||
.sort((a, b) => a.displayName.localeCompare(b.displayName))
|
||||
.map(b => { return { label: b.displayName, data: b }; });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
import { Uri, WorkspaceFolder } from "vscode";
|
||||
import { AzureWizard, IActionContext } from "vscode-azureextensionui";
|
||||
import { ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting } from "../../constants";
|
||||
import { LocalBindingsTreeItem } from "../../tree/localProject/LocalBindingsTreeItem";
|
||||
import { nonNullValue } from "../../utils/nonNull";
|
||||
import { getContainingWorkspace } from "../../utils/workspace";
|
||||
import { getWorkspaceSetting } from "../../vsCodeConfig/settings";
|
||||
import { createChildNode } from "../createChildNode";
|
||||
import { tryGetFunctionProjectRoot } from "../createNewProject/verifyIsProject";
|
||||
import { createBindingWizard } from "./createBindingWizard";
|
||||
|
@ -19,8 +21,10 @@ export async function addBinding(context: IActionContext, data: Uri | LocalBindi
|
|||
const workspaceFolder: WorkspaceFolder = nonNullValue(getContainingWorkspace(functionJsonPath), 'workspaceFolder');
|
||||
const workspacePath: string = workspaceFolder.uri.fsPath;
|
||||
const projectPath: string | undefined = await tryGetFunctionProjectRoot(workspacePath) || workspacePath;
|
||||
const language: ProjectLanguage | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
|
||||
const runtime: ProjectRuntime | undefined = getWorkspaceSetting(projectRuntimeSetting, projectPath);
|
||||
|
||||
const wizardContext: IBindingWizardContext = Object.assign(context, { functionJsonPath: data.fsPath, workspacePath, projectPath, workspaceFolder });
|
||||
const wizardContext: IBindingWizardContext = Object.assign(context, { functionJsonPath: data.fsPath, workspacePath, projectPath, workspaceFolder, language, runtime });
|
||||
const wizard: AzureWizard<IBindingWizardContext> = createBindingWizard(wizardContext);
|
||||
await wizard.prompt();
|
||||
await wizard.execute();
|
||||
|
|
|
@ -10,7 +10,6 @@ import { ext } from '../../extensionVariables';
|
|||
import { getAzureWebJobsStorage } from '../../funcConfig/local.settings';
|
||||
import { localize } from '../../localize';
|
||||
import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
|
||||
import { TemplateProvider } from '../../templates/TemplateProvider';
|
||||
import { nonNullProp } from '../../utils/nonNull';
|
||||
import { getWorkspaceSetting, updateWorkspaceSetting } from '../../vsCodeConfig/settings';
|
||||
import { addBindingSettingSteps } from '../addBinding/settingSteps/addBindingSettingSteps';
|
||||
|
@ -44,8 +43,7 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
|
|||
if (options.templateId) {
|
||||
const language: ProjectLanguage = nonNullProp(context, 'language');
|
||||
const runtime: ProjectRuntime = nonNullProp(context, 'runtime');
|
||||
const templateProvider: TemplateProvider = await ext.templateProviderTask;
|
||||
const templates: IFunctionTemplate[] = await templateProvider.getTemplates(language, runtime, context.projectPath, TemplateFilter.All, context.telemetry.properties);
|
||||
const templates: IFunctionTemplate[] = await ext.templateProvider.getFunctionTemplates(context, language, runtime, TemplateFilter.All);
|
||||
const foundTemplate: IFunctionTemplate | undefined = templates.find((t: IFunctionTemplate) => t.id === options.templateId);
|
||||
if (foundTemplate) {
|
||||
context.functionTemplate = foundTemplate;
|
||||
|
@ -143,12 +141,10 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
|
|||
private async getPicks(context: IFunctionWizardContext, templateFilter: TemplateFilter): Promise<IAzureQuickPickItem<IFunctionTemplate | TemplatePromptResult>[]> {
|
||||
const language: ProjectLanguage = nonNullProp(context, 'language');
|
||||
const runtime: ProjectRuntime = nonNullProp(context, 'runtime');
|
||||
|
||||
const provider: TemplateProvider = await ext.templateProviderTask;
|
||||
let templates: IFunctionTemplate[] = await provider.getTemplates(language, runtime, context.projectPath, templateFilter, context.telemetry.properties);
|
||||
templates = templates.sort((a, b) => sortTemplates(a, b, templateFilter));
|
||||
|
||||
const picks: IAzureQuickPickItem<IFunctionTemplate | TemplatePromptResult>[] = templates.map(t => { return { label: t.name, data: t }; });
|
||||
const templates: IFunctionTemplate[] = await ext.templateProvider.getFunctionTemplates(context, language, runtime, templateFilter);
|
||||
const picks: IAzureQuickPickItem<IFunctionTemplate | TemplatePromptResult>[] = templates
|
||||
.sort((a, b) => sortTemplates(a, b, templateFilter))
|
||||
.map(t => { return { label: t.name, data: t }; });
|
||||
|
||||
if (this._isProjectWizard) {
|
||||
picks.unshift({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as path from 'path';
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { ProjectRuntime } from '../../../constants';
|
||||
import { executeDotnetTemplateCommand } from '../../../templates/executeDotnetTemplateCommand';
|
||||
import { executeDotnetTemplateCommand } from '../../../templates/dotnet/executeDotnetTemplateCommand';
|
||||
import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
|
||||
import { cpUtils } from '../../../utils/cpUtils';
|
||||
import { dotnetUtils } from '../../../utils/dotnetUtils';
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { ext } from '../../../extensionVariables';
|
||||
import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
|
||||
import { removeLanguageFromId } from "../../../templates/TemplateProvider";
|
||||
import { mavenUtils } from "../../../utils/mavenUtils";
|
||||
import { nonNullProp } from '../../../utils/nonNull';
|
||||
import { getJavaFunctionFilePath, IJavaProjectWizardContext } from '../../createNewProject/javaSteps/IJavaProjectWizardContext';
|
||||
|
@ -52,3 +51,7 @@ export class JavaFunctionCreateStep extends FunctionCreateStepBase<IFunctionWiza
|
|||
return getJavaFunctionFilePath(context.projectPath, packageName, functionName);
|
||||
}
|
||||
}
|
||||
|
||||
function removeLanguageFromId(id: string): string {
|
||||
return id.split('-')[0];
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IScriptFunctionTemplate } from '../../../templates/parseScriptTemplates';
|
||||
import { IScriptFunctionTemplate } from '../../../templates/script/parseScriptTemplates';
|
||||
import { IFunctionWizardContext } from '../IFunctionWizardContext';
|
||||
|
||||
export interface IScriptFunctionWizardContext extends IFunctionWizardContext {
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as path from 'path';
|
|||
import { functionJsonFileName, ProjectLanguage } from '../../../constants';
|
||||
import { IFunctionBinding, IFunctionJson } from '../../../funcConfig/function';
|
||||
import { localize } from '../../../localize';
|
||||
import { IScriptFunctionTemplate } from '../../../templates/parseScriptTemplates';
|
||||
import { IScriptFunctionTemplate } from '../../../templates/script/parseScriptTemplates';
|
||||
import * as fsUtil from '../../../utils/fs';
|
||||
import { nonNullProp } from '../../../utils/nonNull';
|
||||
import { FunctionCreateStepBase } from '../FunctionCreateStepBase';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { localize } from "../../../localize";
|
||||
import { IScriptFunctionTemplate } from '../../../templates/parseScriptTemplates';
|
||||
import { IScriptFunctionTemplate } from '../../../templates/script/parseScriptTemplates';
|
||||
import * as fsUtil from '../../../utils/fs';
|
||||
import { nonNullProp } from '../../../utils/nonNull';
|
||||
import { FunctionNameStepBase } from '../FunctionNameStepBase';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { DialogResponses, IActionContext } from 'vscode-azureextensionui';
|
|||
import { gitignoreFileName, hostFileName, localSettingsFileName, ProjectLanguage, ProjectRuntime } from '../../../constants';
|
||||
import { ext } from '../../../extensionVariables';
|
||||
import { localize } from "../../../localize";
|
||||
import { executeDotnetTemplateCommand } from '../../../templates/executeDotnetTemplateCommand';
|
||||
import { executeDotnetTemplateCommand } from '../../../templates/dotnet/executeDotnetTemplateCommand';
|
||||
import { cpUtils } from '../../../utils/cpUtils';
|
||||
import { dotnetUtils } from '../../../utils/dotnetUtils';
|
||||
import { nonNullProp } from '../../../utils/nonNull';
|
||||
|
|
|
@ -58,7 +58,7 @@ import { registerFuncHostTaskEvents } from './funcCoreTools/funcHostTask';
|
|||
import { installOrUpdateFuncCoreTools } from './funcCoreTools/installOrUpdateFuncCoreTools';
|
||||
import { uninstallFuncCoreTools } from './funcCoreTools/uninstallFuncCoreTools';
|
||||
import { validateFuncCoreToolsIsLatest } from './funcCoreTools/validateFuncCoreToolsIsLatest';
|
||||
import { getTemplateProvider } from './templates/TemplateProvider';
|
||||
import { CentralTemplateProvider } from './templates/CentralTemplateProvider';
|
||||
import { AzureAccountTreeItemWithProjects } from './tree/AzureAccountTreeItemWithProjects';
|
||||
import { ProductionSlotTreeItem } from './tree/ProductionSlotTreeItem';
|
||||
import { ProxyTreeItem } from './tree/ProxyTreeItem';
|
||||
|
@ -97,7 +97,7 @@ export async function activateInternal(context: vscode.ExtensionContext, perfSta
|
|||
await verifyVSCodeConfigOnActivate(actionContext, event.added);
|
||||
});
|
||||
|
||||
ext.templateProviderTask = getTemplateProvider();
|
||||
ext.templateProvider = new CentralTemplateProvider();
|
||||
|
||||
registerCommand('azureFunctions.selectSubscriptions', () => vscode.commands.executeCommand('azure-account.selectSubscriptions'));
|
||||
registerCommand('azureFunctions.refresh', async (_actionContext: IActionContext, node?: AzureTreeItem) => await ext.tree.refresh(node));
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
import { ExtensionContext } from "vscode";
|
||||
import { AzExtTreeDataProvider, IAzExtOutputChannel, IAzureUserInput, ITelemetryReporter } from "vscode-azureextensionui";
|
||||
import { func } from "./constants";
|
||||
import { IBindingTemplate } from "./templates/IBindingTemplate";
|
||||
import { TemplateProvider } from "./templates/TemplateProvider";
|
||||
import { CentralTemplateProvider } from "./templates/CentralTemplateProvider";
|
||||
import { AzureAccountTreeItemWithProjects } from "./tree/AzureAccountTreeItemWithProjects";
|
||||
|
||||
/**
|
||||
|
@ -19,17 +18,15 @@ export namespace ext {
|
|||
export let azureAccountTreeItem: AzureAccountTreeItemWithProjects;
|
||||
export let outputChannel: IAzExtOutputChannel;
|
||||
export let ui: IAzureUserInput;
|
||||
export let templateProviderTask: Promise<TemplateProvider>;
|
||||
export let templateProvider: CentralTemplateProvider;
|
||||
export let reporter: ITelemetryReporter;
|
||||
export let funcCliPath: string = func;
|
||||
export let templateSource: TemplateSource | undefined;
|
||||
export let scriptBindings: IBindingTemplate[];
|
||||
// tslint:disable-next-line: strict-boolean-expressions
|
||||
export let ignoreBundle: boolean = !/^(false|0)?$/i.test(process.env.AZCODE_FUNCTIONS_IGNORE_BUNDLE || '');
|
||||
}
|
||||
|
||||
export enum TemplateSource {
|
||||
Backup = 'Backup',
|
||||
CliFeed = 'CliFeed',
|
||||
StagingCliFeed = 'StagingCliFeed'
|
||||
Latest = 'Latest',
|
||||
Staging = 'Staging'
|
||||
}
|
||||
|
|
|
@ -3,208 +3,185 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { callWithTelemetryAndErrorHandling, IActionContext, parseError, TelemetryProperties } from 'vscode-azureextensionui';
|
||||
import { ProjectLanguage, ProjectRuntime, TemplateFilter, templateVersionSetting } from '../constants';
|
||||
import { IActionContext, parseError } from 'vscode-azureextensionui';
|
||||
import { ProjectLanguage, ProjectRuntime, TemplateFilter } from '../constants';
|
||||
import { ext, TemplateSource } from '../extensionVariables';
|
||||
import { localize } from '../localize';
|
||||
import { dotnetUtils } from '../utils/dotnetUtils';
|
||||
import { cliFeedJsonResponse, getFeedRuntime, tryGetCliFeedJson } from '../utils/getCliFeedJson';
|
||||
import { getWorkspaceSetting, updateGlobalSetting } from '../vsCodeConfig/settings';
|
||||
import { DotnetTemplateRetriever, getDotnetVerifiedTemplateIds } from './DotnetTemplateRetriever';
|
||||
import { IBindingSetting } from './IBindingTemplate';
|
||||
import { cliFeedJsonResponse, getCliFeedJson, getFeedRuntime } from '../utils/getCliFeedJson';
|
||||
import { DotnetTemplateProvider } from './dotnet/DotnetTemplateProvider';
|
||||
import { getDotnetVerifiedTemplateIds } from './dotnet/getDotnetVerifiedTemplateIds';
|
||||
import { IBindingTemplate } from './IBindingTemplate';
|
||||
import { IFunctionTemplate, TemplateCategory } from './IFunctionTemplate';
|
||||
import { parseJavaTemplates } from './parseJavaTemplates';
|
||||
import { getScriptVerifiedTemplateIds, ScriptTemplateRetriever } from './ScriptTemplateRetriever';
|
||||
import { TemplateRetriever } from './TemplateRetriever';
|
||||
import { ITemplates } from './ITemplates';
|
||||
import { JavaTemplateProvider } from './java/JavaTemplateProvider';
|
||||
import { getScriptVerifiedTemplateIds } from './script/getScriptVerifiedTemplateIds';
|
||||
import { ScriptTemplateProvider } from './script/ScriptTemplateProvider';
|
||||
import { TemplateProviderBase } from './TemplateProviderBase';
|
||||
|
||||
export class TemplateProvider {
|
||||
private readonly _templatesMap: { [runtime: string]: IFunctionTemplate[] | undefined } = {};
|
||||
// if there are no templates, then there is likely no internet or a problem with the clifeed url
|
||||
private readonly _noInternetErrMsg: string = localize('retryInternet', 'There was an error in retrieving the templates. Recheck your internet connection and try again.');
|
||||
private _javaTemplates: IFunctionTemplate[] | undefined;
|
||||
export class CentralTemplateProvider {
|
||||
public readonly templateSource: TemplateSource | undefined;
|
||||
private readonly _templatesTaskMap: { [key: string]: Promise<ITemplates> | undefined } = {};
|
||||
|
||||
constructor(templatesMap: { [runtime: string]: IFunctionTemplate[] | undefined }) {
|
||||
this._templatesMap = templatesMap;
|
||||
this.copyCSharpSettingsFromJS();
|
||||
public constructor(templateSource?: TemplateSource) {
|
||||
this.templateSource = templateSource;
|
||||
}
|
||||
|
||||
public async getTemplates(language: string, runtime: string, functionAppPath: string, templateFilter?: string, telemetryProperties?: TelemetryProperties): Promise<IFunctionTemplate[]> {
|
||||
const templates: IFunctionTemplate[] | undefined = this._templatesMap[runtime];
|
||||
if (!templates) {
|
||||
throw new Error(this._noInternetErrMsg);
|
||||
}
|
||||
|
||||
if (language === ProjectLanguage.Java) {
|
||||
if (!this._javaTemplates) {
|
||||
this._javaTemplates = await parseJavaTemplates(templates, functionAppPath, telemetryProperties);
|
||||
}
|
||||
return this._javaTemplates;
|
||||
} else {
|
||||
let filterTemplates: IFunctionTemplate[] = templates.filter((t: IFunctionTemplate) => t.language.toLowerCase() === language.toLowerCase());
|
||||
public async getFunctionTemplates(context: IActionContext, language: ProjectLanguage, runtime: ProjectRuntime, templateFilter?: TemplateFilter): Promise<IFunctionTemplate[]> {
|
||||
const templates: ITemplates = await this.getTemplates(context, language, runtime);
|
||||
const functionTemplates: IFunctionTemplate[] = templates.functionTemplates.filter((t: IFunctionTemplate) => t.language.toLowerCase() === language.toLowerCase());
|
||||
switch (templateFilter) {
|
||||
case TemplateFilter.All:
|
||||
break;
|
||||
return functionTemplates;
|
||||
case TemplateFilter.Core:
|
||||
filterTemplates = filterTemplates.filter((t: IFunctionTemplate) => t.categories.find((c: TemplateCategory) => c === TemplateCategory.Core) !== undefined);
|
||||
break;
|
||||
return functionTemplates.filter((t: IFunctionTemplate) => t.categories.find((c: TemplateCategory) => c === TemplateCategory.Core) !== undefined);
|
||||
case TemplateFilter.Verified:
|
||||
default:
|
||||
const verifiedTemplateIds: string[] = getScriptVerifiedTemplateIds(runtime).concat(getDotnetVerifiedTemplateIds(runtime));
|
||||
filterTemplates = filterTemplates.filter((t: IFunctionTemplate) => verifiedTemplateIds.find((vt: string) => vt === t.id));
|
||||
return functionTemplates.filter((t: IFunctionTemplate) => verifiedTemplateIds.find((vt: string) => vt === t.id));
|
||||
}
|
||||
}
|
||||
|
||||
return filterTemplates;
|
||||
public async getBindingTemplates(context: IActionContext, language: ProjectLanguage, runtime: ProjectRuntime): Promise<IBindingTemplate[]> {
|
||||
const templates: ITemplates = await this.getTemplates(context, language, runtime);
|
||||
if (!templates.bindingTemplates) {
|
||||
throw new Error(localize('bindingTemplatesError', 'Binding templates are not supported for language "{0}" and runtime "{1}"', language, runtime));
|
||||
} else {
|
||||
return templates.bindingTemplates;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The dotnet templates do not provide the validation and resourceType information that we desire
|
||||
* As a workaround, we can check for the exact same JavaScript template/setting and leverage that information
|
||||
* Ensures we only have one task going at a time for refreshing templates
|
||||
*/
|
||||
private copyCSharpSettingsFromJS(): void {
|
||||
for (const key of Object.keys(this._templatesMap)) {
|
||||
const templates: IFunctionTemplate[] | undefined = this._templatesMap[key];
|
||||
if (templates) {
|
||||
const jsTemplates: IFunctionTemplate[] = templates.filter((t: IFunctionTemplate) => t.language.toLowerCase() === ProjectLanguage.JavaScript.toLowerCase());
|
||||
const csharpTemplates: IFunctionTemplate[] = templates.filter((t: IFunctionTemplate) => t.language.toLowerCase() === ProjectLanguage.CSharp.toLowerCase());
|
||||
for (const csharpTemplate of csharpTemplates) {
|
||||
const jsTemplate: IFunctionTemplate | undefined = jsTemplates.find((t: IFunctionTemplate) => normalizeId(t.id) === normalizeId(csharpTemplate.id));
|
||||
if (jsTemplate) {
|
||||
for (const cSharpSetting of csharpTemplate.userPromptedSettings) {
|
||||
const jsSetting: IBindingSetting | undefined = jsTemplate.userPromptedSettings.find((t: IBindingSetting) => normalizeName(t.name) === normalizeName(cSharpSetting.name));
|
||||
if (jsSetting) {
|
||||
cSharpSetting.resourceType = jsSetting.resourceType;
|
||||
cSharpSetting.validateSetting = jsSetting.validateSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private async getTemplates(context: IActionContext, language: ProjectLanguage, runtime: ProjectRuntime): Promise<ITemplates> {
|
||||
let provider: TemplateProviderBase;
|
||||
switch (language) {
|
||||
case ProjectLanguage.CSharp:
|
||||
case ProjectLanguage.FSharp:
|
||||
provider = new DotnetTemplateProvider(runtime);
|
||||
break;
|
||||
case ProjectLanguage.Java:
|
||||
provider = new JavaTemplateProvider(runtime);
|
||||
break;
|
||||
default:
|
||||
provider = new ScriptTemplateProvider(runtime);
|
||||
break;
|
||||
}
|
||||
|
||||
const key: string = provider.templateType + provider.runtime;
|
||||
let templatesTask: Promise<ITemplates> | undefined = this._templatesTaskMap[key];
|
||||
if (templatesTask) {
|
||||
return await templatesTask;
|
||||
} else {
|
||||
templatesTask = this.refreshTemplates(context, provider);
|
||||
this._templatesTaskMap[key] = templatesTask;
|
||||
try {
|
||||
return await templatesTask;
|
||||
} catch (error) {
|
||||
// If an error occurs, we want to start from scratch next time we try to get templates so remove this task from the map
|
||||
delete this._templatesTaskMap[key];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ids like "Azure.Function.CSharp.QueueTrigger.2.x" or "QueueTrigger-JavaScript" to "queuetrigger"
|
||||
*/
|
||||
function normalizeId(id: string): string {
|
||||
const match: RegExpMatchArray | null = id.match(/[a-z]+Trigger/i);
|
||||
return normalizeName(match ? match[0] : id);
|
||||
}
|
||||
private async refreshTemplates(context: IActionContext, provider: TemplateProviderBase): Promise<ITemplates> {
|
||||
context.telemetry.properties.runtime = provider.runtime;
|
||||
context.telemetry.properties.templateType = provider.templateType;
|
||||
|
||||
function normalizeName(name: string): string {
|
||||
return name.toLowerCase().replace(/\s/g, '');
|
||||
}
|
||||
|
||||
export async function getTemplateProvider(): Promise<TemplateProvider> {
|
||||
const templatesMap: { [runtime: string]: IFunctionTemplate[] | undefined } = {};
|
||||
const cliFeedJson: cliFeedJsonResponse | undefined = await tryGetCliFeedJson();
|
||||
|
||||
const templateRetrievers: TemplateRetriever[] = [new ScriptTemplateRetriever()];
|
||||
if (await dotnetUtils.isDotnetInstalled()) {
|
||||
templateRetrievers.push(new DotnetTemplateRetriever());
|
||||
}
|
||||
|
||||
for (const templateRetriever of templateRetrievers) {
|
||||
for (const key of Object.keys(ProjectRuntime)) {
|
||||
const runtime: ProjectRuntime = <ProjectRuntime>ProjectRuntime[key];
|
||||
|
||||
await callWithTelemetryAndErrorHandling('azureFunctions.getFunctionTemplates', async (context: IActionContext) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
context.telemetry.properties.runtime = runtime;
|
||||
context.telemetry.properties.templateType = templateRetriever.templateType;
|
||||
const templateVersion: string | undefined = await tryGetTemplateVersionSetting(context, cliFeedJson, runtime);
|
||||
let templates: IFunctionTemplate[] | undefined;
|
||||
let result: ITemplates | undefined;
|
||||
let latestTemplatesError: unknown;
|
||||
try {
|
||||
const cliFeedJson: cliFeedJsonResponse = await getCliFeedJson();
|
||||
const feedRuntime: string = getFeedRuntime(provider.runtime);
|
||||
const templateVersion: string = cliFeedJson.tags[feedRuntime].release;
|
||||
context.telemetry.properties.templateVersion = templateVersion;
|
||||
const cachedTemplateVersion: string | undefined = ext.context.globalState.get(provider.getCacheKey(TemplateProviderBase.templateVersionKey));
|
||||
context.telemetry.properties.cachedTemplateVersion = cachedTemplateVersion;
|
||||
|
||||
// 1. Use the cached templates if they match templateVersion
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!ext.templateSource && ext.context.globalState.get(templateRetriever.getCacheKey(TemplateRetriever.templateVersionKey, runtime)) === templateVersion) {
|
||||
templates = await templateRetriever.tryGetTemplatesFromCache(context, runtime);
|
||||
context.telemetry.properties.templateSource = 'matchingCache';
|
||||
if (cachedTemplateVersion === templateVersion) {
|
||||
result = await this.tryGetCachedTemplates(context, provider);
|
||||
}
|
||||
|
||||
// 2. Download templates from the cli-feed if the cache doesn't match templateVersion
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if ((!ext.templateSource || ext.templateSource === TemplateSource.CliFeed || ext.templateSource === TemplateSource.StagingCliFeed) && !templates && cliFeedJson && templateVersion) {
|
||||
templates = await templateRetriever.tryGetTemplatesFromCliFeed(context, cliFeedJson, templateVersion, runtime);
|
||||
context.telemetry.properties.templateSource = 'cliFeed';
|
||||
if (!result) {
|
||||
result = await this.getLatestTemplates(context, provider, cliFeedJson, templateVersion);
|
||||
}
|
||||
} catch (error) {
|
||||
// This error should be the most actionable to the user, so save it and throw later if cache/backup doesn't work
|
||||
latestTemplatesError = error;
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(localize('latestTemplatesError', 'Failed to get latest templates: {0}', errorMessage));
|
||||
context.telemetry.properties.latestTemplatesError = errorMessage;
|
||||
}
|
||||
|
||||
// 3. Use the cached templates, even if they don't match templateVersion
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!ext.templateSource && !templates) {
|
||||
templates = await templateRetriever.tryGetTemplatesFromCache(context, runtime);
|
||||
context.telemetry.properties.templateSource = 'mismatchCache';
|
||||
if (!result) {
|
||||
result = await this.tryGetCachedTemplates(context, provider);
|
||||
}
|
||||
|
||||
// 4. Use backup templates shipped with the extension
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if ((!ext.templateSource || ext.templateSource === TemplateSource.Backup) && !templates) {
|
||||
templates = await templateRetriever.tryGetTemplatesFromBackup(context, runtime);
|
||||
context.telemetry.properties.templateSource = 'backupFromExtension';
|
||||
if (!result) {
|
||||
result = await this.tryGetBackupTemplates(context, provider);
|
||||
}
|
||||
|
||||
if (templates) {
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
templatesMap[runtime] = (templatesMap[runtime] || []).concat(templates);
|
||||
if (result) {
|
||||
return result;
|
||||
} else if (latestTemplatesError !== undefined) {
|
||||
throw latestTemplatesError;
|
||||
} else {
|
||||
// Failed to get templates for this runtime
|
||||
context.telemetry.properties.templateSource = 'None';
|
||||
}
|
||||
});
|
||||
// This should only happen for dev/test scenarios where we explicitly set templateSource
|
||||
throw new Error(localize('templateSourceError', 'Internal error: Failed to get templates for source "{0}".', this.templateSource));
|
||||
}
|
||||
}
|
||||
|
||||
return new TemplateProvider(templatesMap);
|
||||
private async getLatestTemplates(context: IActionContext, provider: TemplateProviderBase, cliFeedJson: cliFeedJsonResponse, templateVersion: string): Promise<ITemplates | undefined> {
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!this.templateSource || this.templateSource === TemplateSource.Latest || this.templateSource === TemplateSource.Staging) {
|
||||
context.telemetry.properties.templateSource = 'latest';
|
||||
const result: ITemplates = await provider.getLatestTemplates(cliFeedJson, templateVersion, context);
|
||||
ext.context.globalState.update(provider.getCacheKey(TemplateProviderBase.templateVersionKey), templateVersion);
|
||||
await provider.cacheTemplates();
|
||||
return result;
|
||||
}
|
||||
|
||||
export function removeLanguageFromId(id: string): string {
|
||||
return id.split('-')[0];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function tryGetTemplateVersionSetting(context: IActionContext, cliFeedJson: cliFeedJsonResponse | undefined, runtime: ProjectRuntime): Promise<string | undefined> {
|
||||
const feedRuntime: string = getFeedRuntime(runtime);
|
||||
const userTemplateVersion: string | undefined = getWorkspaceSetting(templateVersionSetting);
|
||||
private async tryGetCachedTemplates(context: IActionContext, provider: TemplateProviderBase): Promise<ITemplates | undefined> {
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!this.templateSource) {
|
||||
try {
|
||||
if (userTemplateVersion) {
|
||||
context.telemetry.properties.userTemplateVersion = userTemplateVersion;
|
||||
}
|
||||
let templateVersion: string;
|
||||
if (cliFeedJson) {
|
||||
templateVersion = userTemplateVersion ? userTemplateVersion : cliFeedJson.tags[feedRuntime].release;
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!cliFeedJson.releases[templateVersion]) {
|
||||
const invalidVersion: string = localize('invalidTemplateVersion', 'Failed to retrieve Azure Functions templates for version "{0}".', templateVersion);
|
||||
const selectVersion: vscode.MessageItem = { title: localize('selectVersion', 'Select version') };
|
||||
const useLatest: vscode.MessageItem = { title: localize('useLatest', 'Use latest') };
|
||||
const warningInput: vscode.MessageItem = await ext.ui.showWarningMessage(invalidVersion, selectVersion, useLatest);
|
||||
if (warningInput === selectVersion) {
|
||||
const releaseQuickPicks: vscode.QuickPickItem[] = [];
|
||||
for (const rel of Object.keys(cliFeedJson.releases)) {
|
||||
releaseQuickPicks.push({
|
||||
label: rel,
|
||||
description: ''
|
||||
});
|
||||
}
|
||||
const input: vscode.QuickPickItem | undefined = await ext.ui.showQuickPick(releaseQuickPicks, { placeHolder: invalidVersion });
|
||||
templateVersion = input.label;
|
||||
await updateGlobalSetting(templateVersionSetting, input.label);
|
||||
} else {
|
||||
templateVersion = cliFeedJson.tags[feedRuntime].release;
|
||||
// reset user setting so that it always gets latest
|
||||
await updateGlobalSetting(templateVersionSetting, '');
|
||||
context.telemetry.properties.templateSource = 'cache';
|
||||
return await provider.getCachedTemplates();
|
||||
} catch (error) {
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(localize('cachedTemplatesError', 'Failed to get cached templates: {0}', errorMessage));
|
||||
context.telemetry.properties.cachedTemplatesError = errorMessage;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return templateVersion;
|
||||
private async tryGetBackupTemplates(context: IActionContext, provider: TemplateProviderBase): Promise<ITemplates | undefined> {
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
if (!this.templateSource || this.templateSource === TemplateSource.Backup) {
|
||||
try {
|
||||
context.telemetry.properties.templateSource = 'backup';
|
||||
const backupTemplateVersion: string = provider.getBackupVersion();
|
||||
const result: ITemplates = await provider.getBackupTemplates();
|
||||
ext.context.globalState.update(provider.getCacheKey(TemplateProviderBase.templateVersionKey), backupTemplateVersion);
|
||||
await provider.cacheTemplates();
|
||||
return result;
|
||||
} catch (error) {
|
||||
// if cliJson does not have the template version being searched for, it will throw an error
|
||||
context.telemetry.properties.userTemplateVersion = parseError(error).message;
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(localize('backupTemplatesError', 'Failed to get backup templates: {0}', errorMessage));
|
||||
context.telemetry.properties.backupTemplatesError = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IBindingTemplate } from "./IBindingTemplate";
|
||||
import { IFunctionTemplate } from "./IFunctionTemplate";
|
||||
|
||||
export interface ITemplates {
|
||||
functionTemplates: IFunctionTemplate[];
|
||||
bindingTemplates?: IBindingTemplate[];
|
||||
}
|
|
@ -4,76 +4,37 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { IActionContext, parseError } from 'vscode-azureextensionui';
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { ProjectRuntime } from '../constants';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { localize } from '../localize';
|
||||
import { cliFeedJsonResponse } from '../utils/getCliFeedJson';
|
||||
import { IFunctionTemplate } from './IFunctionTemplate';
|
||||
import { ITemplates } from './ITemplates';
|
||||
|
||||
const v2BackupTemplatesVersion: string = '2.18.1';
|
||||
const v1BackupTemplatesVersion: string = '1.8.0';
|
||||
|
||||
export enum TemplateType {
|
||||
Script = 'Script',
|
||||
Dotnet = '.NET'
|
||||
Dotnet = '.NET',
|
||||
Java = 'Java'
|
||||
}
|
||||
|
||||
export abstract class TemplateRetriever {
|
||||
export abstract class TemplateProviderBase {
|
||||
public static templateVersionKey: string = 'templateVersion';
|
||||
public abstract templateType: TemplateType;
|
||||
public readonly runtime: ProjectRuntime;
|
||||
|
||||
public async tryGetTemplatesFromCache(context: IActionContext, runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined> {
|
||||
try {
|
||||
return await this.getTemplatesFromCache(runtime);
|
||||
} catch (error) {
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(errorMessage);
|
||||
context.telemetry.properties.cacheError = errorMessage;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async tryGetTemplatesFromCliFeed(context: IActionContext, cliFeedJson: cliFeedJsonResponse, templateVersion: string, runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined> {
|
||||
try {
|
||||
context.telemetry.properties.templateVersion = templateVersion;
|
||||
ext.outputChannel.appendLog(localize('updatingTemplates', 'Updating {0} templates for runtime "{1}" to version "{2}"...', this.templateType, runtime, templateVersion));
|
||||
const templates: IFunctionTemplate[] = await this.getTemplatesFromCliFeed(cliFeedJson, templateVersion, runtime, context);
|
||||
ext.context.globalState.update(this.getCacheKey(TemplateRetriever.templateVersionKey, runtime), templateVersion);
|
||||
await this.cacheTemplates(runtime);
|
||||
ext.outputChannel.appendLog(localize('updatedTemplates', 'Successfully updated templates.'));
|
||||
return templates;
|
||||
} catch (error) {
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(errorMessage);
|
||||
context.telemetry.properties.cliFeedError = errorMessage;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async tryGetTemplatesFromBackup(context: IActionContext, runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined> {
|
||||
try {
|
||||
const backupTemplateVersion: string = this.getBackupVersion(runtime);
|
||||
const templates: IFunctionTemplate[] = await this.getTemplatesFromBackup(runtime);
|
||||
ext.context.globalState.update(this.getCacheKey(TemplateRetriever.templateVersionKey, runtime), backupTemplateVersion);
|
||||
await this.cacheTemplates(runtime);
|
||||
ext.outputChannel.appendLog(localize('usingBackupTemplates', 'Falling back to version "{0}" for {1} templates for runtime "{2}".', backupTemplateVersion, this.templateType, runtime));
|
||||
return templates;
|
||||
} catch (error) {
|
||||
const errorMessage: string = parseError(error).message;
|
||||
ext.outputChannel.appendLog(errorMessage);
|
||||
context.telemetry.properties.backupError = errorMessage;
|
||||
return undefined;
|
||||
}
|
||||
public constructor(runtime: ProjectRuntime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds runtime, templateType, and language information to a key to ensure there are no collisions in the cache
|
||||
* For backwards compatability, the original runtime, templateType, and language will not have this information
|
||||
*/
|
||||
public getCacheKey(key: string, runtime: ProjectRuntime): string {
|
||||
if (runtime !== ProjectRuntime.v1) {
|
||||
key = `${key}.${runtime}`;
|
||||
public getCacheKey(key: string): string {
|
||||
if (this.runtime !== ProjectRuntime.v1) {
|
||||
key = `${key}.${this.runtime}`;
|
||||
}
|
||||
|
||||
if (this.templateType !== TemplateType.Script) {
|
||||
|
@ -87,19 +48,19 @@ export abstract class TemplateRetriever {
|
|||
return key;
|
||||
}
|
||||
|
||||
protected abstract getTemplatesFromCache(runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined>;
|
||||
protected abstract getTemplatesFromCliFeed(cliFeedJson: cliFeedJsonResponse, templateVersion: string, runtime: ProjectRuntime, context: IActionContext): Promise<IFunctionTemplate[]>;
|
||||
protected abstract getTemplatesFromBackup(runtime: ProjectRuntime): Promise<IFunctionTemplate[]>;
|
||||
protected abstract cacheTemplates(runtime: ProjectRuntime): Promise<void>;
|
||||
public abstract getCachedTemplates(): Promise<ITemplates | undefined>;
|
||||
public abstract getLatestTemplates(cliFeedJson: cliFeedJsonResponse, templateVersion: string, context: IActionContext): Promise<ITemplates>;
|
||||
public abstract getBackupTemplates(): Promise<ITemplates>;
|
||||
public abstract cacheTemplates(): Promise<void>;
|
||||
|
||||
protected getBackupVersion(runtime: ProjectRuntime): string {
|
||||
switch (runtime) {
|
||||
public getBackupVersion(): string {
|
||||
switch (this.runtime) {
|
||||
case ProjectRuntime.v1:
|
||||
return v1BackupTemplatesVersion;
|
||||
case ProjectRuntime.v2:
|
||||
return v2BackupTemplatesVersion;
|
||||
default:
|
||||
throw new RangeError(localize('invalidRuntime', 'Invalid runtime "{0}".', runtime));
|
||||
throw new RangeError(localize('invalidRuntime', 'Invalid runtime "{0}".', this.runtime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,56 +6,59 @@
|
|||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { ProjectRuntime } from '../constants';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { downloadFile } from '../utils/fs';
|
||||
import { cliFeedJsonResponse } from '../utils/getCliFeedJson';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { dotnetUtils } from '../../utils/dotnetUtils';
|
||||
import { downloadFile } from '../../utils/fs';
|
||||
import { cliFeedJsonResponse } from '../../utils/getCliFeedJson';
|
||||
import { IFunctionTemplate } from '../IFunctionTemplate';
|
||||
import { ITemplates } from '../ITemplates';
|
||||
import { TemplateProviderBase, TemplateType } from '../TemplateProviderBase';
|
||||
import { executeDotnetTemplateCommand, getDotnetItemTemplatePath, getDotnetProjectTemplatePath, getDotnetTemplatesPath } from './executeDotnetTemplateCommand';
|
||||
import { IFunctionTemplate } from './IFunctionTemplate';
|
||||
import { parseDotnetTemplates } from './parseDotnetTemplates';
|
||||
import { TemplateRetriever, TemplateType } from './TemplateRetriever';
|
||||
|
||||
export class DotnetTemplateRetriever extends TemplateRetriever {
|
||||
export class DotnetTemplateProvider extends TemplateProviderBase {
|
||||
public templateType: TemplateType = TemplateType.Dotnet;
|
||||
private readonly _dotnetTemplatesKey: string = 'DotnetTemplates';
|
||||
private _rawTemplates: object[];
|
||||
|
||||
protected async getTemplatesFromCache(runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined> {
|
||||
const projectFilePath: string = getDotnetProjectTemplatePath(runtime);
|
||||
const itemFilePath: string = getDotnetItemTemplatePath(runtime);
|
||||
public async getCachedTemplates(): Promise<ITemplates | undefined> {
|
||||
const projectFilePath: string = getDotnetProjectTemplatePath(this.runtime);
|
||||
const itemFilePath: string = getDotnetItemTemplatePath(this.runtime);
|
||||
if (!await fse.pathExists(projectFilePath) || !await fse.pathExists(itemFilePath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cachedDotnetTemplates: object[] | undefined = ext.context.globalState.get<object[]>(this.getCacheKey(this._dotnetTemplatesKey, runtime));
|
||||
const cachedDotnetTemplates: object[] | undefined = ext.context.globalState.get<object[]>(this.getCacheKey(this._dotnetTemplatesKey));
|
||||
if (cachedDotnetTemplates) {
|
||||
return parseDotnetTemplates(cachedDotnetTemplates, runtime);
|
||||
return { functionTemplates: await parseDotnetTemplates(cachedDotnetTemplates, this.runtime) };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected async getTemplatesFromCliFeed(cliFeedJson: cliFeedJsonResponse, templateVersion: string, runtime: ProjectRuntime, _context: IActionContext): Promise<IFunctionTemplate[]> {
|
||||
const projectFilePath: string = getDotnetProjectTemplatePath(runtime);
|
||||
public async getLatestTemplates(cliFeedJson: cliFeedJsonResponse, templateVersion: string, context: IActionContext): Promise<ITemplates> {
|
||||
await dotnetUtils.validateDotnetInstalled(context);
|
||||
|
||||
const projectFilePath: string = getDotnetProjectTemplatePath(this.runtime);
|
||||
await downloadFile(cliFeedJson.releases[templateVersion].projectTemplates, projectFilePath);
|
||||
|
||||
const itemFilePath: string = getDotnetItemTemplatePath(runtime);
|
||||
const itemFilePath: string = getDotnetItemTemplatePath(this.runtime);
|
||||
await downloadFile(cliFeedJson.releases[templateVersion].itemTemplates, itemFilePath);
|
||||
|
||||
return await this.parseTemplates(runtime);
|
||||
return { functionTemplates: await this.parseTemplates() };
|
||||
}
|
||||
|
||||
protected async getTemplatesFromBackup(runtime: ProjectRuntime): Promise<IFunctionTemplate[]> {
|
||||
public async getBackupTemplates(): Promise<ITemplates> {
|
||||
await fse.copy(ext.context.asAbsolutePath(path.join('resources', 'backupDotnetTemplates')), getDotnetTemplatesPath(), { overwrite: true, recursive: false });
|
||||
return await this.parseTemplates(runtime);
|
||||
return { functionTemplates: await this.parseTemplates() };
|
||||
}
|
||||
|
||||
protected async cacheTemplates(runtime: ProjectRuntime): Promise<void> {
|
||||
ext.context.globalState.update(this.getCacheKey(this._dotnetTemplatesKey, runtime), this._rawTemplates);
|
||||
public async cacheTemplates(): Promise<void> {
|
||||
ext.context.globalState.update(this.getCacheKey(this._dotnetTemplatesKey), this._rawTemplates);
|
||||
}
|
||||
|
||||
private async parseTemplates(runtime: ProjectRuntime): Promise<IFunctionTemplate[]> {
|
||||
this._rawTemplates = <object[]>JSON.parse(await executeDotnetTemplateCommand(runtime, undefined, 'list'));
|
||||
return parseDotnetTemplates(this._rawTemplates, runtime);
|
||||
private async parseTemplates(): Promise<IFunctionTemplate[]> {
|
||||
this._rawTemplates = <object[]>JSON.parse(await executeDotnetTemplateCommand(this.runtime, undefined, 'list'));
|
||||
return parseDotnetTemplates(this._rawTemplates, this.runtime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { ProjectRuntime } from '../constants';
|
||||
import { ext } from "../extensionVariables";
|
||||
import { cpUtils } from "../utils/cpUtils";
|
||||
import { ProjectRuntime } from '../../constants';
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { cpUtils } from "../../utils/cpUtils";
|
||||
|
||||
export async function executeDotnetTemplateCommand(runtime: ProjectRuntime, workingDirectory: string | undefined, operation: 'list' | 'create', ...args: string[]): Promise<string> {
|
||||
const jsonDllPath: string = ext.context.asAbsolutePath(path.join('resources', 'dotnetJsonCli', 'Microsoft.TemplateEngine.JsonCli.dll'));
|
||||
|
@ -26,7 +26,7 @@ export async function executeDotnetTemplateCommand(runtime: ProjectRuntime, work
|
|||
|
||||
export function getDotnetTemplatesPath(): string {
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
return path.join(ext.context.globalStoragePath, 'dotnetTemplates', ext.templateSource || '');
|
||||
return path.join(ext.context.globalStoragePath, 'dotnetTemplates', ext.templateProvider.templateSource || '');
|
||||
}
|
||||
|
||||
export function getDotnetItemTemplatePath(runtime: ProjectRuntime): string {
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProjectLanguage, ProjectRuntime } from '../constants';
|
||||
import { IBindingSetting, ValueType } from './IBindingTemplate';
|
||||
import { IFunctionTemplate, TemplateCategory } from './IFunctionTemplate';
|
||||
import { callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
|
||||
import { ProjectLanguage, ProjectRuntime } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { IBindingSetting, ValueType } from '../IBindingTemplate';
|
||||
import { IFunctionTemplate, TemplateCategory } from '../IFunctionTemplate';
|
||||
|
||||
/**
|
||||
* Describes a dotnet template before it has been parsed
|
||||
|
@ -70,7 +72,7 @@ function parseDotnetTemplate(rawTemplate: IRawTemplate): IFunctionTemplate {
|
|||
* Parses templates used by the .NET CLI
|
||||
* This basically converts the 'raw' templates in the externally defined JSON format to a common and understood format (IFunctionTemplate) used by this extension
|
||||
*/
|
||||
export function parseDotnetTemplates(rawTemplates: object[], runtime: ProjectRuntime): IFunctionTemplate[] {
|
||||
export async function parseDotnetTemplates(rawTemplates: object[], runtime: ProjectRuntime): Promise<IFunctionTemplate[]> {
|
||||
const templates: IFunctionTemplate[] = [];
|
||||
for (const rawTemplate of rawTemplates) {
|
||||
try {
|
||||
|
@ -83,5 +85,46 @@ export function parseDotnetTemplates(rawTemplates: object[], runtime: ProjectRun
|
|||
// Ignore errors so that a single poorly formed template does not affect other templates
|
||||
}
|
||||
}
|
||||
|
||||
await copyCSharpSettingsFromJS(templates, runtime);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dotnet templates do not provide the validation and resourceType information that we desire
|
||||
* As a workaround, we can check for the exact same JavaScript template/setting and leverage that information
|
||||
*/
|
||||
async function copyCSharpSettingsFromJS(csharpTemplates: IFunctionTemplate[], runtime: ProjectRuntime): Promise<void> {
|
||||
// Use separate telemetry event since we don't want to overwrite C# telemetry with JS telemetry
|
||||
await callWithTelemetryAndErrorHandling('copyCSharpSettingsFromJS', async (jsContext: IActionContext) => {
|
||||
jsContext.errorHandling.suppressDisplay = true;
|
||||
jsContext.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
const jsTemplates: IFunctionTemplate[] = await ext.templateProvider.getFunctionTemplates(jsContext, ProjectLanguage.JavaScript, runtime);
|
||||
for (const csharpTemplate of csharpTemplates) {
|
||||
const jsTemplate: IFunctionTemplate | undefined = jsTemplates.find((t: IFunctionTemplate) => normalizeId(t.id) === normalizeId(csharpTemplate.id));
|
||||
if (jsTemplate) {
|
||||
for (const cSharpSetting of csharpTemplate.userPromptedSettings) {
|
||||
const jsSetting: IBindingSetting | undefined = jsTemplate.userPromptedSettings.find((t: IBindingSetting) => normalizeName(t.name) === normalizeName(cSharpSetting.name));
|
||||
if (jsSetting) {
|
||||
cSharpSetting.resourceType = jsSetting.resourceType;
|
||||
cSharpSetting.validateSetting = jsSetting.validateSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ids like "Azure.Function.CSharp.QueueTrigger.2.x" or "QueueTrigger-JavaScript" to "queuetrigger"
|
||||
*/
|
||||
function normalizeId(id: string): string {
|
||||
const match: RegExpMatchArray | null = id.match(/[a-z]+Trigger/i);
|
||||
return normalizeName(match ? match[0] : id);
|
||||
}
|
||||
|
||||
function normalizeName(name: string): string {
|
||||
return name.toLowerCase().replace(/\s/g, '');
|
||||
}
|
||||
|
|
|
@ -3,72 +3,44 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { parseError, TelemetryProperties } from 'vscode-azureextensionui';
|
||||
import { ProjectLanguage } from '../constants';
|
||||
import { mavenUtils } from "../utils/mavenUtils";
|
||||
import { IBindingTemplate } from './IBindingTemplate';
|
||||
import { IFunctionTemplate } from './IFunctionTemplate';
|
||||
import { IConfig, IRawTemplate, IResources, parseScriptBindings, parseScriptTemplate as parseJavaTemplate } from './parseScriptTemplates';
|
||||
import { removeLanguageFromId } from "./TemplateProvider";
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { localize } from '../../localize';
|
||||
import { cliFeedJsonResponse } from '../../utils/getCliFeedJson';
|
||||
import { mavenUtils } from '../../utils/mavenUtils';
|
||||
import { ITemplates } from '../ITemplates';
|
||||
import { parseScriptTemplates } from '../script/parseScriptTemplates';
|
||||
import { ScriptTemplateProvider } from '../script/ScriptTemplateProvider';
|
||||
import { TemplateType } from '../TemplateProviderBase';
|
||||
|
||||
/**
|
||||
* Describes templates output before it has been parsed
|
||||
*/
|
||||
interface IRawJavaTemplates {
|
||||
templates: IRawTemplate[];
|
||||
templates: object[];
|
||||
}
|
||||
|
||||
const backupJavaTemplateNames: string[] = [
|
||||
'HttpTrigger',
|
||||
'BlobTrigger',
|
||||
'QueueTrigger',
|
||||
'TimerTrigger'
|
||||
];
|
||||
|
||||
/**
|
||||
* Parses templates contained in the output of 'mvn azure-functions:list'.
|
||||
* This basically converts the 'raw' templates in the externally defined JSON format to a common and understood format (IFunctionTemplate) used by this extension
|
||||
* Java templates largely follow the same formatting as script templates, but they come from maven
|
||||
*/
|
||||
export async function parseJavaTemplates(allTemplates: IFunctionTemplate[], functionAppPath: string, telemetryProperties?: TelemetryProperties): Promise<IFunctionTemplate[]> {
|
||||
let embeddedTemplates: IRawJavaTemplates = { templates: [] };
|
||||
let embeddedConfig: IConfig | undefined;
|
||||
let embeddedResources: object = {};
|
||||
try {
|
||||
// Try to get the templates information by calling 'mvn azure-functions:list'.
|
||||
const commandResult: string = await mavenUtils.executeMvnCommand(telemetryProperties, undefined, functionAppPath, 'azure-functions:list');
|
||||
export class JavaTemplateProvider extends ScriptTemplateProvider {
|
||||
public templateType: TemplateType = TemplateType.Java;
|
||||
protected readonly _templatesKey: string = 'JavaFunctionTemplates';
|
||||
protected readonly _configKey: string = 'JavaFunctionTemplateConfig';
|
||||
protected readonly _resourcesKey: string = 'JavaFunctionTemplateResources';
|
||||
|
||||
public async getLatestTemplates(_cliFeedJson: cliFeedJsonResponse, _templateVersion: string, context: IActionContext): Promise<ITemplates> {
|
||||
await mavenUtils.validateMavenInstalled(context);
|
||||
|
||||
const commandResult: string = await mavenUtils.executeMvnCommand(context.telemetry.properties, undefined, undefined, 'azure-functions:list');
|
||||
const regExp: RegExp = />> templates begin <<([\S\s]+)^.+INFO.+ >> templates end <<$[\S\s]+>> bindings begin <<([\S\s]+)^.+INFO.+ >> bindings end <<$[\S\s]+>> resources begin <<([\S\s]+)^.+INFO.+ >> resources end <<$/gm;
|
||||
const regExpResult: RegExpExecArray | null = regExp.exec(commandResult);
|
||||
if (regExpResult && regExpResult.length > 3) {
|
||||
embeddedTemplates = <IRawJavaTemplates>JSON.parse(regExpResult[1]);
|
||||
embeddedConfig = <IConfig>JSON.parse(regExpResult[2]);
|
||||
embeddedResources = <object[]>JSON.parse(regExpResult[3]);
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallow the exception if the plugin do not support list templates information.
|
||||
if (telemetryProperties) {
|
||||
telemetryProperties.parseJavaTemplateErrors = parseError(error).message;
|
||||
}
|
||||
}
|
||||
|
||||
const templates: IFunctionTemplate[] = [];
|
||||
if (embeddedConfig) {
|
||||
const bindings: IBindingTemplate[] = parseScriptBindings(embeddedConfig, <IResources>embeddedResources);
|
||||
for (const template of embeddedTemplates.templates) {
|
||||
try {
|
||||
templates.push(parseJavaTemplate(template, <IResources>embeddedResources, bindings));
|
||||
} catch (error) {
|
||||
// Ignore errors so that a single poorly formed template does not affect other templates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (templates.length > 0) {
|
||||
return templates;
|
||||
this._rawTemplates = (<IRawJavaTemplates>JSON.parse(regExpResult[1])).templates;
|
||||
this._rawConfig = <object>JSON.parse(regExpResult[2]);
|
||||
this._rawResources = <object[]>JSON.parse(regExpResult[3]);
|
||||
return parseScriptTemplates(this._rawResources, this._rawTemplates, this._rawConfig);
|
||||
} else {
|
||||
// If the templates.length is 0, this means that the user is using an older version of Maven function plugin,
|
||||
// which do not have the functionality to provide the template information.
|
||||
// For this kind of scenario, we will fallback to leverage the JavaScript templates.
|
||||
const javaScriptTemplates: IFunctionTemplate[] = allTemplates.filter((t: IFunctionTemplate) => t.language === ProjectLanguage.JavaScript);
|
||||
return javaScriptTemplates.filter((t: IFunctionTemplate) => backupJavaTemplateNames.find((vt: string) => vt === removeLanguageFromId(t.id)));
|
||||
throw new Error(localize('oldFunctionPlugin', 'You must update the Azure Functions maven plugin for this functionality.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,27 +8,28 @@ import * as fse from 'fs-extra';
|
|||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { IActionContext } from 'vscode-azureextensionui';
|
||||
import { ProjectRuntime } from '../constants';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { downloadFile } from '../utils/fs';
|
||||
import { cliFeedJsonResponse } from '../utils/getCliFeedJson';
|
||||
import { IFunctionTemplate } from './IFunctionTemplate';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { downloadFile } from '../../utils/fs';
|
||||
import { cliFeedJsonResponse } from '../../utils/getCliFeedJson';
|
||||
import { ITemplates } from '../ITemplates';
|
||||
import { TemplateProviderBase, TemplateType } from '../TemplateProviderBase';
|
||||
import { getScriptResourcesPath } from './getScriptResourcesPath';
|
||||
import { parseScriptTemplates } from './parseScriptTemplates';
|
||||
import { TemplateRetriever, TemplateType } from './TemplateRetriever';
|
||||
|
||||
export class ScriptTemplateRetriever extends TemplateRetriever {
|
||||
export class ScriptTemplateProvider extends TemplateProviderBase {
|
||||
public templateType: TemplateType = TemplateType.Script;
|
||||
private readonly _templatesKey: string = 'FunctionTemplates';
|
||||
private readonly _configKey: string = 'FunctionTemplateConfig';
|
||||
private readonly _resourcesKey: string = 'FunctionTemplateResources';
|
||||
private _rawResources: object;
|
||||
private _rawTemplates: object[];
|
||||
private _rawConfig: object;
|
||||
protected readonly _templatesKey: string = 'FunctionTemplates';
|
||||
protected readonly _configKey: string = 'FunctionTemplateConfig';
|
||||
protected readonly _resourcesKey: string = 'FunctionTemplateResources';
|
||||
|
||||
protected async getTemplatesFromCache(runtime: ProjectRuntime): Promise<IFunctionTemplate[] | undefined> {
|
||||
const cachedResources: object | undefined = ext.context.globalState.get<object>(this.getCacheKey(this._resourcesKey, runtime));
|
||||
const cachedTemplates: object[] | undefined = ext.context.globalState.get<object[]>(this.getCacheKey(this._templatesKey, runtime));
|
||||
const cachedConfig: object | undefined = ext.context.globalState.get<object>(this.getCacheKey(this._configKey, runtime));
|
||||
protected _rawResources: object;
|
||||
protected _rawTemplates: object[];
|
||||
protected _rawConfig: object;
|
||||
|
||||
public async getCachedTemplates(): Promise<ITemplates | undefined> {
|
||||
const cachedResources: object | undefined = ext.context.globalState.get<object>(this.getCacheKey(this._resourcesKey));
|
||||
const cachedTemplates: object[] | undefined = ext.context.globalState.get<object[]>(this.getCacheKey(this._templatesKey));
|
||||
const cachedConfig: object | undefined = ext.context.globalState.get<object>(this.getCacheKey(this._configKey));
|
||||
if (cachedResources && cachedTemplates && cachedConfig) {
|
||||
return parseScriptTemplates(cachedResources, cachedTemplates, cachedConfig);
|
||||
} else {
|
||||
|
@ -36,7 +37,7 @@ export class ScriptTemplateRetriever extends TemplateRetriever {
|
|||
}
|
||||
}
|
||||
|
||||
protected async getTemplatesFromCliFeed(cliFeedJson: cliFeedJsonResponse, templateVersion: string, _runtime: ProjectRuntime, _context: IActionContext): Promise<IFunctionTemplate[]> {
|
||||
public async getLatestTemplates(cliFeedJson: cliFeedJsonResponse, templateVersion: string, _context: IActionContext): Promise<ITemplates> {
|
||||
const templatesPath: string = path.join(os.tmpdir(), 'vscode-azurefunctions-templates');
|
||||
try {
|
||||
const filePath: string = path.join(templatesPath, `templates-${templateVersion}.zip`);
|
||||
|
@ -61,19 +62,19 @@ export class ScriptTemplateRetriever extends TemplateRetriever {
|
|||
}
|
||||
}
|
||||
|
||||
protected async getTemplatesFromBackup(runtime: ProjectRuntime): Promise<IFunctionTemplate[]> {
|
||||
const backupTemplatesPath: string = ext.context.asAbsolutePath(path.join('resources', 'backupScriptTemplates', runtime));
|
||||
public async getBackupTemplates(): Promise<ITemplates> {
|
||||
const backupTemplatesPath: string = ext.context.asAbsolutePath(path.join('resources', 'backupScriptTemplates', this.runtime));
|
||||
return await this.parseTemplates(backupTemplatesPath);
|
||||
}
|
||||
|
||||
protected async cacheTemplates(runtime: ProjectRuntime): Promise<void> {
|
||||
ext.context.globalState.update(this.getCacheKey(this._templatesKey, runtime), this._rawTemplates);
|
||||
ext.context.globalState.update(this.getCacheKey(this._configKey, runtime), this._rawConfig);
|
||||
ext.context.globalState.update(this.getCacheKey(this._resourcesKey, runtime), this._rawResources);
|
||||
public async cacheTemplates(): Promise<void> {
|
||||
ext.context.globalState.update(this.getCacheKey(this._templatesKey), this._rawTemplates);
|
||||
ext.context.globalState.update(this.getCacheKey(this._configKey), this._rawConfig);
|
||||
ext.context.globalState.update(this.getCacheKey(this._resourcesKey), this._rawResources);
|
||||
}
|
||||
|
||||
private async parseTemplates(templatesPath: string): Promise<IFunctionTemplate[]> {
|
||||
this._rawResources = <object>await fse.readJSON(await getResourcesPath(templatesPath));
|
||||
protected async parseTemplates(templatesPath: string): Promise<ITemplates> {
|
||||
this._rawResources = <object>await fse.readJSON(await getScriptResourcesPath(templatesPath));
|
||||
this._rawTemplates = <object[]>await fse.readJSON(path.join(templatesPath, 'templates', 'templates.json'));
|
||||
this._rawConfig = <object>await fse.readJSON(path.join(templatesPath, 'bindings', 'bindings.json'));
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isString } from 'util';
|
||||
import { ProjectLanguage } from '../constants';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { IFunctionBinding, ParsedFunctionJson } from '../funcConfig/function';
|
||||
import { IBindingSetting, IBindingTemplate, IEnumValue, ResourceType, ValueType } from './IBindingTemplate';
|
||||
import { IFunctionTemplate, TemplateCategory } from './IFunctionTemplate';
|
||||
import { ProjectLanguage } from '../../constants';
|
||||
import { IFunctionBinding, ParsedFunctionJson } from '../../funcConfig/function';
|
||||
import { IBindingSetting, IBindingTemplate, IEnumValue, ResourceType, ValueType } from '../IBindingTemplate';
|
||||
import { IFunctionTemplate, TemplateCategory } from '../IFunctionTemplate';
|
||||
import { ITemplates } from '../ITemplates';
|
||||
|
||||
/**
|
||||
* Describes a script template before it has been parsed
|
||||
|
@ -217,16 +217,17 @@ export interface IScriptFunctionTemplate extends IFunctionTemplate {
|
|||
* Parses templates contained in the templateApiZip of the functions cli feed. This contains all 'script' templates, including JavaScript, C#Script, Python, etc.
|
||||
* This basically converts the 'raw' templates in the externally defined JSON format to a common and understood format (IFunctionTemplate) used by this extension
|
||||
*/
|
||||
export function parseScriptTemplates(rawResources: object, rawTemplates: object[], rawConfig: object): IFunctionTemplate[] {
|
||||
ext.scriptBindings = parseScriptBindings(<IConfig>rawConfig, <IResources>rawResources);
|
||||
export function parseScriptTemplates(rawResources: object, rawTemplates: object[], rawConfig: object): ITemplates {
|
||||
const bindingTemplates: IBindingTemplate[] = parseScriptBindings(<IConfig>rawConfig, <IResources>rawResources);
|
||||
|
||||
const templates: IFunctionTemplate[] = [];
|
||||
const functionTemplates: IFunctionTemplate[] = [];
|
||||
for (const rawTemplate of rawTemplates) {
|
||||
try {
|
||||
templates.push(parseScriptTemplate(<IRawTemplate>rawTemplate, <IResources>rawResources, ext.scriptBindings));
|
||||
functionTemplates.push(parseScriptTemplate(<IRawTemplate>rawTemplate, <IResources>rawResources, bindingTemplates));
|
||||
} catch (error) {
|
||||
// Ignore errors so that a single poorly formed template does not affect other templates
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
|
||||
return { functionTemplates, bindingTemplates };
|
||||
}
|
||||
|
|
|
@ -37,10 +37,14 @@ export async function tryGetCliFeedJson(): Promise<cliFeedJsonResponse | undefin
|
|||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
return getCliFeedJson();
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCliFeedJson(): Promise<cliFeedJsonResponse> {
|
||||
const request: requestUtils.Request = await requestUtils.getDefaultRequest(funcCliFeedUrl);
|
||||
const response: string = await requestUtils.sendRequest(request);
|
||||
return <cliFeedJsonResponse>JSON.parse(response);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFeedRuntime(runtime: ProjectRuntime): string {
|
||||
|
@ -56,7 +60,7 @@ export function getFeedRuntime(runtime: ProjectRuntime): string {
|
|||
throw new RangeError(localize('invalidRuntime', 'Invalid runtime "{0}".', runtime));
|
||||
}
|
||||
|
||||
return ext.templateSource === TemplateSource.StagingCliFeed ? `${result}-prerelease` : result;
|
||||
return ext.templateProvider.templateSource === TemplateSource.Staging ? `${result}-prerelease` : result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,14 +19,18 @@ export namespace mavenUtils {
|
|||
await cpUtils.executeCommand(undefined, undefined, mvnCommand, '--version');
|
||||
} catch (error) {
|
||||
const message: string = localize('azFunc.mvnNotFound', 'Failed to find "maven", please ensure that the maven bin directory is in your system path.');
|
||||
|
||||
if (!context.errorHandling.suppressDisplay) {
|
||||
// don't wait
|
||||
vscode.window.showErrorMessage(message, DialogResponses.learnMore).then(async result => {
|
||||
if (result === DialogResponses.learnMore) {
|
||||
await openUrl('https://aka.ms/azurefunction_maven');
|
||||
}
|
||||
});
|
||||
context.errorHandling.suppressDisplay = true; // Swallow errors in case show two error message
|
||||
throw new Error(localize('azFunc.mvnNotFound', 'Failed to find "maven" on path.'));
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { DialogResponses, IActionContext } from 'vscode-azureextensionui';
|
||||
import { callWithTelemetryAndErrorHandling, DialogResponses, IActionContext } from 'vscode-azureextensionui';
|
||||
import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject';
|
||||
import { initProjectForVSCode } from '../commands/initProjectForVSCode/initProjectForVSCode';
|
||||
import { ProjectLanguage, projectLanguageSetting, projectRuntimeSetting } from '../constants';
|
||||
import { ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting } from '../constants';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { localize } from '../localize';
|
||||
import { getWorkspaceSetting, updateGlobalSetting } from './settings';
|
||||
import { convertStringToRuntime, getWorkspaceSetting, updateGlobalSetting } from './settings';
|
||||
import { verifyJavaDeployConfigIsValid } from './verifyJavaDeployConfigIsValid';
|
||||
import { verifyJSDebugConfigIsValid } from './verifyJSDebugConfigIsValid';
|
||||
import { verifyPythonVenv } from './verifyPythonVenv';
|
||||
|
@ -29,7 +29,17 @@ export async function verifyVSCodeConfigOnActivate(context: IActionContext, fold
|
|||
if (projectPath) {
|
||||
context.telemetry.suppressIfSuccessful = false;
|
||||
|
||||
if (isInitializedProject(projectPath)) {
|
||||
const language: ProjectLanguage | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
|
||||
const runtime: ProjectRuntime | undefined = convertStringToRuntime(getWorkspaceSetting(projectRuntimeSetting, projectPath));
|
||||
if (language !== undefined && runtime !== undefined) {
|
||||
// Don't wait
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
callWithTelemetryAndErrorHandling('initializeTemplates', async (templatesContext: IActionContext) => {
|
||||
templatesContext.telemetry.properties.isActivationEvent = 'true';
|
||||
templatesContext.errorHandling.suppressDisplay = true;
|
||||
await ext.templateProvider.getFunctionTemplates(templatesContext, language, runtime);
|
||||
});
|
||||
|
||||
const projectLanguage: string | undefined = getWorkspaceSetting(projectLanguageSetting, workspacePath);
|
||||
context.telemetry.properties.projectLanguage = projectLanguage;
|
||||
switch (projectLanguage) {
|
||||
|
@ -75,9 +85,3 @@ async function promptToInitializeProject(workspacePath: string, context: IAction
|
|||
context.telemetry.properties.verifyConfigResult = 'suppressed';
|
||||
}
|
||||
}
|
||||
|
||||
function isInitializedProject(projectPath: string): boolean {
|
||||
const language: string | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
|
||||
const runtime: string | undefined = getWorkspaceSetting(projectRuntimeSetting, projectPath);
|
||||
return !!language && !!runtime;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import * as assert from 'assert';
|
|||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { Disposable } from 'vscode';
|
||||
import { createFunction, ext, IFunctionTemplate, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, TemplateFilter, templateFilterSetting, TemplateProvider } from '../../extension.bundle';
|
||||
import { runForAllTemplateSources, testFolderPath, testUserInput } from '../global.test';
|
||||
import { createFunction, ext, IFunctionTemplate, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, TemplateFilter, templateFilterSetting } from '../../extension.bundle';
|
||||
import { runForAllTemplateSources, testActionContext, testFolderPath, testUserInput } from '../global.test';
|
||||
import { runWithFuncSetting } from '../runWithSetting';
|
||||
|
||||
export abstract class FunctionTesterBase implements Disposable {
|
||||
|
@ -32,8 +32,7 @@ export abstract class FunctionTesterBase implements Disposable {
|
|||
}
|
||||
|
||||
public async dispose(): Promise<void> {
|
||||
const templateProvider: TemplateProvider = await ext.templateProviderTask;
|
||||
const templates: IFunctionTemplate[] = await templateProvider.getTemplates(this.language, this.runtime, this.baseTestFolder, TemplateFilter.Verified);
|
||||
const templates: IFunctionTemplate[] = await ext.templateProvider.getFunctionTemplates(testActionContext, this.language, this.runtime, TemplateFilter.Verified);
|
||||
assert.deepEqual(this.testedFunctions.sort(), templates.map(t => t.name).sort(), 'Not all "Verified" templates were tested');
|
||||
}
|
||||
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
import * as assert from 'assert';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { ext, getResourcesPath } from '../extension.bundle';
|
||||
import { ext, getScriptResourcesPath } from '../extension.bundle';
|
||||
import { testFolderPath } from './global.test';
|
||||
|
||||
async function verifyLanguage(vscodeLanguage: string, fileName: string, templatesPath?: string): Promise<void> {
|
||||
templatesPath = templatesPath || ext.context.asAbsolutePath(path.join('resources', 'backupScriptTemplates', '~2'));
|
||||
const actual: string = await getResourcesPath(templatesPath, vscodeLanguage);
|
||||
const actual: string = await getScriptResourcesPath(templatesPath, vscodeLanguage);
|
||||
assert.equal(actual, path.join(templatesPath, 'resources', fileName));
|
||||
}
|
||||
|
||||
suite('getResourcesPath', async () => {
|
||||
suite('getScriptResourcesPath', async () => {
|
||||
let extraTemplatesPath: string;
|
||||
suiteSetup(async () => {
|
||||
extraTemplatesPath = path.join(testFolderPath, 'extraTemplates');
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as os from 'os';
|
|||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { TestOutputChannel, TestUserInput } from 'vscode-azureextensiondev';
|
||||
import { ext, getRandomHexString, getTemplateProvider, parseError, TemplateProvider, TemplateSource } from '../extension.bundle';
|
||||
import { CentralTemplateProvider, ext, getRandomHexString, IActionContext, parseError, ProjectLanguage, ProjectRuntime, TemplateSource } from '../extension.bundle';
|
||||
|
||||
/**
|
||||
* Folder for most tests that do not need a workspace open
|
||||
|
@ -25,7 +25,9 @@ export let testWorkspacePath: string;
|
|||
export let longRunningTestsEnabled: boolean;
|
||||
export let testUserInput: TestUserInput = new TestUserInput(vscode);
|
||||
|
||||
let templatesMap: Map<TemplateSource, TemplateProvider>;
|
||||
export const testActionContext: IActionContext = { telemetry: { properties: {}, measurements: {} }, errorHandling: {} };
|
||||
|
||||
let templateProviderMap: Map<TemplateSource, CentralTemplateProvider>;
|
||||
|
||||
// Runs before all tests
|
||||
suiteSetup(async function (this: IHookCallbackContext): Promise<void> {
|
||||
|
@ -35,24 +37,21 @@ suiteSetup(async function (this: IHookCallbackContext): Promise<void> {
|
|||
testWorkspacePath = await initTestWorkspacePath();
|
||||
|
||||
await vscode.commands.executeCommand('azureFunctions.refresh'); // activate the extension before tests begin
|
||||
await ext.templateProviderTask; // make sure default templates are loaded before setting up templates from other sources
|
||||
ext.outputChannel = new TestOutputChannel();
|
||||
ext.ui = testUserInput;
|
||||
|
||||
// Use prerelease func cli installed from gulp task (unless otherwise specified in env)
|
||||
ext.funcCliPath = process.env.FUNC_PATH || path.join(os.homedir(), 'tools', 'func', 'func');
|
||||
|
||||
templatesMap = new Map();
|
||||
try {
|
||||
await preLoadTemplates(ext.templateProvider);
|
||||
templateProviderMap = new Map();
|
||||
for (const key of Object.keys(TemplateSource)) {
|
||||
const source: TemplateSource = <TemplateSource>TemplateSource[key];
|
||||
ext.templateSource = source;
|
||||
templatesMap.set(source, await getTemplateProvider());
|
||||
}
|
||||
} finally {
|
||||
ext.templateSource = undefined;
|
||||
templateProviderMap.set(source, new CentralTemplateProvider(source));
|
||||
}
|
||||
|
||||
await runForAllTemplateSources(async (_source, provider) => await preLoadTemplates(provider));
|
||||
|
||||
// tslint:disable-next-line:strict-boolean-expressions
|
||||
longRunningTestsEnabled = !/^(false|0)?$/i.test(process.env.ENABLE_LONG_RUNNING_TESTS || '');
|
||||
|
||||
|
@ -71,31 +70,44 @@ suiteTeardown(async function (this: IHookCallbackContext): Promise<void> {
|
|||
}
|
||||
});
|
||||
|
||||
export async function runForAllTemplateSources(callback: (source: TemplateSource, templates: TemplateProvider) => Promise<void>): Promise<void> {
|
||||
for (const source of templatesMap.keys()) {
|
||||
await runForTemplateSource(source, (templates: TemplateProvider) => callback(source, templates));
|
||||
/**
|
||||
* Pre-load templates so that the first related unit test doesn't time out
|
||||
*/
|
||||
async function preLoadTemplates(provider: CentralTemplateProvider): Promise<void> {
|
||||
console.log(`Loading templates for source "${provider.templateSource}"`);
|
||||
const runtimes: ProjectRuntime[] = [ProjectRuntime.v1, ProjectRuntime.v2];
|
||||
const languages: ProjectLanguage[] = [ProjectLanguage.JavaScript, ProjectLanguage.CSharp];
|
||||
|
||||
for (const runtime of runtimes) {
|
||||
for (const language of languages) {
|
||||
await provider.getFunctionTemplates(testActionContext, language, runtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runForTemplateSource(source: TemplateSource | undefined, callback: (templates: TemplateProvider) => Promise<void>): Promise<void> {
|
||||
const oldProvider: Promise<TemplateProvider> = ext.templateProviderTask;
|
||||
export async function runForAllTemplateSources(callback: (source: TemplateSource, templateProvider: CentralTemplateProvider) => Promise<void>): Promise<void> {
|
||||
for (const source of templateProviderMap.keys()) {
|
||||
await runForTemplateSource(source, (templateProvider: CentralTemplateProvider) => callback(source, templateProvider));
|
||||
}
|
||||
}
|
||||
|
||||
export async function runForTemplateSource(source: TemplateSource | undefined, callback: (templateProvider: CentralTemplateProvider) => Promise<void>): Promise<void> {
|
||||
const oldProvider: CentralTemplateProvider = ext.templateProvider;
|
||||
try {
|
||||
let templates: TemplateProvider | undefined;
|
||||
let templateProvider: CentralTemplateProvider | undefined;
|
||||
if (source === undefined) {
|
||||
templates = await ext.templateProviderTask;
|
||||
templateProvider = ext.templateProvider;
|
||||
} else {
|
||||
templates = templatesMap.get(source);
|
||||
if (!templates) {
|
||||
templateProvider = templateProviderMap.get(source);
|
||||
if (!templateProvider) {
|
||||
throw new Error(`Unrecognized source ${source}`);
|
||||
}
|
||||
ext.templateSource = source;
|
||||
ext.templateProviderTask = Promise.resolve(templates);
|
||||
ext.templateProvider = templateProvider;
|
||||
}
|
||||
|
||||
await callback(templates);
|
||||
await callback(templateProvider);
|
||||
} finally {
|
||||
ext.templateSource = undefined;
|
||||
ext.templateProviderTask = oldProvider;
|
||||
ext.templateProvider = oldProvider;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,26 +5,26 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { IHookCallbackContext } from 'mocha';
|
||||
import { IFunctionTemplate, ProjectLanguage, ProjectRuntime, TemplateFilter, TemplateProvider, TemplateSource } from '../extension.bundle';
|
||||
import { longRunningTestsEnabled, runForTemplateSource, testFolderPath } from './global.test';
|
||||
import { CentralTemplateProvider, IFunctionTemplate, ProjectLanguage, ProjectRuntime, TemplateFilter, TemplateSource } from '../extension.bundle';
|
||||
import { longRunningTestsEnabled, runForTemplateSource, testActionContext } from './global.test';
|
||||
|
||||
addSuite(undefined);
|
||||
addSuite(TemplateSource.CliFeed);
|
||||
addSuite(TemplateSource.StagingCliFeed);
|
||||
addSuite(TemplateSource.Latest);
|
||||
addSuite(TemplateSource.Staging);
|
||||
addSuite(TemplateSource.Backup);
|
||||
|
||||
function addSuite(source: TemplateSource | undefined): void {
|
||||
suite(`Template Count - ${source === undefined ? 'defaultOnExtensionActivation' : source}`, async () => {
|
||||
test('JavaScript v1', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const jsTemplatesv1: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.JavaScript, ProjectRuntime.v1, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const jsTemplatesv1: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.JavaScript, ProjectRuntime.v1, TemplateFilter.Verified);
|
||||
assert.equal(jsTemplatesv1.length, 8);
|
||||
});
|
||||
});
|
||||
|
||||
test('JavaScript v2', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const jsTemplatesv2: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.JavaScript, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const jsTemplatesv2: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.JavaScript, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(jsTemplatesv2.length, 12);
|
||||
});
|
||||
});
|
||||
|
@ -35,43 +35,43 @@ function addSuite(source: TemplateSource | undefined): void {
|
|||
}
|
||||
this.timeout(60 * 1000);
|
||||
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const javaTemplates: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.Java, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const javaTemplates: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.Java, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(javaTemplates.length, 4);
|
||||
});
|
||||
});
|
||||
|
||||
test('C# v1', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const cSharpTemplates: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.CSharp, ProjectRuntime.v1, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const cSharpTemplates: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.CSharp, ProjectRuntime.v1, TemplateFilter.Verified);
|
||||
assert.equal(cSharpTemplates.length, 12);
|
||||
});
|
||||
});
|
||||
|
||||
test('C# v2', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const cSharpTemplatesv2: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.CSharp, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const cSharpTemplatesv2: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.CSharp, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(cSharpTemplatesv2.length, 9);
|
||||
});
|
||||
});
|
||||
|
||||
test('Python v2', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const pythonTemplates: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.Python, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const pythonTemplates: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.Python, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(pythonTemplates.length, 9);
|
||||
});
|
||||
});
|
||||
|
||||
test('TypeScript v2', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const tsTemplates: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.TypeScript, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const tsTemplates: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.TypeScript, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(tsTemplates.length, 12);
|
||||
});
|
||||
});
|
||||
|
||||
test('PowerShell v2', async () => {
|
||||
await runForTemplateSource(source, async (templates: TemplateProvider) => {
|
||||
const powershellTemplates: IFunctionTemplate[] = await templates.getTemplates(ProjectLanguage.PowerShell, ProjectRuntime.v2, testFolderPath, TemplateFilter.Verified);
|
||||
await runForTemplateSource(source, async (provider: CentralTemplateProvider) => {
|
||||
const powershellTemplates: IFunctionTemplate[] = await provider.getFunctionTemplates(testActionContext, ProjectLanguage.PowerShell, ProjectRuntime.v2, TemplateFilter.Verified);
|
||||
assert.equal(powershellTemplates.length, 9);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче