diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml
index f5ddaf15a4..8dbd1e2ba7 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,11 +33,52 @@ 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:
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",
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,
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"),
+ );
+ });
+ });
});