From 9e1f04e476f9400f04bfec72c69690c2208f7903 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 7 Mar 2019 13:04:50 -0800 Subject: [PATCH] Enforce vscode and ads version check when installing extensions (#4267) * engine check when install extension * gallery install/update and vsix install * FIX COMMENTS * Fix the detail not loading issue when version is invalid * add more comments and adress PR comments * add install telemetry for install from vsix scenario * correct the name of the version property for telemetry --- extensions/admin-pack/package.json | 2 +- extensions/azurecore/package.json | 2 +- extensions/big-data-cluster/package.json | 92 ++++++++++--------- extensions/import/package.json | 2 +- extensions/integration-tests/package.json | 2 +- extensions/notebook/package.json | 2 +- samples/extensionSamples/package.json | 2 +- samples/serverReports/package.json | 2 +- samples/sp_whoIsActive/package.json | 2 +- samples/sqlservices/package.json | 2 +- .../common/extensionManagement.ts | 4 +- .../common/extensionManagementUtil.ts | 6 +- .../node/extensionGalleryService.ts | 17 +++- .../node/extensionManagementService.ts | 26 ++++-- .../extensions/node/extensionValidator.ts | 9 +- .../electron-browser/extensionsActions.ts | 14 +++ .../node/extensionsWorkbenchService.ts | 24 ++++- .../services/extensions/common/extensions.ts | 2 +- .../extensions/node/extensionPoints.ts | 4 +- 19 files changed, 143 insertions(+), 73 deletions(-) diff --git a/extensions/admin-pack/package.json b/extensions/admin-pack/package.json index be1e2ee30a..363eec2851 100644 --- a/extensions/admin-pack/package.json +++ b/extensions/admin-pack/package.json @@ -6,7 +6,7 @@ "publisher": "Microsoft", "engines": { "vscode": "*", - "sqlops": "*" + "azdata": "*" }, "extensionPack": [ "Microsoft.agent", diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index b5b723e608..0199174c11 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -7,7 +7,7 @@ "preview": true, "engines": { "vscode": "^1.25.0", - "sqlops": "*" + "azdata": "*" }, "activationEvents": [ "*" diff --git a/extensions/big-data-cluster/package.json b/extensions/big-data-cluster/package.json index 145726b5c2..e800a957c9 100644 --- a/extensions/big-data-cluster/package.json +++ b/extensions/big-data-cluster/package.json @@ -9,7 +9,8 @@ "icon": "images/sqlserver.png", "aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412", "engines": { - "vscode": "0.10.x" + "vscode": "*", + "azdata": "^1.4.0" }, "activationEvents": [ "*" @@ -27,51 +28,51 @@ "type": "object", "title": "Kubernetes configuration", "properties": { - "mssql-bdc": { - "type": "object", - "description": "Kubernetes configuration", - "properties": { - "mssql-bdc.kubectl-path": { - "type": "string", - "description": "File path to a kubectl binary." - }, - "mssql-bdc.kubectl-path.windows": { - "type": "string", - "description": "File path to a kubectl binary." - }, - "mssql-bdc.kubectl-path.mac": { - "type": "string", - "description": "File path to a kubectl binary." - }, - "mssql-bdc.kubectl-path.linux": { - "type": "string", - "description": "File path to a kubectl binary." - }, - "mssql-bdc.kubeconfig": { - "type": "string", - "description": "File path to the kubeconfig file." - }, - "mssql-bdc.knownKubeconfigs": { - "type": "array", - "description": "File paths to kubeconfig files from which you can select." - }, - "mssql-bdc.outputFormat": { - "enum": [ - "json", - "yaml" - ], - "type": "string", - "description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)." - } - }, - "default": { - "mssql-bdc.namespace": "", - "mssql-bdc.kubectl-path": "", - "mssql-bdc.kubeconfig": "", - "mssql-bdc.knownKubeconfigs": [] - } + "mssql-bdc": { + "type": "object", + "description": "Kubernetes configuration", + "properties": { + "mssql-bdc.kubectl-path": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.windows": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.mac": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.linux": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubeconfig": { + "type": "string", + "description": "File path to the kubeconfig file." + }, + "mssql-bdc.knownKubeconfigs": { + "type": "array", + "description": "File paths to kubeconfig files from which you can select." + }, + "mssql-bdc.outputFormat": { + "enum": [ + "json", + "yaml" + ], + "type": "string", + "description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)." } + }, + "default": { + "mssql-bdc.namespace": "", + "mssql-bdc.kubectl-path": "", + "mssql-bdc.kubeconfig": "", + "mssql-bdc.knownKubeconfigs": [] + } } + } }, "commands": [ { @@ -83,7 +84,8 @@ }, "dependencies": { "vscode-nls": "^3.2.1", - "download": "^6.2.5" + "download": "^6.2.5", + "shelljs": "^0.8.3" }, "devDependencies": { "mocha-junit-reporter": "^1.17.0", diff --git a/extensions/import/package.json b/extensions/import/package.json index bc8127d26b..d274191567 100644 --- a/extensions/import/package.json +++ b/extensions/import/package.json @@ -7,7 +7,7 @@ "preview": true, "engines": { "vscode": "^1.25.0", - "sqlops": "*" + "azdata": "*" }, "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx", "icon": "images/sqlserver.png", diff --git a/extensions/integration-tests/package.json b/extensions/integration-tests/package.json index 2b4ce2dca7..caf4dbebbd 100644 --- a/extensions/integration-tests/package.json +++ b/extensions/integration-tests/package.json @@ -6,7 +6,7 @@ "private": true, "engines": { "vscode": "*", - "sqlops": "*" + "azdata": "*" }, "activationEvents": [ "*" diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json index 912f3022d5..20e4826002 100644 --- a/extensions/notebook/package.json +++ b/extensions/notebook/package.json @@ -6,7 +6,7 @@ "publisher": "Microsoft", "engines": { "vscode": "*", - "sqlops": "*" + "azdata": "*" }, "main": "./out/extension", "activationEvents": [ diff --git a/samples/extensionSamples/package.json b/samples/extensionSamples/package.json index 7588530fc6..8107e2cffd 100644 --- a/samples/extensionSamples/package.json +++ b/samples/extensionSamples/package.json @@ -6,7 +6,7 @@ "publisher": "Microsoft", "engines": { "vscode": "^1.26.0", - "sqlops": "*" + "azdata": "*" }, "license": "SEE LICENSE IN LICENSE.txt", "repository": "https://github.com/Microsoft/azuredatastudio", diff --git a/samples/serverReports/package.json b/samples/serverReports/package.json index 345a75ee61..49ff2f32a3 100644 --- a/samples/serverReports/package.json +++ b/samples/serverReports/package.json @@ -7,7 +7,7 @@ "preview": true, "engines": { "vscode": "^1.26.0", - "sqlops": "*" + "azdata": "*" }, "icon": "images/sqlserver.png", "license": "SEE LICENSE IN LICENSE.txt", diff --git a/samples/sp_whoIsActive/package.json b/samples/sp_whoIsActive/package.json index 8e84a0e850..e5fda342c0 100644 --- a/samples/sp_whoIsActive/package.json +++ b/samples/sp_whoIsActive/package.json @@ -7,7 +7,7 @@ "preview": true, "engines": { "vscode": "^1.26.0", - "sqlops": "*" + "azdata": "*" }, "icon": "images/sqlserver.png", "license": "SEE LICENSE IN LICENSE.txt", diff --git a/samples/sqlservices/package.json b/samples/sqlservices/package.json index 4b49374ba9..f74fc2cf10 100644 --- a/samples/sqlservices/package.json +++ b/samples/sqlservices/package.json @@ -6,7 +6,7 @@ "publisher": "demo", "engines": { "vscode": "^1.26.0", - "sqlops": "*" + "azdata": "*" }, "categories": [ "Other" diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index cd788dd3a4..8018597e6d 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -117,7 +117,7 @@ export interface IExtensionManifest { publisher: string; version: string; // {{SQL CARBON EDIT}} - engines: { vscode: string; sqlops?: string }; + engines: { vscode: string; azdata?: string }; displayName?: string; description?: string; main?: string; @@ -141,6 +141,8 @@ export interface IGalleryExtensionProperties { dependencies?: string[]; extensionPack?: string[]; engine?: string; + // {{SQL CARBON EDIT}} + azDataEngine?: string; localizedLanguages?: string[]; } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index c6665e26cd..93475da7a6 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -71,7 +71,9 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any publisherId: extension.metadata ? extension.metadata.publisherId : null, publisherName: extension.manifest.publisher, publisherDisplayName: extension.metadata ? extension.metadata.publisherDisplayName : null, - dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0 + dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0, + // {{SQL CARBON EDIT}} + extensionVersion: extension.manifest.version }; } @@ -99,6 +101,8 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): publisherName: extension.publisher, publisherDisplayName: extension.publisherDisplayName, dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0), + // {{SQL CARBON EDIT}} + extensionVersion: extension.version, ...extension.telemetryData }; } diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index bdb30865f7..da7f5a7f0e 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -123,6 +123,8 @@ const PropertyType = { Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies', ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack', Engine: 'Microsoft.VisualStudio.Code.Engine', + // {{SQL CARBON EDIT}} + AzDataEngine: 'Microsoft.AzDataEngine', LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages' }; @@ -298,6 +300,12 @@ function getEngine(version: IRawGalleryExtensionVersion): string { return (values.length > 0 && values[0].value) || ''; } +// {{SQL CARBON EDIT}} +function getAzureDataStudioEngine(version: IRawGalleryExtensionVersion): string { + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.AzDataEngine) : []; + return (values.length > 0 && values[0].value) || ''; +} + function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] { const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : []; const value = (values.length > 0 && values[0].value) || ''; @@ -343,6 +351,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller dependencies: getExtensions(version, PropertyType.Dependency), extensionPack: getExtensions(version, PropertyType.ExtensionPack), engine: getEngine(version), + // {{SQL CARBON EDIT}} + azDataEngine: getAzureDataStudioEngine(version), localizedLanguages: getLocalizedLanguages(version) }, /* __GDPR__FRAGMENT__ @@ -724,7 +734,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } loadCompatibleVersion(extension: IGalleryExtension, fromVersion: string = extension.version): Promise { - if (extension.version === fromVersion && extension.properties.engine && isEngineValid(extension.properties.engine)) { + // {{SQL CARBON EDIT}} + // Change to original version: removed the extension version validation + // Reason: This method is used to find the matching gallery extension for the locally installed extension, + // since we only have one entry for each extension (not in-scope to enable mutiple version support for now), + // if the new version of extension is not compatible, the extension won't be displayed properly. + if (extension.version === fromVersion) { return Promise.resolve(extension); } const query = new Query() diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4bbbc55b38..c6a5b1b282 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -46,6 +46,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; +// {{SQL CARBON EDIT} +import product from 'vs/platform/node/product'; + const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; @@ -188,6 +191,9 @@ export class ExtensionManagementService extends Disposable implements IExtension } install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise { + // {{SQL CARBON EDIT}} + let startTime = new Date().getTime(); + this.logService.trace('ExtensionManagementService#install', vsix.toString()); return createCancelablePromise(token => { return this.downloadVsix(vsix) @@ -197,10 +203,11 @@ export class ExtensionManagementService extends Disposable implements IExtension return getManifest(zipPath) .then(manifest => { const identifier = { id: getLocalExtensionIdFromManifest(manifest) }; - // {{SQL CARBON EDIT - Remove VS Code version check}} - // if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) { - // return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, pkg.version))); - // } + // {{SQL CARBON EDIT - Check VSCode and ADS version}} + if (manifest.engines && (!isEngineValid(manifest.engines.vscode, product.vscodeVersion) + || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, pkg.version)))) { + return Promise.reject(new Error(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, pkg.version, manifest.version))); + } return this.removeIfExists(identifier.id) .then( () => { @@ -216,10 +223,13 @@ export class ExtensionManagementService extends Disposable implements IExtension // {{SQL CARBON EDIT}} // Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery return this.installExtension({ zipPath, id: identifier.id, metadata: null }, type, token) - .then( - local => this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }), - error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); } - ); + .then( + local => { + this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getLocalExtensionTelemetryData(local), new Date().getTime() - startTime, void 0); + this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }); + }, + error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); } + ); // return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) // .then( // metadata => this.installFromZipPath(identifier, zipPath, metadata, type, token), diff --git a/src/vs/platform/extensions/node/extensionValidator.ts b/src/vs/platform/extensions/node/extensionValidator.ts index ba1543e2ae..a4cbab7ce0 100644 --- a/src/vs/platform/extensions/node/extensionValidator.ts +++ b/src/vs/platform/extensions/node/extensionValidator.ts @@ -209,7 +209,7 @@ export interface IReducedExtensionDescription { engines: { vscode: string; // {{SQL CARBON EDIT}} - sqlops?: string; + azdata?: string; }; main?: string; } @@ -222,12 +222,13 @@ export function isValidExtensionVersion(version: string, extensionDesc: IReduced } // {{SQL CARBON EDIT}} - return (extensionDesc.engines.sqlops && extensionDesc.engines.sqlops === '*') || isVersionValid(version, extensionDesc.engines.sqlops, notices); + return (extensionDesc.engines.azdata && extensionDesc.engines.azdata === '*') || isVersionValid(version, extensionDesc.engines.azdata, notices); } -export function isEngineValid(engine: string): boolean { +// {{SQL CARBON EDIT}} +export function isEngineValid(engine: string, version: string = pkg.version): boolean { // TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version - return engine === '*' || isVersionValid(pkg.version, engine); + return engine === '*' || isVersionValid(version, engine); } export function isVersionValid(currentVersion: string, requestedVersion: string, notices: string[] = []): boolean { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index f524eff5c7..b45de7857f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -191,6 +191,13 @@ export class InstallAction extends Action { return this.notificationService.error(err); } + // {{SQL CARBON EDIT}} + // Prompt the user that the current ADS version is not compatible with the extension, + // return here as in this scenario it doesn't make sense for the user to download manually. + if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) { + return this.notificationService.error(err); + } + console.error(err); return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), err, this.instantiationService, this.notificationService, this.openerService); @@ -418,6 +425,13 @@ export class UpdateAction extends Action { return this.notificationService.error(err); } + // {{SQL CARBON EDIT}} + // Prompt the user that the current ADS version is not compatible with the extension, + // return here as in this scenario it doesn't make sense for the user to download manually. + if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) { + return this.notificationService.error(err); + } + console.error(err); return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), err, this.instantiationService, this.notificationService, this.openerService); diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 02fa9c7f8b..1ed3c115b0 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -13,9 +13,10 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +// {{SQL CARBON EDIT}} import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest, - InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService + InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -39,6 +40,11 @@ import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +// {{SQL CARBON EDIT}} +import pkg from 'vs/platform/node/package'; +import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator'; +import { ExtensionManagementError } from 'vs/platform/extensionManagement/node/extensionManagementService'; + interface IExtensionStateProvider { (extension: Extension): T; } @@ -703,6 +709,13 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, if (extensionIdentifier) { this.checkAndEnableDisabledDependencies(extensionIdentifier); } + } + // {{SQL CARBON EDIT}} + // This is the error handler when installing local VSIX file. + // Prompt the user about the error detail. + , (error) => { + this.notificationService.error(error); + return Promise.reject(error); })); } @@ -721,6 +734,15 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return Promise.reject(new Error('Missing gallery')); } + // {{SQL CARBON EDIT}} + // This is the execution path for install/update extension from marketplace. + // Check both the vscode version and azure data studio version + // The check is added here because we want to fail fast instead of downloading the VSIX and then fail. + if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, product.vscodeVersion) + || (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, pkg.version)))) { + return Promise.reject(new ExtensionManagementError(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.id, pkg.version, gallery.version), INSTALL_ERROR_INCOMPATIBLE)); + } + return this.installWithProgress( // {{SQL CARBON EDIT}} () => { diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 4f8a96cd82..fa5281bb88 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -24,7 +24,7 @@ export interface IExtensionDescription { readonly engines: { vscode: string; // {{SQL CARBON EDIT}} - sqlops?: string; + azdata?: string; }; readonly main?: string; readonly contributes?: { [point: string]: any; }; diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index a1e605d393..72ee47d491 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -89,8 +89,8 @@ class ExtensionManifestParser extends ExtensionManifestHandler { manifest.uuid = manifest.__metadata.id; } // {{SQL CARBON EDIT}} - if (manifest.engines && !manifest.engines.sqlops) { - manifest.engines.sqlops = '*'; + if (manifest.engines && !manifest.engines.azdata) { + manifest.engines.azdata = '*'; } delete manifest.__metadata; return manifest;