Expose adding files and folders in sql database projects (#14391)

* expose addToProject in dataworkspace.d.ts

* remove changes in data workspace extension

* add sqldbproj.d.ts

* change list to be Uris instead of strings

* don't add files/folders if any don't exist

* fix test on windows
This commit is contained in:
Kim Santiago
2021-02-23 18:15:38 -08:00
committed by GitHub
parent d5385f66d3
commit c05cece683
10 changed files with 85 additions and 35 deletions

View File

@@ -170,6 +170,7 @@ export const externalStreamingJobValidationPassed = localize('externalStreamingJ
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); }
export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); }
export function fileOrFolderDoesNotExist(name: string) { return localize('fileOrFolderDoesNotExist', "File or directory '{0}' doesn't exist", name); }
export function cannotResolvePath(path: string) { return localize('cannotResolvePath', "Cannot resolve path {0}", path); }
export function fileAlreadyExists(filename: string) { return localize('fileAlreadyExists', "A file with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }
export function folderAlreadyExists(filename: string) { return localize('folderAlreadyExists', "A folder with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }

View File

@@ -11,7 +11,7 @@ import * as path from 'path';
import { ProjectsController } from './projectController';
import { NetCoreTool } from '../tools/netcoreTool';
import { IconPathHelper } from '../common/iconHelper';
import { IProjectProvider, WorkspaceTreeItem } from 'dataworkspace';
import { WorkspaceTreeItem } from 'dataworkspace';
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
/**
@@ -37,7 +37,7 @@ export default class MainController implements vscode.Disposable {
public deactivate(): void {
}
public async activate(): Promise<IProjectProvider> {
public async activate(): Promise<SqlDatabaseProjectProvider> {
await this.initializeDatabaseProjects();
return new SqlDatabaseProjectProvider(this.projectsController);
}

View File

@@ -768,7 +768,7 @@ export class ProjectsController {
const project = await Project.openProject(newProjFilePath);
await this.createProjectFromDatabaseApiCall(model); // Call ExtractAPI in DacFx Service
let fileFolderList: string[] = model.extractTarget === mssql.ExtractTarget.file ? [model.filePath] : await this.generateList(model.filePath); // Create a list of all the files and directories to be added to project
let fileFolderList: vscode.Uri[] = model.extractTarget === mssql.ExtractTarget.file ? [vscode.Uri.file(model.filePath)] : await this.generateList(model.filePath); // Create a list of all the files and directories to be added to project
await project.addToProject(fileFolderList); // Add generated file structure to the project
@@ -811,8 +811,8 @@ export class ProjectsController {
/**
* Generate a flat list of all files and folder under a folder.
*/
public async generateList(absolutePath: string): Promise<string[]> {
let fileFolderList: string[] = [];
public async generateList(absolutePath: string): Promise<vscode.Uri[]> {
let fileFolderList: vscode.Uri[] = [];
if (!await utils.exists(absolutePath)) {
if (await utils.exists(absolutePath + constants.sqlFileExtension)) {
@@ -831,13 +831,13 @@ export class ProjectsController {
const stat = await fs.stat(filepath);
if (stat.isDirectory()) {
fileFolderList.push(filepath);
fileFolderList.push(vscode.Uri.file(filepath));
(await fs
.readdir(filepath))
.forEach((f: string) => files.push(path.join(filepath, f)));
}
else if (stat.isFile()) {
fileFolderList.push(filepath);
fileFolderList.push(vscode.Uri.file(filepath));
}
}

View File

@@ -5,11 +5,11 @@
import * as vscode from 'vscode';
import MainController from './controllers/mainController';
import { IProjectProvider } from 'dataworkspace';
import { SqlDatabaseProjectProvider } from './projectProvider/projectProvider';
let controllers: MainController[] = [];
export function activate(context: vscode.ExtensionContext): Promise<IProjectProvider> {
export function activate(context: vscode.ExtensionContext): Promise<SqlDatabaseProjectProvider> {
// Start the main controller
const mainController = new MainController(context);
controllers.push(mainController);

View File

@@ -285,20 +285,20 @@ 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);
// check if file already exists if content was passed to write to a new file
if (contents !== undefined && contents !== '' && await utils.exists(absoluteFilePath)) {
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
}
// create the file
// create the file if contents were passed in
if (contents) {
await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true });
await fs.writeFile(absoluteFilePath, contents);
}
// check that file was successfully created
// check that file exists
let exists = await utils.exists(absoluteFilePath);
if (!exists) {
throw new Error(constants.noFileExist(absoluteFilePath));
@@ -928,21 +928,27 @@ export class Project {
/**
* Adds the list of sql files and directories to the project, and saves the project file
* @param absolutePath Absolute path of the folder
* @param list list of files and folder Uris. Files and folders must already exist. No files or folders will be added if any do not exist.
*/
public async addToProject(list: string[]): Promise<void> {
public async addToProject(list: Uri[]): Promise<void> {
// verify all files/folders exist. If not all exist, none will be added
for (let file of list) {
const exists = await utils.exists(file.fsPath);
for (let i = 0; i < list.length; i++) {
let file: string = list[i];
const relativePath = utils.trimChars(utils.trimUri(Uri.file(this.projectFilePath), Uri.file(file)), '/');
if (!exists) {
throw new Error(constants.fileOrFolderDoesNotExist(file.fsPath));
}
}
for (let file of list) {
const relativePath = utils.trimChars(utils.trimUri(Uri.file(this.projectFilePath), file), '/');
if (relativePath.length > 0) {
let fileStat = await fs.stat(file);
const fileStat = await fs.stat(file.fsPath);
if (fileStat.isFile() && file.toLowerCase().endsWith(constants.sqlFileExtension)) {
if (fileStat.isFile() && file.fsPath.toLowerCase().endsWith(constants.sqlFileExtension)) {
await this.addScriptItem(relativePath);
}
else if (fileStat.isDirectory()) {
} else if (fileStat.isDirectory()) {
await this.addFolderItem(relativePath);
}
}

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dataworkspace from 'dataworkspace';
import * as sqldbproj from 'sqldbproj';
import * as vscode from 'vscode';
import * as constants from '../common/constants';
import { IconPathHelper } from '../common/iconHelper';
@@ -12,7 +13,7 @@ import { ProjectsController } from '../controllers/projectController';
import { Project } from '../models/project';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider {
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider, sqldbproj.IExtension {
constructor(private projectController: ProjectsController) {
}
@@ -74,4 +75,14 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
return vscode.Uri.file(projectFile);
}
/**
* Adds the list of files and directories to the project, and saves the project file
* @param projectFile The Uri of the project file
* @param list list of uris of files and folders to add. Files and folders must already exist. Files and folders must already exist. No files or folders will be added if any do not exist.
*/
async addToProject(projectFile: vscode.Uri, list: vscode.Uri[]): Promise<void> {
const project = await Project.openProject(projectFile.fsPath);
await project.addToProject(list);
}
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'sqldbproj' {
import * as vscode from 'vscode';
export const enum extension {
name = 'Microsoft.sql-database-projects'
}
/**
* sql database projects extension
*/
export interface IExtension {
/**
* 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
*/
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
/**
* Adds the list of files and directories to the project, and saves the project file
* @param projectFile The Uri of the project file
* @param list list of uris of files and folders to add. Files and folders must already exist. No files or folders will be added if any do not exist.
*/
addToProject(projectFile: vscode.Uri, list: vscode.Uri[]): Promise<void>;
}
}

View File

@@ -125,7 +125,7 @@ describe('Project: sqlproj content operations', function (): void {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
let list: string[] = await testUtils.createListOfFiles(path.dirname(projFilePath));
let list: Uri[] = await testUtils.createListOfFiles(path.dirname(projFilePath));
await project.addToProject(list);
@@ -137,13 +137,13 @@ describe('Project: sqlproj content operations', function (): void {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
let list: string[] = [];
let list: Uri[] = [];
let testFolderPath: string = await testUtils.createDummyFileStructure(true, list, path.dirname(projFilePath));
const nonexistentFile = path.join(testFolderPath, 'nonexistentFile.sql');
list.push(nonexistentFile);
list.push(Uri.file(nonexistentFile));
await testUtils.shouldThrowSpecificError(async () => await project.addToProject(list), `ENOENT: no such file or directory, stat \'${nonexistentFile}\'`);
await testUtils.shouldThrowSpecificError(async () => await project.addToProject(list), constants.fileOrFolderDoesNotExist(Uri.file(nonexistentFile).fsPath));
});
it('Should choose correct master dacpac', async function (): Promise<void> {

View File

@@ -11,6 +11,7 @@ import { promises as fs } from 'fs';
import should = require('should');
import { AssertionError } from 'assert';
import { Project } from '../models/project';
import { Uri } from 'vscode';
export async function shouldThrowSpecificError(block: Function, expectedMessage: string, details?: string) {
let succeeded = false;
@@ -77,14 +78,14 @@ export async function createTestFile(contents: string, fileName: string, folderP
* @param createList Boolean specifying to create a list of the files and folders been created
* @param list List of files and folders that are been created
*/
export async function createDummyFileStructure(createList?: boolean, list?: string[], testFolderPath?: string): Promise<string> {
export async function createDummyFileStructure(createList?: boolean, list?: Uri[], testFolderPath?: string): Promise<string> {
testFolderPath = testFolderPath ?? await generateTestFolderPath();
let filePath = path.join(testFolderPath, 'file1.sql');
await fs.writeFile(filePath, '');
if (createList) {
list?.push(testFolderPath);
list?.push(filePath);
list?.push(Uri.file(testFolderPath));
list?.push(Uri.file(filePath));
}
for (let dirCount = 1; dirCount <= 2; dirCount++) {
@@ -92,14 +93,14 @@ export async function createDummyFileStructure(createList?: boolean, list?: stri
await fs.mkdir(dirName, { recursive: true });
if (createList) {
list?.push(dirName);
list?.push(Uri.file(dirName));
}
for (let fileCount = 1; fileCount <= 5; fileCount++) {
let fileName = path.join(dirName, `file${fileCount}.sql`);
await fs.writeFile(fileName, '');
if (createList) {
list?.push(fileName);
list?.push(Uri.file(fileName));
}
}
}
@@ -108,14 +109,14 @@ export async function createDummyFileStructure(createList?: boolean, list?: stri
//await touchFile(filePath);
await fs.writeFile(filePath, '');
if (createList) {
list?.push(filePath);
list?.push(Uri.file(filePath));
}
return testFolderPath;
}
export async function createListOfFiles(filePath?: string): Promise<string[]> {
let fileFolderList: string[] = [];
export async function createListOfFiles(filePath?: string): Promise<Uri[]> {
let fileFolderList: Uri[] = [];
await createDummyFileStructure(true, fileFolderList, filePath);