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

@@ -31,5 +31,4 @@ export class DataWorkspaceExtension implements IExtension {
validateWorkspace(): Promise<boolean> { validateWorkspace(): Promise<boolean> {
return this.workspaceService.validateWorkspace(); return this.workspaceService.validateWorkspace();
} }
} }

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

View File

@@ -768,7 +768,7 @@ export class ProjectsController {
const project = await Project.openProject(newProjFilePath); const project = await Project.openProject(newProjFilePath);
await this.createProjectFromDatabaseApiCall(model); // Call ExtractAPI in DacFx Service 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 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. * Generate a flat list of all files and folder under a folder.
*/ */
public async generateList(absolutePath: string): Promise<string[]> { public async generateList(absolutePath: string): Promise<vscode.Uri[]> {
let fileFolderList: string[] = []; let fileFolderList: vscode.Uri[] = [];
if (!await utils.exists(absolutePath)) { if (!await utils.exists(absolutePath)) {
if (await utils.exists(absolutePath + constants.sqlFileExtension)) { if (await utils.exists(absolutePath + constants.sqlFileExtension)) {
@@ -831,13 +831,13 @@ export class ProjectsController {
const stat = await fs.stat(filepath); const stat = await fs.stat(filepath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
fileFolderList.push(filepath); fileFolderList.push(vscode.Uri.file(filepath));
(await fs (await fs
.readdir(filepath)) .readdir(filepath))
.forEach((f: string) => files.push(path.join(filepath, f))); .forEach((f: string) => files.push(path.join(filepath, f)));
} }
else if (stat.isFile()) { 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 * as vscode from 'vscode';
import MainController from './controllers/mainController'; import MainController from './controllers/mainController';
import { IProjectProvider } from 'dataworkspace'; import { SqlDatabaseProjectProvider } from './projectProvider/projectProvider';
let controllers: MainController[] = []; let controllers: MainController[] = [];
export function activate(context: vscode.ExtensionContext): Promise<IProjectProvider> { export function activate(context: vscode.ExtensionContext): Promise<SqlDatabaseProjectProvider> {
// Start the main controller // Start the main controller
const mainController = new MainController(context); const mainController = new MainController(context);
controllers.push(mainController); controllers.push(mainController);

View File

@@ -285,20 +285,20 @@ export class Project {
* @param contents Contents to be written to the new file * @param contents Contents to be written to the new file
*/ */
public async addScriptItem(relativeFilePath: string, contents?: string, itemType?: string): Promise<FileProjectEntry> { public async addScriptItem(relativeFilePath: string, contents?: string, itemType?: string): Promise<FileProjectEntry> {
// check if file already exists
const absoluteFilePath = path.join(this.projectFolderPath, relativeFilePath); 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)) { if (contents !== undefined && contents !== '' && await utils.exists(absoluteFilePath)) {
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name)); throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
} }
// create the file // create the file if contents were passed in
if (contents) { if (contents) {
await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true }); await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true });
await fs.writeFile(absoluteFilePath, contents); await fs.writeFile(absoluteFilePath, contents);
} }
// check that file was successfully created // check that file exists
let exists = await utils.exists(absoluteFilePath); let exists = await utils.exists(absoluteFilePath);
if (!exists) { if (!exists) {
throw new Error(constants.noFileExist(absoluteFilePath)); 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 * 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++) { if (!exists) {
let file: string = list[i]; throw new Error(constants.fileOrFolderDoesNotExist(file.fsPath));
const relativePath = utils.trimChars(utils.trimUri(Uri.file(this.projectFilePath), Uri.file(file)), '/'); }
}
for (let file of list) {
const relativePath = utils.trimChars(utils.trimUri(Uri.file(this.projectFilePath), file), '/');
if (relativePath.length > 0) { 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); await this.addScriptItem(relativePath);
} } else if (fileStat.isDirectory()) {
else if (fileStat.isDirectory()) {
await this.addFolderItem(relativePath); await this.addFolderItem(relativePath);
} }
} }

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as dataworkspace from 'dataworkspace'; import * as dataworkspace from 'dataworkspace';
import * as sqldbproj from 'sqldbproj';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import { IconPathHelper } from '../common/iconHelper'; import { IconPathHelper } from '../common/iconHelper';
@@ -12,7 +13,7 @@ import { ProjectsController } from '../controllers/projectController';
import { Project } from '../models/project'; import { Project } from '../models/project';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider { export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider, sqldbproj.IExtension {
constructor(private projectController: ProjectsController) { constructor(private projectController: ProjectsController) {
} }
@@ -74,4 +75,14 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
return vscode.Uri.file(projectFile); 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); projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath); 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); await project.addToProject(list);
@@ -137,13 +137,13 @@ describe('Project: sqlproj content operations', function (): void {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath); const project = await Project.openProject(projFilePath);
let list: string[] = []; let list: Uri[] = [];
let testFolderPath: string = await testUtils.createDummyFileStructure(true, list, path.dirname(projFilePath)); let testFolderPath: string = await testUtils.createDummyFileStructure(true, list, path.dirname(projFilePath));
const nonexistentFile = path.join(testFolderPath, 'nonexistentFile.sql'); 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> { 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 should = require('should');
import { AssertionError } from 'assert'; import { AssertionError } from 'assert';
import { Project } from '../models/project'; import { Project } from '../models/project';
import { Uri } from 'vscode';
export async function shouldThrowSpecificError(block: Function, expectedMessage: string, details?: string) { export async function shouldThrowSpecificError(block: Function, expectedMessage: string, details?: string) {
let succeeded = false; 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 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 * @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(); testFolderPath = testFolderPath ?? await generateTestFolderPath();
let filePath = path.join(testFolderPath, 'file1.sql'); let filePath = path.join(testFolderPath, 'file1.sql');
await fs.writeFile(filePath, ''); await fs.writeFile(filePath, '');
if (createList) { if (createList) {
list?.push(testFolderPath); list?.push(Uri.file(testFolderPath));
list?.push(filePath); list?.push(Uri.file(filePath));
} }
for (let dirCount = 1; dirCount <= 2; dirCount++) { 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 }); await fs.mkdir(dirName, { recursive: true });
if (createList) { if (createList) {
list?.push(dirName); list?.push(Uri.file(dirName));
} }
for (let fileCount = 1; fileCount <= 5; fileCount++) { for (let fileCount = 1; fileCount <= 5; fileCount++) {
let fileName = path.join(dirName, `file${fileCount}.sql`); let fileName = path.join(dirName, `file${fileCount}.sql`);
await fs.writeFile(fileName, ''); await fs.writeFile(fileName, '');
if (createList) { 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 touchFile(filePath);
await fs.writeFile(filePath, ''); await fs.writeFile(filePath, '');
if (createList) { if (createList) {
list?.push(filePath); list?.push(Uri.file(filePath));
} }
return testFolderPath; return testFolderPath;
} }
export async function createListOfFiles(filePath?: string): Promise<string[]> { export async function createListOfFiles(filePath?: string): Promise<Uri[]> {
let fileFolderList: string[] = []; let fileFolderList: Uri[] = [];
await createDummyFileStructure(true, fileFolderList, filePath); await createDummyFileStructure(true, fileFolderList, filePath);