Implement a no sync rule (#7216)

* implement a no sync rule

* fix linting disable

* fix unused imports

* exclude more testing

* clean up fs usage

* clean up more fs usage

* remove duplicate of code

* fix compile errors
This commit is contained in:
Anthony Dresser
2019-09-17 13:32:42 -07:00
committed by GitHub
parent 4d62983680
commit 28d453fced
31 changed files with 305 additions and 201 deletions

View File

@@ -0,0 +1,33 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const minimatch = require("minimatch");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const args = this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
exports.Rule = Rule;
class NoSyncRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitCallExpression(node) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}
NoSyncRuleWalker.operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
interface NoSyncRuleConfig {
exclude: string[];
}
export class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const args = <NoSyncRuleConfig>this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
class NoSyncRuleWalker extends Lint.RuleWalker {
private static readonly operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
visitCallExpression(node: ts.CallExpression) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}

View File

@@ -10,8 +10,7 @@ import * as vscode from 'vscode';
import { TelemetryReporter, TelemetryViews } from './telemetry'; import { TelemetryReporter, TelemetryViews } from './telemetry';
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils'; import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
import { ChildProcess, exec } from 'child_process'; import { ChildProcess, exec } from 'child_process';
import { promisify } from 'util'; import { promises as fs } from 'fs';
import { readFile } from 'fs';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -68,7 +67,7 @@ export interface LaunchSsmsDialogParams {
export async function activate(context: vscode.ExtensionContext): Promise<void> { export async function activate(context: vscode.ExtensionContext): Promise<void> {
// This is for Windows-specific support so do nothing on other platforms // This is for Windows-specific support so do nothing on other platforms
if (process.platform === 'win32') { if (process.platform === 'win32') {
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json')); const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
const ssmsMinVer = JSON.parse(rawConfig.toString()).version; const ssmsMinVer = JSON.parse(rawConfig.toString()).version;
exePath = path.join(context.extensionPath, 'ssmsmin', 'Windows', ssmsMinVer, 'ssmsmin.exe'); exePath = path.join(context.extensionPath, 'ssmsmin', 'Windows', ssmsMinVer, 'ssmsmin.exe');
registerCommands(context); registerCommands(context);

View File

@@ -6,9 +6,7 @@
'use strict'; 'use strict';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import { promisify } from 'util';
export class AgentUtils { export class AgentUtils {
@@ -52,18 +50,11 @@ export class AgentUtils {
} }
export function exists(path: string): Promise<boolean> { export async function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path); try {
await fs.access(path);
return true;
} catch (e) {
return false;
} }
export function mkdir(path: string): Promise<void> {
return promisify(fs.mkdir)(path);
}
export function unlink(path: string): Promise<void> {
return promisify(fs.unlink)(path);
}
export function writeFile(path: string, data: string): Promise<void> {
return promisify(fs.writeFile)(path, data);
} }

View File

@@ -7,12 +7,10 @@
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { AgentUtils } from '../agentUtils'; import { AgentUtils, exists } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { IAgentDialogData, AgentDialogMode } from '../interfaces';
import { NotebookDialogOptions } from '../dialogs/notebookDialog'; import { NotebookDialogOptions } from '../dialogs/notebookDialog';
import { createConnection } from 'net';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const NotebookCompletionActionCondition_Always: string = localize('notebookData.whenJobCompletes', 'When the notebook completes'); const NotebookCompletionActionCondition_Always: string = localize('notebookData.whenJobCompletes', 'When the notebook completes');
@@ -179,7 +177,7 @@ export class NotebookData implements IAgentDialogData {
} }
} }
public validate(): { valid: boolean, errorMessages: string[] } { public async validate(): Promise<{ valid: boolean, errorMessages: string[] }> {
let validationErrors: string[] = []; let validationErrors: string[] = [];
if (this.dialogMode !== AgentDialogMode.EDIT) { if (this.dialogMode !== AgentDialogMode.EDIT) {
if (!(this.name && this.name.trim())) { if (!(this.name && this.name.trim())) {
@@ -188,7 +186,7 @@ export class NotebookData implements IAgentDialogData {
if (!(this.templatePath && this.name.trim())) { if (!(this.templatePath && this.name.trim())) {
validationErrors.push(TemplatePathEmptyErrorMessage); validationErrors.push(TemplatePathEmptyErrorMessage);
} }
if (!fs.existsSync(this.templatePath)) { if (!(await exists(this.templatePath))) {
validationErrors.push(InvalidNotebookPathErrorMessage); validationErrors.push(InvalidNotebookPathErrorMessage);
} }
if (NotebookData.jobLists) { if (NotebookData.jobLists) {
@@ -201,7 +199,7 @@ export class NotebookData implements IAgentDialogData {
} }
} }
else { else {
if (this.templatePath && this.templatePath !== '' && !fs.existsSync(this.templatePath)) { if (this.templatePath && this.templatePath !== '' && !(await exists(this.templatePath))) {
validationErrors.push(InvalidNotebookPathErrorMessage); validationErrors.push(InvalidNotebookPathErrorMessage);
} }
} }

View File

@@ -87,9 +87,9 @@ export class NotebookDialog extends AgentDialog<NotebookData> {
this.generalTab = azdata.window.createTab(GeneralTabText); this.generalTab = azdata.window.createTab(GeneralTabText);
this.initializeGeneralTab(); this.initializeGeneralTab();
this.dialog.content = [this.generalTab]; this.dialog.content = [this.generalTab];
this.dialog.registerCloseValidator(() => { this.dialog.registerCloseValidator(async () => {
this.updateModel(); this.updateModel();
let validationResult = this.model.validate(); let validationResult = await this.model.validate();
if (!validationResult.valid) { if (!validationResult.valid) {
// TODO: Show Error Messages // TODO: Show Error Messages
this.dialog.message = { text: validationResult.errorMessages[0] }; this.dialog.message = { text: validationResult.errorMessages[0] };

View File

@@ -7,7 +7,7 @@
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { AlertDialog } from './dialogs/alertDialog'; import { AlertDialog } from './dialogs/alertDialog';
@@ -17,9 +17,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog'; import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
import { JobData } from './data/jobData'; import { JobData } from './data/jobData';
import { AgentUtils, exists, mkdir, unlink, writeFile } from './agentUtils'; import { AgentUtils, exists } from './agentUtils';
import { NotebookDialog, NotebookDialogOptions } from './dialogs/notebookDialog'; import { NotebookDialog, NotebookDialogOptions } from './dialogs/notebookDialog';
import { promisify } from 'util';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -102,7 +101,7 @@ export class MainController {
let templateMap = this.notebookTemplateMap.get(nbEditor.document.uri.toString()); let templateMap = this.notebookTemplateMap.get(nbEditor.document.uri.toString());
let vsEditor = await vscode.workspace.openTextDocument(templateMap.fileUri); let vsEditor = await vscode.workspace.openTextDocument(templateMap.fileUri);
let content = vsEditor.getText(); let content = vsEditor.getText();
promisify(fs.writeFile)(templateMap.tempPath, content); await fs.writeFile(templateMap.tempPath, content);
AgentUtils.getAgentService().then(async (agentService) => { AgentUtils.getAgentService().then(async (agentService) => {
let result = await agentService.updateNotebook(templateMap.ownerUri, templateMap.notebookInfo.name, templateMap.notebookInfo, templateMap.tempPath); let result = await agentService.updateNotebook(templateMap.ownerUri, templateMap.notebookInfo.name, templateMap.notebookInfo, templateMap.tempPath);
if (result.success) { if (result.success) {
@@ -128,14 +127,13 @@ export class MainController {
vscode.commands.registerCommand('agent.openNotebookEditorFromJsonString', async (filename: string, jsonNotebook: string, notebookInfo?: azdata.AgentNotebookInfo, ownerUri?: string) => { vscode.commands.registerCommand('agent.openNotebookEditorFromJsonString', async (filename: string, jsonNotebook: string, notebookInfo?: azdata.AgentNotebookInfo, ownerUri?: string) => {
const tempfilePath = path.join(os.tmpdir(), 'mssql_notebooks', filename + '.ipynb'); const tempfilePath = path.join(os.tmpdir(), 'mssql_notebooks', filename + '.ipynb');
if (!await exists(path.join(os.tmpdir(), 'mssql_notebooks'))) { if (!await exists(path.join(os.tmpdir(), 'mssql_notebooks'))) {
await mkdir(path.join(os.tmpdir(), 'mssql_notebooks')); await fs.mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
} }
let editors = azdata.nb.visibleNotebookEditors;
if (await exists(tempfilePath)) { if (await exists(tempfilePath)) {
await unlink(tempfilePath); await fs.unlink(tempfilePath);
} }
try { try {
await writeFile(tempfilePath, jsonNotebook); await fs.writeFile(tempfilePath, jsonNotebook);
let uri = vscode.Uri.parse(`untitled:${path.basename(tempfilePath)}`); let uri = vscode.Uri.parse(`untitled:${path.basename(tempfilePath)}`);
if (notebookInfo) { if (notebookInfo) {
this.notebookTemplateMap.set(uri.toString(), { notebookInfo: notebookInfo, fileUri: uri, ownerUri: ownerUri, tempPath: tempfilePath }); this.notebookTemplateMap.set(uri.toString(), { notebookInfo: notebookInfo, fileUri: uri, ownerUri: ownerUri, tempPath: tempfilePath });

View File

@@ -6,7 +6,7 @@
import * as adal from 'adal-node'; import * as adal from 'adal-node';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as fs from 'fs'; import { promises as fs } from 'fs';
export default class TokenCache implements adal.TokenCache { export default class TokenCache implements adal.TokenCache {
private static CipherAlgorithm = 'aes-256-cbc'; private static CipherAlgorithm = 'aes-256-cbc';
@@ -58,23 +58,19 @@ export default class TokenCache implements adal.TokenCache {
}); });
} }
public clear(): Thenable<void> { public async clear(): Promise<void> {
let self = this;
// 1) Delete encrypted serialization file // 1) Delete encrypted serialization file
// If we got an 'ENOENT' response, the file doesn't exist, which is fine // If we got an 'ENOENT' response, the file doesn't exist, which is fine
// 3) Delete the encryption key // 3) Delete the encryption key
return new Promise<void>((resolve, reject) => { try {
fs.unlink(self._cacheSerializationPath, err => { await fs.unlink(this._cacheSerializationPath);
if (err && err.code !== 'ENOENT') { } catch (err) {
reject(err); if (err.code !== 'ENOENT') {
} else { throw err;
resolve();
} }
}); }
}) await this._credentialProvider.deleteCredential(this._credentialServiceKey);
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
.then(() => { });
} }
public find(query: any, callback: (error: Error, results: any[]) => void): void { public find(query: any, callback: (error: Error, results: any[]) => void): void {
@@ -230,7 +226,7 @@ export default class TokenCache implements adal.TokenCache {
}); });
} }
private readCache(): Thenable<adal.TokenResponse[]> { private async readCache(): Promise<adal.TokenResponse[]> {
let self = this; let self = this;
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks // NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
@@ -239,13 +235,13 @@ export default class TokenCache implements adal.TokenCache {
// 3) Decrypt the file contents // 3) Decrypt the file contents
// 4) Deserialize and return // 4) Deserialize and return
return this.getOrCreateEncryptionParams() return this.getOrCreateEncryptionParams()
.then(encryptionParams => { .then(async encryptionParams => {
try { try {
return self.decryptCache('utf8', encryptionParams); return self.decryptCache('utf8', encryptionParams);
} catch (e) { } catch (e) {
try { try {
// try to parse using 'binary' encoding and rewrite cache as UTF8 // try to parse using 'binary' encoding and rewrite cache as UTF8
let response = self.decryptCache('binary', encryptionParams); let response = await self.decryptCache('binary', encryptionParams);
self.writeCache(response); self.writeCache(response);
return response; return response;
} catch (e) { } catch (e) {
@@ -260,17 +256,17 @@ export default class TokenCache implements adal.TokenCache {
}); });
} }
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] { private async decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): Promise<adal.TokenResponse[]> {
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions); let cacheCipher = await fs.readFile(this._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector); let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', encoding); let cacheJson = decipher.update(cacheCipher.toString(), 'hex', encoding);
cacheJson += decipher.final(encoding); cacheJson += decipher.final(encoding);
// Deserialize the JSON into the array of tokens // Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson); let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) { for (const obj of cacheObj) {
// Rehydrate Date objects since they will always serialize as a string // Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn); obj.expiresOn = new Date(<string>obj.expiresOn);
} }
return cacheObj; return cacheObj;
@@ -297,7 +293,7 @@ export default class TokenCache implements adal.TokenCache {
// 4) Encrypt the JSON // 4) Encrypt the JSON
// 3) Write to the file // 3) Write to the file
return this.getOrCreateEncryptionParams() return this.getOrCreateEncryptionParams()
.then(encryptionParams => { .then(async encryptionParams => {
try { try {
let cacheJson = JSON.stringify(cache); let cacheJson = JSON.stringify(cache);
@@ -305,7 +301,7 @@ export default class TokenCache implements adal.TokenCache {
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex'); let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
cacheCipher += cipher.final('hex'); cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions); await fs.writeFile(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
} catch (e) { } catch (e) {
throw e; throw e;
} }

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as constants from './constants'; import * as constants from './constants';
@@ -54,12 +54,12 @@ function pushDisposable(disposable: vscode.Disposable): void {
// this method is called when your extension is activated // this method is called when your extension is activated
// your extension is activated the very first time the command is executed // your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
extensionContext = context; extensionContext = context;
const apiWrapper = new ApiWrapper(); const apiWrapper = new ApiWrapper();
let appContext = new AppContext(extensionContext, apiWrapper); let appContext = new AppContext(extensionContext, apiWrapper);
let storagePath = findOrMakeStoragePath(); let storagePath = await findOrMakeStoragePath();
if (!storagePath) { if (!storagePath) {
return undefined; return undefined;
} }
@@ -82,12 +82,21 @@ export function activate(context: vscode.ExtensionContext) {
}; };
} }
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
// Create the folder for storing the token caches // Create the folder for storing the token caches
function findOrMakeStoragePath() { async function findOrMakeStoragePath() {
let storagePath = path.join(getDefaultLogLocation(), constants.extensionName); let storagePath = path.join(getDefaultLogLocation(), constants.extensionName);
try { try {
if (!fs.existsSync(storagePath)) { if (!(await exists(storagePath))) {
fs.mkdirSync(storagePath); await fs.mkdir(storagePath);
console.log('Initialized Azure account extension storage.'); console.log('Initialized Azure account extension storage.');
} }
} }
@@ -124,4 +133,3 @@ function registerCommands(appContext: AppContext, azureResourceTree: AzureResour
registerAzureResourceDatabaseCommands(appContext); registerAzureResourceDatabaseCommands(appContext);
} }

View File

@@ -6,7 +6,7 @@
'use strict'; 'use strict';
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client'; import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
import { IConfig, ServerProvider, Events } from 'service-downloader'; import { ServerProvider, Events } from 'service-downloader';
import { ServerOptions, TransportKind } from 'vscode-languageclient'; import { ServerOptions, TransportKind } from 'vscode-languageclient';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
@@ -18,8 +18,7 @@ import { Telemetry, LanguageClientErrorHandler } from './telemetry';
import * as Constants from '../constants'; import * as Constants from '../constants';
import { TelemetryFeature, FlatFileImportFeature } from './features'; import { TelemetryFeature, FlatFileImportFeature } from './features';
import * as serviceUtils from './serviceUtils'; import * as serviceUtils from './serviceUtils';
import { promisify } from 'util'; import { promises as fs } from 'fs';
import { readFile } from 'fs';
export class ServiceClient { export class ServiceClient {
private statusView: vscode.StatusBarItem; private statusView: vscode.StatusBarItem;
@@ -29,7 +28,7 @@ export class ServiceClient {
} }
public async startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> { public async startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json')); const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
const config = JSON.parse(rawConfig.toString()); const config = JSON.parse(rawConfig.toString());
config.installDirectory = path.join(context.extensionPath, config.installDirectory); config.installDirectory = path.join(context.extensionPath, config.installDirectory);
config.proxy = vscode.workspace.getConfiguration('http').get('proxy'); config.proxy = vscode.workspace.getConfiguration('http').get('proxy');

View File

@@ -28,6 +28,7 @@ import { registerBooksWidget } from './dashboard/bookWidget';
import { createMssqlApi } from './mssqlApiFactory'; import { createMssqlApi } from './mssqlApiFactory';
import { localize } from './localize'; import { localize } from './localize';
import { SqlToolsServer } from './sqlToolsServer'; import { SqlToolsServer } from './sqlToolsServer';
import { promises as fs } from 'fs';
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.'); const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
@@ -42,8 +43,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
} }
// ensure our log path exists // ensure our log path exists
if (!(await Utils.pfs.exists(context.logPath))) { if (!(await Utils.exists(context.logPath))) {
await Utils.pfs.mkdir(context.logPath); await fs.mkdir(context.logPath);
} }
let prompter: IPrompter = new CodeAdapter(); let prompter: IPrompter = new CodeAdapter();
@@ -200,7 +201,7 @@ async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promi
async function handleOpenClusterStatusNotebookTask(profile: azdata.IConnectionProfile, appContext: AppContext): Promise<void> { async function handleOpenClusterStatusNotebookTask(profile: azdata.IConnectionProfile, appContext: AppContext): Promise<void> {
const notebookRelativePath: string = 'notebooks/tsg/cluster-status.ipynb'; const notebookRelativePath: string = 'notebooks/tsg/cluster-status.ipynb';
const notebookFullPath: string = path.join(appContext.extensionContext.extensionPath, notebookRelativePath); const notebookFullPath: string = path.join(appContext.extensionContext.extensionPath, notebookRelativePath);
if (!Utils.fileExists(notebookFullPath)) { if (!(await Utils.exists(notebookFullPath))) {
vscode.window.showErrorMessage(localize("fileNotFound", "Unable to find the file specified")); vscode.window.showErrorMessage(localize("fileNotFound", "Unable to find the file specified"));
} else { } else {
const title: string = Utils.findNextUntitledEditorName(notebookFullPath); const title: string = Utils.findNextUntitledEditorName(notebookFullPath);

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import * as fspath from 'path'; import * as fspath from 'path';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -22,7 +22,7 @@ import { AppContext } from '../appContext';
import { TreeNode } from './treeNodes'; import { TreeNode } from './treeNodes';
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider'; import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider';
function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): vscode.Uri { async function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): Promise<vscode.Uri> {
let root = utils.getUserHome(); let root = utils.getUserHome();
let workspaceFolders = apiWrapper.workspaceFolders; let workspaceFolders = apiWrapper.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) { if (workspaceFolders && workspaceFolders.length > 0) {
@@ -33,7 +33,7 @@ function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: bo
let fileNum = 1; let fileNum = 1;
let fileNameWithoutExtension = fspath.parse(fileName).name; let fileNameWithoutExtension = fspath.parse(fileName).name;
let fileExtension = fspath.parse(fileName).ext; let fileExtension = fspath.parse(fileName).ext;
while (fs.existsSync(fspath.join(root, fileName))) { while (await utils.exists(fspath.join(root, fileName))) {
fileName = `${fileNameWithoutExtension}-${fileNum}${fileExtension}`; fileName = `${fileNameWithoutExtension}-${fileNum}${fileExtension}`;
fileNum++; fileNum++;
} }
@@ -82,7 +82,7 @@ export class UploadFilesCommand extends ProgressCommand {
}; };
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options); let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
if (fileUris) { if (fileUris) {
let files: IFile[] = fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles()); let files: IFile[] = await Promise.all(fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles()));
await this.executeWithProgress( await this.executeWithProgress(
(cancelToken: vscode.CancellationTokenSource) => this.writeFiles(files, folderNode, cancelToken), (cancelToken: vscode.CancellationTokenSource) => this.writeFiles(files, folderNode, cancelToken),
localize('uploading', 'Uploading files to HDFS'), true, localize('uploading', 'Uploading files to HDFS'), true,
@@ -99,9 +99,9 @@ export class UploadFilesCommand extends ProgressCommand {
} }
} }
private mapPathsToFiles(): (value: string, index: number, array: string[]) => File { private mapPathsToFiles(): (value: string, index: number, array: string[]) => Promise<File> {
return (path: string) => { return async (path: string) => {
let isDir = fs.lstatSync(path).isDirectory(); let isDir = (await fs.lstat(path)).isDirectory();
return new File(path, isDir); return new File(path, isDir);
}; };
} }
@@ -115,9 +115,9 @@ export class UploadFilesCommand extends ProgressCommand {
if (file.isDirectory) { if (file.isDirectory) {
let dirName = fspath.basename(file.path); let dirName = fspath.basename(file.path);
let subFolder = await folderNode.mkdir(dirName); let subFolder = await folderNode.mkdir(dirName);
let children: IFile[] = fs.readdirSync(file.path) let children: IFile[] = await Promise.all((await fs.readdir(file.path))
.map(childFileName => joinHdfsPath(file.path, childFileName)) .map(childFileName => joinHdfsPath(file.path, childFileName))
.map(this.mapPathsToFiles()); .map(this.mapPathsToFiles()));
this.writeFiles(children, subFolder, cancelToken); this.writeFiles(children, subFolder, cancelToken);
} else { } else {
await folderNode.writeFile(file); await folderNode.writeFile(file);
@@ -258,7 +258,7 @@ export class SaveFileCommand extends ProgressCommand {
try { try {
let fileNode = await getNode<FileNode>(context, this.appContext); let fileNode = await getNode<FileNode>(context, this.appContext);
if (fileNode) { if (fileNode) {
let defaultUri = getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath)); let defaultUri = await getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
let fileUri: vscode.Uri = await this.apiWrapper.showSaveDialog({ let fileUri: vscode.Uri = await this.apiWrapper.showSaveDialog({
defaultUri: defaultUri defaultUri: defaultUri
}); });
@@ -330,7 +330,7 @@ export class PreviewFileCommand extends ProgressCommand {
private async showNotebookDocument(fileName: string, connectionProfile?: azdata.IConnectionProfile, private async showNotebookDocument(fileName: string, connectionProfile?: azdata.IConnectionProfile,
initialContent?: string initialContent?: string
): Promise<azdata.nb.NotebookEditor> { ): Promise<azdata.nb.NotebookEditor> {
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true) let docUri: vscode.Uri = (await getSaveableUri(this.apiWrapper, fileName, true))
.with({ scheme: constants.UNTITLED_SCHEMA }); .with({ scheme: constants.UNTITLED_SCHEMA });
return await azdata.nb.showNotebookDocument(docUri, { return await azdata.nb.showNotebookDocument(docUri, {
connectionProfile: connectionProfile, connectionProfile: connectionProfile,
@@ -340,7 +340,7 @@ export class PreviewFileCommand extends ProgressCommand {
} }
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> { private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true); let docUri: vscode.Uri = await getSaveableUri(this.apiWrapper, fileName, true);
if (docUri) { if (docUri) {
docUri = docUri.with({ scheme: constants.UNTITLED_SCHEMA }); docUri = docUri.with({ scheme: constants.UNTITLED_SCHEMA });
return await this.apiWrapper.openTextDocument(docUri); return await this.apiWrapper.openTextDocument(docUri);

View File

@@ -8,7 +8,6 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as fspath from 'path'; import * as fspath from 'path';
import * as fs from 'fs';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as utils from '../../../utils'; import * as utils from '../../../utils';
import * as LocalizedConstants from '../../../localizedConstants'; import * as LocalizedConstants from '../../../localizedConstants';
@@ -223,7 +222,7 @@ export class SparkConfigurationTab {
// 1. For local file Source check whether they existed. // 1. For local file Source check whether they existed.
if (this._dataModel.isMainSourceFromLocal) { if (this._dataModel.isMainSourceFromLocal) {
if (!fs.existsSync(this._dataModel.localFileSourcePath)) { if (!(await utils.exists(this._dataModel.localFileSourcePath))) {
this._dataModel.showDialogError(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(this._dataModel.localFileSourcePath)); this._dataModel.showDialogError(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(this._dataModel.localFileSourcePath));
return false; return false;
} }

View File

@@ -8,7 +8,6 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
import * as fs from 'fs';
import * as fspath from 'path'; import * as fspath from 'path';
import * as os from 'os'; import * as os from 'os';
@@ -143,7 +142,7 @@ export class SparkJobSubmissionModel {
return Promise.reject(localize('sparkJobSubmission_localFileOrFolderNotSpecified.', 'Property localFilePath or hdfsFolderPath is not specified. ')); return Promise.reject(localize('sparkJobSubmission_localFileOrFolderNotSpecified.', 'Property localFilePath or hdfsFolderPath is not specified. '));
} }
if (!fs.existsSync(localFilePath)) { if (!(await utils.exists(localFilePath))) {
return Promise.reject(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(localFilePath)); return Promise.reject(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(localFilePath));
} }

View File

@@ -45,12 +45,12 @@ export function getTemplatePath(extensionPath: string, templateName: string): st
} }
export function shellWhichResolving(cmd: string): Promise<string> { export function shellWhichResolving(cmd: string): Promise<string> {
return new Promise<string>(resolve => { return new Promise<string>(resolve => {
which(cmd, (err, foundPath) => { which(cmd, async (err, foundPath) => {
if (err) { if (err) {
resolve(undefined); resolve(undefined);
} else { } else {
// NOTE: Using realpath b/c some system installs are symlinked from */bin // NOTE: Using realpath b/c some system installs are symlinked from */bin
resolve(fs.realpathSync(foundPath)); resolve(await fs.promises.realpath(foundPath));
} }
}); });
}); });

View File

@@ -20,8 +20,7 @@ import { AppContext } from './appContext';
import { DacFxService } from './dacfx/dacFxService'; import { DacFxService } from './dacfx/dacFxService';
import { CmsService } from './cms/cmsService'; import { CmsService } from './cms/cmsService';
import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts'; import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts';
import { promisify } from 'util'; import { promises as fs } from 'fs';
import { readFile } from 'fs';
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName); const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
@@ -70,7 +69,7 @@ export class SqlToolsServer {
} }
private async download(context: AppContext): Promise<string> { private async download(context: AppContext): Promise<string> {
const rawConfig = await promisify(readFile)(path.join(context.extensionContext.extensionPath, 'config.json')); const rawConfig = await fs.readFile(path.join(context.extensionContext.extensionPath, 'config.json'));
this.config = JSON.parse(rawConfig.toString()); this.config = JSON.parse(rawConfig.toString());
this.config.installDirectory = path.join(__dirname, this.config.installDirectory); this.config.installDirectory = path.join(__dirname, this.config.installDirectory);
this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy'); this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy');

View File

@@ -10,8 +10,7 @@ import * as crypto from 'crypto';
import * as os from 'os'; import * as os from 'os';
import * as findRemoveSync from 'find-remove'; import * as findRemoveSync from 'find-remove';
import * as constants from './constants'; import * as constants from './constants';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import { promisify } from 'util';
const configTracingLevel = 'tracingLevel'; const configTracingLevel = 'tracingLevel';
const configLogRetentionMinutes = 'logRetentionMinutes'; const configLogRetentionMinutes = 'logRetentionMinutes';
@@ -31,17 +30,6 @@ export function getAppDataPath() {
} }
} }
export namespace pfs {
export function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path);
}
export function mkdir(path: string, mode?: number): Promise<void> {
return promisify(fs.mkdir)(path, mode);
}
}
/** /**
* Get a file name that is not already used in the target directory * Get a file name that is not already used in the target directory
* @param filePath source notebook file name * @param filePath source notebook file name
@@ -61,14 +49,6 @@ export function findNextUntitledEditorName(filePath: string): string {
return title; return title;
} }
export function fileExists(file: string): boolean {
return fs.existsSync(file);
}
export function copyFile(source: string, target: string): void {
fs.copyFileSync(source, target);
}
export function removeOldLogFiles(logPath: string, prefix: string): JSON { export function removeOldLogFiles(logPath: string, prefix: string): JSON {
return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() }); return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
} }
@@ -303,3 +283,12 @@ export function logDebug(msg: any): void {
console.log(outputMsg); console.log(outputMsg);
} }
} }
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -78,6 +78,7 @@ export class BookTreeItem extends vscode.TreeItem {
if (this.book.tableOfContents.sections[i].url) { if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown. // TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb')); let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// tslint:disable-next-line:no-sync
if (fs.existsSync(pathToNotebook)) { if (fs.existsSync(pathToNotebook)) {
this._previousUri = pathToNotebook; this._previousUri = pathToNotebook;
return; return;
@@ -93,6 +94,7 @@ export class BookTreeItem extends vscode.TreeItem {
if (this.book.tableOfContents.sections[i].url) { if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown. // TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb')); let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// tslint:disable-next-line:no-sync
if (fs.existsSync(pathToNotebook)) { if (fs.existsSync(pathToNotebook)) {
this._nextUri = pathToNotebook; this._nextUri = pathToNotebook;
return; return;

View File

@@ -6,18 +6,16 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import * as glob from 'fast-glob'; import * as glob from 'fast-glob';
import { BookTreeItem, BookTreeItemType } from './bookTreeItem'; import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
import { maxBookSearchDepth, notebookConfigKey } from '../common/constants'; import { maxBookSearchDepth, notebookConfigKey } from '../common/constants';
import { isEditorTitleFree } from '../common/utils'; import { isEditorTitleFree, exists } from '../common/utils';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { promisify } from 'util';
import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content'; import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const existsAsync = promisify(fs.exists);
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider { export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider {
readonly providerId: string = 'BookNavigator'; readonly providerId: string = 'BookNavigator';
@@ -84,7 +82,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
const bookViewer = vscode.window.createTreeView('bookTreeView', { showCollapseAll: true, treeDataProvider: this }); const bookViewer = vscode.window.createTreeView('bookTreeView', { showCollapseAll: true, treeDataProvider: this });
await vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer'); await vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer');
this._openAsUntitled = openAsUntitled; this._openAsUntitled = openAsUntitled;
let books = this.getBooks(); let books = await this.getBooks();
if (books && books.length > 0) { if (books && books.length > 0) {
const rootTreeItem = books[0]; const rootTreeItem = books[0];
const sectionToOpen = rootTreeItem.findChildSection(urlToOpen); const sectionToOpen = rootTreeItem.findChildSection(urlToOpen);
@@ -92,8 +90,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
const urlPath = sectionToOpen ? sectionToOpen.url : rootTreeItem.tableOfContents.sections[0].url; const urlPath = sectionToOpen ? sectionToOpen.url : rootTreeItem.tableOfContents.sections[0].url;
const sectionToOpenMarkdown: string = path.join(bookPath, 'content', urlPath.concat('.md')); const sectionToOpenMarkdown: string = path.join(bookPath, 'content', urlPath.concat('.md'));
const sectionToOpenNotebook: string = path.join(bookPath, 'content', urlPath.concat('.ipynb')); const sectionToOpenNotebook: string = path.join(bookPath, 'content', urlPath.concat('.ipynb'));
const markdownExists = await existsAsync(sectionToOpenMarkdown); const markdownExists = await exists(sectionToOpenMarkdown);
const notebookExists = await existsAsync(sectionToOpenNotebook); const notebookExists = await exists(sectionToOpenNotebook);
if (markdownExists) { if (markdownExists) {
vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(sectionToOpenMarkdown)); vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(sectionToOpenMarkdown));
} }
@@ -208,13 +206,13 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return array.reduce((acc, val) => Array.isArray(val.sections) ? acc.concat(val).concat(this.parseJupyterSection(val.sections)) : acc.concat(val), []); return array.reduce((acc, val) => Array.isArray(val.sections) ? acc.concat(val).concat(this.parseJupyterSection(val.sections)) : acc.concat(val), []);
} }
public getBooks(): BookTreeItem[] { public async getBooks(): Promise<BookTreeItem[]> {
let books: BookTreeItem[] = []; let books: BookTreeItem[] = [];
for (let i in this._tableOfContentPaths) { for (const contentPath of this._tableOfContentPaths) {
let root = path.dirname(path.dirname(this._tableOfContentPaths[i])); let root = path.dirname(path.dirname(contentPath));
try { try {
const config = yaml.safeLoad(fs.readFileSync(path.join(root, '_config.yml'), 'utf-8')); const config = yaml.safeLoad((await fs.readFile(path.join(root, '_config.yml'), 'utf-8')).toString());
const tableOfContents = yaml.safeLoad(fs.readFileSync(this._tableOfContentPaths[i], 'utf-8')); const tableOfContents = yaml.safeLoad((await fs.readFile(contentPath, 'utf-8')).toString());
try { try {
let book = new BookTreeItem({ let book = new BookTreeItem({
title: config.title, title: config.title,
@@ -242,7 +240,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return books; return books;
} }
public getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): BookTreeItem[] { public async getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): Promise<BookTreeItem[]> {
let notebooks: BookTreeItem[] = []; let notebooks: BookTreeItem[] = [];
for (let i = 0; i < sections.length; i++) { for (let i = 0; i < sections.length; i++) {
if (sections[i].url) { if (sections[i].url) {
@@ -267,7 +265,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
let pathToMarkdown = path.join(root, 'content', sections[i].url.concat('.md')); let pathToMarkdown = path.join(root, 'content', sections[i].url.concat('.md'));
// Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook. // Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook.
// Following Jupyter Books behavior for now // Following Jupyter Books behavior for now
if (fs.existsSync(pathToNotebook)) { if (await exists(pathToNotebook)) {
let notebook = new BookTreeItem({ let notebook = new BookTreeItem({
title: sections[i].title, title: sections[i].title,
root: root, root: root,
@@ -286,7 +284,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
if (this._openAsUntitled) { if (this._openAsUntitled) {
this._allNotebooks.set(path.basename(pathToNotebook), notebook); this._allNotebooks.set(path.basename(pathToNotebook), notebook);
} }
} else if (fs.existsSync(pathToMarkdown)) { } else if (await exists(pathToMarkdown)) {
let markdown = new BookTreeItem({ let markdown = new BookTreeItem({
title: sections[i].title, title: sections[i].title,
root: root, root: root,

View File

@@ -188,3 +188,12 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
port: undefined port: undefined
}; };
} }
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as fs from 'fs'; import { promises as fs } from 'fs';
import * as utils from '../common/utils'; import * as utils from '../common/utils';
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation'; import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
@@ -220,7 +220,7 @@ export class ConfigurePythonDialog {
if (useExistingPython) { if (useExistingPython) {
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true); let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
let pythonExists = fs.existsSync(exePath); let pythonExists = await utils.exists(exePath);
if (!pythonExists) { if (!pythonExists) {
this.showErrorMessage(this.PythonNotFoundMsg); this.showErrorMessage(this.PythonNotFoundMsg);
return false; return false;
@@ -243,27 +243,23 @@ export class ConfigurePythonDialog {
return true; return true;
} }
private isFileValid(pythonLocation: string): Promise<boolean> { private async isFileValid(pythonLocation: string): Promise<boolean> {
let self = this; let self = this;
return new Promise<boolean>(function (resolve) { try {
fs.stat(pythonLocation, function (err, stats) { const stats = await fs.stat(pythonLocation);
if (err) { if (stats.isFile()) {
self.showErrorMessage(self.InvalidLocationMsg);
return false;
}
} catch (err) {
// Ignore error if folder doesn't exist, since it will be // Ignore error if folder doesn't exist, since it will be
// created during installation // created during installation
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
self.showErrorMessage(err.message); self.showErrorMessage(err.message);
resolve(false); return false;
} }
} }
else { return true;
if (stats.isFile()) {
self.showErrorMessage(self.InvalidLocationMsg);
resolve(false);
}
}
resolve(true);
});
});
} }
private async handleBrowse(): Promise<void> { private async handleBrowse(): Promise<void> {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as glob from 'glob'; import * as glob from 'glob';
import * as utils from '../common/utils'; import * as utils from '../common/utils';
@@ -92,7 +91,7 @@ export class PythonPathLookup {
const cmd = `"${options.command}" ${args.join(' ')}`; const cmd = `"${options.command}" ${args.join(' ')}`;
let output = await utils.executeBufferedCommand(cmd, {}); let output = await utils.executeBufferedCommand(cmd, {});
let value = output ? output.trim() : ''; let value = output ? output.trim() : '';
if (value.length > 0 && fs.existsSync(value)) { if (value.length > 0 && await utils.exists(value)) {
return value; return value;
} }
} catch (err) { } catch (err) {

View File

@@ -67,7 +67,7 @@ export class JupyterServerInstallation {
} }
private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> { private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) { if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
window.showInformationMessage(msgInstallPkgStart); window.showInformationMessage(msgInstallPkgStart);
this.outputChannel.show(true); this.outputChannel.show(true);
@@ -180,11 +180,11 @@ export class JupyterServerInstallation {
}); });
downloadRequest.pipe(fs.createWriteStream(pythonPackagePathLocal)) downloadRequest.pipe(fs.createWriteStream(pythonPackagePathLocal))
.on('close', () => { .on('close', async () => {
//unpack python zip/tar file //unpack python zip/tar file
this.outputChannel.appendLine(msgPythonUnpackPending); this.outputChannel.appendLine(msgPythonUnpackPending);
let pythonSourcePath = path.join(installPath, constants.pythonBundleVersion); let pythonSourcePath = path.join(installPath, constants.pythonBundleVersion);
if (!this._usingExistingPython && fs.existsSync(pythonSourcePath)) { if (!this._usingExistingPython && await utils.exists(pythonSourcePath)) {
try { try {
fs.removeSync(pythonSourcePath); fs.removeSync(pythonSourcePath);
} catch (err) { } catch (err) {
@@ -256,7 +256,7 @@ export class JupyterServerInstallation {
} }
} }
if (fs.existsSync(this._pythonExecutable)) { if (await utils.exists(this._pythonExecutable)) {
let pythonUserDir = await this.getPythonUserDir(this._pythonExecutable); let pythonUserDir = await this.getPythonUserDir(this._pythonExecutable);
if (pythonUserDir) { if (pythonUserDir) {
this.pythonEnvVarPath = pythonUserDir + delimiter + this.pythonEnvVarPath; this.pythonEnvVarPath = pythonUserDir + delimiter + this.pythonEnvVarPath;
@@ -330,7 +330,7 @@ export class JupyterServerInstallation {
await this.configurePackagePaths(); await this.configurePackagePaths();
}; };
let installReady = new Deferred<void>(); let installReady = new Deferred<void>();
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) { if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
this.apiWrapper.startBackgroundOperation({ this.apiWrapper.startBackgroundOperation({
displayName: msgTaskName, displayName: msgTaskName,
description: msgTaskName, description: msgTaskName,
@@ -522,6 +522,7 @@ export class JupyterServerInstallation {
} }
let condaExePath = this.getCondaExePath(); let condaExePath = this.getCondaExePath();
// tslint:disable-next-line:no-sync
return fs.existsSync(condaExePath); return fs.existsSync(condaExePath);
} }
@@ -538,6 +539,7 @@ export class JupyterServerInstallation {
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper); let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall); let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall);
// tslint:disable-next-line:no-sync
return fs.existsSync(pythonExe); return fs.existsSync(pythonExe);
} }
@@ -568,6 +570,7 @@ export class JupyterServerInstallation {
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey); let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
if (notebookConfig) { if (notebookConfig) {
let configPythonPath = notebookConfig[constants.pythonPathConfigKey]; let configPythonPath = notebookConfig[constants.pythonPathConfigKey];
// tslint:disable-next-line:no-sync
if (configPythonPath && fs.existsSync(configPythonPath)) { if (configPythonPath && fs.existsSync(configPythonPath)) {
path = configPythonPath; path = configPythonPath;
} }

View File

@@ -62,8 +62,13 @@ export class ServerInstanceUtils {
public copy(src: string, dest: string): Promise<void> { public copy(src: string, dest: string): Promise<void> {
return fs.copy(src, dest); return fs.copy(src, dest);
} }
public existsSync(dirPath: string): boolean { public async exists(path: string): Promise<boolean> {
return fs.existsSync(dirPath); try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
} }
public generateUuid(): string { public generateUuid(): string {
return UUID.generateUuid(); return UUID.generateUuid();
@@ -204,7 +209,7 @@ export class PerNotebookServerInstance implements IServerInstance {
private async copyKernelsToSystemJupyterDirs(): Promise<void> { private async copyKernelsToSystemJupyterDirs(): Promise<void> {
let kernelsExtensionSource = path.join(this.options.install.extensionPath, 'kernels'); let kernelsExtensionSource = path.join(this.options.install.extensionPath, 'kernels');
this._systemJupyterDir = path.join(this.getSystemJupyterHomeDir(), 'kernels'); this._systemJupyterDir = path.join(this.getSystemJupyterHomeDir(), 'kernels');
if (!this.utils.existsSync(this._systemJupyterDir)) { if (!(await this.utils.exists(this._systemJupyterDir))) {
await this.utils.mkDir(this._systemJupyterDir, this.options.install.outputChannel); await this.utils.mkDir(this._systemJupyterDir, this.options.install.outputChannel);
} }
await this.utils.copy(kernelsExtensionSource, this._systemJupyterDir); await this.utils.copy(kernelsExtensionSource, this._systemJupyterDir);

View File

@@ -283,9 +283,9 @@ describe.skip('BookTreeViewProviderTests', function() {
await Promise.race([tocRead, errorCase.then(() => { throw new Error('Table of Contents were not ready in time'); })]); await Promise.race([tocRead, errorCase.then(() => { throw new Error('Table of Contents were not ready in time'); })]);
}); });
it('should show error if notebook or markdown file is missing', function(): void { it('should show error if notebook or markdown file is missing', async function(): Promise<void> {
let books = bookTreeViewProvider.getBooks(); let books = bookTreeViewProvider.getBooks();
let children = bookTreeViewProvider.getSections({ sections: [] }, books[0].sections, rootFolderPath); let children = await bookTreeViewProvider.getSections({ sections: [] }, (await books)[0].sections, rootFolderPath);
should(bookTreeViewProvider.errorMessage).equal('Missing file : Notebook1'); should(bookTreeViewProvider.errorMessage).equal('Missing file : Notebook1');
// Rest of book should be detected correctly even with a missing file // Rest of book should be detected correctly even with a missing file
equalBookItems(children[0], expectedNotebook2); equalBookItems(children[0], expectedNotebook2);

View File

@@ -57,7 +57,7 @@ describe('Jupyter server instance', function (): void {
// Given a server instance // Given a server instance
mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve()); mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve());
mockUtils.setup(u => u.existsSync(TypeMoq.It.isAnyString())).returns(() => false); mockUtils.setup(u => u.exists(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(false));
// When I run configure // When I run configure
await serverInstance.configure(); await serverInstance.configure();
@@ -65,7 +65,7 @@ describe('Jupyter server instance', function (): void {
// Then I expect a folder to have been created with config and data subdirs // Then I expect a folder to have been created with config and data subdirs
mockUtils.verify(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5)); mockUtils.verify(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5));
mockUtils.verify(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3)); mockUtils.verify(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3));
mockUtils.verify(u => u.existsSync(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1)); mockUtils.verify(u => u.exists(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
}); });
it('Should have URI info after start', async function (): Promise<void> { it('Should have URI info after start', async function (): Promise<void> {

View File

@@ -26,10 +26,12 @@ export class PlatformService implements IPlatformService {
} }
copyFile(source: string, target: string): void { copyFile(source: string, target: string): void {
// tslint:disable-next-line:no-sync
fs.copyFileSync(source, target); fs.copyFileSync(source, target);
} }
fileExists(file: string): boolean { fileExists(file: string): boolean {
// tslint:disable-next-line:no-sync
return fs.existsSync(file); return fs.existsSync(file);
} }

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as cp from 'child_process'; import * as cp from 'child_process';
import * as fs from 'fs'; import { createWriteStream, promises as fs } from 'fs';
import * as https from 'https'; import * as https from 'https';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
@@ -226,7 +226,7 @@ export class ResourceTypeService implements IResourceTypeService {
private download(url: string): Promise<string> { private download(url: string): Promise<string> {
const self = this; const self = this;
const promise = new Promise<string>((resolve, reject) => { const promise = new Promise<string>((resolve, reject) => {
https.get(url, function (response) { https.get(url, async function (response) {
console.log('Download installer from: ' + url); console.log('Download installer from: ' + url);
if (response.statusCode === 301 || response.statusCode === 302) { if (response.statusCode === 301 || response.statusCode === 302) {
// Redirect and download from new location // Redirect and download from new location
@@ -247,19 +247,19 @@ export class ResourceTypeService implements IResourceTypeService {
let fileName = originalFileName; let fileName = originalFileName;
const downloadFolder = os.homedir(); const downloadFolder = os.homedir();
let cnt = 1; let cnt = 1;
while (fs.existsSync(path.join(downloadFolder, fileName + extension))) { while (await exists(path.join(downloadFolder, fileName + extension))) {
fileName = `${originalFileName}-${cnt}`; fileName = `${originalFileName}-${cnt}`;
cnt++; cnt++;
} }
fileName = path.join(downloadFolder, fileName + extension); fileName = path.join(downloadFolder, fileName + extension);
const file = fs.createWriteStream(fileName); const file = createWriteStream(fileName);
response.pipe(file); response.pipe(file);
file.on('finish', () => { file.on('finish', () => {
file.close(); file.close();
resolve(fileName); resolve(fileName);
}); });
file.on('error', (err) => { file.on('error', async (err) => {
fs.unlink(fileName, () => { }); await fs.unlink(fileName);
reject(err.message); reject(err.message);
}); });
}); });
@@ -268,3 +268,12 @@ export class ResourceTypeService implements IResourceTypeService {
} }
} }
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -9,8 +9,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as os from 'os'; import * as os from 'os';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { isNullOrUndefined } from 'util'; import { promises as fs } from 'fs';
import { existsSync } from 'fs';
import { Telemetry } from '../telemetry'; import { Telemetry } from '../telemetry';
import { getEndpointName } from '../utils'; import { getEndpointName } from '../utils';
import * as mssql from '../../../mssql'; import * as mssql from '../../../mssql';
@@ -35,6 +34,15 @@ const YesButtonText: string = localize('schemaCompareDialog.Yes', 'Yes');
const NoButtonText: string = localize('schemaCompareDialog.No', 'No'); const NoButtonText: string = localize('schemaCompareDialog.No', 'No');
const titleFontSize: number = 13; const titleFontSize: number = 13;
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
export class SchemaCompareDialog { export class SchemaCompareDialog {
public dialog: azdata.window.Dialog; public dialog: azdata.window.Dialog;
public dialogName: string; public dialogName: string;
@@ -194,8 +202,8 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.sourceTextBox', "Source file") ariaLabel: localize('schemaCompareDialog.sourceTextBox', "Source file")
}).component(); }).component();
this.sourceTextBox.onTextChanged((e) => { this.sourceTextBox.onTextChanged(async (e) => {
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
this.targetTextBox = view.modelBuilder.inputBox().withProperties({ this.targetTextBox = view.modelBuilder.inputBox().withProperties({
@@ -204,8 +212,8 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.targetTextBox', "Target file") ariaLabel: localize('schemaCompareDialog.targetTextBox', "Target file")
}).component(); }).component();
this.targetTextBox.onTextChanged(() => { this.targetTextBox.onTextChanged(async () => {
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
this.sourceServerComponent = await this.createSourceServerDropdown(view); this.sourceServerComponent = await this.createSourceServerDropdown(view);
@@ -307,7 +315,7 @@ export class SchemaCompareDialog {
currentButton.onDidClick(async (click) => { currentButton.onDidClick(async (click) => {
// file browser should open where the current dacpac is or the appropriate default folder // file browser should open where the current dacpac is or the appropriate default folder
let rootPath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].name : os.homedir(); let rootPath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].name : os.homedir();
let defaultUri = endpoint && endpoint.packageFilePath && existsSync(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath; let defaultUri = endpoint && endpoint.packageFilePath && await exists(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
let fileUris = await vscode.window.showOpenDialog( let fileUris = await vscode.window.showOpenDialog(
{ {
@@ -351,17 +359,17 @@ export class SchemaCompareDialog {
}).component(); }).component();
// show dacpac file browser // show dacpac file browser
dacpacRadioButton.onDidClick(() => { dacpacRadioButton.onDidClick(async () => {
this.sourceIsDacpac = true; this.sourceIsDacpac = true;
this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText); this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.sourceServerComponent); this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent); this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize }); this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
// show server and db dropdowns or 'No active connections' text // show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => { databaseRadioButton.onDidClick(async () => {
this.sourceIsDacpac = false; this.sourceIsDacpac = false;
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) { if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize }); this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
@@ -370,7 +378,7 @@ export class SchemaCompareDialog {
this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 2, { horizontal: true, titleFontSize: titleFontSize }); this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 2, { horizontal: true, titleFontSize: titleFontSize });
} }
this.formBuilder.removeFormItem(this.sourceDacpacComponent); this.formBuilder.removeFormItem(this.sourceDacpacComponent);
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
// if source is currently a db, show it in the server and db dropdowns // if source is currently a db, show it in the server and db dropdowns
@@ -408,17 +416,17 @@ export class SchemaCompareDialog {
}).component(); }).component();
// show dacpac file browser // show dacpac file browser
dacpacRadioButton.onDidClick(() => { dacpacRadioButton.onDidClick(async () => {
this.targetIsDacpac = true; this.targetIsDacpac = true;
this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText); this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.targetServerComponent); this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent); this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize }); this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
// show server and db dropdowns or 'No active connections' text // show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => { databaseRadioButton.onDidClick(async () => {
this.targetIsDacpac = false; this.targetIsDacpac = false;
this.formBuilder.removeFormItem(this.targetDacpacComponent); this.formBuilder.removeFormItem(this.targetDacpacComponent);
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) { if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
@@ -427,7 +435,7 @@ export class SchemaCompareDialog {
} else { } else {
this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true, titleFontSize: titleFontSize }); this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true, titleFontSize: titleFontSize });
} }
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
// if target is currently a db, show it in the server and db dropdowns // if target is currently a db, show it in the server and db dropdowns
@@ -450,18 +458,18 @@ export class SchemaCompareDialog {
}; };
} }
private shouldEnableOkayButton(): boolean { private async shouldEnableOkayButton(): Promise<boolean> {
let sourcefilled = (this.sourceIsDacpac && this.existsDacpac(this.sourceTextBox.value)) let sourcefilled = (this.sourceIsDacpac && await this.existsDacpac(this.sourceTextBox.value))
|| (!this.sourceIsDacpac && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1); || (!this.sourceIsDacpac && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1);
let targetfilled = (this.targetIsDacpac && this.existsDacpac(this.targetTextBox.value)) let targetfilled = (this.targetIsDacpac && await this.existsDacpac(this.targetTextBox.value))
|| (!this.targetIsDacpac && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1); || (!this.targetIsDacpac && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1);
return sourcefilled && targetfilled; return sourcefilled && targetfilled;
} }
private existsDacpac(filename: string): boolean { private async existsDacpac(filename: string): Promise<boolean> {
return !isNullOrUndefined(filename) && existsSync(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac')); return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
} }
protected async createSourceServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> { protected async createSourceServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
@@ -596,9 +604,9 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.sourceDatabaseDropdown', "Source Database") ariaLabel: localize('schemaCompareDialog.sourceDatabaseDropdown', "Source Database")
} }
).component(); ).component();
this.sourceDatabaseDropdown.onValueChanged((value) => { this.sourceDatabaseDropdown.onValueChanged(async (value) => {
this.sourceDbEditable = value; this.sourceDbEditable = value;
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
return { return {
@@ -615,9 +623,9 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.targetDatabaseDropdown', "Target Database") ariaLabel: localize('schemaCompareDialog.targetDatabaseDropdown', "Target Database")
} }
).component(); ).component();
this.targetDatabaseDropdown.onValueChanged((value) => { this.targetDatabaseDropdown.onValueChanged(async (value) => {
this.targetDbEditable = value; this.targetDbEditable = value;
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
}); });
return { return {
@@ -684,3 +692,7 @@ export class SchemaCompareDialog {
interface ConnectionDropdownValue extends azdata.CategoryValue { interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.ConnectionProfile; connection: azdata.connection.ConnectionProfile;
} }
function isNullOrUndefined(val: any): boolean {
return val === null || val === undefined;
}

View File

@@ -229,6 +229,21 @@
"translation-remind": true, "translation-remind": true,
"no-standalone-editor": true, "no-standalone-editor": true,
"no-nls-in-standalone-editor": true, "no-nls-in-standalone-editor": true,
"no-sync": [
true,
{
"exclude": [
"**/vs/**", // assume they are doing the right thing
"**/extensions/git/**", // assume they are doing the right thing,
"**/extensions/extension-editing/**", // assume they are doing the right thing,
"**/json-language-features/**", // assume they are doing the right thing,
"**/vscode-test-resolver/**", // testing doesn't matter
"**/integration-tests/**", // testing doesn't matter
"**/*.test.*", // testing doesn't matter
"**/test/**" // testing doesn't matter
]
}
],
"no-useless-strict": true "no-useless-strict": true
}, },
"defaultSeverity": "warning" "defaultSeverity": "warning"