From f0b0133cbaccbd8885540e529fc8ffe097b8daf7 Mon Sep 17 00:00:00 2001 From: PtJade Ceramic <185668489+PtJade-Ceramic@users.noreply.github.com> Date: Sat, 12 Sep 2026 22:48:25 +0800 Subject: [PATCH 1/4] test: define WinGet parsing and update prompt behavior Test-first: covers parsing of 'winget show' output and of winget-pkgs manifest listings, plus the buttons and notes the update prompt should show for each WinGet state, including that WinGet is not offered off Windows. --- test/features/UpdatePowerShell.test.ts | 133 ++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/test/features/UpdatePowerShell.test.ts b/test/features/UpdatePowerShell.test.ts index df146b3081..bf6fc89e87 100644 --- a/test/features/UpdatePowerShell.test.ts +++ b/test/features/UpdatePowerShell.test.ts @@ -3,7 +3,13 @@ import assert from "assert"; import * as vscode from "vscode"; -import { UpdatePowerShell } from "../../src/features/UpdatePowerShell"; +import { + buildUpdatePrompt, + getLatestWinGetVersion, + parseWinGetShowOutput, + toTriple, + UpdatePowerShell, +} from "../../src/features/UpdatePowerShell"; import type { IPowerShellVersionDetails } from "../../src/session"; import { changeSetting } from "../../src/settings"; import { testLogger } from "../utils"; @@ -146,4 +152,129 @@ describe("UpdatePowerShell feature", function () { assert(tag?.startsWith("v7.6") || tag?.startsWith("v7.4")); }); }); + + describe("WinGet version detection", function () { + it("Strips the revision component of a WinGet version", function () { + assert.strictEqual(toTriple("7.5.3.0"), "7.5.3"); + assert.strictEqual(toTriple("7.5.3"), "7.5.3"); + }); + + it("Parses the version from 'winget show' output", function () { + assert.strictEqual( + parseWinGetShowOutput( + [ + "Found Microsoft PowerShell [Microsoft.PowerShell]", + "Version: 7.5.3.0", + "Publisher: Microsoft Corporation", + "Moniker: powershell", + ].join("\n"), + ), + "7.5.3", + ); + }); + + it("Returns undefined when 'winget show' has no version", function () { + assert.strictEqual( + parseWinGetShowOutput( + "No package found matching input criteria.", + ), + undefined, + ); + }); + + it("Gets the newest version from a winget-pkgs listing", function () { + assert.strictEqual( + getLatestWinGetVersion([ + { name: "7.4.5.0", type: "dir" }, + { name: "7.5.3.0", type: "dir" }, + { name: "7.6.0", type: "dir" }, + { name: "README.md", type: "file" }, + ]), + "7.6.0", + ); + }); + + it("Returns undefined for a listing without versions", function () { + assert.strictEqual( + getLatestWinGetVersion([ + { name: ".gitattributes", type: "file" }, + ]), + undefined, + ); + }); + }); + + describe("The update prompt", function () { + const titlesOf = (prompt: { options: { title: string }[] }): string[] => + prompt.options.map(({ title }) => title); + + it("Does not offer WinGet where it does not exist", function () { + const prompt = buildUpdatePrompt("7.5.2", "v7.5.3", false, { + installed: false, + }); + assert.deepStrictEqual(prompt.options, [ + { id: "github", title: "Open GitHub Release" }, + { id: "not-now", title: "Not Now" }, + { id: "dont-show", title: "Don't Show Again" }, + ]); + assert(!prompt.message.includes("WinGet")); + }); + + it("Names both versions and asks whether to upgrade", function () { + const prompt = buildUpdatePrompt("7.5.2", "v7.5.3", false, { + installed: false, + }); + assert(prompt.message.includes("PowerShell v7.5.2 is out-of-date")); + assert(prompt.message.includes("The latest version is v7.5.3")); + assert(prompt.message.includes("Would you like to upgrade?")); + }); + + it("Offers to install WinGet on Windows without it", function () { + const prompt = buildUpdatePrompt("7.5.2", "v7.5.3", true, { + installed: false, + }); + assert(titlesOf(prompt).includes("Install WinGet")); + assert(prompt.message.includes("is not installed")); + }); + + it("Mentions the version WinGet would offer when missing", function () { + const prompt = buildUpdatePrompt("7.5.2", "v7.5.3", true, { + installed: false, + version: "7.5.3", + }); + assert( + prompt.message.includes( + "WinGet is not installed. It offers v7.5.3", + ), + ); + }); + + it("Offers to upgrade with WinGet when it has the new version", function () { + const prompt = buildUpdatePrompt("7.5.2", "v7.5.3", true, { + installed: true, + version: "7.5.3", + }); + assert(titlesOf(prompt).includes("Upgrade with WinGet")); + assert(!titlesOf(prompt).includes("View WinGet Progress")); + assert(!prompt.message.includes("caught up")); + }); + + it("Offers WinGet progress and notes its older version", function () { + const prompt = buildUpdatePrompt("7.5.0", "v7.5.3", true, { + installed: true, + version: "7.5.1", + }); + assert(titlesOf(prompt).includes("View WinGet Progress")); + assert(prompt.message.includes("WinGet currently has v7.5.1")); + }); + + it("Says when WinGet has not caught up yet", function () { + assert( + buildUpdatePrompt("7.5.2", "v7.5.3", true, { + installed: true, + version: "7.4.0", + }).message.includes("hasn't caught up yet"), + ); + }); + }); }); From 0c38bd9f015ff377253ad5f3081d79b11ca4c550 Mon Sep 17 00:00:00 2001 From: PtJade Ceramic <185668489+PtJade-Ceramic@users.noreply.github.com> Date: Sat, 12 Sep 2026 22:48:26 +0800 Subject: [PATCH 2/4] feat: add WinGet upgrade option to PowerShell update notification (#5477) Detects WinGet via 'winget show --id Microsoft.PowerShell', falling back to the winget-pkgs listing when the CLI is missing. The prompt can upgrade with WinGet, install WinGet, and follow WinGet's progress, noting the version WinGet currently has. It is built by a pure buildUpdatePrompt() function, and WinGet options are only offered on Windows. --- src/features/UpdatePowerShell.ts | 455 +++++++++++++++++++++++-------- 1 file changed, 338 insertions(+), 117 deletions(-) diff --git a/src/features/UpdatePowerShell.ts b/src/features/UpdatePowerShell.ts index 99532e1d13..b60b5773a0 100644 --- a/src/features/UpdatePowerShell.ts +++ b/src/features/UpdatePowerShell.ts @@ -8,34 +8,129 @@ import type { ILogger } from "../logging"; import type { IPowerShellVersionDetails } from "../session"; import { changeSetting } from "../settings"; -interface IUpdateMessageItem extends vscode.MessageItem { - id: number; +async function fetchJSON(url: string): Promise { + const response = await fetch(url); + if (!response.ok) return undefined; + return response.json(); +} + +/** Strip the 4th component from a WinGet version (e.g. "7.4.5.0" → "7.4.5"). */ +export function toTriple(v: string): string { + return v.split(".").slice(0, 3).join("."); +} + +/** Parse the PowerShell version out of `winget show` output. */ +export function parseWinGetShowOutput(output: string): string | undefined { + const match = /Version:\s*([\d.]+)/.exec(output); + return match ? toTriple(match[1]) : undefined; +} + +/** One entry from the winget-pkgs manifest directory listing. */ +export interface IWinGetManifestEntry { + name: string; + type: string; +} + +/** Get the newest PowerShell version from a manifest directory listing. */ +export function getLatestWinGetVersion( + entries: IWinGetManifestEntry[], +): string | undefined { + return entries + .filter(({ type }) => type === "dir") + .map(({ name }) => toTriple(name)) + .sort((a, b) => new SemVer(b).compare(a))[0]; +} + +/** What WinGet has for PowerShell on this machine, if anything. */ +export interface IWinGetStatus { + /** Whether the `winget` CLI was found. */ + installed: boolean; + /** The newest PowerShell version WinGet knows about, if known. */ + version?: string; +} + +/** A button of the update prompt. */ +export interface IUpdatePromptOption { + id: "winget" | "winget-progress" | "github" | "not-now" | "dont-show"; + title: string; +} + +/** The update prompt to show the user. */ +export interface IUpdatePrompt { + message: string; + options: IUpdatePromptOption[]; +} + +/** + * Build the update prompt's message and buttons. This is a pure function so it + * can be tested; the WinGet status must be detected beforehand. + */ +export function buildUpdatePrompt( + localVersion: string, + releaseTag: string, + isWindows: boolean, + winget: IWinGetStatus, +): IUpdatePrompt { + const releaseVersion = new SemVer(releaseTag); + const options: IUpdatePromptOption[] = []; + + // WinGet only exists on Windows, so don't offer it elsewhere. + if (isWindows) { + options.push({ + id: "winget", + title: winget.installed ? "Upgrade with WinGet" : "Install WinGet", + }); + if ( + winget.installed && + winget.version && + new SemVer(winget.version).compare(releaseVersion.version) < 0 + ) { + options.push({ + id: "winget-progress", + title: "View WinGet Progress", + }); + } + } + options.push( + { id: "github", title: "Open GitHub Release" }, + { id: "not-now", title: "Not Now" }, + { id: "dont-show", title: "Don't Show Again" }, + ); + + let message = + `PowerShell v${localVersion} is out-of-date.\n` + + `The latest version is v${releaseVersion.version}.`; + // Note when WinGet is relevant but not (yet) useful. + if (winget.installed && winget.version) { + const wingetVersion = new SemVer(winget.version); + if (wingetVersion.compare(localVersion) <= 0) { + message += `\n(WinGet hasn't caught up yet — currently v${winget.version}.)`; + } else if (wingetVersion.compare(releaseVersion.version) < 0) { + message += `\n(WinGet currently has v${winget.version}.)`; + } + } else if (isWindows) { + message += winget.version + ? `\n(WinGet is not installed. It offers v${winget.version}.)` + : `\n(WinGet, the Windows Package Manager, is not installed.)`; + } + message += `\nWould you like to upgrade?`; + + return { message, options }; +} + +/** Await a value/promise, and if non-nullish, pass it to `fn`. */ +async function whenSome( + value: T | undefined | null | Promise, + fn: (value: T) => void | Promise, +): Promise { + const resolved = await value; + if (resolved != null) await fn(resolved); } // This attempts to mirror PowerShell's `UpdatesNotification.cs` logic as much as // possibly, documented at: // https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_update_notifications export class UpdatePowerShell { - private static LTSBuildInfoURL = "https://aka.ms/pwsh-buildinfo-lts"; - private static StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable"; - private static PreviewBuildInfoURL = - "https://aka.ms/pwsh-buildinfo-preview"; - private static GitHubWebReleaseURL = - "https://github.com/PowerShell/PowerShell/releases/tag/"; - private static promptOptions: IUpdateMessageItem[] = [ - { - id: 0, - title: "Yes", - }, - { - id: 1, - title: "Not Now", - }, - { - id: 2, - title: "Don't Show Again", - }, - ]; private localVersion: SemVer; constructor( @@ -49,56 +144,47 @@ export class UpdatePowerShell { this.localVersion = new SemVer(versionDetails.commit); } + private skip(reason: string): false { + this.logger.writeDebug(reason); + return false; + } + private shouldCheckForUpdate(): boolean { // Respect user setting. const promptToUpdatePowerShell = vscode.workspace .getConfiguration("powershell") .get("promptToUpdatePowerShell", true); - if (!promptToUpdatePowerShell) { - this.logger.writeDebug( - "Setting 'promptToUpdatePowerShell' was false.", - ); - return false; - } + if (!promptToUpdatePowerShell) + return this.skip("Setting 'promptToUpdatePowerShell' was false."); // Respect environment configuration. - if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") { - this.logger.writeDebug( + if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "off") + return this.skip( "Environment variable 'POWERSHELL_UPDATECHECK' was 'Off'.", ); - return false; - } // Skip prompting when using Windows PowerShell for now. - if (this.localVersion.compare("6.0.0") === -1) { + if (this.localVersion.compare("6.0.0") === -1) // TODO: Maybe we should announce PowerShell Core? - this.logger.writeDebug( - "Not prompting to update Windows PowerShell.", - ); - return false; - } + return this.skip("Not prompting to update Windows PowerShell."); if (this.localVersion.prerelease.length > 1) { // Daily builds look like '7.3.0-daily20221206.1' which split to // ['daily20221206', '1'] and development builds look like // '7.3.0-preview.3-508-g07175...' which splits to ['preview', // '3-508-g0717...']. The ellipsis is hiding a 40 char hash. - const daily = this.localVersion.prerelease[0].toString(); - const commit = this.localVersion.prerelease[1].toString(); - // Skip if PowerShell is self-built, that is, this contains a commit hash. - if (commit.length >= 40) { - this.logger.writeDebug( - "Not prompting to update development build.", - ); - return false; - } + if (this.localVersion.prerelease[1].toString().length >= 40) + return this.skip("Not prompting to update development build."); // Skip if preview is a daily build. - if (daily.toLowerCase().startsWith("daily")) { - this.logger.writeDebug("Not prompting to update daily build."); - return false; - } + if ( + this.localVersion.prerelease[0] + .toString() + .toLowerCase() + .startsWith("daily") + ) + return this.skip("Not prompting to update daily build."); } // TODO: Check if network is available? @@ -107,17 +193,10 @@ export class UpdatePowerShell { } private async getRemoteVersion(url: string): Promise { - const response = await fetch(url); - if (!response.ok) { - return undefined; - } - // Looks like: - // { - // "ReleaseDate": "2022-10-20T22:01:38Z", - // "BlobName": "v7-2-7", - // "ReleaseTag": "v7.2.7" - // } - const data = await response.json(); + const data = await fetchJSON<{ + ReleaseTag: string; + }>(url); + if (!data) return undefined; this.logger.writeDebug( `Received from '${url}':\n${JSON.stringify(data, undefined, 2)}`, ); @@ -130,40 +209,21 @@ export class UpdatePowerShell { } this.logger.writeDebug("Checking for PowerShell update..."); - const tags: string[] = []; - if (process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts") { - // Only check for update to LTS. - this.logger.writeDebug("Checking for LTS update..."); - const tag = await this.getRemoteVersion( - UpdatePowerShell.LTSBuildInfoURL, - ); - if (tag != undefined) { - tags.push(tag); - } - } else { - // Check for update to stable. - this.logger.writeDebug("Checking for stable update..."); - const tag = await this.getRemoteVersion( - UpdatePowerShell.StableBuildInfoURL, - ); - if (tag != undefined) { - tags.push(tag); - } - - // Also check for a preview update. - if (this.localVersion.prerelease.length > 0) { - this.logger.writeDebug("Checking for preview update..."); - const tag = await this.getRemoteVersion( - UpdatePowerShell.PreviewBuildInfoURL, - ); - if (tag != undefined) { - tags.push(tag); - } - } - } - - for (const tag of tags) { - if (this.localVersion.compare(tag) === -1) { + const suffixes = + process.env.POWERSHELL_UPDATECHECK?.toLowerCase() === "lts" + ? ["lts"] + : this.localVersion.prerelease.length > 0 + ? ["stable", "preview"] + : ["stable"]; + this.logger.writeDebug( + `Checking for ${suffixes.join(" and ")} update...`, + ); + for (const tag of await Promise.all( + suffixes.map((s) => + this.getRemoteVersion(`https://aka.ms/pwsh-buildinfo-${s}`), + ), + )) { + if (tag != undefined && this.localVersion.compare(tag) === -1) { return tag; } } @@ -174,11 +234,9 @@ export class UpdatePowerShell { public async checkForUpdate(): Promise { try { - const tag = await this.maybeGetNewRelease(); - if (tag) { - await this.promptToUpdate(tag); - return; - } + await whenSome(this.maybeGetNewRelease(), (tag) => + this.promptToUpdate(tag), + ); } catch (err) { // Best effort. This probably failed to fetch the data from GitHub. this.logger.writeWarning( @@ -188,10 +246,11 @@ export class UpdatePowerShell { } private async openReleaseInBrowser(tag: string): Promise { - const url = vscode.Uri.parse( - UpdatePowerShell.GitHubWebReleaseURL + tag, + await vscode.env.openExternal( + vscode.Uri.parse( + `https://github.com/PowerShell/PowerShell/releases/tag/${tag}`, + ), ); - await vscode.env.openExternal(url); } private async promptToUpdate(tag: string): Promise { @@ -199,11 +258,58 @@ export class UpdatePowerShell { this.logger.write( `Prompting to update PowerShell v${this.localVersion.version} to v${releaseVersion.version}.`, ); + + const isWindows = process.platform === "win32"; + // Get the PowerShell version WinGet has, if WinGet exists here. + let winget: IWinGetStatus = { installed: false }; + if (isWindows) { + try { + const { execFile } = await import("node:child_process"); + const { promisify } = await import("node:util"); + const { stdout } = await promisify(execFile)("winget", [ + "show", + "--id", + "Microsoft.PowerShell", + "-s", + "winget", + "--accept-source-agreements", + ]); + const version = parseWinGetShowOutput(stdout); + if (version !== undefined) { + winget = { installed: true, version }; + } + } catch { + // WinGet may not be installed — fall back to the GitHub API to + // ask which version it would offer. + try { + const entries = await fetchJSON( + "https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/m/Microsoft/PowerShell?per_page=100", + ); + const version = entries + ? getLatestWinGetVersion(entries) + : undefined; + this.logger.writeDebug( + `WinGet repo latest: ${version ?? "not found"}`, + ); + if (version !== undefined) { + winget = { installed: false, version }; + } + } catch { + // Best effort. + } + } + } + + const { message, options } = buildUpdatePrompt( + this.localVersion.version, + tag, + isWindows, + winget, + ); + const result = await vscode.window.showInformationMessage( - `PowerShell v${this.localVersion.version} is out-of-date. - The latest version is v${releaseVersion.version}. - Would you like to open the GitHub release in your browser?`, - ...UpdatePowerShell.promptOptions, + message, + ...options, ); // If the user cancels the notification. @@ -212,20 +318,135 @@ export class UpdatePowerShell { return; } - this.logger.writeDebug( - `User said '${UpdatePowerShell.promptOptions[result.id].title}'.`, - ); + this.logger.writeDebug(`User said '${result.title}'.`); switch (result.id) { - // Yes - case 0: + case "winget": + if (winget.installed) { + this.logger.write("Upgrading PowerShell via WinGet..."); + vscode.window + .createTerminal("PowerShell Upgrade (WinGet)") + .sendText( + "winget update --id Microsoft.PowerShell -e -s winget", + ); + } else { + // From: https://aka.ms/winget-docs + this.logger.write( + "Installing WinGet and upgrading PowerShell...", + ); + vscode.window + .createTerminal("Install WinGet & Upgrade PowerShell") + .sendText( + "$result = Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ErrorAction SilentlyContinue; if ($?) { winget update --id Microsoft.PowerShell -e -s winget } else { Write-Warning 'Failed to install WinGet. See https://aka.ms/winget-docs' }", + ); + } + break; + case "winget-progress": { + // Open the winget-pkgs issue or PR for the version WinGet is + // missing; find the open issue/PR mentioning the highest + // PowerShell version. + let statusUrl = + "https://github.com/microsoft/winget-pkgs/pulls?q=Microsoft.PowerShell+is:open"; + try { + let bestPR: string | undefined; + let bestIssue: string | undefined; + let bestPRVer: string | undefined; + let bestIssueVer: string | undefined; + for (const { + ver, + html_url, + pull_request, + } of await Promise.all( + ( + ( + await fetchJSON<{ + items?: { + title: string; + html_url: string; + pull_request?: unknown; + }[]; + }>( + "https://api.github.com/search/issues?q=Microsoft.PowerShell+repo:microsoft/winget-pkgs+is:open&sort=created&order=desc&per_page=30", + ) + )?.items ?? [] + ).map( + async ( + item, + ): Promise => { + const tm = + /(?:New version|Update|\[Update Request\]).*?(\d+\.\d+\.\d+)/i.exec( + item.title, + ); + if (tm) return { ...item, ver: tm[1] }; + // Try to extract version from the issue body. + try { + const bm = ( + await fetchJSON<{ + body?: string; + }>( + item.html_url.replace( + "https://github.com/", + "https://api.github.com/repos/", + ), + ) + )?.body?.match( + /Package Version.*?(\d+\.\d+\.\d+)/i, + ); + if (bm) { + return { ...item, ver: bm[1] }; + } + } catch { + // Best effort. + } + return item; + }, + ), + )) { + if (!ver) continue; + if (pull_request) { + if ( + !bestPRVer || + new SemVer(ver).compare(bestPRVer) > 0 + ) { + bestPRVer = ver; + bestPR = html_url; + } + } else if ( + !bestIssueVer || + new SemVer(ver).compare(bestIssueVer) > 0 + ) { + bestIssueVer = ver; + bestIssue = html_url; + } + } + // Prefer issue when linked to the PR for the same version. + const prVer = bestPRVer ? new SemVer(bestPRVer) : undefined; + const issueVer = bestIssueVer + ? new SemVer(bestIssueVer) + : undefined; + if (prVer && issueVer && prVer.compare(issueVer) === 0) { + statusUrl = bestIssue!; + } else if ( + prVer && + (!issueVer || prVer.compare(issueVer) > 0) + ) { + statusUrl = bestPR!; + } else if (issueVer) { + statusUrl = bestIssue!; + } + } catch { + // Fall back to generic search. + } + await vscode.env.openExternal(vscode.Uri.parse(statusUrl)); + break; + } + case "github": await this.openReleaseInBrowser(tag); break; - // Not Now - case 1: + case "not-now": + // Do nothing. break; - // Don't Show Again - case 2: + case "dont-show": await changeSetting( "promptToUpdatePowerShell", false, From 34c33ec165c97e77c9107f4692b45963c620007b Mon Sep 17 00:00:00 2001 From: PtJade Ceramic <185668489+PtJade-Ceramic@users.noreply.github.com> Date: Sat, 12 Sep 2026 23:05:59 +0800 Subject: [PATCH 3/4] ci: add nuget.org to the PowerShellEditorServices sources Temporary workaround: PSES's nuget.config only lists an Azure DevOps feed, which returns 401 (Unauthorized) for anonymous downloads, so CI fails while restoring the .NET reference packs before any tests run. Drop this once that is fixed upstream. --- .github/workflows/ci-test.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index f5ddaf15a4..c8195c9535 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -15,6 +15,9 @@ jobs: ci: name: node strategy: + # Report every platform's result instead of cancelling the others as soon + # as one fails. + fail-fast: false matrix: os: [windows-latest, macos-latest, ubuntu-latest] runs-on: ${{ matrix.os }} @@ -30,6 +33,37 @@ jobs: repository: PowerShell/PowerShellEditorServices path: PowerShellEditorServices + # TODO: Drop this once PowerShellEditorServices can restore again. Its + # nuget.config only lists an Azure DevOps feed whose package downloads + # return 401 (Unauthorized) to anonymous clients such as GitHub runners, + # so restoring the .NET reference packs fails before any tests run. Add + # nuget.org and map the reference packs to it (otherwise NuGet still + # downloads them from the feed that 401s). + - name: Add nuget.org for the PowerShellEditorServices reference packs + shell: pwsh + run: | + $config = @( + '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '' + ) + Set-Content -Path PowerShellEditorServices/nuget.config -Value $config + - name: Checkout vscode-powershell uses: actions/checkout@v7 with: From b17a97720c01c29d459a4516988a320266006260 Mon Sep 17 00:00:00 2001 From: PtJade Ceramic <185668489+PtJade-Ceramic@users.noreply.github.com> Date: Sun, 13 Sep 2026 02:15:27 +0800 Subject: [PATCH 4/4] test: update `@vscode/test-electron` to 3.x for the renamed macOS binary VS Code 1.110 renamed the macOS main executable from 'Contents/MacOS/Electron' to the product name ('Code - Insiders' for Insiders), so v2's hardcoded path fails with 'spawn ... Electron ENOENT' before any test runs. v3 resolves it from the bundle's Info.plist. CI needs the `@vscode` scope served by npmjs meanwhile: the PowerShell npm mirror has not cached 3.1.0 yet and returns 401 for packages it has not cached, which npm silently skips for optional dependencies. Fixes #5592. --- .github/workflows/ci-test.yml | 10 ++++++++++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index c8195c9535..8dbd1e2ba7 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -69,6 +69,16 @@ jobs: with: path: vscode-powershell + # TODO: Drop this once the PowerShell npm mirror has cached + # @vscode/test-electron 3.x. The mirror returns 401 for packages it has + # not cached (upstream access needs auth), and npm silently skips optional + # dependencies it cannot fetch, so `vscode-test` cannot resolve the newer + # package and the run dies before doing anything. + - name: Use npmjs instead of the PowerShell mirror + shell: pwsh + working-directory: vscode-powershell + run: (Get-Content .npmrc) -replace '^registry=.*', 'registry=https://registry.npmjs.org/' | Set-Content .npmrc + - name: Validate snippets JSON file shell: pwsh run: $null = ConvertFrom-Json -InputObject (Get-Content -Raw -Path './snippets/PowerShell.json') diff --git a/package-lock.json b/package-lock.json index f51c954422..a05c96e848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "@ungap/structured-clone": "^1.3.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.12", - "@vscode/test-electron": "^2.5.2", + "@vscode/test-electron": "^3.1.0", "esbuild-register": "^3.6.0", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", @@ -1797,8 +1797,8 @@ } }, "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "integrity": "sha1-99QHjoIwzpyUMi8qKcwWwXlUCF0=", + "version": "3.1.0", + "integrity": "sha1-Rrk9EY3NOzyJyq4pahP5p/XrY8U=", "license": "MIT", "optional": true, "dependencies": { @@ -1809,7 +1809,7 @@ "semver": "^7.6.2" }, "engines": { - "node": ">=16" + "node": ">=22" } }, "node_modules/@vscode/vsce": { diff --git a/package.json b/package.json index 75eaf159e8..9f6ba11536 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@ungap/structured-clone": "^1.3.1", "@vscode/debugprotocol": "^1.68.0", "@vscode/test-cli": "^0.0.12", - "@vscode/test-electron": "^2.5.2", + "@vscode/test-electron": "^3.1.0", "esbuild-register": "^3.6.0", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8",