diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index e3021c33d8..fa024420b0 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -104,6 +104,9 @@ const indentationFilter = [ '!extensions/resource-deployment/notebooks/**', '!extensions/mssql/notebooks/**', '!extensions/integration-tests/testData/**', + '!extensions/sql-database-projects/resources/templates/*.xml', + '!extensions/sql-database-projects/src/test/baselines/*.xml', + '!extensions/sql-database-projects/src/test/baselines/*.json', '!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts', '!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts', '!resources/linux/snap/electron-launch' diff --git a/extensions/sql-database-projects/coverConfig.json b/extensions/sql-database-projects/coverConfig.json new file mode 100644 index 0000000000..9d13013c7a --- /dev/null +++ b/extensions/sql-database-projects/coverConfig.json @@ -0,0 +1,19 @@ +{ + "enabled": true, + "relativeSourcePath": "..", + "relativeCoverageDir": "../../coverage", + "ignorePatterns": [ + "**/node_modules/**", + "**/test/**" + ], + "includePid": false, + "reports": [ + "cobertura", + "lcov" + ], + "verbose": false, + "remapOptions": { + "basePath": "..", + "useAbsolutePaths": true + } +} diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index dc5b30964e..961d3264f1 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -36,6 +36,41 @@ "command": "sqlDatabaseProjects.open", "title": "%sqlDatabaseProjects.open%", "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.close", + "title": "%sqlDatabaseProjects.close%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newScript", + "title": "%sqlDatabaseProjects.newScript%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newTable", + "title": "%sqlDatabaseProjects.newTable%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newView", + "title": "%sqlDatabaseProjects.newView%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newStoredProcedure", + "title": "%sqlDatabaseProjects.newStoredProcedure%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newItem", + "title": "%sqlDatabaseProjects.newItem%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.newFolder", + "title": "%sqlDatabaseProjects.newFolder%", + "category": "%sqlDatabaseProjects.displayName%" } ], "menus": { @@ -45,6 +80,64 @@ }, { "command": "sqlDatabaseProjects.open" + }, + { + "command": "sqlDatabaseProjects.close", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newScript", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newTable", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newView", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newStoredProcedure", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newItem", + "when": "false" + }, + { + "command": "sqlDatabaseProjects.newFolder", + "when": "false" + } + ], + "view/item/context": [ + { + "command": "sqlDatabaseProjects.close", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newScript", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newTable", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newView", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newStoredProcedure", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newItem", + "when": "view == sqlDatabaseProjectsView" + }, + { + "command": "sqlDatabaseProjects.newFolder", + "when": "view == sqlDatabaseProjectsView" } ] }, @@ -59,12 +152,18 @@ } }, "dependencies": { + "vscode-languageclient": "^5.3.0-next.1", "vscode-nls": "^3.2.1", - "xml2js": "^0.4.23" + "xmldom": "^0.3.0" }, "devDependencies": { - "@types/xml2js": "^0.4.5", + "@types/xmldom": "^0.1.29", + "mocha-junit-reporter": "^1.17.0", + "mocha-multi-reporters": "^1.1.7", + "should": "^13.2.1", "tslint": "^5.8.0", - "typescript": "^2.6.1" + "typemoq": "^2.1.0", + "typescript": "^2.6.1", + "vscodetestcover": "github:corivera/vscodetestcover#1.0.6" } } diff --git a/extensions/sql-database-projects/package.nls.json b/extensions/sql-database-projects/package.nls.json index b9ebb7ff74..e681585477 100644 --- a/extensions/sql-database-projects/package.nls.json +++ b/extensions/sql-database-projects/package.nls.json @@ -1,7 +1,16 @@ { + "title.projectsView": "Projects", + "sqlDatabaseProjects.displayName": "Database Projects", "sqlDatabaseProjects.description": "Design and deploy SQL database schemas", "sqlDatabaseProjects.new": "New Database Project", "sqlDatabaseProjects.open": "Open Database Project", - "title.projectsView": "Projects" + "sqlDatabaseProjects.close": "Close Database Project", + + "sqlDatabaseProjects.newScript": "Add Script", + "sqlDatabaseProjects.newTable": "Add Table", + "sqlDatabaseProjects.newView": "Add View", + "sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure", + "sqlDatabaseProjects.newItem": "Add Item...", + "sqlDatabaseProjects.newFolder": "Add Folder" } diff --git a/extensions/sql-database-projects/resources/templates/newSqlProjectTemplate.xml b/extensions/sql-database-projects/resources/templates/newSqlProjectTemplate.xml new file mode 100644 index 0000000000..9095afb3a4 --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newSqlProjectTemplate.xml @@ -0,0 +1,60 @@ + + + + Debug + AnyCPU + @@PROJECT_NAME@@ + 2.0 + 4.1 + {@@PROJECT_GUID@@} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + @@PROJECT_NAME@@ + @@PROJECT_NAME@@ + 1033, CI + BySchemaAndSchemaType + True + v4.5 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + diff --git a/extensions/sql-database-projects/resources/templates/newTsqlScriptTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlScriptTemplate.sql new file mode 100644 index 0000000000..1b1d45125f --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlScriptTemplate.sql @@ -0,0 +1 @@ +-- Write your own SQL object definition here, and it'll be included in your package. diff --git a/extensions/sql-database-projects/resources/templates/newTsqlStoredProcedureTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlStoredProcedureTemplate.sql new file mode 100644 index 0000000000..168bf602ec --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlStoredProcedureTemplate.sql @@ -0,0 +1,6 @@ +CREATE PROCEDURE [dbo].[@@OBJECT_NAME@@] + @param1 int = 0, + @param2 int +AS + SELECT @param1, @param2 +RETURN 0 diff --git a/extensions/sql-database-projects/resources/templates/newTsqlTableTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlTableTemplate.sql new file mode 100644 index 0000000000..d8fe93cb30 --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlTableTemplate.sql @@ -0,0 +1,4 @@ +CREATE TABLE [dbo].[@@OBJECT_NAME@@] +( + [Id] INT NOT NULL PRIMARY KEY +) diff --git a/extensions/sql-database-projects/resources/templates/newTsqlViewTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlViewTemplate.sql new file mode 100644 index 0000000000..17df2872d3 --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlViewTemplate.sql @@ -0,0 +1,2 @@ +CREATE VIEW [dbo].[@@OBJECT_NAME@@] + AS SELECT * FROM [SomeTableOrView] diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 08e2da0e3f..40249dda9f 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -9,6 +9,7 @@ const localize = nls.loadMessageBundle(); // Placeholder values export const dataSourcesFileName = 'datasources.json'; +export const sqlprojExtension = '.sqlproj'; // UI Strings @@ -16,6 +17,9 @@ export const noOpenProjectMessage = localize('noProjectOpenMessage', "No open da export const projectNodeName = localize('projectNodeName', "Database Project"); export const dataSourcesNodeName = localize('dataSourcesNodeName', "Data Sources"); export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string"); +export const newDatabaseProjectName = localize('newDatabaseProjectName', "New database project name:"); +export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database project"); +export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); } // Error messages @@ -26,3 +30,21 @@ export const missingVersion = localize('missingVersion', "Missing 'version' entr export const unrecognizedDataSourcesVersion = localize('unrecognizedDataSourcesVersion', "Unrecognized version: "); export const unknownDataSourceType = localize('unknownDataSourceType', "Unknown data source type: "); export const invalidSqlConnectionString = localize('invalidSqlConnectionString', "Invalid SQL connection string"); +export const projectNameRequired = localize('projectNameRequired', "Name is required to create a new database project."); +export const projectLocationRequired = localize('projectLocationRequired', "Location is required to create a new database project."); +export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); } +export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); } + + +// Project script types + +export const scriptFriendlyName = localize('scriptFriendlyName', "Script"); +export const tableFriendlyName = localize('tableFriendlyName', "Table"); +export const viewFriendlyName = localize('viewFriendlyName', "View"); +export const storedProcedureFriendlyName = localize('storedProcedureFriendlyName', "Stored Procedure"); + +// SqlProj file XML names +export const ItemGroup = 'ItemGroup'; +export const Build = 'Build'; +export const Folder = 'Folder'; +export const Include = 'Include'; diff --git a/extensions/sql-database-projects/src/controllers/databaseProjectTreeViewProvider.ts b/extensions/sql-database-projects/src/controllers/databaseProjectTreeViewProvider.ts index fc9b728f3f..354a2a9052 100644 --- a/extensions/sql-database-projects/src/controllers/databaseProjectTreeViewProvider.ts +++ b/extensions/sql-database-projects/src/controllers/databaseProjectTreeViewProvider.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import * as constants from '../common/constants'; -import { BaseProjectTreeItem, MessageTreeItem } from '../models/tree/baseTreeItem'; +import { BaseProjectTreeItem, MessageTreeItem, SpacerTreeItem } from '../models/tree/baseTreeItem'; import { ProjectRootTreeItem } from '../models/tree/projectTreeItem'; import { Project } from '../models/project'; @@ -39,16 +39,20 @@ export class SqlDatabaseProjectTreeViewProvider implements vscode.TreeDataProvid return element.children; } + /** + * Constructs a new set of root nodes from a list of Projects + * @param projects List of Projects + */ public load(projects: Project[]) { - if (projects.length === 0) { - vscode.window.showErrorMessage(constants.noSqlProjFiles); - return; - } - let newRoots: BaseProjectTreeItem[] = []; for (const proj of projects) { newRoots.push(new ProjectRootTreeItem(proj)); + newRoots.push(SpacerTreeItem); + } + + if (newRoots[newRoots.length - 1] === SpacerTreeItem) { + newRoots.pop(); // get rid of the trailing SpacerTreeItem } this.roots = newRoots; diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index 1a3b1cd50e..b34355e8cc 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -4,16 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; +import * as templateMap from '../templates/templateMap'; +import * as templates from '../templates/templates'; +import * as constants from '../common/constants'; +import * as path from 'path'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { getErrorMessage } from '../common/utils'; import { ProjectsController } from './projectController'; +import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView'; -const localize = nls.loadMessageBundle(); - /** * The main controller class that initializes the extension */ @@ -40,11 +42,21 @@ export default class MainController implements vscode.Disposable { private async initializeDatabaseProjects(): Promise { // init commands - vscode.commands.registerCommand('sqlDatabaseProjects.new', () => { console.log('"New Database Project" called.'); }); - vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { this.openProjectFromFile(); }); + vscode.commands.registerCommand('sqlDatabaseProjects.new', async () => { await this.createNewProject(); }); + vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { await this.openProjectFromFile(); }); + vscode.commands.registerCommand('sqlDatabaseProjects.close', (node: BaseProjectTreeItem) => { this.projectsController.closeProject(node); }); + + vscode.commands.registerCommand('sqlDatabaseProjects.newScript', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templateMap.script); }); + vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templateMap.table); }); + vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templateMap.view); }); + vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templateMap.storedProcedure); }); + vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node); }); + vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); }); // init view this.extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider)); + + await templates.loadTemplates(path.join(this._context.extensionPath, 'resources', 'templates')); } /** @@ -55,7 +67,7 @@ export default class MainController implements vscode.Disposable { try { let filter: { [key: string]: string[] } = {}; - filter[localize('sqlDatabaseProject', "SQL database project")] = ['sqlproj']; + filter[constants.sqlDatabaseProject] = ['sqlproj']; let files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ filters: filter }); @@ -70,6 +82,47 @@ export default class MainController implements vscode.Disposable { } } + /** + * Creates a new SQL database project from a template, prompting the user for a name and location + */ + public async createNewProject(): Promise { + try { + let newProjName = await vscode.window.showInputBox({ + prompt: constants.newDatabaseProjectName, + value: `DatabaseProject${this.projectsController.projects.length + 1}` + // TODO: Smarter way to suggest a name. Easy if we prompt for location first, but that feels odd... + }); + + if (!newProjName) { + // TODO: is this case considered an intentional cancellation (shouldn't warn) or an error case (should warn)? + vscode.window.showErrorMessage(constants.projectNameRequired); + return; + } + + let selectionResult = await vscode.window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined + }); + + if (!selectionResult) { + vscode.window.showErrorMessage(constants.projectLocationRequired); + return; + } + + // TODO: what if the selected folder is outside the workspace? + + const newProjFolderUri = (selectionResult as vscode.Uri[])[0]; + console.log(newProjFolderUri.fsPath); + const newProjFilePath = await this.projectsController.createNewProject(newProjName as string, newProjFolderUri as vscode.Uri); + await this.projectsController.openProject(vscode.Uri.file(newProjFilePath)); + } + catch (err) { + vscode.window.showErrorMessage(getErrorMessage(err)); + } + } + public dispose(): void { this.deactivate(); } diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index afab57015e..271d0d55e6 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -4,11 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Project } from '../models/project'; -import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import * as path from 'path'; import * as constants from '../common/constants'; import * as dataSources from '../models/dataSources/dataSources'; +import * as templateMap from '../templates/templateMap'; +import * as utils from '../common/utils'; +import * as UUID from 'vscode-languageclient/lib/utils/uuid'; +import * as templates from '../templates/templates'; + +import { Project } from '../models/project'; +import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; +import { promises as fs } from 'fs'; +import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; +import { ProjectRootTreeItem } from '../models/tree/projectTreeItem'; +import { FolderNode } from '../models/tree/fileFolderTreeItem'; /** * Controller for managing project lifecycle @@ -22,8 +31,18 @@ export class ProjectsController { this.projectTreeViewProvider = projTreeViewProvider; } - public async openProject(projectFile: vscode.Uri) { - console.log('Loading project: ' + projectFile.fsPath); + + public refreshProjectsTree() { + this.projectTreeViewProvider.load(this.projects); + } + + public async openProject(projectFile: vscode.Uri): Promise { + for (const proj of this.projects) { + if (proj.projectFilePath === projectFile.fsPath) { + vscode.window.showInformationMessage(constants.projectAlreadyOpened(projectFile.fsPath)); + return proj; + } + } // Read project file const newProject = new Project(projectFile.fsPath); @@ -32,12 +51,173 @@ export class ProjectsController { // Read datasources.json (if present) const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName); - newProject.dataSources = await dataSources.load(dataSourcesFilePath); + + try { + newProject.dataSources = await dataSources.load(dataSourcesFilePath); + } + catch (err) { + if (err instanceof dataSources.NoDataSourcesFileError) { + // TODO: prompt to create new datasources.json; for now, swallow + console.log(`No ${constants.dataSourcesFileName} file found.`); + } + else { + throw err; + } + } + + this.refreshProjectsTree(); + + return newProject; + } + + public async createNewProject(newProjName: string, folderUri: vscode.Uri, projectGuid?: string): Promise { + if (projectGuid && !UUID.isUUID(projectGuid)) { + throw new Error(`Specified GUID is invalid: '${projectGuid}'`); + } + + const macroDict: Record = { + 'PROJECT_NAME': newProjName, + 'PROJECT_GUID': projectGuid ?? UUID.generateUuid().toUpperCase() + }; + + let newProjFileContents = this.macroExpansion(templates.newSqlProjectTemplate, macroDict); + + let newProjFileName = newProjName; + + if (!newProjFileName.toLowerCase().endsWith(constants.sqlprojExtension)) { + newProjFileName += constants.sqlprojExtension; + } + + const newProjFilePath = path.join(folderUri.fsPath, newProjFileName); + + let fileExists = false; + try { + await fs.access(newProjFilePath); + fileExists = true; + } + catch { } // file doesn't already exist + + if (fileExists) { + throw new Error(constants.projectAlreadyExists(newProjFileName, folderUri.fsPath)); + } + + await fs.mkdir(path.dirname(newProjFilePath), { recursive: true }); + await fs.writeFile(newProjFilePath, newProjFileContents); + + return newProjFilePath; + } + + public closeProject(treeNode: BaseProjectTreeItem) { + const project = this.getProjectContextFromTreeNode(treeNode); + this.projects = this.projects.filter((e) => { return e !== project; }); + this.refreshProjectsTree(); + } + + public async addFolderPrompt(treeNode: BaseProjectTreeItem) { + const project = this.getProjectContextFromTreeNode(treeNode); + const newFolderName = await this.promptForNewObjectName(new templateMap.ProjectScriptType(templateMap.folder, 'Folder', ''), project); + + if (!newFolderName) { + return; // user cancelled + } + + const relativeFolderPath = this.prependContextPath(treeNode, newFolderName); + + await project.addFolderItem(relativeFolderPath); this.refreshProjectsTree(); } - public refreshProjectsTree() { - this.projectTreeViewProvider.load(this.projects); + public async addItemPrompt(treeNode: BaseProjectTreeItem, itemTypeName?: string) { + const project = this.getProjectContextFromTreeNode(treeNode); + + if (!itemTypeName) { + let itemFriendlyNames: string[] = []; + + for (const itemType of templateMap.projectScriptTypes) { + itemFriendlyNames.push(itemType.friendlyName); + } + + itemTypeName = await vscode.window.showQuickPick(itemFriendlyNames, { + canPickMany: false + }); + + if (!itemTypeName) { + return; // user cancelled + } + } + + const itemType = templateMap.projectScriptTypeMap[itemTypeName.toLocaleLowerCase()]; + const itemObjectName = await this.promptForNewObjectName(itemType, project); + + if (!itemObjectName) { + return; // user cancelled + } + + // TODO: file already exists? + + const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName }); + const relativeFilePath = this.prependContextPath(treeNode, itemObjectName + '.sql'); + + const newEntry = await project.addScriptItem(relativeFilePath, newFileText); + + vscode.commands.executeCommand('vscode.open', newEntry.fsUri); + + this.refreshProjectsTree(); } + + //#region Helper methods + + private macroExpansion(template: string, macroDict: Record): string { + const macroIndicator = '@@'; + let output = template; + + for (const macro in macroDict) { + // check if value contains the macroIndicator, which could break expansion for successive macros + if (macroDict[macro].includes(macroIndicator)) { + throw new Error(`Macro value ${macroDict[macro]} is invalid because it contains ${macroIndicator}`); + } + + output = output.replace(new RegExp(macroIndicator + macro + macroIndicator, 'g'), macroDict[macro]); + } + + return output; + } + + private getProjectContextFromTreeNode(treeNode: BaseProjectTreeItem): Project { + if (!treeNode) { + // TODO: prompt for which (currently-open) project when invoked via command pallet + throw new Error('TODO: prompt for which project when invoked via command pallet'); + } + + if (treeNode.root instanceof ProjectRootTreeItem) { + return (treeNode.root as ProjectRootTreeItem).project; + } + else { + throw new Error('"Add item" command invoked from unexpected location: ' + treeNode.uri.path); + } + } + + private async promptForNewObjectName(itemType: templateMap.ProjectScriptType, _project: Project): Promise { + // TODO: ask project for suggested name that doesn't conflict + const suggestedName = itemType.friendlyName.replace(new RegExp('\s', 'g'), '') + '1'; + + const itemObjectName = await vscode.window.showInputBox({ + prompt: constants.newObjectNamePrompt(itemType.friendlyName), + value: suggestedName, + }); + + return itemObjectName; + } + + private prependContextPath(treeNode: BaseProjectTreeItem, objectName: string): string { + if (treeNode instanceof FolderNode) { + return path.join(utils.trimUri(treeNode.root.uri, treeNode.uri), objectName); + } + else { + return objectName; + } + } + + //#endregion } diff --git a/extensions/sql-database-projects/src/models/dataSources/dataSources.ts b/extensions/sql-database-projects/src/models/dataSources/dataSources.ts index bc13b46b96..ce2339fd78 100644 --- a/extensions/sql-database-projects/src/models/dataSources/dataSources.ts +++ b/extensions/sql-database-projects/src/models/dataSources/dataSources.ts @@ -13,13 +13,21 @@ import { SqlConnectionDataSource } from './sqlConnectionStringSource'; export abstract class DataSource { public name: string; public abstract get type(): string; - public abstract get friendlyName(): string; + public abstract get typeFriendlyName(): string; constructor(name: string) { this.name = name; } } +export class NoDataSourcesFileError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = NoDataSourcesFileError.name; + } +} + /** * parses the specified file to load DataSource objects */ @@ -30,7 +38,8 @@ export async function load(dataSourcesFilePath: string): Promise { fileContents = await fs.readFile(dataSourcesFilePath); } catch (err) { - throw new Error(constants.noDataSourcesFile); + // TODO: differentiate between file not existing and other types of failures; need to know whether to prompt to create new + throw new NoDataSourcesFileError(constants.noDataSourcesFile); } const rawJsonContents = JSON.parse(fileContents.toString()); diff --git a/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts b/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts index fdd4427602..197b56d745 100644 --- a/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts +++ b/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts @@ -21,7 +21,7 @@ export class SqlConnectionDataSource extends DataSource { return SqlConnectionDataSource.type; } - public get friendlyName(): string { + public get typeFriendlyName(): string { return constants.sqlConnectionStringFriendly; } @@ -42,6 +42,10 @@ export class SqlConnectionDataSource extends DataSource { } } + public getSetting(settingName: string): string { + return this.connectionStringComponents[settingName]; + } + public static fromJson(json: DataSourceJson): SqlConnectionDataSource { return new SqlConnectionDataSource(json.name, (json.data as unknown as SqlConnectionDataSourceJson).connectionString); } diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index c36ccc614c..3bae437377 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -4,39 +4,40 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as xml2js from 'xml2js'; import * as path from 'path'; +import * as xmldom from 'xmldom'; +import * as constants from '../common/constants'; + import { promises as fs } from 'fs'; import { DataSource } from './dataSources/dataSources'; +import { getErrorMessage } from '../common/utils'; /** * Class representing a Project, and providing functions for operating on it */ export class Project { - public projectFile: string; + public projectFilePath: string; public files: ProjectEntry[] = []; public dataSources: DataSource[] = []; + public get projectFolderPath() { + return path.dirname(this.projectFilePath); + } + + private projFileXmlDoc: any = undefined; + constructor(projectFilePath: string) { - this.projectFile = projectFilePath; + this.projectFilePath = projectFilePath; } /** * Reads the project setting and contents from the file */ public async readProjFile() { - let projFileContents = await fs.readFile(this.projectFile); - - const parser = new xml2js.Parser({ - explicitArray: true, - explicitCharkey: false, - explicitRoot: false - }); - - let result; + const projFileText = await fs.readFile(this.projectFilePath); try { - result = await parser.parseStringPromise(projFileContents.toString()); + this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString()); } catch (err) { vscode.window.showErrorMessage(err); @@ -45,23 +46,115 @@ export class Project { // find all folders and files to include - for (const itemGroup of result['ItemGroup']) { - if (itemGroup['Build'] !== undefined) { - for (const fileEntry of itemGroup['Build']) { - this.files.push(this.createProjectEntry(fileEntry.$['Include'], EntryType.File)); - } + for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) { + const itemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig]; + + for (let b = 0; b < itemGroup.getElementsByTagName(constants.Build).length; b++) { + this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Build)[b].getAttribute(constants.Include), EntryType.File)); } - if (itemGroup['Folder'] !== undefined) { - for (const folderEntry of itemGroup['Folder']) { - this.files.push(this.createProjectEntry(folderEntry.$['Include'], EntryType.Folder)); - } + for (let f = 0; f < itemGroup.getElementsByTagName(constants.Folder).length; f++) { + this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Folder)[f].getAttribute(constants.Include), EntryType.Folder)); } } } + /** + * Adds a folder to the project, and saves the project file + * @param relativeFolderPath Relative path of the folder + */ + public async addFolderItem(relativeFolderPath: string): Promise { + const absoluteFolderPath = path.join(this.projectFolderPath, relativeFolderPath); + await fs.mkdir(absoluteFolderPath, { recursive: true }); + + const folderEntry = this.createProjectEntry(relativeFolderPath, EntryType.Folder); + this.files.push(folderEntry); + + await this.addToProjFile(folderEntry); + return folderEntry; + } + + /** + * Writes a file to disk, adds that file to the project, and writes it to disk + * @param relativeFilePath Relative path of the file + * @param contents Contents to be written to the new file + */ + public async addScriptItem(relativeFilePath: string, contents: string): Promise { + const absoluteFilePath = path.join(this.projectFolderPath, relativeFilePath); + await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true }); + await fs.writeFile(absoluteFilePath, contents); + + const fileEntry = this.createProjectEntry(relativeFilePath, EntryType.File); + this.files.push(fileEntry); + + await this.addToProjFile(fileEntry); + + return fileEntry; + } + private createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry { - return new ProjectEntry(vscode.Uri.file(path.join(this.projectFile, relativePath)), entryType); + return new ProjectEntry(vscode.Uri.file(path.join(this.projectFolderPath, relativePath)), relativePath, entryType); + } + + private findOrCreateItemGroup(containedTag?: string): any { + let outputItemGroup = undefined; + + // find any ItemGroup node that contains files; that's where we'll add + for (let i = 0; i < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; i++) { + const currentItemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[i]; + + // if we're not hunting for a particular child type, or if we are and we find it, use the ItemGroup + if (!containedTag || currentItemGroup.getElementsByTagName(containedTag).length > 0) { + outputItemGroup = currentItemGroup; + break; + } + } + + // if none already exist, make a new ItemGroup for it + if (!outputItemGroup) { + outputItemGroup = this.projFileXmlDoc.createElement(constants.ItemGroup); + this.projFileXmlDoc.documentElement.appendChild(outputItemGroup); + } + + return outputItemGroup; + } + + private addFileToProjFile(path: string) { + const newFileNode = this.projFileXmlDoc.createElement(constants.Build); + newFileNode.setAttribute(constants.Include, path); + + this.findOrCreateItemGroup(constants.Build).appendChild(newFileNode); + } + + private addFolderToProjFile(path: string) { + const newFolderNode = this.projFileXmlDoc.createElement(constants.Folder); + newFolderNode.setAttribute(constants.Include, path); + + this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode); + } + + private async addToProjFile(entry: ProjectEntry) { + try { + switch (entry.type) { + case EntryType.File: + this.addFileToProjFile(entry.relativePath); + break; + case EntryType.Folder: + this.addFolderToProjFile(entry.relativePath); + } + + await this.serializeToProjFile(this.projFileXmlDoc); + } + catch (err) { + vscode.window.showErrorMessage(getErrorMessage(err)); + return; + } + } + + private async serializeToProjFile(projFileContents: any) { + const xml = new xmldom.XMLSerializer().serializeToString(projFileContents); // TODO: how to get this to serialize with "pretty" formatting + + await fs.writeFile(this.projectFilePath, xml); } } @@ -72,16 +165,18 @@ export class ProjectEntry { /** * Absolute file system URI */ - uri: vscode.Uri; + fsUri: vscode.Uri; + relativePath: string; type: EntryType; - constructor(uri: vscode.Uri, type: EntryType) { - this.uri = uri; + constructor(uri: vscode.Uri, relativePath: string, type: EntryType) { + this.fsUri = uri; + this.relativePath = relativePath; this.type = type; } public toString(): string { - return this.uri.path; + return this.fsUri.path; } } diff --git a/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts b/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts index 9d5ac1def1..868d61f73b 100644 --- a/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts @@ -52,3 +52,5 @@ export class MessageTreeItem extends BaseProjectTreeItem { return new vscode.TreeItem(this.message, vscode.TreeItemCollapsibleState.None); } } + +export const SpacerTreeItem = new MessageTreeItem(''); diff --git a/extensions/sql-database-projects/src/models/tree/dataSourceTreeItem.ts b/extensions/sql-database-projects/src/models/tree/dataSourceTreeItem.ts index ddc0e6f6d5..9f80cf4da4 100644 --- a/extensions/sql-database-projects/src/models/tree/dataSourceTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/dataSourceTreeItem.ts @@ -53,7 +53,7 @@ export class SqlConnectionDataSourceTreeItem extends DataSourceTreeItem { public get treeItem(): vscode.TreeItem { let item = new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.Collapsed); - item.label = `${this.dataSource.name} (${this.dataSource.friendlyName})`; + item.label = `${this.dataSource.name} (${this.dataSource.typeFriendlyName})`; return item; } diff --git a/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts b/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts index f26038d9a7..cc6a08c791 100644 --- a/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts @@ -50,7 +50,17 @@ export class FileNode extends BaseProjectTreeItem { } public get treeItem(): vscode.TreeItem { - return new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.None); + const treeItem = new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.None); + + treeItem.command = { + title: 'Open file', + command: 'vscode.open', + arguments: [this.fileSystemUri] + }; + + treeItem.contextValue = 'File'; + + return treeItem; } } @@ -58,7 +68,7 @@ export class FileNode extends BaseProjectTreeItem { * Converts a full filesystem URI to a project-relative URI that's compatible with the project tree */ function fsPathToProjectUri(fileSystemUri: vscode.Uri, projectNode: ProjectRootTreeItem): vscode.Uri { - const projBaseDir = path.dirname(projectNode.project.projectFile); + const projBaseDir = path.dirname(projectNode.project.projectFilePath); let localUri = ''; if (fileSystemUri.fsPath.startsWith(projBaseDir)) { diff --git a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts index 34e7cc7e5c..0a58f020fa 100644 --- a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts @@ -20,7 +20,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { project: Project; constructor(project: Project) { - super(vscode.Uri.parse(path.basename(project.projectFile)), undefined); + super(vscode.Uri.parse(path.basename(project.projectFilePath)), undefined); this.project = project; this.dataSourceNode = new DataSourcesTreeItem(this); @@ -57,16 +57,16 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { switch (entry.type) { case EntryType.File: - newNode = new fileTree.FileNode(entry.uri, parentNode); + newNode = new fileTree.FileNode(entry.fsUri, parentNode); break; case EntryType.Folder: - newNode = new fileTree.FolderNode(entry.uri, parentNode); + newNode = new fileTree.FolderNode(entry.fsUri, parentNode); break; default: throw new Error(`Unknown EntryType: '${entry.type}'`); } - parentNode.fileChildren[path.basename(entry.uri.path)] = newNode; + parentNode.fileChildren[path.basename(entry.fsUri.path)] = newNode; } } @@ -74,7 +74,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { * Gets the immediate parent tree node for an entry in a project file */ private getEntryParentNode(entry: ProjectEntry): fileTree.FolderNode | ProjectRootTreeItem { - const relativePathParts = utils.trimChars(utils.trimUri(vscode.Uri.file(this.project.projectFile), entry.uri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent + const relativePathParts = utils.trimChars(utils.trimUri(vscode.Uri.file(this.project.projectFilePath), entry.fsUri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent if (relativePathParts.length === 0) { return this; // if nothing left after trimming the entry itself, must been root @@ -84,7 +84,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { for (const part of relativePathParts) { if (current.fileChildren[part] === undefined) { - current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(path.dirname(this.project.projectFile), part)), current); + current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(path.dirname(this.project.projectFilePath), part)), current); } if (current.fileChildren[part] instanceof fileTree.FileNode) { diff --git a/extensions/sql-database-projects/src/templates/templateMap.ts b/extensions/sql-database-projects/src/templates/templateMap.ts new file mode 100644 index 0000000000..ba5c2143d7 --- /dev/null +++ b/extensions/sql-database-projects/src/templates/templateMap.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as constants from '../common/constants'; +import * as templates from './templates'; + +export class ProjectScriptType { + type: string; + friendlyName: string; + templateScript: string; + + constructor(type: string, friendlyName: string, templateScript: string) { + this.type = type; + this.friendlyName = friendlyName; + this.templateScript = templateScript; + } +} + +export const script: string = 'script'; +export const table: string = 'table'; +export const view: string = 'view'; +export const storedProcedure: string = 'storedProcedure'; +export const folder: string = 'folder'; + +export const projectScriptTypes: ProjectScriptType[] = [ + new ProjectScriptType(script, constants.scriptFriendlyName, templates.newSqlScriptTemplate), + new ProjectScriptType(table, constants.tableFriendlyName, templates.newSqlTableTemplate), + new ProjectScriptType(view, constants.viewFriendlyName, templates.newSqlViewTemplate), + new ProjectScriptType(storedProcedure, constants.storedProcedureFriendlyName, templates.newSqlStoredProcedureTemplate), +]; + +export const projectScriptTypeMap: Record = {}; + +for (const scriptType of projectScriptTypes) { + if (Object.keys(projectScriptTypeMap).find(s => s === scriptType.type.toLocaleLowerCase() || s === scriptType.friendlyName.toLocaleLowerCase())) { + throw new Error(`Script type map already contains ${scriptType.type} or its friendlyName.`); + } + + projectScriptTypeMap[scriptType.type.toLocaleLowerCase()] = scriptType; + projectScriptTypeMap[scriptType.friendlyName.toLocaleLowerCase()] = scriptType; +} diff --git a/extensions/sql-database-projects/src/templates/templates.ts b/extensions/sql-database-projects/src/templates/templates.ts new file mode 100644 index 0000000000..f2a6d65d1b --- /dev/null +++ b/extensions/sql-database-projects/src/templates/templates.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import { promises as fs } from 'fs'; + +// Project templates +export let newSqlProjectTemplate: string; + +// Script templates + +export let newSqlScriptTemplate: string; +export let newSqlTableTemplate: string; +export let newSqlViewTemplate: string; +export let newSqlStoredProcedureTemplate: string; + +export async function loadTemplates(templateFolderPath: string) { + newSqlProjectTemplate = await loadTemplate(templateFolderPath, 'newSqlProjectTemplate.xml'); + + newSqlScriptTemplate = await loadTemplate(templateFolderPath, 'newTsqlScriptTemplate.sql'); + newSqlTableTemplate = await loadTemplate(templateFolderPath, 'newTsqlTableTemplate.sql'); + newSqlViewTemplate = await loadTemplate(templateFolderPath, 'newTsqlViewTemplate.sql'); + newSqlStoredProcedureTemplate = await loadTemplate(templateFolderPath, 'newTsqlStoredProcedureTemplate.sql'); +} + +async function loadTemplate(templateFolderPath: string, fileName: string): Promise { + return (await fs.readFile(path.join(templateFolderPath, fileName))).toString(); +} diff --git a/extensions/sql-database-projects/src/test/baselines/baselines.ts b/extensions/sql-database-projects/src/test/baselines/baselines.ts new file mode 100644 index 0000000000..adfd32405d --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/baselines.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import { promises as fs } from 'fs'; + +// Project baselines +export let newProjectFileBaseline: string; +export let openProjectFileBaseline: string; +export let openDataSourcesBaseline: string; + +const baselineFolderPath = __dirname; + +export async function loadBaselines() { + newProjectFileBaseline = await loadBaseline(baselineFolderPath, 'newSqlProjectBaseline.xml'); + openProjectFileBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectBaseline.xml'); + openDataSourcesBaseline = await loadBaseline(baselineFolderPath, 'openDataSourcesBaseline.json'); +} + +async function loadBaseline(baselineFolderPath: string, fileName: string): Promise { + return (await fs.readFile(path.join(baselineFolderPath, fileName))).toString(); +} diff --git a/extensions/sql-database-projects/src/test/baselines/newSqlProjectBaseline.xml b/extensions/sql-database-projects/src/test/baselines/newSqlProjectBaseline.xml new file mode 100644 index 0000000000..93d952c88a --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/newSqlProjectBaseline.xml @@ -0,0 +1,60 @@ + + + + Debug + AnyCPU + TestProjectName + 2.0 + 4.1 + {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + TestProjectName + TestProjectName + 1033, CI + BySchemaAndSchemaType + True + v4.5 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + diff --git a/extensions/sql-database-projects/src/test/baselines/openDataSourcesBaseline.json b/extensions/sql-database-projects/src/test/baselines/openDataSourcesBaseline.json new file mode 100644 index 0000000000..21d0f34f10 --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/openDataSourcesBaseline.json @@ -0,0 +1,21 @@ +{ + "version": "0.0.0", + "datasources" : [ + { + "name": "Test Data Source 1", + "type": "sql_connection_string", + "version": "0.0.0", + "data": { + "connectionString": "Data Source=.;Initial Catalog=testDb;Integrated Security=True" + } + }, + { + "name": "My Other Data Source", + "type": "sql_connection_string", + "version": "0.0.0", + "data": { + "connectionString": "Data Source=.;Initial Catalog=testDb2;Integrated Security=False" + } + } + ] +} diff --git a/extensions/sql-database-projects/src/test/baselines/openSqlProjectBaseline.xml b/extensions/sql-database-projects/src/test/baselines/openSqlProjectBaseline.xml new file mode 100644 index 0000000000..1ee9075d77 --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/openSqlProjectBaseline.xml @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + TestProjectName + 2.0 + 4.1 + {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + TestProjectName + TestProjectName + 1033, CI + BySchemaAndSchemaType + True + v4.5 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-database-projects/src/test/datasource.test.ts b/extensions/sql-database-projects/src/test/datasource.test.ts new file mode 100644 index 0000000000..93d6c60a9a --- /dev/null +++ b/extensions/sql-database-projects/src/test/datasource.test.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as baselines from './baselines/baselines'; +import * as testUtils from './testUtils'; +import * as sql from '../models/dataSources/sqlConnectionStringSource'; +import * as dataSources from '../models/dataSources/dataSources'; + +describe('Data Sources: DataSource operations', function (): void { + before(async function () : Promise { + await baselines.loadBaselines(); + }); + + it('Should read DataSources from datasource.json', async function (): Promise { + const dataSourcePath = await testUtils.createTestDataSources(baselines.openDataSourcesBaseline); + const dataSourceList = await dataSources.load(dataSourcePath); + + should(dataSourceList.length).equal(2); + + should(dataSourceList[0].name).equal('Test Data Source 1'); + should(dataSourceList[0].type).equal(sql.SqlConnectionDataSource.type); + should((dataSourceList[0] as sql.SqlConnectionDataSource).getSetting('Initial Catalog')).equal('testDb'); + + should(dataSourceList[1].name).equal('My Other Data Source'); + should((dataSourceList[1] as sql.SqlConnectionDataSource).getSetting('Integrated Security')).equal('False'); + }); +}); diff --git a/extensions/sql-database-projects/src/test/index.ts b/extensions/sql-database-projects/src/test/index.ts new file mode 100644 index 0000000000..6264dd975e --- /dev/null +++ b/extensions/sql-database-projects/src/test/index.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +const testRunner = require('vscodetestcover'); + +const suite = 'Database Projects Extension Tests'; + +const mochaOptions: any = { + ui: 'bdd', + useColors: true, + timeout: 10000 +}; + +// set relevant mocha options from the environment +if (process.env.ADS_TEST_GREP) { + mochaOptions.grep = process.env.ADS_TEST_GREP; + console.log(`setting options.grep to: ${mochaOptions.grep}`); +} +if (process.env.ADS_TEST_INVERT_GREP) { + mochaOptions.invert = parseInt(process.env.ADS_TEST_INVERT_GREP); + console.log(`setting options.invert to: ${mochaOptions.invert}`); +} +if (process.env.ADS_TEST_TIMEOUT) { + mochaOptions.timeout = parseInt(process.env.ADS_TEST_TIMEOUT); + console.log(`setting options.timeout to: ${mochaOptions.timeout}`); +} +if (process.env.ADS_TEST_RETRIES) { + mochaOptions.retries = parseInt(process.env.ADS_TEST_RETRIES); + console.log(`setting options.retries to: ${mochaOptions.retries}`); +} + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + mochaOptions.reporter = 'mocha-multi-reporters'; + mochaOptions.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(mochaOptions, { coverConfig: '../../coverConfig.json' }); + +export = testRunner; diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts new file mode 100644 index 0000000000..89e8e78eb0 --- /dev/null +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as path from 'path'; +import * as baselines from './baselines/baselines'; +import * as testUtils from './testUtils'; + +import { promises as fs } from 'fs'; +import { Project, EntryType } from '../models/project'; + +let projFilePath: string; + +describe('Project: sqlproj content operations', function (): void { + before(async function () : Promise { + await baselines.loadBaselines(); + }); + + beforeEach(async () => { + projFilePath = await testUtils.createTestSqlProj(baselines.openProjectFileBaseline); + }); + + it('Should read Project from sqlproj', async function (): Promise { + const project: Project = new Project(projFilePath); + await project.readProjFile(); + + should(project.files.filter(f => f.type === EntryType.File).length).equal(4); + should(project.files.filter(f => f.type === EntryType.Folder).length).equal(5); + + should(project.files.find(f => f.type === EntryType.Folder && f.relativePath === 'Views\\User')).not.equal(undefined); // mixed ItemGroup folder + should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'Views\\User\\Profile.sql')).not.equal(undefined); // mixed ItemGroup file + }); + + it('Should add Folder and Build entries to sqlproj', async function (): Promise { + const project: Project = new Project(projFilePath); + await project.readProjFile(); + + const folderPath = 'Stored Procedures'; + const filePath = path.join(folderPath, 'Fake Stored Proc.sql'); + const fileContents = 'SELECT \'This is not actually a stored procedure.\''; + + await project.addFolderItem(folderPath); + await project.addScriptItem(filePath, fileContents); + + const newProject = new Project(projFilePath); + await newProject.readProjFile(); + + should(newProject.files.find(f => f.type === EntryType.Folder && f.relativePath === folderPath)).not.equal(undefined); + should(newProject.files.find(f => f.type === EntryType.File && f.relativePath === filePath)).not.equal(undefined); + + const newFileContents = (await fs.readFile(path.join(newProject.projectFolderPath, filePath))).toString(); + + should (newFileContents).equal(fileContents); + }); +}); diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts new file mode 100644 index 0000000000..7f3e980cd5 --- /dev/null +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as path from 'path'; +import * as os from 'os'; +import * as vscode from 'vscode'; +import * as baselines from './baselines/baselines'; +import * as templates from '../templates/templates'; +import * as testUtils from './testUtils'; + +import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider'; +import { ProjectsController } from '../controllers/projectController'; +import { promises as fs } from 'fs'; + +describe('ProjectsController: project controller operations', function (): void { + before(async function () : Promise { + await templates.loadTemplates(path.join(__dirname, '..', '..', 'resources', 'templates')); + await baselines.loadBaselines(); + }); + + it('Should create new sqlproj file with correct values', async function (): Promise { + const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider()); + const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`); + + const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'); + + let projFileText = (await fs.readFile(projFilePath)).toString(); + + should(projFileText).equal(baselines.newProjectFileBaseline); + }); + + it('Should load Project and associated DataSources', async function (): Promise { + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProj(baselines.openProjectFileBaseline, folderPath); + await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath); + + const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider()); + + const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); + + should(project.files.length).equal(9); // detailed sqlproj tests in their own test file + should(project.dataSources.length).equal(2); // detailed datasources tests in their own test file + }); +}); diff --git a/extensions/sql-database-projects/src/test/testUtils.ts b/extensions/sql-database-projects/src/test/testUtils.ts new file mode 100644 index 0000000000..d000f8c95a --- /dev/null +++ b/extensions/sql-database-projects/src/test/testUtils.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as os from 'os'; +import * as constants from '../common/constants'; + +import { promises as fs } from 'fs'; + +export async function createTestSqlProj(contents: string, folderPath?: string): Promise { + return await createTestFile(contents, 'TestProject.sqlproj', folderPath); +} + +export async function createTestDataSources(contents: string, folderPath?: string): Promise { + return await createTestFile(contents, constants.dataSourcesFileName, folderPath); +} + +export async function generateTestFolderPath(): Promise { + const folderPath = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`); + await fs.mkdir(folderPath, { recursive: true }); + + return folderPath; +} + +async function createTestFile(contents: string, fileName: string, folderPath?: string): Promise { + folderPath = folderPath ?? await generateTestFolderPath(); + const filePath = path.join(folderPath, fileName); + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, contents); + + return filePath; +} diff --git a/extensions/sql-database-projects/yarn.lock b/extensions/sql-database-projects/yarn.lock index 1440fb8059..e994a94df2 100644 --- a/extensions/sql-database-projects/yarn.lock +++ b/extensions/sql-database-projects/yarn.lock @@ -9,6 +9,134 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/core@^7.7.5": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.9.0", "@babel/generator@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" + integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== + dependencies: + "@babel/types" "^7.9.5" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" + integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.9.5" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" + integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + "@babel/highlight@^7.0.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" @@ -18,17 +146,67 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@types/node@*": - version "13.1.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385" - integrity sha512-B8emQA1qeKerqd1dmIsQYnXi+mmAzTB7flExjmy5X1aVAKFNNNDubkavwR13kR6JnpeLp3aLoJhwn9trWPAyFQ== - -"@types/xml2js@^0.4.5": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.5.tgz#d21759b056f282d9c7066f15bbf5c19b908f22fa" - integrity sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w== +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== dependencies: - "@types/node" "*" + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" + integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== + +"@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.7.4", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" + integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.5" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" + integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== + dependencies: + "@babel/helper-validator-identifier" "^7.9.5" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@types/xmldom@^0.1.29": + version "0.1.29" + resolved "https://registry.yarnpkg.com/@types/xmldom/-/xmldom-0.1.29.tgz#c4428b0ca86d3b881475726fd94980b38a27c381" + integrity sha1-xEKLDKhtO4gUdXJv2UmAs4onw4E= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-styles@^3.2.1: version "3.2.1" @@ -37,6 +215,13 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -57,11 +242,21 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= +callsite@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= + chalk@^2.0.0, chalk@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -71,6 +266,16 @@ chalk@^2.0.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -83,6 +288,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + commander@^2.12.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -93,12 +303,71 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decache@^4.4.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/decache/-/decache-4.5.1.tgz#94a977a88a4188672c96550ec4889582ceecdf49" + integrity sha512-5J37nATc6FmOTLbcsr9qx7Nm28qQyg1SK4xyEHqM0IBkNhWFp0Sm+vKoWYHD8wq+OUEb9jLyaKFfzzd1A9hcoA== + dependencies: + callsite "^1.0.0" + +default-require-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== + dependencies: + strip-bom "^4.0.0" + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diff@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== -escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -118,7 +387,24 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -glob@^7.1.1: +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -130,11 +416,36 @@ glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -148,6 +459,69 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" + integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== + dependencies: + "@babel/core" "^7.7.5" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -161,7 +535,48 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -minimatch@^3.0.4: +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" + integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + dependencies: + semver "^6.0.0" + +md5@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -173,13 +588,71 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -mkdirp@^0.5.1: +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@0.5.1, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mocha-junit-reporter@^1.17.0: + version "1.23.3" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz#941e219dd759ed732f8641e165918aa8b167c981" + integrity sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== + dependencies: + debug "^2.2.0" + md5 "^2.1.0" + mkdirp "~0.5.1" + strip-ansi "^4.0.0" + xml "^1.0.0" + +mocha-multi-reporters@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" + integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= + dependencies: + debug "^3.1.0" + lodash "^4.16.4" + +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -197,6 +670,16 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postinstall-build@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postinstall-build/-/postinstall-build-5.0.3.tgz#238692f712a481d8f5bc8960e94786036241efc7" + integrity sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg== + resolve@^1.3.2: version "1.13.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" @@ -204,21 +687,106 @@ resolve@^1.3.2: dependencies: path-parse "^1.0.6" -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" -semver@^5.3.0: +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^5.3.0, semver@^5.4.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha1-m/yPdPo5IFxT04w01xcwPidxJPE= + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM= + +should-util@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.1.tgz#fb0d71338f532a3a149213639e2d32cbea8bcb28" + integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== + +should@^13.2.1: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -226,6 +794,18 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + tslib@^1.8.0, tslib@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -257,30 +837,76 @@ tsutils@^2.29.0: dependencies: tslib "^1.8.1" +typemoq@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8" + integrity sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw== + dependencies: + circular-json "^0.3.1" + lodash "^4.17.4" + postinstall-build "^5.0.1" + typescript@^2.6.1: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== +vscode-jsonrpc@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" + integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== + +vscode-languageclient@^5.3.0-next.1: + version "5.3.0-next.9" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.3.0-next.9.tgz#34f58017647f15cd86015f7af45935dc750611f7" + integrity sha512-BFA3X1y2EI2CfsSBy0KG2Xr5BOYfd/97jTmD+doqL6oj+cY8S7AmRCOwb2f9Hbjq8GWL7YC+OJ0leZEUSPgP0A== + dependencies: + semver "^6.3.0" + vscode-languageserver-protocol "^3.15.0-next.8" + +vscode-languageserver-protocol@^3.15.0-next.8: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== + dependencies: + vscode-jsonrpc "^5.0.1" + vscode-languageserver-types "3.15.1" + +vscode-languageserver-types@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== + vscode-nls@^3.2.1: version "3.2.5" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4" integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw== +"vscodetestcover@github:corivera/vscodetestcover#1.0.6": + version "1.0.5" + resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb" + dependencies: + decache "^4.4.0" + glob "^7.1.2" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^3.0.6" + istanbul-reports "^3.0.0" + mocha "^5.2.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xml2js@^0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmldom@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a" + integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== diff --git a/scripts/test-extensions-unit.bat b/scripts/test-extensions-unit.bat index 594411fb3e..a41fda7957 100755 --- a/scripts/test-extensions-unit.bat +++ b/scripts/test-extensions-unit.bat @@ -26,6 +26,8 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( call yarn gulp compile-extension:mssql call yarn gulp compile-extension:notebook call yarn gulp compile-extension:resource-deployment + call yarn gulp compile-extension:machine-learning-services + call yarn gulp compile-extension:sql-database-projects echo "Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build." ) @@ -89,6 +91,11 @@ REM echo *** starting mssql tests *** REM echo ****************************************** REM call "%INTEGRATION_TEST_ELECTRON_PATH%" --extensionDevelopmentPath=%~dp0\..\extensions\mssql --extensionTestsPath=%~dp0\..\extensions\mssql\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 --disable-telemetry --disable-crash-reporter --disable-updates --nogpu +echo ******************************************** +echo *** starting sql-database-projects tests *** +echo ******************************************** +call "%INTEGRATION_TEST_ELECTRON_PATH%" --extensionDevelopmentPath=%~dp0\..\extensions\sql-database-projects --extensionTestsPath=%~dp0\..\extensions\sql-database-projects\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 --disable-telemetry --disable-crash-reporter --disable-updates --nogpu + if %errorlevel% neq 0 exit /b %errorlevel% rmdir /s /q %VSCODEUSERDATADIR% diff --git a/scripts/test-extensions-unit.sh b/scripts/test-extensions-unit.sh index 6395d15a30..591ce615eb 100755 --- a/scripts/test-extensions-unit.sh +++ b/scripts/test-extensions-unit.sh @@ -79,9 +79,9 @@ echo *** starting resource deployment tests *** echo ****************************************** "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX --extensionDevelopmentPath=$ROOT/extensions/resource-deployment --extensionTestsPath=$ROOT/extensions/resource-deployment/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --disable-telemetry --disable-crash-reporter --disable-updates --nogpu -echo ****************************************** +echo ************************************************ echo *** starting machine-learning-services tests *** -echo ****************************************** +echo ************************************************ "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX --extensionDevelopmentPath=$ROOT/extensions/machine-learning-services --extensionTestsPath=$ROOT/extensions/machine-learning-services/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --disable-telemetry --disable-crash-reporter --disable-updates --nogpu # echo ****************************************** @@ -89,5 +89,10 @@ echo ****************************************** # echo ****************************************** # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX --extensionDevelopmentPath=$ROOT/extensions/mssql --extensionTestsPath=$ROOT/extensions/mssql/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --disable-telemetry --disable-crash-reporter --disable-updates --nogpu +echo ******************************************** +echo *** starting sql-database-projects tests *** +echo ******************************************** +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX --extensionDevelopmentPath=$ROOT/extensions/sql-database-projects --extensionTestsPath=$ROOT/extensions/sql-database-projects/out/test --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --disable-telemetry --disable-crash-reporter --disable-updates --nogpu + rm -r $VSCODEUSERDATADIR rm -r $VSCODEEXTDIR