Skip to content

Commit

Permalink
Remove reliance on func cli for 'Create new project' (#63)
Browse files Browse the repository at this point in the history
Benefits:
1. User can now create project and function without func cli (they only need the cli for debugging)
1. We can now prompt the user to overwrite existing files
1. We can automatically detect if we should git init the folder
  • Loading branch information
ejizba authored Nov 10, 2017
1 parent 8239b0b commit 8cdb675
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 136 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Azure Functions for Visual Studio Code (Preview)

## Prerequisites
## Prerequisites for local debugging
* [Node v8.0+](https://nodejs.org/)
* Older versions of node will be supported soon. See issue [#1](https://github.com/Microsoft/vscode-azurefunctions/issues/1)
* [.NET Core 2.0](https://www.microsoft.com/net/download/core)
Expand Down
12 changes: 12 additions & 0 deletions src/DialogResponses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* 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';

export namespace DialogResponses {
export const skipForNow: string = localize('azFunc.SkipForNow', 'Skip for now');
export const yes: string = localize('azFunc.Yes', 'Yes');
export const no: string = localize('azFunc.No', 'No');
}
4 changes: 2 additions & 2 deletions src/LocalAppSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as fse from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import { AzureAccount } from './azure-account.api';
import { DialogResponses } from './DialogResponses';
import { NoSubscriptionError, UserCancelledError } from './errors';
import { IUserInterface, PickWithData } from "./IUserInterface";
import { localize } from './localize';
Expand Down Expand Up @@ -127,8 +128,7 @@ export class LocalAppSettings {

if (settings.Values[key]) {
const message: string = localize('azFunc.SettingAlreadyExists', 'Local app setting \'{0}\' already exists. Overwrite?', key);
const yes: string = localize('azFunc.yes', 'Yes');
if (await vscode.window.showWarningMessage(message, yes) !== yes) {
if (await vscode.window.showWarningMessage(message, DialogResponses.yes) !== DialogResponses.yes) {
return;
}
}
Expand Down
30 changes: 12 additions & 18 deletions src/commands/createFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import * as fse from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import { AzureAccount } from '../azure-account.api';
import { DialogResponses } from '../DialogResponses';
import * as errors from '../errors';
import * as FunctionsCli from '../functions-cli';
import { IUserInterface, Pick, PickWithData } from '../IUserInterface';
import { LocalAppSettings } from '../LocalAppSettings';
import { localize } from '../localize';
Expand All @@ -19,17 +19,14 @@ import { TemplateData } from '../templates/TemplateData';
import * as fsUtil from '../utils/fs';
import * as workspaceUtil from '../utils/workspace';
import { VSCodeUI } from '../VSCodeUI';
import { createNewProject } from './createNewProject';

const expectedFunctionAppFiles: string[] = [
const requiredFunctionAppFiles: string[] = [
'host.json',
'local.settings.json',
path.join('.vscode', 'launch.json')
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 getMissingFunctionAppFiles(rootPath: string): string[] {
return expectedFunctionAppFiles.filter((file: string) => !fse.existsSync(path.join(rootPath, file)));
}

function validateTemplateName(rootPath: string, name: string | undefined): string | undefined {
if (!name) {
return localize('azFunc.emptyTemplateNameError', 'The template name cannot be empty.');
Expand All @@ -40,16 +37,13 @@ function validateTemplateName(rootPath: string, name: string | undefined): strin
}
}

async function validateIsFunctionApp(outputChannel: vscode.OutputChannel, functionAppPath: string): Promise<void> {
const missingFiles: string[] = getMissingFunctionAppFiles(functionAppPath);
if (missingFiles.length !== 0) {
const yes: string = localize('azFunc.yes', 'Yes');
const no: string = localize('azFunc.no', 'No');
const message: string = localize('azFunc.missingFuncAppFiles', 'The current folder is missing the following function app files: \'{0}\'. Add the missing files?', missingFiles.join(','));
const result: string | undefined = await vscode.window.showWarningMessage(message, yes, no);
if (result === yes) {
await FunctionsCli.createNewProject(outputChannel, functionAppPath);
} else {
async function validateIsFunctionApp(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?');
const result: string | undefined = await vscode.window.showWarningMessage(message, DialogResponses.yes, DialogResponses.skipForNow);
if (result === DialogResponses.yes) {
await createNewProject(outputChannel, functionAppPath, false, ui);
} else if (result === undefined) {
throw new errors.UserCancelledError();
}
}
Expand Down Expand Up @@ -106,7 +100,7 @@ export async function createFunction(

const folderPlaceholder: string = localize('azFunc.selectFunctionAppFolderExisting', 'Select the folder containing your function app');
const functionAppPath: string = await workspaceUtil.selectWorkspaceFolder(ui, folderPlaceholder);
await validateIsFunctionApp(outputChannel, functionAppPath);
await validateIsFunctionApp(outputChannel, functionAppPath, ui);

const localAppSettings: LocalAppSettings = new LocalAppSettings(ui, azureAccount, functionAppPath);

Expand Down
134 changes: 121 additions & 13 deletions src/commands/createNewProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,138 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import * as vscode from 'vscode';
import * as FunctionsCli from '../functions-cli';
import { OutputChannel } from 'vscode';
import { IUserInterface } from '../IUserInterface';
import { localize } from '../localize';
import * as TemplateFiles from '../template-files';
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';

export async function createNewProject(outputChannel: vscode.OutputChannel, ui: IUserInterface = new VSCodeUI()): Promise<void> {
const functionAppPath: string = await workspaceUtil.selectWorkspaceFolder(ui, localize('azFunc.selectFunctionAppFolderNew', 'Select the folder that will contain your function app'));
const taskId: string = 'launchFunctionApp';
const tasksJson: {} = {
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: [
{
owner: 'azureFunctions',
pattern: [
{
regexp: '\\b\\B',
file: 1,
location: 2,
message: 3
}
],
background: {
activeOnStart: true,
beginsPattern: '^.*Stopping host.*',
endsPattern: '^.*Job host started.*'
}
}
]
}
]
};

const tasksJsonPath: string = path.join(functionAppPath, '.vscode', 'tasks.json');
const tasksJsonExists: boolean = await fse.pathExists(tasksJsonPath);
const launchJsonPath: string = path.join(functionAppPath, '.vscode', 'launch.json');
const launchJsonExists: boolean = await fse.pathExists(launchJsonPath);
const launchJson: {} = {
version: '0.2.0',
configurations: [
{
name: localize('azFunc.attachToFunc', 'Attach to Azure Functions'),
type: 'node',
request: 'attach',
port: 5858,
protocol: 'inspector',
preLaunchTask: taskId
}
]
};

await FunctionsCli.createNewProject(outputChannel, functionAppPath);
// tslint:disable-next-line:no-multiline-string
const gitignore: string = `bin
obj
csx
.vs
edge
Publish
.vscode
if (!tasksJsonExists && !launchJsonExists) {
await fsUtil.writeFormattedJson(tasksJsonPath, TemplateFiles.tasksJson);
await fsUtil.writeFormattedJson(launchJsonPath, TemplateFiles.launchJson);
*.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: ''
}
};

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 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)) {
await fse.writeFile(gitignorePath, gitignore);
}
}

const tasksJsonPath: string = path.join(vscodePath, 'tasks.json');
if (await confirmOverwriteFile(tasksJsonPath)) {
await fsUtil.writeFormattedJson(tasksJsonPath, tasksJson);
}

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);
}

if (!workspaceUtil.isFolderOpenInWorkspace(functionAppPath)) {
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);
}
Expand Down
41 changes: 0 additions & 41 deletions src/functions-cli.ts

This file was deleted.

55 changes: 0 additions & 55 deletions src/template-files.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/utils/cpUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as cp from 'child_process';
import * as vscode from 'vscode';
import { localize } from '../localize';

export namespace cpUtils {
export async function executeCommand(outputChannel: vscode.OutputChannel | undefined, workingDirectory: string, command: string, ...args: string[]): Promise<void> {
await new Promise((resolve: () => void, reject: (e: Error) => void): void => {
const options: cp.SpawnOptions = {
cwd: workingDirectory,
shell: true
};
const childProc: cp.ChildProcess = cp.spawn(command, args, options);

if (outputChannel) {
childProc.stdout.on('data', (data: string | Buffer) => outputChannel.append(data.toString()));
childProc.stderr.on('data', (data: string | Buffer) => outputChannel.append(data.toString()));
}

childProc.on('error', reject);
childProc.on('close', (code: number) => {
if (code !== 0) {
reject(new Error(localize('azFunc.commandError', 'Command "{0} {1}" failed with exit code "{2}".', command, args.toString(), code)));
} else {
resolve();
}
});
});
}
}
Loading

0 comments on commit 8cdb675

Please sign in to comment.