Skip to content

Commit

Permalink
Support create java function project (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdneo authored and ejizba committed Nov 15, 2017
1 parent bb90c27 commit 28b0620
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 32 deletions.
201 changes: 170 additions & 31 deletions src/commands/createNewProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,36 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { OutputChannel } from 'vscode';
import { IUserInterface } from '../IUserInterface';
import { Pick } from '../IUserInterface';
import { localize } from '../localize';
import { TemplateLanguage } from '../templates/Template';
import { cpUtils } from '../utils/cpUtils';
import { confirmOverwriteFile } from '../utils/fs';
import * as fsUtil from '../utils/fs';
import { gitUtils } from '../utils/gitUtils';
import * as workspaceUtil from '../utils/workspace';
import { VSCodeUI } from '../VSCodeUI';

const taskId: string = 'launchFunctionApp';
const tasksJson: {} = {

const problemMatcher: {} = {
owner: 'azureFunctions',
pattern: [
{
regexp: '\\b\\B',
file: 1,
location: 2,
message: 3
}
],
background: {
activeOnStart: true,
beginsPattern: '^.*Stopping host.*',
endsPattern: '^.*Job host started.*'
}
};

const tasksJsonForJavaScript: {} = {
version: '2.0.0',
tasks: [
{
Expand All @@ -29,28 +50,40 @@ const tasksJson: {} = {
reveal: 'always'
},
problemMatcher: [
{
owner: 'azureFunctions',
pattern: [
{
regexp: '\\b\\B',
file: 1,
location: 2,
message: 3
}
],
background: {
activeOnStart: true,
beginsPattern: '^.*Stopping host.*',
endsPattern: '^.*Job host started.*'
}
}
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 mvn clean package -B; func host start --script-root %path%'
},
type: 'shell',
isBackground: true,
presentation: {
reveal: 'always'
},
problemMatcher: [
problemMatcher
]
}
]
};

const launchJson: {} = {
const launchJsonForJavaScript: {} = {
version: '0.2.0',
configurations: [
{
Expand All @@ -64,6 +97,20 @@ const launchJson: {} = {
]
};

const launchJsonForJava: {} = {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToFunc', 'Attach to Azure Functions'),
type: 'java',
request: 'attach',
hostName: 'localhost',
port: 5005,
preLaunchTask: taskId
}
]
};

// tslint:disable-next-line:no-multiline-string
const gitignore: string = `bin
obj
Expand Down Expand Up @@ -100,45 +147,137 @@ const localSettingsJson: {} = {
}
};

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, undefined, '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, undefined, 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, undefined, 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
};
}

export async function createNewProject(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'));
}

const languages: Pick[] = [
new Pick(TemplateLanguage.JavaScript),
new Pick(TemplateLanguage.Java)
];
const language: string = (await ui.showQuickPick(languages, localize('azFunc.selectFuncTemplate', 'Select a language for your function project'))).label;

let javaTargetPath: string = '';
switch (language) {
case TemplateLanguage.Java:
// Get parameters for Maven command
const { groupId, artifactId, version, packageName, appName } = await promotForMavenParameters(ui, functionAppPath);
// Use maven command to init Java function project.
await cpUtils.executeCommand(
outputChannel,
functionAppPath,
'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
);

functionAppPath = path.join(functionAppPath, artifactId);
javaTargetPath = `target/azure-functions/${appName}/`;
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;
}

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 (await confirmOverwriteFile(gitignorePath)) {
if (language !== TemplateLanguage.Java && await confirmOverwriteFile(gitignorePath)) {
await fse.writeFile(gitignorePath, gitignore);
}
}

const tasksJsonPath: string = path.join(vscodePath, 'tasks.json');
if (await confirmOverwriteFile(tasksJsonPath)) {
await fsUtil.writeFormattedJson(tasksJsonPath, tasksJson);
switch (language) {
case TemplateLanguage.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, tasksJsonForJavaScript);
break;
}
}

const launchJsonPath: string = path.join(vscodePath, 'launch.json');
if (await confirmOverwriteFile(launchJsonPath)) {
await fsUtil.writeFormattedJson(launchJsonPath, launchJson);
}

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);
switch (language) {
case TemplateLanguage.Java:
await fsUtil.writeFormattedJson(launchJsonPath, launchJsonForJava);
break;
default:
await fsUtil.writeFormattedJson(launchJsonPath, launchJsonForJavaScript);
break;
}
}

if (openFolder && !workspaceUtil.isFolderOpenInWorkspace(functionAppPath)) {
// If the selected folder is not open in a workspace, open it now. NOTE: This may restart the extension host
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(functionAppPath), false);
}
}

interface IMavenParameters {
groupId: string;
artifactId: string;
version: string;
packageName: string;
appName: string;
}
3 changes: 2 additions & 1 deletion src/templates/Template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ interface ITemplateMetadata {
}

export enum TemplateLanguage {
JavaScript = 'JavaScript'
JavaScript = 'JavaScript',
Java = 'Java'
}

export enum TemplateCategory {
Expand Down
87 changes: 87 additions & 0 deletions test/createNewProject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
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 { TemplateLanguage } from '../src/templates/Template';
import * as fsUtil from '../src/utils/fs';
import { TestUI } from './TestUI';

const testFolderName: string = `azFunc.createNewProjectTests${fsUtil.getRandomHexString()}`;
const testFolderPath: string = path.join(os.tmpdir(), testFolderName);
const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel('Azure Functions Test');

suite('Create New Java Project Tests', () => {

suiteSetup(async () => {
await fse.ensureDir(testFolderPath);
});

suiteTeardown(async () => {
outputChannel.dispose();
await fse.remove(testFolderPath);
});

const javaProject: string = 'JavaProject';
test(javaProject, async () => {
await testCreateNewProject(
TemplateLanguage.Java,
undefined,
undefined,
undefined,
undefined,
undefined
);
await testJavaProjectFilesExist(path.join(testFolderPath, testFolderName));
}).timeout(60 * 1000);
});

suite('Create New JavaScript Project Tests', () => {

suiteSetup(async () => {
await fse.ensureDir(testFolderPath);
});

suiteTeardown(async () => {
outputChannel.dispose();
await fse.remove(testFolderPath);
});

const javaScriptProject: string = 'JavaScriptProject';
test(javaScriptProject, async () => {
await testCreateNewProject(TemplateLanguage.JavaScript);
await testProjectFilesExist(testFolderPath);
}).timeout(10 * 1000);
});

async function testCreateNewProject(language: string, ...inputs: (string | undefined)[]): Promise<void> {
// Setup common inputs
inputs.unshift(language); // Specify the function name
inputs.unshift(testFolderPath); // Select the test func app folder
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
inputs.unshift(undefined); // If the test environment has an open workspace, select the 'Browse...' option
}

const ui: TestUI = new TestUI(inputs);
await createNewProject(outputChannel, undefined, false, ui);
assert.equal(inputs.length, 0, 'Not all inputs were used.');
}

async function testProjectFilesExist(projectPath: string): Promise<void> {
assert.equal(await fse.pathExists(path.join(projectPath, '.gitignore')), true, '.gitignore does not exist');
assert.equal(await fse.pathExists(path.join(projectPath, 'host.json')), true, 'host.json does not exist');
assert.equal(await fse.pathExists(path.join(projectPath, 'local.settings.json')), true, 'function.json does not exist');
assert.equal(await fse.pathExists(path.join(projectPath, '.git')), true, '.git folder does not exist');
}

async function testJavaProjectFilesExist(projectPath: string): Promise<void> {
await testProjectFilesExist(projectPath);
assert.equal(await fse.pathExists(path.join(projectPath, 'src')), true, 'src folder does not exist');
assert.equal(await fse.pathExists(path.join(projectPath, 'pom.xml')), true, 'pom.xml does not exist');
}

0 comments on commit 28b0620

Please sign in to comment.