Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
5 changes: 5 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,11 @@
"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}. Falling back to {2}.": "No stable versions of {0} found matching {1}. Falling back to {2}.",
"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}",
"Resolved {0} ș {1}": "Resolved {0} ș {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
28 changes: 28 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,34 @@
export function errorExtracting(path: string, error: string) {
return l10n.t("Error extracting files from {0}. Error: {1}", path, error);
}
export function nugetVersionResolutionFallbackWarning(
packageName: string,
version: string,
fallback: string,
) {
return l10n.t(
"No stable versions of {0} found matching {1}. Falling back to {2}.",
packageName,
version,
fallback,
);
}
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 nugetVersionResolved(packageName: string, version: string) {
return l10n.t("Resolved {0} \u2192 {1}", packageName, version);
Comment thread
ssreerama marked this conversation as resolved.
Outdated
}
Comment thread
ssreerama marked this conversation as resolved.
Outdated
Comment thread
ssreerama marked this conversation as resolved.
Outdated
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
16 changes: 16 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,22 @@ import { ISqlProject, SqlTargetPlatform } from "sqldbproj";
import { SystemDatabase } from "./typeHelper";
import { DeploymentScenario } from "./enums";

/**
* Returns true if the version string is a NuGet floating version ("2.*" or "2.1.*").
* Does NOT accept exact semver — callers should check semver.valid() separately.
*/
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
export function isValidMicrosoftBuildSqlVersion(version: string): boolean {
// Accept "N.*" or "N.M.*" floating versions only.
if (!version.endsWith(".*")) {
return false;
}
const parts = version.slice(0, -2).split("."); // strip ".*", split remainder
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,23 @@ export class ProjectsController {
}

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

// Resolve floating NuGet version to an exact version. Fall back gracefully if offline/proxy.
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
23 changes: 22 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,23 @@ export class BuildHelper {
nugetFolderWithExpectedfiles: string,
outputChannel: vscode.OutputChannel,
): Promise<boolean> {
// Resolve NuGet floating versions (e.g. "2.*") to an exact version via the NuGet v3 API
// before constructing the download URL, which requires an exact version.
if (utils.isValidMicrosoftBuildSqlVersion(nugetVersion)) {
try {
nugetVersion = await resolveNugetVersion(nugetName, nugetVersion);
outputChannel.appendLine(constants.nugetVersionResolved(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
129 changes: 109 additions & 20 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,9 @@ import {
UpdateDotnetLocation,
loc0ErroredOut1,
microsoftBuildSqlVersionKey,
nugetVersionResolutionFallbackWarning,
nugetVersionResolutionFailed,
nugetIndexFetchFailed,
} from "../common/constants";
import * as utils from "../common/utils";
import { ShellCommandOptions, ShellExecutionHelper } from "./shellExecutionHelper";
Expand All @@ -33,41 +38,125 @@ 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
* Default Microsoft.Build.Sql version. Uses a NuGet floating version so that projects and
* legacy DLL downloads always target the latest 2.x release. To upgrade to 3.x in the future,
* change only this constant (and the matching default in package.json).
*/
export const FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.1.0";
export const FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.*";

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

const dotnet = os.platform() === "win32" ? "dotnet.exe" : "dotnet";
/**
* Exact version used when NuGet resolution fails (offline / proxy). Kept in sync with the
* latest known-good 2.x release so projects created offline are immediately buildable.
*/
export const OFFLINE_FALLBACK_MICROSOFT_BUILD_SQL_VERSION = "2.2.0";

/**
* 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).
* Accepts both exact semver versions and NuGet floating versions (e.g. "2.*").
* When a floating version is returned, callers that construct NuGet download URLs must resolve
* it to an exact version first via the NuGet v3 index API.
* Falls back to FALLBACK_MICROSOFT_BUILD_SQL_VERSION if the user-configured value is absent or invalid.
*/
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 by resolveFloatingVersion() when the package exists on NuGet but has no stable
* releases matching the requested version prefix. Distinct from network / HTTP failures.
*/
export class NoMatchingNugetVersionError extends Error {
constructor(message: string) {
super(message);
this.name = "NoMatchingNugetVersionError";
}
}

/**
Comment thread
ssreerama marked this conversation as resolved.
Outdated
* Resolves a NuGet floating version (e.g. "2.*", "2.1.*") to the latest matching stable
* exact version by querying the NuGet v3 flat-container index.
* If the version is already exact (valid semver), it is returned as-is.
* If the requested floating version has no matching stable releases on NuGet
* (NoMatchingNugetVersionError), falls back to FALLBACK_MICROSOFT_BUILD_SQL_VERSION and
* shows a VS Code warning. Network / proxy failures are re-thrown immediately without a
* warning so callers can show a single, actionable message.
*/
export async function resolveNugetVersion(packageName: string, version: string): Promise<string> {
if (semver.valid(version)) {
return version; // Already exact — nothing to resolve.
}

try {
return await resolveFloatingVersion(packageName, version);
} catch (e) {
// Only fall back when there are simply no matching stable versions (e.g. user entered
// "4.*" but only 2.x and 3.x exist). For network / proxy / HTTP errors, re-throw
// immediately so the caller can show a single couldNotResolveNugetVersion message.
if (
e instanceof NoMatchingNugetVersionError &&
version !== FALLBACK_MICROSOFT_BUILD_SQL_VERSION
) {
void vscode.window.showWarningMessage(
Comment thread
ssreerama marked this conversation as resolved.
Outdated
nugetVersionResolutionFallbackWarning(
packageName,
version,
FALLBACK_MICROSOFT_BUILD_SQL_VERSION,
),
);
return resolveFloatingVersion(packageName, FALLBACK_MICROSOFT_BUILD_SQL_VERSION);
}
throw e; // Re-throw network/HTTP failures (or fallback version with no match).
}
Comment thread
ssreerama marked this conversation as resolved.
Comment thread
ssreerama marked this conversation as resolved.
}

/**
* Core NuGet v3 index lookup — resolves a floating version prefix to the latest stable exact
* version. Uses the extension's HttpClient (proxy + strictSSL aware) with a 10 s timeout.
* Throws if no match is found or the network call fails.
*/
async function resolveFloatingVersion(packageName: string, version: string): Promise<string> {
// "2.*" → prefix "2." | "2.1.*" → prefix "2.1."
const prefix = version.slice(0, version.lastIndexOf("*"));
const indexUrl = `https://api.nuget.org/v3-flatcontainer/${packageName.toLowerCase()}/index.json`;

Comment thread
ssreerama marked this conversation as resolved.
// Use HttpClient.setupRequest to pick up VS Code proxy + strictSSL configuration.
// setupRequest may return a modified URL (e.g. with an explicit port for certain proxies),
// so we must use the returned url rather than the original indexUrl.
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));
}

// Stable versions only (no pre-release), matching the prefix, pick the last (highest).
const matching = response.data.versions.filter((v) => v.startsWith(prefix) && !v.includes("-"));
Comment thread
ssreerama marked this conversation as resolved.
Outdated

if (matching.length > 0) {
return matching[matching.length - 1];
}

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
Loading
Loading