Adding SQL Edge project template (#13558)

* Checkpoint

* removing flag for not creating subfolder

* Adding Edge template

* Removing janky map function

* Adding templates for additional objects

* Updating tests, fixing bug

* Added Edge project icon

* Updating strings to Drew-approved text

* Cleaning up template scripts and Edge project template names
This commit is contained in:
Benjin Dubishar
2020-12-03 10:33:31 -08:00
committed by GitHub
parent 08735c9434
commit fde031be48
15 changed files with 230 additions and 67 deletions

View File

@@ -23,9 +23,13 @@ export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.S
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
// Project Provider
export const sqlDatabaseProjectTypeId = 'sqldbproj';
export const projectTypeDisplayName = localize('projectTypeDisplayName', "SQL Database");
export const projectTypeDescription = localize('projectTypeDescription', "Design, edit, and publish schemas for SQL databases");
export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj';
export const emptyProjectTypeDisplayName = localize('emptyProjectTypeDisplayName', "SQL Database");
export const emptyProjectTypeDescription = localize('emptyProjectTypeDescription', "Develop and publish schemas for SQL databases starting from an empty project");
export const edgeSqlDatabaseProjectTypeId = 'SqlDbEdgeProj';
export const edgeProjectTypeDisplayName = localize('edgeProjectTypeDisplayName', "SQL Edge");
export const edgeProjectTypeDescription = localize('edgeProjectTypeDescription', "Start with the core pieces to develop and publish schemas for SQL Edge");
// commands
export const revealFileInOsCommand = 'revealFileInOS';
@@ -196,6 +200,9 @@ 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");
export const dataSourceFriendlyName = localize('dataSource', "Data Source");
export const fileFormatFriendlyName = localize('fileFormat', "File Format");
export const externalStreamFriendlyName = localize('externalStream', "External Stream");
export const externalStreamingJobFriendlyName = localize('externalStreamingJobFriendlyName', "External Streaming Job");
export const preDeployScriptFriendlyName = localize('preDeployScriptFriendlyName', "Script.PreDeployment");
export const postDeployScriptFriendlyName = localize('postDeployScriptFriendlyName', "Script.PostDeployment");

View File

@@ -14,6 +14,7 @@ export class IconPathHelper {
private static extensionContext: vscode.ExtensionContext;
public static databaseProject: IconPath;
public static colorfulSqlProject: IconPath;
public static sqlEdgeProject: IconPath;
public static dataSourceGroup: IconPath;
public static dataSourceSql: IconPath;
@@ -33,6 +34,7 @@ export class IconPathHelper {
IconPathHelper.databaseProject = IconPathHelper.makeIcon('databaseProject');
IconPathHelper.colorfulSqlProject = IconPathHelper.makeIcon('colorfulSqlProject', true);
IconPathHelper.sqlEdgeProject = IconPathHelper.makeIcon('sqlEdgeProject', true);
IconPathHelper.dataSourceGroup = IconPathHelper.makeIcon('dataSourceGroup');
IconPathHelper.dataSourceSql = IconPathHelper.makeIcon('dataSource-sql');

View File

@@ -56,40 +56,35 @@ export class ProjectsController {
* @param folderUri
* @param projectGuid
*/
public async createNewProject(newProjName: string, folderUri: vscode.Uri, makeOwnFolder: boolean, projectGuid?: string): Promise<string> {
if (projectGuid && !UUID.isUUID(projectGuid)) {
throw new Error(`Specified GUID is invalid: '${projectGuid}'`);
public async createNewProject(creationParams: NewProjectParams): Promise<string> {
if (creationParams.projectGuid && !UUID.isUUID(creationParams.projectGuid)) {
throw new Error(`Specified GUID is invalid: '${creationParams.projectGuid}'`);
}
const macroDict: Record<string, string> = {
'PROJECT_NAME': newProjName,
'PROJECT_GUID': projectGuid ?? UUID.generateUuid().toUpperCase()
'PROJECT_NAME': creationParams.newProjName,
'PROJECT_GUID': creationParams.projectGuid ?? UUID.generateUuid().toUpperCase()
};
let newProjFileContents = this.macroExpansion(templates.newSqlProjectTemplate, macroDict);
let newProjFileContents = templates.macroExpansion(templates.newSqlProjectTemplate, macroDict);
let newProjFileName = newProjName;
let newProjFileName = creationParams.newProjName;
if (!newProjFileName.toLowerCase().endsWith(constants.sqlprojExtension)) {
newProjFileName += constants.sqlprojExtension;
}
const newProjFilePath = makeOwnFolder ? path.join(folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName) : path.join(folderUri.fsPath, newProjFileName);
const newProjFilePath = path.join(creationParams.folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName);
let fileExists = false;
try {
await fs.access(newProjFilePath);
fileExists = true;
}
catch { } // file doesn't already exist
if (fileExists) {
if (await utils.exists(newProjFilePath)) {
throw new Error(constants.projectAlreadyExists(newProjFileName, path.parse(newProjFilePath).dir));
}
await fs.mkdir(path.dirname(newProjFilePath), { recursive: true });
await fs.writeFile(newProjFilePath, newProjFileContents);
await this.addTemplateFiles(newProjFilePath, creationParams.projectTypeId);
return newProjFilePath;
}
@@ -256,7 +251,7 @@ export class ProjectsController {
}
}
const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()];
const itemType = templates.get(itemTypeName);
const absolutePathToParent = path.join(project.projectFolderPath, relativePath);
let itemObjectName = await this.promptForNewObjectName(itemType, project, absolutePathToParent, constants.sqlFileExtension);
@@ -266,18 +261,10 @@ export class ProjectsController {
return; // user cancelled
}
const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
const newFileText = templates.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
const relativeFilePath = path.join(relativePath, itemObjectName + constants.sqlFileExtension);
try {
// check if file already exists
const absoluteFilePath = path.join(project.projectFolderPath, relativeFilePath);
const fileExists = await utils.exists(absoluteFilePath);
if (fileExists) {
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
}
const newEntry = await project.addScriptItem(relativeFilePath, newFileText, itemType.type);
await vscode.commands.executeCommand(constants.vscodeOpenCommand, newEntry.fsUri);
@@ -460,6 +447,12 @@ export class ProjectsController {
return addDatabaseReferenceDialog;
}
/**
* Adds a database reference to a project, after selections have been made in the dialog
* @param project project to which to add the database reference
* @param settings settings for the database reference
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async addDatabaseReferenceCallback(project: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings, context: dataworkspace.WorkspaceTreeItem): Promise<void> {
try {
if ((<IProjectReferenceSettings>settings).projectName !== undefined) {
@@ -494,6 +487,11 @@ export class ProjectsController {
}
}
/**
* Validates the contents of an external streaming job's query against the last-built dacpac.
* If no dacpac exists at the output path, one will be built first.
* @param node a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise<mssql.ValidateStreamingJobResult> {
const project: Project = this.getProjectFromContext(node);
@@ -547,6 +545,29 @@ export class ProjectsController {
}
}
private async addTemplateFiles(newProjFilePath: string, projectTypeId: string): Promise<void> {
if (projectTypeId === constants.emptySqlDatabaseProjectTypeId || newProjFilePath === '') {
return;
}
if (projectTypeId === constants.edgeSqlDatabaseProjectTypeId) {
const project = await Project.openProject(newProjFilePath);
await this.createFileFromTemplate(project, templates.get(templates.table), 'DataTable.sql', { 'OBJECT_NAME': 'DataTable' });
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'EdgeHubInputDataSource.sql', { 'OBJECT_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'edgehub://' });
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'SqlOutputDataSource.sql', { 'OBJECT_NAME': 'SqlOutputDataSource', 'LOCATION': 'sqlserver://tcp:.,1433' });
await this.createFileFromTemplate(project, templates.get(templates.fileFormat), 'StreamFileFormat.sql', { 'OBJECT_NAME': 'StreamFileFormat' });
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'EdgeHubInputStream.sql', { 'OBJECT_NAME': 'EdgeHubInputStream', 'DATA_SOURCE_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'input', 'OPTIONS': ',\n\tFILE_FORMAT = StreamFileFormat' });
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'SqlOutputStream.sql', { 'OBJECT_NAME': 'SqlOutputStream', 'DATA_SOURCE_NAME': 'SqlOutputDataSource', 'LOCATION': 'TSQLStreaming.dbo.DataTable', 'OPTIONS': '' });
await this.createFileFromTemplate(project, templates.get(templates.externalStreamingJob), 'EdgeStreamingJob.sql', { 'OBJECT_NAME': 'EdgeStreamingJob' });
}
}
private async createFileFromTemplate(project: Project, itemType: templates.ProjectScriptType, relativePath: string, expansionMacros: Record<string, string>): Promise<void> {
const newFileText = templates.macroExpansion(itemType.templateScript, expansionMacros);
await project.addScriptItem(relativePath, newFileText, itemType.type);
}
private getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Project {
if ('element' in context) {
return context.element.root.project;
@@ -570,21 +591,7 @@ export class ProjectsController {
return (ext.exports as mssql.IExtension).dacFx;
}
private macroExpansion(template: string, macroDict: Record<string, string>): 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 async promptForNewObjectName(itemType: templates.ProjectScriptType, _project: Project, folderPath: string, fileExtension?: string): Promise<string | undefined> {
const suggestedName = itemType.friendlyName.replace(/\s+/g, '');
@@ -632,7 +639,12 @@ export class ProjectsController {
const newProjFolderUri = model.filePath;
const newProjFilePath = await this.createNewProject(model.projName, vscode.Uri.file(newProjFolderUri), true);
const newProjFilePath = await this.createNewProject({
newProjName: model.projName,
folderUri: vscode.Uri.file(newProjFolderUri),
projectTypeId: constants.emptySqlDatabaseProjectTypeId
});
model.filePath = path.dirname(newProjFilePath);
this.setFilePath(model);
@@ -718,3 +730,10 @@ export class ProjectsController {
//#endregion
}
export interface NewProjectParams {
newProjName: string;
folderUri: vscode.Uri;
projectTypeId: string;
projectGuid?: string;
}

View File

@@ -282,19 +282,26 @@ export class Project {
* @param contents Contents to be written to the new file
*/
public async addScriptItem(relativeFilePath: string, contents?: string, itemType?: string): Promise<FileProjectEntry> {
// check if file already exists
const absoluteFilePath = path.join(this.projectFolderPath, relativeFilePath);
if (contents !== undefined && contents !== '' && await utils.exists(absoluteFilePath)) {
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
}
// create the file
if (contents) {
await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true });
await fs.writeFile(absoluteFilePath, contents);
}
//Check that file actually exists
// check that file was successfully created
let exists = await utils.exists(absoluteFilePath);
if (!exists) {
throw new Error(constants.noFileExist(absoluteFilePath));
}
// update sqlproj XML
const fileEntry = this.createFileProjectEntry(relativeFilePath, EntryType.File);
let xmlTag;

View File

@@ -5,7 +5,7 @@
import * as dataworkspace from 'dataworkspace';
import * as vscode from 'vscode';
import { sqlprojExtension, projectTypeDisplayName, projectTypeDescription, sqlDatabaseProjectTypeId } from '../common/constants';
import * as constants from '../common/constants';
import { IconPathHelper } from '../common/iconHelper';
import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider';
import { ProjectsController } from '../controllers/projectController';
@@ -44,11 +44,18 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
*/
get supportedProjectTypes(): dataworkspace.IProjectType[] {
return [{
id: sqlDatabaseProjectTypeId,
projectFileExtension: sqlprojExtension.replace(/\./g, ''),
displayName: projectTypeDisplayName,
description: projectTypeDescription,
id: constants.emptySqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.emptyProjectTypeDisplayName,
description: constants.emptyProjectTypeDescription,
icon: IconPathHelper.colorfulSqlProject
},
{
id: constants.edgeSqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.edgeProjectTypeDisplayName,
description: constants.edgeProjectTypeDescription,
icon: IconPathHelper.sqlEdgeProject
}];
}
@@ -56,10 +63,16 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
* Create a project
* @param name name of the project
* @param location the parent directory
* @param projectTypeId the ID of the project/template
* @returns Uri of the newly created project file
*/
async createProject(name: string, location: vscode.Uri, _: string): Promise<vscode.Uri> {
const projectFile = await this.projectController.createNewProject(name, location, true);
async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> {
const projectFile = await this.projectController.createNewProject({
newProjName: name,
folderUri: location,
projectTypeId: projectTypeId
});
return vscode.Uri.file(projectFile);
}
}

View File

@@ -15,6 +15,9 @@ export const script: string = 'script';
export const table: string = 'table';
export const view: string = 'view';
export const storedProcedure: string = 'storedProcedure';
export const dataSource: string = 'dataSource';
export const fileFormat: string = 'fileFormat';
export const externalStream: string = 'externalStream';
export const externalStreamingJob: string = 'externalStreamingJob';
export const folder: string = 'folder';
@@ -25,12 +28,12 @@ export const postDeployScript: string = 'postDeployScript';
let scriptTypeMap: Record<string, ProjectScriptType> = {};
export function projectScriptTypeMap(): Record<string, ProjectScriptType> {
export function get(key: string): ProjectScriptType {
if (Object.keys(scriptTypeMap).length === 0) {
throw new Error('Templates must be loaded from file before attempting to use.');
}
return scriptTypeMap;
return scriptTypeMap[key.toLocaleLowerCase()];
}
let scriptTypes: ProjectScriptType[] = [];
@@ -44,6 +47,8 @@ export function projectScriptTypes(): ProjectScriptType[] {
}
export async function loadTemplates(templateFolderPath: string) {
reset();
await Promise.all([
Promise.resolve(newSqlProjectTemplate = await loadTemplate(templateFolderPath, 'newSqlProjectTemplate.xml')),
loadObjectTypeInfo(script, constants.scriptFriendlyName, templateFolderPath, 'newTsqlScriptTemplate.sql'),
@@ -52,11 +57,14 @@ export async function loadTemplates(templateFolderPath: string) {
loadObjectTypeInfo(storedProcedure, constants.storedProcedureFriendlyName, templateFolderPath, 'newTsqlStoredProcedureTemplate.sql'),
loadObjectTypeInfo(preDeployScript, constants.preDeployScriptFriendlyName, templateFolderPath, 'newTsqlPreDeployScriptTemplate.sql'),
loadObjectTypeInfo(postDeployScript, constants.postDeployScriptFriendlyName, templateFolderPath, 'newTsqlPostDeployScriptTemplate.sql'),
loadObjectTypeInfo(dataSource, constants.dataSourceFriendlyName, templateFolderPath, 'newTsqlDataSourceTemplate.sql'),
loadObjectTypeInfo(fileFormat, constants.fileFormatFriendlyName, templateFolderPath, 'newTsqlFileFormatTemplate.sql'),
loadObjectTypeInfo(externalStream, constants.externalStreamFriendlyName, templateFolderPath, 'newTsqlExternalStreamTemplate.sql'),
loadObjectTypeInfo(externalStreamingJob, constants.externalStreamingJobFriendlyName, templateFolderPath, 'newTsqlExternalStreamingJobTemplate.sql')
]);
for (const scriptType of scriptTypes) {
if (Object.keys(projectScriptTypeMap).find(s => s === scriptType.type.toLocaleLowerCase() || s === scriptType.friendlyName.toLocaleLowerCase())) {
if (Object.keys(scriptTypeMap).find(s => s === scriptType.type.toLocaleLowerCase() || s === scriptType.friendlyName.toLocaleLowerCase())) {
throw new Error(`Script type map already contains ${scriptType.type} or its friendlyName.`);
}
@@ -65,9 +73,27 @@ export async function loadTemplates(templateFolderPath: string) {
}
}
async function loadObjectTypeInfo(key: string, friendlyName: string, templateFolderPath: string, fileName: string) {
export function macroExpansion(template: string, macroDict: Record<string, string>): 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;
}
async function loadObjectTypeInfo(key: string, friendlyName: string, templateFolderPath: string, fileName: string): Promise<string> {
const template = await loadTemplate(templateFolderPath, fileName);
scriptTypes.push(new ProjectScriptType(key, friendlyName, template));
return key;
}
async function loadTemplate(templateFolderPath: string, fileName: string): Promise<string> {

View File

@@ -16,6 +16,7 @@ import { PublishDatabaseDialog } from '../../dialogs/publishDatabaseDialog';
import { Project } from '../../models/project';
import { ProjectsController } from '../../controllers/projectController';
import { IPublishSettings, IGenerateScriptSettings } from '../../models/IPublishSettings';
import { emptySqlDatabaseProjectTypeId } from '../../common/constants';
describe.skip('Publish Database Dialog', () => {
before(async function (): Promise<void> {
@@ -27,7 +28,13 @@ describe.skip('Publish Database Dialog', () => {
const projController = new ProjectsController();
const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), true, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575');
const projFilePath = await projController.createNewProject({
newProjName: 'TestProjectName',
folderUri: vscode.Uri.file(projFileDir),
projectTypeId: emptySqlDatabaseProjectTypeId,
projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'
});
const project = new Project(projFilePath);
const publishDatabaseDialog = new PublishDatabaseDialog(project);
publishDatabaseDialog.openDialog();
@@ -39,7 +46,13 @@ describe.skip('Publish Database Dialog', () => {
const projFolder = `TestProject_${new Date().getTime()}`;
const projFileDir = path.join(os.tmpdir(), projFolder);
const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), true, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575');
const projFilePath = await projController.createNewProject({
newProjName: 'TestProjectName',
folderUri: vscode.Uri.file(projFileDir),
projectTypeId: emptySqlDatabaseProjectTypeId,
projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'
});
const project = new Project(projFilePath);
const publishDatabaseDialog = new PublishDatabaseDialog(project);
@@ -55,7 +68,7 @@ describe.skip('Publish Database Dialog', () => {
let profile: IPublishSettings | IGenerateScriptSettings | undefined;
const expectedPublish: IPublishSettings = {
const expectedPublish: IPublishSettings = {
databaseName: 'MockDatabaseName',
connectionUri: 'Mock|Connection|Uri',
upgradeExisting: true,

View File

@@ -54,7 +54,12 @@ describe('ProjectsController', function (): void {
const projController = new ProjectsController();
const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), false, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575');
const projFilePath = await projController.createNewProject({
newProjName: 'TestProjectName',
folderUri: vscode.Uri.file(projFileDir),
projectTypeId: constants.emptySqlDatabaseProjectTypeId,
projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'
});
let projFileText = (await fs.readFile(projFilePath)).toString();
@@ -152,6 +157,7 @@ describe('ProjectsController', function (): void {
it('Should delete nested ProjectEntry from node', async function (): Promise<void> {
let proj = await testUtils.createTestProject(templates.newSqlProjectTemplate);
const setupResult = await setupDeleteExcludeTest(proj);
const scriptEntry = setupResult[0], projTreeRoot = setupResult[1], preDeployEntry = setupResult[2], postDeployEntry = setupResult[3], noneEntry = setupResult[4];
@@ -436,7 +442,15 @@ describe('ProjectsController', function (): void {
const createProjectFromDatabaseDialog = TypeMoq.Mock.ofType(CreateProjectFromDatabaseDialog, undefined, undefined, undefined);
createProjectFromDatabaseDialog.callBase = true;
createProjectFromDatabaseDialog.setup(x => x.handleCreateButtonClick()).returns(async () => {
await projController.object.createProjectFromDatabaseCallback( { serverId: 'My Id', database: 'My Database', projName: 'testProject', filePath: 'testLocation', version: '1.0.0.0', extractTarget: mssql.ExtractTarget['schemaObjectType'] });
await projController.object.createProjectFromDatabaseCallback({
serverId: 'My Id',
database: 'My Database',
projName: 'testProject',
filePath: 'testLocation',
version: '1.0.0.0',
extractTarget: mssql.ExtractTarget['schemaObjectType']
});
return Promise.resolve(undefined);
});

View File

@@ -14,8 +14,8 @@ describe('Templates: loading templates from disk', function (): void {
});
it('Should throw error when attempting to use templates before loaded from file', async function (): Promise<void> {
await shouldThrowSpecificError(() => templates.projectScriptTypeMap(), 'Templates must be loaded from file before attempting to use.');
await shouldThrowSpecificError(() => templates.projectScriptTypes(), 'Templates must be loaded from file before attempting to use.');
await shouldThrowSpecificError(() => templates.get('foobar'), 'Templates must be loaded from file before attempting to use.');
await shouldThrowSpecificError(() => templates.get('foobar'), 'Templates must be loaded from file before attempting to use.');
});
it('Should load all templates from files', async function (): Promise<void> {
@@ -23,7 +23,7 @@ describe('Templates: loading templates from disk', function (): void {
// check expected counts
const numScriptObjectTypes = 7;
const numScriptObjectTypes = 10;
should(templates.projectScriptTypes().length).equal(numScriptObjectTypes);
should(Object.keys(templates.projectScriptTypes()).length).equal(numScriptObjectTypes);

View File

@@ -28,6 +28,7 @@ export async function shouldThrowSpecificError(block: Function, expectedMessage:
}
export async function createTestSqlProjFile(contents: string, folderPath?: string): Promise<string> {
folderPath = folderPath ?? path.join(await generateTestFolderPath(), 'TestProject');
return await createTestFile(contents, 'TestProject.sqlproj', folderPath);
}
@@ -40,7 +41,7 @@ export async function createTestDataSources(contents: string, folderPath?: strin
}
export async function generateTestFolderPath(): Promise<string> {
const folderPath = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
const folderPath = path.join(os.tmpdir(), `TestRun_${new Date().getTime()}`);
await fs.mkdir(folderPath, { recursive: true });
return folderPath;
@@ -55,6 +56,7 @@ export async function createTestFile(contents: string, fileName: string, folderP
return filePath;
}
/**
* TestFolder directory structure
* - file1.sql