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
6 changes: 5 additions & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ jobs:
run: |
npm ci

- name: Stamp build as pull request version
run: |
node scripts/stamp-prerelease-version.mjs "${{ github.run_id }}" --label pr
Comment on lines +71 to +73

- name: MSSQL - Install dependencies and build
uses: ./.github/actions/build-mssql

Expand All @@ -86,7 +90,7 @@ jobs:
# that increase size
- name: Package extensions
run: |
npm run package
npm run package -- --preview

Comment on lines 91 to 94
- name: MSSQL - Upload VSIX files
uses: actions/upload-artifact@v6
Expand Down
5 changes: 5 additions & 0 deletions build/PullRequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ steps:
displayName: Install root dependencies
workingDirectory: "$(Build.SourcesDirectory)"

- pwsh: |
node scripts/stamp-prerelease-version.mjs "$(Build.BuildNumber)" --label pr
displayName: Stamp build as pull request version
workingDirectory: "$(Build.SourcesDirectory)"

- template: ./templates/build-mssql.yml@self
parameters:
packageOffline: false # don't build offline packages for PR builds to speed runs up
Expand Down
2 changes: 1 addition & 1 deletion build/templates/build-data-workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ steps:
condition: succeededOrFailed()

- pwsh: |
npm run package -- --target data-workspace
npm run package -- --target data-workspace --preview
displayName: DataWorkspace - Package extension
workingDirectory: "$(Build.SourcesDirectory)"

Expand Down
2 changes: 1 addition & 1 deletion build/templates/build-database-management-keymap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ steps:
version: 24.x

- pwsh: |
npm run package -- --target database-management-keymap
npm run package -- --target database-management-keymap --preview
displayName: Keymap - Package extension
workingDirectory: "$(Build.SourcesDirectory)"

Expand Down
4 changes: 2 additions & 2 deletions build/templates/build-mssql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ steps:
condition: succeededOrFailed()

- pwsh: |
npm run package -- --target mssql --online
npm run package -- --target mssql --online --preview
displayName: MSSQL - Package extension (online)
workingDirectory: "$(Build.SourcesDirectory)"

- pwsh: |
npm run package -- --target mssql --offline
npm run package -- --target mssql --offline --preview
displayName: MSSQL - Package extension (offline)
workingDirectory: "$(Build.SourcesDirectory)"
condition: eq( ${{ parameters.packageOffline }}, 'true')
Expand Down
2 changes: 1 addition & 1 deletion build/templates/build-sql-database-projects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ steps:
continueOnError: true

- pwsh: |
npm run package -- --target sql-database-projects
npm run package -- --target sql-database-projects --preview
displayName: SqlProj - Package extension
workingDirectory: "$(Build.SourcesDirectory)"

Expand Down
25 changes: 16 additions & 9 deletions extensions/mssql/scripts/package-extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const args = process.argv.slice(2);
let isOnline = args.includes("--online");
const isOffline = args.includes("--offline");
const skipServiceInstall = args.includes("--skip-service-install");
const isPreRelease = args.includes("--pre-release");
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";

// Platform configurations for offline packaging
Expand Down Expand Up @@ -100,12 +101,16 @@ async function cleanServiceInstallFolder() {
/**
* Package extension using vsce
*/
function packageExtension(packageName = null) {
function packageExtension(packageName = null, preRelease = false) {
logger.step("Packaging extension with vsce...");

try {
const vsceArgs = ["exec", "--", "vsce", "package", "--no-dependencies"];

if (preRelease) {
vsceArgs.push("--pre-release");
}

if (packageName) {
vsceArgs.push("-o", packageName);
}
Expand All @@ -124,7 +129,7 @@ function packageExtension(packageName = null) {
* Package extension for online distribution
*/
async function packageOnline(options = {}) {
const { skipServiceInstall = false } = options;
const { skipServiceInstall = false, preRelease = false } = options;

logger.header("Package extension (Online Mode)");
logger.info("Creating extension package with portable SQL Tools Service");
Expand All @@ -139,7 +144,7 @@ async function packageOnline(options = {}) {
await installSqlToolsService(platform.Runtime.Portable);
}
// Package the extension
packageExtension();
packageExtension(null, preRelease);
logger.success("Online packaging completed successfully!");
} catch (error) {
logger.error(`Online packaging failed: ${error.message}`);
Expand All @@ -150,7 +155,7 @@ async function packageOnline(options = {}) {
/**
* Package extension for a specific platform (offline)
*/
async function packageOfflinePlatform(platformConfig, packageName) {
async function packageOfflinePlatform(platformConfig, packageName, preRelease = false) {
const { rid, runtime } = platformConfig;

logger.step(`Packaging for ${rid}...`);
Expand All @@ -166,7 +171,7 @@ async function packageOfflinePlatform(platformConfig, packageName) {
await installSqlToolsService(runtimeValue);
// Package with platform-specific name
const platformPackageName = `${packageName}-${rid}.vsix`;
packageExtension(platformPackageName);
packageExtension(platformPackageName, preRelease);
logger.success(`${rid} package created`);
} catch (error) {
logger.error(`Failed to package ${rid}: ${error.message}`);
Expand All @@ -177,7 +182,8 @@ async function packageOfflinePlatform(platformConfig, packageName) {
/**
* Package extension for offline distribution (all platforms)
*/
async function packageOffline() {
async function packageOffline(options = {}) {
const { preRelease = false } = options;
logger.header("Package extension (Offline Mode)");

try {
Expand All @@ -200,7 +206,7 @@ async function packageOffline() {
);

try {
await packageOfflinePlatform(platformConfig, packageName);
await packageOfflinePlatform(platformConfig, packageName, preRelease);
} catch (error) {
logger.warning(`Skipping ${platformConfig.rid}: ${error.message}`);
}
Expand Down Expand Up @@ -230,6 +236,7 @@ Modes:
--online Package with portable SQL Tools Service (requires dotnet runtime at runtime). Default if not specified.
--offline Package with native self-contained SQL Tools Service for each platform (no dotnet needed).
--skip-service-install Online mode only. Reuse existing SQL Tools Service files and skip clean/install.
--pre-release Mark the package as a pre-release extension.
--help Show this help message

Examples:
Expand Down Expand Up @@ -272,9 +279,9 @@ async function main() {

try {
if (isOnline) {
await packageOnline({ skipServiceInstall });
await packageOnline({ skipServiceInstall, preRelease: isPreRelease });
} else if (isOffline) {
await packageOffline();
await packageOffline({ preRelease: isPreRelease });
}

logger.success("Packaging script completed successfully!");
Expand Down
93 changes: 93 additions & 0 deletions scripts/stamp-prerelease-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Stamps a pre-release version onto every extension's package.json.
// New version format: <currentVersion>-<label>.<buildId>+<shortGitHash>
//
// Usage:
// node scripts/stamp-prerelease-version.mjs <buildId> --label <label>
//
// Both arguments are required. Callers pick a label appropriate for their flow:
// - "preview" for official/buddy publishing pipelines
// - "pr" for PR-validation pipelines

import { execSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import process from "node:process";

const EXTENSIONS = [
"mssql",
"sql-database-projects",
"data-workspace",
"database-management-keymap",
];

const USAGE = "Usage: node scripts/stamp-prerelease-version.mjs <buildId> --label <label>";

function parseArgs(argv) {
let buildId;
let label;

for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === "--label") {
label = argv[i + 1];
i++;
continue;
}
if (arg.startsWith("--label=")) {
label = arg.slice("--label=".length);
continue;
}
if (!buildId) {
buildId = arg;
continue;
}
}

return { buildId, label };
}

function main() {
const { buildId, label } = parseArgs(process.argv.slice(2));

if (!buildId || buildId.trim().length === 0) {
console.error("Missing required <buildId> argument.");
console.error(USAGE);
process.exit(1);
}

if (!label || label.trim().length === 0) {
console.error("Missing required --label argument.");
console.error(USAGE);
process.exit(1);
}

const commitHash = execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
const shortHash = commitHash.slice(0, 7);

for (const ext of EXTENSIONS) {
const packagePath = path.join("extensions", ext, "package.json");
const original = readFileSync(packagePath, "utf8");

const json = JSON.parse(original);
const currentVersion = json.version;
if (!currentVersion) {
throw new Error(`No "version" field found in ${packagePath}`);
}

const newVersion = `${currentVersion}-${label}.${buildId}+${shortHash}`;
console.log(`Updating ${packagePath}: ${currentVersion} -> ${newVersion}`);

const updated = original.replace(
/"version"\s*:\s*"[^"]+"/,
`"version": "${newVersion}"`,
);

if (updated === original) {
throw new Error(`Failed to update version field in ${packagePath}`);
}

writeFileSync(packagePath, updated);
}
}

main();
25 changes: 22 additions & 3 deletions scripts/workspaces.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function parseArgs(argv) {
action,
forwardedArgs: [],
prod: false,
prerelease: false,
requireTarget: false,
targetValue: undefined,
};
Expand All @@ -45,6 +46,11 @@ function parseArgs(argv) {
continue;
}

if (arg === "--preview" || arg === "--pre-release") {
options.prerelease = true;
continue;
}

if (arg === "--require-target") {
options.requireTarget = true;
continue;
Expand All @@ -64,7 +70,7 @@ function printUsage() {
npm run test [-- --target <name>[,<name>]] [-- <target args>]
npm run smoketest [-- --target <name>[,<name>]] [-- <target args>]
npm run lint [-- --target <name>[,<name>]]
npm run package [-- --target <name>[,<name>]] [-- <target args>]
npm run package [-- --target <name>[,<name>]] [--preview|--pre-release] [-- <target args>]
npm run list:targets
`);
}
Expand Down Expand Up @@ -209,7 +215,7 @@ function listTargets() {
}

function main() {
const { action, forwardedArgs, prod, requireTarget, targetValue } = parseArgs(
const { action, forwardedArgs, prod, prerelease, requireTarget, targetValue } = parseArgs(
process.argv.slice(2),
);

Expand All @@ -235,10 +241,23 @@ function main() {
);
}

if (prerelease && action !== "package") {
throw new Error(
`The --preview/--pre-release flag is only supported for the "package" action.`,
);
}

const targets = resolveTargets(action, targetValue);
ensureProdBuildSupport(targets, prod);

const actionArgs = prod ? [...forwardedArgs, "--prod"] : forwardedArgs;
let actionArgs = forwardedArgs;
if (prod) {
actionArgs = [...actionArgs, "--prod"];
}

if (prerelease) {
actionArgs = [...actionArgs, "--pre-release"];
}

if (action === "watch") {
const watchableTargets = pruneRedundantWatchTargets(targets);
Expand Down
Loading