Better error handling/loading of templates (#1509)

This commit is contained in:
Eric Jizba 2019-09-25 14:53:45 -07:00
Родитель ba8a0d9af3
Коммит e6536a5e61
28 изменённых файлов: 464 добавлений и 470 удалений

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

@ -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);
});
});