Skip to content
Open
Show file tree
Hide file tree
Changes from all 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.*").
*/
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) => /^\d+$/.test(p));
}

/**
* 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.
]),
)),
),
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,
);
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;
}
}

/** 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
11 changes: 7 additions & 4 deletions extensions/sql-database-projects/test/buildHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ suite("BuildHelper: Build Helper tests", function (): void {
}
});

test("Should get correct build folder", async function (): Promise<void> {
const testContext: TestContext = createContext();
test("Should get correct build folder", function (): void {
const buildHelper = new BuildHelper();
await buildHelper.createBuildDirFolder(testContext.outputChannel);

// get expected path for build
// extensionBuildDirPath is set in the constructor — no network calls needed.
const extensionPath =
vscode.extensions.getExtension(sqldbproj.extension.vsCodeName)?.extensionPath ?? "";
expect(buildHelper.extensionBuildDirPath).to.equal(
Expand Down Expand Up @@ -126,6 +124,11 @@ suite("BuildHelper: Build Helper tests", function (): void {
});

test("Should have all required DLLs in build directory", async function (): Promise<void> {
// Stub file-existence checks so all files are considered present — no download triggered.
sandbox.stub(utils, "exists").resolves(true);
// Stub version validation so the floating-version NuGet resolution (network call) is skipped.
sandbox.stub(utils, "isValidMicrosoftBuildSqlVersion").returns(false);

const testContext: TestContext = createContext();
const buildHelper = new BuildHelper();
const success = await buildHelper.createBuildDirFolder(testContext.outputChannel);
Expand Down
Loading
Loading