Use wizard for create function (#1096)

This commit is contained in:
Eric Jizba 2019-03-19 15:48:54 -07:00 коммит произвёл GitHub
Родитель 20ccaf3aaa
Коммит ec9b71adbd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
46 изменённых файлов: 1097 добавлений и 697 удалений

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

@ -17,7 +17,7 @@ export { activateInternal, deactivateInternal } from './src/extension';
//
// The tests should import '../extension.bundle'. At design-time they live in tests/ and so will pick up this file (extension.bundle.ts).
// At runtime the tests live in dist/tests and will therefore pick up the main webpack bundle at dist/extension.bundle.js.
export * from './src/commands/createFunction/CSharpFunctionCreator';
export * from './src/commands/createFunction/dotnetSteps/DotnetNamespaceStep';
export * from './src/commands/createNewProject/createNewProject';
export * from './src/commands/createNewProject/initProjectForVSCode';
export * from './src/commands/createNewProject/JavaScriptProjectCreator';

59
package-lock.json сгенерированный
Просмотреть файл

@ -944,7 +944,7 @@
},
"readable-stream": {
"version": "2.0.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"requires": {
"core-util-is": "~1.0.0",
@ -3285,8 +3285,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -3307,14 +3306,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -3329,20 +3326,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -3459,8 +3453,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -3472,7 +3465,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3487,7 +3479,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -3495,14 +3486,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -3521,7 +3510,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3602,8 +3590,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -3615,7 +3602,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3701,8 +3687,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -3738,7 +3723,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3758,7 +3742,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -3802,14 +3785,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -5731,7 +5712,7 @@
},
"md5.js": {
"version": "1.3.4",
"resolved": "http://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
"integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
"requires": {
"hash-base": "^3.0.0",
@ -6818,7 +6799,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
}
}
@ -7316,7 +7297,7 @@
},
"sax": {
"version": "0.5.8",
"resolved": "http://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
},
"schema-utils": {
@ -7439,7 +7420,7 @@
},
"simple-git": {
"version": "1.92.0",
"resolved": "http://registry.npmjs.org/simple-git/-/simple-git-1.92.0.tgz",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.92.0.tgz",
"integrity": "sha1-YGFGjrfRnwFBB4/HQuYkV+kQ9Uc=",
"requires": {
"debug": "^3.1.0"
@ -8883,7 +8864,7 @@
},
"validator": {
"version": "9.4.1",
"resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
"resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
"integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA=="
},
"value-or-function": {
@ -9623,9 +9604,9 @@
}
},
"vscode-azureextensionui": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/vscode-azureextensionui/-/vscode-azureextensionui-0.21.1.tgz",
"integrity": "sha512-wL44OOTPYEfXmu+OmWi/p82c1qNL9UbNWWfuXJM7MNVBGeMmW47Da4L2zBsr3m4P0ZQni67N2sdmKgIuhmoeHA==",
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/vscode-azureextensionui/-/vscode-azureextensionui-0.21.2.tgz",
"integrity": "sha512-pK6dScTLUwwlBKWQSACC2zQrVsgWqxf2E2wLag/MDn1yUVJMm1qC/bnx3jZepJOYlpOzz+mYZ6UJRrD3mxuNGg==",
"requires": {
"azure-arm-resource": "^3.0.0-preview",
"azure-arm-storage": "^3.1.0",

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

@ -865,7 +865,7 @@
"request-promise": "^4.2.2",
"semver": "^5.5.0",
"vscode-azureappservice": "^0.32.0",
"vscode-azureextensionui": "^0.21.1",
"vscode-azureextensionui": "^0.21.2",
"vscode-azurekudu": "^0.1.8",
"vscode-nls": "^4.0.0",
"websocket": "^1.0.25",

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

@ -4,13 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import { DialogResponses, IActionContext, IAzureQuickPickItem, parseError, StorageAccountKind, StorageAccountPerformance, StorageAccountReplication } from 'vscode-azureextensionui';
import { DialogResponses, IActionContext, parseError, StorageAccountKind, StorageAccountPerformance, StorageAccountReplication } from 'vscode-azureextensionui';
import { localSettingsFileName } from './constants';
import { NoSubscriptionError } from './errors';
import { ext } from './extensionVariables';
import { localize } from './localize';
import { getResourceTypeLabel, ResourceType } from './templates/IFunctionSetting';
import * as azUtil from './utils/azure';
import * as fsUtil from './utils/fs';
@ -22,80 +21,8 @@ export interface ILocalAppSettings {
export const azureWebJobsStorageKey: string = 'AzureWebJobsStorage';
export async function promptForAppSetting(actionContext: IActionContext, localSettingsPath: string, resourceType: ResourceType): Promise<string> {
const settings: ILocalAppSettings = await getLocalSettings(localSettingsPath);
const resourceTypeLabel: string = getResourceTypeLabel(resourceType);
if (settings.Values) {
const existingSettings: string[] = Object.keys(settings.Values);
if (existingSettings.length !== 0) {
let picks: IAzureQuickPickItem<boolean>[] = [{ data: true /* createNewAppSetting */, label: localize('azFunc.newAppSetting', '$(plus) New App Setting'), description: '' }];
picks = picks.concat(existingSettings.map((s: string) => { return { data: false /* createNewAppSetting */, label: s, description: '' }; }));
const options: vscode.QuickPickOptions = { placeHolder: localize('azFunc.selectAppSetting', 'Select an App Setting for your \'{0}\'', resourceTypeLabel) };
const result: IAzureQuickPickItem<boolean> = await ext.ui.showQuickPick(picks, options);
if (!result.data /* createNewAppSetting */) {
return result.label;
}
}
}
let resourceResult: azUtil.IResourceResult | undefined;
try {
switch (resourceType) {
case ResourceType.DocumentDB:
resourceResult = await azUtil.promptForCosmosDBAccount();
break;
case ResourceType.Storage:
resourceResult = await azUtil.promptForStorageAccount(
actionContext,
{
kind: [
StorageAccountKind.BlobStorage
],
learnMoreLink: 'https://aka.ms/T5o0nf'
}
);
break;
case ResourceType.ServiceBus:
resourceResult = await azUtil.promptForServiceBus();
break;
default:
}
} catch (error) {
if (error instanceof NoSubscriptionError) {
// swallow error and prompt for connection string instead
} else {
throw error;
}
}
const appSettingSuffix: string = `_${resourceType.toUpperCase()}`;
let appSettingKey: string;
let connectionString: string;
if (resourceResult) {
appSettingKey = `${resourceResult.name}${appSettingSuffix}`;
connectionString = resourceResult.connectionString;
} else {
const keyOptions: vscode.InputBoxOptions = {
placeHolder: localize('azFunc.AppSettingKeyPlaceholder', '\'{0}\' App Setting Key', resourceTypeLabel),
prompt: localize('azFunc.AppSettingKeyPrompt', 'Enter a key for your \'{0}\' connection string', resourceTypeLabel),
value: `example${appSettingSuffix}`
};
appSettingKey = await ext.ui.showInputBox(keyOptions);
const valueOptions: vscode.InputBoxOptions = {
placeHolder: localize('azFunc.AppSettingValuePlaceholder', '\'{0}\' App Setting Value', resourceTypeLabel),
prompt: localize('azFunc.AppSettingValuePrompt', 'Enter the connection string for your \'{0}\'', resourceTypeLabel)
};
connectionString = await ext.ui.showInputBox(valueOptions);
}
await setAppSetting(settings, localSettingsPath, appSettingKey, connectionString);
return appSettingKey;
}
export async function validateAzureWebJobsStorage(actionContext: IActionContext, localSettingsPath: string): Promise<void> {
const settings: ILocalAppSettings = await getLocalSettings(localSettingsPath);
const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath);
if (settings.Values && settings.Values[azureWebJobsStorageKey]) {
return;
}
@ -104,47 +31,37 @@ export async function validateAzureWebJobsStorage(actionContext: IActionContext,
const selectStorageAccount: vscode.MessageItem = { title: localize('azFunc.SelectStorageAccount', 'Select Storage Account') };
const result: vscode.MessageItem = await ext.ui.showWarningMessage(message, selectStorageAccount, DialogResponses.skipForNow);
if (result === selectStorageAccount) {
let connectionString: string;
try {
const resourceResult: azUtil.IResourceResult = await azUtil.promptForStorageAccount(
actionContext,
{
kind: [
StorageAccountKind.BlobStorage
],
performance: [
StorageAccountPerformance.Premium
],
replication: [
StorageAccountReplication.ZRS
],
learnMoreLink: 'https://aka.ms/Cfqnrc'
}
);
connectionString = resourceResult.connectionString;
} catch (error) {
if (error instanceof NoSubscriptionError) {
const options: vscode.InputBoxOptions = {
placeHolder: localize('azFunc.StoragePlaceholder', '\'{0}\' Connection String', azureWebJobsStorageKey),
prompt: localize('azFunc.StoragePrompt', 'Enter the connection string for your \'{0}\'', azureWebJobsStorageKey)
};
connectionString = await ext.ui.showInputBox(options);
} else {
throw error;
const resourceResult: azUtil.IResourceResult = await azUtil.promptForStorageAccount(
actionContext,
{
kind: [
StorageAccountKind.BlobStorage
],
performance: [
StorageAccountPerformance.Premium
],
replication: [
StorageAccountReplication.ZRS
],
learnMoreLink: 'https://aka.ms/Cfqnrc'
}
}
);
await setAppSetting(settings, localSettingsPath, azureWebJobsStorageKey, connectionString);
// tslint:disable-next-line:strict-boolean-expressions
settings.Values = settings.Values || {};
settings.Values[azureWebJobsStorageKey] = resourceResult.connectionString;
await fsUtil.writeFormattedJson(localSettingsPath, settings);
}
}
export async function setLocalAppSetting(functionAppPath: string, key: string, value: string): Promise<void> {
const localSettingsPath: string = path.join(functionAppPath, localSettingsFileName);
const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath);
async function setAppSetting(settings: ILocalAppSettings, localSettingsPath: string, key: string, value: string): Promise<void> {
if (!settings.Values) {
settings.Values = {};
}
if (settings.Values[key]) {
// tslint:disable-next-line:strict-boolean-expressions
settings.Values = settings.Values || {};
if (settings.Values[key] === value) {
return;
} else if (settings.Values[key]) {
const message: string = localize('azFunc.SettingAlreadyExists', 'Local app setting \'{0}\' already exists. Overwrite?', key);
if (await ext.ui.showWarningMessage(message, { modal: true }, DialogResponses.yes, DialogResponses.cancel) !== DialogResponses.yes) {
return;
@ -155,7 +72,7 @@ async function setAppSetting(settings: ILocalAppSettings, localSettingsPath: str
await fsUtil.writeFormattedJson(localSettingsPath, settings);
}
export async function getLocalSettings(localSettingsPath: string, allowOverwrite: boolean = false): Promise<ILocalAppSettings> {
export async function getLocalAppSettings(localSettingsPath: string, allowOverwrite: boolean = false): Promise<ILocalAppSettings> {
if (await fse.pathExists(localSettingsPath)) {
const data: string = (await fse.readFile(localSettingsPath)).toString();
if (/[^\s]/.test(data)) {

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

@ -9,7 +9,7 @@ import * as vscode from 'vscode';
import { AppSettingsTreeItem, SiteClient } from "vscode-azureappservice";
import { localSettingsFileName } from "../../constants";
import { ext } from "../../extensionVariables";
import { getLocalSettings, ILocalAppSettings } from "../../LocalAppSettings";
import { getLocalAppSettings, ILocalAppSettings } from "../../LocalAppSettings";
import { localize } from "../../localize";
import * as workspaceUtil from '../../utils/workspace';
import { confirmOverwriteSettings } from "./confirmOverwriteSettings";
@ -30,7 +30,7 @@ export async function downloadAppSettings(node?: AppSettingsTreeItem): Promise<v
await node.runWithTemporaryDescription(localize('downloading', 'Downloading...'), async () => {
ext.outputChannel.show(true);
ext.outputChannel.appendLine(localize('downloadStart', 'Downloading settings from "{0}"...', client.fullName));
let localSettings: ILocalAppSettings = await getLocalSettings(localSettingsPath, true /* allowOverwrite */);
let localSettings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath, true /* allowOverwrite */);
const isEncrypted: boolean | undefined = localSettings.IsEncrypted;
if (localSettings.IsEncrypted) {

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

@ -1,112 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { IAzureUserInput } from 'vscode-azureextensionui';
// tslint:disable-next-line:no-require-imports
import XRegExp = require('xregexp');
import { ProjectRuntime } from '../../constants';
import { localize } from "../../localize";
import { executeDotnetTemplateCommand } from '../../templates/executeDotnetTemplateCommand';
import { cpUtils } from '../../utils/cpUtils';
import { dotnetUtils } from '../../utils/dotnetUtils';
import * as fsUtil from '../../utils/fs';
import { FunctionCreatorBase } from './FunctionCreatorBase';
export class CSharpFunctionCreator extends FunctionCreatorBase {
private _functionName: string;
private _namespace: string;
public async promptForSettings(ui: IAzureUserInput, functionName: string | undefined, functionSettings: { [key: string]: string | undefined }): Promise<void> {
if (!functionName) {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueFsPath(this._functionAppPath, this._template.defaultFunctionName, '.cs');
this._functionName = await ui.showInputBox({
placeHolder: localize('azFunc.funcNamePlaceholder', 'Function name'),
prompt: localize('azFunc.funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(s),
value: defaultFunctionName || this._template.defaultFunctionName
});
} else {
this._functionName = functionName;
}
if (functionSettings.namespace !== undefined) {
this._namespace = functionSettings.namespace;
} else {
this._namespace = await ui.showInputBox({
placeHolder: localize('azFunc.namespacePlaceHolder', 'Namespace'),
prompt: localize('azFunc.namespacePrompt', 'Provide a namespace'),
validateInput: validateCSharpNamespace,
value: 'Company.Function'
});
}
}
public async createFunction(userSettings: { [propertyName: string]: string }, runtime: ProjectRuntime): Promise<string | undefined> {
await dotnetUtils.validateDotnetInstalled(this._actionContext);
const args: string[] = [];
args.push('--arg:name');
args.push(cpUtils.wrapArgInQuotes(this._functionName));
args.push('--arg:namespace');
args.push(cpUtils.wrapArgInQuotes(this._namespace));
for (const key of Object.keys(userSettings)) {
args.push(`--arg:${key}`);
args.push(cpUtils.wrapArgInQuotes(userSettings[key]));
}
await executeDotnetTemplateCommand(runtime, this._functionAppPath, 'create', '--identity', this._template.id, ...args);
return path.join(this._functionAppPath, `${this._functionName}.cs`);
}
private validateTemplateName(name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(path.join(this._functionAppPath, `${name}.cs`))) {
return localize('azFunc.existingCSFile', 'A CSharp file with the name \'{0}\' already exists.', name);
} else if (!this._functionNameRegex.test(name)) {
return localize('azFunc.functionNameInvalidError', 'Function name must start with a letter and can contain letters, digits, \'_\' and \'-\'');
} else {
return undefined;
}
}
}
// Identifier specification: https://github.com/dotnet/csharplang/blob/master/spec/lexical-structure.md#identifiers
const formattingCharacter: string = '\\p{Cf}';
const connectingCharacter: string = '\\p{Pc}';
const decimalDigitCharacter: string = '\\p{Nd}';
const combiningCharacter: string = '\\p{Mn}|\\p{Mc}';
const letterCharacter: string = '\\p{Lu}|\\p{Ll}|\\p{Lt}|\\p{Lm}|\\p{Lo}|\\p{Nl}';
const identifierPartCharacter: string = `${letterCharacter}|${decimalDigitCharacter}|${connectingCharacter}|${combiningCharacter}|${formattingCharacter}`;
const identifierStartCharacter: string = `(${letterCharacter}|_)`;
const identifierOrKeyword: string = `${identifierStartCharacter}(${identifierPartCharacter})*`;
const identifierRegex: RegExp = XRegExp(`^${identifierOrKeyword}$`);
// Keywords: https://github.com/dotnet/csharplang/blob/master/spec/lexical-structure.md#keywords
const keywords: string[] = ['abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch', 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default', 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit', 'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach', 'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal', 'is', 'lock', 'long', 'namespace', 'new', 'null', 'object', 'operator', 'out', 'override', 'params', 'private', 'protected', 'public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short', 'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void', 'volatile', 'while'];
export function validateCSharpNamespace(value: string | undefined): string | undefined {
if (!value) {
return localize('azFunc.cSharpEmptyTemplateNameError', 'The template name cannot be empty.');
}
// Namespace specification: https://github.com/dotnet/csharplang/blob/master/spec/namespaces.md#namespace-declarations
const identifiers: string[] = value.split('.');
for (const identifier of identifiers) {
if (identifier === '') {
return localize('azFunc.cSharpExtraPeriod', 'Leading or trailing "." character is not allowed.');
} else if (!identifierRegex.test(identifier)) {
return localize('azFunc.cSharpInvalidCharacters', 'The identifier "{0}" contains invalid characters.', identifier);
} else if (keywords.find((s: string) => s === identifier.toLowerCase()) !== undefined) {
return localize('azFunc.cSharpKeywordWarning', 'The identifier "{0}" is a reserved keyword.', identifier);
}
}
return undefined;
}

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

@ -1,28 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext, IAzureUserInput } from "vscode-azureextensionui";
import { ProjectRuntime } from "../../constants";
import { IFunctionTemplate } from "../../templates/IFunctionTemplate";
export abstract class FunctionCreatorBase {
protected readonly _functionNameRegex: RegExp = /^[a-zA-Z][a-zA-Z\d_\-]*$/;
protected _functionAppPath: string;
protected _template: IFunctionTemplate;
protected _actionContext: IActionContext;
constructor(functionAppPath: string, template: IFunctionTemplate, actionContext: IActionContext) {
this._functionAppPath = functionAppPath;
this._template = template;
this._actionContext = actionContext;
}
/**
* Prompt for any settings that are specific to this creator
* This includes the function name (Since the name could have different restrictions for different languages)
*/
public abstract async promptForSettings(ui: IAzureUserInput, functionName: string | undefined, functionSettings: { [key: string]: string | undefined }): Promise<void>;
public abstract async createFunction(userSettings: { [propertyName: string]: string }, runtime: ProjectRuntime): Promise<string | undefined>;
}

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

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext, ISubscriptionWizardContext } from "vscode-azureextensionui";
import { ProjectLanguage, ProjectRuntime } from "../../constants";
import { IFunctionTemplate } from "../../templates/IFunctionTemplate";
export interface IFunctionWizardContext extends Partial<ISubscriptionWizardContext> {
functionAppPath: string;
runtime: ProjectRuntime;
language: ProjectLanguage;
actionContext: IActionContext;
template: IFunctionTemplate;
functionName?: string;
newFilePath?: string;
}

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

@ -1,79 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { IAzureUserInput } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';
import { localize } from "../../localize";
import { removeLanguageFromId } from "../../templates/TemplateProvider";
import * as fsUtil from '../../utils/fs';
import { getFullClassName, parseJavaClassName, validatePackageName } from "../../utils/javaNameUtils";
import { mavenUtils } from "../../utils/mavenUtils";
import { FunctionCreatorBase } from './FunctionCreatorBase';
function getNewJavaFunctionFilePath(functionAppPath: string, packageName: string, functionName: string): string {
return path.join(functionAppPath, 'src', 'main', 'java', ...packageName.split('.'), `${parseJavaClassName(functionName)}.java`);
}
export class JavaFunctionCreator extends FunctionCreatorBase {
private _packageName: string;
private _functionName: string;
public async promptForSettings(ui: IAzureUserInput, functionName: string | undefined): Promise<void> {
this._packageName = await ui.showInputBox({
placeHolder: localize('azFunc.java.packagePlaceHolder', 'Package'),
prompt: localize('azFunc.java.packagePrompt', 'Provide a package name'),
validateInput: validatePackageName,
value: 'com.function'
});
if (!functionName) {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueJavaFsPath(this._functionAppPath, this._packageName, `${removeLanguageFromId(this._template.id)}Java`);
this._functionName = await ui.showInputBox({
placeHolder: localize('azFunc.funcNamePlaceholder', 'Function name'),
prompt: localize('azFunc.funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(s),
value: defaultFunctionName || this._template.defaultFunctionName
});
} else {
this._functionName = functionName;
}
}
public async createFunction(userSettings: { [propertyName: string]: string }): Promise<string | undefined> {
const javaFuntionProperties: string[] = [];
for (const key of Object.keys(userSettings)) {
javaFuntionProperties.push(mavenUtils.formatMavenArg(`D${key}`, userSettings[key]));
}
await mavenUtils.validateMavenInstalled(this._actionContext, this._functionAppPath);
ext.outputChannel.show();
await mavenUtils.executeMvnCommand(
this._actionContext.properties,
ext.outputChannel,
this._functionAppPath,
'azure-functions:add',
'-B',
mavenUtils.formatMavenArg('Dfunctions.package', this._packageName),
mavenUtils.formatMavenArg('Dfunctions.name', this._functionName),
mavenUtils.formatMavenArg('Dfunctions.template', removeLanguageFromId(this._template.id)),
...javaFuntionProperties
);
return getNewJavaFunctionFilePath(this._functionAppPath, this._packageName, this._functionName);
}
private validateTemplateName(name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(getNewJavaFunctionFilePath(this._functionAppPath, this._packageName, name))) {
return localize('azFunc.existingFolderError', 'The Java class \'{0}\' already exists.', getFullClassName(this._packageName, name));
} else if (!this._functionNameRegex.test(name)) {
return localize('azFunc.functionNameInvalidError', 'Function name must start with a letter and can contain letters, digits, \'_\' and \'-\'');
} else {
return undefined;
}
}
}

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

@ -1,107 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { IActionContext, IAzureUserInput } from 'vscode-azureextensionui';
import { ProjectLanguage } from '../../constants';
import { IFunctionJson } from '../../FunctionConfig';
import { localize } from "../../localize";
import { IScriptFunctionTemplate } from '../../templates/parseScriptTemplates';
import * as fsUtil from '../../utils/fs';
import { FunctionCreatorBase } from './FunctionCreatorBase';
export function getScriptFileNameFromLanguage(language: string): string | undefined {
switch (language) {
case ProjectLanguage.Bash:
return 'run.sh';
case ProjectLanguage.Batch:
return 'run.bat';
case ProjectLanguage.CSharpScript:
return 'run.csx';
case ProjectLanguage.FSharpScript:
return 'run.fsx';
case ProjectLanguage.JavaScript:
return 'index.js';
case ProjectLanguage.PHP:
return 'run.php';
case ProjectLanguage.PowerShell:
return 'run.ps1';
case ProjectLanguage.Python:
return '__init__.py';
case ProjectLanguage.TypeScript:
return 'index.ts';
default:
return undefined;
}
}
/**
* Function creator for multiple languages that don't require compilation (JavaScript, C# Script, Bash, etc.)
*/
export class ScriptFunctionCreator extends FunctionCreatorBase {
protected _template: IScriptFunctionTemplate;
protected _functionName: string;
private _language: string;
constructor(functionAppPath: string, template: IScriptFunctionTemplate, actionContext: IActionContext, language: string) {
super(functionAppPath, template, actionContext);
this._language = language;
}
public async promptForSettings(ui: IAzureUserInput, functionName: string | undefined): Promise<void> {
if (!functionName) {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueFsPath(this._functionAppPath, this._template.defaultFunctionName);
this._functionName = await ui.showInputBox({
placeHolder: localize('azFunc.funcNamePlaceholder', 'Function name'),
prompt: localize('azFunc.funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(s),
value: defaultFunctionName || this._template.defaultFunctionName
});
} else {
this._functionName = functionName;
}
}
public async createFunction(userSettings: { [propertyName: string]: string }): Promise<string | undefined> {
const functionPath: string = path.join(this._functionAppPath, this._functionName);
await fse.ensureDir(functionPath);
await Promise.all(Object.keys(this._template.templateFiles).map(async (fileName: string) => {
await fse.writeFile(path.join(functionPath, fileName), this._template.templateFiles[fileName]);
}));
for (const key of Object.keys(userSettings)) {
this._template.functionConfig.inBinding[key] = userSettings[key];
}
const functionJson: IFunctionJson = this._template.functionConfig.functionJson;
if (this.editFunctionJson) {
await this.editFunctionJson(functionJson);
}
await fsUtil.writeFormattedJson(path.join(functionPath, 'function.json'), functionJson);
const mainFileName: string | undefined = getScriptFileNameFromLanguage(this._language);
if (mainFileName) {
return path.join(functionPath, mainFileName);
} else {
return undefined;
}
}
protected editFunctionJson?(functionJson: IFunctionJson): Promise<void>;
private validateTemplateName(name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(path.join(this._functionAppPath, name))) {
return localize('azFunc.existingFolderError', 'A folder with the name \'{0}\' already exists.', name);
} else if (!this._functionNameRegex.test(name)) {
return localize('azFunc.functionNameInvalidError', 'Function name must start with a letter and can contain letters, digits, \'_\' and \'-\'');
} else {
return undefined;
}
}
}

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

@ -1,25 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { tsConfigFileName, tsDefaultOutDir } from '../../constants';
import { IFunctionJson } from '../../FunctionConfig';
import { ScriptFunctionCreator } from './ScriptFunctionCreator';
export class TypeScriptFunctionCreator extends ScriptFunctionCreator {
protected async editFunctionJson(functionJson: IFunctionJson): Promise<void> {
let outDir: string = tsDefaultOutDir;
try {
const tsconfigPath: string = path.join(this._functionAppPath, tsConfigFileName);
// tslint:disable-next-line:no-unsafe-any
outDir = (await fse.readJSON(tsconfigPath)).compilerOptions.outDir;
} catch {
// ignore and use default outDir
}
functionJson.scriptFile = path.join('..', outDir, this._functionName, 'index.js');
}
}

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

@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Progress } from 'vscode';
import { AzureWizardExecuteStep } from 'vscode-azureextensionui';
import { setLocalAppSetting } from '../../../LocalAppSettings';
import { localize } from '../../../localize';
import { IFunctionSetting } from '../../../templates/IFunctionSetting';
import { nonNullProp } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export interface IConnection {
name: string;
connectionString: string;
}
export abstract class AzureConnectionCreateStepBase<T extends IFunctionWizardContext> extends AzureWizardExecuteStep<T> {
private readonly _setting: IFunctionSetting;
constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public abstract async getConnection(wizardContext: T): Promise<IConnection>;
public async execute(wizardContext: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('retrieving', 'Retrieving connection string...') });
const result: IConnection = await this.getConnection(wizardContext);
const appSettingKey: string = `${result.name}_${nonNullProp(this._setting, 'resourceType').toUpperCase()}`;
wizardContext[this._setting.name] = appSettingKey;
await setLocalAppSetting(wizardContext.functionAppPath, appSettingKey, result.connectionString);
}
}

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

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CosmosDBManagementClient, CosmosDBManagementModels } from 'azure-arm-cosmosdb';
import { createAzureClient } from 'vscode-azureextensionui';
import { getResourceGroupFromId } from '../../../utils/azure';
import { nonNullProp, nonNullValue } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
import { AzureConnectionCreateStepBase, IConnection } from './AzureConnectionCreateStepBase';
import { ICosmosDBWizardContext } from './ICosmosDBWizardContext';
export class CosmosDBConnectionCreateStep extends AzureConnectionCreateStepBase<IFunctionWizardContext & ICosmosDBWizardContext> {
public async getConnection(wizardContext: ICosmosDBWizardContext): Promise<IConnection> {
const databaseAccount: CosmosDBManagementModels.DatabaseAccount = nonNullProp(wizardContext, 'databaseAccount');
const name: string = nonNullProp(databaseAccount, 'name');
const client: CosmosDBManagementClient = createAzureClient(wizardContext, CosmosDBManagementClient);
const resourceGroup: string = getResourceGroupFromId(nonNullProp(databaseAccount, 'id'));
const csListResult: CosmosDBManagementModels.DatabaseAccountListConnectionStringsResult = await client.databaseAccounts.listConnectionStrings(resourceGroup, name);
const cs: CosmosDBManagementModels.DatabaseAccountConnectionString = nonNullValue(nonNullProp(csListResult, 'connectionStrings')[0], 'connectionStrings[0]');
return {
name,
connectionString: nonNullProp(cs, 'connectionString')
};
}
public shouldExecute(wizardContext: ICosmosDBWizardContext): boolean {
return !!wizardContext.databaseAccount;
}
}

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

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CosmosDBManagementClient } from 'azure-arm-cosmosdb';
import { AzureWizardPromptStep, createAzureClient, ISubWizardOptions } from 'vscode-azureextensionui';
import { localize } from '../../../localize';
import { promptForResource } from '../../../utils/azure';
import { ICosmosDBWizardContext } from './ICosmosDBWizardContext';
export class CosmosDBListStep extends AzureWizardPromptStep<ICosmosDBWizardContext> {
public async prompt(wizardContext: ICosmosDBWizardContext): Promise<void | ISubWizardOptions<ICosmosDBWizardContext>> {
const placeHolder: string = localize('placeHolder', 'Select a database account');
const client: CosmosDBManagementClient = createAzureClient(wizardContext, CosmosDBManagementClient);
wizardContext.databaseAccount = await promptForResource(placeHolder, client.databaseAccounts.list());
}
public shouldPrompt(wizardContext: ICosmosDBWizardContext): boolean {
return !wizardContext.databaseAccount;
}
}

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

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CosmosDBManagementModels } from 'azure-arm-cosmosdb';
import { ISubscriptionWizardContext } from 'vscode-azureextensionui';
export interface ICosmosDBWizardContext extends ISubscriptionWizardContext {
databaseAccount?: CosmosDBManagementModels.DatabaseAccount;
}

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

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceBusManagementModels } from 'azure-arm-sb';
import { ISubscriptionWizardContext } from 'vscode-azureextensionui';
export interface IServiceBusWizardContext extends ISubscriptionWizardContext {
sbNamespace?: ServiceBusManagementModels.SBNamespace;
}

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

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceBusManagementClient, ServiceBusManagementModels } from 'azure-arm-sb';
import { createAzureClient } from 'vscode-azureextensionui';
import { localize } from '../../../localize';
import { getResourceGroupFromId } from '../../../utils/azure';
import { nonNullProp } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
import { AzureConnectionCreateStepBase, IConnection } from './AzureConnectionCreateStepBase';
import { IServiceBusWizardContext } from './IServiceBusWizardContext';
export class ServiceBusConnectionCreateStep extends AzureConnectionCreateStepBase<IServiceBusWizardContext & IFunctionWizardContext> {
public async getConnection(wizardContext: IServiceBusWizardContext): Promise<IConnection> {
const sbNamespace: ServiceBusManagementModels.SBNamespace = nonNullProp(wizardContext, 'sbNamespace');
const id: string = nonNullProp(sbNamespace, 'id');
const name: string = nonNullProp(sbNamespace, 'name');
const resourceGroup: string = getResourceGroupFromId(id);
const client: ServiceBusManagementClient = createAzureClient(wizardContext, ServiceBusManagementClient);
const authRules: ServiceBusManagementModels.SBAuthorizationRule[] = await client.namespaces.listAuthorizationRules(resourceGroup, name);
const authRule: ServiceBusManagementModels.SBAuthorizationRule | undefined = authRules.find(ar => ar.rights.some(r => r.toLowerCase() === 'listen'));
if (!authRule) {
throw new Error(localize('noAuthRule', 'Failed to get connection string for service bus namespace "{0}".', name));
}
const keys: ServiceBusManagementModels.AccessKeys = await client.namespaces.listKeys(resourceGroup, name, nonNullProp(authRule, 'name'));
return {
name: name,
connectionString: nonNullProp(keys, 'primaryConnectionString')
};
}
public shouldExecute(wizardContext: IServiceBusWizardContext): boolean {
return !!wizardContext.sbNamespace;
}
}

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

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceBusManagementClient } from 'azure-arm-sb';
import { AzureWizardPromptStep, createAzureClient, ISubWizardOptions } from 'vscode-azureextensionui';
import { localize } from '../../../localize';
import { promptForResource } from '../../../utils/azure';
import { IServiceBusWizardContext } from './IServiceBusWizardContext';
export class ServiceBusListStep extends AzureWizardPromptStep<IServiceBusWizardContext> {
public async prompt(wizardContext: IServiceBusWizardContext): Promise<void | ISubWizardOptions<IServiceBusWizardContext>> {
const placeHolder: string = localize('placeHolder', 'Select a service bus namespace');
const client: ServiceBusManagementClient = createAzureClient(wizardContext, ServiceBusManagementClient);
wizardContext.sbNamespace = await promptForResource(placeHolder, client.namespaces.list());
}
public shouldPrompt(wizardContext: IServiceBusWizardContext): boolean {
return !wizardContext.sbNamespace;
}
}

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

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { StorageManagementClient, StorageManagementModels } from 'azure-arm-storage';
import { createAzureClient, IStorageAccountWizardContext } from 'vscode-azureextensionui';
import { getResourceGroupFromId } from '../../../utils/azure';
import { nonNullProp, nonNullValue } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
import { AzureConnectionCreateStepBase, IConnection } from './AzureConnectionCreateStepBase';
export class StorageConnectionCreateStep extends AzureConnectionCreateStepBase<IStorageAccountWizardContext & IFunctionWizardContext> {
public async getConnection(wizardContext: IStorageAccountWizardContext): Promise<IConnection> {
const storageAccount: StorageManagementModels.StorageAccount = <StorageManagementModels.StorageAccount>nonNullProp(wizardContext, 'storageAccount');
const name: string = nonNullProp(storageAccount, 'name');
const client: StorageManagementClient = createAzureClient(wizardContext, StorageManagementClient);
const resourceGroup: string = getResourceGroupFromId(nonNullProp(storageAccount, 'id'));
const result: StorageManagementModels.StorageAccountListKeysResult = await client.storageAccounts.listKeys(resourceGroup, name);
const key: string = nonNullProp(nonNullValue(nonNullProp(result, 'keys')[0], 'keys[0]'), 'value');
let endpointSuffix: string = nonNullProp(wizardContext.environment, 'storageEndpointSuffix');
// https://github.com/Azure/azure-sdk-for-node/issues/4706
if (endpointSuffix.startsWith('.')) {
endpointSuffix = endpointSuffix.substr(1);
}
return {
name,
connectionString: `DefaultEndpointsProtocol=https;AccountName=${name};AccountKey=${key};EndpointSuffix=${endpointSuffix}`
};
}
public shouldExecute(wizardContext: IStorageAccountWizardContext): boolean {
return !!wizardContext.storageAccount;
}
}

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

@ -6,71 +6,37 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import { isString } from 'util';
import { InputBoxOptions, MessageItem, ProgressLocation, QuickPickItem, Uri, window, workspace } from 'vscode';
import { callWithTelemetryAndErrorHandling, DialogResponses, IActionContext, IAzureQuickPickItem, TelemetryProperties } from 'vscode-azureextensionui';
import { MessageItem, Uri, window, workspace, WorkspaceFolder } from 'vscode';
import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, callWithTelemetryAndErrorHandling, DialogResponses, IActionContext, IAzureQuickPickItem, IWizardOptions, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui';
import { localSettingsFileName, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, TemplateFilter } from '../../constants';
import { SkipForNowError } from '../../errors';
import { NoWorkspaceError } from '../../errors';
import { ext } from '../../extensionVariables';
import { addLocalFuncTelemetry } from '../../funcCoreTools/getLocalFuncCoreToolsVersion';
import { promptForAppSetting, validateAzureWebJobsStorage } from '../../LocalAppSettings';
import { validateAzureWebJobsStorage } from '../../LocalAppSettings';
import { localize } from '../../localize';
import { getProjectLanguage, getProjectRuntime, getTemplateFilter, promptForProjectLanguage, promptForProjectRuntime, selectTemplateFilter, updateWorkspaceSetting } from '../../ProjectSettings';
import { IEnumValue, IFunctionSetting, ValueType } from '../../templates/IFunctionSetting';
import { ValueType } from '../../templates/IFunctionSetting';
import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { IScriptFunctionTemplate } from '../../templates/parseScriptTemplates';
import { TemplateProvider } from '../../templates/TemplateProvider';
import { nonNullValue } from '../../utils/nonNull';
import * as workspaceUtil from '../../utils/workspace';
import { createNewProject } from '../createNewProject/createNewProject';
import { tryGetFunctionProjectRoot } from '../createNewProject/isFunctionProject';
import { CSharpFunctionCreator } from './CSharpFunctionCreator';
import { FunctionCreatorBase } from './FunctionCreatorBase';
import { JavaFunctionCreator } from './JavaFunctionCreator';
import { ScriptFunctionCreator } from './ScriptFunctionCreator';
import { TypeScriptFunctionCreator } from './TypeScriptFunctionCreator';
import { DotnetFunctionCreateStep } from './dotnetSteps/DotnetFunctionCreateStep';
import { DotnetFunctionNameStep } from './dotnetSteps/DotnetFunctionNameStep';
import { DotnetNamespaceStep } from './dotnetSteps/DotnetNamespaceStep';
import { IDotnetFunctionWizardContext } from './dotnetSteps/IDotnetFunctionWizardContext';
import { BooleanPromptStep } from './genericSteps/BooleanPromptStep';
import { EnumPromptStep } from './genericSteps/EnumPromptStep';
import { LocalAppSettingListStep } from './genericSteps/LocalAppSettingListStep';
import { StringPromptStep } from './genericSteps/StringPromptStep';
import { IFunctionWizardContext } from './IFunctionWizardContext';
import { JavaFunctionCreateStep } from './javaSteps/JavaFunctionCreateStep';
import { JavaFunctionNameStep } from './javaSteps/JavaFunctionNameStep';
import { JavaPackageNameStep } from './javaSteps/JavaPackageNameStep';
import { ScriptFunctionCreateStep } from './scriptSteps/ScriptFunctionCreateStep';
import { ScriptFunctionNameStep } from './scriptSteps/ScriptFunctionNameStep';
import { TypeScriptFunctionCreateStep } from './scriptSteps/TypeScriptFunctionCreateStep';
async function promptForSetting(actionContext: IActionContext, localSettingsPath: string, setting: IFunctionSetting): Promise<string> {
if (setting.resourceType !== undefined) {
return await promptForAppSetting(actionContext, localSettingsPath, setting.resourceType);
} else {
switch (setting.valueType) {
case ValueType.boolean:
return await promptForBooleanSetting(setting);
case ValueType.enum:
return await promptForEnumSetting(setting);
default:
// Default to 'string' type for any setting that isn't supported
return await promptForStringSetting(setting);
}
}
}
async function promptForEnumSetting(setting: IFunctionSetting): Promise<string> {
const picks: IAzureQuickPickItem<string>[] = setting.enums.map((ev: IEnumValue) => { return { data: ev.value, label: ev.displayName, description: '' }; });
return (await ext.ui.showQuickPick(picks, { placeHolder: setting.label })).data;
}
async function promptForBooleanSetting(setting: IFunctionSetting): Promise<string> {
const picks: QuickPickItem[] = [
{ label: 'true', description: '' },
{ label: 'false', description: '' }
];
return (await ext.ui.showQuickPick(picks, { placeHolder: setting.label })).label;
}
async function promptForStringSetting(setting: IFunctionSetting): Promise<string> {
const options: InputBoxOptions = {
placeHolder: setting.label,
prompt: setting.description || localize('azFunc.stringSettingPrompt', 'Provide a \'{0}\'', setting.label),
validateInput: (s: string): string | undefined => setting.validateSetting(s),
value: setting.defaultValue
};
return await ext.ui.showInputBox(options);
}
// tslint:disable-next-line:max-func-body-length cyclomatic-complexity
export async function createFunction(
actionContext: IActionContext,
folderPath?: string,
@ -87,15 +53,27 @@ export async function createFunction(
}
if (folderPath === undefined) {
const folderPlaceholder: string = localize('azFunc.selectFunctionAppFolderExisting', 'Select the folder containing your function project');
folderPath = await workspaceUtil.selectWorkspaceFolder(ext.ui, folderPlaceholder);
const folderPlaceholder: string = localize('selectFunctionAppFolderExisting', 'Select the folder containing your function project');
let folder: WorkspaceFolder | undefined;
if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) {
throw new NoWorkspaceError();
} else if (workspace.workspaceFolders.length === 1) {
folder = workspace.workspaceFolders[0];
} else {
folder = await window.showWorkspaceFolderPick({ placeHolder: folderPlaceholder });
if (!folder) {
throw new UserCancelledError();
}
}
folderPath = folder.uri.fsPath;
}
let isNewProject: boolean = false;
let templateFilter: TemplateFilter;
let functionAppPath: string | undefined = await tryGetFunctionProjectRoot(folderPath);
if (!functionAppPath) {
const message: string = localize('azFunc.notFunctionApp', 'The selected folder is not a function app project. Initialize Project?');
const message: string = localize('notFunctionApp', 'The selected folder is not a function app project. Initialize Project?');
const result: MessageItem = await ext.ui.showWarningMessage(message, { modal: true }, DialogResponses.yes, DialogResponses.skipForNow, DialogResponses.cancel);
if (result === DialogResponses.yes) {
await createNewProject(actionContext, folderPath, undefined, undefined, false);
@ -109,8 +87,6 @@ export async function createFunction(
functionAppPath = folderPath;
}
const localSettingsPath: string = path.join(functionAppPath, localSettingsFileName);
if (language === undefined) {
language = await getProjectLanguage(functionAppPath, ext.ui);
}
@ -138,54 +114,21 @@ export async function createFunction(
actionContext.properties.projectLanguage = language;
actionContext.properties.projectRuntime = runtime;
actionContext.properties.templateFilter = templateFilter;
actionContext.properties.templateId = template.id;
let functionCreator: FunctionCreatorBase;
switch (language) {
case ProjectLanguage.Java:
functionCreator = new JavaFunctionCreator(functionAppPath, template, actionContext);
break;
case ProjectLanguage.CSharp:
functionCreator = new CSharpFunctionCreator(functionAppPath, template, actionContext);
break;
case ProjectLanguage.TypeScript:
functionCreator = new TypeScriptFunctionCreator(functionAppPath, <IScriptFunctionTemplate>template, actionContext, language);
break;
default:
functionCreator = new ScriptFunctionCreator(functionAppPath, <IScriptFunctionTemplate>template, actionContext, language);
break;
const wizardContext: IFunctionWizardContext = { functionName, functionAppPath, template, actionContext, runtime, language };
const wizard: AzureWizard<IFunctionWizardContext> = new AzureWizard(wizardContext, getWizardOptions(wizardContext, functionSettings));
await wizard.prompt(actionContext);
await wizard.execute(actionContext);
const newFilePath: string | undefined = wizardContext.newFilePath;
if (newFilePath && (await fse.pathExists(newFilePath))) {
const newFileUri: Uri = Uri.file(newFilePath);
window.showTextDocument(await workspace.openTextDocument(newFileUri));
}
await functionCreator.promptForSettings(ext.ui, functionName, functionSettings);
const userSettings: { [propertyName: string]: string } = {};
for (const setting of template.userPromptedSettings) {
try {
let settingValue: string | undefined;
if (functionSettings[setting.name.toLowerCase()] !== undefined) {
settingValue = functionSettings[setting.name.toLowerCase()];
} else {
settingValue = await promptForSetting(actionContext, localSettingsPath, setting);
}
userSettings[setting.name] = settingValue ? settingValue : '';
} catch (error) {
if (!(error instanceof SkipForNowError)) { // ignore error if user wants to skip this app setting
throw error;
}
}
}
await window.withProgress({ location: ProgressLocation.Notification, title: localize('creatingFunction', 'Creating function...') }, async () => {
const newFilePath: string | undefined = await functionCreator.createFunction(userSettings, nonNullValue(runtime, 'runtime'));
if (newFilePath && (await fse.pathExists(newFilePath))) {
const newFileUri: Uri = Uri.file(newFilePath);
window.showTextDocument(await workspace.openTextDocument(newFileUri));
}
});
if (!template.isHttpTrigger) {
const localSettingsPath: string = path.join(functionAppPath, localSettingsFileName);
await validateAzureWebJobsStorage(actionContext, localSettingsPath);
}
@ -199,6 +142,54 @@ export async function createFunction(
}
}
function getWizardOptions(wizardContext: IFunctionWizardContext, defaultSettings: { [key: string]: string | undefined }): IWizardOptions<IFunctionWizardContext> {
const promptSteps: AzureWizardPromptStep<IFunctionWizardContext>[] = [];
const executeSteps: AzureWizardExecuteStep<IFunctionWizardContext>[] = [];
switch (wizardContext.language) {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep(), new JavaFunctionNameStep());
executeSteps.push(new JavaFunctionCreateStep());
break;
case ProjectLanguage.CSharp:
(<IDotnetFunctionWizardContext>wizardContext).namespace = defaultSettings.namespace;
promptSteps.push(new DotnetFunctionNameStep(), new DotnetNamespaceStep());
executeSteps.push(new DotnetFunctionCreateStep());
break;
case ProjectLanguage.TypeScript:
promptSteps.push(new ScriptFunctionNameStep());
executeSteps.push(new TypeScriptFunctionCreateStep());
break;
default:
promptSteps.push(new ScriptFunctionNameStep());
executeSteps.push(new ScriptFunctionCreateStep());
break;
}
for (const setting of wizardContext.template.userPromptedSettings) {
if (defaultSettings[setting.name.toLowerCase()] !== undefined) {
wizardContext[setting.name] = defaultSettings[setting.name.toLowerCase()];
} else if (setting.resourceType !== undefined) {
promptSteps.push(new LocalAppSettingListStep(setting));
} else {
switch (setting.valueType) {
case ValueType.boolean:
promptSteps.push(new BooleanPromptStep(setting));
break;
case ValueType.enum:
promptSteps.push(new EnumPromptStep(setting));
break;
default:
// Default to 'string' type for any valueType that isn't supported
promptSteps.push(new StringPromptStep(setting));
break;
}
}
}
const title: string = localize('createFunction', 'Create new {0}', wizardContext.template.name);
return { promptSteps, executeSteps, title, showExecuteProgress: true };
}
async function promptForTemplate(functionAppPath: string, language: ProjectLanguage, runtime: ProjectRuntime, templateFilter: TemplateFilter, telemetryProperties: TelemetryProperties): Promise<[IFunctionTemplate, ProjectLanguage, ProjectRuntime, TemplateFilter]> {
const runtimePickId: string = 'runtime';
const languagePickId: string = 'language';
@ -216,7 +207,7 @@ async function promptForTemplate(functionAppPath: string, language: ProjectLangu
]);
});
const placeHolder: string = localize('azFunc.selectFuncTemplate', 'Select a function template');
const placeHolder: string = localize('selectFuncTemplate', 'Select a function template');
const result: IFunctionTemplate | string = (await ext.ui.showQuickPick(picksTask, { placeHolder })).data;
if (isString(result)) {
switch (result) {

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

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { Progress } from 'vscode';
import { AzureWizardExecuteStep } from 'vscode-azureextensionui';
import { localize } from "../../../localize";
import { executeDotnetTemplateCommand } from '../../../templates/executeDotnetTemplateCommand';
import { cpUtils } from '../../../utils/cpUtils';
import { dotnetUtils } from '../../../utils/dotnetUtils';
import { nonNullProp } from '../../../utils/nonNull';
import { IDotnetFunctionWizardContext } from './IDotnetFunctionWizardContext';
export class DotnetFunctionCreateStep extends AzureWizardExecuteStep<IDotnetFunctionWizardContext> {
public async execute(wizardContext: IDotnetFunctionWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('creatingFunction', 'Creating {0}...', wizardContext.template.name) });
await dotnetUtils.validateDotnetInstalled(wizardContext.actionContext);
const args: string[] = [];
args.push('--arg:name');
args.push(cpUtils.wrapArgInQuotes(nonNullProp(wizardContext, 'functionName')));
args.push('--arg:namespace');
args.push(cpUtils.wrapArgInQuotes(nonNullProp(wizardContext, 'namespace')));
for (const setting of wizardContext.template.userPromptedSettings) {
args.push(`--arg:${setting.name}`);
// tslint:disable-next-line: strict-boolean-expressions no-unsafe-any
args.push(cpUtils.wrapArgInQuotes(wizardContext[setting.name] || ''));
}
await executeDotnetTemplateCommand(wizardContext.runtime, wizardContext.functionAppPath, 'create', '--identity', wizardContext.template.id, ...args);
wizardContext.newFilePath = path.join(wizardContext.functionAppPath, `${wizardContext.functionName}.cs`);
}
public shouldExecute(wizardContext: IDotnetFunctionWizardContext): boolean {
return !wizardContext.newFilePath;
}
}

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

@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { functionNameInvalidMessage, functionNameRegex } from '../../../constants';
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import * as fsUtil from '../../../utils/fs';
import { IDotnetFunctionWizardContext } from './IDotnetFunctionWizardContext';
export class DotnetFunctionNameStep extends AzureWizardPromptStep<IDotnetFunctionWizardContext> {
public async prompt(wizardContext: IDotnetFunctionWizardContext): Promise<void> {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueFsPath(wizardContext.functionAppPath, wizardContext.template.defaultFunctionName, '.cs');
wizardContext.functionName = await ext.ui.showInputBox({
placeHolder: localize('funcNamePlaceholder', 'Function name'),
prompt: localize('funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(wizardContext, s),
value: defaultFunctionName || wizardContext.template.defaultFunctionName
});
}
public shouldPrompt(wizardContext: IDotnetFunctionWizardContext): boolean {
return !wizardContext.functionName;
}
private validateTemplateName(wizardContext: IDotnetFunctionWizardContext, name: string | undefined): string | undefined {
if (!name) {
return localize('emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(path.join(wizardContext.functionAppPath, `${name}.cs`))) {
return localize('existingFile', 'A file with the name "{0}" already exists.', name);
} else if (!functionNameRegex.test(name)) {
return functionNameInvalidMessage;
} else {
return undefined;
}
}
}

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

@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
// tslint:disable-next-line:no-require-imports
import XRegExp = require('xregexp');
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import { IDotnetFunctionWizardContext } from './IDotnetFunctionWizardContext';
export class DotnetNamespaceStep extends AzureWizardPromptStep<IDotnetFunctionWizardContext> {
public async prompt(wizardContext: IDotnetFunctionWizardContext): Promise<void> {
wizardContext.namespace = await ext.ui.showInputBox({
placeHolder: localize('namespacePlaceHolder', 'Namespace'),
prompt: localize('namespacePrompt', 'Provide a namespace'),
validateInput: validateCSharpNamespace,
value: 'Company.Function'
});
}
public shouldPrompt(wizardContext: IDotnetFunctionWizardContext): boolean {
return !wizardContext.namespace;
}
}
// Identifier specification: https://github.com/dotnet/csharplang/blob/master/spec/lexical-structure.md#identifiers
const formattingCharacter: string = '\\p{Cf}';
const connectingCharacter: string = '\\p{Pc}';
const decimalDigitCharacter: string = '\\p{Nd}';
const combiningCharacter: string = '\\p{Mn}|\\p{Mc}';
const letterCharacter: string = '\\p{Lu}|\\p{Ll}|\\p{Lt}|\\p{Lm}|\\p{Lo}|\\p{Nl}';
const identifierPartCharacter: string = `${letterCharacter}|${decimalDigitCharacter}|${connectingCharacter}|${combiningCharacter}|${formattingCharacter}`;
const identifierStartCharacter: string = `(${letterCharacter}|_)`;
const identifierOrKeyword: string = `${identifierStartCharacter}(${identifierPartCharacter})*`;
const identifierRegex: RegExp = XRegExp(`^${identifierOrKeyword}$`);
// Keywords: https://github.com/dotnet/csharplang/blob/master/spec/lexical-structure.md#keywords
const keywords: string[] = ['abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch', 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default', 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit', 'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach', 'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal', 'is', 'lock', 'long', 'namespace', 'new', 'null', 'object', 'operator', 'out', 'override', 'params', 'private', 'protected', 'public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short', 'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void', 'volatile', 'while'];
export function validateCSharpNamespace(value: string | undefined): string | undefined {
if (!value) {
return localize('cSharpEmptyTemplateNameError', 'The template name cannot be empty.');
}
// Namespace specification: https://github.com/dotnet/csharplang/blob/master/spec/namespaces.md#namespace-declarations
const identifiers: string[] = value.split('.');
for (const identifier of identifiers) {
if (identifier === '') {
return localize('cSharpExtraPeriod', 'Leading or trailing "." character is not allowed.');
} else if (!identifierRegex.test(identifier)) {
return localize('cSharpInvalidCharacters', 'The identifier "{0}" contains invalid characters.', identifier);
} else if (keywords.find((s: string) => s === identifier.toLowerCase()) !== undefined) {
return localize('cSharpKeywordWarning', 'The identifier "{0}" is a reserved keyword.', identifier);
}
}
return undefined;
}

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

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export interface IDotnetFunctionWizardContext extends IFunctionWizardContext {
namespace?: string;
}

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

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QuickPickItem } from "vscode";
import { AzureWizardPromptStep } from "vscode-azureextensionui";
import { ext } from "../../../extensionVariables";
import { IFunctionSetting } from "../../../templates/IFunctionSetting";
import { IFunctionWizardContext } from "../IFunctionWizardContext";
export class BooleanPromptStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _setting: IFunctionSetting;
constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<void> {
const picks: QuickPickItem[] = [
{ label: 'true', description: '' },
{ label: 'false', description: '' }
];
wizardContext[this._setting.name] = (await ext.ui.showQuickPick(picks, { placeHolder: this._setting.label })).label;
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._setting.name];
}
}

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

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep, IAzureQuickPickItem } from "vscode-azureextensionui";
import { ext } from "../../../extensionVariables";
import { IFunctionSetting } from "../../../templates/IFunctionSetting";
import { IFunctionWizardContext } from "../IFunctionWizardContext";
export class EnumPromptStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _setting: IFunctionSetting;
constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<void> {
const picks: IAzureQuickPickItem<string>[] = this._setting.enums.map(e => { return { data: e.value, label: e.displayName }; });
wizardContext[this._setting.name] = (await ext.ui.showQuickPick(picks, { placeHolder: this._setting.label })).data;
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._setting.name];
}
}

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

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Progress } from 'vscode';
import { AzureWizardExecuteStep } from 'vscode-azureextensionui';
import { localSettingsFileName } from '../../../constants';
import { setLocalAppSetting } from '../../../LocalAppSettings';
import { localize } from '../../../localize';
import { nonNullProp } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export class LocalAppSettingCreateStep extends AzureWizardExecuteStep<IFunctionWizardContext> {
private readonly _nameKey: string;
private readonly _valueKey: string;
constructor(nameKey: string, valueKey: string) {
super();
this._nameKey = nameKey;
this._valueKey = valueKey;
}
public async execute(wizardContext: IFunctionWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('updatingLocalSettings', 'Updating {0}...', localSettingsFileName) });
// tslint:disable-next-line: no-unsafe-any no-any
await setLocalAppSetting(wizardContext.functionAppPath, nonNullProp(wizardContext, <any>this._nameKey), nonNullProp(wizardContext, <any>this._valueKey));
}
public shouldExecute(wizardContext: IFunctionWizardContext): boolean {
return !!wizardContext[this._valueKey];
}
}

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

@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { AzureWizardExecuteStep, AzureWizardPromptStep, IAzureQuickPickItem, ISubscriptionWizardContext, ISubWizardOptions, StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication } from 'vscode-azureextensionui';
import { localSettingsFileName } from '../../../constants';
import { ext } from '../../../extensionVariables';
import { getLocalAppSettings, ILocalAppSettings } from '../../../LocalAppSettings';
import { localize } from '../../../localize';
import { IFunctionSetting, ResourceType } from '../../../templates/IFunctionSetting';
import { CosmosDBConnectionCreateStep } from '../azureSteps/CosmosDBConnectionCreateStep';
import { CosmosDBListStep } from '../azureSteps/CosmosDBListStep';
import { ServiceBusConnectionCreateStep } from '../azureSteps/ServiceBusConnectionCreateStep';
import { ServiceBusListStep } from '../azureSteps/ServiceBusListStep';
import { StorageConnectionCreateStep } from '../azureSteps/StorageConnectionCreateStep';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
import { LocalAppSettingCreateStep } from './LocalAppSettingCreateStep';
import { LocalAppSettingNameStep } from './LocalAppSettingNameStep';
import { LocalAppSettingValueStep } from './LocalAppSettingValueStep';
export class LocalAppSettingListStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _setting: IFunctionSetting;
constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<ISubWizardOptions<IFunctionWizardContext> | void> {
const localSettingsPath: string = path.join(wizardContext.functionAppPath, localSettingsFileName);
const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath);
if (settings.Values) {
const existingSettings: string[] = Object.keys(settings.Values);
if (existingSettings.length > 0) {
let picks: IAzureQuickPickItem<string | undefined>[] = [{ label: localize('newAppSetting', '$(plus) Create new local app setting'), data: undefined }];
picks = picks.concat(existingSettings.map((s: string) => { return { data: s, label: s }; }));
const placeHolder: string = localize('selectAppSetting', 'Select setting from "{0}"', localSettingsFileName);
const result: string | undefined = (await ext.ui.showQuickPick(picks, { placeHolder })).data;
if (result) {
wizardContext[this._setting.name] = result;
return;
}
}
}
const azurePromptSteps: AzureWizardPromptStep<IFunctionWizardContext & ISubscriptionWizardContext>[] = [];
const azureExecuteSteps: AzureWizardExecuteStep<IFunctionWizardContext & ISubscriptionWizardContext>[] = [];
switch (this._setting.resourceType) {
case ResourceType.DocumentDB:
azurePromptSteps.push(new CosmosDBListStep());
azureExecuteSteps.push(new CosmosDBConnectionCreateStep(this._setting));
break;
case ResourceType.Storage:
azurePromptSteps.push(new StorageAccountListStep(
{ kind: StorageAccountKind.Storage, performance: StorageAccountPerformance.Standard, replication: StorageAccountReplication.LRS },
{ kind: [StorageAccountKind.BlobStorage], learnMoreLink: 'https://aka.ms/T5o0nf' }
));
azureExecuteSteps.push(new StorageConnectionCreateStep(this._setting));
break;
case ResourceType.ServiceBus:
azurePromptSteps.push(new ServiceBusListStep());
azureExecuteSteps.push(new ServiceBusConnectionCreateStep(this._setting));
break;
default:
// Unsupported resource type - prompt user to enter connection string manually
const valueKey: string = this._setting.name + '_value';
return {
promptSteps: [new LocalAppSettingNameStep(this._setting), new LocalAppSettingValueStep(valueKey)],
executeSteps: [new LocalAppSettingCreateStep(this._setting.name, valueKey)]
};
}
const subscriptionPromptStep: AzureWizardPromptStep<ISubscriptionWizardContext> | undefined = await ext.tree.getSubscriptionPromptStep(wizardContext);
if (subscriptionPromptStep) {
azurePromptSteps.unshift(subscriptionPromptStep);
}
return { promptSteps: azurePromptSteps, executeSteps: azureExecuteSteps };
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._setting.name];
}
}

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

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { ext } from '../../../extensionVariables';
import { localize } from '../../../localize';
import { IFunctionSetting } from '../../../templates/IFunctionSetting';
import { nonNullProp } from '../../../utils/nonNull';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export class LocalAppSettingNameStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _setting: IFunctionSetting;
public constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<void> {
const appSettingSuffix: string = `_${nonNullProp(this._setting, 'resourceType').toUpperCase()}`;
wizardContext[this._setting.name] = await ext.ui.showInputBox({
placeHolder: localize('appSettingKeyPlaceholder', 'Local app setting key'),
prompt: localize('appSettingKeyPrompt', 'Provide a key for a connection string'),
value: `example${appSettingSuffix}`
});
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._setting.name];
}
}

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

@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { ext } from '../../../extensionVariables';
import { localize } from '../../../localize';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export class LocalAppSettingValueStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _key: string;
public constructor(key: string) {
super();
this._key = key;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<void> {
wizardContext[this._key] = await ext.ui.showInputBox({
placeHolder: localize('appSettingValuePlaceholder', 'App setting value'),
prompt: localize('appSettingValuePrompt', 'Provide a connection string')
});
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._key];
}
}

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

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from "vscode-azureextensionui";
import { ext } from "../../../extensionVariables";
import { localize } from "../../../localize";
import { IFunctionSetting } from "../../../templates/IFunctionSetting";
import { IFunctionWizardContext } from "../IFunctionWizardContext";
export class StringPromptStep extends AzureWizardPromptStep<IFunctionWizardContext> {
private readonly _setting: IFunctionSetting;
constructor(setting: IFunctionSetting) {
super();
this._setting = setting;
}
public async prompt(wizardContext: IFunctionWizardContext): Promise<void> {
wizardContext[this._setting.name] = await ext.ui.showInputBox({
placeHolder: this._setting.label,
prompt: this._setting.description || localize('stringSettingPrompt', 'Provide a \'{0}\'', this._setting.label),
validateInput: (s: string): string | undefined => this._setting.validateSetting(s),
value: this._setting.defaultValue
});
}
public shouldPrompt(wizardContext: IFunctionWizardContext): boolean {
return !wizardContext[this._setting.name];
}
}

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

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export interface IJavaFunctionWizardContext extends IFunctionWizardContext {
packageName?: string;
}

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

@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { Progress } from 'vscode';
import { AzureWizardExecuteStep } from 'vscode-azureextensionui';
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import { removeLanguageFromId } from "../../../templates/TemplateProvider";
import { parseJavaClassName } from "../../../utils/javaNameUtils";
import { mavenUtils } from "../../../utils/mavenUtils";
import { nonNullProp } from '../../../utils/nonNull';
import { IJavaFunctionWizardContext } from './IJavaFunctionWizardContext';
export function getNewJavaFunctionFilePath(functionAppPath: string, packageName: string, functionName: string): string {
return path.join(functionAppPath, 'src', 'main', 'java', ...packageName.split('.'), `${parseJavaClassName(functionName)}.java`);
}
export class JavaFunctionCreateStep extends AzureWizardExecuteStep<IJavaFunctionWizardContext> {
public async execute(wizardContext: IJavaFunctionWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
await mavenUtils.validateMavenInstalled(wizardContext.actionContext, wizardContext.functionAppPath);
progress.report({ message: localize('creatingFunction', 'Creating {0}...', wizardContext.template.name) });
const args: string[] = [];
for (const setting of wizardContext.template.userPromptedSettings) {
// tslint:disable-next-line: strict-boolean-expressions no-unsafe-any
args.push(mavenUtils.formatMavenArg(`D${setting.name}`, wizardContext[setting.name] || ''));
}
const packageName: string = nonNullProp(wizardContext, 'packageName');
const functionName: string = nonNullProp(wizardContext, 'functionName');
await mavenUtils.executeMvnCommand(
wizardContext.actionContext.properties,
ext.outputChannel,
wizardContext.functionAppPath,
'azure-functions:add',
'-B',
mavenUtils.formatMavenArg('Dfunctions.package', packageName),
mavenUtils.formatMavenArg('Dfunctions.name', functionName),
mavenUtils.formatMavenArg('Dfunctions.template', removeLanguageFromId(wizardContext.template.id)),
...args
);
wizardContext.newFilePath = getNewJavaFunctionFilePath(wizardContext.functionAppPath, packageName, functionName);
}
public shouldExecute(wizardContext: IJavaFunctionWizardContext): boolean {
return !wizardContext.newFilePath;
}
}

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

@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { functionNameInvalidMessage, functionNameRegex } from '../../../constants';
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import { removeLanguageFromId } from "../../../templates/TemplateProvider";
import * as fsUtil from '../../../utils/fs';
import { getFullClassName } from "../../../utils/javaNameUtils";
import { nonNullProp } from '../../../utils/nonNull';
import { IJavaFunctionWizardContext } from './IJavaFunctionWizardContext';
import { getNewJavaFunctionFilePath } from './JavaFunctionCreateStep';
export class JavaFunctionNameStep extends AzureWizardPromptStep<IJavaFunctionWizardContext> {
public async prompt(wizardContext: IJavaFunctionWizardContext): Promise<void> {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueJavaFsPath(wizardContext.functionAppPath, nonNullProp(wizardContext, 'packageName'), `${removeLanguageFromId(wizardContext.template.id)}Java`);
wizardContext.functionName = await ext.ui.showInputBox({
placeHolder: localize('funcNamePlaceholder', 'Function name'),
prompt: localize('funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(wizardContext, s),
value: defaultFunctionName || wizardContext.template.defaultFunctionName
});
}
public shouldPrompt(wizardContext: IJavaFunctionWizardContext): boolean {
return !wizardContext.functionName;
}
private validateTemplateName(wizardContext: IJavaFunctionWizardContext, name: string | undefined): string | undefined {
const packageName: string = nonNullProp(wizardContext, 'packageName');
if (!name) {
return localize('emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(getNewJavaFunctionFilePath(wizardContext.functionAppPath, packageName, name))) {
return localize('existingFolderError', 'The Java class "{0}" already exists.', getFullClassName(packageName, name));
} else if (!functionNameRegex.test(name)) {
return functionNameInvalidMessage;
} else {
return undefined;
}
}
}

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

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import { validatePackageName } from "../../../utils/javaNameUtils";
import { IJavaFunctionWizardContext } from './IJavaFunctionWizardContext';
export class JavaPackageNameStep extends AzureWizardPromptStep<IJavaFunctionWizardContext> {
public async prompt(wizardContext: IJavaFunctionWizardContext): Promise<void> {
wizardContext.packageName = await ext.ui.showInputBox({
placeHolder: localize('packagePlaceHolder', 'Package'),
prompt: localize('packagePrompt', 'Provide a package name'),
validateInput: validatePackageName,
value: 'com.function'
});
}
public shouldPrompt(wizardContext: IJavaFunctionWizardContext): boolean {
return !wizardContext.packageName;
}
}

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

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IScriptFunctionTemplate } from '../../../templates/parseScriptTemplates';
import { IFunctionWizardContext } from '../IFunctionWizardContext';
export interface IScriptFunctionWizardContext extends IFunctionWizardContext {
template: IScriptFunctionTemplate;
}

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

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { Progress } from 'vscode';
import { AzureWizardExecuteStep } from 'vscode-azureextensionui';
import { ProjectLanguage } from '../../../constants';
import { IFunctionJson } from '../../../FunctionConfig';
import { localize } from "../../../localize";
import * as fsUtil from '../../../utils/fs';
import { nonNullProp } from '../../../utils/nonNull';
import { IScriptFunctionWizardContext } from './IScriptFunctionWizardContext';
export function getScriptFileNameFromLanguage(language: string): string | undefined {
switch (language) {
case ProjectLanguage.Bash:
return 'run.sh';
case ProjectLanguage.Batch:
return 'run.bat';
case ProjectLanguage.CSharpScript:
return 'run.csx';
case ProjectLanguage.FSharpScript:
return 'run.fsx';
case ProjectLanguage.JavaScript:
return 'index.js';
case ProjectLanguage.PHP:
return 'run.php';
case ProjectLanguage.PowerShell:
return 'run.ps1';
case ProjectLanguage.Python:
return '__init__.py';
case ProjectLanguage.TypeScript:
return 'index.ts';
default:
return undefined;
}
}
export class ScriptFunctionCreateStep extends AzureWizardExecuteStep<IScriptFunctionWizardContext> {
public async execute(wizardContext: IScriptFunctionWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('creatingFunction', 'Creating {0}...', wizardContext.template.name) });
const functionPath: string = path.join(wizardContext.functionAppPath, nonNullProp(wizardContext, 'functionName'));
await fse.ensureDir(functionPath);
await Promise.all(Object.keys(wizardContext.template.templateFiles).map(async fileName => {
await fse.writeFile(path.join(functionPath, fileName), wizardContext.template.templateFiles[fileName]);
}));
for (const setting of wizardContext.template.userPromptedSettings) {
// tslint:disable-next-line: strict-boolean-expressions no-unsafe-any
wizardContext.template.functionConfig.inBinding[setting.name] = wizardContext[setting.name] || '';
}
const functionJson: IFunctionJson = wizardContext.template.functionConfig.functionJson;
if (this.editFunctionJson) {
await this.editFunctionJson(wizardContext, functionJson);
}
await fsUtil.writeFormattedJson(path.join(functionPath, 'function.json'), functionJson);
const scriptFileName: string | undefined = getScriptFileNameFromLanguage(wizardContext.language);
if (scriptFileName) {
wizardContext.newFilePath = path.join(functionPath, scriptFileName);
}
}
public shouldExecute(wizardContext: IScriptFunctionWizardContext): boolean {
return !wizardContext.newFilePath;
}
protected editFunctionJson?(wizardContext: IScriptFunctionWizardContext, functionJson: IFunctionJson): Promise<void>;
}

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

@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { AzureWizardPromptStep } from 'vscode-azureextensionui';
import { functionNameInvalidMessage, functionNameRegex } from '../../../constants';
import { ext } from '../../../extensionVariables';
import { localize } from "../../../localize";
import * as fsUtil from '../../../utils/fs';
import { IScriptFunctionWizardContext } from './IScriptFunctionWizardContext';
export class ScriptFunctionNameStep extends AzureWizardPromptStep<IScriptFunctionWizardContext> {
public async prompt(wizardContext: IScriptFunctionWizardContext): Promise<void> {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueFsPath(wizardContext.functionAppPath, wizardContext.template.defaultFunctionName);
wizardContext.functionName = await ext.ui.showInputBox({
placeHolder: localize('funcNamePlaceholder', 'Function name'),
prompt: localize('funcNamePrompt', 'Provide a function name'),
validateInput: (s: string): string | undefined => this.validateTemplateName(wizardContext, s),
value: defaultFunctionName || wizardContext.template.defaultFunctionName
});
}
public shouldPrompt(wizardContext: IScriptFunctionWizardContext): boolean {
return !wizardContext.functionName;
}
private validateTemplateName(wizardContext: IScriptFunctionWizardContext, name: string | undefined): string | undefined {
if (!name) {
return localize('emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(path.join(wizardContext.functionAppPath, name))) {
return localize('existingFolderError', 'A folder with the name "{0}" already exists.', name);
} else if (!functionNameRegex.test(name)) {
return functionNameInvalidMessage;
} else {
return undefined;
}
}
}

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

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { tsConfigFileName, tsDefaultOutDir } from '../../../constants';
import { IFunctionJson } from '../../../FunctionConfig';
import { nonNullProp } from '../../../utils/nonNull';
import { IScriptFunctionWizardContext } from './IScriptFunctionWizardContext';
import { ScriptFunctionCreateStep } from './ScriptFunctionCreateStep';
export class TypeScriptFunctionCreateStep extends ScriptFunctionCreateStep {
protected async editFunctionJson(wizardContext: IScriptFunctionWizardContext, functionJson: IFunctionJson): Promise<void> {
let outDir: string = tsDefaultOutDir;
try {
const tsconfigPath: string = path.join(wizardContext.functionAppPath, tsConfigFileName);
// tslint:disable-next-line:no-unsafe-any
outDir = (await fse.readJSON(tsconfigPath)).compilerOptions.outDir;
} catch {
// ignore and use default outDir
}
functionJson.scriptFile = path.join('..', outDir, nonNullProp(wizardContext, 'functionName'), 'index.js');
}
}

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

@ -14,7 +14,7 @@ import { extensionPrefix, extInstallCommand, extInstallTaskName, func, funcWatch
import { pythonDebugConfig } from '../../debug/PythonDebugProvider';
import { ext } from '../../extensionVariables';
import { validateFuncCoreToolsInstalled } from '../../funcCoreTools/validateFuncCoreToolsInstalled';
import { azureWebJobsStorageKey, getLocalSettings, ILocalAppSettings } from '../../LocalAppSettings';
import { azureWebJobsStorageKey, getLocalAppSettings, ILocalAppSettings } from '../../LocalAppSettings';
import { localize } from "../../localize";
import { getGlobalFuncExtensionSetting } from '../../ProjectSettings';
import { cpUtils } from "../../utils/cpUtils";
@ -150,7 +150,7 @@ export class PythonProjectCreator extends ScriptProjectCreatorBase {
// Make sure local settings isn't using Storage Emulator for non-windows
// https://github.com/Microsoft/vscode-azurefunctions/issues/583
const localSettingsPath: string = path.join(this.functionAppPath, localSettingsFileName);
const localSettings: ILocalAppSettings = await getLocalSettings(localSettingsPath);
const localSettings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath);
// tslint:disable-next-line:strict-boolean-expressions
localSettings.Values = localSettings.Values || {};
localSettings.Values[azureWebJobsStorageKey] = '';

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

@ -6,7 +6,7 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import { ProjectLanguage } from '../../constants';
import { getScriptFileNameFromLanguage } from '../createFunction/ScriptFunctionCreator';
import { getScriptFileNameFromLanguage } from '../createFunction/scriptSteps/ScriptFunctionCreateStep';
import { tryGetCsprojFile, tryGetFsprojFile } from './DotnetProjectCreator';
/**

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

@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from "./localize";
export const isWindows: boolean = /^win/.test(process.platform);
export const extensionPrefix: string = 'azureFunctions';
@ -91,3 +93,6 @@ export const localhost: string = '127.0.0.1';
export const tsDefaultOutDir: string = 'dist';
export const tsConfigFileName: string = 'tsconfig.json';
export const functionNameRegex: RegExp = /^[a-zA-Z][a-zA-Z\d_\-]*$/;
export const functionNameInvalidMessage: string = localize('functionNameInvalidMessage', 'Function name must start with a letter and can only contain letters, digits, "_" and "-"');

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

@ -5,14 +5,8 @@
import { localize } from './localize';
// tslint:disable:max-classes-per-file
// tslint:disable:max-classes-per-file export-name
export class NoWorkspaceError extends Error {
public message: string = localize('azFunc.noWorkspaceError', 'You must have a workspace open to perform this operation.');
public message: string = localize('noWorkspaceError', 'You must have a workspace open to perform this operation.');
}
export class NoSubscriptionError extends Error {
public message: string = localize('azFunc.noSubscriptionError', 'You must be signed in to Azure to perform this operation.');
}
export class SkipForNowError extends Error { }

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

@ -3,8 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from '../localize';
export enum ValueType {
string = 'string',
boolean = 'boolean',
@ -20,21 +18,6 @@ export enum ResourceType {
ServiceBus = 'ServiceBus'
}
export function getResourceTypeLabel(resourceType: ResourceType): string {
switch (resourceType) {
case ResourceType.DocumentDB:
return localize('azFunc.DocumentDB', 'Cosmos DB Account');
case ResourceType.Storage:
return localize('azFunc.Storage', 'Storage Account');
case ResourceType.EventHub:
return localize('azFunc.EventHub', 'Event Hub');
case ResourceType.ServiceBus:
return localize('azFunc.ServiceBus', 'Service Bus');
default:
return resourceType;
}
}
export interface IEnumValue {
value: string;
displayName: string;

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

@ -3,17 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CosmosDBManagementClient, CosmosDBManagementModels } from 'azure-arm-cosmosdb';
import { ServiceBusManagementClient, ServiceBusManagementModels } from 'azure-arm-sb';
import { StorageManagementClient, StorageManagementModels } from 'azure-arm-storage';
import { BaseResource } from 'ms-rest-azure';
import { isArray } from 'util';
import { QuickPickOptions } from 'vscode';
import { AzureTreeItem, AzureWizard, createAzureClient, IActionContext, IAzureQuickPickItem, IAzureUserInput, IStorageAccountFilters, IStorageAccountWizardContext, StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication, SubscriptionTreeItem } from 'vscode-azureextensionui';
import { SkipForNowError } from '../errors';
import { AzureTreeItem, AzureWizard, createAzureClient, IActionContext, IAzureQuickPickItem, IStorageAccountFilters, IStorageAccountWizardContext, StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication, SubscriptionTreeItem } from 'vscode-azureextensionui';
import { ext } from '../extensionVariables';
import { localize } from '../localize';
import { getResourceTypeLabel, ResourceType } from '../templates/IFunctionSetting';
import { nonNullProp, nonNullValue } from './nonNull';
function parseResourceId(id: string): RegExpMatchArray {
@ -42,7 +37,7 @@ interface IBaseResourceWithName extends BaseResource {
name?: string;
}
async function promptForResource<T extends IBaseResourceWithName>(ui: IAzureUserInput, resourceType: string, resourcesTask: Promise<T[]>): Promise<T> {
export async function promptForResource<T extends IBaseResourceWithName>(placeHolder: string, resourcesTask: Promise<T[]>): Promise<T | undefined> {
const picksTask: Promise<IAzureQuickPickItem<T | undefined>[]> = resourcesTask.then((resources: T[]) => {
const picks: IAzureQuickPickItem<T | undefined>[] = !isArray(resources) ? [] : <IAzureQuickPickItem<T>[]>(resources
.map((r: T) => r.name ? { data: r, label: r.name } : undefined)
@ -55,14 +50,7 @@ async function promptForResource<T extends IBaseResourceWithName>(ui: IAzureUser
return picks;
});
const options: QuickPickOptions = { placeHolder: localize('azFunc.resourcePrompt', 'Select a \'{0}\'', resourceType) };
const result: T | undefined = (await ui.showQuickPick(picksTask, options)).data;
if (!result) {
throw new SkipForNowError();
} else {
return result;
}
return (await ext.ui.showQuickPick(picksTask, { placeHolder })).data;
}
export interface IResourceResult {
@ -70,23 +58,6 @@ export interface IResourceResult {
connectionString: string;
}
export async function promptForCosmosDBAccount(): Promise<IResourceResult> {
const resourceTypeLabel: string = getResourceTypeLabel(ResourceType.DocumentDB);
const node: AzureTreeItem = await ext.tree.showTreeItemPicker(SubscriptionTreeItem.contextValue);
const client: CosmosDBManagementClient = createAzureClient(node.root, CosmosDBManagementClient);
const dbAccount: CosmosDBManagementModels.DatabaseAccount = await promptForResource<CosmosDBManagementModels.DatabaseAccount>(ext.ui, resourceTypeLabel, client.databaseAccounts.list());
const name: string = nonNullProp(dbAccount, 'name');
const resourceGroup: string = getResourceGroupFromId(nonNullProp(dbAccount, 'id'));
const csListResult: CosmosDBManagementModels.DatabaseAccountListConnectionStringsResult = await client.databaseAccounts.listConnectionStrings(resourceGroup, name);
const cs: CosmosDBManagementModels.DatabaseAccountConnectionString = nonNullValue(nonNullProp(csListResult, 'connectionStrings')[0], 'connectionString[0]');
return {
name: name,
connectionString: nonNullProp(cs, 'connectionString')
};
}
export async function promptForStorageAccount(actionContext: IActionContext, filterOptions: IStorageAccountFilters): Promise<IResourceResult> {
const node: AzureTreeItem = await ext.tree.showTreeItemPicker(SubscriptionTreeItem.contextValue);
@ -118,25 +89,3 @@ export async function promptForStorageAccount(actionContext: IActionContext, fil
connectionString: `DefaultEndpointsProtocol=https;AccountName=${name};AccountKey=${key};EndpointSuffix=${endpointSuffix}`
};
}
export async function promptForServiceBus(): Promise<IResourceResult> {
const resourceTypeLabel: string = getResourceTypeLabel(ResourceType.ServiceBus);
const node: AzureTreeItem = await ext.tree.showTreeItemPicker(SubscriptionTreeItem.contextValue);
const client: ServiceBusManagementClient = createAzureClient(node.root, ServiceBusManagementClient);
const resource: ServiceBusManagementModels.SBNamespace = await promptForResource<ServiceBusManagementModels.SBNamespace>(ext.ui, resourceTypeLabel, client.namespaces.list());
const id: string = nonNullProp(resource, 'id');
const name: string = nonNullProp(resource, 'name');
const resourceGroup: string = getResourceGroupFromId(id);
const authRules: ServiceBusManagementModels.SBAuthorizationRule[] = await client.namespaces.listAuthorizationRules(resourceGroup, name);
const authRule: ServiceBusManagementModels.SBAuthorizationRule | undefined = authRules.find((ar: ServiceBusManagementModels.SBAuthorizationRule) => ar.rights.some((r: string) => r.toLowerCase() === 'listen'));
if (!authRule) {
throw new Error(localize('noAuthRule', 'Failed to get connection string for Service Bus namespace "{0}".', name));
}
const keys: ServiceBusManagementModels.AccessKeys = await client.namespaces.listKeys(resourceGroup, name, nonNullProp(authRule, 'name'));
return {
name: name,
connectionString: nonNullProp(keys, 'primaryConnectionString')
};
}

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

@ -47,16 +47,12 @@ export abstract class FunctionTesterBase {
const funcName: string = templateName.replace(/ /g, '');
inputs.unshift(funcName); // Specify the function name
inputs.unshift(templateName); // Select the function template
inputs.unshift(testFolder); // Select the test func app folder
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
inputs.unshift('$(file-directory) Browse...'); // If the test environment has an open workspace, select the 'Browse...' option
}
ext.ui = new TestUserInput(inputs);
await runWithFuncSetting(templateFilterSetting, TemplateFilter.All, async () => {
await runWithFuncSetting(projectLanguageSetting, this.language, async () => {
await runWithFuncSetting(projectRuntimeSetting, this.runtime, async () => {
await vscode.commands.executeCommand('azureFunctions.createFunction');
await vscode.commands.executeCommand('azureFunctions.createFunction', testFolder);
});
});
});