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:
Родитель
0f10d458de
Коммит
925796cee3
|
@ -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 don’t 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)));
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче