Support for new Python language model (#3235)

* Sketch preview project type.

* Switch to language model rather than separate (sub) languages.

* Exclude Python v2+ from creating functions on project create.

* Store project language model in workspace settings.

* Add project-level function source.

* Use "isolated mode" detection of functions for Python v2+.

* Open function .py file on project creation.

* Detect Python language model.

* Add Python preview to VS Code init list.

* Extract python preview model into constant.

* Sketch prompt for Python function location.

* Sketch selecting a script.

* Sketch opening template.

* Append new function to file.

* Enforce storage configuration for Python V2+.

* Exclude extension bundles from Python V2+ projects.

* Check for valid Functions version.

* Complete merge from `main`.

* Resolve linter warnings.

* Updates per PM feedback.

* Updates per PR feedback.

* Shift subwizard to separate class.

* Move Python location script up front.

* Move Python script name prompt up front.

* Switch to local content provider.

* Fake Markdown for trigger template.

* Tweak text.

* Move content to query string.

* Sketch embedding PyStein templates in extension.

* Add Python Preview templates to verified list.

* Exclude OpenAPI function from PyStein projects.

* Account for lack of new files in function creation.

* Use template for project.

* Fix finding MD content in template.

* Disable name prompt for V2.

* Update required Function host version, add whitespace during append.

* Update version number.

* Replace direct use of fs-extra.

* Add notes for preview Python model numbering.

* More notes.

* Show getting started MD on creation.

* Do not expose properties commands from PyStein functions in tree.

* Exclude PyStein from re-adding bundle property on function create.

* Updates to documentation per feedback in PR.

* Update supported PyStein version number and comments.

* Allow opening MD to the side.

* Use opt-out list instead of opt-in.
This commit is contained in:
Phillip Hoff 2022-08-19 13:54:27 -07:00 коммит произвёл GitHub
Родитель 0f10d458de
Коммит 925796cee3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
46 изменённых файлов: 5919 добавлений и 187 удалений

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

@ -2,7 +2,7 @@
"name": "vscode-azurefunctions",
"displayName": "Azure Functions",
"description": "%azureFunctions.description%",
"version": "1.7.5-alpha.0",
"version": "1.8.0-alpha.0",
"publisher": "ms-azuretools",
"icon": "resources/azure-functions.png",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
@ -77,6 +77,7 @@
"onCommand:azureFunctions.viewDeploymentLogs",
"onCommand:azureFunctions.viewProperties",
"onDebugInitialConfigurations",
"onFileSystem:vscode-azurefunctions-static-content",
"onUri",
"onView:azureWorkspace",
"workspaceContains:**/host.json"
@ -833,6 +834,12 @@
""
]
},
"azureFunctions.projectLanguageModel": {
"scope": "resource",
"type": "number",
"description": "%azureFunctions.projectLanguageModel%",
"minimum": 1
},
"azureFunctions.projectTemplateKey": {
"scope": "resource",
"type": "string",

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

@ -50,6 +50,7 @@
"azureFunctions.problemMatchers.funcPythonWatch": "Azure Functions Python problems (watch mode)",
"azureFunctions.problemMatchers.funcWatch": "Azure Functions problems (watch mode)",
"azureFunctions.projectLanguage": "The default language to use when performing operations like \"Create New Function\".",
"azureFunctions.projectLanguageModel": "The default language model to use when performing operations like \"Create New Function\".",
"azureFunctions.projectLanguage.preview": "(Preview)",
"azureFunctions.projectOpenBehavior": "The behavior to use after creating a new project. The options are \"AddToWorkspace\", \"OpenInNewWindow\", or \"OpenInCurrentWindow\".",
"azureFunctions.projectRuntime": "The default version of the Azure Functions runtime to use when performing operations like \"Create New Function\".",

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -8,7 +8,7 @@ import * as path from 'path';
import { Disposable, workspace, WorkspaceFolder } from "vscode";
import { tryGetFunctionProjectRoot } from "./commands/createNewProject/verifyIsProject";
import { getFunctionAppName, getJavaDebugSubpath } from "./commands/initProjectForVSCode/InitVSCodeStep/JavaInitVSCodeStep";
import { funcVersionSetting, JavaBuildTool, javaBuildTool, ProjectLanguage, projectLanguageSetting } from "./constants";
import { funcVersionSetting, JavaBuildTool, javaBuildTool, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting } from "./constants";
import { FuncVersion, tryParseFuncVersion } from "./FuncVersion";
import { localize } from "./localize";
import { InitLocalProjectTreeItem } from "./tree/localProject/InitLocalProjectTreeItem";
@ -35,6 +35,7 @@ export class FunctionsLocalResourceProvider implements WorkspaceResourceProvider
if (projectPath) {
try {
const language: ProjectLanguage | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
const languageModel: number | undefined = getWorkspaceSetting(projectLanguageModelSetting, projectPath);
const version: FuncVersion | undefined = tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, projectPath));
if (language === undefined || version === undefined) {
children.push(new InitLocalProjectTreeItem(parent, projectPath, folder));
@ -52,7 +53,7 @@ export class FunctionsLocalResourceProvider implements WorkspaceResourceProvider
}
const treeItem: LocalProjectTreeItem = new LocalProjectTreeItem(parent, { effectiveProjectPath, folder, language, version, preCompiledProjectPath, isIsolated });
const treeItem: LocalProjectTreeItem = new LocalProjectTreeItem(parent, { effectiveProjectPath, folder, language, languageModel, version, preCompiledProjectPath, isIsolated });
this._projectDisposables.push(treeItem);
children.push(treeItem);
}

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

@ -42,7 +42,7 @@ export class BindingListStep extends AzureWizardPromptStep<IBindingWizardContext
const language: ProjectLanguage = nonNullProp(context, 'language');
const version: FuncVersion = nonNullProp(context, 'version');
const templateProvider = ext.templateProvider.get(context);
const templates: IBindingTemplate[] = await templateProvider.getBindingTemplates(context, context.projectPath, language, version);
const templates: IBindingTemplate[] = await templateProvider.getBindingTemplates(context, context.projectPath, language, undefined, version);
return templates
.filter(b => b.direction.toLowerCase() === direction.toLowerCase())
.sort((a, b) => a.displayName.localeCompare(b.displayName))

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

@ -32,7 +32,9 @@ export async function addBinding(context: IActionContext, data: Uri | LocalFunct
workspaceFolder = nonNullValue(getContainingWorkspace(functionJsonPath), 'workspaceFolder');
workspacePath = workspaceFolder.uri.fsPath;
projectPath = await tryGetFunctionProjectRoot(context, workspaceFolder, 'modalPrompt') || workspacePath;
[language, version] = await verifyInitForVSCode(context, projectPath);
const verifiedInit = await verifyInitForVSCode(context, projectPath);
language = verifiedInit.language;
version = verifiedInit.version;
} else {
if (!data) {
const noItemFoundErrorMessage: string = localize('noLocalProject', 'No matching functions found. C# and Java projects do not support this operation.');
@ -48,7 +50,7 @@ export async function addBinding(context: IActionContext, data: Uri | LocalFunct
workspaceFolder = projectTi.workspaceFolder;
workspacePath = projectTi.workspacePath;
projectPath = projectTi.effectiveProjectPath;
language = projectTi.langauge;
language = projectTi.language;
version = projectTi.version;
}

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

@ -74,7 +74,8 @@ function runPostFunctionCreateSteps(func: ICachedFunction): void {
void callWithTelemetryAndErrorHandling('postFunctionCreate', async (context: IActionContext) => {
context.telemetry.suppressIfSuccessful = true;
if (getContainingWorkspace(func.projectPath)) {
// If function creation created a new file, open it in an editor...
if (func.newFilePath && getContainingWorkspace(func.projectPath)) {
if (await AzExtFsExtra.pathExists(func.newFilePath)) {
await window.showTextDocument(await workspace.openTextDocument(Uri.file(func.newFilePath)));
}

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

@ -3,32 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, IAzureQuickPickItem, IAzureQuickPickOptions, IWizardOptions } from '@microsoft/vscode-azext-utils';
import { AzureWizardPromptStep, IActionContext, IAzureQuickPickItem, IAzureQuickPickOptions, IWizardOptions } from '@microsoft/vscode-azext-utils';
import * as escape from 'escape-string-regexp';
import { JavaBuildTool, ProjectLanguage, TemplateFilter, templateFilterSetting } from '../../constants';
import { canValidateAzureWebJobStorageOnDebug } from '../../debug/validatePreDebug';
import { ext } from '../../extensionVariables';
import { getAzureWebJobsStorage } from '../../funcConfig/local.settings';
import { FuncVersion } from '../../FuncVersion';
import { localize } from '../../localize';
import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { nonNullProp } from '../../utils/nonNull';
import { isPythonV2Plus } from '../../utils/pythonUtils';
import { getWorkspaceSetting, updateWorkspaceSetting } from '../../vsCodeConfig/settings';
import { addBindingSettingSteps } from '../addBinding/settingSteps/addBindingSettingSteps';
import { AzureWebJobsStorageExecuteStep } from '../appSettings/AzureWebJobsStorageExecuteStep';
import { AzureWebJobsStoragePromptStep } from '../appSettings/AzureWebJobsStoragePromptStep';
import { JavaPackageNameStep } from '../createNewProject/javaSteps/JavaPackageNameStep';
import { DotnetFunctionCreateStep } from './dotnetSteps/DotnetFunctionCreateStep';
import { DotnetFunctionNameStep } from './dotnetSteps/DotnetFunctionNameStep';
import { DotnetNamespaceStep } from './dotnetSteps/DotnetNamespaceStep';
import { FunctionSubWizard } from './FunctionSubWizard';
import { IFunctionWizardContext } from './IFunctionWizardContext';
import { JavaFunctionCreateStep } from './javaSteps/JavaFunctionCreateStep';
import { JavaFunctionNameStep } from './javaSteps/JavaFunctionNameStep';
import { OpenAPICreateStep } from './openAPISteps/OpenAPICreateStep';
import { OpenAPIGetSpecificationFileStep } from './openAPISteps/OpenAPIGetSpecificationFileStep';
import { ScriptFunctionCreateStep } from './scriptSteps/ScriptFunctionCreateStep';
import { ScriptFunctionNameStep } from './scriptSteps/ScriptFunctionNameStep';
import { TypeScriptFunctionCreateStep } from './scriptSteps/TypeScriptFunctionCreateStep';
import { PythonLocationStep } from './scriptSteps/PythonLocationStep';
export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardContext> {
public hideStepCount: boolean = true;
@ -47,7 +34,7 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
const language: ProjectLanguage = nonNullProp(context, 'language');
const version: FuncVersion = nonNullProp(context, 'version');
const templateProvider = ext.templateProvider.get(context);
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, version, TemplateFilter.All, context.projectTemplateKey);
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, TemplateFilter.All, context.projectTemplateKey);
const foundTemplate: IFunctionTemplate | undefined = templates.find((t: IFunctionTemplate) => {
if (options.templateId) {
const actualId: string = t.id.toLowerCase();
@ -69,75 +56,15 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
}
public async getSubWizard(context: IFunctionWizardContext): Promise<IWizardOptions<IFunctionWizardContext> | undefined> {
const template: IFunctionTemplate | undefined = context.functionTemplate;
if (template) {
const promptSteps: AzureWizardPromptStep<IFunctionWizardContext>[] = [];
switch (context.language) {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep(), new JavaFunctionNameStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
promptSteps.push(new DotnetFunctionNameStep(), new DotnetNamespaceStep());
break;
default:
promptSteps.push(new ScriptFunctionNameStep());
break;
}
const isV2PythonModel = isPythonV2Plus(context.language, context.languageModel);
// Add settings to context that were programmatically passed in
for (const key of Object.keys(this._functionSettings)) {
context[key.toLowerCase()] = this._functionSettings[key];
}
addBindingSettingSteps(template.userPromptedSettings, promptSteps);
const executeSteps: AzureWizardExecuteStep<IFunctionWizardContext>[] = [];
switch (context.language) {
case ProjectLanguage.Java:
executeSteps.push(new JavaFunctionCreateStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
executeSteps.push(await DotnetFunctionCreateStep.createStep(context));
break;
case ProjectLanguage.TypeScript:
executeSteps.push(new TypeScriptFunctionCreateStep());
break;
default:
executeSteps.push(new ScriptFunctionCreateStep());
break;
}
if ((!template.isHttpTrigger && !template.isSqlBindingTemplate) && !canValidateAzureWebJobStorageOnDebug(context.language) && !await getAzureWebJobsStorage(context, context.projectPath)) {
promptSteps.push(new AzureWebJobsStoragePromptStep());
executeSteps.push(new AzureWebJobsStorageExecuteStep());
}
const title: string = localize('createFunction', 'Create new {0}', template.name);
return { promptSteps, executeSteps, title };
} else if (context.generateFromOpenAPI) {
const promptSteps: AzureWizardPromptStep<IFunctionWizardContext>[] = [];
const executeSteps: AzureWizardExecuteStep<IFunctionWizardContext>[] = [];
switch (context.language) {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep());
break;
case ProjectLanguage.CSharp:
promptSteps.push(new DotnetNamespaceStep());
break;
default:
break;
}
promptSteps.push(new OpenAPIGetSpecificationFileStep());
executeSteps.push(await OpenAPICreateStep.createStep(context));
const title: string = localize('createFunction', 'Create new {0}', 'HTTP Triggers from OpenAPI (v2/v3) Specification File');
return { promptSteps, executeSteps, title };
if (isV2PythonModel) {
return {
// TODO: Title?
promptSteps: [ new PythonLocationStep(this._functionSettings) ]
};
} else {
return undefined;
return await FunctionSubWizard.createSubWizard(context, this._functionSettings);
}
}
@ -169,7 +96,7 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
context.generateFromOpenAPI = true;
break;
} else if (result === 'reloadTemplates') {
await templateProvider.clearTemplateCache(context, context.projectPath, nonNullProp(context, 'language'), nonNullProp(context, 'version'));
await templateProvider.clearTemplateCache(context, context.projectPath, nonNullProp(context, 'language'), context.languageModel, nonNullProp(context, 'version'));
context.telemetry.properties.reloaded = 'true';
} else {
context.functionTemplate = result;
@ -185,9 +112,10 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
private async getPicks(context: IFunctionWizardContext, templateFilter: TemplateFilter): Promise<IAzureQuickPickItem<IFunctionTemplate | TemplatePromptResult>[]> {
const language: ProjectLanguage = nonNullProp(context, 'language');
const languageModel = context.languageModel;
const version: FuncVersion = nonNullProp(context, 'version');
const templateProvider = ext.templateProvider.get(context);
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, version, templateFilter, context.projectTemplateKey);
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, templateFilter, context.projectTemplateKey);
context.telemetry.measurements.templateCount = templates.length;
const picks: IAzureQuickPickItem<IFunctionTemplate | TemplatePromptResult>[] = templates
.sort((a, b) => sortTemplates(a, b, templateFilter))
@ -208,7 +136,7 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
data: <IFunctionTemplate | TemplatePromptResult><unknown>undefined,
onPicked: () => { /* do nothing */ }
})
} else if (language === ProjectLanguage.CSharp || language === ProjectLanguage.Java || language === ProjectLanguage.Python || language === ProjectLanguage.TypeScript) {
} else if (language === ProjectLanguage.CSharp || language === ProjectLanguage.Java || (language === ProjectLanguage.Python && !isPythonV2Plus(language, languageModel)) || language === ProjectLanguage.TypeScript) {
// NOTE: Only show this if we actually found other templates
picks.push({
label: localize('openAPI', 'HTTP trigger(s) from OpenAPI V2/V3 Specification (Preview)'),

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

@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardExecuteStep, AzureWizardPromptStep, IWizardOptions } from '@microsoft/vscode-azext-utils';
import { ProjectLanguage } from '../../constants';
import { canValidateAzureWebJobStorageOnDebug } from '../../debug/validatePreDebug';
import { getAzureWebJobsStorage } from '../../funcConfig/local.settings';
import { localize } from '../../localize';
import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { isPythonV2Plus } from '../../utils/pythonUtils';
import { addBindingSettingSteps } from '../addBinding/settingSteps/addBindingSettingSteps';
import { AzureWebJobsStorageExecuteStep } from '../appSettings/AzureWebJobsStorageExecuteStep';
import { AzureWebJobsStoragePromptStep } from '../appSettings/AzureWebJobsStoragePromptStep';
import { JavaPackageNameStep } from '../createNewProject/javaSteps/JavaPackageNameStep';
import { DotnetFunctionCreateStep } from './dotnetSteps/DotnetFunctionCreateStep';
import { DotnetFunctionNameStep } from './dotnetSteps/DotnetFunctionNameStep';
import { DotnetNamespaceStep } from './dotnetSteps/DotnetNamespaceStep';
import { IFunctionWizardContext } from './IFunctionWizardContext';
import { JavaFunctionCreateStep } from './javaSteps/JavaFunctionCreateStep';
import { JavaFunctionNameStep } from './javaSteps/JavaFunctionNameStep';
import { OpenAPICreateStep } from './openAPISteps/OpenAPICreateStep';
import { OpenAPIGetSpecificationFileStep } from './openAPISteps/OpenAPIGetSpecificationFileStep';
import { PythonFunctionCreateStep } from './scriptSteps/PythonFunctionCreateStep';
import { PythonScriptStep } from './scriptSteps/PythonScriptStep';
import { ScriptFunctionCreateStep } from './scriptSteps/ScriptFunctionCreateStep';
import { ScriptFunctionNameStep } from './scriptSteps/ScriptFunctionNameStep';
import { TypeScriptFunctionCreateStep } from './scriptSteps/TypeScriptFunctionCreateStep';
export class FunctionSubWizard {
public static async createSubWizard(context: IFunctionWizardContext, functionSettings: { [key: string]: string | undefined } | undefined): Promise<IWizardOptions<IFunctionWizardContext> | undefined> {
functionSettings = functionSettings ?? {};
const template: IFunctionTemplate | undefined = context.functionTemplate;
if (template) {
const promptSteps: AzureWizardPromptStep<IFunctionWizardContext>[] = [];
const isV2PythonModel = isPythonV2Plus(context.language, context.languageModel);
if (isV2PythonModel) {
promptSteps.push(new PythonScriptStep());
}
switch (context.language) {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep(), new JavaFunctionNameStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
promptSteps.push(new DotnetFunctionNameStep(), new DotnetNamespaceStep());
break;
default:
// NOTE: The V2 Python model has attributed bindings and we don't (yet) update them from the template.
if (!isV2PythonModel) {
promptSteps.push(new ScriptFunctionNameStep());
}
break;
}
// Add settings to context that were programmatically passed in
for (const key of Object.keys(functionSettings)) {
context[key.toLowerCase()] = functionSettings[key];
}
addBindingSettingSteps(template.userPromptedSettings, promptSteps);
const executeSteps: AzureWizardExecuteStep<IFunctionWizardContext>[] = [];
switch (context.language) {
case ProjectLanguage.Java:
executeSteps.push(new JavaFunctionCreateStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
executeSteps.push(await DotnetFunctionCreateStep.createStep(context));
break;
case ProjectLanguage.TypeScript:
executeSteps.push(new TypeScriptFunctionCreateStep());
break;
default:
if (isV2PythonModel) {
executeSteps.push(new PythonFunctionCreateStep());
} else {
executeSteps.push(new ScriptFunctionCreateStep());
}
break;
}
if ((!template.isHttpTrigger && !template.isSqlBindingTemplate) && !canValidateAzureWebJobStorageOnDebug(context.language) && !await getAzureWebJobsStorage(context, context.projectPath)) {
promptSteps.push(new AzureWebJobsStoragePromptStep());
executeSteps.push(new AzureWebJobsStorageExecuteStep());
}
const title: string = localize('createFunction', 'Create new {0}', template.name);
return { promptSteps, executeSteps, title };
} else if (context.generateFromOpenAPI) {
const promptSteps: AzureWizardPromptStep<IFunctionWizardContext>[] = [];
const executeSteps: AzureWizardExecuteStep<IFunctionWizardContext>[] = [];
switch (context.language) {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep());
break;
case ProjectLanguage.CSharp:
promptSteps.push(new DotnetNamespaceStep());
break;
default:
break;
}
promptSteps.push(new OpenAPIGetSpecificationFileStep());
executeSteps.push(await OpenAPICreateStep.createStep(context));
const title: string = localize('createFunction', 'Create new {0}', 'HTTP Triggers from OpenAPI (v2/v3) Specification File');
return { promptSteps, executeSteps, title };
} else {
return undefined;
}
}
}

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

@ -63,9 +63,10 @@ export async function createFunctionInternal(context: IActionContext, options: a
return;
}
const [language, version] = await verifyInitForVSCode(context, projectPath, options.language, options.version);
const { language, languageModel, version } = await verifyInitForVSCode(context, projectPath, options.language, /* TODO: languageModel: */ undefined, options.version);
const projectTemplateKey: string | undefined = getWorkspaceSetting(projectTemplateKeySetting, projectPath);
const wizardContext: IFunctionWizardContext = Object.assign(context, options, { projectPath, workspacePath, workspaceFolder, version, language, projectTemplateKey });
const wizardContext: IFunctionWizardContext = Object.assign(context, options, { projectPath, workspacePath, workspaceFolder, version, language, languageModel, projectTemplateKey });
const wizard: AzureWizard<IFunctionWizardContext> = new AzureWizard(wizardContext, {
promptSteps: [await FunctionListStep.create(wizardContext, { templateId: options.templateId, functionSettings: options.functionSettings, isProjectWizard: false })]
});

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

@ -49,7 +49,7 @@ export class DotnetFunctionCreateStep extends FunctionCreateStepBase<IDotnetFunc
let projectTemplateKey = context.projectTemplateKey;
if (!projectTemplateKey) {
const templateProvider = ext.templateProvider.get(context);
projectTemplateKey = await templateProvider.getProjectTemplateKey(context, context.projectPath, nonNullProp(context, 'language'), context.version, undefined);
projectTemplateKey = await templateProvider.getProjectTemplateKey(context, context.projectPath, nonNullProp(context, 'language'), undefined, context.version, undefined);
}
await executeDotnetTemplateCommand(context, version, projectTemplateKey, context.projectPath, 'create', '--identity', template.id, ...args);

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

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IScriptFunctionWizardContext } from './IScriptFunctionWizardContext';
export enum FunctionLocation {
MainScript,
SelectedScript,
Document
}
export interface IPythonFunctionWizardContext extends IScriptFunctionWizardContext {
functionLocation?: FunctionLocation;
functionScript?: string;
}

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

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* 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 { IScriptFunctionTemplate } from '../../../templates/script/parseScriptTemplates';
import { nonNullProp } from '../../../utils/nonNull';
import { FunctionCreateStepBase } from '../FunctionCreateStepBase';
import { FunctionLocation, IPythonFunctionWizardContext } from './IPythonFunctionWizardContext';
import { showMarkdownPreviewContent } from '../../../utils/textUtils';
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
function createMarkdown(name: string, content: string): string {
return `# ${name}
\`\`\` python
${content}
\`\`\``;
}
export class PythonFunctionCreateStep extends FunctionCreateStepBase<IPythonFunctionWizardContext> {
public async executeCore(context: IPythonFunctionWizardContext): Promise<string> {
const template: IScriptFunctionTemplate = nonNullProp(context, 'functionTemplate');
const content = template.templateFiles['function_app.py'];
if (context.functionLocation === FunctionLocation.Document) {
const name = nonNullProp(template, 'name');
const filename = `${name}.md`;
const markdownFilename = Object.keys(template.templateFiles).find(filename => filename.toLowerCase().endsWith('.md'));
const markdownContent =
markdownFilename
? template.templateFiles[markdownFilename]
: createMarkdown(name, content);
await showMarkdownPreviewContent(markdownContent, filename, /* openToSide: */ true);
// NOTE: No "real" file being generated...
return '';
} else {
const functionScript = nonNullProp(context, 'functionScript');
const functionScriptPath: string = path.isAbsolute(functionScript) ? functionScript : path.join(context.projectPath, functionScript);
// NOTE: AzExtFsExtra doesn't have fs-extra's handy appendFile() function.
// NOTE: We add two (end-of-)lines to ensure an empty line between functions definitions.
const existingContent = await AzExtFsExtra.readFile(functionScriptPath);
await AzExtFsExtra.writeFile(functionScriptPath, existingContent + '\r\n\r\n' + content);
return functionScriptPath;
}
}
}

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

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* 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, IAzureQuickPickOptions, IWizardOptions } from '@microsoft/vscode-azext-utils';
import { FunctionLocation, IPythonFunctionWizardContext } from './IPythonFunctionWizardContext';
import { localize } from '../../../localize';
import { pythonFunctionAppFileName } from '../../../constants';
import { FunctionSubWizard } from '../FunctionSubWizard';
import { PythonFunctionCreateStep } from './PythonFunctionCreateStep';
export class PythonLocationStep extends AzureWizardPromptStep<IPythonFunctionWizardContext> {
private readonly _functionSettings: { [key: string]: string | undefined };
public constructor(functionSettings: { [key: string]: string | undefined } | undefined) {
super();
this._functionSettings = functionSettings || {};
}
public async prompt(wizardContext: IPythonFunctionWizardContext): Promise<void> {
const picks: IAzureQuickPickItem<FunctionLocation>[] = [
{ label: localize('appendToMainScript', 'Append to {0} (Recommended)', pythonFunctionAppFileName), data: FunctionLocation.MainScript },
{ label: localize('appendToSelectedScript', 'Append to selected file...'), data: FunctionLocation.SelectedScript },
{ label: localize('viewTemplate', 'Preview template'), data: FunctionLocation.Document }
];
const options: IAzureQuickPickOptions = { placeHolder: localize('selectLocation', 'Select a location for the function') };
const option = await wizardContext.ui.showQuickPick(picks, options);
wizardContext.functionLocation = option.data;
if (wizardContext.functionLocation === FunctionLocation.MainScript
&& wizardContext.functionScript === undefined) {
wizardContext.functionScript = pythonFunctionAppFileName;
}
}
public async getSubWizard(wizardContext: IPythonFunctionWizardContext): Promise<IWizardOptions<IPythonFunctionWizardContext> | undefined> {
if (wizardContext.functionLocation === FunctionLocation.Document) {
return {
executeSteps: [ new PythonFunctionCreateStep() ]
};
} else {
return await FunctionSubWizard.createSubWizard(wizardContext, this._functionSettings);
}
}
public shouldPrompt(wizardContext: IPythonFunctionWizardContext): boolean {
return wizardContext.functionLocation === undefined;
}
}

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

@ -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 * as path from 'path';
import * as vscode from 'vscode';
import { AzureWizardPromptStep, IAzureQuickPickItem, IAzureQuickPickOptions } from '@microsoft/vscode-azext-utils';
import { FunctionLocation, IPythonFunctionWizardContext } from './IPythonFunctionWizardContext';
import { localize } from '../../../localize';
export class PythonScriptStep extends AzureWizardPromptStep<IPythonFunctionWizardContext> {
public async prompt(wizardContext: IPythonFunctionWizardContext): Promise<void> {
const entries = await vscode.workspace.fs.readDirectory(vscode.Uri.file(wizardContext.projectPath));
const scripts =
entries
.filter(entry => entry[1] === vscode.FileType.File && path.extname(entry[0]) === '.py')
.map(entry => entry[0]);
const filePicks: IAzureQuickPickItem<string>[] = scripts.map(file => ({ label: path.basename(file), data: file }));
const fileOptions: IAzureQuickPickOptions = { placeHolder: localize('selectScript', 'Select a script in which to append the function') };
const fileOption = await wizardContext.ui.showQuickPick(filePicks, fileOptions);
wizardContext.functionScript = fileOption.data;
}
public shouldPrompt(wizardContext: IPythonFunctionWizardContext): boolean {
return wizardContext.functionLocation === FunctionLocation.SelectedScript
&& wizardContext.functionScript === undefined;
}
}

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

@ -15,6 +15,7 @@ export interface IProjectWizardContext extends IActionContext {
workspaceFolder: WorkspaceFolder | undefined;
language?: ProjectLanguage;
languageModel?: number;
languageFilter?: RegExp;
version: FuncVersion;
projectTemplateKey: string | undefined;

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

@ -5,10 +5,11 @@
import { AzureWizardExecuteStep, AzureWizardPromptStep, IAzureQuickPickItem, IWizardOptions, UserCancelledError } from '@microsoft/vscode-azext-utils';
import { QuickPickOptions } from 'vscode';
import { ProjectLanguage } from '../../constants';
import { previewPythonModel, ProjectLanguage, pythonNewModelPreview } from '../../constants';
import { localize } from '../../localize';
import { nonNullProp } from '../../utils/nonNull';
import { openUrl } from '../../utils/openUrl';
import { isPythonV2Plus } from '../../utils/pythonUtils';
import { FunctionListStep } from '../createFunction/FunctionListStep';
import { addInitVSCodeSteps } from '../initProjectForVSCode/InitVSCodeLanguageStep';
import { DotnetRuntimeStep } from './dotnetSteps/DotnetRuntimeStep';
@ -18,6 +19,7 @@ import { CustomProjectCreateStep } from './ProjectCreateStep/CustomProjectCreate
import { DotnetProjectCreateStep } from './ProjectCreateStep/DotnetProjectCreateStep';
import { JavaScriptProjectCreateStep } from './ProjectCreateStep/JavaScriptProjectCreateStep';
import { PowerShellProjectCreateStep } from './ProjectCreateStep/PowerShellProjectCreateStep';
import { PysteinProjectCreateStep } from './ProjectCreateStep/PysteinProjectCreateStep';
import { PythonProjectCreateStep } from './ProjectCreateStep/PythonProjectCreateStep';
import { ScriptProjectCreateStep } from './ProjectCreateStep/ScriptProjectCreateStep';
import { TypeScriptProjectCreateStep } from './ProjectCreateStep/TypeScriptProjectCreateStep';
@ -36,31 +38,33 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
public async prompt(context: IProjectWizardContext): Promise<void> {
// Only display 'supported' languages that can be debugged in VS Code
let languagePicks: IAzureQuickPickItem<ProjectLanguage | undefined>[] = [
{ label: ProjectLanguage.JavaScript, data: ProjectLanguage.JavaScript },
{ label: ProjectLanguage.TypeScript, data: ProjectLanguage.TypeScript },
{ label: ProjectLanguage.CSharp, data: ProjectLanguage.CSharp },
{ label: ProjectLanguage.Python, data: ProjectLanguage.Python },
{ label: ProjectLanguage.Java, data: ProjectLanguage.Java },
{ label: ProjectLanguage.PowerShell, data: ProjectLanguage.PowerShell },
{ label: localize('customHandler', 'Custom Handler'), data: ProjectLanguage.Custom }
let languagePicks: IAzureQuickPickItem<{ language: ProjectLanguage, model?: number } | undefined>[] = [
{ label: ProjectLanguage.JavaScript, data: { language: ProjectLanguage.JavaScript } },
{ label: ProjectLanguage.TypeScript, data: { language: ProjectLanguage.TypeScript } },
{ label: ProjectLanguage.CSharp, data: { language: ProjectLanguage.CSharp } },
{ label: ProjectLanguage.Python, data: { language: ProjectLanguage.Python } },
{ label: pythonNewModelPreview, data: { language: ProjectLanguage.Python, model: previewPythonModel } },
{ label: ProjectLanguage.Java, data: { language: ProjectLanguage.Java } },
{ label: ProjectLanguage.PowerShell, data: { language: ProjectLanguage.PowerShell } },
{ label: localize('customHandler', 'Custom Handler'), data: { language: ProjectLanguage.Custom } }
];
languagePicks.push({ label: localize('viewSamples', '$(link-external) View sample projects'), data: undefined, suppressPersistence: true });
if (context.languageFilter) {
languagePicks = languagePicks.filter(p => {
return p.data !== undefined && context.languageFilter?.test(p.data);
return p.data !== undefined && context.languageFilter?.test(p.data.language);
});
}
const options: QuickPickOptions = { placeHolder: localize('selectLanguage', 'Select a language') };
const result: ProjectLanguage | undefined = (await context.ui.showQuickPick(languagePicks, options)).data;
const result = (await context.ui.showQuickPick(languagePicks, options)).data;
if (result === undefined) {
await openUrl('https://aka.ms/AA4ul9b');
throw new UserCancelledError('viewSampleProjects');
} else {
context.language = result;
context.language = result.language;
context.languageModel = result.model;
}
}
@ -86,7 +90,10 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
executeSteps.push(await DotnetProjectCreateStep.createStep(context));
break;
case ProjectLanguage.Python:
executeSteps.push(new PythonProjectCreateStep());
executeSteps.push(
isPythonV2Plus(context.language, context.languageModel)
? new PysteinProjectCreateStep()
: new PythonProjectCreateStep());
break;
case ProjectLanguage.PowerShell:
executeSteps.push(new PowerShellProjectCreateStep());
@ -106,13 +113,15 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
const wizardOptions: IWizardOptions<IProjectWizardContext> = { promptSteps, executeSteps };
// All languages except Java support creating a function after creating a project
// Languages except Python v2+ and Java support creating a function after creating a project
// Java needs to fix this issue first: https://github.com/Microsoft/vscode-azurefunctions/issues/81
promptSteps.push(await FunctionListStep.create(context, {
isProjectWizard: true,
templateId: this._templateId,
functionSettings: this._functionSettings
}));
if (!isPythonV2Plus(context.language, context.languageModel)) {
promptSteps.push(await FunctionListStep.create(context, {
isProjectWizard: true,
templateId: this._templateId,
functionSettings: this._functionSettings
}));
}
return wizardOptions;
}

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

@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import * as vscode from 'vscode';
import { Progress, Uri, window, workspace } from 'vscode';
import { hostFileName, localSettingsFileName, ProjectLanguage, pythonFunctionAppFileName } from '../../../constants';
import { IHostJsonV2 } from '../../../funcConfig/host';
import { IScriptFunctionTemplate } from '../../../templates/script/parseScriptTemplates';
import { PysteinTemplateProvider } from '../../../templates/script/PysteinTemplateProvider';
import { confirmOverwriteFile } from '../../../utils/fs';
import { IProjectWizardContext } from '../IProjectWizardContext';
import { ScriptProjectCreateStep } from './ScriptProjectCreateStep';
import { localize } from "../../../localize";
import { ILocalSettingsJson } from '../../../funcConfig/local.settings';
import { showMarkdownPreviewFile } from '../../../utils/textUtils';
const gettingStartedFileName = 'getting_started.md';
async function fileExists(uri: vscode.Uri): Promise<boolean> {
try {
const result = await vscode.workspace.fs.stat(uri);
return result.type === vscode.FileType.File;
} catch {
return false;
}
}
export class PysteinProjectCreateStep extends ScriptProjectCreateStep {
protected gitignore: string = pythonGitignore;
public async executeCore(context: IProjectWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
const projectTemplate = await PysteinProjectCreateStep.getProjectTemplate(context);
const localSettingsContent = projectTemplate.templateFiles[localSettingsFileName];
if (localSettingsContent) {
this.localSettingsJson = JSON.parse(localSettingsContent) as ILocalSettingsJson;
} else {
// Python V2+ model currently requires the EnableWorkerIndexing feature flag be enabled.
this.localSettingsJson.Values ??= {};
this.localSettingsJson.Values['AzureWebJobsFeatureFlags'] = 'EnableWorkerIndexing';
}
await super.executeCore(context, progress);
const explicitlyHandledFiles = [
localSettingsFileName,
hostFileName
];
// NOTE: We want to add all files in the templates *except* those we've explicitly handled (above and below).
const filesToAdd =
Object
.keys(projectTemplate.templateFiles)
.filter(file => !explicitlyHandledFiles.includes(file));
for (const file of filesToAdd) {
const fileContent = projectTemplate.templateFiles[file];
if (!fileContent) {
throw new Error(localize('pysteinTemplateFileNotFound', 'The expected {0} file could not be found in the project template.', file));
}
const filePath = path.join(context.projectPath, file);
if (await confirmOverwriteFile(context, filePath)) {
await AzExtFsExtra.writeFile(filePath, fileContent);
}
}
const functionAppPath = Uri.file(path.join(context.projectPath, pythonFunctionAppFileName));
if (await fileExists(functionAppPath)) {
await window.showTextDocument(await workspace.openTextDocument(functionAppPath));
}
const gettingStartedPath = Uri.file(path.join(context.projectPath, gettingStartedFileName));
if (await fileExists(gettingStartedPath)) {
await showMarkdownPreviewFile(gettingStartedPath, /* openToSide: */ true);
}
}
protected async getHostContent(context: IProjectWizardContext): Promise<IHostJsonV2> {
const projectTemplate = await PysteinProjectCreateStep.getProjectTemplate(context);
const hostContent = projectTemplate.templateFiles[hostFileName];
let hostJson: IHostJsonV2;
if (hostContent) {
hostJson = JSON.parse(hostContent) as IHostJsonV2;
} else {
hostJson = await super.getHostContent(context);
}
// Python V2+ model currently does not work when extension bundles are specified.
hostJson.extensionBundle = undefined;
return hostJson;
}
private static async getProjectTemplate(context: IProjectWizardContext): Promise<IScriptFunctionTemplate> {
const templateProvider = new PysteinTemplateProvider(context.version, context.projectPath, ProjectLanguage.Python, context.projectTemplateKey);
const projectTemplate = await templateProvider.getProjectTemplate();
if (!projectTemplate) {
throw new Error(localize('pysteinNoProjectTemplate', 'No PyStein project template could be found.'));
}
return projectTemplate;
}
}
// https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
const pythonGitignore: string = `# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that dont work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
`;

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

@ -10,7 +10,6 @@ import * as vscode from 'vscode';
import { deploySubpathSetting, functionFilter, ProjectLanguage, remoteBuildSetting, ScmType } from '../../constants';
import { ext } from '../../extensionVariables';
import { addLocalFuncTelemetry } from '../../funcCoreTools/getLocalFuncCoreToolsVersion';
import { FuncVersion } from '../../FuncVersion';
import { localize } from '../../localize';
import { ResolvedFunctionAppResource } from '../../tree/ResolvedFunctionAppResource';
import { SlotTreeItem } from '../../tree/SlotTreeItem';
@ -43,9 +42,11 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string |
expectedChildContextValue: expectedContextValue
}));
const [language, version]: [ProjectLanguage, FuncVersion] = await verifyInitForVSCode(context, context.effectiveDeployFsPath);
const { language, version } = await verifyInitForVSCode(context, context.effectiveDeployFsPath);
context.telemetry.properties.projectLanguage = language;
context.telemetry.properties.projectRuntime = version;
// TODO: telemetry for language model.
if (language === ProjectLanguage.Python && !node.site.isLinux) {
context.errorHandling.suppressReportIssue = true;

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

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardExecuteStep, AzureWizardPromptStep, IWizardOptions } from '@microsoft/vscode-azext-utils';
import { QuickPickItem, QuickPickOptions } from 'vscode';
import { ProjectLanguage } from '../../constants';
import { AzureWizardExecuteStep, AzureWizardPromptStep, IAzureQuickPickItem, IWizardOptions } from '@microsoft/vscode-azext-utils';
import { QuickPickOptions } from 'vscode';
import { previewPythonModel, ProjectLanguage, pythonNewModelPreview } from '../../constants';
import { localize } from '../../localize';
import { IProjectWizardContext } from '../createNewProject/IProjectWizardContext';
import { DotnetInitVSCodeStep } from './InitVSCodeStep/DotnetInitVSCodeStep';
@ -22,21 +22,24 @@ export class InitVSCodeLanguageStep extends AzureWizardPromptStep<IProjectWizard
public async prompt(context: IProjectWizardContext): Promise<void> {
// Display all languages, even if we don't have full support for them
const languagePicks: QuickPickItem[] = [
{ label: ProjectLanguage.CSharp },
{ label: ProjectLanguage.CSharpScript },
{ label: ProjectLanguage.FSharp },
{ label: ProjectLanguage.FSharpScript },
{ label: ProjectLanguage.Java },
{ label: ProjectLanguage.JavaScript },
{ label: ProjectLanguage.PowerShell },
{ label: ProjectLanguage.Python },
{ label: ProjectLanguage.TypeScript },
{ label: ProjectLanguage.Custom }
const languagePicks: IAzureQuickPickItem<{ language: ProjectLanguage, model?: number }>[] = [
{ label: ProjectLanguage.CSharp, data: { language: ProjectLanguage.CSharp } },
{ label: ProjectLanguage.CSharpScript, data: { language: ProjectLanguage.CSharpScript } },
{ label: ProjectLanguage.FSharp, data: { language: ProjectLanguage.FSharp } },
{ label: ProjectLanguage.FSharpScript, data: { language: ProjectLanguage.FSharpScript } },
{ label: ProjectLanguage.Java, data: { language: ProjectLanguage.Java } },
{ label: ProjectLanguage.JavaScript, data: { language: ProjectLanguage.JavaScript } },
{ label: ProjectLanguage.PowerShell, data: { language: ProjectLanguage.PowerShell } },
{ label: ProjectLanguage.Python, data: { language: ProjectLanguage.Python } },
{ label: pythonNewModelPreview, data: { language: ProjectLanguage.Python, model: previewPythonModel } },
{ label: ProjectLanguage.TypeScript, data: { language: ProjectLanguage.TypeScript } },
{ label: ProjectLanguage.Custom, data: { language: ProjectLanguage.Custom } }
];
const options: QuickPickOptions = { placeHolder: localize('selectLanguage', "Select your project's language") };
context.language = <ProjectLanguage>(await context.ui.showQuickPick(languagePicks, options)).label;
const option = await context.ui.showQuickPick(languagePicks, options);
context.language = option.data.language;
context.languageModel = option.data.model;
}
public shouldPrompt(context: IProjectWizardContext): boolean {

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

@ -6,7 +6,7 @@
import { AzExtFsExtra, AzureWizardExecuteStep, IActionContext } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { DebugConfiguration, MessageItem, TaskDefinition, WorkspaceFolder } from 'vscode';
import { deploySubpathSetting, extensionId, func, funcVersionSetting, gitignoreFileName, launchFileName, preDeployTaskSetting, ProjectLanguage, projectLanguageSetting, projectSubpathSetting, settingsFileName, tasksFileName } from '../../../constants';
import { deploySubpathSetting, extensionId, func, funcVersionSetting, gitignoreFileName, launchFileName, preDeployTaskSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, projectSubpathSetting, settingsFileName, tasksFileName } from '../../../constants';
import { ext } from '../../../extensionVariables';
import { FuncVersion } from '../../../FuncVersion';
import { localize } from '../../../localize';
@ -40,7 +40,7 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
await AzExtFsExtra.ensureDir(vscodePath);
await this.writeTasksJson(context, vscodePath, language);
await this.writeLaunchJson(context, context.workspaceFolder, vscodePath, version);
await this.writeSettingsJson(context, vscodePath, language, version);
await this.writeSettingsJson(context, vscodePath, language, context.languageModel, version);
await this.writeExtensionsJson(context, vscodePath, language);
// Remove '.vscode' from gitignore if applicable
@ -204,7 +204,7 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
return existingConfigs;
}
private async writeSettingsJson(context: IProjectWizardContext, vscodePath: string, language: string, version: FuncVersion): Promise<void> {
private async writeSettingsJson(context: IProjectWizardContext, vscodePath: string, language: string, languageModel: number | undefined, version: FuncVersion): Promise<void> {
const settings: ISettingToAdd[] = this.settings.concat(
{ key: projectLanguageSetting, value: language },
{ key: funcVersionSetting, value: version },
@ -212,6 +212,10 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep<IProject
{ prefix: 'debug', key: 'internalConsoleOptions', value: 'neverOpen' }
);
if (languageModel) {
settings.push({ key: projectLanguageModelSetting, value: languageModel });
}
// Add "projectSubpath" setting if project is far enough down that we won't auto-detect it
if (path.posix.relative(context.projectPath, context.workspacePath).startsWith('../..')) {
settings.push({

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

@ -5,7 +5,7 @@
import { AzExtFsExtra, IActionContext } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { buildGradleFileName, localSettingsFileName, pomXmlFileName, ProjectLanguage, workerRuntimeKey } from '../../constants';
import { buildGradleFileName, localSettingsFileName, pomXmlFileName, previewPythonModel, ProjectLanguage, pythonFunctionAppFileName, workerRuntimeKey } from '../../constants';
import { getLocalSettingsJson, ILocalSettingsJson } from '../../funcConfig/local.settings';
import { dotnetUtils } from '../../utils/dotnetUtils';
import { telemetryUtils } from '../../utils/telemetryUtils';
@ -104,3 +104,21 @@ async function detectScriptLanguages(context: IActionContext, projectPath: strin
return detectedLangs;
});
}
export async function detectProjectLanguageModel(language: ProjectLanguage | undefined, projectPath: string): Promise<number | undefined> {
switch (language) {
case ProjectLanguage.Python:
const uris = await findFiles(projectPath, pythonFunctionAppFileName);
if (uris.length > 0) {
return previewPythonModel;
}
break;
default:
break;
}
return undefined;
}

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

@ -5,7 +5,7 @@
import { AzureWizard, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils';
import { window, workspace, WorkspaceFolder } from 'vscode';
import { funcVersionSetting, ProjectLanguage, projectLanguageSetting, projectTemplateKeySetting } from '../../constants';
import { funcVersionSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, projectTemplateKeySetting } from '../../constants';
import { NoWorkspaceError } from '../../errors';
import { tryGetLocalFuncVersion } from '../../funcCoreTools/tryGetLocalFuncVersion';
import { FuncVersion, latestGAVersion } from '../../FuncVersion';
@ -14,7 +14,7 @@ import { getContainingWorkspace } from '../../utils/workspace';
import { getGlobalSetting } from '../../vsCodeConfig/settings';
import { IProjectWizardContext } from '../createNewProject/IProjectWizardContext';
import { verifyAndPromptToCreateProject } from '../createNewProject/verifyIsProject';
import { detectProjectLanguage } from './detectProjectLanguage';
import { detectProjectLanguage, detectProjectLanguageModel } from './detectProjectLanguage';
import { InitVSCodeLanguageStep } from './InitVSCodeLanguageStep';
export async function initProjectForVSCode(context: IActionContext, fsPath?: string, language?: ProjectLanguage): Promise<void> {
@ -43,10 +43,11 @@ export async function initProjectForVSCode(context: IActionContext, fsPath?: str
}
language = language || getGlobalSetting(projectLanguageSetting) || await detectProjectLanguage(context, projectPath);
const languageModel: number | undefined = getGlobalSetting(projectLanguageModelSetting) || await detectProjectLanguageModel(language, projectPath);
const version: FuncVersion = getGlobalSetting(funcVersionSetting) || await tryGetLocalFuncVersion(context, workspacePath) || latestGAVersion;
const projectTemplateKey: string | undefined = getGlobalSetting(projectTemplateKeySetting);
const wizardContext: IProjectWizardContext = Object.assign(context, { projectPath, workspacePath, language, version, workspaceFolder, projectTemplateKey });
const wizardContext: IProjectWizardContext = Object.assign(context, { projectPath, workspacePath, language, languageModel, version, workspaceFolder, projectTemplateKey });
const wizard: AzureWizard<IProjectWizardContext> = new AzureWizard(wizardContext, { promptSteps: [new InitVSCodeLanguageStep()] });
await wizard.prompt();
await wizard.execute();

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

@ -7,6 +7,7 @@ import { localize } from "./localize";
export const extensionId: string = 'ms-azuretools.vscode-azurefunctions';
export const projectLanguageSetting: string = 'projectLanguage';
export const projectLanguageModelSetting: string = 'projectLanguageModel';
export const funcVersionSetting: string = 'projectRuntime'; // Using this name for the sake of backwards compatability even though it's not the most accurate
export const projectSubpathSetting: string = 'projectSubpath';
export const templateFilterSetting: string = 'templateFilter';
@ -33,6 +34,13 @@ export enum ProjectLanguage {
Custom = 'Custom'
}
/**
* The "original" (i.e. first) Python model is 1 (and assumed, if the number is omitted).
* The new (i.e. second) Python model (i.e. with binding attributes, now in Preview) is 2.
* Any significantly changed new model should use an incremented number.
*/
export const previewPythonModel: number = 2;
export enum TemplateFilter {
All = 'All',
Core = 'Core',
@ -48,6 +56,7 @@ export const settingsFileName: string = 'settings.json';
export const vscodeFolderName: string = '.vscode';
export const gitignoreFileName: string = '.gitignore';
export const requirementsFileName: string = 'requirements.txt';
export const pythonFunctionAppFileName: string = 'function_app.py';
export const extensionsCsprojFileName: string = 'extensions.csproj';
export const pomXmlFileName: string = 'pom.xml';
export const buildGradleFileName: string = 'build.gradle';
@ -97,6 +106,8 @@ export const contentShareKey: string = 'WEBSITE_CONTENTSHARE';
export const viewOutput: string = localize('viewOutput', 'View Output');
export const previewDescription: string = localize('preview', '(Preview)');
export const pythonNewModelPreview: string = localize('pythonNewModelPreview', 'Python (New Model Preview)');
export const webProvider: string = 'Microsoft.Web';
export const functionFilter = {

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

@ -6,17 +6,20 @@
import { BlobServiceClient } from '@azure/storage-blob';
import { AzExtFsExtra, AzureWizard, IActionContext, parseError } from "@microsoft/vscode-azext-utils";
import * as path from 'path';
import * as semver from 'semver';
import * as vscode from 'vscode';
import { AzureWebJobsStorageExecuteStep } from "../commands/appSettings/AzureWebJobsStorageExecuteStep";
import { AzureWebJobsStoragePromptStep } from "../commands/appSettings/AzureWebJobsStoragePromptStep";
import { IAzureWebJobsStorageWizardContext } from "../commands/appSettings/IAzureWebJobsStorageWizardContext";
import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject';
import { functionJsonFileName, localEmulatorConnectionString, localSettingsFileName, ProjectLanguage, projectLanguageSetting, workerRuntimeKey } from "../constants";
import { functionJsonFileName, localEmulatorConnectionString, localSettingsFileName, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, workerRuntimeKey } from "../constants";
import { ParsedFunctionJson } from "../funcConfig/function";
import { azureWebJobsStorageKey, getAzureWebJobsStorage, MismatchBehavior, setLocalAppSetting } from "../funcConfig/local.settings";
import { getLocalFuncCoreToolsVersion } from '../funcCoreTools/getLocalFuncCoreToolsVersion';
import { validateFuncCoreToolsInstalled } from '../funcCoreTools/validateFuncCoreToolsInstalled';
import { localize } from '../localize';
import { getFunctionFolders } from "../tree/localProject/LocalFunctionsTreeItem";
import { isPythonV2Plus } from '../utils/pythonUtils';
import { getDebugConfigs, isDebugConfigEqual } from '../vsCodeConfig/launch';
import { getWorkspaceSetting, tryGetFunctionsWorkerRuntimeForProject } from "../vsCodeConfig/settings";
@ -41,13 +44,19 @@ export async function preDebugValidate(context: IActionContext, debugConfig: vsc
if (projectPath) {
const projectLanguage: string | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
const projectLanguageModel: number | undefined = getWorkspaceSetting(projectLanguageModelSetting, projectPath);
context.telemetry.properties.projectLanguage = projectLanguage;
context.telemetry.properties.projectLanguageModel = projectLanguageModel?.toString();
context.telemetry.properties.lastValidateStep = 'functionVersion';
shouldContinue = await validateFunctionVersion(context, projectLanguage, projectLanguageModel, workspace.uri.fsPath);
context.telemetry.properties.lastValidateStep = 'workerRuntime';
await validateWorkerRuntime(context, projectLanguage, projectPath);
context.telemetry.properties.lastValidateStep = 'azureWebJobsStorage';
await validateAzureWebJobsStorage(context, projectLanguage, projectPath);
await validateAzureWebJobsStorage(context, projectLanguage, projectLanguageModel, projectPath);
context.telemetry.properties.lastValidateStep = 'emulatorRunning';
shouldContinue = await validateEmulatorIsRunning(context, projectPath);
@ -98,6 +107,32 @@ function getMatchingWorkspace(debugConfig: vscode.DebugConfiguration): vscode.Wo
throw new Error(localize('noDebug', 'Failed to find launch config matching name "{0}", request "{1}", and type "{2}".', debugConfig.name, debugConfig.request, debugConfig.type));
}
/**
* Ensure that that Python V2+ projects have an appropriate version of Functions tools installed.
*/
async function validateFunctionVersion(context: IActionContext, projectLanguage: string | undefined, projectLanguageModel: number | undefined, workspacePath: string): Promise<boolean> {
const validateTools = getWorkspaceSetting<boolean>('validateFuncCoreTools', workspacePath) !== false;
if (validateTools && isPythonV2Plus(projectLanguage, projectLanguageModel)) {
const version = await getLocalFuncCoreToolsVersion(context, workspacePath);
// NOTE: This is the latest version available as of this commit,
// but not necessarily the final "preview release" version.
// The Functions team is ok with using this version as the
// minimum bar.
const expectedVersionRange = '>=4.0.4742';
if (version && !semver.satisfies(version, expectedVersionRange)) {
const message: string = localize('invalidFunctionVersion', 'The version of installed Functions tools "{0}" is not sufficient for this project type ("{1}").', version, expectedVersionRange);
const debugAnyway: vscode.MessageItem = { title: localize('debugWithInvalidFunctionVersionAnyway', 'Debug anyway') };
const result: vscode.MessageItem = await context.ui.showWarningMessage(message, { modal: true, stepName: 'failedWithInvalidFunctionVersion' }, debugAnyway);
return result === debugAnyway;
}
}
return true;
}
/**
* Automatically add worker runtime setting since it's required to debug, but often gets deleted since it's stored in "local.settings.json" which isn't tracked in source control
*/
@ -109,7 +144,7 @@ async function validateWorkerRuntime(context: IActionContext, projectLanguage: s
}
}
async function validateAzureWebJobsStorage(context: IActionContext, projectLanguage: string | undefined, projectPath: string): Promise<void> {
async function validateAzureWebJobsStorage(context: IActionContext, projectLanguage: string | undefined, projectLanguageModel: number | undefined, projectPath: string): Promise<void> {
if (canValidateAzureWebJobStorageOnDebug(projectLanguage)) {
const azureWebJobsStorage: string | undefined = await getAzureWebJobsStorage(context, projectPath);
if (!azureWebJobsStorage) {
@ -119,7 +154,8 @@ async function validateAzureWebJobsStorage(context: IActionContext, projectLangu
return new ParsedFunctionJson(await AzExtFsExtra.readJSON(functionJsonPath));
}));
if (functions.some(f => !f.isHttpTrigger)) {
// NOTE: Currently, Python V2+ requires storage to be configured, even for HTTP triggers.
if (functions.some(f => !f.isHttpTrigger) || isPythonV2Plus(projectLanguage, projectLanguageModel)) {
const wizardContext: IAzureWebJobsStorageWizardContext = Object.assign(context, { projectPath });
const wizard: AzureWizard<IAzureWebJobsStorageWizardContext> = new AzureWizard(wizardContext, {
promptSteps: [new AzureWebJobsStoragePromptStep(true /* suppressSkipForNow */)],

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

@ -30,6 +30,7 @@ import { FunctionAppResolver } from './FunctionAppResolver';
import { getResourceGroupsApi } from './getExtensionApi';
import { FunctionsLocalResourceProvider } from './LocalResourceProvider';
import { CentralTemplateProvider } from './templates/CentralTemplateProvider';
import { registerContentProvider } from './utils/textUtils';
import { AzureFunctionsExtensionApi } from './vscode-azurefunctions.api';
import { verifyVSCodeConfigOnActivate } from './vsCodeConfig/verifyVSCodeConfigOnActivate';
@ -87,6 +88,8 @@ export async function activateInternal(context: vscode.ExtensionContext, perfSta
handleUri
}));
registerContentProvider();
ext.experimentationService = await createExperimentationService(context);
ext.rgApi = await getResourceGroupsApi();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

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

@ -11,6 +11,7 @@ import { FuncVersion } from '../FuncVersion';
import { localize } from '../localize';
import { delay } from '../utils/delay';
import { nonNullValue } from '../utils/nonNull';
import { isPythonV2Plus } from '../utils/pythonUtils';
import { requestUtils } from '../utils/requestUtils';
import { getWorkspaceSetting } from '../vsCodeConfig/settings';
import { DotnetTemplateProvider } from './dotnet/DotnetTemplateProvider';
@ -22,6 +23,7 @@ import { getJavaVerifiedTemplateIds } from './java/getJavaVerifiedTemplateIds';
import { JavaTemplateProvider } from './java/JavaTemplateProvider';
import { getScriptVerifiedTemplateIds } from './script/getScriptVerifiedTemplateIds';
import { IScriptFunctionTemplate } from './script/parseScriptTemplates';
import { PysteinTemplateProvider } from './script/PysteinTemplateProvider';
import { ScriptBundleTemplateProvider } from './script/ScriptBundleTemplateProvider';
import { ScriptTemplateProvider } from './script/ScriptTemplateProvider';
import { TemplateProviderBase } from './TemplateProviderBase';
@ -47,7 +49,7 @@ export class CentralTemplateProvider implements Disposable {
this._providersMap.clear();
}
public static getProviders(projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion, projectTemplateKey: string | undefined): TemplateProviderBase[] {
public static getProviders(projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, projectTemplateKey: string | undefined): TemplateProviderBase[] {
const providers: TemplateProviderBase[] = [];
switch (language) {
case ProjectLanguage.CSharp:
@ -58,17 +60,21 @@ export class CentralTemplateProvider implements Disposable {
providers.push(new JavaTemplateProvider(version, projectPath, language, projectTemplateKey));
break;
default:
providers.push(new ScriptTemplateProvider(version, projectPath, language, projectTemplateKey));
if (version !== FuncVersion.v1) {
providers.push(new ScriptBundleTemplateProvider(version, projectPath, language, projectTemplateKey));
if (isPythonV2Plus(language, languageModel)) {
providers.push(new PysteinTemplateProvider(version, projectPath, language, projectTemplateKey));
} else {
providers.push(new ScriptTemplateProvider(version, projectPath, language, projectTemplateKey));
if (version !== FuncVersion.v1) {
providers.push(new ScriptBundleTemplateProvider(version, projectPath, language, projectTemplateKey));
}
}
break;
}
return providers;
}
public async getFunctionTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion, templateFilter: TemplateFilter, projectTemplateKey: string | undefined): Promise<IFunctionTemplate[]> {
const templates: ITemplates = await this.getTemplates(context, projectPath, language, version, projectTemplateKey);
public async getFunctionTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, templateFilter: TemplateFilter, projectTemplateKey: string | undefined): Promise<IFunctionTemplate[]> {
const templates: ITemplates = await this.getTemplates(context, projectPath, language, languageModel, version, projectTemplateKey);
switch (templateFilter) {
case TemplateFilter.All:
return templates.functionTemplates;
@ -81,8 +87,8 @@ export class CentralTemplateProvider implements Disposable {
}
}
public async clearTemplateCache(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion): Promise<void> {
const providers: TemplateProviderBase[] = CentralTemplateProvider.getProviders(projectPath, language, version, undefined);
public async clearTemplateCache(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion): Promise<void> {
const providers: TemplateProviderBase[] = CentralTemplateProvider.getProviders(projectPath, language, languageModel, version, undefined);
for (const provider of providers) {
await provider.clearCachedTemplateMetadata();
await provider.clearCachedTemplates(context);
@ -94,14 +100,14 @@ export class CentralTemplateProvider implements Disposable {
}
}
public async getBindingTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion): Promise<IBindingTemplate[]> {
const templates: ITemplates = await this.getTemplates(context, projectPath, language, version, undefined);
public async getBindingTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion): Promise<IBindingTemplate[]> {
const templates: ITemplates = await this.getTemplates(context, projectPath, language, languageModel, version, undefined);
return templates.bindingTemplates;
}
public async tryGetSampleData(context: IActionContext, version: FuncVersion, triggerBindingType: string): Promise<string | undefined> {
try {
const templates: IScriptFunctionTemplate[] = <IScriptFunctionTemplate[]>await this.getFunctionTemplates(context, undefined, ProjectLanguage.JavaScript, version, TemplateFilter.All, undefined);
const templates: IScriptFunctionTemplate[] = <IScriptFunctionTemplate[]>await this.getFunctionTemplates(context, undefined, ProjectLanguage.JavaScript, undefined, version, TemplateFilter.All, undefined);
const template: IScriptFunctionTemplate | undefined = templates.find(t => t.functionJson.triggerBinding?.type?.toLowerCase() === triggerBindingType.toLowerCase());
return template?.templateFiles['sample.dat'];
} catch {
@ -109,8 +115,8 @@ export class CentralTemplateProvider implements Disposable {
}
}
public async getProjectTemplateKey(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion, projectTemplateKey: string | undefined): Promise<string> {
const cachedProviders = await this.getCachedProviders(context, projectPath, language, version, projectTemplateKey);
public async getProjectTemplateKey(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, projectTemplateKey: string | undefined): Promise<string> {
const cachedProviders = await this.getCachedProviders(context, projectPath, language, languageModel, version, projectTemplateKey);
// .NET is the only language that supports project template keys and they only have one provider
// We probably need to do something better here once multi-provider languages support project template keys
const provider = nonNullValue(cachedProviders.providers[0], 'firstProvider');
@ -140,10 +146,10 @@ export class CentralTemplateProvider implements Disposable {
this._providersMap.set(key, cachedProviders);
}
private async getCachedProviders(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion, projectTemplateKey: string | undefined): Promise<CachedProviders> {
private async getCachedProviders(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, projectTemplateKey: string | undefined): Promise<CachedProviders> {
let cachedProviders = this.tryGetCachedProviders(projectPath, language, version);
if (!cachedProviders) {
cachedProviders = { providers: CentralTemplateProvider.getProviders(projectPath, language, version, projectTemplateKey) };
cachedProviders = { providers: CentralTemplateProvider.getProviders(projectPath, language, languageModel, version, projectTemplateKey) };
this.setCachedProviders(projectPath, language, version, cachedProviders);
} else {
await Promise.all(cachedProviders.providers.map(async p => {
@ -158,11 +164,11 @@ export class CentralTemplateProvider implements Disposable {
/**
* Ensures we only have one task going at a time for refreshing templates
*/
private async getTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, version: FuncVersion, projectTemplateKey: string | undefined): Promise<ITemplates> {
private async getTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, projectTemplateKey: string | undefined): Promise<ITemplates> {
context.telemetry.properties.projectRuntime = version;
context.telemetry.properties.projectLanguage = language;
const cachedProviders = await this.getCachedProviders(context, projectPath, language, version, projectTemplateKey);
const cachedProviders = await this.getCachedProviders(context, projectPath, language, languageModel, version, projectTemplateKey);
let templatesTask: Promise<ITemplates> | undefined = cachedProviders.templatesTask;
if (templatesTask) {
return await templatesTask;

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

@ -22,4 +22,5 @@ export interface IFunctionTemplate {
isSqlBindingTemplate: boolean;
userPromptedSettings: IBindingSetting[];
categories: TemplateCategory[];
categoryStyle?: string;
}

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

@ -117,7 +117,7 @@ async function copyCSharpSettingsFromJS(csharpTemplates: IFunctionTemplate[], ve
jsContext.telemetry.properties.isActivationEvent = 'true';
const templateProvider = ext.templateProvider.get(jsContext);
const jsTemplates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(jsContext, undefined, ProjectLanguage.JavaScript, version, TemplateFilter.All, undefined);
const jsTemplates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(jsContext, undefined, ProjectLanguage.JavaScript, undefined, version, TemplateFilter.All, undefined);
for (const csharpTemplate of csharpTemplates) {
const normalizedDotnetId = normalizeDotnetId(csharpTemplate.id);
const jsTemplate: IFunctionTemplate | undefined = jsTemplates.find((t: IFunctionTemplate) => normalizeScriptId(t.id) === normalizedDotnetId);

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

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzExtFsExtra, IActionContext } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { ProjectLanguage } from '../../constants';
import { IBindingTemplate } from '../IBindingTemplate';
import { IFunctionTemplate } from '../IFunctionTemplate';
import { ITemplates } from '../ITemplates';
import { TemplateProviderBase, TemplateType } from '../TemplateProviderBase';
import { getScriptResourcesLanguage } from './getScriptResourcesLanguage';
import { IScriptFunctionTemplate, parseScriptTemplates } from './parseScriptTemplates';
export class PysteinTemplateProvider extends TemplateProviderBase {
public templateType: TemplateType = TemplateType.Script;
protected get backupSubpath(): string {
return path.join('pystein');
}
protected _rawTemplates: object[];
public async getCachedTemplates(): Promise<ITemplates | undefined> {
return await this.getBackupTemplates();
}
public async getLatestTemplateVersion(_context: IActionContext): Promise<string> {
return '1.0';
}
public async getLatestTemplates(_context: IActionContext, _latestTemplateVersion: string): Promise<ITemplates> {
return await this.getBackupTemplates();
}
public async getBackupTemplates(): Promise<ITemplates> {
return await this.parseTemplates(this.getBackupPath());
}
public async getProjectTemplate(): Promise<IScriptFunctionTemplate | undefined> {
const templates = await this.getBackupTemplates();
// Find the first Python Preview project root template...
return templates.functionTemplates.find(template =>
template.language === ProjectLanguage.Python
&& template.id.endsWith('-Python-Preview')
&& template.categoryStyle === 'projectroot') as IScriptFunctionTemplate;
}
public async updateBackupTemplates(): Promise<void> {
// NOTE: No-op as the templates are only bundled with this extension.
await Promise.resolve();
}
public async cacheTemplates(): Promise<void> {
// NOTE: No-op as the templates are only bundled with this extension.
await Promise.resolve();
}
public async clearCachedTemplates(): Promise<void> {
// NOTE: No-op as the templates are only bundled with this extension.
await Promise.resolve();
}
public includeTemplate(template: IFunctionTemplate | IBindingTemplate): boolean {
return this.isFunctionTemplate(template)
&& template.language === ProjectLanguage.Python
&& template.id.endsWith('-Python-Preview-Append');
}
protected async parseTemplates(rootPath: string): Promise<ITemplates> {
const paths: ITemplatePaths = this.getTemplatePaths(rootPath);
this._rawTemplates = <object[]>await AzExtFsExtra.readJSON(paths.templates);
return parseScriptTemplates({}, this._rawTemplates, {});
}
protected getResourcesLanguage(): string {
return this.resourcesLanguage || getScriptResourcesLanguage();
}
private getTemplatePaths(rootPath: string): ITemplatePaths {
const templates: string = path.join(rootPath, 'templates', 'templates.json');
return { templates };
}
private isFunctionTemplate(template: IFunctionTemplate | IBindingTemplate): template is IFunctionTemplate {
return (template as IFunctionTemplate).id !== undefined;
}
}
interface ITemplatePaths {
templates: string;
}

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

@ -37,6 +37,7 @@ export function getScriptVerifiedTemplateIds(version: string): (string | RegExp)
]);
// These languages are only supported in v2+ - same functions as JavaScript, with a few minor exceptions that aren't worth distinguishing here
return verifiedTemplateIds.map(t => new RegExp(`^${t}-(JavaScript|TypeScript|Python|PowerShell|Custom)$`, 'i'));
// NOTE: The Python Preview IDs are only temporary.
return verifiedTemplateIds.map(t => new RegExp(`^${t}-(JavaScript|TypeScript|Python|PowerShell|Custom|Python-Preview|Python-Preview-Append)$`, 'i'));
}
}

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

@ -23,6 +23,7 @@ export interface IRawTemplate {
language: ProjectLanguage;
userPrompt?: string[];
category?: TemplateCategory[];
categoryStyle?: string;
};
files?: { [filename: string]: string };
}
@ -228,7 +229,8 @@ export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResou
language,
userPromptedSettings,
templateFiles: rawTemplate.files || {},
categories: rawTemplate.metadata.category || []
categories: rawTemplate.metadata.category || [],
categoryStyle: rawTemplate.metadata.categoryStyle
};
}

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

@ -9,7 +9,7 @@ import * as path from 'path';
import { Disposable, workspace, WorkspaceFolder } from 'vscode';
import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject';
import { getFunctionAppName, getJavaDebugSubpath } from '../commands/initProjectForVSCode/InitVSCodeStep/JavaInitVSCodeStep';
import { funcVersionSetting, hostFileName, javaBuildTool, JavaBuildTool, ProjectLanguage, projectLanguageSetting, projectSubpathSetting } from '../constants';
import { funcVersionSetting, hostFileName, javaBuildTool, JavaBuildTool, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, projectSubpathSetting } from '../constants';
import { ext } from '../extensionVariables';
import { FuncVersion, tryParseFuncVersion } from '../FuncVersion';
import { localize } from '../localize';
@ -72,6 +72,7 @@ export class AzureAccountTreeItemWithProjects extends AzureAccountTreeItemBase {
hasLocalProject = true;
const language: ProjectLanguage | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
const languageModel: number | undefined = getWorkspaceSetting(projectLanguageModelSetting, projectPath);
const version: FuncVersion | undefined = tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, projectPath));
if (language === undefined || version === undefined) {
children.push(new InitLocalProjectTreeItem(this, projectPath, folder));
@ -89,7 +90,7 @@ export class AzureAccountTreeItemWithProjects extends AzureAccountTreeItemBase {
}
const treeItem: LocalProjectTreeItem = new LocalProjectTreeItem(this, { effectiveProjectPath, folder, language, version, preCompiledProjectPath, isIsolated });
const treeItem: LocalProjectTreeItem = new LocalProjectTreeItem(this, { effectiveProjectPath, folder, language, languageModel, version, preCompiledProjectPath, isIsolated });
this._projectDisposables.push(treeItem);
children.push(treeItem);
}

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

@ -23,12 +23,15 @@ export abstract class FunctionTreeItemBase extends AzExtTreeItem {
private _disabled: boolean;
private _func: FunctionEnvelope | undefined;
protected constructor(parent: FunctionsTreeItemBase, config: ParsedFunctionJson, name: string, func: FunctionEnvelope | undefined) {
protected constructor(parent: FunctionsTreeItemBase, config: ParsedFunctionJson, name: string, func: FunctionEnvelope | undefined, enableProperties: boolean = true) {
super(parent);
this._config = config;
this.name = name;
this._func = func;
this.commandId = 'azureFunctions.viewProperties';
if (enableProperties) {
this.commandId = 'azureFunctions.viewProperties';
}
}
public get id(): string {

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

@ -14,7 +14,7 @@ export class LocalFunctionTreeItem extends FunctionTreeItemBase {
public readonly functionJsonPath: string | undefined;
private constructor(parent: LocalFunctionsTreeItem, name: string, config: ParsedFunctionJson, functionJsonPath: string | undefined, func: FunctionEnvelope | undefined) {
super(parent, config, name, func);
super(parent, config, name, func, /* enableProperties: */ functionJsonPath !== undefined);
this.functionJsonPath = functionJsonPath;
}

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

@ -12,6 +12,7 @@ import { ParsedFunctionJson } from '../../funcConfig/function';
import { runningFuncTaskMap } from '../../funcCoreTools/funcHostTask';
import { localize } from '../../localize';
import { nonNullProp } from '../../utils/nonNull';
import { isPythonV2Plus } from '../../utils/pythonUtils';
import { requestUtils } from '../../utils/requestUtils';
import { telemetryUtils } from '../../utils/telemetryUtils';
import { findFiles } from '../../utils/workspace';
@ -33,8 +34,10 @@ export class LocalFunctionsTreeItem extends FunctionsTreeItemBase {
}
public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
if (this.parent.isIsolated) {
return await this.getChildrenForIsolatedProject(context);
const isParentPythonV2Plus = isPythonV2Plus(this.parent.language, this.parent.languageModel);
if (this.parent.isIsolated || isParentPythonV2Plus) {
return await this.getChildrenForHostedProjects(context);
} else {
const functions: string[] = await getFunctionFolders(context, this.parent.effectiveProjectPath);
const children: AzExtTreeItem[] = await this.createTreeItemsWithErrorHandling(
@ -75,9 +78,9 @@ export class LocalFunctionsTreeItem extends FunctionsTreeItemBase {
}
/**
* .NET Isolated projects don't have typical "function.json" files, so we'll have to ping localhost to get functions (only available if the project is running)
* Some projects (e.g. .NET Isolated and PyStein (i.e. Python model >=2)) don't have typical "function.json" files, so we'll have to ping localhost to get functions (only available if the project is running)
*/
private async getChildrenForIsolatedProject(context: IActionContext): Promise<AzExtTreeItem[]> {
private async getChildrenForHostedProjects(context: IActionContext): Promise<AzExtTreeItem[]> {
if (runningFuncTaskMap.has(this.parent.workspaceFolder)) {
const hostRequest = await this.parent.getHostRequest(context);
const functions = await requestUtils.sendRequestWithExtTimeout(context, {

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

@ -24,6 +24,7 @@ export type LocalProjectOptions = {
folder: WorkspaceFolder;
version: FuncVersion;
language: ProjectLanguage;
languageModel?: number;
preCompiledProjectPath?: string
isIsolated?: boolean;
}
@ -37,7 +38,8 @@ export class LocalProjectTreeItem extends LocalProjectTreeItemBase implements Di
public readonly workspacePath: string;
public readonly workspaceFolder: WorkspaceFolder;
public readonly version: FuncVersion;
public readonly langauge: ProjectLanguage;
public readonly language: ProjectLanguage;
public readonly languageModel: number | undefined;
public readonly isIsolated: boolean;
private readonly _disposables: Disposable[] = [];
@ -50,7 +52,8 @@ export class LocalProjectTreeItem extends LocalProjectTreeItemBase implements Di
this.workspaceFolder = options.folder;
this.preCompiledProjectPath = options.preCompiledProjectPath;
this.version = options.version;
this.langauge = options.language;
this.language = options.language;
this.languageModel = options.languageModel;
this.isIsolated = !!options.isIsolated;
this._disposables.push(createRefreshFileWatcher(this, path.join(this.effectiveProjectPath, '*', functionJsonFileName)));

10
src/utils/pythonUtils.ts Normal file
Просмотреть файл

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProjectLanguage } from "../constants";
export function isPythonV2Plus(language: string | undefined, model: number | undefined): boolean {
return language === ProjectLanguage.Python && model !== undefined && model > 1;
}

76
src/utils/textUtils.ts Normal file
Просмотреть файл

@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as crypto from 'crypto';
import { URLSearchParams } from 'url';
import * as vscode from 'vscode';
import { ext } from '../extensionVariables';
const contentScheme = 'vscode-azurefunctions-static-content';
/**
* @remarks Borrowed from vscode-azuretools
*/
function getPseudononymousStringHash(s: string, encoding: crypto.BinaryToTextEncoding = 'base64'): string {
return crypto.createHash('sha256').update(s).digest(encoding);
}
class StaticContentProvider implements vscode.TextDocumentContentProvider {
provideTextDocumentContent(uri: vscode.Uri, _token: vscode.CancellationToken): vscode.ProviderResult<string> {
const searchParams = new URLSearchParams(uri.query);
return searchParams.get('content') ?? undefined;
}
registerTextDocumentContent(content: string, filename: string = 'text.txt'): vscode.Uri {
const searchParams = new URLSearchParams();
searchParams.append('content', content);
const query = searchParams.toString();
// TODO: Do we need the hash now?
const hash = getPseudononymousStringHash(content);
const uri = vscode.Uri.from(
{
scheme: contentScheme,
path: `${hash}/${filename}`,
query
});
return uri;
}
}
let contentProvider: StaticContentProvider | undefined;
export function registerContentProvider(): StaticContentProvider {
if (!contentProvider) {
contentProvider = new StaticContentProvider();
ext.context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(contentScheme, contentProvider));
}
return contentProvider;
}
export function registerStaticContent(content: string, filename?: string): vscode.Uri {
return registerContentProvider().registerTextDocumentContent(content, filename);
}
export async function showMarkdownPreviewContent(content: string, filename: string, openToSide?: boolean): Promise<void> {
const uri = registerStaticContent(content, filename);
await showMarkdownPreviewFile(uri, openToSide);
}
export async function showMarkdownPreviewFile(uri: vscode.Uri, openToSide: boolean = false): Promise<void> {
await vscode.commands.executeCommand(
openToSide
? 'markdown.showPreviewToSide'
: 'markdown.showPreview',
uri);
}

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

@ -15,14 +15,17 @@ import { IBindingTemplate } from '../templates/IBindingTemplate';
import { IFunctionTemplate } from '../templates/IFunctionTemplate';
import { promptToReinitializeProject } from '../vsCodeConfig/promptToReinitializeProject';
import { bundleFeedUtils } from './bundleFeedUtils';
import { isPythonV2Plus } from './pythonUtils';
export async function verifyExtensionBundle(context: IFunctionWizardContext | IBindingWizardContext, template: IFunctionTemplate | IBindingTemplate): Promise<void> {
// v1 doesn't support bundles
// http and timer triggers don't need a bundle
// F# and C# specify extensions as dependencies in their proj file instead of using a bundle
// PyStein (i.e. Python model >=2 does not currently support bundles)
if (context.version === FuncVersion.v1 ||
!bundleFeedUtils.isBundleTemplate(template) ||
context.language === ProjectLanguage.CSharp || context.language === ProjectLanguage.FSharp) {
context.language === ProjectLanguage.CSharp || context.language === ProjectLanguage.FSharp ||
isPythonV2Plus(context.language, context.languageModel)) {
context.telemetry.properties.bundleResult = 'n/a';
return;
}

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

@ -5,17 +5,24 @@
import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils';
import { initProjectForVSCode } from '../commands/initProjectForVSCode/initProjectForVSCode';
import { funcVersionSetting, ProjectLanguage, projectLanguageSetting } from '../constants';
import { funcVersionSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting } from '../constants';
import { FuncVersion, tryParseFuncVersion } from '../FuncVersion';
import { localize } from '../localize';
import { nonNullOrEmptyValue } from '../utils/nonNull';
import { getWorkspaceSetting } from './settings';
export interface VerifiedInit {
language: ProjectLanguage,
languageModel: number | undefined,
version: FuncVersion
}
/**
* Simpler function than `verifyVSCodeConfigOnActivate` to be used right before an operation that requires the project to be initialized for VS Code
*/
export async function verifyInitForVSCode(context: IActionContext, fsPath: string, language?: string, version?: string): Promise<[ProjectLanguage, FuncVersion]> {
export async function verifyInitForVSCode(context: IActionContext, fsPath: string, language?: string, languageModel?: number, version?: string): Promise<VerifiedInit> {
language = language || getWorkspaceSetting(projectLanguageSetting, fsPath);
languageModel = languageModel || getWorkspaceSetting(projectLanguageModelSetting, fsPath);
version = tryParseFuncVersion(version || getWorkspaceSetting(funcVersionSetting, fsPath));
if (!language || !version) {
@ -24,8 +31,13 @@ export async function verifyInitForVSCode(context: IActionContext, fsPath: strin
await context.ui.showWarningMessage(message, { modal: true, stepName: 'initProject' }, DialogResponses.yes);
await initProjectForVSCode(context, fsPath);
language = nonNullOrEmptyValue(getWorkspaceSetting(projectLanguageSetting, fsPath), projectLanguageSetting);
languageModel = getWorkspaceSetting(projectLanguageModelSetting, fsPath);
version = nonNullOrEmptyValue(tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, fsPath)), funcVersionSetting);
}
return [<ProjectLanguage>language, <FuncVersion>version];
return {
language: <ProjectLanguage>language,
languageModel,
version: <FuncVersion>version
};
}

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

@ -8,7 +8,7 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject';
import { initProjectForVSCode } from '../commands/initProjectForVSCode/initProjectForVSCode';
import { funcVersionSetting, ProjectLanguage, projectLanguageSetting, TemplateFilter } from '../constants';
import { funcVersionSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, TemplateFilter } from '../constants';
import { ext } from '../extensionVariables';
import { FuncVersion, tryParseFuncVersion } from '../FuncVersion';
import { localize } from '../localize';
@ -30,6 +30,7 @@ export async function verifyVSCodeConfigOnActivate(context: IActionContext, fold
context.telemetry.suppressIfSuccessful = false;
const language: ProjectLanguage | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath);
const languageModel = getWorkspaceSetting<number>(projectLanguageModelSetting, projectPath);
const version: FuncVersion | undefined = tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, projectPath));
if (language !== undefined && version !== undefined) {
// Don't wait
@ -37,7 +38,7 @@ export async function verifyVSCodeConfigOnActivate(context: IActionContext, fold
templatesContext.telemetry.properties.isActivationEvent = 'true';
templatesContext.errorHandling.suppressDisplay = true;
const templateProvider = ext.templateProvider.get(templatesContext);
await templateProvider.getFunctionTemplates(templatesContext, projectPath, language, version, TemplateFilter.Verified, undefined);
await templateProvider.getFunctionTemplates(templatesContext, projectPath, language, languageModel, version, TemplateFilter.Verified, undefined);
});
let isDotnet: boolean = false;

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

@ -47,7 +47,7 @@ export abstract class FunctionTesterBase implements Disposable {
await this.initializeTestFolder(this.projectPath);
// This will initialize and cache the templatesTask for this project. Better to do it here than during the first test
await templateProvider.getFunctionTemplates(context, this.projectPath, this.language, this.version, TemplateFilter.Verified, undefined);
await templateProvider.getFunctionTemplates(context, this.projectPath, this.language, undefined, this.version, TemplateFilter.Verified, undefined);
});
});
}
@ -55,7 +55,7 @@ export abstract class FunctionTesterBase implements Disposable {
public async dispose(): Promise<void> {
await runWithTestActionContext('testCreateFunctionDispose', async context => {
await runForTemplateSource(context, this.source, async (templateProvider) => {
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, this.projectPath, this.language, this.version, TemplateFilter.Verified, undefined);
const templates: IFunctionTemplate[] = await templateProvider.getFunctionTemplates(context, this.projectPath, this.language, undefined, this.version, TemplateFilter.Verified, undefined);
assert.deepEqual(this.testedFunctions.sort(), templates.map(t => t.name).sort(), 'Not all "Verified" templates were tested');
});
});

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

@ -105,7 +105,7 @@ async function preLoadTemplates(): Promise<void> {
}
for (const language of [ProjectLanguage.JavaScript, ProjectLanguage.CSharp]) {
tasks.push(provider.getFunctionTemplates(context, testWorkspaceFolders[0], language, version, TemplateFilter.Verified, undefined));
tasks.push(provider.getFunctionTemplates(context, testWorkspaceFolders[0], language, undefined, version, TemplateFilter.Verified, undefined));
}
}
});

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

@ -71,7 +71,7 @@ function addSuite(source: TemplateSource | undefined): void {
await runWithTestActionContext('getFunctionTemplates', async context => {
await runForTemplateSource(context, source, async (provider: CentralTemplateProvider) => {
const templates: IFunctionTemplate[] = await provider.getFunctionTemplates(context, testWorkspacePath, language, version, TemplateFilter.Verified, projectTemplateKey);
const templates: IFunctionTemplate[] = await provider.getFunctionTemplates(context, testWorkspacePath, language, undefined, version, TemplateFilter.Verified, projectTemplateKey);
assert.equal(templates.length, expectedCount);
});
});

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

@ -44,7 +44,7 @@ suite('Backup templates', () => {
continue;
}
const providers: TemplateProviderBase[] = CentralTemplateProvider.getProviders(testWorkspacePath, worker.language, version, worker.projectTemplateKey);
const providers: TemplateProviderBase[] = CentralTemplateProvider.getProviders(testWorkspacePath, worker.language, undefined, version, worker.projectTemplateKey);
const context = await createTestActionContext();
for (const provider of providers) {