From f39647a2d8e7326ef0bef7594ce4baad35da03b8 Mon Sep 17 00:00:00 2001 From: Ben Polinsky <78756012+ben-polinsky@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:26:51 -0500 Subject: [PATCH] [electron] require reauth after scopes change (#276) Co-authored-by: Ben Polinsky --- .github/workflows/ci.yaml | 4 +- .github/workflows/codeql-analysis.yaml | 2 +- .github/workflows/create-release.yaml | 6 +- .gitignore | 1 - .pipelines/integration-test.yaml | 2 +- ...-aff4393d-f27d-43de-8277-d8e1d47d14d4.json | 7 + ...-c779af30-967f-4a82-8c03-07e6eb78b688.json | 7 + package.json | 5 +- packages/electron/package.json | 10 +- .../src/integration-test/integration.test.ts | 19 +- .../src/integration-test/test-app/index.html | 34 +- .../src/integration-test/tsconfig.json | 6 +- packages/electron/src/main/Client.ts | 5 +- packages/electron/src/main/TokenStore.ts | 72 +- .../src/test/MainAuthorizationClient.test.ts | 252 +++---- .../electron/src/test/helpers/testHelper.ts | 97 +++ packages/electron/vite.config.mts | 15 + .../src/test-integration/basic.test.ts | 2 +- pnpm-lock.yaml | 699 +++++++++++++++--- 19 files changed, 927 insertions(+), 318 deletions(-) create mode 100644 change/@itwin-electron-authorization-aff4393d-f27d-43de-8277-d8e1d47d14d4.json create mode 100644 change/@itwin-oidc-signin-tool-c779af30-967f-4a82-8c03-07e6eb78b688.json create mode 100644 packages/electron/src/test/helpers/testHelper.ts create mode 100644 packages/electron/vite.config.mts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3108d15e..655b0c91 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,12 +30,10 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: "18.12.0" + node-version: 18.x - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: latest - name: Install packages run: pnpm install diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index 4bb1dd99..adca896d 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml index d032b26a..b3d3fe43 100644 --- a/.github/workflows/create-release.yaml +++ b/.github/workflows/create-release.yaml @@ -11,19 +11,17 @@ jobs: name: Release packages steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.IMJS_ADMIN_GH_TOKEN }} - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: latest - name: Use Node.js 18 uses: actions/setup-node@v3 with: - node-version: 18.16.x + node-version: 18.x registry-url: https://registry.npmjs.org/ - name: Install dependencies diff --git a/.gitignore b/.gitignore index 987bc318..7702f338 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ dist # testing .nyc_output -.parcel-cache junit_results.xml /**/test-results/**/* generated-docs diff --git a/.pipelines/integration-test.yaml b/.pipelines/integration-test.yaml index 47e21206..cfbe8e07 100644 --- a/.pipelines/integration-test.yaml +++ b/.pipelines/integration-test.yaml @@ -34,7 +34,7 @@ jobs: - task: NodeTool@0 displayName: Use Node 18 inputs: - versionSpec: 18.x + versionSpec: 18.16.x - script: npm install -g pnpm@9 displayName: Install pnpm diff --git a/change/@itwin-electron-authorization-aff4393d-f27d-43de-8277-d8e1d47d14d4.json b/change/@itwin-electron-authorization-aff4393d-f27d-43de-8277-d8e1d47d14d4.json new file mode 100644 index 00000000..72bd65ad --- /dev/null +++ b/change/@itwin-electron-authorization-aff4393d-f27d-43de-8277-d8e1d47d14d4.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Delete stored token if application scopes change", + "packageName": "@itwin/electron-authorization", + "email": "ben-polinsky@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@itwin-oidc-signin-tool-c779af30-967f-4a82-8c03-07e6eb78b688.json b/change/@itwin-oidc-signin-tool-c779af30-967f-4a82-8c03-07e6eb78b688.json new file mode 100644 index 00000000..970ad1ae --- /dev/null +++ b/change/@itwin-oidc-signin-tool-c779af30-967f-4a82-8c03-07e6eb78b688.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "", + "packageName": "@itwin/oidc-signin-tool", + "email": "ben-polinsky@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/package.json b/package.json index 11ff667e..12cd6b67 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,6 @@ "devDependencies": { "beachball": "^2.49.1", "lage": "^2.11.13" - } -} \ No newline at end of file + }, + "packageManager": "pnpm@9.15.0+sha256.09a8fe31a34fda706354680619f4002f4ccef6dadff93240d24ef6c831f0fd28" +} diff --git a/packages/electron/package.json b/packages/electron/package.json index 25e6d5dd..fdb4f902 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -27,8 +27,8 @@ "test": "mocha \"./lib/cjs/test/**/*.test.js\"", "test:integration": "pnpm test:integration:build && pnpm exec playwright test", "test:integration:start": "pnpm test:integration:build && electron dist/integration-test/test-app/index.js", - "test:integration:build": "pnpm test:integration:parcel && tsc -p ./src/integration-test/", - "test:integration:parcel": "parcel build src/integration-test/test-app/index.html --dist-dir dist/integration-test/test-app --public-url ./", + "test:integration:build": "pnpm test:integration:vite && tsc -p ./src/integration-test/", + "test:integration:vite": "vite build", "pack": "npm pack", "rebuild": "npm run clean && npm run build" }, @@ -66,13 +66,11 @@ "eslint": "^8.56.0", "mocha": "^10.2.0", "nyc": "^17.0.0", - "parcel": "~2.12.0", - "path-browserify": "~1.0.1", - "process": "~0.11.10", "rimraf": "^3.0.2", "sinon": "^15.0.1", "source-map-support": "^0.5.9", - "typescript": "~5.3.3" + "typescript": "~5.3.3", + "vite": "^6.0.3" }, "peerDependencies": { "@itwin/core-bentley": "^3.3.0 || ^4.0.0", diff --git a/packages/electron/src/integration-test/integration.test.ts b/packages/electron/src/integration-test/integration.test.ts index 9cc29c01..6d826da8 100644 --- a/packages/electron/src/integration-test/integration.test.ts +++ b/packages/electron/src/integration-test/integration.test.ts @@ -37,7 +37,7 @@ const userDataPath = getElectronUserDataPath(); let electronApp: ElectronApplication; let electronPage: Page; const testHelper = new TestHelper(signInOptions); -const tokenStore = new RefreshTokenStore(getTokenStoreFileName(),getTokenStoreKey(), userDataPath); +const tokenStore = new RefreshTokenStore(getTokenStoreFileName(), getTokenStoreKey(), userDataPath); function getTokenStoreKey(issuerUrl?: string): string { const authority = new URL(issuerUrl ?? "https://ims.bentley.com"); @@ -93,7 +93,8 @@ test("sign in successful", async ({ browser }) => { const page = await browser.newPage(); await testHelper.checkStatus(electronPage, false); await testHelper.clickSignIn(electronPage); - await testHelper.signIn(page, await getUrl(electronApp)); + const url = await getUrl(electronApp); + await testHelper.signIn(page, url); await page.waitForLoadState("networkidle"); await testHelper.checkStatus(electronPage, true); await page.close(); @@ -110,3 +111,17 @@ test("sign out successful", async ({ browser }) => { await testHelper.checkStatus(electronPage, false); await page.close(); }); + +test("when scopes change, sign in is required", async ({ browser }) => { + const page = await browser.newPage(); + await testHelper.clickSignIn(electronPage); + await testHelper.signIn(page, await getUrl(electronApp)); + await page.waitForLoadState("networkidle"); + await testHelper.checkStatus(electronPage, true); + + // Admittedly this is cheating: no user would interact + // with the tokenStore directly, but this is a tough + // case to test otherwise. + await tokenStore.load("itwin-platform realitydata:read"); + await testHelper.checkStatus(electronPage, false); +}); diff --git a/packages/electron/src/integration-test/test-app/index.html b/packages/electron/src/integration-test/test-app/index.html index cb165cc4..7558178a 100644 --- a/packages/electron/src/integration-test/test-app/index.html +++ b/packages/electron/src/integration-test/test-app/index.html @@ -1,19 +1,19 @@ - - - - - Hello World! - - - - - - -

- - + + + + + + Hello World! + + + + + + + +

+ + + \ No newline at end of file diff --git a/packages/electron/src/integration-test/tsconfig.json b/packages/electron/src/integration-test/tsconfig.json index f2da22ef..9631971d 100644 --- a/packages/electron/src/integration-test/tsconfig.json +++ b/packages/electron/src/integration-test/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "module": "node16", - "moduleResolution": "node16", + "module": "NodeNext", + "moduleResolution": "nodenext", "lib": ["DOM"], "target": "ES5", "skipLibCheck": true, @@ -10,4 +10,4 @@ }, "include": ["./test-app/index.ts", "../renderer/ElectronPreload.ts"], "exclude": ["node_modules"] -} \ No newline at end of file +} diff --git a/packages/electron/src/main/Client.ts b/packages/electron/src/main/Client.ts index ef4dd990..fd165bee 100644 --- a/packages/electron/src/main/Client.ts +++ b/packages/electron/src/main/Client.ts @@ -302,7 +302,8 @@ export class ElectronMainAuthorization implements AuthorizationClient { * @return AccessToken if it's possible to get a valid access token, and undefined otherwise. */ private async loadAccessToken(): Promise { - const refreshToken = await this._refreshTokenStore.load(); + const refreshToken = await this._refreshTokenStore.load(this._scopes); + if (!refreshToken) return ""; @@ -526,7 +527,7 @@ export class ElectronMainAuthorization implements AuthorizationClient { ): Promise { this._refreshToken = tokenResponse.refreshToken; if (this._refreshToken) - await this._refreshTokenStore.save(this._refreshToken); + await this._refreshTokenStore.save(this._refreshToken, this._scopes); const expiresAtMilliseconds = (tokenResponse.issuedAt + (tokenResponse.expiresIn ?? 0)) * 1000; diff --git a/packages/electron/src/main/TokenStore.ts b/packages/electron/src/main/TokenStore.ts index 93fea396..6dfbead6 100644 --- a/packages/electron/src/main/TokenStore.ts +++ b/packages/electron/src/main/TokenStore.ts @@ -7,6 +7,7 @@ import { safeStorage } from "electron"; // eslint-disable-next-line @typescript-eslint/naming-convention const Store = require("electron-store"); // eslint-disable-line @typescript-eslint/no-var-requires + /** * Utility class used to store and read OAuth refresh tokens. * @internal @@ -34,31 +35,8 @@ export class RefreshTokenStore { }); } - private async getUserName(): Promise { - if (!this._userName) { - this._userName = await (await import("username")).username(); - } - - return this._userName; - } - - // Note: this is intentionally made async in case this code doesn't run in electron's main process and safeStorage must be polyfilled with async function - private async encryptRefreshToken(token: string): Promise { - return safeStorage.encryptString(token); - } - - // Note: this is intentionally made async in case this code doesn't run in electron's main process and safeStorage must be polyfilled with async function - private async decryptRefreshToken(encryptedToken: Buffer): Promise { - return safeStorage.decryptString(Buffer.from(encryptedToken)); - } - - private async getKey(): Promise { - const userName = await this.getUserName(); - return `${this._appStorageKey}${userName}`; - } - - /** Load refresh token if available */ - public async load(): Promise { + /** (Load) refresh token if available */ + public async load(scopes?: string): Promise { const userName = await this.getUserName(); if (!userName) return undefined; @@ -67,19 +45,26 @@ export class RefreshTokenStore { if (!this._store.has(key)) { return undefined; } + + if (scopes && !(await this.scopesMatch(scopes))) + return; + const encryptedToken = this._store.get(key); const refreshToken = await this.decryptRefreshToken(encryptedToken).catch(() => undefined); + return refreshToken; } /** Save refresh token after signin */ - public async save(refreshToken: string): Promise { + public async save(refreshToken: string, scopes?: string): Promise { const userName = await this.getUserName(); if (!userName) return; const encryptedToken = await this.encryptRefreshToken(refreshToken); const key = await this.getKey(); this._store.set(key, encryptedToken); + if (scopes) + this._store.set(`${key}:scopes`, scopes); } /** Delete refresh token after signout */ @@ -90,5 +75,40 @@ export class RefreshTokenStore { const key = await this.getKey(); await this._store.delete(key); + await this._store.delete(`${key}:scopes`); + } + + private async getUserName(): Promise { + if (!this._userName) { + this._userName = await (await import("username")).username(); + } + + return this._userName; + } + + // Note: this is intentionally made async in case this code doesn't run in electron's main process and safeStorage must be polyfilled with async function + private async encryptRefreshToken(token: string): Promise { + return safeStorage.encryptString(token); + } + + // Note: this is intentionally made async in case this code doesn't run in electron's main process and safeStorage must be polyfilled with async function + private async decryptRefreshToken(encryptedToken: Buffer): Promise { + return safeStorage.decryptString(Buffer.from(encryptedToken)); + } + + private async getKey(): Promise { + const userName = await this.getUserName(); + return `${this._appStorageKey}${userName}`; + } + + private async scopesMatch(scopes: string): Promise { + const key = await this.getKey(); + const savedScopes = this._store.get(`${key}:scopes`); + if (savedScopes) { + return savedScopes.split(" ").sort().join(" ") === scopes.split(" ").sort().join(" "); + } + + // no stored scopes, so all good + return true; } } diff --git a/packages/electron/src/test/MainAuthorizationClient.test.ts b/packages/electron/src/test/MainAuthorizationClient.test.ts index 3b3a81e6..97570baf 100644 --- a/packages/electron/src/test/MainAuthorizationClient.test.ts +++ b/packages/electron/src/test/MainAuthorizationClient.test.ts @@ -3,16 +3,14 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { AuthorizationListener, AuthorizationServiceConfiguration, TokenRequest } from "@openid/appauth"; -import { AuthorizationNotifier, AuthorizationRequest, AuthorizationResponse, BaseTokenRequestHandler, TokenResponse } from "@openid/appauth"; +import type { AuthorizationServiceConfiguration, TokenRequest } from "@openid/appauth"; +import { BaseTokenRequestHandler } from "@openid/appauth"; import * as chai from "chai"; import * as chaiAsPromised from "chai-as-promised"; import * as sinon from "sinon"; -import type { ElectronMainAuthorizationConfiguration } from "../main/Client"; import { ElectronMainAuthorization } from "../main/Client"; -import { ElectronMainAuthorizationRequestHandler } from "../main/ElectronMainAuthorizationRequestHandler"; -import { LoopbackWebServer } from "../main/LoopbackWebServer"; import { RefreshTokenStore } from "../main/TokenStore"; +import { getConfig, getMockTokenResponse, setupMockAuthServer, stubTokenCrypto } from "./helpers/testHelper"; /* eslint-disable @typescript-eslint/naming-convention */ const assert = chai.assert; const expect = chai.expect; @@ -57,33 +55,16 @@ describe("ElectronMainAuthorization Token Logic", () => { }); it("Should load token response from token store", async () => { - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - const mockTokenResponseJson = { - access_token: "testAccessToken", - refresh_token: "testRefreshToken", - issued_at: (new Date()).getTime(), - expires_in: "60000", - }; - const mockTokenResponse = new TokenResponse(mockTokenResponseJson); + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse({ accessToken: "testAccessToken" }); const refreshToken = "old refresh token"; - sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(refreshToken))); - sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(refreshToken)); + stubTokenCrypto(refreshToken); // Load refresh token into token store - use clientId const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); await tokenStore.save(refreshToken); - // Mock auth request - const spy = sinon.fake(); - sinon.stub(ElectronMainAuthorization.prototype, "refreshToken").callsFake(spy); - sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { - return mockTokenResponse; - }); - + const spy = await setupMockAuthServer(mockTokenResponse); // Create client and silent sign in const client = new ElectronMainAuthorization(config); await client.signInSilent(); @@ -95,47 +76,15 @@ describe("ElectronMainAuthorization Token Logic", () => { }); it("Should sign in", async () => { - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - const mockTokenResponse: TokenResponse = new TokenResponse( - { - access_token: "testAccessTokenSignInTest", - refresh_token: "testRefreshToken", - issued_at: (new Date()).getTime() / 1000, - expires_in: "60000", - }); - - sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(mockTokenResponse.refreshToken!))); - sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(mockTokenResponse.refreshToken)); + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse(); + + stubTokenCrypto(mockTokenResponse.refreshToken!); // Clear token store const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); await tokenStore.delete(); - // Mock auth request - sinon.stub(LoopbackWebServer, "start").resolves(); - sinon.stub(ElectronMainAuthorizationRequestHandler.prototype, "performAuthorizationRequest").callsFake(async () => { - await new Promise((resolve) => setImmediate(resolve, () => { })); - }); - sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { - return mockTokenResponse; - }); - sinon.stub(AuthorizationNotifier.prototype, "setAuthorizationListener").callsFake((listener: AuthorizationListener) => { - const authRequest = new AuthorizationRequest({ - response_type: "testResponseType", - client_id: "testClient", - redirect_uri: "testRedirect", - scope: "testScope", - internal: { code_verifier: "testCodeVerifier" }, - state: "testState", - }); - - const authResponse = new AuthorizationResponse({ code: "testCode", state: "testState" }); - listener(authRequest, authResponse, null); - }); - + await setupMockAuthServer(mockTokenResponse); // Create client and call initialize const client = new ElectronMainAuthorization(config); await client.signIn(); @@ -145,31 +94,16 @@ describe("ElectronMainAuthorization Token Logic", () => { }); it("Should refresh old token", async () => { - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - const mockTokenResponse: TokenResponse = new TokenResponse( - { - access_token: "testAccessToken", - refresh_token: "testRefreshToken", - issued_at: new Date().getTime() / 1000, - expires_in: "60000", - }); + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse(); const refreshToken = "old refresh token"; - sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(refreshToken))); - sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(refreshToken)); + stubTokenCrypto(refreshToken); // Load refresh token into token store - use clientId const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); await tokenStore.save(refreshToken); - // Mock auth request - sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { - return mockTokenResponse; - }); - + await setupMockAuthServer(mockTokenResponse); // Create client and silent signin const client = new ElectronMainAuthorization(config); await client.signInSilent(); @@ -188,47 +122,20 @@ describe("ElectronMainAuthorization Token Logic", () => { }); it("should save new refresh token after signIn() when no electron-store token is present", async () => { - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - const mockTokenResponse: TokenResponse = new TokenResponse( - { - access_token: "testAccessTokenSignInTest", - refresh_token: "testRefreshToken", - issued_at: (new Date()).getTime() / 1000, - expires_in: "60000", - }); - - sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(mockTokenResponse.refreshToken!))); - sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(mockTokenResponse.refreshToken)); + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse(); + stubTokenCrypto(mockTokenResponse.refreshToken!); + // Clear token store const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); await tokenStore.delete(); - // Mock auth request - sinon.stub(LoopbackWebServer, "start").resolves(); - sinon.stub(ElectronMainAuthorizationRequestHandler.prototype, "performAuthorizationRequest").callsFake(async () => { - await new Promise((resolve) => setImmediate(resolve, () => { })); - }); - sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { - await tokenStore.save(mockTokenResponse.refreshToken!); - return mockTokenResponse; - }); - sinon.stub(AuthorizationNotifier.prototype, "setAuthorizationListener").callsFake((listener: AuthorizationListener) => { - const authRequest = new AuthorizationRequest({ - response_type: "testResponseType", - client_id: "testClient", - redirect_uri: "testRedirect", - scope: "testScope", - internal: { code_verifier: "testCodeVerifier" }, - state: "testState", - }); - - const authResponse = new AuthorizationResponse({ code: "testCode", state: "testState" }); - listener(authRequest, authResponse, null); + await setupMockAuthServer(mockTokenResponse, { + performTokenRequestCb: async () => { + await tokenStore.save(mockTokenResponse.refreshToken!); + }, }); + const saveSpy = sinon.spy(tokenStore, "save"); // Create client and call initialize const client = new ElectronMainAuthorization(config); @@ -240,33 +147,18 @@ describe("ElectronMainAuthorization Token Logic", () => { }); it("should load and decrypt refresh token on signIn() given an existing refresh token in electron-store", async () => { - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - const mockTokenResponseJson = { - access_token: "testAccessToken", - refresh_token: "testRefreshToken", - issued_at: (new Date()).getTime(), - expires_in: "60000", - }; - const mockTokenResponse = new TokenResponse(mockTokenResponseJson); + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse({ accessToken: "testAccessToken" }); const refreshToken = "old refresh token"; - sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(refreshToken))); - const decryptSpy = sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(refreshToken)); + const { decryptSpy } = stubTokenCrypto(refreshToken); + // Load refresh token into token store - use clientId const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); await tokenStore.delete(); await tokenStore.save(refreshToken); - // Mock auth request - const spy = sinon.fake(); - sinon.stub(ElectronMainAuthorization.prototype, "refreshToken").callsFake(spy); - sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { - return mockTokenResponse; - }); + const spy = await setupMockAuthServer(mockTokenResponse); // Create client and silent sign in const client = new ElectronMainAuthorization(config); @@ -283,11 +175,7 @@ describe("ElectronMainAuthorization Authority URL Logic", () => { sinon.stub(ElectronMainAuthorization.prototype, "setupIPCHandlers" as any); }); - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; + const config = getConfig(); const testAuthority = "https://test.authority.com"; it("should use config authority without prefix", async () => { @@ -327,13 +215,8 @@ describe("ElectronMainAuthorization Config Scope Logic", () => { sinon.stub(ElectronMainAuthorization.prototype, "setupIPCHandlers" as any); }); - const config: ElectronMainAuthorizationConfiguration = { - clientId: "testClientId", - scopes: "testScope", - redirectUris: ["testRedirectUri_1", "testRedirectUri_2"], - }; - it("Should add offline_access scope", async () => { + const config = getConfig(); const client = new ElectronMainAuthorization(config); expect(client.scopes).equals(`${config.scopes} offline_access`); }); @@ -346,4 +229,75 @@ describe("ElectronMainAuthorization Config Scope Logic", () => { }); expect(client.scopes).equals("testScope offline_access"); }); + + describe("scope changes", () => { + beforeEach(() => { + sinon.restore(); + // Stub Electron calls + sinon.stub(ElectronMainAuthorization.prototype, "setupIPCHandlers" as any); + sinon.stub(ElectronMainAuthorization.prototype, "notifyFrontendAccessTokenChange" as any); + sinon.stub(ElectronMainAuthorization.prototype, "notifyFrontendAccessTokenExpirationChange" as any); + }); + + it("delete the current refresh token", async () => { + const config = getConfig(); + const mockTokenResponse = getMockTokenResponse(); + + stubTokenCrypto(mockTokenResponse.refreshToken!); + + await setupMockAuthServer(mockTokenResponse); + + const client = new ElectronMainAuthorization(config); + expect(client.scopes).equals(`${config.scopes} offline_access`); + await client.signIn(); + + const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); + const token = await tokenStore.load("testScope offline_access"); + assert.equal(token, mockTokenResponse.refreshToken); + + const _token = await tokenStore.load("differetnTestScope offline_access"); + assert.equal(_token, undefined); + }); + + it("delete the current refresh token regardless of order", async () => { + const config = getConfig({ scopes: "testScope blurgh-platform ReadTHINGS" }); + const mockTokenResponse = getMockTokenResponse(); + + stubTokenCrypto(mockTokenResponse.refreshToken!); + + await setupMockAuthServer(mockTokenResponse); + + const client = new ElectronMainAuthorization(config); + expect(client.scopes).equals("testScope blurgh-platform ReadTHINGS offline_access"); + await client.signIn(); + + const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); + const token = await tokenStore.load("ReadTHINGS blurgh-platform offline_access testScope"); + assert.equal(token, mockTokenResponse.refreshToken); + + const _token = await tokenStore.load("ReadTHINGS offline_access testScope blurgh-platform new-scope"); + assert.equal(_token, undefined); + }); + + it("delete the current refresh token works with explicit offline_access added", async () => { + const config = getConfig({ scopes: "testScope blurgh-platform offline_access ReadTHINGS" }); + const mockTokenResponse = getMockTokenResponse(); + + stubTokenCrypto(mockTokenResponse.refreshToken!); + + await setupMockAuthServer(mockTokenResponse); + + const client = new ElectronMainAuthorization(config); + expect(client.scopes).equals("testScope blurgh-platform offline_access ReadTHINGS"); + await client.signIn(); + + const tokenStore = new RefreshTokenStore(getTokenStoreFileName(config.clientId), getTokenStoreKey(config.clientId)); + const token = await tokenStore.load("offline_access ReadTHINGS blurgh-platform testScope"); + assert.equal(token, mockTokenResponse.refreshToken); + + const _token = await tokenStore.load("ReadTHINGS offline_access testScope blurgh-platform new-scope"); + assert.equal(_token, undefined); + }); + }); + }); diff --git a/packages/electron/src/test/helpers/testHelper.ts b/packages/electron/src/test/helpers/testHelper.ts new file mode 100644 index 00000000..5b353bc4 --- /dev/null +++ b/packages/electron/src/test/helpers/testHelper.ts @@ -0,0 +1,97 @@ +import type { AuthorizationListener, AuthorizationServiceConfiguration, TokenRequest } from "@openid/appauth"; +import { AuthorizationNotifier, AuthorizationRequest, AuthorizationResponse, BaseTokenRequestHandler, TokenResponse } from "@openid/appauth"; +import type { ElectronMainAuthorizationConfiguration } from "../../ElectronMain"; +import { ElectronMainAuthorization } from "../../main/Client"; +import * as sinon from "sinon"; +import { LoopbackWebServer } from "../../main/LoopbackWebServer"; +import { ElectronMainAuthorizationRequestHandler } from "../../main/ElectronMainAuthorizationRequestHandler"; +import { RefreshTokenStore } from "../../main/TokenStore"; + +interface ClientConfig { + clientId?: string; + scopes?: string; + redirectUris?: string[]; +} + +interface SetupMockAuthServerOptions { + performTokenRequestCb?: () => any; +} + +/** + * Get configuration for the test client + */ +export function getConfig({ clientId, scopes, redirectUris }: ClientConfig = {}): ElectronMainAuthorizationConfiguration { + return { + clientId: clientId ?? "testClientId", + scopes: scopes ?? "testScope", + redirectUris: redirectUris ?? ["testRedirectUri_1", "testRedirectUri_2"], + }; +} + +interface GetMockTokenResponseProps { + accessToken?: string; + refreshToken?: string; + issuedAt?: number; + expiresIn?: string; +} + +/** + * Construct a mock token response + */ +export function getMockTokenResponse({ accessToken, refreshToken, issuedAt, expiresIn }: GetMockTokenResponseProps = {}): TokenResponse { + return new TokenResponse( + { + /* eslint-disable @typescript-eslint/naming-convention */ + access_token: accessToken ?? "testAccessTokenSignInTest", + refresh_token: refreshToken ?? "testRefreshToken", + issued_at: issuedAt ?? (new Date()).getTime() / 1000, + expires_in: expiresIn ?? "60000", + /* eslint-disable @typescript-eslint/naming-convention */ + }); +} + +/** + * Setup a mock auth server and listen for refresh token calls + * @returns a spy which can be used to make assertions on the refresh token call + */ +export async function setupMockAuthServer(mockTokenResponse: TokenResponse, setupMockAuthServerOptions: SetupMockAuthServerOptions = {}): Promise> { + sinon.stub(LoopbackWebServer, "start").resolves(); + sinon.stub(ElectronMainAuthorizationRequestHandler.prototype, "performAuthorizationRequest").callsFake(async () => { + await new Promise((resolve) => setImmediate(resolve, () => { })); + }); + const spy = sinon.fake(); + sinon.stub(ElectronMainAuthorization.prototype, "refreshToken").callsFake(spy); + sinon.stub(BaseTokenRequestHandler.prototype, "performTokenRequest").callsFake(async (_configuration: AuthorizationServiceConfiguration, _request: TokenRequest) => { + if (setupMockAuthServerOptions.performTokenRequestCb) { + await setupMockAuthServerOptions.performTokenRequestCb(); + } + return mockTokenResponse; + }); + + sinon.stub(AuthorizationNotifier.prototype, "setAuthorizationListener").callsFake((listener: AuthorizationListener) => { + const authRequest = new AuthorizationRequest({ + /* eslint-disable @typescript-eslint/naming-convention */ + response_type: "testResponseType", + client_id: "testClient", + redirect_uri: "testRedirect", + scope: "testScope", + internal: { code_verifier: "testCodeVerifier" }, + state: "testState", + /* eslint-disable @typescript-eslint/naming-convention */ + }); + + const authResponse = new AuthorizationResponse({ code: "testCode", state: "testState" }); + listener(authRequest, authResponse, null); + }); + + return spy; +} + +/** + * Convenience function to stub the token encryption/decryption methods + */ +export function stubTokenCrypto(token: string) { + const encryptSpy = sinon.stub(RefreshTokenStore.prototype, "encryptRefreshToken" as any).returns(Promise.resolve(Buffer.from(token))); + const decryptSpy = sinon.stub(RefreshTokenStore.prototype, "decryptRefreshToken" as any).returns(Promise.resolve(token)); + return { encryptSpy, decryptSpy }; +} diff --git a/packages/electron/vite.config.mts b/packages/electron/vite.config.mts new file mode 100644 index 00000000..89d82978 --- /dev/null +++ b/packages/electron/vite.config.mts @@ -0,0 +1,15 @@ + +import { defineConfig, loadEnv } from 'vite'; +const env = loadEnv("", process.cwd(), ''); + +export default defineConfig({ + root: './src/integration-test/test-app', + build: { + outDir: '../../../dist/integration-test/test-app', + emptyOutDir: true, + }, + base: "./", + define: { + 'process.env': env, + }, +}); \ No newline at end of file diff --git a/packages/oidc-signin-tool/src/test-integration/basic.test.ts b/packages/oidc-signin-tool/src/test-integration/basic.test.ts index 44a924dd..592452aa 100644 --- a/packages/oidc-signin-tool/src/test-integration/basic.test.ts +++ b/packages/oidc-signin-tool/src/test-integration/basic.test.ts @@ -28,7 +28,7 @@ test.describe("Sign in (#integration)", () => { const token = await getTestAccessToken( { ...oidcConfig, - scope: `${oidcConfig.scope} projects:read`, + scope: oidcConfig.scope, }, validUser, ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 994015d3..0344b3cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 2.49.1(typescript@5.3.3) lage: specifier: ^2.11.13 - version: 2.11.15 + version: 2.12.4 packages/browser: dependencies: @@ -68,7 +68,7 @@ importers: version: 17.0.0 parcel: specifier: ~2.12.0 - version: 2.12.0(@swc/helpers@0.5.11)(typescript@5.3.3) + version: 2.12.0(@swc/helpers@0.5.11)(postcss@8.4.49)(typescript@5.3.3) process: specifier: ~0.11.10 version: 0.11.10 @@ -148,15 +148,6 @@ importers: nyc: specifier: ^17.0.0 version: 17.0.0 - parcel: - specifier: ~2.12.0 - version: 2.12.0(@swc/helpers@0.5.11)(typescript@5.3.3) - path-browserify: - specifier: ~1.0.1 - version: 1.0.1 - process: - specifier: ~0.11.10 - version: 0.11.10 rimraf: specifier: ^3.0.2 version: 3.0.2 @@ -169,6 +160,9 @@ importers: typescript: specifier: ~5.3.3 version: 5.3.3 + vite: + specifier: ^6.0.3 + version: 6.0.3(@types/node@18.18.14)(lightningcss@1.25.1) packages/node-cli: dependencies: @@ -501,6 +495,150 @@ packages: resolution: {integrity: sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==} engines: {node: '>=16'} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -522,6 +660,7 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -529,6 +668,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -556,7 +696,7 @@ packages: optional: true '@itwin/core-bentley@3.7.12': - resolution: {integrity: sha1-UE6Q9Q1lxzH+/rtZ89WcN1ZYyqI=} + resolution: {integrity: sha512-QwOTXxqeR3Oofh3QYOvbadJZG10JFu5n96j/H5o2aDGyTIuJoBaan51u5lCD7kiNl+CC60bPqekr0/gx91L7zQ==} '@itwin/core-bentley@4.7.1': resolution: {integrity: sha512-Us1C1RTsqmT41K0k7r4N/IFxhYCyEt4XgiJNuaYK5g7NCwKlpUedT/KOscC0Dh4saDQ53IK40YFSPp25GfK4YA==} @@ -1005,6 +1145,101 @@ packages: engines: {node: '>=18'} hasBin: true + '@rollup/rollup-android-arm-eabi@4.28.1': + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.28.1': + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.28.1': + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.28.1': + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.28.1': + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.28.1': + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.28.1': + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.28.1': + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.28.1': + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.28.1': + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.28.1': + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.28.1': + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.28.1': + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.28.1': + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.28.1': + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.28.1': + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} + cpu: [x64] + os: [win32] + '@rushstack/node-core-library@4.0.2': resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==} peerDependencies: @@ -1153,7 +1388,7 @@ packages: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} '@types/chai-as-promised@7.1.5': - resolution: {integrity: sha1-bgFoEfbHpk8u7YIxkcOmlVCU4lU=} + resolution: {integrity: sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==} '@types/chai@4.3.5': resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} @@ -1161,6 +1396,9 @@ packages: '@types/connect@3.4.35': resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.17.35': resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==} @@ -1204,7 +1442,7 @@ packages: resolution: {integrity: sha512-MXwo/ijhPIIKa5jLxwBfs8wTVZzwO36V/a12TSzsm5hAMFiY9CgSzbFobvQEmQTRu778CIIhJoT59KF2BzSYwg==} '@types/node@18.17.1': - resolution: {integrity: sha1-hMMpA786CfeHjDkdMf8I9v59gzU=} + resolution: {integrity: sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==} '@types/node@18.18.14': resolution: {integrity: sha512-iSOeNeXYNYNLLOMDSVPvIFojclvMZ/HDY2dU17kUlcsOsSQETbWIslJbYLZgA+ox8g2XQwSHKTkght1a5X26lQ==} @@ -1359,7 +1597,7 @@ packages: engines: {node: '>=8'} ajv-formats@2.1.1: - resolution: {integrity: sha1-bmaUAGWet0lzu/LjMycYCgmWtSA=} + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: ajv: ^8.0.0 peerDependenciesMeta: @@ -1370,7 +1608,7 @@ packages: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ajv@8.12.0: - resolution: {integrity: sha1-0aBScyPiL1NWLFZ8AJkVd9++GdE=} + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} ansi-colors@4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} @@ -1477,7 +1715,7 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} atomically@1.7.0: - resolution: {integrity: sha1-wHoEWEMuptvJo1Bv/6QktIvMqv4=} + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} engines: {node: '>=10.12.0'} available-typed-arrays@1.0.7: @@ -1524,12 +1762,13 @@ packages: boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} brace-expansion@2.0.1: - resolution: {integrity: sha1-HtxFng8MVISG7Pn8mfIiE2S5oK4=} + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1628,7 +1867,7 @@ packages: resolution: {integrity: sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==} chai-as-promised@7.1.1: - resolution: {integrity: sha1-CGRdgl3rhpbuYXJdv1kMAS6wDKA=} + resolution: {integrity: sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==} peerDependencies: chai: '>= 2.1.2 < 5' @@ -1652,7 +1891,7 @@ packages: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} check-error@1.0.2: - resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=} + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -1733,7 +1972,7 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} conf@10.2.0: - resolution: {integrity: sha1-g451e+lj8aI4bf4Eipj49p97VdY=} + resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} engines: {node: '>=12'} content-disposition@0.5.4: @@ -1784,7 +2023,7 @@ packages: hasBin: true cpx2@5.0.0: - resolution: {integrity: sha1-vFyaP1rLfjLHBh7p8OpaglVbdIc=} + resolution: {integrity: sha512-43LMini/KvlKMHjU7solCgVgNBspCmQ1noP5ckMa0VApaXrJFLcdfSxZxcdvH3wWcOvCwFGvOPBawOesI3oWMQ==} engines: {node: '>=16'} hasBin: true @@ -1841,11 +2080,11 @@ packages: engines: {node: '>= 0.4'} debounce-fn@4.0.0: - resolution: {integrity: sha1-7XbSBtilDmDeDdZtSU2Cg1/+Ycc=} + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} debounce@1.2.1: - resolution: {integrity: sha1-OIgdj0FmpcWEgCDBGCe4NLyz4KU=} + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -1890,7 +2129,7 @@ packages: engines: {node: '>=10'} decompress-response@6.0.0: - resolution: {integrity: sha1-yjh2Et234QS9FthaqwDV7PCcZvw=} + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} deep-eql@4.1.4: @@ -1997,7 +2236,7 @@ packages: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} dot-prop@6.0.1: - resolution: {integrity: sha1-/CazzxQrnlm3Tb057WbOYgxoEIM=} + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} dotenv-expand@5.1.0: @@ -2016,7 +2255,7 @@ packages: engines: {node: '>=6'} duplexer@0.1.2: - resolution: {integrity: sha1-Or5DrvODX4rgd9E23c4PJ2sEAOY=} + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2028,7 +2267,7 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} electron-store@8.1.0: - resolution: {integrity: sha1-RqOY8r2aqDxKnaquKDgOKzucdZc=} + resolution: {integrity: sha512-2clHg/juMjOH0GT9cQ6qtmIvK183B39ZXR0bUoPwKwYHJsEF3quqyDzMFUAu+0OP8ijmN2CbPRAelhNbWUbzwA==} electron-to-chromium@1.4.805: resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} @@ -2106,6 +2345,11 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -2209,6 +2453,7 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -2296,7 +2541,7 @@ packages: engines: {node: '>=8'} find-index@0.1.1: - resolution: {integrity: sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=} + resolution: {integrity: sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==} find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} @@ -2368,7 +2613,7 @@ packages: engines: {node: '>=12'} fs-extra@11.1.1: - resolution: {integrity: sha1-2mn3w587ACN4sJVLtq5+/cCHbi0=} + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} engines: {node: '>=14.14'} fs-extra@11.2.0: @@ -2455,7 +2700,7 @@ packages: resolution: {integrity: sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==} glob-gitignore@1.0.14: - resolution: {integrity: sha1-i3CMsCnnO9OI0i9/k1ITqpOh58I=} + resolution: {integrity: sha512-YuAEPqL58bOQDqDF2kMv009rIjSAtPs+WPzyGbwRWK+wD0UWQVRoP34Pz6yJ6ivco65C9tZnaIt0I3JCuQ8NZQ==} engines: {node: '>= 6'} glob-hasher-darwin-arm64@1.4.2: @@ -2501,7 +2746,7 @@ packages: engines: {node: '>=10.13.0'} glob2base@0.0.12: - resolution: {integrity: sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=} + resolution: {integrity: sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==} engines: {node: '>= 0.10'} glob@10.3.12: @@ -2511,6 +2756,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -2585,7 +2831,7 @@ packages: engines: {node: '>= 0.4'} has@1.0.3: - resolution: {integrity: sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=} + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} hash-base@3.0.4: @@ -2705,6 +2951,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2751,7 +2998,7 @@ packages: engines: {node: '>= 0.4'} is-core-module@2.12.1: - resolution: {integrity: sha1-DAtohbb4ABHHFUHOFcjWbPWk+f0=} + resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} @@ -2808,7 +3055,7 @@ packages: engines: {node: '>=0.12.0'} is-obj@2.0.0: - resolution: {integrity: sha1-Rz+wXZc3BeP9liBUUBjKjiLvSYI=} + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} is-path-inside@3.0.3: @@ -2967,10 +3214,10 @@ packages: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: - resolution: {integrity: sha1-rnvLNlard6c7pcSb9lTzjmtoYOI=} + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@7.0.3: - resolution: {integrity: sha1-I/9IG4tO680soSO0+gQJ5mRpotk=} + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -3030,8 +3277,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lage@2.11.15: - resolution: {integrity: sha512-NpXRXtyTAZJrS1Yu+ybB6WKqveN5ALhKb3LAX6XYJO2TpCQqWpvKpYsCQKgPKNstSW7YJA1vxrmlFtEQ4gHG3g==} + lage@2.12.4: + resolution: {integrity: sha512-To94fAOzLL6ujKuLvQji3gKdhmLAkjJ4wqQnLCFTtEgRYNwcy8dvrR9LVhgh+jCDL9l9t95VX1swdalumiV7eg==} hasBin: true language-subtag-registry@0.3.22: @@ -3129,7 +3376,7 @@ packages: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} lodash.difference@4.5.0: - resolution: {integrity: sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=} + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} lodash.flattendeep@4.4.0: resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} @@ -3144,7 +3391,7 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash.union@4.6.0: - resolution: {integrity: sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=} + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -3189,7 +3436,7 @@ packages: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} make-array@1.0.5: - resolution: {integrity: sha1-Mmp2NcdWqfYc4LKm/dXMNGBBm8s=} + resolution: {integrity: sha512-sgK2SAzxT19rWU+qxKUcn6PAh/swiIiz2F8C2cZjLc1z4iwYIfdoihqFIDQ8BDzAGtWPYJ6Sr13K1j/DXynDLA==} engines: {node: '>=0.10.0'} make-dir@3.1.0: @@ -3286,7 +3533,7 @@ packages: engines: {node: '>=10'} mimic-response@4.0.0: - resolution: {integrity: sha1-NUaLGefHXRD1Fl6iXnWlzup89w8=} + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} minimalistic-assert@1.0.1: @@ -3303,7 +3550,7 @@ packages: engines: {node: '>=10'} minimatch@9.0.3: - resolution: {integrity: sha1-puAMPeRMOlQr+q5wq/wiQgptqCU=} + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} minimatch@9.0.4: @@ -3311,7 +3558,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: - resolution: {integrity: sha1-waRk52kzAuCCoHXO4MBXdBrEdyw=} + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} minipass@7.1.0: resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==} @@ -3348,6 +3595,11 @@ packages: msgpackr@1.10.2: resolution: {integrity: sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -3393,7 +3645,7 @@ packages: engines: {node: '>=10'} normalize-url@8.0.0: - resolution: {integrity: sha1-WT29KE90Po3Pal3fj63/FJyCcBo=} + resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} engines: {node: '>=14.16'} npm-run-path@4.0.1: @@ -3520,7 +3772,7 @@ packages: engines: {node: '>=8'} p-map@6.0.0: - resolution: {integrity: sha1-TZxA0xcWMvhsR2AbcJ9LSs1w/tQ=} + resolution: {integrity: sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==} engines: {node: '>=16'} p-try@2.2.0: @@ -3558,9 +3810,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -3582,7 +3831,7 @@ packages: engines: {node: '>=12'} path-parse@1.0.7: - resolution: {integrity: sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU=} + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-scurry@1.10.2: resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} @@ -3611,6 +3860,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -3620,7 +3872,7 @@ packages: engines: {node: '>=8'} pkg-up@3.1.0: - resolution: {integrity: sha1-EA7CNcwVDk/UJRlBJZaihRKg3vU=} + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} playwright-core@1.35.1: @@ -3650,6 +3902,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + posthtml-parser@0.10.2: resolution: {integrity: sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==} engines: {node: '>=12'} @@ -3781,7 +4037,7 @@ packages: engines: {node: '>=0.10.0'} require-from-string@2.0.2: - resolution: {integrity: sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=} + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} require-main-filename@2.0.0: @@ -3806,7 +4062,7 @@ packages: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} resolve@1.22.3: - resolution: {integrity: sha1-S0BVNJ/7liYAly2h/cM8RqTrMoM=} + resolution: {integrity: sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw==} hasBin: true resolve@1.22.8: @@ -3830,6 +4086,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true ripemd160@2.0.2: @@ -3839,6 +4096,11 @@ packages: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} + rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3923,7 +4185,7 @@ packages: engines: {node: '>=8'} shell-quote@1.8.1: - resolution: {integrity: sha1-bb9Nt1UVrVusY7TxiUw6FUx2ZoA=} + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} @@ -3941,9 +4203,11 @@ packages: sinon@15.0.4: resolution: {integrity: sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==} + deprecated: 16.1.1 sinon@15.2.0: resolution: {integrity: sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==} + deprecated: 16.1.1 sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3952,6 +4216,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -4052,7 +4320,7 @@ packages: engines: {node: '>=8'} subarg@1.0.0: - resolution: {integrity: sha1-9izxdYHplrSPyWVpn1TAauJouNI=} + resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} @@ -4071,7 +4339,7 @@ packages: engines: {node: '>=10'} supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha1-btpL00SjyUrqN21MwxvHcxEDngk=} + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} svgo@2.8.0: @@ -4149,7 +4417,7 @@ packages: engines: {node: '>=10'} type-fest@0.20.2: - resolution: {integrity: sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ=} + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} type-fest@0.8.1: @@ -4157,7 +4425,7 @@ packages: engines: {node: '>=8'} type-fest@2.19.0: - resolution: {integrity: sha1-iAaAFbszA2pZi5UuVekxGmD9Ops=} + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} type-is@1.6.18: @@ -4239,7 +4507,7 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} util.inherits@1.0.3: - resolution: {integrity: sha1-qcYmoNBtNIKdR7pWyrEnjXRfnOY=} + resolution: {integrity: sha512-gMirHcfcq5D87nXDwbZqf5vl65S0mpMZBsHXJsXOO3Hc3G+JoQLwgaJa1h+PL7h3WhocnuLqoe8CuvMlztkyCA==} engines: {node: '>=4'} utility-types@3.11.0: @@ -4262,6 +4530,46 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@6.0.3: + resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} @@ -4550,6 +4858,78 @@ snapshots: esquery: 1.5.0 jsdoc-type-pratt-parser: 4.0.0 + '@esbuild/aix-ppc64@0.24.0': + optional: true + + '@esbuild/android-arm64@0.24.0': + optional: true + + '@esbuild/android-arm@0.24.0': + optional: true + + '@esbuild/android-x64@0.24.0': + optional: true + + '@esbuild/darwin-arm64@0.24.0': + optional: true + + '@esbuild/darwin-x64@0.24.0': + optional: true + + '@esbuild/freebsd-arm64@0.24.0': + optional: true + + '@esbuild/freebsd-x64@0.24.0': + optional: true + + '@esbuild/linux-arm64@0.24.0': + optional: true + + '@esbuild/linux-arm@0.24.0': + optional: true + + '@esbuild/linux-ia32@0.24.0': + optional: true + + '@esbuild/linux-loong64@0.24.0': + optional: true + + '@esbuild/linux-mips64el@0.24.0': + optional: true + + '@esbuild/linux-ppc64@0.24.0': + optional: true + + '@esbuild/linux-riscv64@0.24.0': + optional: true + + '@esbuild/linux-s390x@0.24.0': + optional: true + + '@esbuild/linux-x64@0.24.0': + optional: true + + '@esbuild/netbsd-x64@0.24.0': + optional: true + + '@esbuild/openbsd-arm64@0.24.0': + optional: true + + '@esbuild/openbsd-x64@0.24.0': + optional: true + + '@esbuild/sunos-x64@0.24.0': + optional: true + + '@esbuild/win32-arm64@0.24.0': + optional: true + + '@esbuild/win32-ia32@0.24.0': + optional: true + + '@esbuild/win32-x64@0.24.0': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -4758,14 +5138,6 @@ snapshots: '@lmdb/lmdb-win32-x64@2.8.5': optional: true - '@microsoft/api-extractor-model@7.28.13': - dependencies: - '@microsoft/tsdoc': 0.14.2 - '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 4.0.2(@types/node@18.18.14) - transitivePeerDependencies: - - '@types/node' - '@microsoft/api-extractor-model@7.28.13(@types/node@18.17.1)': dependencies: '@microsoft/tsdoc': 0.14.2 @@ -4784,12 +5156,12 @@ snapshots: '@microsoft/api-extractor@7.40.6': dependencies: - '@microsoft/api-extractor-model': 7.28.13 + '@microsoft/api-extractor-model': 7.28.13(@types/node@18.17.1) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 4.0.2(@types/node@18.18.14) + '@rushstack/node-core-library': 4.0.2(@types/node@18.17.1) '@rushstack/rig-package': 0.5.2 - '@rushstack/terminal': 0.9.0 + '@rushstack/terminal': 0.9.0(@types/node@18.17.1) '@rushstack/ts-command-line': 4.17.3 lodash: 4.17.21 resolve: 1.22.8 @@ -4902,15 +5274,13 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)': + '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.11) '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/logger': 2.12.0 '@parcel/utils': 2.12.0 lmdb: 2.8.5 - transitivePeerDependencies: - - '@swc/helpers' '@parcel/codeframe@2.12.0': dependencies: @@ -4922,14 +5292,14 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/config-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(typescript@5.3.3)': + '@parcel/config-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(postcss@8.4.49)(typescript@5.3.3)': dependencies: '@parcel/bundler-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/compressor-raw': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/core': 2.12.0(@swc/helpers@0.5.11) '@parcel/namer-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/optimizer-css': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) - '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(typescript@5.3.3) + '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(postcss@8.4.49)(typescript@5.3.3) '@parcel/optimizer-image': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/optimizer-svgo': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/optimizer-swc': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) @@ -4970,7 +5340,7 @@ snapshots: '@parcel/core@2.12.0(@swc/helpers@0.5.11)': dependencies: '@mischnic/json-sourcemap': 0.1.1 - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) @@ -4983,7 +5353,7 @@ snapshots: '@parcel/source-map': 2.1.1 '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) abortcontroller-polyfill: 1.7.5 base-x: 3.0.9 browserslist: 4.23.1 @@ -5011,7 +5381,7 @@ snapshots: '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/utils': 2.12.0 '@parcel/watcher': 2.4.1 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) transitivePeerDependencies: - '@swc/helpers' @@ -5060,10 +5430,10 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(typescript@5.3.3)': + '@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(postcss@8.4.49)(typescript@5.3.3)': dependencies: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) - htmlnano: 2.1.1(svgo@2.8.0)(typescript@5.3.3) + htmlnano: 2.1.1(postcss@8.4.49)(svgo@2.8.0)(typescript@5.3.3) nullthrows: 1.1.1 posthtml: 0.16.6 svgo: 2.8.0 @@ -5085,7 +5455,7 @@ snapshots: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/rust': 2.12.0 '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/optimizer-svgo@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))': dependencies: @@ -5117,7 +5487,7 @@ snapshots: '@parcel/node-resolver-core': 3.3.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@swc/core': 1.6.1(@swc/helpers@0.5.11) semver: 7.6.2 transitivePeerDependencies: @@ -5306,7 +5676,7 @@ snapshots: '@parcel/core': 2.12.0(@swc/helpers@0.5.11) '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) nullthrows: 1.1.1 '@parcel/transformer-js@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))': @@ -5317,7 +5687,7 @@ snapshots: '@parcel/rust': 2.12.0 '@parcel/source-map': 2.1.1 '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@swc/helpers': 0.5.11 browserslist: 4.23.1 nullthrows: 1.1.1 @@ -5385,12 +5755,12 @@ snapshots: '@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)': dependencies: - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) '@parcel/diagnostic': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/source-map': 2.1.1 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) utility-types: 3.11.0 transitivePeerDependencies: - '@parcel/core' @@ -5463,7 +5833,7 @@ snapshots: '@parcel/watcher-win32-ia32': 2.4.1 '@parcel/watcher-win32-x64': 2.4.1 - '@parcel/workers@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))': + '@parcel/workers@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.11) '@parcel/diagnostic': 2.12.0 @@ -5472,6 +5842,8 @@ snapshots: '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11) '@parcel/utils': 2.12.0 nullthrows: 1.1.1 + transitivePeerDependencies: + - '@swc/helpers' '@pkgjs/parseargs@0.11.0': optional: true @@ -5480,6 +5852,63 @@ snapshots: dependencies: playwright: 1.48.2 + '@rollup/rollup-android-arm-eabi@4.28.1': + optional: true + + '@rollup/rollup-android-arm64@4.28.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.28.1': + optional: true + + '@rollup/rollup-darwin-x64@4.28.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.28.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.28.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.28.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.28.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.28.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.28.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.28.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.28.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.28.1': + optional: true + '@rushstack/node-core-library@4.0.2(@types/node@18.17.1)': dependencies: fs-extra: 7.0.1 @@ -5507,11 +5936,6 @@ snapshots: resolve: 1.22.8 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.9.0': - dependencies: - '@rushstack/node-core-library': 4.0.2(@types/node@18.18.14) - colors: 1.2.5 - '@rushstack/terminal@0.9.0(@types/node@18.17.1)': dependencies: '@rushstack/node-core-library': 4.0.2(@types/node@18.17.1) @@ -5528,7 +5952,7 @@ snapshots: '@rushstack/ts-command-line@4.17.3': dependencies: - '@rushstack/terminal': 0.9.0 + '@rushstack/terminal': 0.9.0(@types/node@18.17.1) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -5670,6 +6094,8 @@ snapshots: dependencies: '@types/node': 18.18.14 + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.17.35': dependencies: '@types/node': 18.18.14 @@ -6830,6 +7256,33 @@ snapshots: es6-error@4.1.1: {} + esbuild@0.24.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 + escalade@3.1.1: {} escalade@3.1.2: {} @@ -7505,12 +7958,13 @@ snapshots: html-escaper@2.0.2: {} - htmlnano@2.1.1(svgo@2.8.0)(typescript@5.3.3): + htmlnano@2.1.1(postcss@8.4.49)(svgo@2.8.0)(typescript@5.3.3): dependencies: cosmiconfig: 9.0.0(typescript@5.3.3) posthtml: 0.16.6 timsort: 0.3.0 optionalDependencies: + postcss: 8.4.49 svgo: 2.8.0 transitivePeerDependencies: - typescript @@ -7888,7 +8342,7 @@ snapshots: kleur@3.0.3: {} - lage@2.11.15: + lage@2.12.4: dependencies: glob-hasher: 1.4.2 optionalDependencies: @@ -8192,6 +8646,8 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.3 + nanoid@3.3.8: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -8402,9 +8858,9 @@ snapshots: lodash.flattendeep: 4.4.0 release-zalgo: 1.0.0 - parcel@2.12.0(@swc/helpers@0.5.11)(typescript@5.3.3): + parcel@2.12.0(@swc/helpers@0.5.11)(postcss@8.4.49)(typescript@5.3.3): dependencies: - '@parcel/config-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(typescript@5.3.3) + '@parcel/config-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(postcss@8.4.49)(typescript@5.3.3) '@parcel/core': 2.12.0(@swc/helpers@0.5.11) '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 @@ -8459,8 +8915,6 @@ snapshots: parseurl@1.3.3: {} - path-browserify@1.0.1: {} - path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -8500,6 +8954,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} pkg-dir@4.2.0: @@ -8528,6 +8984,12 @@ snapshots: postcss-value-parser@4.2.0: {} + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + posthtml-parser@0.10.2: dependencies: htmlparser2: 7.2.0 @@ -8736,6 +9198,31 @@ snapshots: sprintf-js: 1.1.2 optional: true + rollup@4.28.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -8883,6 +9370,8 @@ snapshots: slash@3.0.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -9189,6 +9678,16 @@ snapshots: vary@1.1.2: {} + vite@6.0.3(@types/node@18.18.14)(lightningcss@1.25.1): + dependencies: + esbuild: 0.24.0 + postcss: 8.4.49 + rollup: 4.28.1 + optionalDependencies: + '@types/node': 18.18.14 + fsevents: 2.3.3 + lightningcss: 1.25.1 + vscode-oniguruma@1.7.0: {} vscode-textmate@8.0.0: {}