From dc29b6baed40f06dc28a8db943307f7d299f0b16 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:32:00 +0200 Subject: [PATCH 01/13] Reload the application once so first-run breakpoints are hit (#1) A launch installs and starts the app, which finishes evaluating its bundle well before the CDP proxy manages to attach. Hermes replays scriptParsed on Debugger.enable, so breakpoints registered for that first run resolve and look bound, but the code they belong to has already executed and they are never reached. Reloading by hand makes them work, which is what users hit today. Hermes has no pause-on-load to wait for: it answers Runtime.runIfWaitingForDebugger with "Unsupported method" (-32601), so the only lever is to run the bundle again once the debugger is ready. Send a single Page.reload after the debugger goes quiet following its initial breakpoint requests. Reloads are driven by the device (the nativePageReloads capability), so the debugger stays attached and the breakpoints re-bind. The request is sent out of band with an id the debugger never issued, so it discards the reply as an unknown response id. --- .../CDPMessageHandlers/CDPAPINames.ts | 1 + .../hermesCDPMessageHandler.ts | 66 ++++++++++++ .../cdp-proxy/hermesCDPMessageHandler.test.ts | 101 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 test/cdp-proxy/hermesCDPMessageHandler.test.ts diff --git a/src/cdp-proxy/CDPMessageHandlers/CDPAPINames.ts b/src/cdp-proxy/CDPMessageHandlers/CDPAPINames.ts index e005635f9..710553244 100644 --- a/src/cdp-proxy/CDPMessageHandlers/CDPAPINames.ts +++ b/src/cdp-proxy/CDPMessageHandlers/CDPAPINames.ts @@ -12,4 +12,5 @@ export const CDP_API_NAMES = { CLOSE: "close", DEBUGGER_SCRIPT_PARSED: "Debugger.scriptParsed", DEBUGGER_SET_BREAKPOINT_BY_URL: "Debugger.setBreakpointByUrl", + PAGE_RELOAD: "Page.reload", }; diff --git a/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts b/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts index 82314e4ec..02b5f748d 100644 --- a/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts +++ b/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts @@ -19,8 +19,34 @@ export class HermesCDPMessageHandler extends BaseCDPMessageHandler { */ private readonly HERMES_NATIVE_FUNCTION_SCRIPT_ID: string = "4294967295"; + /** + * @description Quiet period after the last breakpoint request before the application + * is reloaded, so that a burst of breakpoint requests causes a single reload + * @type {number} + */ + private readonly RELOAD_DEBOUNCE_MS: number = 300; + + /** + * @description Id of the out-of-band Page.reload request. The debugger never sent it, + * so it discards the reply as an unknown response id + * @type {number} + */ + private readonly RELOAD_REQUEST_ID: number = 2147483646; + + private isFirstRun: boolean = true; + private reloadTimer: NodeJS.Timeout | null = null; + public processDebuggerCDPMessage(event: any): ProcessedCDPMessage { let sendBack = false; + if (event.method === CDP_API_NAMES.CLOSE) { + this.cancelFirstRunReload(); + } else if ( + event.method === CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT || + event.method === CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL + ) { + this.scheduleFirstRunReload(); + } + if (event.method === CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT) { event = this.handleBreakpointSetting(event); } else if (event.method === CDP_API_NAMES.RUNTIME_CALL_FUNCTION_ON) { @@ -100,6 +126,46 @@ export class HermesCDPMessageHandler extends BaseCDPMessageHandler { return event; } + /** + * The application finishes executing its bundle before the debugger manages to attach, + * so breakpoints registered for the first run resolve but are never reached. Reload the + * application once the debugger has finished sending its initial breakpoints, so that the + * bundle is evaluated again while they are in place. Reloading is driven by the device + * (the "nativePageReloads" capability), so the debugger stays attached across it. + */ + private scheduleFirstRunReload(): void { + if (!this.isFirstRun) { + return; + } + + if (this.reloadTimer) { + clearTimeout(this.reloadTimer); + } + + this.reloadTimer = setTimeout(() => { + this.reloadTimer = null; + + if (!this.isFirstRun) { + return; + } + this.isFirstRun = false; + + this.applicationTarget?.send({ + id: this.RELOAD_REQUEST_ID, + method: CDP_API_NAMES.PAGE_RELOAD, + params: {}, + }); + }, this.RELOAD_DEBOUNCE_MS); + } + + private cancelFirstRunReload(): void { + if (this.reloadTimer) { + clearTimeout(this.reloadTimer); + this.reloadTimer = null; + } + this.isFirstRun = true; + } + private handleBreakpointSetting(event: any): any { if (event.params) { delete event.params.location.columnNumber; diff --git a/test/cdp-proxy/hermesCDPMessageHandler.test.ts b/test/cdp-proxy/hermesCDPMessageHandler.test.ts new file mode 100644 index 000000000..91a630af6 --- /dev/null +++ b/test/cdp-proxy/hermesCDPMessageHandler.test.ts @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +import { Connection } from "vscode-cdp-proxy"; +import { HermesCDPMessageHandler } from "../../src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler"; +import { CDP_API_NAMES } from "../../src/cdp-proxy/CDPMessageHandlers/CDPAPINames"; +import { PromiseUtil } from "../../src/common/node/promise"; +import assert = require("assert"); + +suite("hermesCDPMessageHandler", function () { + // Must outlast RELOAD_DEBOUNCE_MS (300) in the handler + const afterDebounce = 500; + + function createHandler(): { + handler: HermesCDPMessageHandler; + sent: Array>; + } { + const handler = new HermesCDPMessageHandler(); + const sent: Array> = []; + handler.setApplicationTarget({ + send: (message: Record) => sent.push(message), + } as unknown as Connection); + return { handler, sent }; + } + + suite("first run reload", function () { + test("reloads the application once the debugger has sent its breakpoints", async () => { + const { handler, sent } = createHandler(); + + handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL, + params: {}, + }); + await PromiseUtil.delay(afterDebounce); + + assert.strictEqual(sent.length, 1); + assert.strictEqual(sent[0].method, CDP_API_NAMES.PAGE_RELOAD); + }); + + test("coalesces a burst of breakpoints into a single reload", async () => { + const { handler, sent } = createHandler(); + + for (let i = 0; i < 5; i++) { + handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL, + params: {}, + }); + } + await PromiseUtil.delay(afterDebounce); + + assert.strictEqual(sent.length, 1); + }); + + test("does not reload again for later breakpoints in the same session", async () => { + const { handler, sent } = createHandler(); + + handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL, + params: {}, + }); + await PromiseUtil.delay(afterDebounce); + + handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL, + params: {}, + }); + await PromiseUtil.delay(afterDebounce); + + assert.strictEqual(sent.length, 1); + }); + + test("does not reload after the debugger disconnects", async () => { + const { handler, sent } = createHandler(); + + handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT_BY_URL, + params: {}, + }); + handler.processDebuggerCDPMessage({ method: CDP_API_NAMES.CLOSE }); + await PromiseUtil.delay(afterDebounce); + + assert.strictEqual(sent.length, 0); + }); + + test("leaves breakpoint messages on their way to the application", function () { + const { handler } = createHandler(); + + const processed = handler.processDebuggerCDPMessage({ + method: CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT, + params: { location: { lineNumber: 1, columnNumber: 2 } }, + }); + + const event = processed.event as Record; + + assert.strictEqual(processed.sendBack, false); + assert.strictEqual(event.method, CDP_API_NAMES.DEBUGGER_SET_BREAKPOINT); + // Hermes rejects breakpoints carrying a column number + assert.strictEqual(event.params.location.columnNumber, undefined); + }); + }); +}); From 7a27b7b06cf60254f958e2c3ad563c512a5c46a2 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:33:32 +0200 Subject: [PATCH 02/13] React Native 0.87 debugger fixes and local build tooling (#2) * Wait for the debug adapter server to bind before reading its port Net.Server.listen() is asynchronous, so address() could still return null when DebugAdapterServer was constructed, producing a debug session pointed at an invalid port. Resolve the descriptor from the listen callback instead, and surface bind failures through reject. * Set the expected Origin header when connecting to the Metro inspector React Native's @react-native/dev-middleware validates the Origin header on /inspector/debug WebSocket upgrades and closes connections whose origin is neither the dev server nor a loopback host. The proxy connected without one, so Metro answered 401 and logged: Connection from DevTools failed to be established for origin 'undefined' Build the application transport explicitly so the Origin header derived from the inspector URL is sent, and update the CDP proxy test for the new transport creation path. * Reload the application once so first-run breakpoints are hit A launch installs and starts the app, which finishes evaluating its bundle well before the CDP proxy manages to attach. Hermes replays scriptParsed on Debugger.enable, so breakpoints registered for that first run resolve and look bound, but the code they belong to has already executed and they are never reached. Reloading by hand makes them work, which is what users hit today. Hermes has no pause-on-load to wait for: it answers Runtime.runIfWaitingForDebugger with "Unsupported method" (-32601), so the only lever is to run the bundle again once the debugger is ready. Send a single Page.reload after the debugger goes quiet following its initial breakpoint requests. Reloads are driven by the device (the nativePageReloads capability), so the debugger stays attached and the breakpoints re-bind. The request is sent out of band with an id the debugger never issued, so it discards the reply as an unknown response id. * Resolve packages from the public npm registry The lockfile pinned 220 tarballs to internal Azure Artifacts feeds and to registry.npmmirror.com. npm refuses those as "remote" type packages (EALLOWREMOTE), so npm install fails outside Microsoft's network. Both hosts mirror the public registry at the same paths, so the integrity hashes are unchanged. Drop this commit before opening a pull request upstream. * Bump version to 1.14.2 for local builds Keeps the sideloaded build ahead of the published 1.14.1 so the marketplace does not replace it on auto-update. Drop this commit before opening a pull request upstream. * Add a VSIX build workflow and an install-local script Packages the extension on a v* tag and attaches the VSIX to that release, so it can be installed on a machine without the build toolchain. Also runnable on demand via workflow_dispatch, which just uploads the artifact. npm run install-local packages and installs into Codium in one step for the local edit loop. Drop this commit before opening a pull request upstream. --- .github/workflows/build-vsix.yml | 51 ++ package-lock.json | 524 ++++++++++----------- package.json | 3 +- src/cdp-proxy/reactNativeCDPProxy.ts | 39 +- src/extension/reactNativeSessionManager.ts | 17 +- test/cdp-proxy/reactNativeCDPProxy.test.ts | 9 +- 6 files changed, 368 insertions(+), 275 deletions(-) create mode 100644 .github/workflows/build-vsix.yml diff --git a/.github/workflows/build-vsix.yml b/.github/workflows/build-vsix.yml new file mode 100644 index 000000000..1dbad40dc --- /dev/null +++ b/.github/workflows/build-vsix.yml @@ -0,0 +1,51 @@ +name: Build VSIX + +# Packages the extension and, for a v* tag, attaches the VSIX to that release +# so it can be installed on a machine without the build toolchain: +# +# gh release download v1.14.2 --repo teoboetti/vscode-react-native +# codium --install-extension vscode-react-native-1.14.2.vsix + +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + package: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Package + run: npx vsce package + + - name: Attach the VSIX to the release + if: startsWith(github.ref, 'refs/tags/') + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + gh release upload "$GITHUB_REF_NAME" ./*.vsix --clobber + else + gh release create "$GITHUB_REF_NAME" ./*.vsix --generate-notes + fi + + - uses: actions/upload-artifact@v4 + with: + name: vsix + path: "*.vsix" + if-no-files-found: error diff --git a/package-lock.json b/package-lock.json index 3a2401a8e..be0a81c7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-react-native", - "version": "1.14.1", + "version": "1.14.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-react-native", - "version": "1.14.1", + "version": "1.14.2", "hasInstallScript": true, "license": "%reactNative.license%", "dependencies": { @@ -637,7 +637,7 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", "integrity": "sha1-iRG9crLDZApUNgngQAuMTS5+fLY=", "dev": true, "license": "MIT", @@ -656,7 +656,7 @@ }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true, "license": "Apache-2.0", @@ -669,7 +669,7 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha1-vM32Fbz3tujbgw7AuNIcmiXeWXs=", "dev": true, "license": "MIT", @@ -679,7 +679,7 @@ }, "node_modules/@eslint/eslintrc": { "version": "2.1.4", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha1-OIomnw8lwbatwxe1osVXFIlMcK0=", "dev": true, "license": "MIT", @@ -703,7 +703,7 @@ }, "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha1-MfEoGzgyYwQ0gxwxDAHMzajL4AY=", "dev": true, "license": "MIT", @@ -716,7 +716,7 @@ }, "node_modules/@eslint/js": { "version": "8.57.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint/js/-/js-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha1-3mM9s+wu9qPIni8ZA4Bj6KEi4sI=", "dev": true, "license": "MIT", @@ -747,7 +747,7 @@ }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "integrity": "sha1-+5B2JN8yVtBLmqLfUNeql+xkh0g=", "deprecated": "Use @eslint/config-array instead", "dev": true, @@ -763,7 +763,7 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha1-r1smkaIrRL6EewyoFkHF+2rQFyw=", "dev": true, "license": "Apache-2.0", @@ -777,7 +777,7 @@ }, "node_modules/@humanwhocodes/object-schema": { "version": "2.0.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha1-Siho111taWPkI7z5C3/RvjQ0CdM=", "deprecated": "Use @eslint/object-schema instead", "dev": true, @@ -998,7 +998,7 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha1-dhnC6yGyVIP20WdUi0z9WnSIw9U=", "dev": true, "license": "MIT", @@ -1012,7 +1012,7 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha1-W9Jir5Tp0lvR5xsF3u1Eh2oiLos=", "dev": true, "license": "MIT", @@ -1022,7 +1022,7 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha1-6Vc36LtnRt3t9pxVaVNJTxlv5po=", "dev": true, "license": "MIT", @@ -1584,7 +1584,7 @@ }, "node_modules/@types/vscode": { "version": "1.75.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/vscode/-/vscode-1.75.0.tgz", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", "integrity": "sha1-8Ykqcn25oOtJlwWKgEFwuMC6IY4=", "dev": true, "license": "MIT" @@ -1609,7 +1609,7 @@ }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", "integrity": "sha1-Uvnw5H1adXHEM25pv+6lgVCe8s8=", "dev": true, "license": "MIT", @@ -1638,7 +1638,7 @@ }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { "version": "7.0.6", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ignore/-/ignore-7.0.6.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", "integrity": "sha1-aleq70yQ3yesNZCHXSno8RmIyI4=", "dev": true, "license": "MIT", @@ -1648,7 +1648,7 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", "integrity": "sha1-AVgCLsmSfgr81YqMwq1X4B2JL1w=", "dev": true, "license": "MIT", @@ -1673,7 +1673,7 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", "integrity": "sha1-FVLbAHypIGocbHrPSeIQvReoxW8=", "dev": true, "license": "MIT", @@ -1695,7 +1695,7 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", "integrity": "sha1-TUwtoJVg0Q3X2UfLotKdFNJa8W0=", "dev": true, "license": "MIT", @@ -1713,7 +1713,7 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", "integrity": "sha1-9Fo+umuRMvtHFB7APOLydfHqmR0=", "dev": true, "license": "MIT", @@ -1730,7 +1730,7 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.67.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", "integrity": "sha1-lr7RBSdVWd87zwRJtzpkFNNcWc4=", "dev": true, "license": "MIT", @@ -1755,7 +1755,7 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/types/-/types-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", "integrity": "sha1-So0AzB+rpcFP6rxg+Ft6MmUvNLY=", "dev": true, "license": "MIT", @@ -1769,7 +1769,7 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", "integrity": "sha1-EWw6R8BhGcWgUOiFGGHWSX3WS8I=", "dev": true, "license": "MIT", @@ -1797,7 +1797,7 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "10.2.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/minimatch/-/minimatch-10.2.6.tgz", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", "integrity": "sha1-/ZVrvgt3JB6fFaxdzLHGOAYJaO8=", "dev": true, "license": "BlueOak-1.0.0", @@ -1813,7 +1813,7 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.8.5", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/semver/-/semver-7.8.5.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha1-ObZGA33VDBT7RR5+TKxY7YuGP2k=", "dev": true, "license": "ISC", @@ -1826,7 +1826,7 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", "integrity": "sha1-PkeKPWnTMKH8UMEnRswu4HMsz80=", "dev": true, "license": "MIT", @@ -1850,7 +1850,7 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", "integrity": "sha1-YB1Ar5rPgqKNoihvPtr8abupAX8=", "dev": true, "license": "MIT", @@ -1868,7 +1868,7 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha1-njyUiWl4JNLUzjqK0SYo+R6fWb4=", "dev": true, "license": "Apache-2.0", @@ -1882,7 +1882,7 @@ "node_modules/@typescript/old": { "name": "typescript", "version": "6.0.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha1-kCUdwAeRbpcnhsuU100VsYVXfSE=", "dev": true, "license": "Apache-2.0", @@ -1896,7 +1896,7 @@ }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", "integrity": "sha1-zcfOgdYPHgkDSWDd+x+4gNendrY=", "cpu": [ "ppc64" @@ -1913,7 +1913,7 @@ }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", "integrity": "sha1-pV/fz6WN9Y0n2yI3zealweNacjU=", "cpu": [ "arm64" @@ -1930,7 +1930,7 @@ }, "node_modules/@typescript/typescript-darwin-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", "integrity": "sha1-ONHJFygAqR1we+xk0qNwoBZjTbQ=", "cpu": [ "x64" @@ -1947,7 +1947,7 @@ }, "node_modules/@typescript/typescript-freebsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", "integrity": "sha1-8f+IEAMLNdK1vg22otxlBGDqlPo=", "cpu": [ "arm64" @@ -1964,7 +1964,7 @@ }, "node_modules/@typescript/typescript-freebsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", "integrity": "sha1-PYawPzU8WxupUWLrbONVM7/ClL0=", "cpu": [ "x64" @@ -1981,7 +1981,7 @@ }, "node_modules/@typescript/typescript-linux-arm": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", "integrity": "sha1-rZS0HhruKk3MaimMe2fEM0X94y4=", "cpu": [ "arm" @@ -1998,7 +1998,7 @@ }, "node_modules/@typescript/typescript-linux-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", "integrity": "sha1-2TNNltbaxv+F2pyGVYiUjek56R8=", "cpu": [ "arm64" @@ -2015,7 +2015,7 @@ }, "node_modules/@typescript/typescript-linux-loong64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", "integrity": "sha1-KWWu5PyHM2ATnYk9qv5jl6KROK0=", "cpu": [ "loong64" @@ -2032,7 +2032,7 @@ }, "node_modules/@typescript/typescript-linux-mips64el": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", "integrity": "sha1-Goh6MRvtOoM/gL/Uqe03wnGTbPA=", "cpu": [ "mips64el" @@ -2049,7 +2049,7 @@ }, "node_modules/@typescript/typescript-linux-ppc64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", "integrity": "sha1-i2PJsvRFs5PrTkPsIdoiXa3jV30=", "cpu": [ "ppc64" @@ -2066,7 +2066,7 @@ }, "node_modules/@typescript/typescript-linux-riscv64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", "integrity": "sha1-tuijXCibPql6kqQdRhqu7Q07NuE=", "cpu": [ "riscv64" @@ -2083,7 +2083,7 @@ }, "node_modules/@typescript/typescript-linux-s390x": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", "integrity": "sha1-Lvlmk75IYfbReWVCflsAnLvtGj4=", "cpu": [ "s390x" @@ -2100,7 +2100,7 @@ }, "node_modules/@typescript/typescript-linux-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", "integrity": "sha1-cyacsLq6UK6gygYERaa4jlg/HOI=", "cpu": [ "x64" @@ -2117,7 +2117,7 @@ }, "node_modules/@typescript/typescript-netbsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", "integrity": "sha1-OjZJ+X+vohC05uN5jBXgZgXIqQE=", "cpu": [ "arm64" @@ -2134,7 +2134,7 @@ }, "node_modules/@typescript/typescript-netbsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", "integrity": "sha1-R+xZSRpAxHDSgH3E0rglUo/Zeas=", "cpu": [ "x64" @@ -2151,7 +2151,7 @@ }, "node_modules/@typescript/typescript-openbsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", "integrity": "sha1-eWvo2gvZidij+5bygB44qDZbS68=", "cpu": [ "arm64" @@ -2168,7 +2168,7 @@ }, "node_modules/@typescript/typescript-openbsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", "integrity": "sha1-03/ipynrlCwHbEVO5/GBX699Vg8=", "cpu": [ "x64" @@ -2185,7 +2185,7 @@ }, "node_modules/@typescript/typescript-sunos-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", "integrity": "sha1-q6jTRkw1ZacER4m6upaRa9SrLIg=", "cpu": [ "x64" @@ -2202,7 +2202,7 @@ }, "node_modules/@typescript/typescript-win32-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", "integrity": "sha1-ud5QoXGWOD9iYgtfnQovNK07YNc=", "cpu": [ "arm64" @@ -2219,7 +2219,7 @@ }, "node_modules/@typescript/typescript-win32-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", "integrity": "sha1-zzt7DWzlY12spMjgHBic3N5H7Dw=", "cpu": [ "x64" @@ -2256,7 +2256,7 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", "integrity": "sha1-CUBB4aTLGYfwODNUISgayL45C8w=", "dev": true, "license": "ISC" @@ -2344,7 +2344,7 @@ }, "node_modules/@vscode/test-electron": { "version": "3.1.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@vscode/test-electron/-/test-electron-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.1.0.tgz", "integrity": "sha1-Rrk9EY3NOzyJyq4pahP5p/XrY8U=", "dev": true, "license": "MIT", @@ -3019,7 +3019,7 @@ }, "node_modules/acorn": { "version": "8.18.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/acorn/-/acorn-8.18.0.tgz", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha1-T68BstbTJr/u2XrqH1IiC19MGUA=", "dev": true, "license": "MIT", @@ -3032,7 +3032,7 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=", "dev": true, "license": "MIT", @@ -3077,7 +3077,7 @@ }, "node_modules/ajv": { "version": "6.15.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ajv/-/ajv-6.15.0.tgz", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "integrity": "sha1-B+mCx0YmFnqnoklcU4F4ktcTlJI=", "dev": true, "license": "MIT", @@ -3712,7 +3712,7 @@ }, "node_modules/brace-expansion": { "version": "5.0.9", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/brace-expansion/-/brace-expansion-5.0.9.tgz", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", "integrity": "sha1-fHJDiAm1+lur9UGZofHCgaaYT88=", "license": "MIT", "dependencies": { @@ -3817,7 +3817,7 @@ }, "node_modules/builtin-modules": { "version": "3.3.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/builtin-modules/-/builtin-modules-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha1-yuYoEriYAellYzbkYiPgMDhr57Y=", "dev": true, "license": "MIT", @@ -3908,7 +3908,7 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/callsites/-/callsites-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=", "dev": true, "license": "MIT", @@ -3939,7 +3939,7 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { @@ -4055,7 +4055,7 @@ }, "node_modules/ci-info": { "version": "3.9.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ci-info/-/ci-info-3.9.0.tgz", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha1-QnmmICinsfJi80c/yWBfXiGMWbQ=", "dev": true, "funding": [ @@ -4395,7 +4395,7 @@ }, "node_modules/dateformat": { "version": "4.6.3", - "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-4.6.3.tgz", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, "engines": { @@ -4430,7 +4430,7 @@ }, "node_modules/decode-uri-component": { "version": "0.5.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/decode-uri-component/-/decode-uri-component-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.5.0.tgz", "integrity": "sha1-RZL6Hh1kDsXidg4uForSq18snaE=", "license": "MIT", "engines": { @@ -4462,7 +4462,7 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/deep-is/-/deep-is-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha1-pvLc5hL63S7x9Rm3NVHxfoUZmDE=", "dev": true, "license": "MIT" @@ -5045,7 +5045,7 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, @@ -5060,7 +5060,7 @@ }, "node_modules/eslint": { "version": "8.57.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint/-/eslint-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha1-ffEJZUq6fju+XI6uUzxeRh08bKk=", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, @@ -5418,7 +5418,7 @@ }, "node_modules/eslint-plugin-unicorn": { "version": "48.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz", "integrity": "sha1-plc7wWh66NtxIf3Y+SOUtlSaaVk=", "dev": true, "license": "MIT", @@ -5532,7 +5532,7 @@ }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=", "dev": true, "license": "MIT", @@ -5545,7 +5545,7 @@ }, "node_modules/eslint/node_modules/eslint-scope": { "version": "7.2.2", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-scope/-/eslint-scope-7.2.2.tgz", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha1-3rT5JWM5DzIAaJSvYqItuhxGQj8=", "dev": true, "license": "BSD-2-Clause", @@ -5562,7 +5562,7 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true, "license": "Apache-2.0", @@ -5575,7 +5575,7 @@ }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/estraverse/-/estraverse-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha1-LupSkHAvJquP5TcDcP+GyWXSESM=", "dev": true, "license": "BSD-2-Clause", @@ -5585,7 +5585,7 @@ }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/find-up/-/find-up-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha1-TJKBnstwg1YeT0okCoa+UZj1Nvw=", "dev": true, "license": "MIT", @@ -5602,7 +5602,7 @@ }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/glob-parent/-/glob-parent-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha1-bSN9mQg5UMeSkPJMdkKj3poo+eM=", "dev": true, "license": "ISC", @@ -5615,7 +5615,7 @@ }, "node_modules/eslint/node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-glob/-/is-glob-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", "dev": true, "license": "MIT", @@ -5628,7 +5628,7 @@ }, "node_modules/eslint/node_modules/is-path-inside": { "version": "3.0.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-path-inside/-/is-path-inside-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha1-0jE2LlOgf/Kw4Op/7QSRYf/RYoM=", "dev": true, "license": "MIT", @@ -5638,7 +5638,7 @@ }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/locate-path/-/locate-path-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha1-VTIeswn+u8WcSAHZMackUqaB0oY=", "dev": true, "license": "MIT", @@ -5654,7 +5654,7 @@ }, "node_modules/eslint/node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/p-limit/-/p-limit-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs=", "dev": true, "license": "MIT", @@ -5670,7 +5670,7 @@ }, "node_modules/eslint/node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/p-locate/-/p-locate-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha1-g8gxXGeFAF470CGDlBHJ4RDm2DQ=", "dev": true, "license": "MIT", @@ -5686,7 +5686,7 @@ }, "node_modules/espree": { "version": "9.6.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/espree/-/espree-9.6.1.tgz", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha1-oqF7jkNGkKVDLy+AGM5x0zGkjG8=", "dev": true, "license": "BSD-2-Clause", @@ -5704,7 +5704,7 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true, "license": "Apache-2.0", @@ -5717,7 +5717,7 @@ }, "node_modules/esquery": { "version": "1.7.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/esquery/-/esquery-1.7.0.tgz", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha1-CNBI8mHw3e21uulfRoCUY9nJSW0=", "dev": true, "license": "BSD-3-Clause", @@ -5962,21 +5962,21 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-uri/-/fast-uri-3.1.6.tgz", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", "integrity": "sha1-v90wjvdj+DX+0Fnzxskl9wjjPjo=", "dev": true, "funding": [ @@ -6298,7 +6298,7 @@ }, "node_modules/fs-extra": { "version": "10.1.0", - "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "dependencies": { @@ -6333,7 +6333,7 @@ }, "node_modules/fsu": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/fsu/-/fsu-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", "dev": true }, @@ -6700,7 +6700,7 @@ }, "node_modules/globals": { "version": "13.24.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/globals/-/globals-13.24.0.tgz", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha1-hDKhnXjODB6DOUnDats0VAC7EXE=", "dev": true, "license": "MIT", @@ -6716,7 +6716,7 @@ }, "node_modules/globals/node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/type-fest/-/type-fest-0.20.2.tgz", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ=", "dev": true, "license": "(MIT OR CC0-1.0)", @@ -6758,7 +6758,7 @@ }, "node_modules/graphemer": { "version": "1.4.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/graphemer/-/graphemer-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha1-+y8dVeDjoYSa7/yQxPoN1ToOZsY=", "dev": true, "license": "MIT" @@ -7652,7 +7652,7 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/import-fresh/-/import-fresh-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha1-nOy1ZQPAraHydB271lRuSxO1fM8=", "dev": true, "license": "MIT", @@ -7745,7 +7745,7 @@ }, "node_modules/ip-address": { "version": "10.3.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ip-address/-/ip-address-10.3.1.tgz", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", "integrity": "sha1-kp+WKdFyT34bdIXOiXUvNnUzahA=", "license": "MIT", "engines": { @@ -7830,7 +7830,7 @@ }, "node_modules/is-builtin-module": { "version": "3.2.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "integrity": "sha1-8DJxcX2GVM/K8HqwRj+qNXFYEWk=", "dev": true, "license": "MIT", @@ -8437,7 +8437,7 @@ }, "node_modules/js-yaml": { "version": "4.3.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/js-yaml/-/js-yaml-4.3.2.tgz", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", "integrity": "sha1-jkT7FKJkPFlya7FXh7XxUSyz0/s=", "dev": true, "funding": [ @@ -8479,7 +8479,7 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true, "license": "MIT" @@ -8519,7 +8519,7 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, @@ -8541,7 +8541,7 @@ }, "node_modules/jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "dependencies": { @@ -8676,7 +8676,7 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/levn/-/levn-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha1-rkViwAdHO5MqYgDUAyaN0v/8at4=", "dev": true, "license": "MIT", @@ -8800,13 +8800,13 @@ }, "node_modules/lodash.isempty": { "version": "4.4.0", - "resolved": "https://registry.npmmirror.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", "dev": true }, "node_modules/lodash.isfunction": { "version": "3.0.9", - "resolved": "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", "dev": true }, @@ -8824,7 +8824,7 @@ }, "node_modules/lodash.isobject": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", "dev": true }, @@ -8836,13 +8836,13 @@ }, "node_modules/lodash.isstring": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/lodash.merge/-/lodash.merge-4.6.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=", "dev": true, "license": "MIT" @@ -9090,7 +9090,7 @@ }, "node_modules/min-indent": { "version": "1.0.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/min-indent/-/min-indent-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha1-pj9oFnOzBXH76LwlaGrnRu76mGk=", "dev": true, "license": "MIT", @@ -9431,7 +9431,7 @@ }, "node_modules/mochawesome": { "version": "7.1.3", - "resolved": "https://registry.npmmirror.com/mochawesome/-/mochawesome-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", "dev": true, "dependencies": { @@ -9452,7 +9452,7 @@ }, "node_modules/mochawesome-report-generator": { "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", "integrity": "sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg==", "dev": true, "dependencies": { @@ -9475,7 +9475,7 @@ }, "node_modules/mochawesome-report-generator/node_modules/yargs": { "version": "17.5.1", - "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.5.1.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "dependencies": { @@ -9493,7 +9493,7 @@ }, "node_modules/mochawesome-report-generator/node_modules/yargs-parser": { "version": "21.0.1", - "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.0.1.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", "dev": true, "engines": { @@ -10130,7 +10130,7 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/optionator/-/optionator-0.9.4.tgz", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha1-fqHBpdkddk+yghOciP4R4YKjpzQ=", "dev": true, "license": "MIT", @@ -10247,7 +10247,7 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/parent-module/-/parent-module-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "license": "MIT", @@ -10690,7 +10690,7 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/prelude-ls/-/prelude-ls-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha1-3rxkidem5rDnYRiIzsiAM30xY5Y=", "dev": true, "license": "MIT", @@ -10750,7 +10750,7 @@ }, "node_modules/prop-types": { "version": "15.8.1", - "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "dependencies": { @@ -10848,7 +10848,7 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/queue-microtask/-/queue-microtask-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha1-SSkii7xyTfrEPg77BYyve2z7YkM=", "dev": true, "funding": [ @@ -10915,7 +10915,7 @@ }, "node_modules/react-is": { "version": "16.13.1", - "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, @@ -11047,7 +11047,7 @@ }, "node_modules/regexp-tree": { "version": "0.1.27", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/regexp-tree/-/regexp-tree-0.1.27.tgz", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", "integrity": "sha1-IZjw71RRj/p0P+dNmDtW/9Yxts0=", "dev": true, "license": "MIT", @@ -11057,7 +11057,7 @@ }, "node_modules/regjsparser": { "version": "0.10.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/regjsparser/-/regjsparser-0.10.0.tgz", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", "integrity": "sha1-se0mBRc2tDbyL97ByPcmNfn0SJI=", "dev": true, "license": "BSD-2-Clause", @@ -11070,7 +11070,7 @@ }, "node_modules/regjsparser/node_modules/jsesc": { "version": "0.5.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true, "bin": { @@ -11156,7 +11156,7 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=", "dev": true, "license": "MIT", @@ -11291,7 +11291,7 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/run-parallel/-/run-parallel-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha1-ZtE2jae9+SHrnZW9GpIp5/IaQ+4=", "dev": true, "funding": [ @@ -11573,7 +11573,7 @@ }, "node_modules/shell-quote": { "version": "1.10.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/shell-quote/-/shell-quote-1.10.0.tgz", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", "integrity": "sha1-SCAz4ZLk9cBxUVIf+gNADscbGw8=", "dev": true, "license": "MIT", @@ -11981,7 +11981,7 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", @@ -12028,7 +12028,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" @@ -12059,7 +12059,7 @@ }, "node_modules/strip-indent": { "version": "3.0.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/strip-indent/-/strip-indent-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha1-wy4c7pQLazQyx3G8LFS8znPNMAE=", "dev": true, "license": "MIT", @@ -12236,13 +12236,13 @@ }, "node_modules/tcomb": { "version": "3.2.29", - "resolved": "https://registry.npmmirror.com/tcomb/-/tcomb-3.2.29.tgz", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", "dev": true }, "node_modules/tcomb-validation": { "version": "3.4.1", - "resolved": "https://registry.npmmirror.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", "dev": true, "dependencies": { @@ -12484,7 +12484,7 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/tinyglobby/-/tinyglobby-0.2.17.tgz", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha1-ViqabJ6ys7Ej05cZ+a9btE/NdjE=", "dev": true, "license": "MIT", @@ -12501,7 +12501,7 @@ }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fdir/-/fdir-6.5.0.tgz", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha1-7Sq5Z6MxreYvGNB32uGSaE1Q01A=", "dev": true, "license": "MIT", @@ -12519,7 +12519,7 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.5", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/picomatch/-/picomatch-4.0.5.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha1-UepXoX2G9gX4EDlZX7xA7QalX6s=", "dev": true, "license": "MIT", @@ -12587,7 +12587,7 @@ }, "node_modules/ts-api-utils": { "version": "2.5.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "integrity": "sha1-Ss1KFV4ic0mQpe0f6el/ETvLN8E=", "dev": true, "license": "MIT", @@ -12649,7 +12649,7 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/type-check/-/type-check-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha1-B7ggO/pwVsBlcFDjzNLDdzC6uPE=", "dev": true, "license": "MIT", @@ -12700,7 +12700,7 @@ "node_modules/typescript": { "name": "@typescript/typescript6", "version": "6.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript6/-/typescript6-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", "integrity": "sha1-eeDkdJJWTIP4YCAT190Q1evFGow=", "dev": true, "license": "Apache-2.0", @@ -12714,7 +12714,7 @@ "node_modules/typescript-7": { "name": "typescript", "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "integrity": "sha1-nsdz15VKjBgsF8xbvVdaoovFFYI=", "dev": true, "license": "Apache-2.0", @@ -12866,7 +12866,7 @@ }, "node_modules/universalify": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, "engines": { @@ -13457,7 +13457,7 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/word-wrap/-/word-wrap-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha1-0sRcbdT7zmIaZvE2y+Mor9BBCzQ=", "dev": true, "license": "MIT", @@ -14153,7 +14153,7 @@ }, "@eslint-community/eslint-utils": { "version": "4.10.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", "integrity": "sha1-iRG9crLDZApUNgngQAuMTS5+fLY=", "dev": true, "requires": { @@ -14162,7 +14162,7 @@ "dependencies": { "eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true } @@ -14170,13 +14170,13 @@ }, "@eslint-community/regexpp": { "version": "4.12.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha1-vM32Fbz3tujbgw7AuNIcmiXeWXs=", "dev": true }, "@eslint/eslintrc": { "version": "2.1.4", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha1-OIomnw8lwbatwxe1osVXFIlMcK0=", "dev": true, "requires": { @@ -14193,7 +14193,7 @@ "dependencies": { "strip-json-comments": { "version": "3.1.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha1-MfEoGzgyYwQ0gxwxDAHMzajL4AY=", "dev": true } @@ -14201,7 +14201,7 @@ }, "@eslint/js": { "version": "8.57.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@eslint/js/-/js-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha1-3mM9s+wu9qPIni8ZA4Bj6KEi4sI=", "dev": true }, @@ -14222,7 +14222,7 @@ }, "@humanwhocodes/config-array": { "version": "0.13.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "integrity": "sha1-+5B2JN8yVtBLmqLfUNeql+xkh0g=", "dev": true, "requires": { @@ -14233,13 +14233,13 @@ }, "@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha1-r1smkaIrRL6EewyoFkHF+2rQFyw=", "dev": true }, "@humanwhocodes/object-schema": { "version": "2.0.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha1-Siho111taWPkI7z5C3/RvjQ0CdM=", "dev": true }, @@ -14403,7 +14403,7 @@ }, "@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha1-dhnC6yGyVIP20WdUi0z9WnSIw9U=", "dev": true, "requires": { @@ -14413,13 +14413,13 @@ }, "@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha1-W9Jir5Tp0lvR5xsF3u1Eh2oiLos=", "dev": true }, "@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha1-6Vc36LtnRt3t9pxVaVNJTxlv5po=", "dev": true, "requires": { @@ -14923,7 +14923,7 @@ }, "@types/vscode": { "version": "1.75.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/vscode/-/vscode-1.75.0.tgz", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", "integrity": "sha1-8Ykqcn25oOtJlwWKgEFwuMC6IY4=", "dev": true }, @@ -14947,7 +14947,7 @@ }, "@typescript-eslint/eslint-plugin": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", "integrity": "sha1-Uvnw5H1adXHEM25pv+6lgVCe8s8=", "dev": true, "requires": { @@ -14963,7 +14963,7 @@ "dependencies": { "ignore": { "version": "7.0.6", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ignore/-/ignore-7.0.6.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", "integrity": "sha1-aleq70yQ3yesNZCHXSno8RmIyI4=", "dev": true } @@ -14971,7 +14971,7 @@ }, "@typescript-eslint/parser": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", "integrity": "sha1-AVgCLsmSfgr81YqMwq1X4B2JL1w=", "dev": true, "requires": { @@ -14984,7 +14984,7 @@ }, "@typescript-eslint/project-service": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", "integrity": "sha1-FVLbAHypIGocbHrPSeIQvReoxW8=", "dev": true, "requires": { @@ -14995,7 +14995,7 @@ }, "@typescript-eslint/scope-manager": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", "integrity": "sha1-TUwtoJVg0Q3X2UfLotKdFNJa8W0=", "dev": true, "requires": { @@ -15005,14 +15005,14 @@ }, "@typescript-eslint/tsconfig-utils": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", "integrity": "sha1-9Fo+umuRMvtHFB7APOLydfHqmR0=", "dev": true, "requires": {} }, "@typescript-eslint/type-utils": { "version": "8.67.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", "integrity": "sha1-lr7RBSdVWd87zwRJtzpkFNNcWc4=", "dev": true, "requires": { @@ -15025,13 +15025,13 @@ }, "@typescript-eslint/types": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/types/-/types-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", "integrity": "sha1-So0AzB+rpcFP6rxg+Ft6MmUvNLY=", "dev": true }, "@typescript-eslint/typescript-estree": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", "integrity": "sha1-EWw6R8BhGcWgUOiFGGHWSX3WS8I=", "dev": true, "requires": { @@ -15048,7 +15048,7 @@ "dependencies": { "minimatch": { "version": "10.2.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/minimatch/-/minimatch-10.2.6.tgz", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", "integrity": "sha1-/ZVrvgt3JB6fFaxdzLHGOAYJaO8=", "dev": true, "requires": { @@ -15057,7 +15057,7 @@ }, "semver": { "version": "7.8.5", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/semver/-/semver-7.8.5.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha1-ObZGA33VDBT7RR5+TKxY7YuGP2k=", "dev": true } @@ -15065,7 +15065,7 @@ }, "@typescript-eslint/utils": { "version": "8.67.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", "integrity": "sha1-PkeKPWnTMKH8UMEnRswu4HMsz80=", "dev": true, "requires": { @@ -15077,7 +15077,7 @@ }, "@typescript-eslint/visitor-keys": { "version": "8.67.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", "integrity": "sha1-YB1Ar5rPgqKNoihvPtr8abupAX8=", "dev": true, "requires": { @@ -15087,7 +15087,7 @@ "dependencies": { "eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha1-njyUiWl4JNLUzjqK0SYo+R6fWb4=", "dev": true } @@ -15095,146 +15095,146 @@ }, "@typescript/old": { "version": "npm:typescript@6.0.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha1-kCUdwAeRbpcnhsuU100VsYVXfSE=", "dev": true }, "@typescript/typescript-aix-ppc64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", "integrity": "sha1-zcfOgdYPHgkDSWDd+x+4gNendrY=", "dev": true, "optional": true }, "@typescript/typescript-darwin-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", "integrity": "sha1-pV/fz6WN9Y0n2yI3zealweNacjU=", "dev": true, "optional": true }, "@typescript/typescript-darwin-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", "integrity": "sha1-ONHJFygAqR1we+xk0qNwoBZjTbQ=", "dev": true, "optional": true }, "@typescript/typescript-freebsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", "integrity": "sha1-8f+IEAMLNdK1vg22otxlBGDqlPo=", "dev": true, "optional": true }, "@typescript/typescript-freebsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", "integrity": "sha1-PYawPzU8WxupUWLrbONVM7/ClL0=", "dev": true, "optional": true }, "@typescript/typescript-linux-arm": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", "integrity": "sha1-rZS0HhruKk3MaimMe2fEM0X94y4=", "dev": true, "optional": true }, "@typescript/typescript-linux-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", "integrity": "sha1-2TNNltbaxv+F2pyGVYiUjek56R8=", "dev": true, "optional": true }, "@typescript/typescript-linux-loong64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", "integrity": "sha1-KWWu5PyHM2ATnYk9qv5jl6KROK0=", "dev": true, "optional": true }, "@typescript/typescript-linux-mips64el": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", "integrity": "sha1-Goh6MRvtOoM/gL/Uqe03wnGTbPA=", "dev": true, "optional": true }, "@typescript/typescript-linux-ppc64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", "integrity": "sha1-i2PJsvRFs5PrTkPsIdoiXa3jV30=", "dev": true, "optional": true }, "@typescript/typescript-linux-riscv64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", "integrity": "sha1-tuijXCibPql6kqQdRhqu7Q07NuE=", "dev": true, "optional": true }, "@typescript/typescript-linux-s390x": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", "integrity": "sha1-Lvlmk75IYfbReWVCflsAnLvtGj4=", "dev": true, "optional": true }, "@typescript/typescript-linux-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", "integrity": "sha1-cyacsLq6UK6gygYERaa4jlg/HOI=", "dev": true, "optional": true }, "@typescript/typescript-netbsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", "integrity": "sha1-OjZJ+X+vohC05uN5jBXgZgXIqQE=", "dev": true, "optional": true }, "@typescript/typescript-netbsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", "integrity": "sha1-R+xZSRpAxHDSgH3E0rglUo/Zeas=", "dev": true, "optional": true }, "@typescript/typescript-openbsd-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", "integrity": "sha1-eWvo2gvZidij+5bygB44qDZbS68=", "dev": true, "optional": true }, "@typescript/typescript-openbsd-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", "integrity": "sha1-03/ipynrlCwHbEVO5/GBX699Vg8=", "dev": true, "optional": true }, "@typescript/typescript-sunos-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", "integrity": "sha1-q6jTRkw1ZacER4m6upaRa9SrLIg=", "dev": true, "optional": true }, "@typescript/typescript-win32-arm64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", "integrity": "sha1-ud5QoXGWOD9iYgtfnQovNK07YNc=", "dev": true, "optional": true }, "@typescript/typescript-win32-x64": { "version": "7.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", "integrity": "sha1-zzt7DWzlY12spMjgHBic3N5H7Dw=", "dev": true, "optional": true @@ -15260,7 +15260,7 @@ }, "@ungap/structured-clone": { "version": "1.3.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", "integrity": "sha1-CUBB4aTLGYfwODNUISgayL45C8w=", "dev": true }, @@ -15329,7 +15329,7 @@ }, "@vscode/test-electron": { "version": "3.1.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@vscode/test-electron/-/test-electron-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.1.0.tgz", "integrity": "sha1-Rrk9EY3NOzyJyq4pahP5p/XrY8U=", "dev": true, "requires": { @@ -15824,13 +15824,13 @@ }, "acorn": { "version": "8.18.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/acorn/-/acorn-8.18.0.tgz", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha1-T68BstbTJr/u2XrqH1IiC19MGUA=", "dev": true }, "acorn-jsx": { "version": "5.3.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=", "dev": true, "requires": {} @@ -15862,7 +15862,7 @@ }, "ajv": { "version": "6.15.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ajv/-/ajv-6.15.0.tgz", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "integrity": "sha1-B+mCx0YmFnqnoklcU4F4ktcTlJI=", "dev": true, "requires": { @@ -16353,7 +16353,7 @@ }, "brace-expansion": { "version": "5.0.9", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/brace-expansion/-/brace-expansion-5.0.9.tgz", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", "integrity": "sha1-fHJDiAm1+lur9UGZofHCgaaYT88=", "requires": { "balanced-match": "^4.0.2" @@ -16424,7 +16424,7 @@ }, "builtin-modules": { "version": "3.3.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/builtin-modules/-/builtin-modules-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha1-yuYoEriYAellYzbkYiPgMDhr57Y=", "dev": true }, @@ -16483,7 +16483,7 @@ }, "callsites": { "version": "3.1.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/callsites/-/callsites-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=", "dev": true }, @@ -16495,7 +16495,7 @@ }, "chalk": { "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { @@ -16589,7 +16589,7 @@ }, "ci-info": { "version": "3.9.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ci-info/-/ci-info-3.9.0.tgz", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha1-QnmmICinsfJi80c/yWBfXiGMWbQ=", "dev": true }, @@ -16844,7 +16844,7 @@ }, "dateformat": { "version": "4.6.3", - "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-4.6.3.tgz", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true }, @@ -16865,7 +16865,7 @@ }, "decode-uri-component": { "version": "0.5.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/decode-uri-component/-/decode-uri-component-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.5.0.tgz", "integrity": "sha1-RZL6Hh1kDsXidg4uForSq18snaE=" }, "decompress-response": { @@ -16887,7 +16887,7 @@ }, "deep-is": { "version": "0.1.4", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/deep-is/-/deep-is-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha1-pvLc5hL63S7x9Rm3NVHxfoUZmDE=", "dev": true }, @@ -17343,7 +17343,7 @@ }, "escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, @@ -17355,7 +17355,7 @@ }, "eslint": { "version": "8.57.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint/-/eslint-8.57.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha1-ffEJZUq6fju+XI6uUzxeRh08bKk=", "dev": true, "requires": { @@ -17401,13 +17401,13 @@ "dependencies": { "escape-string-regexp": { "version": "4.0.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=", "dev": true }, "eslint-scope": { "version": "7.2.2", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-scope/-/eslint-scope-7.2.2.tgz", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha1-3rT5JWM5DzIAaJSvYqItuhxGQj8=", "dev": true, "requires": { @@ -17417,19 +17417,19 @@ }, "eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true }, "estraverse": { "version": "5.3.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/estraverse/-/estraverse-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha1-LupSkHAvJquP5TcDcP+GyWXSESM=", "dev": true }, "find-up": { "version": "5.0.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/find-up/-/find-up-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha1-TJKBnstwg1YeT0okCoa+UZj1Nvw=", "dev": true, "requires": { @@ -17439,7 +17439,7 @@ }, "glob-parent": { "version": "6.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/glob-parent/-/glob-parent-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha1-bSN9mQg5UMeSkPJMdkKj3poo+eM=", "dev": true, "requires": { @@ -17448,7 +17448,7 @@ }, "is-glob": { "version": "4.0.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-glob/-/is-glob-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", "dev": true, "requires": { @@ -17457,13 +17457,13 @@ }, "is-path-inside": { "version": "3.0.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-path-inside/-/is-path-inside-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha1-0jE2LlOgf/Kw4Op/7QSRYf/RYoM=", "dev": true }, "locate-path": { "version": "6.0.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/locate-path/-/locate-path-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha1-VTIeswn+u8WcSAHZMackUqaB0oY=", "dev": true, "requires": { @@ -17472,7 +17472,7 @@ }, "p-limit": { "version": "3.1.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/p-limit/-/p-limit-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs=", "dev": true, "requires": { @@ -17481,7 +17481,7 @@ }, "p-locate": { "version": "5.0.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/p-locate/-/p-locate-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha1-g8gxXGeFAF470CGDlBHJ4RDm2DQ=", "dev": true, "requires": { @@ -17725,7 +17725,7 @@ }, "eslint-plugin-unicorn": { "version": "48.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz", "integrity": "sha1-plc7wWh66NtxIf3Y+SOUtlSaaVk=", "dev": true, "requires": { @@ -17812,7 +17812,7 @@ }, "espree": { "version": "9.6.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/espree/-/espree-9.6.1.tgz", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha1-oqF7jkNGkKVDLy+AGM5x0zGkjG8=", "dev": true, "requires": { @@ -17823,7 +17823,7 @@ "dependencies": { "eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha1-DNcv6FUOPC6uFWqWpN3c0cisWAA=", "dev": true } @@ -17831,7 +17831,7 @@ }, "esquery": { "version": "1.7.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/esquery/-/esquery-1.7.0.tgz", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha1-CNBI8mHw3e21uulfRoCUY9nJSW0=", "dev": true, "requires": { @@ -18028,19 +18028,19 @@ }, "fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=", "dev": true }, "fast-levenshtein": { "version": "2.0.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "fast-uri": { "version": "3.1.6", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fast-uri/-/fast-uri-3.1.6.tgz", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", "integrity": "sha1-v90wjvdj+DX+0Fnzxskl9wjjPjo=", "dev": true }, @@ -18301,7 +18301,7 @@ }, "fs-extra": { "version": "10.1.0", - "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { @@ -18325,7 +18325,7 @@ }, "fsu": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/fsu/-/fsu-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", "dev": true }, @@ -18601,7 +18601,7 @@ }, "globals": { "version": "13.24.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/globals/-/globals-13.24.0.tgz", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha1-hDKhnXjODB6DOUnDats0VAC7EXE=", "dev": true, "requires": { @@ -18610,7 +18610,7 @@ "dependencies": { "type-fest": { "version": "0.20.2", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/type-fest/-/type-fest-0.20.2.tgz", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ=", "dev": true } @@ -18638,7 +18638,7 @@ }, "graphemer": { "version": "1.4.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/graphemer/-/graphemer-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha1-+y8dVeDjoYSa7/yQxPoN1ToOZsY=", "dev": true }, @@ -19258,7 +19258,7 @@ }, "import-fresh": { "version": "3.3.1", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/import-fresh/-/import-fresh-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha1-nOy1ZQPAraHydB271lRuSxO1fM8=", "dev": true, "requires": { @@ -19324,7 +19324,7 @@ }, "ip-address": { "version": "10.3.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ip-address/-/ip-address-10.3.1.tgz", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", "integrity": "sha1-kp+WKdFyT34bdIXOiXUvNnUzahA=" }, "is": { @@ -19390,7 +19390,7 @@ }, "is-builtin-module": { "version": "3.2.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "integrity": "sha1-8DJxcX2GVM/K8HqwRj+qNXFYEWk=", "dev": true, "requires": { @@ -19825,7 +19825,7 @@ }, "js-yaml": { "version": "4.3.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/js-yaml/-/js-yaml-4.3.2.tgz", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", "integrity": "sha1-jkT7FKJkPFlya7FXh7XxUSyz0/s=", "dev": true, "requires": { @@ -19846,7 +19846,7 @@ }, "json-schema-traverse": { "version": "0.4.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, @@ -19879,7 +19879,7 @@ }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, @@ -19898,7 +19898,7 @@ }, "jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { @@ -20013,7 +20013,7 @@ }, "levn": { "version": "0.4.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/levn/-/levn-0.4.1.tgz", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha1-rkViwAdHO5MqYgDUAyaN0v/8at4=", "dev": true, "requires": { @@ -20109,13 +20109,13 @@ }, "lodash.isempty": { "version": "4.4.0", - "resolved": "https://registry.npmmirror.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", "dev": true }, "lodash.isfunction": { "version": "3.0.9", - "resolved": "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", "dev": true }, @@ -20133,7 +20133,7 @@ }, "lodash.isobject": { "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", "dev": true }, @@ -20145,13 +20145,13 @@ }, "lodash.isstring": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true }, "lodash.merge": { "version": "4.6.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/lodash.merge/-/lodash.merge-4.6.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=", "dev": true }, @@ -20333,7 +20333,7 @@ }, "min-indent": { "version": "1.0.1", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/min-indent/-/min-indent-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha1-pj9oFnOzBXH76LwlaGrnRu76mGk=", "dev": true }, @@ -20582,7 +20582,7 @@ }, "mochawesome": { "version": "7.1.3", - "resolved": "https://registry.npmmirror.com/mochawesome/-/mochawesome-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", "dev": true, "requires": { @@ -20608,7 +20608,7 @@ }, "mochawesome-report-generator": { "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", "integrity": "sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg==", "dev": true, "requires": { @@ -20628,7 +20628,7 @@ "dependencies": { "yargs": { "version": "17.5.1", - "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.5.1.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "requires": { @@ -20643,7 +20643,7 @@ }, "yargs-parser": { "version": "21.0.1", - "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.0.1.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", "dev": true } @@ -21150,7 +21150,7 @@ }, "optionator": { "version": "0.9.4", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/optionator/-/optionator-0.9.4.tgz", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha1-fqHBpdkddk+yghOciP4R4YKjpzQ=", "dev": true, "requires": { @@ -21242,7 +21242,7 @@ }, "parent-module": { "version": "1.0.1", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/parent-module/-/parent-module-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "requires": { @@ -21573,7 +21573,7 @@ }, "prelude-ls": { "version": "1.2.1", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/prelude-ls/-/prelude-ls-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha1-3rxkidem5rDnYRiIzsiAM30xY5Y=", "dev": true }, @@ -21617,7 +21617,7 @@ }, "prop-types": { "version": "15.8.1", - "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "requires": { @@ -21696,7 +21696,7 @@ }, "queue-microtask": { "version": "1.2.3", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/queue-microtask/-/queue-microtask-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha1-SSkii7xyTfrEPg77BYyve2z7YkM=", "dev": true }, @@ -21741,7 +21741,7 @@ }, "react-is": { "version": "16.13.1", - "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, @@ -21842,13 +21842,13 @@ }, "regexp-tree": { "version": "0.1.27", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/regexp-tree/-/regexp-tree-0.1.27.tgz", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", "integrity": "sha1-IZjw71RRj/p0P+dNmDtW/9Yxts0=", "dev": true }, "regjsparser": { "version": "0.10.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/regjsparser/-/regjsparser-0.10.0.tgz", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", "integrity": "sha1-se0mBRc2tDbyL97ByPcmNfn0SJI=", "dev": true, "requires": { @@ -21857,7 +21857,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } @@ -21924,7 +21924,7 @@ }, "resolve-from": { "version": "4.0.0", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=", "dev": true }, @@ -22032,7 +22032,7 @@ }, "run-parallel": { "version": "1.2.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/run-parallel/-/run-parallel-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha1-ZtE2jae9+SHrnZW9GpIp5/IaQ+4=", "dev": true, "requires": { @@ -22228,7 +22228,7 @@ }, "shell-quote": { "version": "1.10.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/shell-quote/-/shell-quote-1.10.0.tgz", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", "integrity": "sha1-SCAz4ZLk9cBxUVIf+gNADscbGw8=", "dev": true }, @@ -22565,7 +22565,7 @@ }, "string-width": { "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { "emoji-regex": "^8.0.0", @@ -22605,7 +22605,7 @@ }, "strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { "ansi-regex": "^5.0.1" @@ -22626,7 +22626,7 @@ }, "strip-indent": { "version": "3.0.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/strip-indent/-/strip-indent-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha1-wy4c7pQLazQyx3G8LFS8znPNMAE=", "dev": true, "requires": { @@ -22777,13 +22777,13 @@ }, "tcomb": { "version": "3.2.29", - "resolved": "https://registry.npmmirror.com/tcomb/-/tcomb-3.2.29.tgz", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", "dev": true }, "tcomb-validation": { "version": "3.4.1", - "resolved": "https://registry.npmmirror.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", "dev": true, "requires": { @@ -22970,7 +22970,7 @@ }, "tinyglobby": { "version": "0.2.17", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/tinyglobby/-/tinyglobby-0.2.17.tgz", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha1-ViqabJ6ys7Ej05cZ+a9btE/NdjE=", "dev": true, "requires": { @@ -22980,14 +22980,14 @@ "dependencies": { "fdir": { "version": "6.5.0", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fdir/-/fdir-6.5.0.tgz", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha1-7Sq5Z6MxreYvGNB32uGSaE1Q01A=", "dev": true, "requires": {} }, "picomatch": { "version": "4.0.5", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/picomatch/-/picomatch-4.0.5.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha1-UepXoX2G9gX4EDlZX7xA7QalX6s=", "dev": true } @@ -23038,7 +23038,7 @@ }, "ts-api-utils": { "version": "2.5.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "integrity": "sha1-Ss1KFV4ic0mQpe0f6el/ETvLN8E=", "dev": true, "requires": {} @@ -23087,7 +23087,7 @@ }, "type-check": { "version": "0.4.0", - "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/type-check/-/type-check-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha1-B7ggO/pwVsBlcFDjzNLDdzC6uPE=", "dev": true, "requires": { @@ -23126,7 +23126,7 @@ }, "typescript": { "version": "npm:@typescript/typescript6@6.0.2", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typescript/typescript6/-/typescript6-6.0.2.tgz", + "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", "integrity": "sha1-eeDkdJJWTIP4YCAT190Q1evFGow=", "dev": true, "requires": { @@ -23135,7 +23135,7 @@ }, "typescript-7": { "version": "npm:typescript@7.0.2", - "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-7.0.2.tgz", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "integrity": "sha1-nsdz15VKjBgsF8xbvVdaoovFFYI=", "dev": true, "requires": { @@ -23251,7 +23251,7 @@ }, "universalify": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, @@ -23689,7 +23689,7 @@ }, "word-wrap": { "version": "1.2.5", - "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/word-wrap/-/word-wrap-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha1-0sRcbdT7zmIaZvE2y+Mor9BBCzQ=", "dev": true }, diff --git a/package.json b/package.json index 4fe1eeb6f..9795b6f78 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-react-native", "displayName": "React Native Tools", - "version": "1.14.1", + "version": "1.14.2", "private": true, "publisher": "msjsdiag", "icon": "resources/images/icon.png", @@ -1394,6 +1394,7 @@ }, "scripts": { "build": "gulp build", + "install-local": "vsce package && codium --install-extension vscode-react-native-$npm_package_version.vsix", "lint": "gulp lint", "format": "gulp format", "vscode:prepublish": "gulp", diff --git a/src/cdp-proxy/reactNativeCDPProxy.ts b/src/cdp-proxy/reactNativeCDPProxy.ts index dfd947afd..e5e067db4 100644 --- a/src/cdp-proxy/reactNativeCDPProxy.ts +++ b/src/cdp-proxy/reactNativeCDPProxy.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. import { IncomingMessage } from "http"; +import { URL } from "url"; import { Connection, Server, @@ -11,6 +12,7 @@ import { IProtocolSuccess, } from "vscode-cdp-proxy"; import { CancellationToken, EventEmitter } from "vscode"; +import WebSocket = require("ws"); import { OutputChannelLogger } from "../extension/log/OutputChannelLogger"; import { LogLevel } from "../extension/log/LogHelper"; import { DebuggerEndpointHelper } from "./debuggerEndpointHelper"; @@ -123,9 +125,7 @@ export class ReactNativeCDPProxy { } } - this.applicationTarget = new Connection( - await WebSocketTransport.create(this.browserInspectUri), - ); + this.applicationTarget = new Connection(await this.createApplicationTransport()); this.applicationTarget.onError(this.onApplicationTargetError.bind(this)); this.debuggerTarget.onError(this.onDebuggerTargetError.bind(this)); @@ -214,6 +214,39 @@ export class ReactNativeCDPProxy { this.logger.error("Error on application transport", err); } + private createApplicationTransport(): Promise { + // Metro's inspector proxy rejects debugger WebSockets whose Origin is not the + // dev server itself (or a loopback host), so send the expected Origin header. + const webSocket = new WebSocket(this.browserInspectUri, [], { + perMessageDeflate: false, + maxPayload: 256 * 1024 * 1024, + origin: ReactNativeCDPProxy.getWebSocketOrigin(this.browserInspectUri), + } as WebSocket.IClientOptions & { maxPayload: number; perMessageDeflate: boolean }); + + return new Promise((resolve, reject) => { + webSocket.once("open", () => { + // vscode-cdp-proxy resolves its own copy of @types/ws, so the two + // structurally identical WebSocket types are not assignable + resolve(new WebSocketTransport(webSocket as any)); + }); + webSocket.once("error", reject); + }); + } + + private static getWebSocketOrigin(webSocketUrl: string): string | undefined { + const url = new URL(webSocketUrl); + + if (url.protocol === "ws:") { + return `http://${url.host}`; + } + + if (url.protocol === "wss:") { + return `https://${url.host}`; + } + + return undefined; + } + private async onApplicationTargetClosed() { this.applicationTarget = null; this.applicationTargetEventEmitter.fire({}); diff --git a/src/extension/reactNativeSessionManager.ts b/src/extension/reactNativeSessionManager.ts index 62438be97..3bb81dbd2 100644 --- a/src/extension/reactNativeSessionManager.ts +++ b/src/extension/reactNativeSessionManager.ts @@ -23,7 +23,7 @@ export class ReactNativeSessionManager ): vscode.ProviderResult { const rnSession = new RNSession(session); - let debugServer; + let debugServer: Net.Server; if (session.configuration.platform != "expoweb") { debugServer = Net.createServer(socket => { const rnDebugSession = @@ -45,11 +45,18 @@ export class ReactNativeSessionManager }); } - debugServer.listen(0, "127.0.0.1"); - this.servers.set(session.id, debugServer); + return new Promise((resolve, reject) => { + debugServer.once("error", reject); + debugServer.listen(0, "127.0.0.1", () => { + debugServer.removeListener("error", reject); + this.servers.set(session.id, debugServer); - // make VS Code connect to debug server - return new vscode.DebugAdapterServer((debugServer.address()).port); + // make VS Code connect to debug server + resolve( + new vscode.DebugAdapterServer((debugServer.address()).port), + ); + }); + }); } public terminate(terminateEvent: TerminateEventArgs): void { diff --git a/test/cdp-proxy/reactNativeCDPProxy.test.ts b/test/cdp-proxy/reactNativeCDPProxy.test.ts index 30ee415ef..521c47b6f 100644 --- a/test/cdp-proxy/reactNativeCDPProxy.test.ts +++ b/test/cdp-proxy/reactNativeCDPProxy.test.ts @@ -290,9 +290,10 @@ suite("reactNativeCDPProxy", function () { generateRandomPortNumber(), ); const debuggerTarget = createConnectionStub(); - const createWebSocketTransportStub = Sinon.stub(WebSocketTransport, "create").returns( - Promise.resolve(createTransportStub()), - ); + const createApplicationTransportStub = Sinon.stub( + localProxy as any, + "createApplicationTransport", + ).returns(Promise.resolve(createTransportStub())); Object.assign(localProxy, { browserInspectUri: "ws://localhost:1234/debugger", @@ -310,7 +311,7 @@ suite("reactNativeCDPProxy", function () { }, ]); } finally { - createWebSocketTransportStub.restore(); + createApplicationTransportStub.restore(); } assert.strictEqual(debuggerTarget.close.called, false); From 170ec376b130c2a14e7f18c17bc35a023ba7c255 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:41:12 +0200 Subject: [PATCH 03/13] Document installing in VSCodium, and mark local builds as patched (#3) * Mark local builds as patched and date-stamp their version The extension keeps upstream's msjsdiag.vscode-react-native id, so a patched local build was indistinguishable from the published one in the extensions list. Rename the display name, and put the build date in the patch version. 1.14. keeps the upstream base visible, always sorts above a real 1.14.x, and rises on every build without being managed by hand. An upstream 1.15.0 would sort above it, which is the point at which this fork wants rebasing anyway. npm run install-local now stamps the date before packaging, so every local install carries the day it was built. It writes package.json and the lockfile, so expect a dirty tree after a local build. Drop this commit before opening a pull request upstream. * Document installing the extension in VSCodium VSCodium resolves extensions from Open VSX, which does not carry this fork, so the packaged VSIX has to be installed directly. Cover both routes: the VSIX attached to a release by CI, and a local build via npm run install-local. Also record how a local build is told apart from the published extension, and that auto update has to be disabled because the fork keeps upstream's extension identifier. Drop this commit before opening a pull request upstream. --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 6 +++--- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f70ca21e6..24f2dfd3f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,49 @@ You can not only download React Native Tools in the marketplace, but can also pa Building and testing React Native Tools from source requires Node.js 22 or later. This development requirement does not affect the runtime requirements of the installed extension. +## Install in VSCodium + +VSCodium installs extensions from [Open VSX](https://open-vsx.org/), which does not carry this fork, so install the packaged `.vsix` directly. + +### From a release + +A `v*` tag builds the extension in CI and attaches the `.vsix` to the release, so no local toolchain is needed: + +```bash +gh release download --repo teoboetti/vscode-react-native --pattern '*.vsix' +codium --install-extension vscode-react-native-*.vsix +``` + +Omitting the tag takes the latest release; pass one (for example `v1.14.20260928`) to install a specific build. + +### From source + +```bash +git clone https://github.com/teoboetti/vscode-react-native.git +cd vscode-react-native +npm install +npm run install-local +``` + +`npm run install-local` stamps the version with the build date, packages the extension and installs it into VSCodium in one step. + +Reload the window afterwards (**Developer: Reload Window**) for VSCodium to pick up the new build. + +If `codium` is not on your `PATH`, run **Shell Command: Install 'codium' command in PATH** from the Command Palette, or install the `.vsix` through **Extensions: Install from VSIX...**. + +### Telling it apart from the published extension + +Local builds are named **React Native Tools (patched)** and versioned `1.14.`, so they are recognisable in the extensions list and always sort above a published `1.14.x`. + +They keep upstream's `msjsdiag.vscode-react-native` identifier, so VSCodium may still try to replace one with a marketplace build. Right-click the extension and choose **Disable Auto Update** to prevent that. + +To pick up upstream changes and rebuild: + +```bash +git fetch upstream && git rebase upstream/master +npm run install-local +``` + ## About the extension This VS Code extension provides a development environment for React Native and Expo projects. @@ -34,6 +77,7 @@ Using this extension, you can **debug your code and quickly run `react-native` o - [React Native Tools Preview](#react-native-tools-preview) - [Package extension locally](#package-extension-locally) +- [Install in VSCodium](#install-in-vscodium) - [About the extension](#about-the-extension) - [Getting started](#getting-started) - [React Native commands in the Command Palette](#react-native-commands-in-the-command-palette) diff --git a/package-lock.json b/package-lock.json index be0a81c7e..05c65714c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-react-native", - "version": "1.14.2", + "version": "1.14.20260928", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-react-native", - "version": "1.14.2", + "version": "1.14.20260928", "hasInstallScript": true, "license": "%reactNative.license%", "dependencies": { diff --git a/package.json b/package.json index 9795b6f78..63d5d96df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-react-native", - "displayName": "React Native Tools", - "version": "1.14.2", + "displayName": "React Native Tools (patched)", + "version": "1.14.20260928", "private": true, "publisher": "msjsdiag", "icon": "resources/images/icon.png", @@ -1394,7 +1394,7 @@ }, "scripts": { "build": "gulp build", - "install-local": "vsce package && codium --install-extension vscode-react-native-$npm_package_version.vsix", + "install-local": "npm version 1.14.$(date +%Y%m%d) --no-git-tag-version --allow-same-version && vsce package -o vscode-react-native-local.vsix && codium --install-extension vscode-react-native-local.vsix", "lint": "gulp lint", "format": "gulp format", "vscode:prepublish": "gulp", From 408af8286e50b13d7d114252258fb7a1af1c1a12 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:01:01 +0200 Subject: [PATCH 04/13] Fix localized strings, Android launch errors and stop Metro on debug stop (#4) * fix(build): keep nls metadata keys relative under gulp 5 gulp 5 stores the literal "." passed as `base` on the vinyl file instead of resolving it, so vscode-nls-dev could not strip the project root from the module path and wrote absolute paths as keys in nls.metadata.json. At runtime vscode-nls looks modules up by their path relative to dist/, found nothing and rendered every localized string as "Messages not found.". Pass the absolute appRoot as base so the keys come out as src/... again. * fix(android): surface the run-android error when the device drops offline When `react-native run-android` failed and the device had gone offline in the meantime, the target lookup in the finally block threw error 1209 and replaced the original failure, so the debug console only said "There is no any Android debuggable online target" while the real cause (for example Gradle's "device offline") was buried in the Run android output channel. Keep the run-android error when at least one device was online at the start of the run, and drop the redundant second lookup in the catch block that could only re-throw the same 1209. When nothing was online from the start 1209 is still reported. Also replace the nested ternary in getTargetIdForRunApp with plain conditions and `includes`, which clears the two ESLint warnings in the file. * feat(debugger): stop the packager when the debug session is stopped Pressing Stop in the debug toolbar left Metro running. On a disconnect with terminateDebuggee set and no restart flag, stop the packager the extension spawned. Restart and plain Disconnect keep it alive, and a packager started outside the editor is never touched. --- gulp_scripts/builder.js | 4 +- src/debugger/debugSessionBase.ts | 3 ++ src/extension/android/androidPlatform.ts | 37 ++++++++++++------- .../extension/android/androidPlatform.test.ts | 22 +++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/gulp_scripts/builder.js b/gulp_scripts/builder.js index 4472a946b..51ac4edbe 100644 --- a/gulp_scripts/builder.js +++ b/gulp_scripts/builder.js @@ -28,7 +28,7 @@ const generateSrcLocBundle = () => { return runTypeScriptCompile().then(() => streamToPromise( gulp - .src(["src/**/*.js"], { base: ".", allowEmpty: true }) + .src(["src/**/*.js"], { base: appRoot, allowEmpty: true }) .pipe(nls.createMetaDataFiles()) .pipe(nls.createAdditionalLanguageFiles(defaultLanguages, "i18n")) .pipe(nls.bundleMetaDataFiles(fullExtensionName, "dist")) @@ -81,7 +81,7 @@ async function build(failOnError, buildNls) { await runTypeScriptCompile(); const stream = gulp - .src(["src/**/*.js", "test/**/*.js"], { base: ".", allowEmpty: true }) + .src(["src/**/*.js", "test/**/*.js"], { base: appRoot, allowEmpty: true }) .pipe(createLinePreservingPreprocessor(preprocessorContext)) .pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) .pipe( diff --git a/src/debugger/debugSessionBase.ts b/src/debugger/debugSessionBase.ts index 095ab1625..a7a1858b2 100644 --- a/src/debugger/debugSessionBase.ts +++ b/src/debugger/debugSessionBase.ts @@ -229,6 +229,9 @@ export abstract class DebugSessionBase extends LoggingDebugSession { ): Promise { if (this.appLauncher) { await this.appLauncher.getRnCdpProxy().stopServer(); + if (args.terminateDebuggee && !args.restart) { + await this.appLauncher.getPackager().stop(true); + } } DeviceStatusIndicator.hide(); diff --git a/src/extension/android/androidPlatform.ts b/src/extension/android/androidPlatform.ts index 83c404dc5..c75a1bf2d 100644 --- a/src/extension/android/androidPlatform.ts +++ b/src/extension/android/androidPlatform.ts @@ -201,13 +201,22 @@ export class AndroidPlatform extends GeneralMobilePlatform { target => target.id, ); let targetId: string | undefined; + let runError: unknown; try { try { await output; + } catch (error) { + runError = error; + throw error; } finally { - targetId = await this.getTargetIdForRunApp(onlineTargetsIds); + targetId = await this.getTargetIdForRunApp(onlineTargetsIds).catch(error => { + if (runError && onlineTargetsIds.length) { + return undefined; + } + throw error; + }); this.packageName = await this.getPackageName(); - devicesIdsForLaunch = [targetId]; + devicesIdsForLaunch = targetId ? [targetId] : []; // Save target info for status indicator if (targetId) { @@ -244,9 +253,6 @@ export class AndroidPlatform extends GeneralMobilePlatform { } } } catch (error) { - if (!targetId) { - targetId = await this.getTargetIdForRunApp(onlineTargetsIds); - } if ( (error as Error).message === ErrorHelper.getInternalError( @@ -343,14 +349,19 @@ export class AndroidPlatform extends GeneralMobilePlatform { "--deviceId", ); } - return deviceId - ? deviceId - : this.runOptions.target && - this.runOptions.target !== TargetType.Simulator && - this.runOptions.target !== TargetType.Device && - onlineTargetsIds.find(id => id === this.runOptions.target) - ? this.runOptions.target - : (await this.getTarget()).id; + if (deviceId) { + return deviceId; + } + const { target } = this.runOptions; + if ( + target && + target !== TargetType.Simulator && + target !== TargetType.Device && + onlineTargetsIds.includes(target) + ) { + return target; + } + return (await this.getTarget()).id; } private getAppIdSuffixFromRunArgumentsIfExists(): string | undefined { diff --git a/test/extension/android/androidPlatform.test.ts b/test/extension/android/androidPlatform.test.ts index 6a095eb7a..8df1bf2a8 100644 --- a/test/extension/android/androidPlatform.test.ts +++ b/test/extension/android/androidPlatform.test.ts @@ -260,6 +260,28 @@ suite("androidPlatform", function () { }, ); + testWithRecordings( + "runApp reports the run-android failure when the only online device goes offline during the run", + ["react-native/run-android/win10-rn0.21.0/failsDueToNoDevicesConnected"], + async () => { + devices = fillDevices(["Nexus_5"]); + getOnlineTargetsStub.restore(); + let onlineTargetsRequests = 0; + getOnlineTargetsStub = sinon.stub(adbHelper, "getOnlineTargets", async () => + onlineTargetsRequests++ === 0 ? devices : [], + ); + + try { + await androidPlatform.runApp(); + should.assert(false, "runApp should've exited with an error"); + } catch (error) { + (error as Error).message + .startsWith("Unknown error: not all success patterns were matched") + .should.be.true(); + } + }, + ); + testWithRecordings( "runApp launches the app in an online emulator only", ["react-native/run-android/win10-rn0.21.0/succeedsWithFiveVSEmulators"], From 8bb875c7d893908e102cbc7f48b2570196f430b5 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:04:09 +0200 Subject: [PATCH 05/13] chore: add project Claude Code settings to disable commit attribution (#5) --- .claude/settings.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..6d08ee00d --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "attribution": { + "commit": "", + "pr": "", + "sessionUrl": false + } +} From d55594f4618583525a101c16f84fa6f3eae092a3 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:34:12 +0200 Subject: [PATCH 06/13] chore(lint): clear all eslint warnings (#6) * chore(lint): clear all eslint warnings Fix the 128 code warnings reported by gulp lint with minimal, behaviour-preserving edits: strict equality, no parameter reassignment, explicit return types on exported functions, flattened promise chains, and reduce/for-index/nested-ternary rewrites. Loops that are retry, polling, or order-dependent keep their sequential await behind a no-await-in-loop disable with a reason. Add the 28 existing file names that fail unicorn/filename-case to the rule's ignore list rather than renaming them. * ci: add GitHub Actions CI, bump actions to v7, enable Dependabot Add a CI workflow that runs lint, build, unit tests and localization tests on pushes to master and on pull requests. Bump checkout, setup-node and upload-artifact to v7 in both workflows and let Dependabot keep GitHub Actions up to date weekly. * ci: bundle the extension before running tests The test suites activate the extension through its dist/rn-extension entry point, which only the webpack production build produces. Run gulp prod-build before the dev build, matching upstream's validation pipeline. --- .eslintrc.js | 32 ++++++++- .github/dependabot.yml | 6 ++ .github/workflows/build-vsix.yml | 6 +- .github/workflows/ci.yml | 40 +++++++++++ .../hermesCDPMessageHandler.ts | 6 +- .../iOSDirectCDPMessageHandler.ts | 3 +- src/cdp-proxy/debuggerEndpointHelper.ts | 7 +- src/cdp-proxy/reactNativeCDPProxy.ts | 20 +++--- src/common/entryPointHandler.ts | 8 ++- src/common/installHelper.ts | 11 +-- src/common/node/childProcess.ts | 13 +--- src/common/node/promise.ts | 26 +++---- src/common/nodeHelper.ts | 2 +- src/common/packageLoader.ts | 4 +- src/common/packager.ts | 7 +- src/common/projectVersionHelper.ts | 15 ++-- src/common/reactNativeProjectHelper.ts | 4 +- src/common/telemetryHelper.ts | 7 +- src/common/utils.ts | 8 +-- src/debugger/debugSessionBase.ts | 2 +- src/debugger/direct/directDebugSession.ts | 28 ++++---- src/debugger/forkedAppWorker.ts | 3 +- src/debugger/jsDebugConfigAdapter.ts | 2 +- src/debugger/rnDebugSession.ts | 2 +- src/debugger/sourceMap.ts | 12 ++-- src/debugger/sourceMapsCombinator.ts | 19 +++-- src/debugger/webDebugSession.ts | 10 +-- src/extension/android/adb.ts | 2 +- .../android/androidContainerUtility.ts | 71 +++++++++---------- src/extension/android/androidDeviceTracker.ts | 1 + src/extension/android/androidTargetManager.ts | 4 +- src/extension/appLauncher.ts | 6 +- .../commands/cleanRestartPackager.ts | 2 + .../commands/installExpoGoApplication.ts | 10 +-- src/extension/commands/launchExpoWeb.ts | 2 +- src/extension/commands/openEASProject.ts | 4 +- src/extension/commands/revertOpenModule.ts | 6 +- src/extension/commands/runEasBuild.ts | 4 +- src/extension/commands/stopPackager.ts | 7 +- src/extension/commands/testDevEnvironment.ts | 14 ++-- src/extension/commands/util/index.ts | 5 +- .../debuggingConfiguration/multiStepInput.ts | 7 +- src/extension/exponent/exponentHelper.ts | 7 +- src/extension/exponent/exponentPlatform.ts | 10 ++- src/extension/generalMobilePlatform.ts | 10 ++- src/extension/generalPlatform.ts | 4 +- src/extension/ios/iOSTargetManager.ts | 2 +- src/extension/ios/plistBuddy.ts | 12 ++-- src/extension/log/ConsoleLogger.ts | 3 +- src/extension/log/OutputChannelLogger.ts | 6 +- .../views/inspectorConsoleView.ts | 20 +++--- src/extension/packagerStatusIndicator.ts | 3 +- src/extension/reactNativeSessionManager.ts | 36 +++++----- src/extension/rn-extension.ts | 6 +- .../experimentService/experimentService.ts | 1 + .../experiments/RNTPreviewPrompt.ts | 3 +- .../services/validationService/checks/env.ts | 2 +- .../validationService/checks/visualStudio.ts | 10 ++- .../services/validationService/util.ts | 17 +++-- src/extension/settingsHelper.ts | 2 +- test/extension/commands/launchExpoWeb.test.ts | 4 +- 61 files changed, 345 insertions(+), 261 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml diff --git a/.eslintrc.js b/.eslintrc.js index c3cac4161..013ae65b8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -194,7 +194,37 @@ module.exports = { cases: { camelCase: true, // pascalCase: true, }, - ignore: [/rn-extension\.ts/], + ignore: [ + /rn-extension\.ts/, + /CDPAPINames\.ts/, + /ConsoleLogger\.ts/, + /ICDPMessageHandler\.ts/, + /IExperiment\.ts/, + /IWDPHelper\.ts/, + /LogHelper\.ts/, + /NullLogger\.ts/, + /OutputChannelLogger\.ts/, + /RNTPreviewPrompt\.ts/, + /baseCDPMessageHandler\.ts/, + /configEASBuild\.ts/, + /graphQLFormatter\.ts/, + /hermesCDPMessageHandler\.ts/, + /iOSClienDevice\.ts/, + /iOSContainerUtility\.ts/, + /iOSDebugModeManager\.ts/, + /iOSDeviceTracker\.ts/, + /iOSDirectCDPMessageHandler\.ts/, + /iOSPlatform\.ts/, + /iOSTargetManager\.ts/, + /macOSDebugModeManager\.ts/, + /macOSPlatform\.ts/, + /nodeJS\.ts/, + /openEASProject\.ts/, + /openRNUpgradeHelper\.ts/, + /reactNativeCDPProxy\.ts/, + /reopenQRCode\.ts/, + /rnCDPMessageHandler\.ts/, + ], }, ], "unicorn/no-array-reduce": "warn", diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ca79ca5b4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/build-vsix.yml b/.github/workflows/build-vsix.yml index 1dbad40dc..0322c8cb8 100644 --- a/.github/workflows/build-vsix.yml +++ b/.github/workflows/build-vsix.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: "24" cache: npm @@ -44,7 +44,7 @@ jobs: gh release create "$GITHUB_REF_NAME" ./*.vsix --generate-notes fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: vsix path: "*.vsix" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..22461b155 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Bundle extension + run: npx gulp prod-build + + - name: Build + run: npm run build + + - name: Test + run: xvfb-run -a npm test + + - name: Localization tests + run: xvfb-run -a npm run test-localization diff --git a/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts b/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts index 02b5f748d..207310199 100644 --- a/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts +++ b/src/cdp-proxy/CDPMessageHandlers/hermesCDPMessageHandler.ts @@ -36,7 +36,8 @@ export class HermesCDPMessageHandler extends BaseCDPMessageHandler { private isFirstRun: boolean = true; private reloadTimer: NodeJS.Timeout | null = null; - public processDebuggerCDPMessage(event: any): ProcessedCDPMessage { + public processDebuggerCDPMessage(initialEvent: any): ProcessedCDPMessage { + let event = initialEvent; let sendBack = false; if (event.method === CDP_API_NAMES.CLOSE) { this.cancelFirstRunReload(); @@ -60,7 +61,8 @@ export class HermesCDPMessageHandler extends BaseCDPMessageHandler { }; } - public processApplicationCDPMessage(event: any): ProcessedCDPMessage { + public processApplicationCDPMessage(initialEvent: any): ProcessedCDPMessage { + let event = initialEvent; const sendBack = false; if (event.method === CDP_API_NAMES.DEBUGGER_PAUSED) { event = this.handlePausedEvent(event); diff --git a/src/cdp-proxy/CDPMessageHandlers/iOSDirectCDPMessageHandler.ts b/src/cdp-proxy/CDPMessageHandlers/iOSDirectCDPMessageHandler.ts index 565c86683..103526899 100644 --- a/src/cdp-proxy/CDPMessageHandlers/iOSDirectCDPMessageHandler.ts +++ b/src/cdp-proxy/CDPMessageHandlers/iOSDirectCDPMessageHandler.ts @@ -39,7 +39,8 @@ export class IOSDirectCDPMessageHandler extends BaseCDPMessageHandler { }; } - public processApplicationCDPMessage(event: any): ProcessedCDPMessage { + public processApplicationCDPMessage(initialEvent: any): ProcessedCDPMessage { + let event = initialEvent; if (event.method === CDP_API_NAMES.CONSOLE_MESSAGE_ADDED) { event = this.processDeprecatedConsoleMessage(event); } diff --git a/src/cdp-proxy/debuggerEndpointHelper.ts b/src/cdp-proxy/debuggerEndpointHelper.ts index 397ac22c8..a1dcfc426 100644 --- a/src/cdp-proxy/debuggerEndpointHelper.ts +++ b/src/cdp-proxy/debuggerEndpointHelper.ts @@ -34,22 +34,26 @@ export class DebuggerEndpointHelper { */ public async retryGetWSEndpoint( browserURL: string, - attemptNumber: number, + initialAttemptNumber: number, cancellationToken: CancellationToken, isHermes: boolean = false, settingsPort?: number, ): Promise { + let attemptNumber = initialAttemptNumber; while (true) { try { let url = ""; if (settingsPort) { url = `http://localhost:${settingsPort}`; try { + // eslint-disable-next-line no-await-in-loop -- sequential retry loop return await this.getWSEndpoint(browserURL, isHermes); } catch { + // eslint-disable-next-line no-await-in-loop -- sequential retry loop return await this.getWSEndpoint(url, isHermes); } } else { + // eslint-disable-next-line no-await-in-loop -- sequential retry loop return await this.getWSEndpoint(browserURL, isHermes); } } catch (err) { @@ -71,6 +75,7 @@ export class DebuggerEndpointHelper { } attemptNumber--; + // eslint-disable-next-line no-await-in-loop -- delay between retry attempts await PromiseUtil.delay(700); } } diff --git a/src/cdp-proxy/reactNativeCDPProxy.ts b/src/cdp-proxy/reactNativeCDPProxy.ts index e5e067db4..12dd07316 100644 --- a/src/cdp-proxy/reactNativeCDPProxy.ts +++ b/src/cdp-proxy/reactNativeCDPProxy.ts @@ -112,17 +112,15 @@ export class ReactNativeCDPProxy { this.debuggerTarget.pause(); // don't listen for events until the target is ready if (!this.browserInspectUri) { - if (this.cancellationToken) { - this.browserInspectUri = await this.debuggerEndpointHelper.retryGetWSEndpoint( - `http://localhost:${this.applicationTargetPort}`, - 90, - this.cancellationToken, - ); - } else { - this.browserInspectUri = await this.debuggerEndpointHelper.getWSEndpoint( - `http://localhost:${this.applicationTargetPort}`, - ); - } + this.browserInspectUri = this.cancellationToken + ? await this.debuggerEndpointHelper.retryGetWSEndpoint( + `http://localhost:${this.applicationTargetPort}`, + 90, + this.cancellationToken, + ) + : await this.debuggerEndpointHelper.getWSEndpoint( + `http://localhost:${this.applicationTargetPort}`, + ); } this.applicationTarget = new Connection(await this.createApplicationTransport()); diff --git a/src/common/entryPointHandler.ts b/src/common/entryPointHandler.ts index 38509b38b..a63d820cb 100644 --- a/src/common/entryPointHandler.ts +++ b/src/common/entryPointHandler.ts @@ -76,14 +76,18 @@ export class EntryPointHandler { } private handleErrors( - error: InternalError, + internalError: InternalError, resultOfCode: Promise, errorsAreFatal: boolean, ): Promise { resultOfCode.catch(reason => { const isDebugeeProcess = this.processType === ProcessType.Debugee; const shouldLogStack = !errorsAreFatal || isDebugeeProcess; - this.logger.error(error.message, ErrorHelper.wrapError(error, reason), shouldLogStack); + this.logger.error( + internalError.message, + ErrorHelper.wrapError(internalError, reason), + shouldLogStack, + ); // For the debugee process we don't want to throw an exception because the debugger // will appear to the user if he turned on the VS Code uncaught exceptions feature. if (errorsAreFatal) { diff --git a/src/common/installHelper.ts b/src/common/installHelper.ts index 68380ab5b..1c92bd77b 100644 --- a/src/common/installHelper.ts +++ b/src/common/installHelper.ts @@ -7,7 +7,10 @@ import { SimctrlHelper } from "../extension/ios/simctl"; import { OutputChannelLogger } from "../extension/log/OutputChannelLogger"; import { ChildProcess } from "./node/childProcess"; -export async function installAndroidApplication(project: AppLauncher, appPath: string) { +export async function installAndroidApplication( + project: AppLauncher, + appPath: string, +): Promise { const logger = OutputChannelLogger.getMainChannel(); const adbHelper = new AdbHelper( project.getPackager().getProjectPath(), @@ -15,7 +18,7 @@ export async function installAndroidApplication(project: AppLauncher, appPath: s ); const targets = await adbHelper.getOnlineTargets(); - if (targets.length == 0) { + if (targets.length === 0) { throw new Error("No online target found, please check your emulator status."); } else if (targets.length > 1) { logger.logStream( @@ -37,7 +40,7 @@ export async function installAndroidApplication(project: AppLauncher, appPath: s } } -export async function installiOSApplication(project: AppLauncher, appPath: string) { +export async function installiOSApplication(project: AppLauncher, appPath: string): Promise { const logger = OutputChannelLogger.getMainChannel(); const childProcess: ChildProcess = new ChildProcess(); @@ -54,7 +57,7 @@ export async function installiOSApplication(project: AppLauncher, appPath: strin throw e; } - if (targets.length == 1) { + if (targets.length === 1) { throw new Error("No booted iOS simulator found, please check your simulator status."); } else if (targets.length > 2) { logger.logStream( diff --git a/src/common/node/childProcess.ts b/src/common/node/childProcess.ts index 32f460cee..bf1877ca5 100644 --- a/src/common/node/childProcess.ts +++ b/src/common/node/childProcess.ts @@ -58,12 +58,7 @@ export class ChildProcess { process = this.childProcess.exec( command, options, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ( - error: nodeChildProcess.ExecException | null, - stdout: string | Buffer, - stderr: string | Buffer, - ) => { + (error: nodeChildProcess.ExecException | null, stdout: string | Buffer) => { if (error) { reject( ErrorHelper.getNestedError( @@ -98,11 +93,7 @@ export class ChildProcess { command, args, options, - ( - error: nodeChildProcess.ExecException | null, - stdout: string | Buffer, - stderr: string | Buffer, - ) => { + (error: nodeChildProcess.ExecException | null, stdout: string | Buffer) => { if (error) { reject( ErrorHelper.getNestedError( diff --git a/src/common/node/promise.ts b/src/common/node/promise.ts index fa7f06041..500857b95 100644 --- a/src/common/node/promise.ts +++ b/src/common/node/promise.ts @@ -49,10 +49,10 @@ export class PromiseUtil { ): Promise { const arraySources: T[] = sources instanceof Promise ? await sources : sources; - return arraySources.reduce(async (previousReduction: Promise, newSource: T) => { - await previousReduction; - return generateAsyncOperation(newSource); - }, Promise.resolve()); + for (const source of arraySources) { + // eslint-disable-next-line no-await-in-loop -- operations run sequentially by design + await generateAsyncOperation(source); + } } public static async delay(duration: number): Promise { @@ -66,7 +66,7 @@ export class PromiseUtil { interval: number = 1000, timeout?: number, ): Promise { - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { let rejectTimeout: NodeJS.Timeout | undefined; // eslint-disable-next-line prefer-const let сheckInterval: NodeJS.Timeout | undefined; @@ -103,14 +103,14 @@ export class PromiseUtil { } }; - const resolved = await tryToResolve(); - if (resolved) { - return; - } - - сheckInterval = setInterval(async () => { - await tryToResolve(); - }, interval); + void tryToResolve().then(resolved => { + if (resolved) { + return; + } + сheckInterval = setInterval(async () => { + await tryToResolve(); + }, interval); + }); }); } diff --git a/src/common/nodeHelper.ts b/src/common/nodeHelper.ts index 5effd87ab..52c35d338 100644 --- a/src/common/nodeHelper.ts +++ b/src/common/nodeHelper.ts @@ -3,7 +3,7 @@ import { ChildProcess } from "./node/childProcess"; -export async function getNodeVersion(projectPath: string, env: object) { +export async function getNodeVersion(projectPath: string, env: object): Promise { try { return await new ChildProcess().execToString("node -v", { cwd: projectPath, env }); } catch (error) { diff --git a/src/common/packageLoader.ts b/src/common/packageLoader.ts index d3a1daa8e..cdd20e268 100644 --- a/src/common/packageLoader.ts +++ b/src/common/packageLoader.ts @@ -175,6 +175,7 @@ export class PackageLoader { const load = this.packagesQueue.length; const packagesForInstall = this.packagesQueue.slice(0, load); + // eslint-disable-next-line no-await-in-loop -- packages are installed one npm command at a time await commandExecutor.spawnWithProgress( HostPlatform.getNpmCliCommand("npm"), ["install", ...packagesForInstall, "--verbose", "--no-save", "--global-style"], @@ -184,7 +185,8 @@ export class PackageLoader { ); // Try to require all pending packages after every 'npm install ...' command const requiresToRemove: ((load?: string[]) => Promise)[] = []; - for (tryToRequire of this.requireQueue) { + for (const tryToRequire of this.requireQueue) { + // eslint-disable-next-line no-await-in-loop -- iterates a live queue that may grow while awaiting if (await tryToRequire(packagesForInstall)) { requiresToRemove.push(tryToRequire); } diff --git a/src/common/packager.ts b/src/common/packager.ts index e69168629..ff5a89f7c 100644 --- a/src/common/packager.ts +++ b/src/common/packager.ts @@ -273,13 +273,16 @@ export class Packager { ); let packagerSpawnResult; - if (this.runOptions?.platform != "exponent" && this.runOptions?.platform != "expoweb") { + if ( + this.runOptions?.platform !== "exponent" && + this.runOptions?.platform !== "expoweb" + ) { packagerSpawnResult = new CommandExecutor( nodeModulesRoot, this.projectPath, this.logger, ).spawnReactPackager(args, spawnOptions); - } else if (this.runOptions?.platform == "exponent") { + } else if (this.runOptions?.platform === "exponent") { this.runOptions?.expoPlatformType && args.push(`--${this.runOptions?.expoPlatformType?.toLowerCase()}`); logger.log( diff --git a/src/common/projectVersionHelper.ts b/src/common/projectVersionHelper.ts index 1c9715d7c..298bc51e1 100644 --- a/src/common/projectVersionHelper.ts +++ b/src/common/projectVersionHelper.ts @@ -56,8 +56,9 @@ export class ProjectVersionHelper { */ public static async getReactNativeRequiredNodeRange( projectRoot: string, - nodeModulesRoot?: string, + initialNodeModulesRoot?: string, ): Promise { + let nodeModulesRoot = initialNodeModulesRoot; try { if (!nodeModulesRoot) { nodeModulesRoot = @@ -88,8 +89,9 @@ export class ProjectVersionHelper { public static async getReactNativeVersions( projectRoot: string, additionalPackagesToCheck?: ParsedPackage[], - nodeModulesRoot?: string, + initialNodeModulesRoot?: string, ): Promise { + let nodeModulesRoot = initialNodeModulesRoot; if (!nodeModulesRoot) { nodeModulesRoot = ProjectsStorage.getFolderByProjectRootPath( @@ -113,8 +115,9 @@ export class ProjectVersionHelper { public static async tryToGetRNSemverValidVersionsFromProjectPackage( projectRoot: string, additionalPackagesToCheck?: ParsedPackage[], - nodeModulesRoot?: string, + initialNodeModulesRoot?: string, ): Promise { + let nodeModulesRoot = initialNodeModulesRoot; const versions = await ProjectVersionHelper.getReactNativeVersionsFromProjectPackage( projectRoot, additionalPackagesToCheck, @@ -186,11 +189,7 @@ export class ProjectVersionHelper { }); const packageVersionArray = await Promise.all(versionPromises); - const packageVersions = packageVersionArray.reduce( - (allPackageVersions, packageVersion) => - Object.assign(allPackageVersions, packageVersion), - {}, - ); + const packageVersions: PackageVersion = Object.assign({}, ...packageVersionArray); if (ProjectVersionHelper.isVersionError(packageVersions["react-native"])) { throw ErrorHelper.getInternalError(InternalErrorCode.ReactNativePackageIsNotInstalled); } diff --git a/src/common/reactNativeProjectHelper.ts b/src/common/reactNativeProjectHelper.ts index 129511be8..c1bb0545d 100644 --- a/src/common/reactNativeProjectHelper.ts +++ b/src/common/reactNativeProjectHelper.ts @@ -101,7 +101,7 @@ export class ReactNativeProjectHelper { return hermesEnabled; } - public static async UpdateMertoBundlerForExpoWeb(launchArgs: any) { + public static async UpdateMertoBundlerForExpoWeb(launchArgs: any): Promise { const appJsonPath = path.join(launchArgs.cwd, "app.json"); const fs = new FileSystem(); const appJson = await fs.readFile(appJsonPath).then(content => { @@ -114,7 +114,7 @@ export class ReactNativeProjectHelper { } } - public static async verifyMetroConfigFile(projectRoot: string) { + public static async verifyMetroConfigFile(projectRoot: string): Promise { const logger = OutputChannelLogger.getChannel(OutputChannelLogger.MAIN_CHANNEL_NAME, true); let version; diff --git a/src/common/telemetryHelper.ts b/src/common/telemetryHelper.ts index 1cc4945b5..854112c8d 100644 --- a/src/common/telemetryHelper.ts +++ b/src/common/telemetryHelper.ts @@ -60,8 +60,9 @@ export class TelemetryHelper { public static addPlatformPropertiesToTelemetryProperties( args: IRunOptions, versions: RNPackageVersions, - properties: ICommandTelemetryProperties, + initialProperties: ICommandTelemetryProperties, ): any { + let properties = initialProperties; properties = TelemetryHelper.addPropertyToTelemetryProperties( versions.reactNativeVersion, "reactNativeVersion", @@ -286,11 +287,11 @@ export class TelemetryHelper { propertyValue: any, isPii: boolean, ): void { - for (let i = 0; i < propertyValue.length; i++) { + for (const [i, value] of propertyValue.entries()) { TelemetryHelper.setTelemetryEventProperty( event, propertyName + String(i), - propertyValue[i], + value, isPii, ); } diff --git a/src/common/utils.ts b/src/common/utils.ts index 0084ecebd..394f19d41 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -46,9 +46,9 @@ export function areSameDates(date1: Date, date2: Date): boolean { ); } -export function getRandomIntInclusive(min: number, max: number): number { - min = Math.ceil(min); - max = Math.floor(max); +export function getRandomIntInclusive(initialMin: number, initialMax: number): number { + const min = Math.ceil(initialMin); + const max = Math.floor(initialMax); return Math.floor(Math.random() * (max - min + 1)) + min; } @@ -127,7 +127,7 @@ export function ipToBuffer(ip: string): Buffer { throw new Error("Invalid IP address format."); } -export async function switchBundleOptions(projectRootPath: string, flag: boolean) { +export async function switchBundleOptions(projectRootPath: string, flag: boolean): Promise { const splitBundleOptionsPath = path.resolve( projectRootPath, "node_modules", diff --git a/src/debugger/debugSessionBase.ts b/src/debugger/debugSessionBase.ts index a7a1858b2..6f2b6f18a 100644 --- a/src/debugger/debugSessionBase.ts +++ b/src/debugger/debugSessionBase.ts @@ -215,7 +215,7 @@ export abstract class DebugSessionBase extends LoggingDebugSession { ); } const settingsPort = this.appLauncher.getPackagerPort(projectRootPath); - if (this.appLauncher.getPackager().getPort() != settingsPort) { + if (this.appLauncher.getPackager().getPort() !== settingsPort) { this.appLauncher.getPackager().resetToSettingsPort(); } } diff --git a/src/debugger/direct/directDebugSession.ts b/src/debugger/direct/directDebugSession.ts index e8e9f5dce..d1deb8262 100644 --- a/src/debugger/direct/directDebugSession.ts +++ b/src/debugger/direct/directDebugSession.ts @@ -79,7 +79,7 @@ export class DirectDebugSession extends DebugSessionBase { try { try { - if (launchArgs.platform != "exponent") { + if (launchArgs.platform !== "exponent") { await ReactNativeProjectHelper.verifyMetroConfigFile(launchArgs.cwd); } await this.initializeSettings(launchArgs); @@ -184,11 +184,12 @@ export class DirectDebugSession extends DebugSessionBase { // eslint-disable-next-line @typescript-eslint/no-unused-vars await TelemetryHelper.generate("attach", extProps, async generator => { - const port = attachArgs.useHermesEngine - ? attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd) - : attachArgs.platform === PlatformType.iOS - ? attachArgs.port || IWDPHelper.iOS_WEBKIT_DEBUG_PROXY_DEFAULT_PORT - : null; + let port: number | null = null; + if (attachArgs.useHermesEngine) { + port = attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd); + } else if (attachArgs.platform === PlatformType.iOS) { + port = attachArgs.port || IWDPHelper.iOS_WEBKIT_DEBUG_PROXY_DEFAULT_PORT; + } if (port === null) { throw ErrorHelper.getInternalError( InternalErrorCode.CouldNotDirectDebugWithoutHermesEngine, @@ -199,11 +200,12 @@ export class DirectDebugSession extends DebugSessionBase { logger.log(`Connecting to ${attachArgs.port} port`); await this.appLauncher.getRnCdpProxy().stopServer(); - const cdpMessageHandler: BaseCDPMessageHandler | null = attachArgs.useHermesEngine - ? new HermesCDPMessageHandler() - : attachArgs.platform === PlatformType.iOS - ? new IOSDirectCDPMessageHandler() - : null; + let cdpMessageHandler: BaseCDPMessageHandler | null = null; + if (attachArgs.useHermesEngine) { + cdpMessageHandler = new HermesCDPMessageHandler(); + } else if (attachArgs.platform === PlatformType.iOS) { + cdpMessageHandler = new IOSDirectCDPMessageHandler(); + } if (!cdpMessageHandler) { throw ErrorHelper.getInternalError( @@ -262,7 +264,7 @@ export class DirectDebugSession extends DebugSessionBase { const debuggerType = await this.debuggerEndpointHelper.getDebuggerTpye( `http://localhost:${attachArgs.port}`, ); - if (debuggerType == "expo") { + if (debuggerType === "expo") { const expoBrowserInspectUri = `${browserInspectUri.split("&")[0]}&page=2`; this.appLauncher.getRnCdpProxy().setBrowserInspectUri(expoBrowserInspectUri); } else { @@ -291,7 +293,7 @@ export class DirectDebugSession extends DebugSessionBase { this.debugSessionStatus = DebugSessionStatus.Stopping; // Stop packager when using expo-cli, to avoid launch conflicts in the next launch request - if (this.appLauncher.getPackager().getPlatform() == "exponent") { + if (this.appLauncher.getPackager().getPlatform() === "exponent") { await this.appLauncher.getPackager().stop(); } diff --git a/src/debugger/forkedAppWorker.ts b/src/debugger/forkedAppWorker.ts index 691599e15..8c6836798 100644 --- a/src/debugger/forkedAppWorker.ts +++ b/src/debugger/forkedAppWorker.ts @@ -136,7 +136,8 @@ export class ForkedAppWorker implements IDebuggeeWorker { return port; } - public async postMessage(rnMessage: RNAppMessage): Promise { + public async postMessage(rawMessage: RNAppMessage): Promise { + let rnMessage = rawMessage; // Before sending messages, make sure that the worker is loaded await new Promise(resolve => { if (this.workerLoaded) { diff --git a/src/debugger/jsDebugConfigAdapter.ts b/src/debugger/jsDebugConfigAdapter.ts index 00e3f84b3..e81a0c481 100644 --- a/src/debugger/jsDebugConfigAdapter.ts +++ b/src/debugger/jsDebugConfigAdapter.ts @@ -30,7 +30,7 @@ export class JsDebugConfigAdapter { attachArgs: IAttachRequestArgs, cdpProxyPort: number, sessionId: string, - ) { + ): Promise { const extraArgs: any = {}; const versions = await ProjectVersionHelper.getReactNativeVersions(attachArgs.cwd); // Handle project file path from 0.76 diff --git a/src/debugger/rnDebugSession.ts b/src/debugger/rnDebugSession.ts index 4227caba8..9deb0d474 100644 --- a/src/debugger/rnDebugSession.ts +++ b/src/debugger/rnDebugSession.ts @@ -59,7 +59,7 @@ export class RNDebugSession extends DebugSessionBase { ): Promise { try { try { - if (launchArgs.platform != "exponent") { + if (launchArgs.platform !== "exponent") { await ReactNativeProjectHelper.verifyMetroConfigFile(launchArgs.cwd); } const requiredNodeRange = diff --git a/src/debugger/sourceMap.ts b/src/debugger/sourceMap.ts index f9f4bba02..fc026b7da 100644 --- a/src/debugger/sourceMap.ts +++ b/src/debugger/sourceMap.ts @@ -103,7 +103,8 @@ export class SourceMapUtil { } } - public appendSourceMapPaths(scriptBody: string, sourceMappingUrl: string): string { + public appendSourceMapPaths(initialScriptBody: string, sourceMappingUrl: string): string { + let scriptBody = initialScriptBody; scriptBody += `//# sourceMappingURL=${sourceMappingUrl}`; return scriptBody; } @@ -176,11 +177,14 @@ export class SourceMapUtil { * 2. It changes the path separators to Unix style. */ private updateSourceMapPath( - sourcePath: string, + initialSourcePath: string, sourcesRootPath: string, - packagerRemoteRoot?: string, - packagerLocalRoot?: string, + initialPackagerRemoteRoot?: string, + initialPackagerLocalRoot?: string, ) { + let sourcePath = initialSourcePath; + let packagerRemoteRoot = initialPackagerRemoteRoot; + let packagerLocalRoot = initialPackagerLocalRoot; if (packagerRemoteRoot && packagerLocalRoot) { packagerRemoteRoot = this.makeUnixStylePath(packagerRemoteRoot); packagerLocalRoot = this.makeUnixStylePath(packagerLocalRoot); diff --git a/src/debugger/sourceMapsCombinator.ts b/src/debugger/sourceMapsCombinator.ts index 76ac031c0..7208ca7c9 100644 --- a/src/debugger/sourceMapsCombinator.ts +++ b/src/debugger/sourceMapsCombinator.ts @@ -22,17 +22,14 @@ const DISK_LETTER_RE = /^(?:[a-z]{2,}:\/{3})?[a-z]:/i; export class SourceMapsCombinator { public convert(rawBundleSourcemap: RawSourceMap): RawSourceMap { // Find user files from bundle files list - const consumers: { [key: string]: SourceMapConsumer } = rawBundleSourcemap.sources.reduce( - (result: { [key: string]: SourceMapConsumer }, file) => { - // Skip files inside node_modules - if (file.includes("node_modules")) return result; - - const consumer: SourceMapConsumer | null = this.getSourceMapConsumerFrom(file); - if (consumer) result[file] = consumer; - return result; - }, - {}, - ); + const consumers: { [key: string]: SourceMapConsumer } = {}; + for (const file of rawBundleSourcemap.sources) { + // Skip files inside node_modules + if (file.includes("node_modules")) continue; + + const consumer: SourceMapConsumer | null = this.getSourceMapConsumerFrom(file); + if (consumer) consumers[file] = consumer; + } if (Object.keys(consumers).length === 0) { // Sourcemaps not found, so return original bundle sourcemap diff --git a/src/debugger/webDebugSession.ts b/src/debugger/webDebugSession.ts index c0e89d82b..ea71b14ff 100644 --- a/src/debugger/webDebugSession.ts +++ b/src/debugger/webDebugSession.ts @@ -68,7 +68,7 @@ export class WebDebugSession extends DebugSessionBase { logger.verbose(`Launching the application: ${JSON.stringify(launchArgs, null, 2)}`); await this.updateWebpackMetroConfig(launchArgs); - await this.verifyExpoWebRequiredDependencies(launchArgs); + this.verifyExpoWebRequiredDependencies(launchArgs); await this.appLauncher.launchExpoWeb(launchArgs); await this.waitExpoWebIsRunning(launchArgs); await this.appLauncher.launchBrowser(launchArgs); @@ -89,7 +89,6 @@ export class WebDebugSession extends DebugSessionBase { protected async attachRequest( response: DebugProtocol.AttachResponse, attachArgs: IAttachRequestArgs, - request?: DebugProtocol.Request, ): Promise { const doAttach = async (attachArgs: IAttachRequestArgs) => { try { @@ -128,7 +127,7 @@ export class WebDebugSession extends DebugSessionBase { } }, ); - await this.establishDebugSession(processedAttachArgs); + this.establishDebugSession(processedAttachArgs); this.debugSessionStatus = DebugSessionStatus.ConnectionDone; }); @@ -162,10 +161,7 @@ export class WebDebugSession extends DebugSessionBase { return super.disconnectRequest(response, args, request); } - protected establishDebugSession( - attachArgs: IAttachRequestArgs, - resolve?: (value?: void | PromiseLike | undefined) => void, - ): void { + protected establishDebugSession(attachArgs: IAttachRequestArgs): void { if (this.cdpProxy) { const attachArguments = JsDebugConfigAdapter.createChromeDebuggingConfig( attachArgs, diff --git a/src/extension/android/adb.ts b/src/extension/android/adb.ts index 2fc99fbdd..d51b2cddf 100644 --- a/src/extension/android/adb.ts +++ b/src/extension/android/adb.ts @@ -279,7 +279,7 @@ export class AdbHelper { const result: IDebuggableMobileTarget[] = []; const regex = new RegExp("^(\\S+)\\t(\\S+)$", "mg"); let match = regex.exec(input); - while (match != null) { + while (match !== null) { result.push({ id: match[1], isOnline: match[2] === "device", diff --git a/src/extension/android/androidContainerUtility.ts b/src/extension/android/androidContainerUtility.ts index 05d6b4502..f25813a26 100644 --- a/src/extension/android/androidContainerUtility.ts +++ b/src/extension/android/androidContainerUtility.ts @@ -41,7 +41,7 @@ class RunAsError extends Error { * @format */ -export function push( +export async function push( adbHelper: AdbHelper, deviceId: string, app: string, @@ -49,27 +49,22 @@ export function push( contents: string, logger?: OutputChannelLogger, ): Promise { - return validateAppName(app).then(validApp => - validateFilePath(filepath).then(validFilepath => - validateFileContent(contents).then(validContent => - _push(adbHelper, deviceId, validApp, validFilepath, validContent, logger), - ), - ), - ); + const validApp = await validateAppName(app); + const validFilepath = await validateFilePath(filepath); + const validContent = await validateFileContent(contents); + return _push(adbHelper, deviceId, validApp, validFilepath, validContent, logger); } -export function pull( +export async function pull( adbHelper: AdbHelper, deviceId: string, app: string, path: string, logger?: OutputChannelLogger, ): Promise { - return validateAppName(app).then(validApp => - validateFilePath(path).then(validPath => - _pull(adbHelper, deviceId, validApp, validPath, logger), - ), - ); + const validApp = await validateAppName(app); + const validPath = await validateFilePath(path); + return _pull(adbHelper, deviceId, validApp, validPath, logger); } /** @@ -152,7 +147,7 @@ function validateFileContent(content: string): Promise { return Promise.reject(new Error(`Disallowed escaping file content: ${content}`)); } -function _push( +async function _push( adbHelper: AdbHelper, deviceId: string, app: string, @@ -161,25 +156,25 @@ function _push( logger?: OutputChannelLogger, ): Promise { const command = `echo \\"${contents}\\" > "${filename}" && chmod 644 "${filename}"`; - return executeCommandAsApp(adbHelper, deviceId, app, command) - .then(res => { - logger?.debug(res); - }) - .catch(error => { - if (error instanceof RunAsError) { - // Fall back to running the command directly. This will work if adb is running as root. - return executeCommandWithSu(adbHelper, deviceId, app, command) - .then(() => undefined) - .catch(e => { - logger?.debug(e.toString()); - throw error; - }); + try { + const res = await executeCommandAsApp(adbHelper, deviceId, app, command); + logger?.debug(res); + } catch (error) { + if (error instanceof RunAsError) { + // Fall back to running the command directly. This will work if adb is running as root. + try { + await executeCommandWithSu(adbHelper, deviceId, app, command); + return; + } catch (e) { + logger?.debug(String(e)); + throw error; } - throw error; - }); + } + throw error; + } } -function _pull( +async function _pull( adbHelper: AdbHelper, deviceId: string, app: string, @@ -187,17 +182,21 @@ function _pull( logger?: OutputChannelLogger, ): Promise { const command = `cat "${path}"`; - return executeCommandAsApp(adbHelper, deviceId, app, command).catch(error => { + try { + return await executeCommandAsApp(adbHelper, deviceId, app, command); + } catch (error) { if (error instanceof RunAsError) { // Fall back to running the command directly. This will work if adb is running as root. - return executeCommandWithSu(adbHelper, deviceId, app, command).catch(e => { + try { + return await executeCommandWithSu(adbHelper, deviceId, app, command); + } catch (e) { // Throw the original error. - logger?.debug(e.toString()); + logger?.debug(String(e)); throw error; - }); + } } throw error; - }); + } } // Keep this method private since it relies on pre-validated arguments diff --git a/src/extension/android/androidDeviceTracker.ts b/src/extension/android/androidDeviceTracker.ts index 6fb8d5da7..f64e858a8 100644 --- a/src/extension/android/androidDeviceTracker.ts +++ b/src/extension/android/androidDeviceTracker.ts @@ -44,6 +44,7 @@ export class AndroidDeviceTracker extends AbstractDeviceTracker { onlineDevice.isVirtualTarget, ClientOS.Android, ); + // eslint-disable-next-line no-await-in-loop -- adb reverse is set up per device sequentially; keeps fail-fast and insertion order await this.initAndroidDevice(androidDevice); DeviceStorage.devices.set(androidDevice.id, androidDevice); } diff --git a/src/extension/android/androidTargetManager.ts b/src/extension/android/androidTargetManager.ts index 5954e4fb2..18dfb8f50 100644 --- a/src/extension/android/androidTargetManager.ts +++ b/src/extension/android/androidTargetManager.ts @@ -119,6 +119,7 @@ export class AndroidTargetManager extends MobileTargetManager { const onlineTargets = await this.adbHelper.getOnlineTargets(); for (const device of onlineTargets) { if (device.isVirtualTarget && collectSimulators) { + // eslint-disable-next-line no-await-in-loop -- adb is queried per device sequentially const avdName = await this.adbHelper.getAvdNameById(device.id); const emulatorTarget = targetList.find(target => target.name === avdName); if (emulatorTarget) { @@ -153,7 +154,7 @@ export class AndroidTargetManager extends MobileTargetManager { emulatorProcess.outcome.catch(error => { emulatorLaunchFailed = true; if ( - process.platform == "win32" && + process.platform === "win32" && process.env.SESSIONNAME && process.env.SESSIONNAME.toLowerCase().includes("rdp-tcp") ) { @@ -175,6 +176,7 @@ export class AndroidTargetManager extends MobileTargetManager { throw new Error("Android emulator launch failed unexpectedly"); const connectedDevices = await this.adbHelper.getOnlineTargets(); for (const target of connectedDevices) { + // eslint-disable-next-line no-await-in-loop -- polling condition, returns on first match const onlineAvdName = await this.adbHelper.getAvdNameById(target.id); if (onlineAvdName === emulatorTarget.name) { return target.id; diff --git a/src/extension/appLauncher.ts b/src/extension/appLauncher.ts index d0f786af4..e28f0c923 100644 --- a/src/extension/appLauncher.ts +++ b/src/extension/appLauncher.ts @@ -519,7 +519,7 @@ export class AppLauncher { // Launch browser let browserFinder: BrowserHelper.IBrowserFinder; - if (launchArgs.platform == PlatformType.ExpoWeb) { + if (launchArgs.platform === PlatformType.ExpoWeb) { switch (launchArgs.browserTarget) { case BROWSER_TYPES.Edge: browserFinder = new BrowserHelper.EdgeBrowserFinder( @@ -563,9 +563,9 @@ export class AppLauncher { public getRunArguments(launchArgs: any): string[] { let userDataDir; - if (launchArgs.browserTarget == BROWSER_TYPES.Chrome) { + if (launchArgs.browserTarget === BROWSER_TYPES.Chrome) { userDataDir = path.join(HostPlatform.getSettingsHome(), AppLauncher.CHROME_DATA_DIR); - } else if (launchArgs.browserTarget == BROWSER_TYPES.Edge) { + } else if (launchArgs.browserTarget === BROWSER_TYPES.Edge) { userDataDir = path.join(HostPlatform.getSettingsHome(), AppLauncher.EDGE_DATA_DIR); } else { userDataDir = ""; diff --git a/src/extension/commands/cleanRestartPackager.ts b/src/extension/commands/cleanRestartPackager.ts index dae1b2baa..6ba1534c7 100644 --- a/src/extension/commands/cleanRestartPackager.ts +++ b/src/extension/commands/cleanRestartPackager.ts @@ -71,6 +71,7 @@ export class CleanRestartPackager extends ReactNativeCommand { if (match && match[1]) { const pid = match[1]; logger.info(`Found Metro process with PID: ${pid}`); + // eslint-disable-next-line no-await-in-loop -- kill PIDs one at a time; taskkill /T kills process trees, so later PIDs may already be gone await childProcess .exec(`taskkill /PID ${pid} /F /T`) .then(killResult => killResult.outcome); @@ -143,6 +144,7 @@ export class CleanRestartPackager extends ReactNativeCommand { const stat = fs.statSync(filePath); if (stat.isDirectory()) { + // eslint-disable-next-line no-await-in-loop -- entries are removed sequentially before rmdir await this.deleteDirectory(filePath); } else { fs.unlinkSync(filePath); diff --git a/src/extension/commands/installExpoGoApplication.ts b/src/extension/commands/installExpoGoApplication.ts index c7e03110f..c6c47c1e1 100644 --- a/src/extension/commands/installExpoGoApplication.ts +++ b/src/extension/commands/installExpoGoApplication.ts @@ -46,7 +46,7 @@ export class InstallExpoGoApplication extends Command { const currentSdkVersion = await expoHelper.exponentSdk(true); const expoUrlInfo = jsonContent.sdkVersions[currentSdkVersion]; - if (item == "Android") { + if (item === "Android") { void vscode.window.showInformationMessage("Downloading Expo Go for Android."); logger.logStream( localize("DownloadAndroidExpoGo", "\nDownloading Expo Go for Android. \n"), @@ -70,7 +70,7 @@ export class InstallExpoGoApplication extends Command { } } - if (installItem == "Auto") { + if (installItem === "Auto") { try { await installAndroidApplication(this.project, fileName); } catch { @@ -86,8 +86,8 @@ export class InstallExpoGoApplication extends Command { ), ); } - } else if (item == "iOS") { - if (os.platform() != "darwin") { + } else if (item === "iOS") { + if (os.platform() !== "darwin") { logger.warning( localize( "NotDarwinPlatform", @@ -122,7 +122,7 @@ export class InstallExpoGoApplication extends Command { } } - if (installItem == "Auto" && os.platform() == "darwin") { + if (installItem === "Auto" && os.platform() === "darwin") { try { await installiOSApplication(this.project, tarFile); } catch { diff --git a/src/extension/commands/launchExpoWeb.ts b/src/extension/commands/launchExpoWeb.ts index 4e78737ef..cb86ecf85 100644 --- a/src/extension/commands/launchExpoWeb.ts +++ b/src/extension/commands/launchExpoWeb.ts @@ -20,7 +20,7 @@ export class launchExpoWeb extends Command { label = "Launch ExpoWeb"; error = ErrorHelper.getInternalError(InternalErrorCode.FailedToLaunchExpoWeb); - async baseFn(launchArgs: any): Promise { + async baseFn(): Promise { assert(this.project); const expoHelper = this.project.getExponentHelper(); logger.info(localize("CheckExpoEnvironment", "Checking Expo project environment.")); diff --git a/src/extension/commands/openEASProject.ts b/src/extension/commands/openEASProject.ts index 268115c21..aa5309097 100644 --- a/src/extension/commands/openEASProject.ts +++ b/src/extension/commands/openEASProject.ts @@ -42,14 +42,14 @@ export class OpenEASProject extends ReactNativeCommand { await expoHelper.getExpoEasProjectName().then(result => { name = result; }); - if (id == null || owner == null) { + if (id === null || owner === null) { const error = localize( "ExpoProjectNotLinkToEAS", "Your app not link to EAS project. Please run 'eas init' firstly to bind your app to EAS project.", ); void vscode.window.showErrorMessage(error); logger.error(error); - } else if (name != null) { + } else if (name !== null) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const url = `https://expo.dev/accounts/${owner}/projects/${name}`; await vscode.env.openExternal(vscode.Uri.parse(url)); diff --git a/src/extension/commands/revertOpenModule.ts b/src/extension/commands/revertOpenModule.ts index f417acf2c..75acade5c 100644 --- a/src/extension/commands/revertOpenModule.ts +++ b/src/extension/commands/revertOpenModule.ts @@ -78,7 +78,7 @@ export class RevertOpenModule extends ReactNativeCommand { const mainFilePath = path.resolve(openModulePath, "open-main.js"); if (fs.existsSync(mainFilePath)) { try { - await fs.unlinkSync(mainFilePath); + fs.unlinkSync(mainFilePath); } catch { logger.error( localize("FailedToDeleteMainFile", "Failed to delete open-main.js file."), @@ -95,10 +95,10 @@ export class RevertOpenModule extends ReactNativeCommand { const packageFilePath = path.resolve(openModulePath, "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageFilePath, "utf-8")); - if (packageJson.main == "open-main.js") { + if (packageJson.main === "open-main.js") { try { delete packageJson.main; - await fs.writeFileSync( + fs.writeFileSync( packageFilePath, JSON.stringify(>packageJson), ); diff --git a/src/extension/commands/runEasBuild.ts b/src/extension/commands/runEasBuild.ts index 7481462ea..818f110b3 100644 --- a/src/extension/commands/runEasBuild.ts +++ b/src/extension/commands/runEasBuild.ts @@ -15,8 +15,8 @@ export class runEasBuild extends ReactNativeCommand { error = ErrorHelper.getInternalError(InternalErrorCode.FailedToRunEasBuild); async baseFn(): Promise { assert(this.project); - const packager = await this.project.getPackager(); - const projectRootPath = await packager.getProjectPath(); + const packager = this.project.getPackager(); + const projectRootPath = packager.getProjectPath(); if (!projectRootPath) { void vscode.window.showErrorMessage( diff --git a/src/extension/commands/stopPackager.ts b/src/extension/commands/stopPackager.ts index 096d885a5..af05442b2 100644 --- a/src/extension/commands/stopPackager.ts +++ b/src/extension/commands/stopPackager.ts @@ -17,11 +17,8 @@ export class StopPackager extends ReactNativeCommand<[AppLauncher]> { async onBeforeExecute(appLauncher: AppLauncher): Promise { await super.onBeforeExecute(appLauncher); - if (appLauncher instanceof AppLauncher) { - this.project = appLauncher; - } else { - this.project = await this.selectProject(); - } + this.project = + appLauncher instanceof AppLauncher ? appLauncher : await this.selectProject(); } // this function requires argument because we need it in extension 'deactivate' hook diff --git a/src/extension/commands/testDevEnvironment.ts b/src/extension/commands/testDevEnvironment.ts index a4eae2a31..77b85003b 100644 --- a/src/extension/commands/testDevEnvironment.ts +++ b/src/extension/commands/testDevEnvironment.ts @@ -64,13 +64,11 @@ export class TestDevEnvironment extends Command { [ValidationCategoryE.macOS]: (projectObserver && projectObserver.isRNMacosProject) || false, } as const; - if (project && projectObserver) { - await runChecks( - shouldCheck, - RNPackageVersionsToPackageVersion(projectObserver.rnPackageVersions), - ); - } else { - await runChecks(shouldCheck); - } + await (project && projectObserver + ? runChecks( + shouldCheck, + RNPackageVersionsToPackageVersion(projectObserver.rnPackageVersions), + ) + : runChecks(shouldCheck)); } } diff --git a/src/extension/commands/util/index.ts b/src/extension/commands/util/index.ts index 8d413ceba..f126e1de1 100644 --- a/src/extension/commands/util/index.ts +++ b/src/extension/commands/util/index.ts @@ -112,7 +112,10 @@ export async function selectProject(): Promise { return ProjectsStorage.projectsCache[selected]; } -export async function sendMessageToMetro(method: "devMenu" | "reload", project: AppLauncher) { +export async function sendMessageToMetro( + method: "devMenu" | "reload", + project: AppLauncher, +): Promise { const port = SettingsHelper.getPackagerPort(project.getWorkspaceFolderUri().fsPath); const ws = new WebSocket(`ws://localhost:${port}/message`); await new Promise(resolve => { diff --git a/src/extension/debuggingConfiguration/multiStepInput.ts b/src/extension/debuggingConfiguration/multiStepInput.ts index d4f48a48a..418a0f267 100644 --- a/src/extension/debuggingConfiguration/multiStepInput.ts +++ b/src/extension/debuggingConfiguration/multiStepInput.ts @@ -112,11 +112,7 @@ export class MultiStepInput implements IMultiStepInput { input.items = items; input.matchOnDescription = matchOnDescription || false; input.matchOnDetail = matchOnDetail || false; - if (activeItem) { - input.activeItems = [activeItem]; - } else { - input.activeItems = []; - } + input.activeItems = activeItem ? [activeItem] : []; input.buttons = [ ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || []), @@ -242,6 +238,7 @@ export class MultiStepInput implements IMultiStepInput { this.current.busy = true; } try { + // eslint-disable-next-line no-await-in-loop -- each step depends on the previous step's result step = await step(this, state); } catch (err) { if (err === InputFlowAction.back) { diff --git a/src/extension/exponent/exponentHelper.ts b/src/extension/exponent/exponentHelper.ts index 024bbc314..75dfeeb4d 100644 --- a/src/extension/exponent/exponentHelper.ts +++ b/src/extension/exponent/exponentHelper.ts @@ -312,9 +312,10 @@ export class ExponentHelper { this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)), this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)), ]); - return expo - ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) - : ios + if (expo) { + return this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX); + } + return ios ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) : this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX); } diff --git a/src/extension/exponent/exponentPlatform.ts b/src/extension/exponent/exponentPlatform.ts index b6297b4e4..b85c80176 100644 --- a/src/extension/exponent/exponentPlatform.ts +++ b/src/extension/exponent/exponentPlatform.ts @@ -57,11 +57,9 @@ export class ExponentPlatform extends GeneralPlatform { // https://github.com/expo/expo-cli/blob/1d515d21200841e181518358fd9dc4c7b24c7cd6/packages/xdl/src/Project.ts#L2226-L2370 // we added this to be sure that our Expo launching logic doesn't have any negative side effects - if (this.runOptions.expoHostType === "tunnel") { - await this.prepareExpoTunnels(); - } else { - await XDL.stopAdbReverse(this.projectPath); - } + await (this.runOptions.expoHostType === "tunnel" + ? this.prepareExpoTunnels() + : XDL.stopAdbReverse(this.projectPath)); const isAdbReversed = this.runOptions.expoHostType !== "local" @@ -207,6 +205,6 @@ export class ExponentPlatform extends GeneralPlatform { } } -export function getQRCodeUrl() { +export function getQRCodeUrl(): string { return QRCodeUrl; } diff --git a/src/extension/generalMobilePlatform.ts b/src/extension/generalMobilePlatform.ts index c33f08671..ea4a311c2 100644 --- a/src/extension/generalMobilePlatform.ts +++ b/src/extension/generalMobilePlatform.ts @@ -82,15 +82,13 @@ export abstract class GeneralMobilePlatform extends GeneralPlatform { ), ); cleanupTargetModifications(); - } else { + } else if (this instanceof IOSPlatform || (await this.needToPassTargetToRunArgs())) { // For iOS we should pass exact target id, // because the “react-native run-ios” command does not check booted devices // and just launches the first device - if (this instanceof IOSPlatform || (await this.needToPassTargetToRunArgs())) { - this.addTargetToRunArgs(this.target); - } else { - cleanupTargetModifications(); - } + this.addTargetToRunArgs(this.target); + } else { + cleanupTargetModifications(); } } catch (error) { if ( diff --git a/src/extension/generalPlatform.ts b/src/extension/generalPlatform.ts index 5aa010d98..66164bae2 100644 --- a/src/extension/generalPlatform.ts +++ b/src/extension/generalPlatform.ts @@ -214,7 +214,7 @@ export class GeneralPlatform { if (envFile) { // .env variables never overwrite existing variables const argsFromEnvFile = this.readEnvFile(envFile); - if (argsFromEnvFile != null) { + if (argsFromEnvFile !== null && argsFromEnvFile !== undefined) { // eslint-disable-next-line no-restricted-syntax for (const key in argsFromEnvFile) { if (argsFromEnvFile.hasOwnProperty(key) && !modifyEnv.hasOwnProperty(key)) { @@ -247,7 +247,7 @@ export class GeneralPlatform { } buffer.split("\n").forEach((line: string) => { - const r = line.match(/^\s*([\w.\-]+)\s*=\s*(.*)?\s*$/); + const r = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/); if (r !== null) { const key = r[1]; let value = r[2] || ""; diff --git a/src/extension/ios/iOSTargetManager.ts b/src/extension/ios/iOSTargetManager.ts index 6408c9bf8..f047dd4da 100644 --- a/src/extension/ios/iOSTargetManager.ts +++ b/src/extension/ios/iOSTargetManager.ts @@ -198,7 +198,7 @@ export class IOSTargetManager extends MobileTargetManager { filter as ((el: IMobileTarget) => boolean) | undefined, )) as IDebuggableIOSTarget[]; // If we select only from devices, we should not select system - if (!targets.find(target => target.isVirtualTarget)) { + if (!targets.some(target => target.isVirtualTarget)) { return IOSTargetManager.ANY_SYSTEM; } const names: Set = new Set(targets.map(target => target.system)); diff --git a/src/extension/ios/plistBuddy.ts b/src/extension/ios/plistBuddy.ts index 172400af1..c92ded9b6 100644 --- a/src/extension/ios/plistBuddy.ts +++ b/src/extension/ios/plistBuddy.ts @@ -69,8 +69,9 @@ export class PlistBuddy { simulator: boolean = true, configuration: string = "Debug", productName?: string, - scheme?: string, + initialScheme?: string, ): Promise { + let scheme = initialScheme; const rnVersions = await ProjectVersionHelper.getReactNativeVersions(projectRoot); let productsFolder; if ( @@ -251,10 +252,11 @@ export class PlistBuddy { const nodeModulesRoot: string = AppLauncher.getNodeModulesRootByProjectPath(projectRoot); + const applePlatform = semver.gte(rnVersion, PlistBuddy.RN_VERSION_CLI_CONFIG_APPLE) + ? "cli-config-apple" + : "cli-platform-apple"; const iOSCliPlatform = semver.gte(rnVersion, PlistBuddy.RN_VERSION_CLI_PLATFORM_APPLE) - ? semver.gte(rnVersion, PlistBuddy.RN_VERSION_CLI_CONFIG_APPLE) - ? "cli-config-apple" - : "cli-platform-apple" + ? applePlatform : "cli-platform-ios"; const iOSCliFolderName = semver.gte(rnVersion, PlistBuddy.NEW_RN_IOS_CLI_LOCATION_VERSION) || @@ -270,7 +272,7 @@ export class PlistBuddy { fs.existsSync(pnpmProjectPath) && SettingsHelper.getPackageManager() === "pnpm"; if (isPnpmProject) { const modules = fs.readdirSync(pnpmProjectPath); - const regex = new RegExp(`\@react-native-community\\+${iOSCliFolderName}@`); + const regex = new RegExp(`@react-native-community\\+${iOSCliFolderName}@`); const communityModule = modules.find(module => regex.test(module)); if (communityModule) { findXcodeBase = path.join( diff --git a/src/extension/log/ConsoleLogger.ts b/src/extension/log/ConsoleLogger.ts index 9ffc01e9b..7455f924e 100644 --- a/src/extension/log/ConsoleLogger.ts +++ b/src/extension/log/ConsoleLogger.ts @@ -4,7 +4,8 @@ import { ILogger, LogLevel, LogHelper } from "./LogHelper"; export class ConsoleLogger implements ILogger { - public log(message: string, level: LogLevel): void { + public log(initialMessage: string, level: LogLevel): void { + let message = initialMessage; if (LogHelper.LOG_LEVEL === LogLevel.None) { return; } diff --git a/src/extension/log/OutputChannelLogger.ts b/src/extension/log/OutputChannelLogger.ts index dc0a1efde..5ed104bd5 100644 --- a/src/extension/log/OutputChannelLogger.ts +++ b/src/extension/log/OutputChannelLogger.ts @@ -78,7 +78,8 @@ export class OutputChannelLogger implements ILogger { } } - public log(message: string, level: LogLevel): void { + public log(initialMessage: string, level: LogLevel): void { + let message = initialMessage; if (LogHelper.LOG_LEVEL === LogLevel.None) { return; } @@ -96,7 +97,8 @@ export class OutputChannelLogger implements ILogger { } } - public logWithCustomTag(tag: string, message: string, level: LogLevel): void { + public logWithCustomTag(tag: string, initialMessage: string, level: LogLevel): void { + let message = initialMessage; if (LogHelper.LOG_LEVEL === LogLevel.None) { return; } diff --git a/src/extension/networkInspector/views/inspectorConsoleView.ts b/src/extension/networkInspector/views/inspectorConsoleView.ts index 5f4aa23ed..da41f0d2e 100644 --- a/src/extension/networkInspector/views/inspectorConsoleView.ts +++ b/src/extension/networkInspector/views/inspectorConsoleView.ts @@ -74,11 +74,10 @@ export class InspectorConsoleView extends InspectorView { } private setupConsoleLogsColor(systemColorTheme: SystemColorTheme): void { - if (systemColorTheme === SystemColorTheme.Light) { - this.consoleLogsColor = this.consoleLogsColors.Blue; - } else { - this.consoleLogsColor = this.consoleLogsColors.Orange; - } + this.consoleLogsColor = + systemColorTheme === SystemColorTheme.Light + ? this.consoleLogsColors.Blue + : this.consoleLogsColors.Orange; } private handleRequest(data: Request): void { @@ -169,13 +168,13 @@ export class InspectorConsoleView extends InspectorView { const response: Response = partialResponseEntry.initialResponse; const allChunks: string[] = - response.data != null + response.data !== null && response.data !== undefined ? [ response.data, ...Object.entries(partialResponseEntry.followupChunks) // It's important to parseInt here or it sorts lexicographically .sort((a, b) => parseInt(a[0], 10) - parseInt(b[0], 10)) - .map(([_k, v]: [string, string]) => v), + .map(([, v]: [string, string]) => v), ] : []; const data = combineBase64Chunks(allChunks); @@ -236,10 +235,11 @@ export class InspectorConsoleView extends InspectorView { } private prepareHeadersViewObject(headers: Header[]): Record { - return headers.reduce((headersViewObject, header) => { + const headersViewObject: Record = {}; + for (const header of headers) { headersViewObject[header.key] = header.value; - return headersViewObject; - }, {} as Record); + } + return headersViewObject; } private printNetworkRequestData(networkRequestData: ConsoleNetworkRequestDataView): void { diff --git a/src/extension/packagerStatusIndicator.ts b/src/extension/packagerStatusIndicator.ts index fad07cae1..74cb2b8d7 100644 --- a/src/extension/packagerStatusIndicator.ts +++ b/src/extension/packagerStatusIndicator.ts @@ -113,10 +113,11 @@ export class PackagerStatusIndicator implements Disposable { } private setupPackagerStatusIndicatorItems( - icon: string, + initialIcon: string, command?: string, tooltip: string = "", ): void { + let icon = initialIcon; this.updateDisplayVersion(); this.togglePackagerItem.command = command; this.togglePackagerItem.tooltip = this.getTooltip(tooltip); diff --git a/src/extension/reactNativeSessionManager.ts b/src/extension/reactNativeSessionManager.ts index 3bb81dbd2..9865ca89f 100644 --- a/src/extension/reactNativeSessionManager.ts +++ b/src/extension/reactNativeSessionManager.ts @@ -23,27 +23,25 @@ export class ReactNativeSessionManager ): vscode.ProviderResult { const rnSession = new RNSession(session); - let debugServer: Net.Server; - if (session.configuration.platform != "expoweb") { - debugServer = Net.createServer(socket => { - const rnDebugSession = - session.type === DEBUG_TYPES.REACT_NATIVE - ? new RNDebugSession(rnSession) - : new DirectDebugSession(rnSession); + const debugServer: Net.Server = + session.configuration.platform !== "expoweb" + ? Net.createServer(socket => { + const rnDebugSession = + session.type === DEBUG_TYPES.REACT_NATIVE + ? new RNDebugSession(rnSession) + : new DirectDebugSession(rnSession); - this.connections.set(session.id, socket); + this.connections.set(session.id, socket); - rnDebugSession.setRunAsServer(true); - rnDebugSession.start(socket, socket); - }); - } else { - debugServer = Net.createServer(socket => { - const webDebugSession = new WebDebugSession(rnSession); - webDebugSession.setRunAsServer(true); - this.connections.set(session.id, socket); - webDebugSession.start(socket, socket); - }); - } + rnDebugSession.setRunAsServer(true); + rnDebugSession.start(socket, socket); + }) + : Net.createServer(socket => { + const webDebugSession = new WebDebugSession(rnSession); + webDebugSession.setRunAsServer(true); + this.connections.set(session.id, socket); + webDebugSession.start(socket, socket); + }); return new Promise((resolve, reject) => { debugServer.once("error", reject); diff --git a/src/extension/rn-extension.ts b/src/extension/rn-extension.ts index 50f7a330b..86593f068 100644 --- a/src/extension/rn-extension.ts +++ b/src/extension/rn-extension.ts @@ -356,9 +356,9 @@ export async function onFolderAdded(folder: vscode.WorkspaceFolder): Promise val.original !== val.resolved, + ([, val]) => val.original !== val.resolved, )?.[0]; if (valUsesEnv) { diff --git a/src/extension/services/validationService/checks/visualStudio.ts b/src/extension/services/validationService/checks/visualStudio.ts index ed6521689..6d0f51510 100644 --- a/src/extension/services/validationService/checks/visualStudio.ts +++ b/src/extension/services/validationService/checks/visualStudio.ts @@ -15,12 +15,9 @@ const toLocale = nls.loadMessageBundle(); const label = "Compilers, build tools, SDKs and Visual Studio"; async function test(): Promise { - let vswherePath = ``; - if (process.env["ProgramFiles(x86)"]) { - vswherePath = `"${process.env["ProgramFiles(x86)"]}\\Microsoft Visual Studio\\Installer\\vswhere.exe"`; - } else { - vswherePath = `"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"`; - } + const vswherePath = process.env["ProgramFiles(x86)"] + ? `"${process.env["ProgramFiles(x86)"]}\\Microsoft Visual Studio\\Installer\\vswhere.exe"` + : `"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"`; const components = [ "Microsoft.Component.MSBuild", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", @@ -35,6 +32,7 @@ async function test(): Promise { const valid = (version: string) => semver.gtr(version, "16.5"); if (versions.some(valid)) { for (const comp of components) { + // eslint-disable-next-line no-await-in-loop -- returns early on the first missing component const pathToComponent = await executeCommand( `${vswherePath} -requires ${comp} -property productPath`, ); diff --git a/src/extension/services/validationService/util.ts b/src/extension/services/validationService/util.ts index 6030eb40e..01509e66e 100644 --- a/src/extension/services/validationService/util.ts +++ b/src/extension/services/validationService/util.ts @@ -47,11 +47,8 @@ export const basicCheck = async (arg: { return result; } - result.versionCompare = semver.gtr(version, arg.versionRange) - ? 1 - : semver.ltr(version, arg.versionRange) - ? -1 - : 0; + const lowerCompare = semver.ltr(version, arg.versionRange) ? -1 : 0; + result.versionCompare = semver.gtr(version, arg.versionRange) ? 1 : lowerCompare; return result; }; @@ -73,10 +70,12 @@ export const parseVersion = async ( // change typescript lib to es2019 ? export const fromEntries = ( entries: Iterable, -): Record => - [...entries].reduce((obj, [key, val]) => { +): Record => { + const obj = {} as Record; + for (const [key, val] of entries) { obj[key] = val; - return obj; - }, {} as Record); + } + return obj; +}; // export const flatten = (ary: any[]): unknown[] => diff --git a/src/extension/settingsHelper.ts b/src/extension/settingsHelper.ts index cc09cc56d..4aca8ce49 100644 --- a/src/extension/settingsHelper.ts +++ b/src/extension/settingsHelper.ts @@ -229,7 +229,7 @@ export class SettingsHelper { return []; } - public static getWorkspaceTelemetry(fsPath: string) { + public static getWorkspaceTelemetry(fsPath: string): any { if (!fsPath || !fs.existsSync(fsPath)) { return ""; } diff --git a/test/extension/commands/launchExpoWeb.test.ts b/test/extension/commands/launchExpoWeb.test.ts index 0af6d0eb0..401481fcd 100644 --- a/test/extension/commands/launchExpoWeb.test.ts +++ b/test/extension/commands/launchExpoWeb.test.ts @@ -83,7 +83,7 @@ suite("launchExpoWebCommand", function () { const command = launchExpoWeb.formInstance(); (command as any).project = project; - await command.baseFn({}); + await command.baseFn(); assert.strictEqual(isExpoManagedAppStub.calledWithExactly(true), true); assert.strictEqual(getRunOptionsStub.called, false); @@ -107,7 +107,7 @@ suite("launchExpoWebCommand", function () { const command = launchExpoWeb.formInstance(); (command as any).project = project; - await command.baseFn({}); + await command.baseFn(); assert.strictEqual(isExpoManagedAppStub.calledWithExactly(true), true); assert.strictEqual( From 37e873858dcf686c11ef1083680881ac8422d6a6 Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:37:43 +0200 Subject: [PATCH 07/13] chore: release 1.14.20261006 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46a3540a9..b53f13046 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-react-native", - "version": "1.14.20260929", + "version": "1.14.20261006", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-react-native", - "version": "1.14.20260929", + "version": "1.14.20261006", "hasInstallScript": true, "license": "%reactNative.license%", "dependencies": { diff --git a/package.json b/package.json index 16ce62642..70c5c1912 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-react-native", "displayName": "React Native Tools (patched)", - "version": "1.14.20260929", + "version": "1.14.20261006", "private": true, "publisher": "msjsdiag", "icon": "resources/images/icon.png", From b4b82826f4a0810616368463b9721a5fa45b1798 Mon Sep 17 00:00:00 2001 From: Matteo Boetti <71014493+teoboetti@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:05:12 +0200 Subject: [PATCH 08/13] chore(ci): add weekly upstream sync workflow (#7) --- .github/workflows/sync-upstream.yml | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 000000000..150da808c --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,66 @@ +name: Sync upstream + +on: + schedule: + - cron: "0 6 * * 1" # Mondays 06:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + sync: + runs-on: ubuntu-latest + env: + # PAT (repo + workflow scopes): GITHUB_TOKEN can't push upstream workflow changes and its PRs don't trigger CI + GH_TOKEN: ${{ secrets.SYNC_PAT }} + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + token: ${{ secrets.SYNC_PAT }} + + - uses: actions/setup-node@v7 + with: + node-version: "24" + cache: npm + + - name: Merge upstream + id: merge + run: | + # an open sync PR may hold manual fixes; don't overwrite it + gh pr list --head upstream-sync --state open --json number -q '.[].number' | grep -q . && exit 0 + git config user.name "upstream-sync" + git config user.email "upstream-sync@users.noreply.github.com" + git fetch https://github.com/microsoft/vscode-react-native.git master + [ "$(git rev-list --count HEAD..FETCH_HEAD)" = 0 ] && exit 0 + git checkout -b upstream-sync + if git merge --no-edit FETCH_HEAD; then + echo "result=clean" >> "$GITHUB_OUTPUT" + else + echo "files=$(git diff --name-only --diff-filter=U | tr '\n' ' ')" >> "$GITHUB_OUTPUT" + echo "result=conflict" >> "$GITHUB_OUTPUT" + fi + + - name: Resolve conflicts with Claude + if: steps.merge.outputs.result == 'conflict' + timeout-minutes: 30 # hard cap; the default is 6 hours + run: npx -y @anthropic-ai/claude-code -p "$PROMPT" --allowedTools "Bash,Read,Edit,Write" --max-turns 40 --model sonnet + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # from `claude setup-token` + PROMPT: | + A merge of microsoft/vscode-react-native master into this fork stopped on conflicts in: + ${{ steps.merge.outputs.files }} + Resolve them: take upstream's changes but keep this fork's patches (see `git log` for fork-only commits). + Then run `npm ci`, `npm run lint`, `npx gulp prod-build` and `npm run build`, and fix anything the merge broke. + Finish with `git add -A` and `git commit --no-edit`. Do not push. + + - name: Open PR + if: steps.merge.outputs.result != '' + run: | + if git grep -n '^<<<<<<< '; then exit 1; fi # leftover conflict markers: fail, no PR + git push -f origin upstream-sync + gh pr create --head upstream-sync --base master \ + --title "Sync upstream $(date +%F)" \ + --body "Weekly upstream merge. Files resolved by Claude: ${{ steps.merge.outputs.files || 'none' }}" From dfcd2e436902b8691a1e349a9c0bc6c56c3a84d6 Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 12 Oct 2026 07:42:54 +0200 Subject: [PATCH 09/13] chore: release 1.14.20261012 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 601eb0d92..75c1ad808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-react-native", - "version": "1.14.20261006", + "version": "1.14.20261012", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-react-native", - "version": "1.14.20261006", + "version": "1.14.20261012", "hasInstallScript": true, "license": "%reactNative.license%", "dependencies": { diff --git a/package.json b/package.json index cfcbd2a0a..4ca44c5eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-react-native", "displayName": "React Native Tools (patched)", - "version": "1.14.20261006", + "version": "1.14.20261012", "private": true, "publisher": "msjsdiag", "icon": "resources/images/icon.png", From be0ba9b5187339b8249fedd84f4aaf54fa644c69 Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 12 Oct 2026 07:55:41 +0200 Subject: [PATCH 10/13] chore: add release skill for cutting and installing patched builds --- .claude/skills/release/SKILL.md | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .claude/skills/release/SKILL.md diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 000000000..3d680d606 --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,84 @@ +--- +name: release +description: Use when asked to deploy, release, publish, cut or tag a new version of this patched vscode-react-native fork (versions like v1.14.20261012), or to install the latest patched VSIX into the local editor. +argument-hint: "[version] [install] | install" +--- + +# Release the patched fork + +## Mode + +Decide the mode from the request: +- **Install only.** The request asks to install and doesn't mention deploy/release/publish. Set `V=$(gh release view --repo teoboetti/vscode-react-native --json tagName -q .tagName | sed 's/^v//')`, then do step 5 only. +- **Release.** Do steps 1-4. Also do step 5 if the request mentions install. +- **Version.** If the request names one (e.g. `1.14.20261020`), use it without the `v`. Otherwise use `..$(date +%Y%m%d)`, taking major.minor from `package.json`. If that version is already released, STOP and ask. Never add a suffix to the date: `1.14.2026101201` sorts above every later date and blocks future releases. + +## How this works + +A release is a `chore: release X` commit that bumps the version in `package.json` and `package-lock.json`, plus a `vX` tag **on that commit**. Pushing the tag runs `.github/workflows/build-vsix.yml`, which builds the VSIX and creates the GitHub release with it attached. The VSIX version comes from `package.json`, not from the tag. + +**Shell variables don't persist between Bash calls.** Start every call with: +```bash +cd "$(git rev-parse --show-toplevel)"; R=teoboetti/vscode-react-native; V= +``` +Pass `--repo $R` to every `gh` call. The gh default repo here is upstream `microsoft/vscode-react-native`. + +## Steps + +**1. Preflight.** If any check fails, STOP and report it: +```bash +git fetch origin --quiet +git status --porcelain --untracked-files=no # must print nothing +git rev-parse --abbrev-ref HEAD # must be master +git rev-parse master origin/master # both lines must match +gh run list --repo $R --workflow ci.yml --commit "$(git rev-parse HEAD)" --limit 1 # must be completed + success +node -p 'require("./package.json").version' # must be lower than $V +``` + +**2. Tag check.** +```bash +git ls-remote --tags origin "v$V" # prints anything → already released: STOP and report +git tag -l "v$V" # prints the tag → it's a stale local tag: git tag -d "v$V" +``` + +**3. Bump, commit, tag.** +```bash +OLD=$(node -p 'require("./package.json").version') +sed -i '' "s/\"version\": \"$OLD\"/\"version\": \"$V\"/" package.json package-lock.json +grep -c "\"version\": \"$V\"" package.json package-lock.json # must be package.json:1, package-lock.json:2 +git commit -am "chore: release $V" # a pre-commit lint hook runs +git tag "v$V" +``` +The commit gets no attribution trailers. If anything in steps 3-4 fails **before the push**, undo it with `git tag -d "v$V"; git reset --hard origin/master`, then STOP and report. This is safe because preflight confirmed there were no tracked changes. + +**4. Push and verify.** Use a 10-minute Bash timeout. +```bash +git push --atomic origin master "v$V" # both refs land, or neither does +for i in $(seq 12); do id=$(gh run list --repo $R --workflow build-vsix.yml --branch "v$V" --commit "$(git rev-parse HEAD)" --limit 1 --json databaseId -q '.[0].databaseId'); [ -n "$id" ] && break; sleep 5; done +gh run watch "${id:?no build-vsix run found for v$V}" --repo $R --exit-status --interval 15 +gh release view "v$V" --repo $R --json url,assets -q '.url, .assets[].name' # must list vscode-react-native-$V.vsix +``` +If the build fails after the push, don't delete the pushed tag. Report the run URL; rerunning it is the user's call. + +**5. Install.** The patched build lives in **VSCodium** (`codium`). VS Code (`code`) runs the Microsoft marketplace version, so don't touch it unless the request names it (then use `code` wherever this step says `codium`). +```bash +codium --list-extensions --show-versions | grep -x "msjsdiag.vscode-react-native@$V" # prints → already installed: skip the next two commands +D=$(mktemp -d) && gh release download "v$V" --repo $R --dir "$D" && codium --install-extension "$D/vscode-react-native-$V.vsix" --force +codium --list-extensions --show-versions | grep -x "msjsdiag.vscode-react-native@$V" # must print +``` + +## Report + +Report in a few lines. On success, give the version, the commit sha, the release URL, the VSIX asset name, and the installed version if you installed. After an install, remind the user to run **Developer: Reload Window** in VSCodium. On a STOP, give the failing command, its output, and anything you undid. + +## Common mistakes + +| Mistake | Result | +|---|---| +| Tagging before bumping `package.json` | VSIX and release carry the previous version | +| Bumping only `package.json` | `npm ci` in the workflow fails on the lockfile mismatch | +| Using `npm version` to bump | Can rewrite the v2 lockfile | +| Bare `gh` without `--repo $R` | Queries the upstream Microsoft repo | +| Picking the build run without `--branch` + `--commit` | Can watch an old run from an earlier push of the same tag | +| Pushing `master` and the tag separately | One can land without the other, and the undo step then deletes the only copy of the tag | +| Installing a local `npx vsce package` build | Installed build differs from the published release | From 89452f4530758398c0e4cd0b4cb2076d195122c3 Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:25:06 +0200 Subject: [PATCH 11/13] fix: resolve lint warnings in reactNativeSessionManager --- src/extension/reactNativeSessionManager.ts | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/extension/reactNativeSessionManager.ts b/src/extension/reactNativeSessionManager.ts index 3bb81dbd2..404fbad3c 100644 --- a/src/extension/reactNativeSessionManager.ts +++ b/src/extension/reactNativeSessionManager.ts @@ -23,27 +23,25 @@ export class ReactNativeSessionManager ): vscode.ProviderResult { const rnSession = new RNSession(session); - let debugServer: Net.Server; - if (session.configuration.platform != "expoweb") { - debugServer = Net.createServer(socket => { - const rnDebugSession = - session.type === DEBUG_TYPES.REACT_NATIVE - ? new RNDebugSession(rnSession) - : new DirectDebugSession(rnSession); + const debugServer = + session.configuration.platform !== "expoweb" + ? Net.createServer(socket => { + const rnDebugSession = + session.type === DEBUG_TYPES.REACT_NATIVE + ? new RNDebugSession(rnSession) + : new DirectDebugSession(rnSession); - this.connections.set(session.id, socket); + this.connections.set(session.id, socket); - rnDebugSession.setRunAsServer(true); - rnDebugSession.start(socket, socket); - }); - } else { - debugServer = Net.createServer(socket => { - const webDebugSession = new WebDebugSession(rnSession); - webDebugSession.setRunAsServer(true); - this.connections.set(session.id, socket); - webDebugSession.start(socket, socket); - }); - } + rnDebugSession.setRunAsServer(true); + rnDebugSession.start(socket, socket); + }) + : Net.createServer(socket => { + const webDebugSession = new WebDebugSession(rnSession); + webDebugSession.setRunAsServer(true); + this.connections.set(session.id, socket); + webDebugSession.start(socket, socket); + }); return new Promise((resolve, reject) => { debugServer.once("error", reject); From 21bb3944acbee7e363951f118832cbd3b250a24d Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:28:53 +0200 Subject: [PATCH 12/13] chore: release 1.14.20261019 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75c1ad808..223f6b48e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-react-native", - "version": "1.14.20261012", + "version": "1.14.20261019", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-react-native", - "version": "1.14.20261012", + "version": "1.14.20261019", "hasInstallScript": true, "license": "%reactNative.license%", "dependencies": { diff --git a/package.json b/package.json index 4ca44c5eb..6f5fafaaa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-react-native", "displayName": "React Native Tools (patched)", - "version": "1.14.20261012", + "version": "1.14.20261019", "private": true, "publisher": "msjsdiag", "icon": "resources/images/icon.png", From 0c816dede2288b31311b6434c8a1851ea686d32f Mon Sep 17 00:00:00 2001 From: teoboetti <71014493+teoboetti@users.noreply.github.com> Date: Mon, 19 Oct 2026 16:30:01 +0200 Subject: [PATCH 13/13] feat: add Flutter-style device selector Status bar item (right side) shows the selected device and opens a picker listing running devices and offline emulators/simulators. Starting an offline Android emulator (normal or cold boot) or iOS simulator selects it once it connects. The selection is saved per workspace and fills `target` for Android/iOS launch configs that leave it open (empty, "simulator" or "device"); an explicit target still wins. - add DeviceSelector and the reactNative.selectDevice command - make Android/iOS launchSimulator public; add Android cold boot flag - open DeviceHub (Xcode 27+) or Simulator.app after booting a simulator - remove DeviceStatusIndicator, replaced by the new status bar item --- package.json | 6 + package.nls.json | 1 + src/common/error/errorStrings.ts | 1 + src/common/error/internalErrorCode.ts | 1 + src/debugger/debugSessionBase.ts | 3 - src/extension/android/androidTargetManager.ts | 11 +- src/extension/appLauncher.ts | 10 - src/extension/commands/index.ts | 1 + src/extension/commands/selectDevice.ts | 18 + src/extension/deviceSelector.ts | 573 ++++++++++++++++++ src/extension/deviceStatusIndicator.ts | 41 -- src/extension/ios/iOSTargetManager.ts | 2 +- src/extension/rn-extension.ts | 15 + test/extension/deviceSelector.test.ts | 303 +++++++++ test/extension/deviceStatusIndicator.test.ts | 76 --- test/extension/rn-extension.test.ts | 1 + 16 files changed, 930 insertions(+), 133 deletions(-) create mode 100644 src/extension/commands/selectDevice.ts create mode 100644 src/extension/deviceSelector.ts delete mode 100644 src/extension/deviceStatusIndicator.ts create mode 100644 test/extension/deviceSelector.test.ts delete mode 100644 test/extension/deviceStatusIndicator.test.ts diff --git a/package.json b/package.json index 6f5fafaaa..6fa382fd9 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,12 @@ "category": "React Native", "enablement": "!config.security.workspace.trust.enabled || isWorkspaceTrusted" }, + { + "command": "reactNative.selectDevice", + "title": "%reactNative.command.selectDevice.title%", + "category": "React Native", + "enablement": "!config.security.workspace.trust.enabled || isWorkspaceTrusted" + }, { "command": "reactNative.launchExpoWeb", "title": "%reactNative.command.launchExpoWeb.title%", diff --git a/package.nls.json b/package.nls.json index 6b4043408..94fec838c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -5,6 +5,7 @@ "reactNative.virtualWorkspaces.description": "Debugging is not possible in virtual workspaces.", "reactNative.command.launchAndroidSimulator.title": "Launch Android Emulator", "reactNative.command.launchIOSSimulator.title": "Launch iOS Simulator", + "reactNative.command.selectDevice.title": "Select Device", "reactNative.command.launchExpoWeb.title": "Expo - Launch ExpoWeb", "reactNative.command.selectAndInsertDebugConfiguration.title": "Select a React Native debug configuration", "reactNative.command.runAndroidSimulator.title": "Run Android on Emulator", diff --git a/src/common/error/errorStrings.ts b/src/common/error/errorStrings.ts index 9d443bec1..ec3136fb5 100644 --- a/src/common/error/errorStrings.ts +++ b/src/common/error/errorStrings.ts @@ -423,4 +423,5 @@ export const ERROR_STRINGS = { [InternalErrorCode.FailedToToggleNetworkView]: "Failed to config network view", [InternalErrorCode.FailedToRunEasBuild]: "Failed to run eas build", [InternalErrorCode.FailedToInstallPods]: "Failed to install pods", + [InternalErrorCode.FailedToSelectDevice]: "Failed to select device", }; diff --git a/src/common/error/internalErrorCode.ts b/src/common/error/internalErrorCode.ts index 61734b2ad..be707fa0d 100644 --- a/src/common/error/internalErrorCode.ts +++ b/src/common/error/internalErrorCode.ts @@ -40,6 +40,7 @@ export enum InternalErrorCode { FailedToToggleNetworkView = 136, FailedToRunEasBuild = 137, FailedToInstallPods = 138, + FailedToSelectDevice = 139, // Device Deployer errors IOSDeployNotFound = 201, diff --git a/src/debugger/debugSessionBase.ts b/src/debugger/debugSessionBase.ts index 6f2b6f18a..ee3937d49 100644 --- a/src/debugger/debugSessionBase.ts +++ b/src/debugger/debugSessionBase.ts @@ -17,7 +17,6 @@ import { ILaunchArgs, IRunOptions, PlatformType } from "../extension/launchArgs" import type { AppLauncher } from "../extension/appLauncher"; import { RNPackageVersions } from "../common/projectVersionHelper"; import { OutputChannelLogger } from "../extension/log/OutputChannelLogger"; -import { DeviceStatusIndicator } from "../extension/deviceStatusIndicator"; import { RNSession } from "./debugSessionWrapper"; nls.config({ @@ -234,8 +233,6 @@ export abstract class DebugSessionBase extends LoggingDebugSession { } } - DeviceStatusIndicator.hide(); - this.cancellationTokenSource.cancel(); this.cancellationTokenSource.dispose(); diff --git a/src/extension/android/androidTargetManager.ts b/src/extension/android/androidTargetManager.ts index 18dfb8f50..436bf13bc 100644 --- a/src/extension/android/androidTargetManager.ts +++ b/src/extension/android/androidTargetManager.ts @@ -140,12 +140,19 @@ export class AndroidTargetManager extends MobileTargetManager { return this.selectTarget(filter); } - protected async launchSimulator(emulatorTarget: IMobileTarget): Promise { + public async launchSimulator( + emulatorTarget: IMobileTarget, + coldBoot = false, + ): Promise { return new Promise((resolve, reject) => { let emulatorLaunchFailed = false; const emulatorProcess = this.childProcess.spawn( AndroidTargetManager.EMULATOR_COMMAND, - [AndroidTargetManager.EMULATOR_AVD_START_COMMAND, emulatorTarget.name as string], + [ + AndroidTargetManager.EMULATOR_AVD_START_COMMAND, + emulatorTarget.name as string, + ...(coldBoot ? ["-no-snapshot-load"] : []), + ], { detached: true, }, diff --git a/src/extension/appLauncher.ts b/src/extension/appLauncher.ts index e28f0c923..3beb498a1 100644 --- a/src/extension/appLauncher.ts +++ b/src/extension/appLauncher.ts @@ -38,7 +38,6 @@ import { LaunchScenariosManager } from "./launchScenariosManager"; import { createAdditionalWorkspaceFolder, onFolderAdded } from "./rn-extension"; import { RNProjectObserver } from "./rnProjectObserver"; import { GeneralMobilePlatform } from "./generalMobilePlatform"; -import { DeviceStatusIndicator } from "./deviceStatusIndicator"; nls.config({ messageFormat: nls.MessageFormat.bundle, @@ -205,7 +204,6 @@ export class AppLauncher { this.packager.getStatusIndicator().dispose(); void this.packager.stop(true); this.mobilePlatform.dispose(); - DeviceStatusIndicator.hide(); } public async openFileAtLocation(filename: string, lineNumber: number): Promise { @@ -341,14 +339,6 @@ export class AppLauncher { await this.mobilePlatform.runApp(); } - // Show device name in status bar after app is launched - if (this.mobilePlatform instanceof GeneralMobilePlatform) { - const target = this.mobilePlatform.getResolvedTarget(); - if (target?.name) { - DeviceStatusIndicator.show(target.name); - } - } - if (mobilePlatformOptions.isDirect) { if (launchArgs.useHermesEngine) { generator.step("mobilePlatform.enableHermesDebuggingMode"); diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts index 407a37a1b..1e542ad35 100644 --- a/src/extension/commands/index.ts +++ b/src/extension/commands/index.ts @@ -16,6 +16,7 @@ export * from "./runIos"; export * from "./runMacOs"; export * from "./runWindows"; export * from "./selectAndInsertDebugConfiguration"; +export * from "./selectDevice"; export * from "./showDevMenu"; export * from "./startLogCatMonitor"; export * from "./startPackager"; diff --git a/src/extension/commands/selectDevice.ts b/src/extension/commands/selectDevice.ts new file mode 100644 index 000000000..ce3d298f0 --- /dev/null +++ b/src/extension/commands/selectDevice.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +import { ErrorHelper } from "../../common/error/errorHelper"; +import { InternalErrorCode } from "../../common/error/internalErrorCode"; +import { DeviceSelector } from "../deviceSelector"; +import { Command } from "./util/command"; + +export class SelectDevice extends Command { + codeName = "selectDevice"; + label = "Select Device"; + requiresProject = false; + error = ErrorHelper.getInternalError(InternalErrorCode.FailedToSelectDevice); + + async baseFn(): Promise { + await DeviceSelector.instance?.showPicker(); + } +} diff --git a/src/extension/deviceSelector.ts b/src/extension/deviceSelector.ts new file mode 100644 index 000000000..934d2c905 --- /dev/null +++ b/src/extension/deviceSelector.ts @@ -0,0 +1,573 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +import * as vscode from "vscode"; +import * as nls from "vscode-nls"; +import { ChildProcess } from "../common/node/childProcess"; +import { AdbHelper } from "./android/adb"; +import { AndroidTargetManager } from "./android/androidTargetManager"; +import { TargetType } from "./generalPlatform"; +import { IDebuggableIOSTarget, IOSTargetManager } from "./ios/iOSTargetManager"; +import { PlatformType } from "./launchArgs"; +import { OutputChannelLogger } from "./log/OutputChannelLogger"; +import { IDebuggableMobileTarget } from "./mobileTarget"; +import { ProjectsStorage } from "./projectsStorage"; + +nls.config({ + messageFormat: nls.MessageFormat.bundle, + bundleFormat: nls.BundleFormat.standalone, +})(); +const localize = nls.loadMessageBundle(); + +export interface Device { + platform: PlatformType.Android | PlatformType.iOS; + /** Stable identity, saved and passed as `target`: AVD name, Android serial or iOS UDID. */ + key: string; + /** Runtime id shown in the picker: serial (emulator-5554) or UDID. */ + id: string; + name: string; + isVirtual: boolean; + isOnline: boolean; + /** iOS only, e.g. "iOS 27.0". */ + system?: string; +} + +export interface DeviceItem extends vscode.QuickPickItem { + device?: Device; + coldBoot?: boolean; +} + +export function statusBarText(device: Device | undefined): string { + if (!device) { + return localize("NoDevice", "No Device"); + } + let kind = ""; + if (device.isVirtual) { + kind = device.platform === PlatformType.iOS ? " simulator" : " emulator"; + } + return `${device.name} (${device.platform}${kind})`; +} + +export function statusBarTooltip(devices: Device[]): string | undefined { + const count = devices.filter(device => device.isOnline).length; + if (count === 0) { + return undefined; + } + return count === 1 + ? localize("OneDeviceConnected", "1 Device Connected") + : localize("DevicesConnected", "{0} Devices Connected", count); +} + +export function buildPickerItems(devices: Device[], current: Device | undefined): DeviceItem[] { + const isCurrent = (device: Device) => device.key === current?.key; + const online = devices + .filter(device => device.isOnline) + .sort( + (a, b) => Number(isCurrent(b)) - Number(isCurrent(a)) || a.name.localeCompare(b.name), + ); + const offline = devices.filter(device => !device.isOnline && device.isVirtual); + const items: DeviceItem[] = []; + + if (online.length) { + items.push({ + kind: vscode.QuickPickItemKind.Separator, + label: localize("AvailableDevices", "Available Devices"), + }); + for (const device of online) { + items.push({ + label: `$(device-mobile) ${device.name}`, + description: `${device.id} - ${device.platform}`, + detail: isCurrent(device) ? localize("CurrentDevice", "Current Device") : undefined, + device, + }); + } + } + + if (offline.length) { + items.push({ + kind: vscode.QuickPickItemKind.Separator, + label: localize("OfflineEmulators", "Offline Emulators"), + }); + for (const device of offline) { + const label = `$(play) ${localize("StartDevice", "Start {0}", device.name)}`; + if (device.platform === PlatformType.Android) { + items.push({ label, description: "mobile emulator", device }); + items.push({ + label, + description: "mobile emulator (cold boot)", + device, + coldBoot: true, + }); + } else { + items.push({ + label, + description: `mobile simulator (${device.system ?? ""})`, + device, + }); + } + } + } + + return items; +} + +/** + * Picks the current device after a refresh. Pass `previous === undefined` on the first refresh. + * A newly connected device is selected unless the current one is the device the user picked + * (`saved`); the picked device is always re-selected when it reconnects. + */ +export function nextCurrent( + current: Device | undefined, + previous: Device[] | undefined, + devices: Device[], + saved: string | undefined, +): Device | undefined { + const online = devices.filter(device => device.isOnline); + const find = (key: string | undefined) => online.find(device => device.key === key); + const fallback = () => find(saved) ?? online[0]; + if (!previous) { + return fallback(); + } + + const wasOnline = new Set(previous.filter(device => device.isOnline).map(device => device.key)); + let next = find(current?.key); + for (const device of online) { + const isNew = !wasOnline.has(device.key); + if (isNew && (!next || device.key === saved || next.key !== saved)) { + next = device; + } + } + return next ?? fallback(); +} + +/** + * Fills `config.target` from the selected device when the launch config leaves the target open. + * Returns a note for the output channel when the selection could not be used. + */ +export function applySelectedDevice( + config: vscode.DebugConfiguration, + device: Device | undefined, +): string | undefined { + if ( + config.request !== "launch" || + (config.platform !== PlatformType.Android && config.platform !== PlatformType.iOS) + ) { + return undefined; + } + const target = typeof config.target === "string" ? config.target.toLowerCase() : ""; + if (target && target !== TargetType.Simulator && target !== TargetType.Device) { + return undefined; // an explicit target always wins + } + if (!device) { + return localize( + "NoDeviceSelected", + "No device selected; using the default target selection.", + ); + } + if (device.platform !== config.platform) { + return localize( + "SelectedDeviceMismatch", + "Selected device '{0}' ({1}) doesn't match '{2}' ({3}); using the default target selection.", + device.name, + device.platform, + config.name, + config.platform, + ); + } + const typeMatches = + !target || + (target === TargetType.Simulator && device.isVirtual) || + (target === TargetType.Device && !device.isVirtual); + if (typeMatches) { + config.target = device.key; + } + return undefined; +} + +interface RunningAndroidTarget { + id: string; + isVirtual: boolean; + name: string; +} + +/** Running emulators are keyed by AVD name (serials change between boots), phones by serial. */ +export function toAndroidDevices(running: RunningAndroidTarget[], avdNames: string[]): Device[] { + const devices = running.map( + (target): Device => ({ + platform: PlatformType.Android, + key: target.isVirtual ? target.name : target.id, + id: target.id, + name: target.name, + isVirtual: target.isVirtual, + isOnline: true, + }), + ); + const runningAvds = new Set(devices.filter(device => device.isVirtual).map(d => d.key)); + for (const avd of avdNames) { + if (!runningAvds.has(avd)) { + devices.push({ + platform: PlatformType.Android, + key: avd, + id: avd, + name: avd, + isVirtual: true, + isOnline: false, + }); + } + } + return devices; +} + +/** + * Flutter-style device selector: a status bar item showing the selected device, a picker to + * change it or start an offline emulator/simulator, and a debug config resolver that runs + * launches on the selected device. + */ +export class DeviceSelector implements vscode.DebugConfigurationProvider, vscode.Disposable { + public static instance: DeviceSelector | undefined; + + private static readonly SAVED_DEVICE_KEY = "reactNative.selectedDevice"; + private static readonly REFRESH_INTERVAL_MS = 5000; + // ponytail: `xcrun xctrace list devices` takes ~2.5 s, so physical iPhones refresh only every + // 6th tick (~30 s) and when the picker opens. Switch to `xcrun devicectl list devices` + // (0.14 s) once its JSON format settles (fields are deprecated in Xcode 27). + private static readonly IOS_DEVICES_EVERY_N_REFRESHES = 6; + + private readonly statusBarItem: vscode.StatusBarItem; + private readonly logger = OutputChannelLogger.getMainChannel(); + private readonly childProcess = new ChildProcess(); + private readonly simulatorManager = new IOSTargetManager(); + private readonly iosDeviceManager = new IOSTargetManager(); + private readonly onDidChangeDevices = new vscode.EventEmitter(); + private readonly androidNames = new Map(); + private readonly loggedErrors = new Set(); + private readonly disposables: vscode.Disposable[] = []; + private adbHelper?: AdbHelper; + private devices?: Device[]; + private iosDevices: Device[] = []; + private current?: Device; + private launchingText?: string; + private refreshing?: Promise; + private wantIosDevices = false; + private refreshCount = 0; + private timer?: NodeJS.Timeout; + + constructor(private readonly workspaceState: vscode.Memento) { + this.statusBarItem = vscode.window.createStatusBarItem( + "reactNative.device", + vscode.StatusBarAlignment.Right, + Number.MIN_SAFE_INTEGER, // rightmost: lower priority sits further right + ); + this.statusBarItem.name = "React Native Device"; + this.statusBarItem.command = "reactNative.selectDevice"; + DeviceSelector.instance = this; + } + + /** Shows the status bar item and starts refreshing. Safe to call more than once. */ + public start(): void { + if (this.timer) { + return; + } + this.timer = setInterval(() => { + if (vscode.window.state.focused) { + void this.refresh(); + } + }, DeviceSelector.REFRESH_INTERVAL_MS); + this.disposables.push( + vscode.window.onDidChangeWindowState(state => { + if (state.focused) { + void this.refresh(); + } + }), + ); + this.render(); + this.statusBarItem.show(); + void this.refresh(); + } + + public async showPicker(): Promise { + const quickPick = vscode.window.createQuickPick(); + quickPick.placeholder = localize("SelectDeviceToUse", "Select a device to use"); + quickPick.ignoreFocusOut = true; + const updateItems = () => { + quickPick.items = buildPickerItems(this.devices ?? [], this.current); + }; + const subscription = this.onDidChangeDevices.event(updateItems); + updateItems(); + quickPick.busy = true; + void this.refresh(true).finally(() => { + quickPick.busy = false; + }); + + const selection = await new Promise(resolve => { + quickPick.onDidAccept(() => resolve(quickPick.selectedItems[0])); + quickPick.onDidHide(() => resolve(undefined)); + quickPick.show(); + }); + subscription.dispose(); + quickPick.dispose(); + + if (selection?.device?.isOnline) { + this.select(selection.device); + } else if (selection?.device) { + await this.launch(selection.device, !!selection.coldBoot); + } + } + + public resolveDebugConfiguration( + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + ): vscode.DebugConfiguration { + const note = applySelectedDevice(config, this.current); + if (note) { + this.logger.info(note); + } + return config; + } + + public dispose(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = undefined; + } + this.disposables.forEach(disposable => disposable.dispose()); + this.onDidChangeDevices.dispose(); + this.statusBarItem.dispose(); + DeviceSelector.instance = undefined; + } + + private select(device: Device): void { + this.current = device; + void this.workspaceState.update(DeviceSelector.SAVED_DEVICE_KEY, device.key); + this.render(); + } + + private render(): void { + this.statusBarItem.text = this.launchingText ?? statusBarText(this.current); + this.statusBarItem.tooltip = statusBarTooltip(this.devices ?? []); + } + + /** Coalesces concurrent calls; a request for iOS devices during a run triggers one more run. */ + private refresh(withIosDevices = false): Promise { + this.wantIosDevices = this.wantIosDevices || withIosDevices; + if (!this.refreshing) { + this.refreshing = this.collectAll().finally(() => { + this.refreshing = undefined; + if (this.wantIosDevices) { + void this.refresh(); + } + }); + } + return this.refreshing; + } + + private async collectAll(): Promise { + const withIosDevices = + this.wantIosDevices || + this.refreshCount++ % DeviceSelector.IOS_DEVICES_EVERY_N_REFRESHES === 0; + this.wantIosDevices = false; + const [android, simulators, iosDevices] = await Promise.all([ + this.collectAndroid(), + this.collectIos(this.simulatorManager, TargetType.Simulator), + withIosDevices + ? this.collectIos(this.iosDeviceManager, TargetType.Device) + : Promise.resolve(this.iosDevices), + ]); + this.iosDevices = iosDevices; + this.update([...android, ...simulators, ...iosDevices]); + } + + private update(devices: Device[]): void { + const previous = this.devices; + if (previous && JSON.stringify(previous) === JSON.stringify(devices)) { + return; + } + this.devices = devices; + this.current = nextCurrent( + this.current, + previous, + devices, + this.workspaceState.get(DeviceSelector.SAVED_DEVICE_KEY), + ); + this.render(); + this.onDidChangeDevices.fire(); + } + + private async collectAndroid(): Promise { + const adbHelper = this.getAdbHelper(); + if (!adbHelper) { + return []; + } + const [avdNames, running] = await Promise.all([ + this.attempt("Android emulators", () => adbHelper.getAvdsNames(), [] as string[]), + this.attempt("Android devices", () => this.runningAndroidTargets(adbHelper), []), + ]); + return toAndroidDevices(running, avdNames); + } + + private async runningAndroidTargets(adbHelper: AdbHelper): Promise { + const online = await adbHelper.getOnlineTargets(); + for (const serial of this.androidNames.keys()) { + if (!online.some(target => target.id === serial)) { + this.androidNames.delete(serial); + } + } + return Promise.all( + online.map(async target => ({ + id: target.id, + isVirtual: target.isVirtualTarget, + name: await this.androidName(adbHelper, target), + })), + ); + } + + /** AVD name for emulators, model for phones. Lookups that fail are retried next refresh. */ + private async androidName( + adbHelper: AdbHelper, + target: IDebuggableMobileTarget, + ): Promise { + const cached = this.androidNames.get(target.id); + if (cached) { + return cached; + } + const name = target.isVirtualTarget + ? await adbHelper.getAvdNameById(target.id) + : ( + await adbHelper + .executeShellCommand(target.id, "getprop ro.product.model") + .catch(() => "") + ).trim(); + if (!name) { + return target.id; + } + this.androidNames.set(target.id, name); + return name; + } + + private getAdbHelper(): AdbHelper | undefined { + const project = Object.values(ProjectsStorage.projectsCache)[0]; + if (!this.adbHelper && project) { + try { + this.adbHelper = new AdbHelper( + project.getPackager().getProjectPath(), + project.getOrUpdateNodeModulesRoot(), + ); + } catch { + // node_modules is not resolvable yet; retried on the next refresh + } + } + return this.adbHelper; + } + + private async collectIos(manager: IOSTargetManager, targetType: TargetType): Promise { + if (process.platform !== "darwin") { + return []; + } + const source = targetType === TargetType.Simulator ? "iOS simulators" : "iOS devices"; + return this.attempt( + source, + async () => { + await manager.collectTargets(targetType); + const targets = (await manager.getTargetList()) as IDebuggableIOSTarget[]; + return targets.map( + (target): Device => ({ + platform: PlatformType.iOS, + key: target.id, + id: target.id, + name: target.name, + isVirtual: target.isVirtualTarget, + isOnline: target.isOnline, + system: target.system, + }), + ); + }, + [], + ); + } + + /** Runs `collect`. Its first failure per source is logged until it succeeds again. */ + private async attempt(source: string, collect: () => Promise, fallback: T): Promise { + try { + const result = await collect(); + this.loggedErrors.delete(source); + return result; + } catch (error) { + if (!this.loggedErrors.has(source)) { + this.loggedErrors.add(source); + this.logger.warning( + localize( + "CouldNotListDevices", + "Could not list {0}: {1}", + source, + error instanceof Error ? error.message : String(error), + ), + ); + } + return fallback; + } + } + + private async launch(device: Device, coldBoot: boolean): Promise { + const isIos = device.platform === PlatformType.iOS; + this.launchingText = isIos + ? localize("LaunchingSimulator", "Launching simulator...") + : localize("LaunchingEmulator", "Launching emulator..."); + this.render(); + try { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: localize("LaunchingDevice", "Launching {0}...", device.name), + }, + () => (isIos ? this.bootSimulator(device) : this.bootEmulator(device, coldBoot)), + ); + await this.refresh(); + this.select( + this.devices?.find(d => d.key === device.key && d.isOnline) ?? { + ...device, + isOnline: true, + }, + ); + } catch (error) { + void vscode.window.showErrorMessage( + localize( + "FailedToLaunchDevice", + "Failed to launch {0}: {1}", + device.name, + error instanceof Error ? error.message : String(error), + ), + ); + } finally { + this.launchingText = undefined; + this.render(); + } + } + + private async bootEmulator(device: Device, coldBoot: boolean): Promise { + const adbHelper = this.getAdbHelper(); + if (!adbHelper) { + throw new Error(localize("NoReactNativeProject", "No React Native project is open")); + } + await new AndroidTargetManager(adbHelper).launchSimulator( + { name: device.key, isOnline: false, isVirtualTarget: true }, + coldBoot, + ); + } + + private async bootSimulator(device: Device): Promise { + // A separate manager instance: launchSimulator re-collects targets while it waits, + // which would race with the refresh loop's simulatorManager. + await new IOSTargetManager().launchSimulator({ + id: device.id, + name: device.name, + system: device.system ?? "", + isVirtualTarget: true, + isOnline: false, + }); + // `simctl boot` starts the simulator without a window. Xcode 27 renamed Simulator.app to DeviceHub.app. + await this.childProcess + .execFileToString("open", ["-a", "DeviceHub"]) + .catch(() => this.childProcess.execFileToString("open", ["-a", "Simulator"])) + .catch(error => this.logger.warning(error)); + } +} diff --git a/src/extension/deviceStatusIndicator.ts b/src/extension/deviceStatusIndicator.ts deleted file mode 100644 index 31052b078..000000000 --- a/src/extension/deviceStatusIndicator.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -import { window, Disposable, StatusBarItem, StatusBarAlignment } from "vscode"; - -/** - * Shows the currently connected device/simulator name in the status bar during a debug session. - */ -export class DeviceStatusIndicator implements Disposable { - private static readonly DEVICE_ICON = "$(device-mobile)"; - private static instance: DeviceStatusIndicator | undefined; - - private statusBarItem: StatusBarItem; - - private constructor() { - this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 9); - } - - private static getInstance(): DeviceStatusIndicator { - if (!DeviceStatusIndicator.instance) { - DeviceStatusIndicator.instance = new DeviceStatusIndicator(); - } - return DeviceStatusIndicator.instance; - } - - public static show(deviceName: string): void { - const instance = DeviceStatusIndicator.getInstance(); - instance.statusBarItem.text = `${DeviceStatusIndicator.DEVICE_ICON} ${deviceName}`; - instance.statusBarItem.tooltip = `Active debug target: ${deviceName}`; - instance.statusBarItem.show(); - } - - public static hide(): void { - DeviceStatusIndicator.instance?.statusBarItem.hide(); - } - - public dispose(): void { - this.statusBarItem.dispose(); - DeviceStatusIndicator.instance = undefined; - } -} diff --git a/src/extension/ios/iOSTargetManager.ts b/src/extension/ios/iOSTargetManager.ts index f047dd4da..36c98b99b 100644 --- a/src/extension/ios/iOSTargetManager.ts +++ b/src/extension/ios/iOSTargetManager.ts @@ -218,7 +218,7 @@ export class IOSTargetManager extends MobileTargetManager { return result?.toString(); } - protected async launchSimulator( + public async launchSimulator( virtualTarget: IDebuggableIOSTarget, ): Promise { return new Promise((resolve, reject) => { diff --git a/src/extension/rn-extension.ts b/src/extension/rn-extension.ts index 86593f068..a954c7644 100644 --- a/src/extension/rn-extension.ts +++ b/src/extension/rn-extension.ts @@ -41,6 +41,7 @@ import { } from "./debuggingConfiguration/launchJsonCompletionProvider"; import { ReactNativeSessionManager } from "./reactNativeSessionManager"; import { ProjectsStorage } from "./projectsStorage"; +import { DeviceSelector } from "./deviceSelector"; import { AppLauncher } from "./appLauncher"; import { LogCatMonitorManager } from "./android/logCatMonitorManager"; import { ExtensionConfigManager } from "./extensionConfigManager"; @@ -204,6 +205,19 @@ export async function activate(context: vscode.ExtensionContext): Promise ), ); + const deviceSelector = new DeviceSelector(EXTENSION_CONTEXT.workspaceState); + EXTENSION_CONTEXT.subscriptions.push( + deviceSelector, + vscode.debug.registerDebugConfigurationProvider( + DEBUG_TYPES.REACT_NATIVE, + deviceSelector, + ), + vscode.debug.registerDebugConfigurationProvider( + DEBUG_TYPES.REACT_NATIVE_DIRECT, + deviceSelector, + ), + ); + EXTENSION_CONTEXT.subscriptions.push( vscode.languages.registerCompletionItemProvider( { language: JsonLanguages.json }, @@ -402,6 +416,7 @@ export async function onFolderAdded(folder: vscode.WorkspaceFolder): Promise + item.kind === vscode.QuickPickItemKind.Separator + ? `-- ${item.label}` + : [item.label, item.description, item.detail, item.coldBoot && "cold"] + .filter(Boolean) + .join(" | "), + ); +} + +suite("DeviceSelector", function () { + suite("statusBarText", function () { + test("shows No Device when nothing is selected", function () { + assert.strictEqual(statusBarText(undefined), "No Device"); + }); + + test("labels emulators, simulators and physical devices", function () { + assert.strictEqual(statusBarText(pixel), "Pixel_7_API_34 (android emulator)"); + assert.strictEqual(statusBarText(phone), "Pixel 8 (android)"); + assert.strictEqual(statusBarText(iphone), "iPhone 17 (ios simulator)"); + }); + }); + + suite("statusBarTooltip", function () { + test("counts only online devices", function () { + assert.strictEqual(statusBarTooltip([]), undefined); + assert.strictEqual(statusBarTooltip([offlineAvd]), undefined); + assert.strictEqual(statusBarTooltip([pixel, offlineAvd]), "1 Device Connected"); + assert.strictEqual(statusBarTooltip([pixel, iphone]), "2 Devices Connected"); + }); + }); + + suite("buildPickerItems", function () { + test("lists the current device first, then offline emulators with cold boot rows", function () { + const items = buildPickerItems([iphone, pixel, offlineAvd, offlineSim], pixel); + assert.deepStrictEqual(describeItems(items), [ + "-- Available Devices", + "$(device-mobile) Pixel_7_API_34 | emulator-5554 - android | Current Device", + "$(device-mobile) iPhone 17 | UDID-1 - ios", + "-- Offline Emulators", + "$(play) Start Pixel_4_API_30 | mobile emulator", + "$(play) Start Pixel_4_API_30 | mobile emulator (cold boot) | cold", + "$(play) Start iPhone 17e | mobile simulator (iOS 27.0)", + ]); + assert.strictEqual(items[1].device, pixel); + assert.strictEqual(items[5].device, offlineAvd); + }); + + test("sorts available devices by name when none is current", function () { + const items = buildPickerItems([pixel, iphone], undefined); + assert.deepStrictEqual(describeItems(items), [ + "-- Available Devices", + "$(device-mobile) iPhone 17 | UDID-1 - ios", + "$(device-mobile) Pixel_7_API_34 | emulator-5554 - android", + ]); + }); + + test("omits empty sections", function () { + assert.deepStrictEqual(buildPickerItems([], undefined), []); + assert.deepStrictEqual(describeItems(buildPickerItems([offlineAvd], undefined)), [ + "-- Offline Emulators", + "$(play) Start Pixel_4_API_30 | mobile emulator", + "$(play) Start Pixel_4_API_30 | mobile emulator (cold boot) | cold", + ]); + }); + }); + + suite("nextCurrent", function () { + test("first refresh picks the saved device when it is online", function () { + assert.strictEqual( + nextCurrent(undefined, undefined, [pixel, iphone], "UDID-1"), + iphone, + ); + }); + + test("first refresh falls back to the first online device", function () { + assert.strictEqual( + nextCurrent(undefined, undefined, [offlineAvd, pixel, iphone], "gone"), + pixel, + ); + assert.strictEqual( + nextCurrent(undefined, undefined, [offlineAvd], undefined), + undefined, + ); + }); + + test("a newly connected device replaces a current device that was not picked", function () { + assert.strictEqual(nextCurrent(pixel, [pixel], [pixel, iphone], undefined), iphone); + }); + + test("a newly connected device does not replace the picked device", function () { + assert.strictEqual( + nextCurrent(pixel, [pixel], [pixel, iphone], "Pixel_7_API_34"), + pixel, + ); + }); + + test("the picked device is re-selected when it reconnects", function () { + assert.strictEqual( + nextCurrent(iphone, [iphone], [iphone, pixel], "Pixel_7_API_34"), + pixel, + ); + }); + + test("an offline AVD coming online counts as newly connected", function () { + const booted: Device = { ...offlineAvd, id: "emulator-5556", isOnline: true }; + assert.strictEqual( + nextCurrent(pixel, [pixel, offlineAvd], [pixel, booted], undefined), + booted, + ); + }); + + test("when the current device disconnects, prefers the saved device, then the first online", function () { + assert.strictEqual( + nextCurrent(iphone, [iphone, pixel, phone], [phone, pixel], "Pixel_7_API_34"), + pixel, + ); + assert.strictEqual(nextCurrent(iphone, [iphone, phone], [phone], undefined), phone); + assert.strictEqual(nextCurrent(iphone, [iphone], [], undefined), undefined); + }); + + test("returns the fresh object from the new device list", function () { + const stale: Device = { ...pixel, name: "old" }; + assert.strictEqual(nextCurrent(stale, [pixel], [pixel], undefined), pixel); + }); + }); + + suite("applySelectedDevice", function () { + const launch = (platform: string, target?: string): vscode.DebugConfiguration => ({ + name: platform === "ios" ? "Debug iOS" : "Debug Android", + type: "reactnative", + request: "launch", + platform, + ...(target === undefined ? {} : { target }), + }); + + test("fills an empty target with the device key", function () { + const config = launch("android"); + assert.strictEqual(applySelectedDevice(config, pixel), undefined); + assert.strictEqual(config.target, "Pixel_7_API_34"); + }); + + test("fills simulator/device targets only when the device type matches", function () { + const simulator = launch("android", "simulator"); + applySelectedDevice(simulator, pixel); + assert.strictEqual(simulator.target, "Pixel_7_API_34"); + + const device = launch("android", "device"); + applySelectedDevice(device, phone); + assert.strictEqual(device.target, "R5CT"); + + const wrongType = launch("android", "simulator"); + assert.strictEqual(applySelectedDevice(wrongType, phone), undefined); + assert.strictEqual(wrongType.target, "simulator"); + + const wrongType2 = launch("ios", "device"); + applySelectedDevice(wrongType2, iphone); + assert.strictEqual(wrongType2.target, "device"); + }); + + test("never overrides an explicit target", function () { + const config = launch("android", "emulator-5556"); + assert.strictEqual(applySelectedDevice(config, pixel), undefined); + assert.strictEqual(config.target, "emulator-5556"); + assert.strictEqual( + applySelectedDevice(launch("android", "emulator-5556"), undefined), + undefined, + ); + }); + + test("returns a note on platform mismatch and leaves the config alone", function () { + const config = launch("ios"); + assert.strictEqual( + applySelectedDevice(config, pixel), + "Selected device 'Pixel_7_API_34' (android) doesn't match 'Debug iOS' (ios); using the default target selection.", + ); + assert.strictEqual(config.target, undefined); + }); + + test("returns a note when no device is selected", function () { + assert.strictEqual( + applySelectedDevice(launch("android"), undefined), + "No device selected; using the default target selection.", + ); + }); + + test("ignores attach requests and other platforms", function () { + const attach: vscode.DebugConfiguration = { ...launch("android"), request: "attach" }; + assert.strictEqual(applySelectedDevice(attach, pixel), undefined); + assert.strictEqual(attach.target, undefined); + + const expo = launch("exponent"); + assert.strictEqual(applySelectedDevice(expo, pixel), undefined); + assert.strictEqual(expo.target, undefined); + }); + }); + + suite("toAndroidDevices", function () { + test("keys running emulators by AVD name, phones by serial, and lists AVDs that are not running", function () { + const devices = toAndroidDevices( + [ + { id: "emulator-5554", isVirtual: true, name: "Pixel_7_API_34" }, + { id: "R5CT", isVirtual: false, name: "Pixel 8" }, + ], + ["Pixel_4_API_30", "Pixel_7_API_34"], + ); + assert.deepStrictEqual(devices, [pixel, phone, offlineAvd]); + }); + }); +}); + +suite("AndroidTargetManager.launchSimulator", function () { + test("passes -no-snapshot-load for a cold boot and resolves with the emulator serial", async function () { + this.timeout(5000); + const spawnStub = sinon.stub(ChildProcess.prototype, "spawn").returns({ + spawnedProcess: { unref: () => undefined }, + outcome: new Promise(() => undefined), + } as any); + const adbHelper = { + getOnlineTargets: async () => [ + { id: "emulator-5554", isOnline: true, isVirtualTarget: true }, + ], + getAvdNameById: async () => "Pixel_4_API_30", + } as unknown as AdbHelper; + try { + const target = await new AndroidTargetManager(adbHelper).launchSimulator( + { name: "Pixel_4_API_30", isOnline: false, isVirtualTarget: true }, + true, + ); + assert.deepStrictEqual(spawnStub.firstCall.args.slice(0, 2), [ + "emulator", + ["-avd", "Pixel_4_API_30", "-no-snapshot-load"], + ]); + assert.strictEqual(target.id, "emulator-5554"); + } finally { + spawnStub.restore(); + } + }); +}); diff --git a/test/extension/deviceStatusIndicator.test.ts b/test/extension/deviceStatusIndicator.test.ts deleted file mode 100644 index 5947a45c4..000000000 --- a/test/extension/deviceStatusIndicator.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -import assert = require("assert"); -import * as vscode from "vscode"; -import * as sinon from "sinon"; -import { DeviceStatusIndicator } from "../../src/extension/deviceStatusIndicator"; - -function makeFakeStatusBarItem(): vscode.StatusBarItem { - return { - id: "", - name: "", - text: "", - tooltip: undefined, - color: undefined, - backgroundColor: undefined, - command: undefined, - accessibilityInformation: undefined, - alignment: vscode.StatusBarAlignment.Left, - priority: undefined, - show: sinon.stub(), - hide: sinon.stub(), - dispose: sinon.stub(), - } as unknown as vscode.StatusBarItem; -} - -suite("DeviceStatusIndicator", function () { - suite("extensionContext", function () { - let createStatusBarItemStub: Sinon.SinonStub; - let fakeItem: vscode.StatusBarItem; - setup(() => { - fakeItem = makeFakeStatusBarItem(); - createStatusBarItemStub = sinon.stub(vscode.window, "createStatusBarItem"); - createStatusBarItemStub.returns(fakeItem); - }); - teardown(() => { - const instance = (DeviceStatusIndicator as any).instance; - if (instance) { - instance.dispose(); - } - createStatusBarItemStub.restore(); - }); - test("show should set correct icon and device name in text", function () { - DeviceStatusIndicator.show("Test Device"); - assert.strictEqual(fakeItem.text, "$(device-mobile) Test Device"); - }); - test("show should set correct tooltip with device name", function () { - DeviceStatusIndicator.show("Test Device"); - assert.strictEqual(fakeItem.tooltip, "Active debug target: Test Device"); - }); - test("show should call show on the status bar item", function () { - DeviceStatusIndicator.show("Test Device"); - assert.ok((fakeItem.show as Sinon.SinonStub).calledOnce); - }); - test("hide should call hide on the status bar item after show", function () { - DeviceStatusIndicator.show("Test Device"); - DeviceStatusIndicator.hide(); - assert.ok((fakeItem.hide as Sinon.SinonStub).calledOnce); - }); - test("hide before show should not throw", function () { - assert.doesNotThrow(() => DeviceStatusIndicator.hide()); - }); - test("show should reuse singleton instance across multiple calls", function () { - DeviceStatusIndicator.show("Device A"); - DeviceStatusIndicator.show("Device B"); - assert.strictEqual(createStatusBarItemStub.callCount, 1); - assert.strictEqual(fakeItem.text, "$(device-mobile) Device B"); - }); - test("dispose should clear the singleton instance", function () { - DeviceStatusIndicator.show("Test Device"); - const instance = (DeviceStatusIndicator as any).instance as DeviceStatusIndicator; - instance.dispose(); - assert.strictEqual((DeviceStatusIndicator as any).instance, undefined); - }); - }); -}); diff --git a/test/extension/rn-extension.test.ts b/test/extension/rn-extension.test.ts index 18cfe909f..969f0dc55 100644 --- a/test/extension/rn-extension.test.ts +++ b/test/extension/rn-extension.test.ts @@ -203,6 +203,7 @@ suite("rn-extension", function () { "reactNative.runMacOS", "reactNative.runWindows", "reactNative.selectAndInsertDebugConfiguration", + "reactNative.selectDevice", "reactNative.showDevMenu", "reactNative.startLogCatMonitor", "reactNative.startPackager",