Split function/project creation into separate files

This helps isolate language-specific code and will be more important as we add C# class library support. It should have no functional effect.
This commit is contained in:
Eric Jizba 2017-12-21 14:21:43 -08:00
Родитель d5433158d5
Коммит a8a8276378
14 изменённых файлов: 506 добавлений и 373 удалений

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

@ -152,26 +152,3 @@ export function convertStringToRuntime(rawRuntime?: string): ProjectRuntime | un
return undefined;
}
}
export function getFileNameFromLanguage(language: string): string | undefined {
switch (language) {
case ProjectLanguage.Bash:
return 'run.sh';
case ProjectLanguage.Batch:
return 'run.bat';
case ProjectLanguage.FSharp:
return 'run.fsx';
case ProjectLanguage.JavaScript:
return 'index.js';
case ProjectLanguage.PHP:
return 'run.php';
case ProjectLanguage.PowerShell:
return 'run.ps1';
case ProjectLanguage.Python:
return 'run.py';
case ProjectLanguage.TypeScript:
return 'index.ts';
default:
return undefined;
}
}

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

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserInterface } from "../../IUserInterface";
import { Template } from "../../templates/Template";
export interface IFunctionCreator {
/**
* Prompt for any settings that are specific to this creator
* This includes the function name (Since the name could have different restrictions for different languages)
*/
promptForSettings(functionAppPath: string, template: Template, ui: IUserInterface): Promise<void>;
createFunction(functionAppPath: string, template: Template, userSettings: { [propertyName: string]: string }): Promise<string | undefined>;
}

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

@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* 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 { OutputChannel } from "vscode";
import { IUserInterface } from "../../IUserInterface";
import { localize } from "../../localize";
import { Template } from "../../templates/Template";
import { convertTemplateIdToJava } from "../../templates/TemplateData";
import { cpUtils } from "../../utils/cpUtils";
import * as fsUtil from '../../utils/fs';
import { getJavaClassName, validateJavaFunctionName, validatePackageName } from "../../utils/javaNameUtils";
import { mavenUtils } from "../../utils/mavenUtils";
import { IFunctionCreator } from './IFunctionCreator';
function getNewJavaFunctionFilePath(functionAppPath: string, packageName: string, functionName: string): string {
return path.join(functionAppPath, 'src', 'main', 'java', ...packageName.split('.'), `${getJavaClassName(functionName)}.java`);
}
export class JavaFunctionCreator implements IFunctionCreator {
private _outputChannel: OutputChannel;
private _packageName: string;
private _functionName: string;
constructor(outputChannel: OutputChannel) {
this._outputChannel = outputChannel;
}
public async promptForSettings(functionAppPath: string, template: Template, ui: IUserInterface): Promise<void> {
const packagePlaceHolder: string = localize('azFunc.java.packagePlaceHolder', 'Package');
const packagePrompt: string = localize('azFunc.java.packagePrompt', 'Provide a package name');
this._packageName = await ui.showInputBox(packagePlaceHolder, packagePrompt, false, validatePackageName, 'com.function');
const defaultFunctionName: string | undefined = await fsUtil.getUniqueJavaFsPath(functionAppPath, this._packageName, `${convertTemplateIdToJava(template.id)}Java`);
const placeHolder: string = localize('azFunc.funcNamePlaceholder', 'Function name');
const prompt: string = localize('azFunc.funcNamePrompt', 'Provide a function name');
this._functionName = await ui.showInputBox(placeHolder, prompt, false, (s: string) => this.validateTemplateName(s), defaultFunctionName || template.defaultFunctionName);
}
public async createFunction(functionAppPath: string, template: Template, userSettings: { [propertyName: string]: string }): Promise<string | undefined> {
const javaFuntionProperties: string[] = [];
for (const key of Object.keys(userSettings)) {
javaFuntionProperties.push(`"-D${key}=${userSettings[key]}"`);
}
await mavenUtils.validateMavenInstalled(functionAppPath);
this._outputChannel.show();
await cpUtils.executeCommand(
this._outputChannel,
functionAppPath,
'mvn',
'azure-functions:add',
'-B',
`"-Dfunctions.package=${this._packageName}"`,
`"-Dfunctions.name=${this._functionName}"`,
`"-Dfunctions.template=${convertTemplateIdToJava(template.id)}"`,
...javaFuntionProperties
);
return getNewJavaFunctionFilePath(functionAppPath, this._packageName, this._functionName);
}
private validateTemplateName(name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
} else {
return validateJavaFunctionName(name);
}
}
}

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

@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { IUserInterface } from "../../IUserInterface";
import { localize } from "../../localize";
import { ProjectLanguage } from '../../ProjectSettings';
import { Template } from "../../templates/Template";
import * as fsUtil from '../../utils/fs';
import { IFunctionCreator } from './IFunctionCreator';
const functionNameRegex: RegExp = /^[a-zA-Z][a-zA-Z\d_\-]*$/;
function getFileNameFromLanguage(language: string): string | undefined {
switch (language) {
case ProjectLanguage.Bash:
return 'run.sh';
case ProjectLanguage.Batch:
return 'run.bat';
case ProjectLanguage.FSharp:
return 'run.fsx';
case ProjectLanguage.JavaScript:
return 'index.js';
case ProjectLanguage.PHP:
return 'run.php';
case ProjectLanguage.PowerShell:
return 'run.ps1';
case ProjectLanguage.Python:
return 'run.py';
case ProjectLanguage.TypeScript:
return 'index.ts';
default:
return undefined;
}
}
/**
* Function creator for multiple languages that don't require compilation (JavaScript, C# Script, Bash, etc.)
*/
export class ScriptFunctionCreator implements IFunctionCreator {
private _language: string;
private _functionName: string;
constructor(language: string) {
this._language = language;
}
public async promptForSettings(functionAppPath: string, template: Template, ui: IUserInterface): Promise<void> {
const defaultFunctionName: string | undefined = await fsUtil.getUniqueFsPath(functionAppPath, template.defaultFunctionName);
const prompt: string = localize('azFunc.funcNamePrompt', 'Provide a function name');
const placeHolder: string = localize('azFunc.funcNamePlaceholder', 'Function name');
this._functionName = await ui.showInputBox(placeHolder, prompt, false, (s: string) => this.validateTemplateName(functionAppPath, s), defaultFunctionName || template.defaultFunctionName);
}
public async createFunction(functionAppPath: string, template: Template, userSettings: { [propertyName: string]: string; }): Promise<string | undefined> {
const functionPath: string = path.join(functionAppPath, this._functionName);
await fse.ensureDir(functionPath);
await Promise.all(Object.keys(template.templateFiles).map(async (fileName: string) => {
await fse.writeFile(path.join(functionPath, fileName), template.templateFiles[fileName]);
}));
for (const key of Object.keys(userSettings)) {
template.functionConfig.inBinding[key] = userSettings[key];
}
await fsUtil.writeFormattedJson(path.join(functionPath, 'function.json'), template.functionConfig.functionJson);
const mainFileName: string | undefined = getFileNameFromLanguage(this._language);
if (mainFileName) {
return path.join(functionPath, mainFileName);
} else {
return undefined;
}
}
private validateTemplateName(rootPath: string, name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
} else if (fse.existsSync(path.join(rootPath, name))) {
return localize('azFunc.existingFolderError', 'A folder with the name \'{0}\' already exists.', name);
} else if (!functionNameRegex.test(name)) {
return localize('azFunc.functionNameInvalidError', 'Function name must start with a letter and can contain letters, digits, \'_\' and \'-\'');
} else {
return undefined;
}
}
}

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

@ -7,25 +7,22 @@ import * as fse from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import { UserCancelledError } from 'vscode-azureextensionui';
import { AzureAccount } from '../azure-account.api';
import { DialogResponses } from '../DialogResponses';
import { IUserInterface, Pick, PickWithData } from '../IUserInterface';
import { LocalAppSettings } from '../LocalAppSettings';
import { localize } from '../localize';
import { getFileNameFromLanguage, getProjectLanguage, getProjectRuntime, getTemplateFilter, ProjectLanguage, ProjectRuntime, TemplateFilter } from '../ProjectSettings';
import { ConfigSetting, ValueType } from '../templates/ConfigSetting';
import { EnumValue } from '../templates/EnumValue';
import { Template } from '../templates/Template';
import { convertTemplateIdToJava, TemplateData } from '../templates/TemplateData';
import { cpUtils } from '../utils/cpUtils';
import * as fsUtil from '../utils/fs';
import { getJavaClassName, validateJavaFunctionName, validatePackageName } from '../utils/javaNameUtils';
import { mavenUtils } from '../utils/mavenUtils';
import * as workspaceUtil from '../utils/workspace';
import { VSCodeUI } from '../VSCodeUI';
import { createNewProject } from './createNewProject';
const functionNameRegex: RegExp = /^[a-zA-Z][a-zA-Z\d_\-]*$/;
import { AzureAccount } from '../../azure-account.api';
import { DialogResponses } from '../../DialogResponses';
import { IUserInterface, Pick, PickWithData } from '../../IUserInterface';
import { LocalAppSettings } from '../../LocalAppSettings';
import { localize } from '../../localize';
import { getProjectLanguage, getProjectRuntime, getTemplateFilter, ProjectLanguage, ProjectRuntime, TemplateFilter } from '../../ProjectSettings';
import { ConfigSetting, ValueType } from '../../templates/ConfigSetting';
import { EnumValue } from '../../templates/EnumValue';
import { Template } from '../../templates/Template';
import { TemplateData } from '../../templates/TemplateData';
import * as workspaceUtil from '../../utils/workspace';
import { VSCodeUI } from '../../VSCodeUI';
import { createNewProject } from '../createNewProject/createNewProject';
import { IFunctionCreator } from './IFunctionCreator';
import { JavaFunctionCreator } from './JavaFunctionCreator';
import { ScriptFunctionCreator } from './ScriptFunctionCreator';
const requiredFunctionAppFiles: string[] = [
'host.json',
@ -33,24 +30,6 @@ const requiredFunctionAppFiles: string[] = [
path.join('.vscode', 'launch.json') // NOTE: tasks.json is not required if the user prefers to run 'func host start' from the command line
];
function validateTemplateName(rootPath: string, name: string | undefined, language: string): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
}
if (language === ProjectLanguage.Java) {
return validateJavaFunctionName(name);
} else {
if (fse.existsSync(path.join(rootPath, name))) {
return localize('azFunc.existingFolderError', 'A folder with the name \'{0}\' already exists.', name);
}
if (!functionNameRegex.test(name)) {
return localize('azFunc.functionNameInvalidError', 'Function name must start with a letter and can contain letters, digits, \'_\' and \'-\'');
}
return undefined;
}
}
async function validateIsFunctionApp(telemetryProperties: { [key: string]: string; }, outputChannel: vscode.OutputChannel, functionAppPath: string, ui: IUserInterface): Promise<void> {
if (requiredFunctionAppFiles.find((file: string) => !fse.existsSync(path.join(functionAppPath, file))) !== undefined) {
const message: string = localize('azFunc.notFunctionApp', 'The selected folder is not a function app project. Initialize Project?');
@ -63,19 +42,6 @@ async function validateIsFunctionApp(telemetryProperties: { [key: string]: strin
}
}
async function promptForFunctionName(ui: IUserInterface, functionAppPath: string, template: Template, language: string, packageName: string): Promise<string> {
let defaultFunctionName: string | undefined;
if (language === ProjectLanguage.Java) {
defaultFunctionName = await fsUtil.getUniqueJavaFsPath(functionAppPath, packageName, `${convertTemplateIdToJava(template.id)}Java`);
} else {
defaultFunctionName = await fsUtil.getUniqueFsPath(functionAppPath, template.defaultFunctionName);
}
const prompt: string = localize('azFunc.funcNamePrompt', 'Provide a function name');
const placeHolder: string = localize('azFunc.funcNamePlaceholder', 'Function name');
return await ui.showInputBox(placeHolder, prompt, false, (s: string) => validateTemplateName(functionAppPath, s, language), defaultFunctionName || template.defaultFunctionName);
}
async function promptForSetting(ui: IUserInterface, localAppSettings: LocalAppSettings, setting: ConfigSetting, defaultValue?: string): Promise<string> {
if (setting.resourceType !== undefined) {
return await localAppSettings.promptForAppSetting(setting.resourceType);
@ -111,16 +77,6 @@ async function promptForStringSetting(ui: IUserInterface, setting: ConfigSetting
return await ui.showInputBox(setting.label, prompt, false, (s: string) => setting.validateSetting(s), defaultValue);
}
async function promptForPackageName(ui: IUserInterface): Promise<string> {
const packagePlaceHolder: string = localize('azFunc.java.packagePlaceHolder', 'Package');
const packagePrompt: string = localize('azFunc.java.packagePrompt', 'Provide a package name');
return await ui.showInputBox(packagePlaceHolder, packagePrompt, false, validatePackageName, 'com.function');
}
function getNewJavaFunctionFilePath(functionAppPath: string, packageName: string, functionName: string): string {
return path.join(functionAppPath, 'src', 'main', 'java', ...packageName.split('.'), `${getJavaClassName(functionName)}.java`);
}
export async function createFunction(
telemetryProperties: { [key: string]: string; },
outputChannel: vscode.OutputChannel,
@ -150,49 +106,29 @@ export async function createFunction(
await localAppSettings.validateAzureWebJobsStorage();
}
const packageName: string = language === ProjectLanguage.Java ? await promptForPackageName(ui) : '';
let functionCreator: IFunctionCreator;
switch (language) {
case ProjectLanguage.Java:
functionCreator = new JavaFunctionCreator(outputChannel);
break;
default:
functionCreator = new ScriptFunctionCreator(language);
break;
}
const name: string = await promptForFunctionName(ui, functionAppPath, template, language, packageName);
const javaFuntionProperties: string[] = [];
await functionCreator.promptForSettings(functionAppPath, template, ui);
const userSettings: { [propertyName: string]: string } = {};
for (const settingName of template.userPromptedSettings) {
const setting: ConfigSetting | undefined = await templateData.getSetting(runtime, template.functionConfig.inBindingType, settingName);
if (setting) {
const defaultValue: string | undefined = template.functionConfig.inBinding[settingName];
const settingValue: string | undefined = await promptForSetting(ui, localAppSettings, setting, defaultValue);
if (language === ProjectLanguage.Java) {
javaFuntionProperties.push(`"-D${settingName}=${settingValue}"`);
} else {
template.functionConfig.inBinding[settingName] = settingValue ? settingValue : '';
}
}
}
let newFilePath: string | undefined;
if (language === ProjectLanguage.Java) {
await mavenUtils.validateMavenInstalled(functionAppPath);
outputChannel.show();
await cpUtils.executeCommand(
outputChannel,
functionAppPath,
'mvn',
'azure-functions:add',
'-B',
`"-Dfunctions.package=${packageName}"`,
`"-Dfunctions.name=${name}"`,
`"-Dfunctions.template=${convertTemplateIdToJava(template.id)}"`,
...javaFuntionProperties
);
newFilePath = getNewJavaFunctionFilePath(functionAppPath, packageName, name);
} else {
const functionPath: string = path.join(functionAppPath, name);
await template.writeTemplateFiles(functionPath);
const fileName: string | undefined = getFileNameFromLanguage(language);
if (fileName) {
newFilePath = path.join(functionPath, fileName);
userSettings[settingName] = settingValue ? settingValue : '';
}
}
const newFilePath: string | undefined = await functionCreator.createFunction(functionAppPath, template, userSettings);
if (newFilePath && (await fse.pathExists(newFilePath))) {
const newFileUri: vscode.Uri = vscode.Uri.file(newFilePath);
vscode.window.showTextDocument(await vscode.workspace.openTextDocument(newFileUri));

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

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProjectRuntime } from "../../ProjectSettings";
export interface IProjectCreator {
/**
* Add all project files not included in the '.vscode' folder
*/
addNonVSCodeFiles(functionAppPath: string): Promise<void>;
getTasksJson(launchTaskId: string, funcProblemMatcher: {}): {};
getLaunchJson(launchTaskId: string): {};
getRuntime(): ProjectRuntime;
}

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

@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { OutputChannel } from 'vscode';
import { IUserInterface } from '../../IUserInterface';
import { localize } from "../../localize";
import { ProjectRuntime } from '../../ProjectSettings';
import { cpUtils } from '../../utils/cpUtils';
import * as fsUtil from '../../utils/fs';
import { validateMavenIdentifier, validatePackageName } from '../../utils/javaNameUtils';
import { mavenUtils } from '../../utils/mavenUtils';
import { IProjectCreator } from './IProjectCreator';
export class JavaProjectCreator implements IProjectCreator {
private _javaTargetPath: string;
private _outputChannel: OutputChannel;
private _ui: IUserInterface;
constructor(outputChannel: OutputChannel, ui: IUserInterface) {
this._outputChannel = outputChannel;
this._ui = ui;
}
public async addNonVSCodeFiles(functionAppPath: string): Promise<void> {
await mavenUtils.validateMavenInstalled(functionAppPath);
const groupIdPlaceHolder: string = localize('azFunc.java.groupIdPlaceholder', 'Group ID');
const groupIdPrompt: string = localize('azFunc.java.groupIdPrompt', 'Provide value for groupId');
const groupId: string = await this._ui.showInputBox(groupIdPlaceHolder, groupIdPrompt, false, validateMavenIdentifier, 'com.function');
const artifactIdPlaceHolder: string = localize('azFunc.java.artifactIdPlaceholder', 'Artifact ID');
const artifactIdprompt: string = localize('azFunc.java.artifactIdPrompt', 'Provide value for artifactId');
const artifactId: string = await this._ui.showInputBox(artifactIdPlaceHolder, artifactIdprompt, false, validateMavenIdentifier, path.basename(functionAppPath));
const versionPlaceHolder: string = localize('azFunc.java.versionPlaceHolder', 'Version');
const versionPrompt: string = localize('azFunc.java.versionPrompt', 'Provide value for version');
const version: string = await this._ui.showInputBox(versionPlaceHolder, versionPrompt, false, undefined, '1.0-SNAPSHOT');
const packagePlaceHolder: string = localize('azFunc.java.packagePlaceHolder', 'Package');
const packagePrompt: string = localize('azFunc.java.packagePrompt', 'Provide value for package');
const packageName: string = await this._ui.showInputBox(packagePlaceHolder, packagePrompt, false, validatePackageName, groupId);
const appNamePlaceHolder: string = localize('azFunc.java.appNamePlaceHolder', 'App Name');
const appNamePrompt: string = localize('azFunc.java.appNamePrompt', 'Provide value for appName');
const appName: string = await this._ui.showInputBox(appNamePlaceHolder, appNamePrompt, false, undefined, `${artifactId}-${Date.now()}`);
const tempFolder: string = path.join(os.tmpdir(), fsUtil.getRandomHexString());
await fse.ensureDir(tempFolder);
try {
// Use maven command to init Java function project.
this._outputChannel.show();
await cpUtils.executeCommand(
this._outputChannel,
tempFolder,
'mvn',
'archetype:generate',
'-DarchetypeGroupId="com.microsoft.azure"',
'-DarchetypeArtifactId="azure-functions-archetype"',
`-DgroupId="${groupId}"`,
`-DartifactId="${artifactId}"`,
`-Dversion="${version}"`,
`-Dpackage="${packageName}"`,
`-DappName="${appName}"`,
'-B' // in Batch Mode
);
await fsUtil.copyFolder(path.join(tempFolder, artifactId), functionAppPath);
} finally {
await fse.remove(tempFolder);
}
this._javaTargetPath = `target/azure-functions/${appName}/`;
}
public getRuntime(): ProjectRuntime {
return ProjectRuntime.beta;
}
public getTasksJson(launchTaskId: string, funcProblemMatcher: {}): {} {
let tasksJson: {} = {
version: '2.0.0',
tasks: [
{
label: localize('azFunc.launchFuncApp', 'Launch Function App'),
identifier: launchTaskId,
linux: {
command: 'sh -c "mvn clean package -B && func host start --script-root \\\"%path%\\\""'
},
osx: {
command: 'sh -c "mvn clean package -B && func host start --script-root \\\"%path%\\\""'
},
windows: {
command: 'powershell -command "mvn clean package -B; func host start --script-root \\\"%path%\\\""'
},
type: 'shell',
isBackground: true,
presentation: {
reveal: 'always'
},
problemMatcher: [
funcProblemMatcher
]
}
]
};
let tasksJsonString: string = JSON.stringify(tasksJson);
tasksJsonString = tasksJsonString.replace(/%path%/g, this._javaTargetPath);
// tslint:disable-next-line:no-string-literal no-unsafe-any
tasksJson = JSON.parse(tasksJsonString);
// tslint:disable-next-line:no-string-literal no-unsafe-any
tasksJson['tasks'][0]['problemMatcher'][0]['background']['beginsPattern'] = '^.*Scanning for projects.*';
return tasksJson;
}
public getLaunchJson(launchTaskId: string): {} {
return {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToJavaFunc', 'Attach to Java Functions'),
type: 'java',
request: 'attach',
hostName: 'localhost',
port: 5005,
preLaunchTask: launchTaskId
}
]
};
}
}

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

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from "../../localize";
import { ProjectRuntime } from "../../ProjectSettings";
import { ScriptProjectCreatorBase } from './ScriptProjectCreatorBase';
export class JavaScriptProjectCreator extends ScriptProjectCreatorBase {
public getRuntime(): ProjectRuntime {
return ProjectRuntime.one;
}
public getTasksJson(launchTaskId: string, funcProblemMatcher: {}): {} {
return {
version: '2.0.0',
tasks: [
{
label: localize('azFunc.launchFuncApp', 'Launch Function App'),
identifier: launchTaskId,
type: 'shell',
command: 'func host start',
isBackground: true,
presentation: {
reveal: 'always'
},
problemMatcher: [
funcProblemMatcher
]
}
]
};
}
public getLaunchJson(launchTaskId: string): {} {
return {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToJavaScriptFunc', 'Attach to JavaScript Functions'),
type: 'node',
request: 'attach',
port: 5858,
protocol: 'inspector',
preLaunchTask: launchTaskId
}
]
};
}
}

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

@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { ProjectRuntime } from '../../ProjectSettings';
import { confirmOverwriteFile } from "../../utils/fs";
import * as fsUtil from '../../utils/fs';
import { IProjectCreator } from './IProjectCreator';
// tslint:disable-next-line:no-multiline-string
const gitignore: string = `bin
obj
csx
.vs
edge
Publish
.vscode
*.user
*.suo
*.cscfg
*.Cache
project.lock.json
/packages
/TestResults
/tools/NuGet.exe
/App_Data
/secrets
/data
.secrets
appsettings.json
local.settings.json
`;
/**
* Base class for all projects based on a simple script (i.e. JavaScript, C# Script, Bash, etc.) that don't require compilation
*/
export abstract class ScriptProjectCreatorBase implements IProjectCreator {
public abstract getTasksJson(launchTaskId: string, funcProblemMatcher: {}): {};
public abstract getLaunchJson(launchTaskId: string): {};
public abstract getRuntime(): ProjectRuntime;
public async addNonVSCodeFiles(functionAppPath: string): Promise<void> {
const hostJsonPath: string = path.join(functionAppPath, 'host.json');
if (await confirmOverwriteFile(hostJsonPath)) {
const hostJson: {} = {};
await fsUtil.writeFormattedJson(hostJsonPath, hostJson);
}
const localSettingsJsonPath: string = path.join(functionAppPath, 'local.settings.json');
if (await confirmOverwriteFile(localSettingsJsonPath)) {
const localSettingsJson: {} = {
IsEncrypted: false,
Values: {
AzureWebJobsStorage: ''
}
};
await fsUtil.writeFormattedJson(localSettingsJsonPath, localSettingsJson);
}
const gitignorePath: string = path.join(functionAppPath, '.gitignore');
if (await confirmOverwriteFile(gitignorePath)) {
await fse.writeFile(gitignorePath, gitignore);
}
}
}

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

@ -4,25 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { OutputChannel } from 'vscode';
import { IUserInterface, Pick } from '../IUserInterface';
import { localize } from '../localize';
import { extensionPrefix, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, TemplateFilter, templateFilterSetting } from '../ProjectSettings';
import { cpUtils } from '../utils/cpUtils';
import * as fsUtil from '../utils/fs';
import { confirmOverwriteFile } from '../utils/fs';
import { gitUtils } from '../utils/gitUtils';
import { validateMavenIdentifier, validatePackageName } from '../utils/javaNameUtils';
import { mavenUtils } from '../utils/mavenUtils';
import * as workspaceUtil from '../utils/workspace';
import { VSCodeUI } from '../VSCodeUI';
import { IUserInterface, Pick } from '../../IUserInterface';
import { localize } from '../../localize';
import { extensionPrefix, ProjectLanguage, projectLanguageSetting, projectRuntimeSetting, TemplateFilter, templateFilterSetting } from '../../ProjectSettings';
import * as fsUtil from '../../utils/fs';
import { confirmOverwriteFile } from '../../utils/fs';
import { gitUtils } from '../../utils/gitUtils';
import * as workspaceUtil from '../../utils/workspace';
import { VSCodeUI } from '../../VSCodeUI';
import { IProjectCreator } from './IProjectCreator';
import { JavaProjectCreator } from './JavaProjectCreator';
import { JavaScriptProjectCreator } from './JavaScriptProjectCreator';
const taskId: string = 'launchFunctionApp';
const launchTaskId: string = 'launchFunctionApp';
const problemMatcher: {} = {
const funcProblemMatcher: {} = {
owner: extensionPrefix,
pattern: [
{
@ -39,174 +38,6 @@ const problemMatcher: {} = {
}
};
const defaultTasksJson: {} = {
version: '2.0.0',
tasks: [
{
label: localize('azFunc.launchFuncApp', 'Launch Function App'),
identifier: taskId,
type: 'shell',
command: 'func host start',
isBackground: true,
presentation: {
reveal: 'always'
},
problemMatcher: [
problemMatcher
]
}
]
};
const tasksJsonForJava: {} = {
version: '2.0.0',
tasks: [
{
label: localize('azFunc.launchFuncApp', 'Launch Function App'),
identifier: taskId,
linux: {
command: 'sh -c "mvn clean package -B && func host start --script-root \\\"%path%\\\""'
},
osx: {
command: 'sh -c "mvn clean package -B && func host start --script-root \\\"%path%\\\""'
},
windows: {
command: 'powershell -command "mvn clean package -B; func host start --script-root \\\"%path%\\\""'
},
type: 'shell',
isBackground: true,
presentation: {
reveal: 'always'
},
problemMatcher: [
problemMatcher
]
}
]
};
const launchJsonForJavaScript: {} = {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToJavaScriptFunc', 'Attach to JavaScript Functions'),
type: 'node',
request: 'attach',
port: 5858,
protocol: 'inspector',
preLaunchTask: taskId
}
]
};
const launchJsonForJava: {} = {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToJavaFunc', 'Attach to Java Functions'),
type: 'java',
request: 'attach',
hostName: 'localhost',
port: 5005,
preLaunchTask: taskId
}
]
};
// tslint:disable-next-line:no-multiline-string
const gitignore: string = `bin
obj
csx
.vs
edge
Publish
.vscode
*.user
*.suo
*.cscfg
*.Cache
project.lock.json
/packages
/TestResults
/tools/NuGet.exe
/App_Data
/secrets
/data
.secrets
appsettings.json
local.settings.json
`;
const hostJson: {} = {};
const localSettingsJson: {} = {
IsEncrypted: false,
Values: {
AzureWebJobsStorage: ''
}
};
async function promotForMavenParameters(ui: IUserInterface, functionAppPath: string): Promise<IMavenParameters> {
const groupIdPlaceHolder: string = localize('azFunc.java.groupIdPlaceholder', 'Group ID');
const groupIdPrompt: string = localize('azFunc.java.groupIdPrompt', 'Provide value for groupId');
const groupId: string = await ui.showInputBox(groupIdPlaceHolder, groupIdPrompt, false, validateMavenIdentifier, 'com.function');
const artifactIdPlaceHolder: string = localize('azFunc.java.artifactIdPlaceholder', 'Artifact ID');
const artifactIdprompt: string = localize('azFunc.java.artifactIdPrompt', 'Provide value for artifactId');
const artifactId: string = await ui.showInputBox(artifactIdPlaceHolder, artifactIdprompt, false, validateMavenIdentifier, path.basename(functionAppPath));
const versionPlaceHolder: string = localize('azFunc.java.versionPlaceHolder', 'Version');
const versionPrompt: string = localize('azFunc.java.versionPrompt', 'Provide value for version');
const version: string = await ui.showInputBox(versionPlaceHolder, versionPrompt, false, undefined, '1.0-SNAPSHOT');
const packagePlaceHolder: string = localize('azFunc.java.packagePlaceHolder', 'Package');
const packagePrompt: string = localize('azFunc.java.packagePrompt', 'Provide value for package');
const packageName: string = await ui.showInputBox(packagePlaceHolder, packagePrompt, false, validatePackageName, groupId);
const appNamePlaceHolder: string = localize('azFunc.java.appNamePlaceHolder', 'App Name');
const appNamePrompt: string = localize('azFunc.java.appNamePrompt', 'Provide value for appName');
const appName: string = await ui.showInputBox(appNamePlaceHolder, appNamePrompt, false, undefined, `${artifactId}-${Date.now()}`);
return {
groupId: groupId,
artifactId: artifactId,
version: version,
packageName: packageName,
appName: appName
};
}
async function createJavaFunctionProject(outputChannel: OutputChannel, functionAppPath: string, ui: IUserInterface): Promise<string> {
await mavenUtils.validateMavenInstalled(functionAppPath);
// Get parameters for Maven command
const { groupId, artifactId, version, packageName, appName } = await promotForMavenParameters(ui, functionAppPath);
const tempFolder: string = path.join(os.tmpdir(), fsUtil.getRandomHexString());
await fse.ensureDir(tempFolder);
// Use maven command to init Java function project.
outputChannel.show();
await cpUtils.executeCommand(
outputChannel,
tempFolder,
'mvn',
'archetype:generate',
'-DarchetypeGroupId="com.microsoft.azure"',
'-DarchetypeArtifactId="azure-functions-archetype"',
`-DgroupId="${groupId}"`,
`-DartifactId="${artifactId}"`,
`-Dversion="${version}"`,
`-Dpackage="${packageName}"`,
`-DappName="${appName}"`,
'-B' // in Batch Mode
);
await fsUtil.copyFolder(path.join(tempFolder, artifactId), functionAppPath);
await fse.remove(tempFolder);
return appName;
}
// tslint:disable-next-line:max-func-body-length
export async function createNewProject(telemetryProperties: { [key: string]: string; }, outputChannel: OutputChannel, functionAppPath?: string, openFolder: boolean = true, ui: IUserInterface = new VSCodeUI()): Promise<void> {
if (functionAppPath === undefined) {
functionAppPath = await workspaceUtil.selectWorkspaceFolder(ui, localize('azFunc.selectFunctionAppFolderNew', 'Select the folder that will contain your function app'));
@ -220,81 +51,41 @@ export async function createNewProject(telemetryProperties: { [key: string]: str
const language: string = (await ui.showQuickPick(languagePicks, localize('azFunc.selectFuncTemplate', 'Select a language for your function project'))).label;
telemetryProperties.projectLanguage = language;
let javaTargetPath: string = '';
let projectCreator: IProjectCreator;
switch (language) {
case ProjectLanguage.Java:
const javaFunctionAppName: string = await createJavaFunctionProject(outputChannel, functionAppPath, ui);
javaTargetPath = `target/azure-functions/${javaFunctionAppName}/`;
projectCreator = new JavaProjectCreator(outputChannel, ui);
break;
case ProjectLanguage.JavaScript:
projectCreator = new JavaScriptProjectCreator();
break;
default:
// the maven archetype contains these files, so not check them when language is Java
const hostJsonPath: string = path.join(functionAppPath, 'host.json');
if (await confirmOverwriteFile(hostJsonPath)) {
await fsUtil.writeFormattedJson(hostJsonPath, hostJson);
}
const localSettingsJsonPath: string = path.join(functionAppPath, 'local.settings.json');
if (await confirmOverwriteFile(localSettingsJsonPath)) {
await fsUtil.writeFormattedJson(localSettingsJsonPath, localSettingsJson);
}
break;
throw new Error(localize('unrecognizedLanguage', 'Unrecognized language "{0}"', language));
}
await projectCreator.addNonVSCodeFiles(functionAppPath);
const vscodePath: string = path.join(functionAppPath, '.vscode');
await fse.ensureDir(vscodePath);
if (await gitUtils.isGitInstalled(functionAppPath)) {
await gitUtils.gitInit(outputChannel, functionAppPath);
const gitignorePath: string = path.join(functionAppPath, '.gitignore');
if (language !== ProjectLanguage.Java && await confirmOverwriteFile(gitignorePath)) {
await fse.writeFile(gitignorePath, gitignore);
}
}
const tasksJsonPath: string = path.join(vscodePath, 'tasks.json');
if (await confirmOverwriteFile(tasksJsonPath)) {
switch (language) {
case ProjectLanguage.Java:
let tasksJsonString: string = JSON.stringify(tasksJsonForJava);
tasksJsonString = tasksJsonString.replace(/%path%/g, javaTargetPath);
// tslint:disable-next-line:no-string-literal no-unsafe-any
const tasksJson: {} = JSON.parse(tasksJsonString);
// tslint:disable-next-line:no-string-literal no-unsafe-any
tasksJson['tasks'][0]['problemMatcher'][0]['background']['beginsPattern'] = '^.*Scanning for projects.*';
await fsUtil.writeFormattedJson(tasksJsonPath, tasksJson);
break;
default:
await fsUtil.writeFormattedJson(tasksJsonPath, defaultTasksJson);
break;
}
await fsUtil.writeFormattedJson(tasksJsonPath, projectCreator.getTasksJson(launchTaskId, funcProblemMatcher));
}
const launchJsonPath: string = path.join(vscodePath, 'launch.json');
if (await confirmOverwriteFile(launchJsonPath)) {
switch (language) {
case ProjectLanguage.Java:
await fsUtil.writeFormattedJson(launchJsonPath, launchJsonForJava);
break;
default:
await fsUtil.writeFormattedJson(launchJsonPath, launchJsonForJavaScript);
break;
}
await fsUtil.writeFormattedJson(launchJsonPath, projectCreator.getLaunchJson(launchTaskId));
}
const settingsJsonPath: string = path.join(vscodePath, 'settings.json');
if (await confirmOverwriteFile(settingsJsonPath)) {
let runtime: ProjectRuntime;
switch (language) {
case ProjectLanguage.Java:
runtime = ProjectRuntime.beta;
break;
default:
runtime = ProjectRuntime.one;
break;
}
const settings: {} = {};
settings[`${extensionPrefix}.${projectRuntimeSetting}`] = runtime;
settings[`${extensionPrefix}.${projectRuntimeSetting}`] = projectCreator.getRuntime();
settings[`${extensionPrefix}.${projectLanguageSetting}`] = language;
settings[`${extensionPrefix}.${templateFilterSetting}`] = TemplateFilter.Verified;
await fsUtil.writeFormattedJson(settingsJsonPath, settings);
@ -305,11 +96,3 @@ export async function createNewProject(telemetryProperties: { [key: string]: str
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(functionAppPath), false);
}
}
interface IMavenParameters {
groupId: string;
artifactId: string;
version: string;
packageName: string;
appName: string;
}

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

@ -13,8 +13,8 @@ import { AzureAccount } from './azure-account.api';
import { configureDeploymentSource } from './commands/configureDeploymentSource';
import { copyFunctionUrl } from './commands/copyFunctionUrl';
import { createChildNode } from './commands/createChildNode';
import { createFunction } from './commands/createFunction';
import { createNewProject } from './commands/createNewProject';
import { createFunction } from './commands/createFunction/createFunction';
import { createNewProject } from './commands/createNewProject/createNewProject';
import { deleteNode } from './commands/deleteNode';
import { deploy } from './commands/deploy';
import { editAppSetting } from './commands/editAppSetting';

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

@ -3,11 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import * as path from 'path';
import { FunctionConfig } from '../FunctionConfig';
import { ProjectLanguage } from '../ProjectSettings';
import * as fsUtil from '../utils/fs';
import { Resources } from './Resources';
interface ITemplate {
@ -65,14 +62,7 @@ export class Template {
return this._template.metadata.userPrompt ? this._template.metadata.userPrompt : [];
}
public async writeTemplateFiles(functionPath: string): Promise<void> {
await fse.ensureDir(functionPath);
const tasks: Promise<void>[] = Object.keys(this._template.files).map(async (fileName: string) => {
await fse.writeFile(path.join(functionPath, fileName), this._template.files[fileName]);
});
tasks.push(fsUtil.writeFormattedJson(path.join(functionPath, 'function.json'), this._template.function));
await Promise.all(tasks);
public get templateFiles(): { [filename: string]: string } {
return this._template.files;
}
}

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

@ -9,7 +9,7 @@ import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { WorkspaceConfiguration } from 'vscode';
import { createFunction } from '../src/commands/createFunction';
import { createFunction } from '../src/commands/createFunction/createFunction';
import { extensionPrefix, ProjectLanguage, projectLanguageSetting, ProjectRuntime, projectRuntimeSetting, TemplateFilter, templateFilterSetting } from '../src/ProjectSettings';
import { TemplateData } from '../src/templates/TemplateData';
import * as fsUtil from '../src/utils/fs';

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

@ -8,7 +8,7 @@ import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { createNewProject } from '../src/commands/createNewProject';
import { createNewProject } from '../src/commands/createNewProject/createNewProject';
import { ProjectLanguage } from '../src/ProjectSettings';
import * as fsUtil from '../src/utils/fs';
import { TestUI } from './TestUI';