Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/sql-database-projects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ _The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

- Added **Rename Symbol** refactoring support for SQL project files.
- Added **Move to Schema** refactoring support for SQL project files.
- Updated the default Microsoft.Build.Sql version to `2.*` for new SDK-style projects, enabling projects to automatically fetch the latest available 2.x NuGet release.

## [1.6.1] - 2026-06-03

Expand Down
2 changes: 1 addition & 1 deletion extensions/sql-database-projects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ CREATE TABLE [dbo].[Product] (
### General Settings

- `sqlDatabaseProjects.dotnetSDK Location`: The path to the folder containing the `dotnet` folder for the .NET SDK. If not set, the extension will attempt to find the .NET SDK on the system.
- `sqlDatabaseProjects.microsoftBuildSqlVersion`: Version of Microsoft.Build.Sql to use for SQL projects. Controls the SDK version referenced in newly created SDK-style project templates and the binaries used when building non-SDK-style SQL projects. If not set, the extension will use Microsoft.Build.Sql 2.1.0.
- `sqlDatabaseProjects.microsoftBuildSqlVersion`: Version of Microsoft.Build.Sql to use for SQL projects. Controls the SDK version referenced in newly created SDK-style project templates and the binaries used when building non-SDK-style SQL projects. Supports exact versions (e.g. `2.1.0`) and NuGet floating versions (e.g. `2.*`). If not set, the extension will use Microsoft.Build.Sql 2.\*.
- `sqlDatabaseProjects.netCoreDoNotAsk`: When true, no longer prompts to install .NET SDK when a supported installation is not found.
- `sqlDatabaseProjects.collapseProjectNodes`: Option to set the default state of the project nodes in the database projects view to collapsed. If not set, the extension will default to expanded.

Expand Down
3 changes: 3 additions & 0 deletions extensions/sql-database-projects/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@
"Extracting DacFx build DLLs to {0}": "Extracting DacFx build DLLs to {0}",
"Error downloading {0}. Error: {1}": "Error downloading {0}. Error: {1}",
"Error extracting files from {0}. Error: {1}": "Error extracting files from {0}. Error: {1}",
"No stable versions of {0} found matching {1}.": "No stable versions of {0} found matching {1}.",
"Failed to fetch NuGet index for {0}: HTTP {1}": "Failed to fetch NuGet index for {0}: HTTP {1}",
"Could not resolve Microsoft.Build.Sql version from NuGet ({0}). Using {1}.": "Could not resolve Microsoft.Build.Sql version from NuGet ({0}). Using {1}.",
"Only moving files and folders are supported": "Only moving files and folders are supported",
"Moving files between projects is not supported": "Moving files between projects is not supported",
"Error when moving file from {0} to {1}. Error: {2}": "Error when moving file from {0} to {1}. Error: {2}",
Expand Down
2 changes: 1 addition & 1 deletion extensions/sql-database-projects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
},
"sqlDatabaseProjects.microsoftBuildSqlVersion": {
"type": "string",
"default": "2.1.0",
"default": "2.*",
Comment thread
ssreerama marked this conversation as resolved.
"description": "%sqlDatabaseProjects.microsoftBuildSqlVersion%"
},
"sqlDatabaseProjects.autoCreateFolders": {
Expand Down
13 changes: 13 additions & 0 deletions extensions/sql-database-projects/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
export const dataSourceDropdownTitle = l10n.t("Data source");
export const noDataSourcesText = l10n.t("No data sources in this project");
export const loadProfilePlaceholderText = l10n.t("Load profile...");
export const profileReadError = (err: any) =>

Check warning on line 210 in extensions/sql-database-projects/src/common/constants.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Unexpected any. Specify a different type
l10n.t("Error loading the publish profile. {0}", utils.getErrorMessage(err));
export const sqlCmdVariables = l10n.t("SQLCMD Variables");
export const sqlCmdVariableColumn = l10n.t("Name");
Expand Down Expand Up @@ -533,7 +533,7 @@
export function circularProjectReference(project1: string, project2: string) {
return l10n.t("Circular reference from project {0} to project {1}", project1, project2);
}
export function errorFindingBuildFilesLocation(err: any) {

Check warning on line 536 in extensions/sql-database-projects/src/common/constants.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Unexpected any. Specify a different type
return l10n.t("Error finding build files location: {0}", utils.getErrorMessage(err));
}
export function projBuildFailed(errorMessage: string) {
Expand Down Expand Up @@ -946,6 +946,19 @@
export function errorExtracting(path: string, error: string) {
return l10n.t("Error extracting files from {0}. Error: {1}", path, error);
}
export function nugetVersionResolutionFailed(packageName: string, version: string) {
return l10n.t("No stable versions of {0} found matching {1}.", packageName, version);
}
export function nugetIndexFetchFailed(packageName: string, status: number) {
return l10n.t("Failed to fetch NuGet index for {0}: HTTP {1}", packageName, status);
}
export function couldNotResolveNugetVersion(errorMessage: string, fallback: string) {
return l10n.t(
"Could not resolve Microsoft.Build.Sql version from NuGet ({0}). Using {1}.",
errorMessage,
fallback,
);
}

//#endregion

Expand Down
14 changes: 14 additions & 0 deletions extensions/sql-database-projects/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ import { ISqlProject, SqlTargetPlatform } from "sqldbproj";
import { SystemDatabase } from "./typeHelper";
import { DeploymentScenario } from "./enums";

/**
* Returns true if version is a valid NuGet floating version ("2.*" or "2.1.*").
*/
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
export function isValidMicrosoftBuildSqlVersion(version: string): boolean {
if (!version.endsWith(".*")) {
return false;
}
const parts = version.slice(0, -2).split(".");
if (parts.length < 1 || parts.length > 2) {
return false;
}
return parts.every((p) => p.length > 0 && String(parseInt(p, 10)) === p);
}
Comment thread
ssreerama marked this conversation as resolved.

/**
* Consolidates on the error message string
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import { SqlDatabaseProjectTreeViewProvider } from "./databaseProjectTreeViewPro
import { FolderNode, FileNode } from "../models/tree/fileFolderTreeItem";
import { BaseProjectTreeItem } from "../models/tree/baseTreeItem";
import { ImportDataModel } from "../models/api/import";
import { NetCoreTool, DotNetError, getMicrosoftBuildSqlVersion } from "../tools/netcoreTool";
import {
NetCoreTool,
DotNetError,
getMicrosoftBuildSqlVersion,
resolveNugetVersion,
OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION,
} from "../tools/netcoreTool";
import { BuildHelper } from "../tools/buildHelper";
import {
ISystemDatabaseReferenceSettings,
Expand Down Expand Up @@ -138,7 +144,22 @@ export class ProjectsController {
}

const sqlProjectsService = await utils.getSqlProjectsService();
const microsoftBuildSqlSDKStyleDefaultVersion = getMicrosoftBuildSqlVersion();

let microsoftBuildSqlSDKStyleDefaultVersion: string;
try {
microsoftBuildSqlSDKStyleDefaultVersion = await resolveNugetVersion(
"Microsoft.Build.Sql",
getMicrosoftBuildSqlVersion(),
);
} catch (e) {
microsoftBuildSqlSDKStyleDefaultVersion = OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION;
void vscode.window.showWarningMessage(
constants.couldNotResolveNugetVersion(
utils.getErrorMessage(e),
microsoftBuildSqlSDKStyleDefaultVersion,
),
);
}
const projectStyle = creationParams.sdkStyle
? mssqlVscode.ProjectType.SdkStyle
: mssqlVscode.ProjectType.LegacyStyle;
Expand Down
12 changes: 10 additions & 2 deletions extensions/sql-database-projects/src/templates/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as path from "path";
import { promises as fs } from "fs";
import { ItemType } from "sqldbproj";
import * as constants from "../common/constants";
import { getMicrosoftBuildSqlVersion } from "../tools/netcoreTool";
import { getMicrosoftBuildSqlVersion, resolveNugetVersion } from "../tools/netcoreTool";

Comment thread
ssreerama marked this conversation as resolved.
export let newSqlProjectTemplate: string;
export let newSdkSqlProjectTemplate: string;
Expand Down Expand Up @@ -82,7 +82,15 @@ export async function loadTemplates(templateFolderPath: string) {
Promise.resolve(
(newSdkSqlProjectTemplate = macroExpansion(
await loadTemplate(templateFolderPath, "newSdkSqlProjectTemplate.xml"),
new Map([["MICROSOFT_BUILD_SQL_VERSION", getMicrosoftBuildSqlVersion()]]),
new Map([
[
Comment thread
ssreerama marked this conversation as resolved.
"MICROSOFT_BUILD_SQL_VERSION",
await resolveNugetVersion(
"Microsoft.Build.Sql",
Comment thread
ssreerama marked this conversation as resolved.
getMicrosoftBuildSqlVersion(),
),
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
],
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
]),
)),
),
loadObjectTypeInfo(
Expand Down
20 changes: 19 additions & 1 deletion extensions/sql-database-projects/src/tools/buildHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import * as sqldbproj from "sqldbproj";
import * as extractZip from "extract-zip";
import * as constants from "../common/constants";
import { HttpClient } from "../http/httpClient";
import { getMicrosoftBuildSqlVersion } from "./netcoreTool";
import {
getMicrosoftBuildSqlVersion,
resolveNugetVersion,
OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION,
} from "./netcoreTool";
import { ProjectType } from "../common/typeHelper";
import * as vscodeMssql from "vscode-mssql";

Expand Down Expand Up @@ -131,6 +135,20 @@ export class BuildHelper {
nugetFolderWithExpectedfiles: string,
outputChannel: vscode.OutputChannel,
): Promise<boolean> {
if (utils.isValidMicrosoftBuildSqlVersion(nugetVersion)) {
try {
nugetVersion = await resolveNugetVersion(nugetName, nugetVersion);
} catch (e) {
nugetVersion = OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION;
Comment thread
ssreerama marked this conversation as resolved.
const fallbackMessage = constants.couldNotResolveNugetVersion(
utils.getErrorMessage(e),
nugetVersion,
);
Comment thread
ssreerama marked this conversation as resolved.
outputChannel.appendLine(fallbackMessage);
void vscode.window.showWarningMessage(fallbackMessage);
}
}

const fullNugetName = `${nugetName}.${nugetVersion}`;
const fullNugetPath = path.join(this.extensionBuildDir, `${fullNugetName}.nupkg`);

Expand Down
93 changes: 68 additions & 25 deletions extensions/sql-database-projects/src/tools/netcoreTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as os from "os";
import * as path from "path";
import * as semver from "semver";
import * as vscode from "vscode";
import axios from "axios";
import { HttpClient } from "../http/httpClient";
import {
DoNotAskAgain,
Install,
Expand All @@ -17,6 +19,8 @@ import {
UpdateDotnetLocation,
loc0ErroredOut1,
microsoftBuildSqlVersionKey,
nugetVersionResolutionFailed,
nugetIndexFetchFailed,
} from "../common/constants";
import * as utils from "../common/utils";
import { ShellCommandOptions, ShellExecutionHelper } from "./shellExecutionHelper";
Expand All @@ -32,42 +36,81 @@ export const macPlatform = "darwin";
export const linuxPlatform = "linux";
export const minSupportedNetCoreVersionForBuild = "8.0.0";

/**
* Fallback version for Microsoft.Build.Sql when the setting is not configured or invalid.
* NOTE: Keep this in sync with the default value in package.json:
* sqlDatabaseProjects.microsoftBuildSqlVersion.default
*/
export const FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.1.0";
/** Default Microsoft.Build.Sql floating version. Change to target a different major. */
export const FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.*";

export const enum netCoreInstallState {
netCoreNotPresent,
netCoreVersionNotSupported,
netCoreVersionSupported,
}
/** Exact fallback version used when NuGet resolution fails (offline/proxy). */
export const OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.2.0";

const dotnet = os.platform() === "win32" ? "dotnet.exe" : "dotnet";

/**
* Returns the configured Microsoft.Build.Sql version.
*
* Resolution order:
* 1. User's configured value (global or workspace settings.json) — if it is a valid semver.
* 2. Package.json default value — returned by config.get() when the user has not overridden the setting.
* 3. FALLBACK_MICROSOFT_BUILD_SQL_VERSION — used only when both of the above are unavailable or
* not a valid semver (e.g. the extension package.json default is missing or the user typed an
* invalid version string).
*/
/** Returns the configured Microsoft.Build.Sql version, falling back to FALLBACK_MICROSOFT_BUILD_SQL_VERSION. */
export function getMicrosoftBuildSqlVersion(): string {
const config = vscode.workspace.getConfiguration(DBProjectConfigurationKey);
const configured = config.get<string>(microsoftBuildSqlVersionKey)?.trim();
if (configured && semver.valid(configured)) {
if (
configured &&
(semver.valid(configured) || utils.isValidMicrosoftBuildSqlVersion(configured))
) {
return configured;
}

// Fall back to the hardcoded constant if config value is unavailable or invalid
return FALLBACK_MICROSOFT_BUILD_SQL_VERSION;
}

/** Thrown when no stable NuGet versions match the requested version prefix. */
export class NoMatchingNugetVersionError extends Error {
constructor(message: string) {
super(message);
this.name = "NoMatchingNugetVersionError";
}
}

/** Resolves a floating NuGet version to the latest matching stable release. Exact versions are returned as-is. Throws on network failure. */
export async function resolveNugetVersion(packageName: string, version: string): Promise<string> {
if (semver.valid(version)) {
return version;
}

try {
return await resolveFloatingVersion(packageName, version);
} catch (e) {
if (
e instanceof NoMatchingNugetVersionError &&
version !== FALLBACK_MICROSOFT_BUILD_SQL_VERSION
) {
return resolveFloatingVersion(packageName, FALLBACK_MICROSOFT_BUILD_SQL_VERSION);
}
throw e;
}
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
}

/** Resolves a floating version prefix to the latest stable exact version via the NuGet v3 API. */
async function resolveFloatingVersion(packageName: string, version: string): Promise<string> {
const indexUrl = `https://api.nuget.org/v3-flatcontainer/${packageName.toLowerCase()}/index.json`;

Comment thread
ssreerama marked this conversation as resolved.
const { requestUrl: resolvedUrl, config } = new HttpClient().setupRequest(indexUrl);
config.timeout = 10_000;

const response = await axios.get<{ versions: string[] }>(resolvedUrl, config);
if (response.status !== 200) {
throw new Error(nugetIndexFetchFailed(packageName, response.status));
}

const resolved = semver.maxSatisfying(response.data.versions, version);
if (resolved) {
return resolved;
}

throw new NoMatchingNugetVersionError(nugetVersionResolutionFailed(packageName, version));
}

export const enum netCoreInstallState {
netCoreNotPresent,
netCoreVersionNotSupported,
netCoreVersionSupported,
}

const dotnet = os.platform() === "win32" ? "dotnet.exe" : "dotnet";

export class NetCoreTool extends ShellExecutionHelper {
private osPlatform: string = os.platform();
private netCoreSdkInstalledVersion: string | undefined;
Expand Down
60 changes: 60 additions & 0 deletions extensions/sql-database-projects/test/netCoreTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import * as fs from "fs";
import * as path from "path";
import * as vscode from "vscode";
import * as sinon from "sinon";
import axios from "axios";
import {
NetCoreTool,
DBProjectConfigurationKey,
DotnetInstallLocationKey,
FALLBACK_MICROSOFT_BUILD_SQL_VERSION,
getMicrosoftBuildSqlVersion,
resolveNugetVersion,
} from "../src/tools/netcoreTool";
import { deleteGeneratedTestFolder, generateTestFolderPath } from "./testUtils";
import { createContext, TestContext } from "./testContext";
Expand Down Expand Up @@ -159,4 +161,62 @@ suite("NetCoreTool: Net core tests", function (): void {
expect(result).to.equal(FALLBACK_MICROSOFT_BUILD_SQL_VERSION);
});
});

suite("resolveNugetVersion tests", function (): void {
let axiosGetStub: sinon.SinonStub;

setup(function (): void {
axiosGetStub = sandbox.stub(axios, "get");
});

function stubNugetResponse(versions: string[]): void {
axiosGetStub.resolves({ status: 200, data: { versions } });
}

test("Should return exact version unchanged", async function (): Promise<void> {
// No network call expected for exact versions
const result = await resolveNugetVersion("Microsoft.Build.Sql", "2.1.0");
expect(result).to.equal("2.1.0");
expect(axiosGetStub.callCount).to.equal(0);
});

test("Should resolve floating version to latest matching stable", async function (): Promise<void> {
stubNugetResponse(["2.0.0", "2.1.0", "2.1.1", "2.1.1-preview", "3.0.0"]);
const result = await resolveNugetVersion("Microsoft.Build.Sql", "2.*");
expect(result).to.equal("2.1.1");
});

test("Should resolve floating minor version to latest matching stable", async function (): Promise<void> {
stubNugetResponse(["2.1.0", "2.1.1", "2.2.0"]);
const result = await resolveNugetVersion("Microsoft.Build.Sql", "2.1.*");
expect(result).to.equal("2.1.1");
});

test("Should fall back to FALLBACK version when no match found for requested version", async function (): Promise<void> {
// First call returns no matching versions (for "4.*")
// Second call (fallback to "2.*") returns matching versions
let callCount = 0;
axiosGetStub.callsFake(async () => {
callCount++;
const versions = callCount === 1 ? ["3.0.0"] : ["2.0.0", "2.1.0"];
return { status: 200, data: { versions } };
});
const result = await resolveNugetVersion("Microsoft.Build.Sql", "4.*");
expect(result).to.equal("2.1.0");
});

test("Should throw when network error occurs and no fallback available", async function (): Promise<void> {
axiosGetStub.rejects(new Error("network failure"));
// Use FALLBACK version so no second fallback attempt
try {
await resolveNugetVersion(
"Microsoft.Build.Sql",
FALLBACK_MICROSOFT_BUILD_SQL_VERSION,
);
expect.fail("Should have thrown");
} catch (e: unknown) {
expect((e as Error).message).to.include("network failure");
}
});
});
});
Loading
Loading