diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb439f474..07edcf302 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,14 +19,13 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' - run: pnpm install - run: pnpm build - - run: pnpm prepare:remove-source-maps - run: pnpm package:macos --publish=never -c.mac.identity=null env: CSC_LINK: ${{ secrets.CSC_LINK }} @@ -54,14 +53,13 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' - run: pnpm install - run: pnpm build - - run: pnpm prepare:remove-source-maps - run: pnpm package:win --publish=never - name: Clean up builds @@ -86,14 +84,13 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' - run: pnpm install - run: pnpm build - - run: pnpm prepare:remove-source-maps - run: pnpm package:linux --publish=never - name: Clean up builds diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7b255528e..9ad0524d5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ad23ee507..8752c8efb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,7 +20,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' @@ -30,7 +30,6 @@ jobs: env: OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} - - run: pnpm prepare:remove-source-maps - run: pnpm package:macos --publish onTagOrDraft env: APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }} @@ -60,7 +59,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' @@ -70,7 +69,6 @@ jobs: env: OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} - - run: pnpm prepare:remove-source-maps - run: pnpm package:win --publish onTagOrDraft env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -94,7 +92,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' @@ -104,7 +102,6 @@ jobs: env: OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} - - run: pnpm prepare:remove-source-maps - run: pnpm package:linux --publish onTagOrDraft env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 6052d4859..cf3c62d60 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -27,7 +27,7 @@ jobs: run_install: false - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: .nvmrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d39f438ba..8509d4f49 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' @@ -51,7 +51,7 @@ jobs: uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: '.nvmrc' cache: 'pnpm' @@ -65,7 +65,7 @@ jobs: path: coverage/ - name: SonarQube Cloud Scan - uses: SonarSource/sonarqube-scan-action@8c71dc039c2dd71d3821e89a2b58ecc7fee6ced9 # v5.3.0 + uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 2f3f18126..bcf3962a8 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -22,7 +22,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@7f33ba792281b034f64e96f4c0b5496782dd3b37 # v6.1.0 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.nvmrc b/.nvmrc index 89b93fd74..3a6161c2a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.18.0 \ No newline at end of file +22.19.0 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 308fbc6eb..95b1876a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,9 @@ "source.fixAll.biome": "explicit" }, "editor.defaultFormatter": "biomejs.biome", + "files.associations": { + "*.css": "tailwindcss" + }, "[typescript]": { "editor.defaultFormatter": "biomejs.biome" }, diff --git a/README.md b/README.md index b83e4465c..e860a5a7e 100644 --- a/README.md +++ b/README.md @@ -43,19 +43,19 @@ For more information, see [LICENSE](LICENSE). [brew]: https://brew.sh/ [homebrew-cask]: https://formulae.brew.sh/cask/gitify -[coverage-badge]: https://img.shields.io/sonar/coverage/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarcloud +[coverage-badge]: https://img.shields.io/sonar/coverage/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [coverage]: https://sonarcloud.io/summary/new_code?id=gitify-app_gitify -[quality-badge]: https://img.shields.io/sonar/quality_gate/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarcloud +[quality-badge]: https://img.shields.io/sonar/quality_gate/gitify-app_gitify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [quality]: https://sonarcloud.io/summary/new_code?id=gitify-app_gitify -[ci-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/ci.yml/badge.svg -[release-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/release.yml/badge.svg +[ci-workflow-badge]: https://img.shields.io/github/actions/workflow/status/gitify-app/gitify/ci.yml?logo=github&label=CI +[release-workflow-badge]: https://img.shields.io/github/actions/workflow/status/gitify-app/gitify/release.yml?logo=github&label=Release [downloads-total-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/total?label=downloads@all&logo=github [downloads-latest-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/latest/total?logo=github [contributors-badge]: https://img.shields.io/github/contributors/gitify-app/gitify?logo=github [license]: LICENSE [license-badge]: https://img.shields.io/github/license/gitify-app/gitify?logo=github [github-release-badge]: https://img.shields.io/github/v/release/gitify-app/gitify?logo=github -[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew +[homebrew-cask-badge]: https://img.shields.io/homebrew/cask/v/gitify?logo=homebrew&logoColor=white [renovate]: https://renovatebot.com/ -[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovate +[renovate-badge]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=renovate&logoColor=white diff --git a/biome.json b/biome.json index 4663b8ca1..077e2a520 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", "assist": { "actions": { "source": { @@ -17,10 +17,13 @@ ":BLANK_LINE:", ":PACKAGE:", ":BLANK_LINE:", + "**/shared/**", + ":BLANK_LINE:", "**" ] } - } + }, + "useSortedAttributes": "on" } } }, @@ -41,7 +44,12 @@ "useExhaustiveDependencies": { "level": "warn", "options": { - "hooks": [{ "name": "useNavigate", "stableResult": true }] + "hooks": [ + { + "name": "useNavigate", + "stableResult": true + } + ] } }, "useUniqueElementIds": "warn" diff --git a/config/electron-builder.js b/config/electron-builder.js index 3bd2d15fe..4d76067c0 100644 --- a/config/electron-builder.js +++ b/config/electron-builder.js @@ -1,7 +1,5 @@ -const { Configuration } = require('electron-builder'); - /** - * @type {Configuration} + * @type {import('electron-builder').Configuration} */ const config = { productName: 'Gitify', @@ -28,7 +26,7 @@ const config = { icon: 'assets/images/app-icon.icns', identity: 'Adam Setch (5KD23H9729)', type: 'distribution', - notarize: false, + notarize: false, // Handle notarization in afterSign.js target: { target: 'default', arch: ['universal'], @@ -37,12 +35,6 @@ const config = { entitlements: 'assets/entitlements.mac.plist', entitlementsInherit: 'assets/entitlements.mac.plist', gatekeeperAssess: false, - extendInfo: { - NSBluetoothAlwaysUsageDescription: null, - NSBluetoothPeripheralUsageDescription: null, - NSCameraUsageDescription: null, - NSMicrophoneUsageDescription: null, - }, }, dmg: { icon: 'assets/images/app-icon.icns', diff --git a/config/webpack.config.main.base.ts b/config/webpack.config.main.base.ts index af0807023..2762011a3 100644 --- a/config/webpack.config.main.base.ts +++ b/config/webpack.config.main.base.ts @@ -18,9 +18,6 @@ const configuration: webpack.Configuration = { output: { path: webpackPaths.buildPath, filename: 'main.js', - library: { - type: 'umd', - }, }, }; diff --git a/config/webpack.config.main.prod.ts b/config/webpack.config.main.prod.ts index 3ebfe192f..7ecf4bac5 100644 --- a/config/webpack.config.main.prod.ts +++ b/config/webpack.config.main.prod.ts @@ -5,7 +5,7 @@ import { merge } from 'webpack-merge'; import baseConfig from './webpack.config.main.base'; const configuration: webpack.Configuration = { - devtool: 'source-map', + devtool: false, mode: 'production', diff --git a/config/webpack.config.preload.base.ts b/config/webpack.config.preload.base.ts new file mode 100644 index 000000000..1fe0af5c8 --- /dev/null +++ b/config/webpack.config.preload.base.ts @@ -0,0 +1,24 @@ +import path from 'node:path'; + +import type webpack from 'webpack'; +import { merge } from 'webpack-merge'; + +import baseConfig from './webpack.config.common'; +import webpackPaths from './webpack.paths'; + +const configuration: webpack.Configuration = { + devtool: 'inline-source-map', + + mode: 'development', + + target: 'electron-preload', + + entry: [path.join(webpackPaths.srcPreloadPath, 'index.ts')], + + output: { + path: webpackPaths.buildPath, + filename: 'preload.js', + }, +}; + +export default merge(baseConfig, configuration); diff --git a/config/webpack.config.preload.prod.ts b/config/webpack.config.preload.prod.ts new file mode 100644 index 000000000..15ad49881 --- /dev/null +++ b/config/webpack.config.preload.prod.ts @@ -0,0 +1,18 @@ +import TerserPlugin from 'terser-webpack-plugin'; +import type webpack from 'webpack'; +import { merge } from 'webpack-merge'; + +import baseConfig from './webpack.config.preload.base'; + +const configuration: webpack.Configuration = { + devtool: false, + + mode: 'production', + + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + }, +}; + +export default merge(baseConfig, configuration); diff --git a/config/webpack.config.renderer.base.ts b/config/webpack.config.renderer.base.ts index bb70c7d96..be1212a90 100644 --- a/config/webpack.config.renderer.base.ts +++ b/config/webpack.config.renderer.base.ts @@ -1,30 +1,43 @@ import path from 'node:path'; +import twemoji from '@discordapp/twemoji'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import webpack from 'webpack'; import { merge } from 'webpack-merge'; -import { ALL_EMOJI_SVG_FILENAMES } from '../src/renderer/utils/emojis'; +import { Constants } from '../src/renderer/constants'; +import { Errors } from '../src/renderer/utils/errors'; import baseConfig from './webpack.config.common'; import webpackPaths from './webpack.paths'; +const ALL_EMOJIS = [ + ...Constants.ALL_READ_EMOJIS, + ...Errors.BAD_CREDENTIALS.emojis, + ...Errors.MISSING_SCOPES.emojis, + ...Errors.NETWORK.emojis, + ...Errors.RATE_LIMITED.emojis, + ...Errors.UNKNOWN.emojis, +]; + +export const ALL_EMOJI_SVG_FILENAMES = ALL_EMOJIS.map((emoji) => { + const imgHtml = twemoji.parse(emoji, { folder: 'svg', ext: '.svg' }); + return extractSvgFilename(imgHtml); +}); + const configuration: webpack.Configuration = { devtool: 'inline-source-map', mode: 'development', - target: 'electron-renderer', + target: ['web', 'electron-renderer'], entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], output: { path: webpackPaths.buildPath, filename: 'renderer.js', - library: { - type: 'umd', - }, }, module: { @@ -61,7 +74,6 @@ const configuration: webpack.Configuration = { removeAttributeQuotes: true, removeComments: true, }, - isBrowser: false, }), // Twemoji SVGs for Emoji parsing @@ -87,4 +99,11 @@ const configuration: webpack.Configuration = { ], }; +function extractSvgFilename(imgHtml: string) { + const srcMatch = /src="https://github.com/(.*)"/.exec(imgHtml); + const src = srcMatch ? srcMatch[1] : ''; + const filename = src.split('/').pop(); // Get the last part after splitting by "/" + return filename; +} + export default merge(baseConfig, configuration); diff --git a/config/webpack.config.renderer.prod.ts b/config/webpack.config.renderer.prod.ts index babaaf36a..f1fdb2948 100644 --- a/config/webpack.config.renderer.prod.ts +++ b/config/webpack.config.renderer.prod.ts @@ -6,7 +6,7 @@ import { merge } from 'webpack-merge'; import baseConfig from './webpack.config.renderer.base'; const configuration: webpack.Configuration = { - devtool: 'source-map', + devtool: false, mode: 'production', diff --git a/config/webpack.paths.ts b/config/webpack.paths.ts index 50686e8a0..c878e42ba 100644 --- a/config/webpack.paths.ts +++ b/config/webpack.paths.ts @@ -5,7 +5,11 @@ const rootPath = path.join(__dirname, '..'); const nodeModulesPath = path.join(rootPath, 'node_modules'); const srcPath = path.join(rootPath, 'src'); + const srcMainPath = path.join(srcPath, 'main'); + +const srcPreloadPath = path.join(srcPath, 'preload'); + const srcRendererPath = path.join(srcPath, 'renderer'); const buildPath = path.join(rootPath, 'build'); @@ -17,6 +21,7 @@ export default { nodeModulesPath, srcPath, srcMainPath, + srcPreloadPath, srcRendererPath, buildPath, distPath, diff --git a/package.json b/package.json index bbc283abb..de4563eb4 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,14 @@ "main": "build/main.js", "scripts": { "clean": "rimraf build coverage dist node_modules", - "build": "concurrently --names \"main,renderer\" --prefix-colors \"blue,green\" \"pnpm build:main\" \"pnpm build:renderer\"", + "build": "concurrently --names \"main,preload,renderer\" --prefix-colors \"blue,magenta,green\" \"pnpm build:main\" \"pnpm build:preload\" \"pnpm build:renderer\"", "build:main": "webpack --config ./config/webpack.config.main.prod.ts", + "build:preload": "webpack --config ./config/webpack.config.preload.prod.ts", "build:renderer": "webpack --config ./config/webpack.config.renderer.prod.ts", - "watch": "concurrently --names \"main,renderer\" --prefix-colors \"blue,green\" \"pnpm watch:main\" \"pnpm watch:renderer\"", + "watch": "concurrently --names \"main,preload,renderer\" --prefix-colors \"blue,magenta,green\" \"pnpm watch:main\" \"pnpm watch:preload\" \"pnpm watch:renderer\"", "watch:main": "webpack --watch --config ./config/webpack.config.main.base.ts", + "watch:preload": "webpack --watch --config ./config/webpack.config.preload.base.ts", "watch:renderer": "webpack --watch --config ./config/webpack.config.renderer.base.ts", - "prepare:remove-source-maps": "ts-node ./scripts/delete-source-maps.ts", "package:linux": "electron-builder --linux --config ./config/electron-builder.js", "package:macos": "electron-builder --mac --config ./config/electron-builder.js", "package:win": "electron-builder --win --config ./config/electron-builder.js", @@ -31,12 +32,10 @@ "keywords": [ "gitify", "github", - "notifier", "notifications", "electron", - "atom", - "shell", - "app", + "menubar", + "taskbar", "tray" ], "author": { @@ -67,66 +66,65 @@ }, "homepage": "https://gitify.io/", "dependencies": { - "@electron/remote": "2.1.3", "electron-log": "5.4.3", "electron-updater": "6.6.2", "menubar": "9.5.1", "react": "19.1.1", "react-dom": "19.1.1", - "react-router-dom": "7.8.1", - "update-electron-app": "3.1.1" + "react-router-dom": "7.8.2" }, "devDependencies": { - "@biomejs/biome": "2.2.0", + "@biomejs/biome": "2.2.3", "@discordapp/twemoji": "16.0.1", - "@electron/notarize": "3.0.2", - "@primer/octicons-react": "19.15.5", + "@electron/notarize": "3.1.0", + "@primer/css": "22.0.2", + "@primer/octicons-react": "19.16.0", "@primer/primitives": "11.1.0", "@primer/react": "36.27.0", - "@tailwindcss/postcss": "4.1.12", - "@testing-library/jest-dom": "6.7.0", + "@tailwindcss/postcss": "4.1.13", + "@testing-library/jest-dom": "6.8.0", "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "30.0.0", - "@types/node": "22.17.2", - "@types/react": "19.1.10", - "@types/react-dom": "19.1.7", + "@types/node": "22.18.1", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", "@types/react-router-dom": "5.3.3", - "@types/semver": "7.7.0", + "@types/semver": "7.7.1", "axios": "1.11.0", "clsx": "2.1.1", - "concurrently": "9.2.0", + "concurrently": "9.2.1", "copy-webpack-plugin": "13.0.1", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.2", "date-fns": "4.1.0", - "electron": "37.3.1", - "electron-builder": "25.1.8", + "electron": "38.0.0", + "electron-builder": "26.0.12", "final-form": "5.0.0", "graphql-tag": "2.12.6", "html-webpack-plugin": "5.6.4", "husky": "9.1.7", - "jest": "30.0.5", - "jest-environment-jsdom": "30.0.5", + "jest": "30.1.3", + "jest-environment-jsdom": "30.1.2", "mini-css-extract-plugin": "2.9.4", "nock": "13.5.6", "postcss": "8.5.6", - "postcss-loader": "8.1.1", + "postcss-loader": "8.2.0", "rimraf": "6.0.1", "semver": "7.7.2", "styled-components": "6.1.19", "tailwind-merge": "3.3.1", - "tailwindcss": "4.1.12", + "tailwindcss": "4.1.13", "terser-webpack-plugin": "5.3.14", "ts-jest": "29.4.1", - "ts-loader": "9.5.2", + "ts-loader": "9.5.4", "ts-node": "10.9.2", "typescript": "5.9.2", "webpack": "5.101.3", "webpack-cli": "6.0.1", "webpack-merge": "6.0.1" }, - "packageManager": "pnpm@10.15.0", + "packageManager": "pnpm@10.15.1", "pnpm": { "onlyBuiltDependencies": [ "@biomejs/biome", @@ -137,6 +135,6 @@ }, "lint-staged": { "*": "biome check --fix --no-errors-on-unmatched", - "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests -u" + "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3036e9a97..0f229aa89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@electron/remote': - specifier: 2.1.3 - version: 2.1.3(electron@37.3.1) electron-log: specifier: 5.4.3 version: 5.4.3 @@ -19,7 +16,7 @@ importers: version: 6.6.2 menubar: specifier: 9.5.1 - version: 9.5.1(electron@37.3.1) + version: 9.5.1(electron@38.0.0) react: specifier: 19.1.1 version: 19.1.1 @@ -27,39 +24,39 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) react-router-dom: - specifier: 7.8.1 - version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - update-electron-app: - specifier: 3.1.1 - version: 3.1.1 + specifier: 7.8.2 + version: 7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) devDependencies: '@biomejs/biome': - specifier: 2.2.0 - version: 2.2.0 + specifier: 2.2.3 + version: 2.2.3 '@discordapp/twemoji': specifier: 16.0.1 version: 16.0.1 '@electron/notarize': - specifier: 3.0.2 - version: 3.0.2 + specifier: 3.1.0 + version: 3.1.0 + '@primer/css': + specifier: 22.0.2 + version: 22.0.2(@primer/primitives@11.1.0) '@primer/octicons-react': - specifier: 19.15.5 - version: 19.15.5(react@19.1.1) + specifier: 19.16.0 + version: 19.16.0(react@19.1.1) '@primer/primitives': specifier: 11.1.0 version: 11.1.0 '@primer/react': specifier: 36.27.0 - version: 36.27.0(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(styled-components@6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1)) + version: 36.27.0(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(styled-components@6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1)) '@tailwindcss/postcss': - specifier: 4.1.12 - version: 4.1.12 + specifier: 4.1.13 + version: 4.1.13 '@testing-library/jest-dom': - specifier: 6.7.0 - version: 6.7.0 + specifier: 6.8.0 + version: 6.8.0 '@testing-library/react': specifier: 16.3.0 - version: 16.3.0(@testing-library/dom@10.0.0)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 16.3.0(@testing-library/dom@10.0.0)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.0.0) @@ -67,20 +64,20 @@ importers: specifier: 30.0.0 version: 30.0.0 '@types/node': - specifier: 22.17.2 - version: 22.17.2 + specifier: 22.18.1 + version: 22.18.1 '@types/react': - specifier: 19.1.10 - version: 19.1.10 + specifier: 19.1.12 + version: 19.1.12 '@types/react-dom': - specifier: 19.1.7 - version: 19.1.7(@types/react@19.1.10) + specifier: 19.1.9 + version: 19.1.9(@types/react@19.1.12) '@types/react-router-dom': specifier: 5.3.3 version: 5.3.3 '@types/semver': - specifier: 7.7.0 - version: 7.7.0 + specifier: 7.7.1 + version: 7.7.1 axios: specifier: 1.11.0 version: 1.11.0 @@ -88,8 +85,8 @@ importers: specifier: 2.1.1 version: 2.1.1 concurrently: - specifier: 9.2.0 - version: 9.2.0 + specifier: 9.2.1 + version: 9.2.1 copy-webpack-plugin: specifier: 13.0.1 version: 13.0.1(webpack@5.101.3) @@ -103,11 +100,11 @@ importers: specifier: 4.1.0 version: 4.1.0 electron: - specifier: 37.3.1 - version: 37.3.1 + specifier: 38.0.0 + version: 38.0.0 electron-builder: - specifier: 25.1.8 - version: 25.1.8(electron-builder-squirrel-windows@24.13.3) + specifier: 26.0.12 + version: 26.0.12(electron-builder-squirrel-windows@24.13.3) final-form: specifier: 5.0.0 version: 5.0.0 @@ -121,11 +118,11 @@ importers: specifier: 9.1.7 version: 9.1.7 jest: - specifier: 30.0.5 - version: 30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) + specifier: 30.1.3 + version: 30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) jest-environment-jsdom: - specifier: 30.0.5 - version: 30.0.5 + specifier: 30.1.2 + version: 30.1.2 mini-css-extract-plugin: specifier: 2.9.4 version: 2.9.4(webpack@5.101.3) @@ -136,8 +133,8 @@ importers: specifier: 8.5.6 version: 8.5.6 postcss-loader: - specifier: 8.1.1 - version: 8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.3) + specifier: 8.2.0 + version: 8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.3) rimraf: specifier: 6.0.1 version: 6.0.1 @@ -151,20 +148,20 @@ importers: specifier: 3.3.1 version: 3.3.1 tailwindcss: - specifier: 4.1.12 - version: 4.1.12 + specifier: 4.1.13 + version: 4.1.13 terser-webpack-plugin: specifier: 5.3.14 version: 5.3.14(webpack@5.101.3) ts-jest: specifier: 29.4.1 - version: 29.4.1(@babel/core@7.27.4)(@jest/transform@30.0.5)(@jest/types@30.0.5)(babel-jest@30.0.5(@babel/core@7.27.4))(jest-util@30.0.5)(jest@30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)))(typescript@5.9.2) + version: 29.4.1(@babel/core@7.27.4)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.27.4))(jest-util@30.0.5)(jest@30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)))(typescript@5.9.2) ts-loader: - specifier: 9.5.2 - version: 9.5.2(typescript@5.9.2)(webpack@5.101.3) + specifier: 9.5.4 + version: 9.5.4(typescript@5.9.2)(webpack@5.101.3) ts-node: specifier: 10.9.2 - version: 10.9.2(@types/node@22.17.2)(typescript@5.9.2) + version: 10.9.2(@types/node@22.18.1)(typescript@5.9.2) typescript: specifier: 5.9.2 version: 5.9.2 @@ -197,10 +194,6 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@babel/code-frame@7.24.2': - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -251,10 +244,6 @@ packages: resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.2': - resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} - engines: {node: '>=6.9.0'} - '@babel/parser@7.27.5': resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} engines: {node: '>=6.0.0'} @@ -370,55 +359,55 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@biomejs/biome@2.2.0': - resolution: {integrity: sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==} + '@biomejs/biome@2.2.3': + resolution: {integrity: sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.2.0': - resolution: {integrity: sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==} + '@biomejs/cli-darwin-arm64@2.2.3': + resolution: {integrity: sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.2.0': - resolution: {integrity: sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==} + '@biomejs/cli-darwin-x64@2.2.3': + resolution: {integrity: sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.2.0': - resolution: {integrity: sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==} + '@biomejs/cli-linux-arm64-musl@2.2.3': + resolution: {integrity: sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.2.0': - resolution: {integrity: sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==} + '@biomejs/cli-linux-arm64@2.2.3': + resolution: {integrity: sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.2.0': - resolution: {integrity: sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==} + '@biomejs/cli-linux-x64-musl@2.2.3': + resolution: {integrity: sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.2.0': - resolution: {integrity: sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==} + '@biomejs/cli-linux-x64@2.2.3': + resolution: {integrity: sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.2.0': - resolution: {integrity: sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==} + '@biomejs/cli-win32-arm64@2.2.3': + resolution: {integrity: sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.2.0': - resolution: {integrity: sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==} + '@biomejs/cli-win32-x64@2.2.3': + resolution: {integrity: sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -471,10 +460,20 @@ packages: engines: {node: '>=10.12.0'} hasBin: true + '@electron/fuses@1.8.0': + resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} + hasBin: true + '@electron/get@2.0.3': resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} engines: {node: '>=12'} + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': + resolution: {tarball: https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2} + version: 10.2.0-electron.1 + engines: {node: '>=12.13.0'} + hasBin: true + '@electron/notarize@2.2.1': resolution: {integrity: sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==} engines: {node: '>= 10.0.0'} @@ -483,8 +482,8 @@ packages: resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} engines: {node: '>= 10.0.0'} - '@electron/notarize@3.0.2': - resolution: {integrity: sha512-3LCARsJXOxEzzbmKFu/I2oq1eroyLdLBY2/J5ysCiPQ8i58TVIpQ7UdOZmthpppy1aBEiy+YGLUkxz5UMDRfgg==} + '@electron/notarize@3.1.0': + resolution: {integrity: sha512-DHePlWvmUKTNok2+yoOp2ZZ+3ZaLlDWdWD43BfzZhRlUPrBs+EwXDBq3kTJlO+jw2KZzeC3GGlypgw4HQZTD5w==} engines: {node: '>= 22.12.0'} '@electron/osx-sign@1.0.5': @@ -497,16 +496,11 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - '@electron/rebuild@3.6.1': - resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + '@electron/rebuild@3.7.0': + resolution: {integrity: sha512-VW++CNSlZwMYP7MyXEbrKjpzEwhB5kDNbzGtiPEjwYysqyTCF+YbNJ210Dj3AjWsGSV4iEEwNkmJN9yGZmVvmw==} engines: {node: '>=12.13.0'} hasBin: true - '@electron/remote@2.1.3': - resolution: {integrity: sha512-XlpxC8S4ttj/v2d+PKp9na/3Ev8bV7YWNL7Cw5b9MAWgTphEml7iYgbc7V0r9D6yDOfOkj06bchZgOZdlWJGNA==} - peerDependencies: - electron: '>= 13.0.0' - '@electron/universal@1.5.1': resolution: {integrity: sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==} engines: {node: '>=8.6'} @@ -567,12 +561,12 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/console@30.0.5': - resolution: {integrity: sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==} + '@jest/console@30.1.2': + resolution: {integrity: sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/core@30.0.5': - resolution: {integrity: sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==} + '@jest/core@30.1.3': + resolution: {integrity: sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -588,8 +582,8 @@ packages: resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/environment-jsdom-abstract@30.0.5': - resolution: {integrity: sha512-gpWwiVxZunkoglP8DCnT3As9x5O8H6gveAOpvaJd2ATAoSh7ZSSCWbr9LQtUMvr8WD3VjG9YnDhsmkCK5WN1rQ==} + '@jest/environment-jsdom-abstract@30.1.2': + resolution: {integrity: sha512-u8kTh/ZBl97GOmnGJLYK/1GuwAruMC4hoP6xuk/kwltmVWsA9u/6fH1/CsPVGt2O+Wn2yEjs8n1B1zZJ62Cx0w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -598,36 +592,36 @@ packages: canvas: optional: true - '@jest/environment@30.0.5': - resolution: {integrity: sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==} + '@jest/environment@30.1.2': + resolution: {integrity: sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/expect-utils@30.0.0': resolution: {integrity: sha512-UiWfsqNi/+d7xepfOv8KDcbbzcYtkWBe3a3kVDtg6M1kuN6CJ7b4HzIp5e1YHrSaQaVS8sdCoyCMCZClTLNKFQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect-utils@30.0.5': - resolution: {integrity: sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==} + '@jest/expect-utils@30.1.2': + resolution: {integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect@30.0.5': - resolution: {integrity: sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==} + '@jest/expect@30.1.2': + resolution: {integrity: sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/fake-timers@30.0.5': - resolution: {integrity: sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==} + '@jest/fake-timers@30.1.2': + resolution: {integrity: sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/get-type@30.0.0': resolution: {integrity: sha512-VZWMjrBzqfDKngQ7sUctKeLxanAbsBFoZnPxNIG6CmxK7Gv6K44yqd0nzveNIBfuhGZMmk1n5PGbvdSTOu0yTg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/get-type@30.0.1': - resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==} + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/globals@30.0.5': - resolution: {integrity: sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==} + '@jest/globals@30.1.2': + resolution: {integrity: sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/pattern@30.0.0': @@ -638,8 +632,8 @@ packages: resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/reporters@30.0.5': - resolution: {integrity: sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==} + '@jest/reporters@30.1.3': + resolution: {integrity: sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -659,24 +653,24 @@ packages: resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/snapshot-utils@30.0.5': - resolution: {integrity: sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==} + '@jest/snapshot-utils@30.1.2': + resolution: {integrity: sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/source-map@30.0.1': resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-result@30.0.5': - resolution: {integrity: sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==} + '@jest/test-result@30.1.3': + resolution: {integrity: sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-sequencer@30.0.5': - resolution: {integrity: sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==} + '@jest/test-sequencer@30.1.3': + resolution: {integrity: sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/transform@30.0.5': - resolution: {integrity: sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==} + '@jest/transform@30.1.2': + resolution: {integrity: sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/types@29.6.3': @@ -715,6 +709,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -765,11 +762,17 @@ packages: '@primer/behaviors@1.8.0': resolution: {integrity: sha512-ZUfhWVY4ZBKc2Fh3fIa2Qwwa3SnOi914lY5wcmN+UNtsBxeXsjWNwpohJbwRwWZm+nJ3C1n9qJFWpHuBlDVU1A==} + '@primer/css@22.0.2': + resolution: {integrity: sha512-FfXd1ga05oewKjDRi9cPmy7BSnb/6QOjTIxAtDj94Hoyk+qJxHhgvhbcnZwBfL3WKP6HeUT3PnsT9k+43Bmg3Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@primer/primitives': 10.x || 11.x + '@primer/live-region-element@0.7.1': resolution: {integrity: sha512-9uQCeBCb3wefz3kJNSo+PECc7T7TNB3k22JUdHY08Zlv9bd1rtsQgpazM5umcbZQrACzGbgufAfdbhGUBXI3jA==} - '@primer/octicons-react@19.15.5': - resolution: {integrity: sha512-JEoxBVkd6F8MaKEO1QKau0Nnk3IVroYn7uXGgMqZawcLQmLljfzua3S1fs2FQs295SYM9I6DlkESgz5ORq5yHA==} + '@primer/octicons-react@19.16.0': + resolution: {integrity: sha512-IbM5Qn2uOpHia2oQ9WtR6ZsnsiQk7Otc4Y7YfE4Q5023co24iGlu+xz2pOUxd5iSACM4qLZOPyWiwsL8P9Inkw==} engines: {node: '>=8'} peerDependencies: react: '>=16.3' @@ -863,65 +866,65 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} - '@tailwindcss/node@4.1.12': - resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} + '@tailwindcss/node@4.1.13': + resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} - '@tailwindcss/oxide-android-arm64@4.1.12': - resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==} + '@tailwindcss/oxide-android-arm64@4.1.13': + resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.12': - resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==} + '@tailwindcss/oxide-darwin-arm64@4.1.13': + resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.12': - resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==} + '@tailwindcss/oxide-darwin-x64@4.1.13': + resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.12': - resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==} + '@tailwindcss/oxide-freebsd-x64@4.1.13': + resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': - resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': - resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.12': - resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.12': - resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.12': - resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==} + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.12': - resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==} + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -932,31 +935,31 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': - resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.12': - resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.12': - resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==} + '@tailwindcss/oxide@4.1.13': + resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} engines: {node: '>= 10'} - '@tailwindcss/postcss@4.1.12': - resolution: {integrity: sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==} + '@tailwindcss/postcss@4.1.13': + resolution: {integrity: sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==} '@testing-library/dom@10.0.0': resolution: {integrity: sha512-PmJPnogldqoVFf+EwbHvbBJ98MmqASV8kLrBYgsDNxQcFMeIS7JFL48sfyXvuMtgmWO/wMhh25odr+8VhDmn4g==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.7.0': - resolution: {integrity: sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==} + '@testing-library/jest-dom@6.8.0': + resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.3.0': @@ -1078,8 +1081,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@22.17.2': - resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + '@types/node@22.18.1': + resolution: {integrity: sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==} '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -1087,8 +1090,8 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/react-dom@19.1.7': - resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} + '@types/react-dom@19.1.9': + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} peerDependencies: '@types/react': ^19.0.0 @@ -1101,14 +1104,14 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@19.1.10': - resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==} + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.7.0': - resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -1343,10 +1346,6 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} - engines: {node: '>= 14'} - agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -1395,10 +1394,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1418,8 +1413,8 @@ packages: app-builder-bin@4.0.0: resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==} - app-builder-bin@5.0.0-alpha.10: - resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + app-builder-bin@5.0.0-alpha.12: + resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==} app-builder-lib@24.13.3: resolution: {integrity: sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==} @@ -1428,15 +1423,12 @@ packages: dmg-builder: 24.13.3 electron-builder-squirrel-windows: 24.13.3 - app-builder-lib@25.1.8: - resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + app-builder-lib@26.0.12: + resolution: {integrity: sha512-+/CEPH1fVKf6HowBUs6LcAIoRcjeqgvAeoSE+cl7Y7LndyQ9ViGPYibNk7wmhMHzNgHIuIbw4nWADPO+4mjgWw==} engines: {node: '>=14.0.0'} peerDependencies: - dmg-builder: 25.1.8 - electron-builder-squirrel-windows: 25.1.8 - - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dmg-builder: 26.0.12 + electron-builder-squirrel-windows: 26.0.12 archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} @@ -1450,11 +1442,6 @@ packages: resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} engines: {node: '>= 10'} - are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1492,8 +1479,8 @@ packages: axios@1.11.0: resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} - babel-jest@30.0.5: - resolution: {integrity: sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==} + babel-jest@30.1.2: + resolution: {integrity: sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 @@ -1577,10 +1564,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - builder-util-runtime@9.2.10: - resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} - engines: {node: '>=12.0.0'} - builder-util-runtime@9.2.4: resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==} engines: {node: '>=12.0.0'} @@ -1592,8 +1575,8 @@ packages: builder-util@24.13.1: resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==} - builder-util@25.1.7: - resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + builder-util@26.0.11: + resolution: {integrity: sha512-xNjXfsldUEe153h1DraD0XvDOpqGR0L5eKFkdReB7eFW5HqysDZFfly4rckda6y9dF39N3pkPlOblcfHKGw+uA==} cacache@16.1.3: resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} @@ -1635,10 +1618,6 @@ packages: caniuse-lite@1.0.30001676: resolution: {integrity: sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1726,23 +1705,13 @@ packages: collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - color2k@2.0.3: resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==} @@ -1789,8 +1758,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.2.0: - resolution: {integrity: sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==} + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} engines: {node: '>=18'} hasBin: true @@ -1800,9 +1769,6 @@ packages: config-file-ts@0.2.8-rc1: resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2021,9 +1987,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2053,8 +2016,8 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dmg-builder@25.1.8: - resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + dmg-builder@26.0.12: + resolution: {integrity: sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==} dmg-license@1.0.11: resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} @@ -2127,8 +2090,8 @@ packages: electron-builder-squirrel-windows@24.13.3: resolution: {integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==} - electron-builder@25.1.8: - resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + electron-builder@26.0.12: + resolution: {integrity: sha512-cD1kz5g2sgPTMFHjLxfMjUK5JABq3//J4jPswi93tOPFz6btzXYtK5NrDt717NRbukCUDOrrvmYVOWERlqoiXA==} engines: {node: '>=14.0.0'} hasBin: true @@ -2142,8 +2105,8 @@ packages: electron-publish@24.13.1: resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==} - electron-publish@25.1.7: - resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + electron-publish@26.0.11: + resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==} electron-to-chromium@1.5.50: resolution: {integrity: sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==} @@ -2151,8 +2114,8 @@ packages: electron-updater@6.6.2: resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==} - electron@37.3.1: - resolution: {integrity: sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==} + electron@38.0.0: + resolution: {integrity: sha512-egljptiPJqbL/oamFCEY+g3RNeONWTVxZSGeyLqzK8xq106JhzuxnhJZ3sxt4DzJFaofbGyGJA37Oe9d+gVzYw==} engines: {node: '>= 12.20.55'} hasBin: true @@ -2172,10 +2135,6 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} - engines: {node: '>=10.13.0'} - enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2232,10 +2191,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -2281,8 +2236,8 @@ packages: resolution: {integrity: sha512-xCdPp6gwiR9q9lsPCHANarIkFTN/IMZso6Kkq03sOm9IIGtzK/UJqml0dkhHibGh8HKOj8BIDIpZ0BZuU7QK6w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - expect@30.0.5: - resolution: {integrity: sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==} + expect@30.1.2: + resolution: {integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} exponential-backoff@3.1.1: @@ -2362,10 +2317,6 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -2407,11 +2358,6 @@ packages: fzy.js@0.4.1: resolution: {integrity: sha512-4sPVXf+9oGhzg2tYzgWe4hgAY0wEbkqeuKVEgdnqX8S8VcLosQsDjb0jV+f5uoQlf8INWId1w0IGoufAoik1TA==} - gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2440,9 +2386,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - github-url-to-object@4.0.6: - resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -2506,10 +2449,6 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2529,9 +2468,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2596,10 +2532,6 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} - engines: {node: '>= 14'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -2737,9 +2669,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-url@1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2794,12 +2723,12 @@ packages: resolution: {integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-circus@30.0.5: - resolution: {integrity: sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==} + jest-circus@30.1.3: + resolution: {integrity: sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-cli@30.0.5: - resolution: {integrity: sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==} + jest-cli@30.1.3: + resolution: {integrity: sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -2808,8 +2737,8 @@ packages: node-notifier: optional: true - jest-config@30.0.5: - resolution: {integrity: sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==} + jest-config@30.1.3: + resolution: {integrity: sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@types/node': '*' @@ -2827,20 +2756,20 @@ packages: resolution: {integrity: sha512-TgT1+KipV8JTLXXeFX0qSvIJR/UXiNNojjxb/awh3vYlBZyChU/NEmyKmq+wijKjWEztyrGJFL790nqMqNjTHA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-diff@30.0.5: - resolution: {integrity: sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==} + jest-diff@30.1.2: + resolution: {integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-docblock@30.0.1: resolution: {integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-each@30.0.5: - resolution: {integrity: sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==} + jest-each@30.1.0: + resolution: {integrity: sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-environment-jsdom@30.0.5: - resolution: {integrity: sha512-BmnDEoAH+jEjkPrvE9DTKS2r3jYSJWlN/r46h0/DBUxKrkgt2jAZ5Nj4wXLAcV1KWkRpcFqA5zri9SWzJZ1cCg==} + jest-environment-jsdom@30.1.2: + resolution: {integrity: sha512-LXsfAh5+mDTuXDONGl1ZLYxtJEaS06GOoxJb2arcJTjIfh1adYg8zLD8f6P0df8VmjvCaMrLmc1PgHUI/YUTbg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -2848,32 +2777,32 @@ packages: canvas: optional: true - jest-environment-node@30.0.5: - resolution: {integrity: sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==} + jest-environment-node@30.1.2: + resolution: {integrity: sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-haste-map@30.0.5: - resolution: {integrity: sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==} + jest-haste-map@30.1.0: + resolution: {integrity: sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-leak-detector@30.0.5: - resolution: {integrity: sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==} + jest-leak-detector@30.1.0: + resolution: {integrity: sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-matcher-utils@30.0.0: resolution: {integrity: sha512-m5mrunqopkrqwG1mMdJxe1J4uGmS9AHHKYUmoxeQOxBcLjEvirIrIDwuKmUYrecPHVB/PUBpXs2gPoeA2FSSLQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-matcher-utils@30.0.5: - resolution: {integrity: sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==} + jest-matcher-utils@30.1.2: + resolution: {integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-message-util@30.0.0: resolution: {integrity: sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-message-util@30.0.5: - resolution: {integrity: sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==} + jest-message-util@30.1.0: + resolution: {integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-mock@30.0.0: @@ -2901,24 +2830,24 @@ packages: resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve-dependencies@30.0.5: - resolution: {integrity: sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==} + jest-resolve-dependencies@30.1.3: + resolution: {integrity: sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve@30.0.5: - resolution: {integrity: sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==} + jest-resolve@30.1.3: + resolution: {integrity: sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runner@30.0.5: - resolution: {integrity: sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==} + jest-runner@30.1.3: + resolution: {integrity: sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runtime@30.0.5: - resolution: {integrity: sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==} + jest-runtime@30.1.3: + resolution: {integrity: sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-snapshot@30.0.5: - resolution: {integrity: sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==} + jest-snapshot@30.1.2: + resolution: {integrity: sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-util@29.7.0: @@ -2933,12 +2862,12 @@ packages: resolution: {integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-validate@30.0.5: - resolution: {integrity: sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==} + jest-validate@30.1.0: + resolution: {integrity: sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-watcher@30.0.5: - resolution: {integrity: sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==} + jest-watcher@30.1.3: + resolution: {integrity: sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-worker@27.5.1: @@ -2949,12 +2878,12 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-worker@30.0.5: - resolution: {integrity: sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==} + jest-worker@30.1.0: + resolution: {integrity: sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest@30.0.5: - resolution: {integrity: sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==} + jest@30.1.3: + resolution: {integrity: sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -2963,10 +2892,6 @@ packages: node-notifier: optional: true - jiti@1.21.0: - resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} - hasBin: true - jiti@2.5.1: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true @@ -3181,10 +3106,6 @@ packages: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} - lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3207,8 +3128,8 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.18: + resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -3475,11 +3396,6 @@ packages: node-api-version@0.2.0: resolution: {integrity: sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==} - node-gyp@9.4.1: - resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} - engines: {node: ^12.13 || ^14.13 || >=16} - hasBin: true - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -3503,11 +3419,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -3672,8 +3583,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-loader@8.1.1: - resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==} + postcss-loader@8.2.0: + resolution: {integrity: sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==} engines: {node: '>= 18.12.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -3863,6 +3774,10 @@ packages: resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + proc-log@2.0.1: + resolution: {integrity: sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -3944,15 +3859,15 @@ packages: '@types/react': '>=16' react: '>=16' - react-router-dom@7.8.1: - resolution: {integrity: sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==} + react-router-dom@7.8.2: + resolution: {integrity: sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.8.1: - resolution: {integrity: sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==} + react-router@7.8.2: + resolution: {integrity: sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -4066,8 +3981,8 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} @@ -4106,6 +4021,10 @@ packages: semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4122,9 +4041,6 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -4143,8 +4059,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4284,10 +4201,6 @@ packages: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4315,8 +4228,8 @@ packages: tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} - tailwindcss@4.1.12: - resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} + tailwindcss@4.1.13: + resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -4362,6 +4275,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + tiny-async-pool@1.3.0: + resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} + tiny-typed-emitter@2.1.0: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} @@ -4438,8 +4354,8 @@ packages: jest-util: optional: true - ts-loader@9.5.2: - resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==} + ts-loader@9.5.4: + resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} engines: {node: '>=12.0.0'} peerDependencies: typescript: '*' @@ -4537,9 +4453,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-electron-app@3.1.1: - resolution: {integrity: sha512-7duRr6sYn014tifhKgT/5i8N+6xLzmJVJ8hVtNrHXlIDNP6QbRe6VxZ1hSi2UH5oJPzhor/PH7yKU9em5xjRzQ==} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -4641,9 +4554,6 @@ packages: engines: {node: '>= 8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -4746,11 +4656,6 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 - '@babel/code-frame@7.24.2': - dependencies: - '@babel/highlight': 7.24.2 - picocolors: 1.1.1 - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -4824,13 +4729,6 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.27.6 - '@babel/highlight@7.24.2': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/parser@7.27.5': dependencies: '@babel/types': 7.27.6 @@ -4949,39 +4847,39 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@biomejs/biome@2.2.0': + '@biomejs/biome@2.2.3': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.2.0 - '@biomejs/cli-darwin-x64': 2.2.0 - '@biomejs/cli-linux-arm64': 2.2.0 - '@biomejs/cli-linux-arm64-musl': 2.2.0 - '@biomejs/cli-linux-x64': 2.2.0 - '@biomejs/cli-linux-x64-musl': 2.2.0 - '@biomejs/cli-win32-arm64': 2.2.0 - '@biomejs/cli-win32-x64': 2.2.0 - - '@biomejs/cli-darwin-arm64@2.2.0': + '@biomejs/cli-darwin-arm64': 2.2.3 + '@biomejs/cli-darwin-x64': 2.2.3 + '@biomejs/cli-linux-arm64': 2.2.3 + '@biomejs/cli-linux-arm64-musl': 2.2.3 + '@biomejs/cli-linux-x64': 2.2.3 + '@biomejs/cli-linux-x64-musl': 2.2.3 + '@biomejs/cli-win32-arm64': 2.2.3 + '@biomejs/cli-win32-x64': 2.2.3 + + '@biomejs/cli-darwin-arm64@2.2.3': optional: true - '@biomejs/cli-darwin-x64@2.2.0': + '@biomejs/cli-darwin-x64@2.2.3': optional: true - '@biomejs/cli-linux-arm64-musl@2.2.0': + '@biomejs/cli-linux-arm64-musl@2.2.3': optional: true - '@biomejs/cli-linux-arm64@2.2.0': + '@biomejs/cli-linux-arm64@2.2.3': optional: true - '@biomejs/cli-linux-x64-musl@2.2.0': + '@biomejs/cli-linux-x64-musl@2.2.3': optional: true - '@biomejs/cli-linux-x64@2.2.0': + '@biomejs/cli-linux-x64@2.2.3': optional: true - '@biomejs/cli-win32-arm64@2.2.0': + '@biomejs/cli-win32-arm64@2.2.3': optional: true - '@biomejs/cli-win32-x64@2.2.0': + '@biomejs/cli-win32-x64@2.2.3': optional: true '@cspotcode/source-map-support@0.8.1': @@ -5028,6 +4926,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + '@electron/fuses@1.8.0': + dependencies: + chalk: 4.1.2 + fs-extra: 9.1.0 + minimist: 1.2.8 + '@electron/get@2.0.3': dependencies: debug: 4.4.0 @@ -5042,6 +4946,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.1 + glob: 8.1.0 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + proc-log: 2.0.1 + semver: 7.7.2 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + '@electron/notarize@2.2.1': dependencies: debug: 4.4.0 @@ -5058,7 +4978,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/notarize@3.0.2': + '@electron/notarize@3.1.0': dependencies: debug: 4.4.0 promise-retry: 2.0.1 @@ -5087,8 +5007,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/rebuild@3.6.1': + '@electron/rebuild@3.7.0': dependencies: + '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 debug: 4.4.0 @@ -5097,7 +5018,6 @@ snapshots: got: 11.8.6 node-abi: 3.67.0 node-api-version: 0.2.0 - node-gyp: 9.4.1 ora: 5.4.1 read-binary-file-arch: 1.0.6 semver: 7.7.2 @@ -5107,10 +5027,6 @@ snapshots: - bluebird - supports-color - '@electron/remote@2.1.3(electron@37.3.1)': - dependencies: - electron: 37.3.1 - '@electron/universal@1.5.1': dependencies: '@electron/asar': 3.2.18 @@ -5194,42 +5110,42 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jest/console@30.0.5': + '@jest/console@30.1.2': dependencies: '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 - jest-message-util: 30.0.5 + jest-message-util: 30.1.0 jest-util: 30.0.5 slash: 3.0.0 - '@jest/core@30.0.5(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2))': + '@jest/core@30.1.3(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2))': dependencies: - '@jest/console': 30.0.5 + '@jest/console': 30.1.2 '@jest/pattern': 30.0.1 - '@jest/reporters': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 + '@jest/reporters': 30.1.3 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.2.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.0.5 - jest-config: 30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) - jest-haste-map: 30.0.5 - jest-message-util: 30.0.5 + jest-config: 30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-resolve-dependencies: 30.0.5 - jest-runner: 30.0.5 - jest-runtime: 30.0.5 - jest-snapshot: 30.0.5 + jest-resolve: 30.1.3 + jest-resolve-dependencies: 30.1.3 + jest-runner: 30.1.3 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 jest-util: 30.0.5 - jest-validate: 30.0.5 - jest-watcher: 30.0.5 + jest-validate: 30.1.0 + jest-watcher: 30.1.3 micromatch: 4.0.8 pretty-format: 30.0.5 slash: 3.0.0 @@ -5243,56 +5159,56 @@ snapshots: '@jest/diff-sequences@30.0.1': {} - '@jest/environment-jsdom-abstract@30.0.5(jsdom@26.1.0)': + '@jest/environment-jsdom-abstract@30.1.2(jsdom@26.1.0)': dependencies: - '@jest/environment': 30.0.5 - '@jest/fake-timers': 30.0.5 + '@jest/environment': 30.1.2 + '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 '@types/jsdom': 21.1.7 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-mock: 30.0.5 jest-util: 30.0.5 jsdom: 26.1.0 - '@jest/environment@30.0.5': + '@jest/environment@30.1.2': dependencies: - '@jest/fake-timers': 30.0.5 + '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-mock: 30.0.5 '@jest/expect-utils@30.0.0': dependencies: '@jest/get-type': 30.0.0 - '@jest/expect-utils@30.0.5': + '@jest/expect-utils@30.1.2': dependencies: - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 - '@jest/expect@30.0.5': + '@jest/expect@30.1.2': dependencies: - expect: 30.0.5 - jest-snapshot: 30.0.5 + expect: 30.1.2 + jest-snapshot: 30.1.2 transitivePeerDependencies: - supports-color - '@jest/fake-timers@30.0.5': + '@jest/fake-timers@30.1.2': dependencies: '@jest/types': 30.0.5 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 22.17.2 - jest-message-util: 30.0.5 + '@types/node': 22.18.1 + jest-message-util: 30.1.0 jest-mock: 30.0.5 jest-util: 30.0.5 '@jest/get-type@30.0.0': {} - '@jest/get-type@30.0.1': {} + '@jest/get-type@30.1.0': {} - '@jest/globals@30.0.5': + '@jest/globals@30.1.2': dependencies: - '@jest/environment': 30.0.5 - '@jest/expect': 30.0.5 + '@jest/environment': 30.1.2 + '@jest/expect': 30.1.2 '@jest/types': 30.0.5 jest-mock: 30.0.5 transitivePeerDependencies: @@ -5300,23 +5216,23 @@ snapshots: '@jest/pattern@30.0.0': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-regex-util: 30.0.0 '@jest/pattern@30.0.1': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-regex-util: 30.0.1 - '@jest/reporters@30.0.5': + '@jest/reporters@30.1.3': dependencies: '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 + '@jest/console': 30.1.2 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit-x: 0.2.2 @@ -5327,9 +5243,9 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 - jest-message-util: 30.0.5 + jest-message-util: 30.1.0 jest-util: 30.0.5 - jest-worker: 30.0.5 + jest-worker: 30.1.0 slash: 3.0.0 string-length: 4.0.2 v8-to-istanbul: 9.2.0 @@ -5348,7 +5264,7 @@ snapshots: dependencies: '@sinclair/typebox': 0.34.33 - '@jest/snapshot-utils@30.0.5': + '@jest/snapshot-utils@30.1.2': dependencies: '@jest/types': 30.0.5 chalk: 4.1.2 @@ -5361,21 +5277,21 @@ snapshots: callsites: 3.1.0 graceful-fs: 4.2.11 - '@jest/test-result@30.0.5': + '@jest/test-result@30.1.3': dependencies: - '@jest/console': 30.0.5 + '@jest/console': 30.1.2 '@jest/types': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 - '@jest/test-sequencer@30.0.5': + '@jest/test-sequencer@30.1.3': dependencies: - '@jest/test-result': 30.0.5 + '@jest/test-result': 30.1.3 graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 + jest-haste-map: 30.1.0 slash: 3.0.0 - '@jest/transform@30.0.5': + '@jest/transform@30.1.2': dependencies: '@babel/core': 7.27.4 '@jest/types': 30.0.5 @@ -5385,7 +5301,7 @@ snapshots: convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 + jest-haste-map: 30.1.0 jest-regex-util: 30.0.1 jest-util: 30.0.5 micromatch: 4.0.8 @@ -5400,7 +5316,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -5410,7 +5326,7 @@ snapshots: '@jest/schemas': 30.0.0 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -5420,7 +5336,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -5448,6 +5364,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -5505,11 +5423,15 @@ snapshots: '@primer/behaviors@1.8.0': {} + '@primer/css@22.0.2(@primer/primitives@11.1.0)': + dependencies: + '@primer/primitives': 11.1.0 + '@primer/live-region-element@0.7.1': dependencies: '@lit-labs/ssr-dom-shim': 1.2.1 - '@primer/octicons-react@19.15.5(react@19.1.1)': + '@primer/octicons-react@19.16.0(react@19.1.1)': dependencies: react: 19.1.1 @@ -5517,7 +5439,7 @@ snapshots: '@primer/primitives@7.17.1': {} - '@primer/react@36.27.0(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(styled-components@6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1))': + '@primer/react@36.27.0(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(styled-components@6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1))': dependencies: '@github/combobox-nav': 2.3.1 '@github/markdown-toolbar-element': 2.2.3 @@ -5528,7 +5450,7 @@ snapshots: '@oddbird/popover-polyfill': 0.3.8 '@primer/behaviors': 1.8.0 '@primer/live-region-element': 0.7.1 - '@primer/octicons-react': 19.15.5(react@19.1.1) + '@primer/octicons-react': 19.16.0(react@19.1.1) '@primer/primitives': 7.17.1 '@styled-system/css': 5.1.5 '@styled-system/props': 5.1.5 @@ -5549,12 +5471,12 @@ snapshots: react-dom: 19.1.1(react@19.1.1) react-intersection-observer: 9.13.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-is: 18.2.0 - react-markdown: 8.0.7(@types/react@19.1.10)(react@19.1.1) + react-markdown: 8.0.7(@types/react@19.1.12)(react@19.1.1) styled-components: 6.1.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) styled-system: 5.1.5 optionalDependencies: - '@types/react': 19.1.10 - '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) transitivePeerDependencies: - supports-color @@ -5635,77 +5557,77 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/node@4.1.12': + '@tailwindcss/node@4.1.13': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.18.3 jiti: 2.5.1 lightningcss: 1.30.1 - magic-string: 0.30.17 + magic-string: 0.30.18 source-map-js: 1.2.1 - tailwindcss: 4.1.12 + tailwindcss: 4.1.13 - '@tailwindcss/oxide-android-arm64@4.1.12': + '@tailwindcss/oxide-android-arm64@4.1.13': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.12': + '@tailwindcss/oxide-darwin-arm64@4.1.13': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.12': + '@tailwindcss/oxide-darwin-x64@4.1.13': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.12': + '@tailwindcss/oxide-freebsd-x64@4.1.13': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.12': + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.12': + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.12': + '@tailwindcss/oxide-linux-x64-musl@4.1.13': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.12': + '@tailwindcss/oxide-wasm32-wasi@4.1.13': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.12': + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': optional: true - '@tailwindcss/oxide@4.1.12': + '@tailwindcss/oxide@4.1.13': dependencies: detect-libc: 2.0.4 tar: 7.4.3 optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.12 - '@tailwindcss/oxide-darwin-arm64': 4.1.12 - '@tailwindcss/oxide-darwin-x64': 4.1.12 - '@tailwindcss/oxide-freebsd-x64': 4.1.12 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.12 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.12 - '@tailwindcss/oxide-linux-x64-musl': 4.1.12 - '@tailwindcss/oxide-wasm32-wasi': 4.1.12 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.12 - - '@tailwindcss/postcss@4.1.12': + '@tailwindcss/oxide-android-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-x64': 4.1.13 + '@tailwindcss/oxide-freebsd-x64': 4.1.13 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-x64-musl': 4.1.13 + '@tailwindcss/oxide-wasm32-wasi': 4.1.13 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + + '@tailwindcss/postcss@4.1.13': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.12 - '@tailwindcss/oxide': 4.1.12 + '@tailwindcss/node': 4.1.13 + '@tailwindcss/oxide': 4.1.13 postcss: 8.5.6 - tailwindcss: 4.1.12 + tailwindcss: 4.1.13 '@testing-library/dom@10.0.0': dependencies: @@ -5718,7 +5640,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.7.0': + '@testing-library/jest-dom@6.8.0': dependencies: '@adobe/css-tools': 4.4.2 aria-query: 5.3.0 @@ -5727,15 +5649,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.0(@testing-library/dom@10.0.0)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@testing-library/react@16.3.0(@testing-library/dom@10.0.0)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.24.1 '@testing-library/dom': 10.0.0 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.10 - '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) '@testing-library/user-event@14.6.1(@testing-library/dom@10.0.0)': dependencies: @@ -5787,7 +5709,7 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/responselike': 1.0.3 '@types/debug@4.1.12': @@ -5808,7 +5730,7 @@ snapshots: '@types/fs-extra@9.0.13': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/hast@2.3.10': dependencies: @@ -5837,7 +5759,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -5845,7 +5767,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@types/mdast@3.0.15': dependencies: @@ -5853,46 +5775,46 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@22.17.2': + '@types/node@22.18.1': dependencies: undici-types: 6.21.0 '@types/plist@3.0.5': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 xmlbuilder: 15.1.1 optional: true '@types/prop-types@15.7.14': {} - '@types/react-dom@19.1.7(@types/react@19.1.10)': + '@types/react-dom@19.1.9(@types/react@19.1.12)': dependencies: - '@types/react': 19.1.10 + '@types/react': 19.1.12 '@types/react-is@18.3.0': dependencies: - '@types/react': 19.1.10 + '@types/react': 19.1.12 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 19.1.10 + '@types/react': 19.1.12 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.1.10 + '@types/react': 19.1.12 - '@types/react@19.1.10': + '@types/react@19.1.12': dependencies: csstype: 3.1.3 '@types/responselike@1.0.3': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 - '@types/semver@7.7.0': {} + '@types/semver@7.7.1': {} '@types/stack-utils@2.0.3': {} @@ -5923,7 +5845,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 optional: true '@ungap/structured-clone@1.3.0': {} @@ -6098,12 +6020,6 @@ snapshots: transitivePeerDependencies: - supports-color - agent-base@7.1.1: - dependencies: - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - agent-base@7.1.3: {} agentkeepalive@4.5.0: @@ -6150,10 +6066,6 @@ snapshots: ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -6169,9 +6081,9 @@ snapshots: app-builder-bin@4.0.0: {} - app-builder-bin@5.0.0-alpha.10: {} + app-builder-bin@5.0.0-alpha.12: {} - app-builder-lib@24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@24.13.3(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -6185,9 +6097,9 @@ snapshots: builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 debug: 4.4.0 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.1.8) + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.0.12) electron-publish: 24.13.1 form-data: 4.0.4 fs-extra: 10.1.0 @@ -6205,29 +6117,29 @@ snapshots: transitivePeerDependencies: - supports-color - app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 + '@electron/asar': 3.2.18 + '@electron/fuses': 1.8.0 '@electron/notarize': 2.5.0 '@electron/osx-sign': 1.3.1 - '@electron/rebuild': 3.6.1 + '@electron/rebuild': 3.7.0 '@electron/universal': 2.0.1 '@malept/flatpak-bundler': 0.4.0 '@types/fs-extra': 9.0.13 async-exit-hook: 2.0.1 - bluebird-lst: 1.0.9 - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chromium-pickle-js: 0.2.0 config-file-ts: 0.2.8-rc1 debug: 4.4.0 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) dotenv: 16.4.5 dotenv-expand: 11.0.6 ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.1.8) - electron-publish: 25.1.7 - form-data: 4.0.0 + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.0.12) + electron-publish: 26.0.11 fs-extra: 10.1.0 hosted-git-info: 4.1.0 is-ci: 3.0.1 @@ -6236,17 +6148,16 @@ snapshots: json5: 2.2.3 lazy-val: 1.0.5 minimatch: 10.0.1 + plist: 3.1.0 resedit: 1.7.1 - sanitize-filename: 1.6.3 semver: 7.7.2 tar: 6.2.1 temp-file: 3.4.0 + tiny-async-pool: 1.3.0 transitivePeerDependencies: - bluebird - supports-color - aproba@2.0.0: {} - archiver-utils@2.1.0: dependencies: glob: 7.2.3 @@ -6283,11 +6194,6 @@ snapshots: tar-stream: 2.2.0 zip-stream: 4.1.1 - are-we-there-yet@3.0.1: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - arg@4.1.3: {} argparse@1.0.10: @@ -6322,10 +6228,10 @@ snapshots: transitivePeerDependencies: - debug - babel-jest@30.0.5(@babel/core@7.27.4): + babel-jest@30.1.2(@babel/core@7.27.4): dependencies: '@babel/core': 7.27.4 - '@jest/transform': 30.0.5 + '@jest/transform': 30.1.2 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.0 babel-preset-jest: 30.0.1(@babel/core@7.27.4) @@ -6438,13 +6344,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builder-util-runtime@9.2.10: - dependencies: - debug: 4.4.0 - sax: 1.3.0 - transitivePeerDependencies: - - supports-color - builder-util-runtime@9.2.4: dependencies: debug: 4.4.0 @@ -6480,24 +6379,25 @@ snapshots: transitivePeerDependencies: - supports-color - builder-util@25.1.7: + builder-util@26.0.11: dependencies: 7zip-bin: 5.2.0 '@types/debug': 4.1.12 - app-builder-bin: 5.0.0-alpha.10 - bluebird-lst: 1.0.9 - builder-util-runtime: 9.2.10 + app-builder-bin: 5.0.0-alpha.12 + builder-util-runtime: 9.3.1 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0 fs-extra: 10.1.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-ci: 3.0.1 js-yaml: 4.1.0 + sanitize-filename: 1.6.3 source-map-support: 0.5.21 stat-mode: 1.0.0 temp-file: 3.4.0 + tiny-async-pool: 1.3.0 transitivePeerDependencies: - supports-color @@ -6563,12 +6463,6 @@ snapshots: caniuse-lite@1.0.30001676: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6636,20 +6530,12 @@ snapshots: collect-v8-coverage@1.0.2: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} - color-support@1.1.3: {} - color2k@2.0.3: {} colord@2.9.3: {} @@ -6683,12 +6569,11 @@ snapshots: concat-map@0.0.1: {} - concurrently@9.2.0: + concurrently@9.2.1: dependencies: chalk: 4.1.2 - lodash: 4.17.21 - rxjs: 7.8.1 - shell-quote: 1.8.1 + rxjs: 7.8.2 + shell-quote: 1.8.3 supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 @@ -6703,8 +6588,6 @@ snapshots: glob: 10.4.5 typescript: 5.9.2 - console-control-strings@1.1.0: {} - convert-source-map@2.0.0: {} cookie@1.0.2: {} @@ -6925,8 +6808,6 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - dequal@2.0.3: {} detect-libc@2.0.4: {} @@ -6950,11 +6831,11 @@ snapshots: minimatch: 3.1.2 p-limit: 3.1.0 - dmg-builder@25.1.8(electron-builder-squirrel-windows@24.13.3): + dmg-builder@26.0.12(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 fs-extra: 10.1.0 iconv-lite: 0.6.3 js-yaml: 4.1.0 @@ -7046,9 +6927,9 @@ snapshots: dependencies: jake: 10.8.7 - electron-builder-squirrel-windows@24.13.3(dmg-builder@25.1.8): + electron-builder-squirrel-windows@24.13.3(dmg-builder@26.0.12): dependencies: - app-builder-lib: 24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) + app-builder-lib: 24.13.3(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) archiver: 5.3.2 builder-util: 24.13.1 fs-extra: 10.1.0 @@ -7056,13 +6937,13 @@ snapshots: - dmg-builder - supports-color - electron-builder@25.1.8(electron-builder-squirrel-windows@24.13.3): + electron-builder@26.0.12(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@24.13.3) - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chalk: 4.1.2 - dmg-builder: 25.1.8(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.0.12(electron-builder-squirrel-windows@24.13.3) fs-extra: 10.1.0 is-ci: 3.0.1 lazy-val: 1.0.5 @@ -7089,12 +6970,13 @@ snapshots: transitivePeerDependencies: - supports-color - electron-publish@25.1.7: + electron-publish@26.0.11: dependencies: '@types/fs-extra': 9.0.13 - builder-util: 25.1.7 - builder-util-runtime: 9.2.10 + builder-util: 26.0.11 + builder-util-runtime: 9.3.1 chalk: 4.1.2 + form-data: 4.0.4 fs-extra: 10.1.0 lazy-val: 1.0.5 mime: 2.6.0 @@ -7116,10 +6998,10 @@ snapshots: transitivePeerDependencies: - supports-color - electron@37.3.1: + electron@38.0.0: dependencies: '@electron/get': 2.0.3 - '@types/node': 22.17.2 + '@types/node': 22.18.1 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -7139,11 +7021,6 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.17.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -7187,8 +7064,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} escape-string-regexp@4.0.0: @@ -7234,12 +7109,12 @@ snapshots: jest-mock: 30.0.0 jest-util: 30.0.0 - expect@30.0.5: + expect@30.1.2: dependencies: - '@jest/expect-utils': 30.0.5 - '@jest/get-type': 30.0.1 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 + '@jest/expect-utils': 30.1.2 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 jest-mock: 30.0.5 jest-util: 30.0.5 @@ -7308,12 +7183,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -7362,17 +7231,6 @@ snapshots: fzy.js@0.4.1: {} - gauge@4.0.4: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -7403,10 +7261,6 @@ snapshots: get-stream@6.0.1: {} - github-url-to-object@4.0.6: - dependencies: - is-url: 1.2.4 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -7499,8 +7353,6 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -7516,8 +7368,6 @@ snapshots: dependencies: has-symbols: 1.0.3 - has-unicode@2.0.1: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -7579,7 +7429,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.1 + agent-base: 7.1.3 debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7596,13 +7446,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.5: - dependencies: - agent-base: 7.1.1 - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 @@ -7711,8 +7554,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-url@1.2.4: {} - isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -7779,22 +7620,22 @@ snapshots: jest-util: 30.0.5 p-limit: 3.1.0 - jest-circus@30.0.5: + jest-circus@30.1.3: dependencies: - '@jest/environment': 30.0.5 - '@jest/expect': 30.0.5 - '@jest/test-result': 30.0.5 + '@jest/environment': 30.1.2 + '@jest/expect': 30.1.2 + '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0 is-generator-fn: 2.1.0 - jest-each: 30.0.5 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 - jest-runtime: 30.0.5 - jest-snapshot: 30.0.5 + jest-each: 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 jest-util: 30.0.5 p-limit: 3.1.0 pretty-format: 30.0.5 @@ -7805,17 +7646,17 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)): + jest-cli@30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)): dependencies: - '@jest/core': 30.0.5(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) - '@jest/test-result': 30.0.5 + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) + '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) + jest-config: 30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) jest-util: 30.0.5 - jest-validate: 30.0.5 + jest-validate: 30.1.0 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' @@ -7824,35 +7665,35 @@ snapshots: - supports-color - ts-node - jest-config@30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)): + jest-config@30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)): dependencies: '@babel/core': 7.27.4 - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.0.5 + '@jest/test-sequencer': 30.1.3 '@jest/types': 30.0.5 - babel-jest: 30.0.5(@babel/core@7.27.4) + babel-jest: 30.1.2(@babel/core@7.27.4) chalk: 4.1.2 ci-info: 4.2.0 deepmerge: 4.3.1 glob: 10.4.5 graceful-fs: 4.2.11 - jest-circus: 30.0.5 + jest-circus: 30.1.3 jest-docblock: 30.0.1 - jest-environment-node: 30.0.5 + jest-environment-node: 30.1.2 jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-runner: 30.0.5 + jest-resolve: 30.1.3 + jest-runner: 30.1.3 jest-util: 30.0.5 - jest-validate: 30.0.5 + jest-validate: 30.1.0 micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 30.0.5 slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.17.2 - ts-node: 10.9.2(@types/node@22.17.2)(typescript@5.9.2) + '@types/node': 22.18.1 + ts-node: 10.9.2(@types/node@22.18.1)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -7864,10 +7705,10 @@ snapshots: chalk: 4.1.2 pretty-format: 30.0.0 - jest-diff@30.0.5: + jest-diff@30.1.2: dependencies: '@jest/diff-sequences': 30.0.1 - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 chalk: 4.1.2 pretty-format: 30.0.5 @@ -7875,54 +7716,54 @@ snapshots: dependencies: detect-newline: 3.1.0 - jest-each@30.0.5: + jest-each@30.1.0: dependencies: - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 '@jest/types': 30.0.5 chalk: 4.1.2 jest-util: 30.0.5 pretty-format: 30.0.5 - jest-environment-jsdom@30.0.5: + jest-environment-jsdom@30.1.2: dependencies: - '@jest/environment': 30.0.5 - '@jest/environment-jsdom-abstract': 30.0.5(jsdom@26.1.0) + '@jest/environment': 30.1.2 + '@jest/environment-jsdom-abstract': 30.1.2(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - jest-environment-node@30.0.5: + jest-environment-node@30.1.2: dependencies: - '@jest/environment': 30.0.5 - '@jest/fake-timers': 30.0.5 + '@jest/environment': 30.1.2 + '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-mock: 30.0.5 jest-util: 30.0.5 - jest-validate: 30.0.5 + jest-validate: 30.1.0 - jest-haste-map@30.0.5: + jest-haste-map@30.1.0: dependencies: '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 30.0.1 jest-util: 30.0.5 - jest-worker: 30.0.5 + jest-worker: 30.1.0 micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - jest-leak-detector@30.0.5: + jest-leak-detector@30.1.0: dependencies: - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 pretty-format: 30.0.5 jest-matcher-utils@30.0.0: @@ -7932,11 +7773,11 @@ snapshots: jest-diff: 30.0.0 pretty-format: 30.0.0 - jest-matcher-utils@30.0.5: + jest-matcher-utils@30.1.2: dependencies: - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 chalk: 4.1.2 - jest-diff: 30.0.5 + jest-diff: 30.1.2 pretty-format: 30.0.5 jest-message-util@30.0.0: @@ -7951,7 +7792,7 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 - jest-message-util@30.0.5: + jest-message-util@30.1.0: dependencies: '@babel/code-frame': 7.27.1 '@jest/types': 30.0.5 @@ -7966,114 +7807,114 @@ snapshots: jest-mock@30.0.0: dependencies: '@jest/types': 30.0.0 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-util: 30.0.0 jest-mock@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-util: 30.0.5 - jest-pnp-resolver@1.2.3(jest-resolve@30.0.5): + jest-pnp-resolver@1.2.3(jest-resolve@30.1.3): optionalDependencies: - jest-resolve: 30.0.5 + jest-resolve: 30.1.3 jest-regex-util@30.0.0: {} jest-regex-util@30.0.1: {} - jest-resolve-dependencies@30.0.5: + jest-resolve-dependencies@30.1.3: dependencies: jest-regex-util: 30.0.1 - jest-snapshot: 30.0.5 + jest-snapshot: 30.1.2 transitivePeerDependencies: - supports-color - jest-resolve@30.0.5: + jest-resolve@30.1.3: dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - jest-pnp-resolver: 1.2.3(jest-resolve@30.0.5) + jest-haste-map: 30.1.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.1.3) jest-util: 30.0.5 - jest-validate: 30.0.5 + jest-validate: 30.1.0 slash: 3.0.0 unrs-resolver: 1.8.1 - jest-runner@30.0.5: + jest-runner@30.1.3: dependencies: - '@jest/console': 30.0.5 - '@jest/environment': 30.0.5 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 + '@jest/console': 30.1.2 + '@jest/environment': 30.1.2 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-docblock: 30.0.1 - jest-environment-node: 30.0.5 - jest-haste-map: 30.0.5 - jest-leak-detector: 30.0.5 - jest-message-util: 30.0.5 - jest-resolve: 30.0.5 - jest-runtime: 30.0.5 + jest-environment-node: 30.1.2 + jest-haste-map: 30.1.0 + jest-leak-detector: 30.1.0 + jest-message-util: 30.1.0 + jest-resolve: 30.1.3 + jest-runtime: 30.1.3 jest-util: 30.0.5 - jest-watcher: 30.0.5 - jest-worker: 30.0.5 + jest-watcher: 30.1.3 + jest-worker: 30.1.0 p-limit: 3.1.0 source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - jest-runtime@30.0.5: + jest-runtime@30.1.3: dependencies: - '@jest/environment': 30.0.5 - '@jest/fake-timers': 30.0.5 - '@jest/globals': 30.0.5 + '@jest/environment': 30.1.2 + '@jest/fake-timers': 30.1.2 + '@jest/globals': 30.1.2 '@jest/source-map': 30.0.1 - '@jest/test-result': 30.0.5 - '@jest/transform': 30.0.5 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 cjs-module-lexer: 2.1.0 collect-v8-coverage: 1.0.2 glob: 10.4.5 graceful-fs: 4.2.11 - jest-haste-map: 30.0.5 - jest-message-util: 30.0.5 + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 jest-mock: 30.0.5 jest-regex-util: 30.0.1 - jest-resolve: 30.0.5 - jest-snapshot: 30.0.5 + jest-resolve: 30.1.3 + jest-snapshot: 30.1.2 jest-util: 30.0.5 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@30.0.5: + jest-snapshot@30.1.2: dependencies: '@babel/core': 7.27.4 '@babel/generator': 7.27.5 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) '@babel/types': 7.27.6 - '@jest/expect-utils': 30.0.5 - '@jest/get-type': 30.0.1 - '@jest/snapshot-utils': 30.0.5 - '@jest/transform': 30.0.5 + '@jest/expect-utils': 30.1.2 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.1.2 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4) chalk: 4.1.2 - expect: 30.0.5 + expect: 30.1.2 graceful-fs: 4.2.11 - jest-diff: 30.0.5 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 + jest-diff: 30.1.2 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 jest-util: 30.0.5 pretty-format: 30.0.5 semver: 7.7.2 @@ -8084,7 +7925,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -8093,7 +7934,7 @@ snapshots: jest-util@30.0.0: dependencies: '@jest/types': 30.0.0 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 ci-info: 4.2.0 graceful-fs: 4.2.11 @@ -8102,26 +7943,26 @@ snapshots: jest-util@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 chalk: 4.1.2 ci-info: 4.2.0 graceful-fs: 4.2.11 picomatch: 4.0.2 - jest-validate@30.0.5: + jest-validate@30.1.0: dependencies: - '@jest/get-type': 30.0.1 + '@jest/get-type': 30.1.0 '@jest/types': 30.0.5 camelcase: 6.3.0 chalk: 4.1.2 leven: 3.1.0 pretty-format: 30.0.5 - jest-watcher@30.0.5: + jest-watcher@30.1.3: dependencies: - '@jest/test-result': 30.0.5 + '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 - '@types/node': 22.17.2 + '@types/node': 22.18.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -8130,31 +7971,31 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest-worker@30.0.5: + jest-worker@30.1.0: dependencies: - '@types/node': 22.17.2 + '@types/node': 22.18.1 '@ungap/structured-clone': 1.3.0 jest-util: 30.0.5 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)): + jest@30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)): dependencies: - '@jest/core': 30.0.5(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) '@jest/types': 30.0.5 import-local: 3.2.0 - jest-cli: 30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) + jest-cli: 30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -8162,8 +8003,6 @@ snapshots: - supports-color - ts-node - jiti@1.21.0: {} - jiti@2.5.1: {} js-tokens@4.0.0: {} @@ -8346,8 +8185,6 @@ snapshots: lowercase-keys@2.0.0: {} - lru-cache@10.2.0: {} - lru-cache@10.4.3: {} lru-cache@11.0.0: {} @@ -8364,9 +8201,9 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.17: + magic-string@0.30.18: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 make-dir@4.0.0: dependencies: @@ -8449,9 +8286,9 @@ snapshots: mdn-data@2.0.30: {} - menubar@9.5.1(electron@37.3.1): + menubar@9.5.1(electron@38.0.0): dependencies: - electron: 37.3.1 + electron: 38.0.0 electron-positioner: 4.1.0 merge-stream@2.0.0: {} @@ -8719,23 +8556,6 @@ snapshots: dependencies: semver: 7.7.2 - node-gyp@9.4.1: - dependencies: - env-paths: 2.2.1 - exponential-backoff: 3.1.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - make-fetch-happen: 10.2.1 - nopt: 6.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - which: 2.0.2 - transitivePeerDependencies: - - bluebird - - supports-color - node-int64@0.4.0: {} node-releases@2.0.18: {} @@ -8752,13 +8572,6 @@ snapshots: dependencies: path-key: 3.1.1 - npmlog@6.0.2: - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -8823,7 +8636,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.2 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -8847,7 +8660,7 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.2.0 + lru-cache: 10.4.3 minipass: 7.1.2 path-scurry@2.0.0: @@ -8914,10 +8727,10 @@ snapshots: dependencies: postcss: 8.5.6 - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.3): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.3): dependencies: cosmiconfig: 9.0.0(typescript@5.9.2) - jiti: 1.21.0 + jiti: 2.5.1 postcss: 8.5.6 semver: 7.7.2 optionalDependencies: @@ -9105,6 +8918,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + proc-log@2.0.1: {} + process-nextick-args@2.0.1: {} progress@2.0.3: {} @@ -9162,11 +8977,11 @@ snapshots: react-is@18.3.1: {} - react-markdown@8.0.7(@types/react@19.1.10)(react@19.1.1): + react-markdown@8.0.7(@types/react@19.1.12)(react@19.1.1): dependencies: '@types/hast': 2.3.10 '@types/prop-types': 15.7.14 - '@types/react': 19.1.10 + '@types/react': 19.1.12 '@types/unist': 2.0.11 comma-separated-tokens: 2.0.3 hast-util-whitespace: 2.0.1 @@ -9184,13 +8999,13 @@ snapshots: transitivePeerDependencies: - supports-color - react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router-dom@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-router: 7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: cookie: 1.0.2 react: 19.1.1 @@ -9327,7 +9142,7 @@ snapshots: rrweb-cssom@0.8.0: {} - rxjs@7.8.1: + rxjs@7.8.2: dependencies: tslib: 2.6.2 @@ -9370,6 +9185,8 @@ snapshots: semver-compare@1.0.0: optional: true + semver@5.7.2: {} + semver@6.3.1: {} semver@7.7.2: {} @@ -9383,8 +9200,6 @@ snapshots: dependencies: randombytes: 2.1.0 - set-blocking@2.0.0: {} - set-cookie-parser@2.7.1: {} shallow-clone@3.0.1: @@ -9399,7 +9214,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.1: {} + shell-quote@1.8.3: {} signal-exit@3.0.7: {} @@ -9556,10 +9371,6 @@ snapshots: transitivePeerDependencies: - supports-color - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -9588,7 +9399,7 @@ snapshots: tailwind-merge@3.3.1: {} - tailwindcss@4.1.12: {} + tailwindcss@4.1.13: {} tapable@2.2.1: {} @@ -9645,6 +9456,10 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + tiny-async-pool@1.3.0: + dependencies: + semver: 5.7.2 + tiny-typed-emitter@2.1.0: {} tinyglobby@0.2.12: @@ -9688,12 +9503,12 @@ snapshots: dependencies: utf8-byte-length: 1.0.4 - ts-jest@29.4.1(@babel/core@7.27.4)(@jest/transform@30.0.5)(@jest/types@30.0.5)(babel-jest@30.0.5(@babel/core@7.27.4))(jest-util@30.0.5)(jest@30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)))(typescript@5.9.2): + ts-jest@29.4.1(@babel/core@7.27.4)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.27.4))(jest-util@30.0.5)(jest@30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)))(typescript@5.9.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.0.5(@types/node@22.17.2)(ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2)) + jest: 30.1.3(@types/node@22.18.1)(ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -9703,29 +9518,29 @@ snapshots: yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.27.4 - '@jest/transform': 30.0.5 + '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - babel-jest: 30.0.5(@babel/core@7.27.4) + babel-jest: 30.1.2(@babel/core@7.27.4) jest-util: 30.0.5 - ts-loader@9.5.2(typescript@5.9.2)(webpack@5.101.3): + ts-loader@9.5.4(typescript@5.9.2)(webpack@5.101.3): dependencies: chalk: 4.1.2 - enhanced-resolve: 5.17.1 + enhanced-resolve: 5.18.3 micromatch: 4.0.8 semver: 7.7.2 source-map: 0.7.4 typescript: 5.9.2 webpack: 5.101.3(webpack-cli@6.0.1) - ts-node@10.9.2(@types/node@22.17.2)(typescript@5.9.2): + ts-node@10.9.2(@types/node@22.18.1)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.17.2 + '@types/node': 22.18.1 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -9829,11 +9644,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-electron-app@3.1.1: - dependencies: - github-url-to-object: 4.0.6 - ms: 2.1.2 - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -9971,10 +9781,6 @@ snapshots: dependencies: isexe: 2.0.0 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - wildcard@2.0.1: {} wordwrap@1.0.0: {} diff --git a/scripts/afterPack.js b/scripts/afterPack.js index ee66116b5..53174b9a7 100644 --- a/scripts/afterPack.js +++ b/scripts/afterPack.js @@ -1,6 +1,5 @@ const path = require('node:path'); const fs = require('node:fs'); -const { AfterPackContext } = require('electron-builder'); const builderConfig = require('../config/electron-builder'); const electronLanguages = builderConfig.electronLanguages; @@ -11,7 +10,7 @@ function logAfterPackProgress(msg) { } /** - * @param {AfterPackContext} context + * @param {import('electron-builder').AfterPackContext} context */ const afterPack = async (context) => { logAfterPackProgress('Starting...'); diff --git a/scripts/afterSign.js b/scripts/afterSign.js index 57a6b688f..431e47434 100644 --- a/scripts/afterSign.js +++ b/scripts/afterSign.js @@ -1,5 +1,4 @@ const { notarize } = require('@electron/notarize'); -const { AfterPackContext } = require('electron-builder'); function logAfterSignProgress(msg) { // biome-ignore lint/suspicious/noConsole: log notarizing progress @@ -7,7 +6,7 @@ function logAfterSignProgress(msg) { } /** - * @param {AfterPackContext} context + * @param {import('electron-builder').AfterPackContext} context */ const afterSign = async (context) => { logAfterSignProgress('Starting...'); diff --git a/scripts/delete-source-maps.ts b/scripts/delete-source-maps.ts deleted file mode 100644 index 8d61b989b..000000000 --- a/scripts/delete-source-maps.ts +++ /dev/null @@ -1,16 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -import { rimrafSync } from 'rimraf'; - -import webpackPaths from '../config/webpack.paths'; - -function deleteSourceMaps() { - if (fs.existsSync(webpackPaths.buildPath)) { - rimrafSync(path.join(webpackPaths.buildPath, '*.map'), { - glob: true, - }); - } -} - -deleteSourceMaps(); diff --git a/src/main/events.test.ts b/src/main/events.test.ts new file mode 100644 index 000000000..7960e8767 --- /dev/null +++ b/src/main/events.test.ts @@ -0,0 +1,58 @@ +import { EVENTS } from '../shared/events'; + +const onMock = jest.fn(); +const handleMock = jest.fn(); + +jest.mock('electron', () => ({ + ipcMain: { + on: (...args: unknown[]) => onMock(...args), + handle: (...args: unknown[]) => handleMock(...args), + }, +})); + +import type { Menubar } from 'menubar'; + +import { handleMainEvent, onMainEvent, sendRendererEvent } from './events'; + +type MockMenubar = { window: { webContents: { send: jest.Mock } } }; + +describe('main/events', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('onMainEvent registers ipcMain.on listener', () => { + const listener = jest.fn(); + onMainEvent( + EVENTS.WINDOW_SHOW, + listener as unknown as (e: Electron.IpcMainEvent, d: unknown) => void, + ); + expect(onMock).toHaveBeenCalledWith(EVENTS.WINDOW_SHOW, listener); + }); + + it('handleMainEvent registers ipcMain.handle listener', () => { + const listener = jest.fn(); + handleMainEvent( + EVENTS.VERSION, + listener as unknown as ( + e: Electron.IpcMainInvokeEvent, + d: unknown, + ) => void, + ); + expect(handleMock).toHaveBeenCalledWith(EVENTS.VERSION, listener); + }); + + it('sendRendererEvent forwards event to webContents with data', () => { + const send = jest.fn(); + const mb: MockMenubar = { window: { webContents: { send } } }; + sendRendererEvent(mb as unknown as Menubar, EVENTS.UPDATE_TITLE, 'title'); + expect(send).toHaveBeenCalledWith(EVENTS.UPDATE_TITLE, 'title'); + }); + + it('sendRendererEvent forwards event without data', () => { + const send = jest.fn(); + const mb: MockMenubar = { window: { webContents: { send } } }; + sendRendererEvent(mb as unknown as Menubar, EVENTS.RESET_APP); + expect(send).toHaveBeenCalledWith(EVENTS.RESET_APP, undefined); + }); +}); diff --git a/src/main/events.ts b/src/main/events.ts new file mode 100644 index 000000000..35273c4fc --- /dev/null +++ b/src/main/events.ts @@ -0,0 +1,42 @@ +import { ipcMain } from 'electron'; +import type { Menubar } from 'menubar'; + +import type { EventData, EventType } from '../shared/events'; + +/** + * Handle main event without expecting a response + * @param event + * @param listener + */ +export function onMainEvent( + event: EventType, + listener: (event: Electron.IpcMainEvent, args: EventData) => void, +) { + ipcMain.on(event, listener); +} + +/** + * Handle main event and return a response + * @param event + * @param listener + */ +export function handleMainEvent( + event: EventType, + listener: (event: Electron.IpcMainInvokeEvent, data: EventData) => void, +) { + ipcMain.handle(event, listener); +} + +/** + * Send main event to renderer + * @param mb the menubar instance + * @param event the type of event to send + * @param data the data to send with the event + */ +export function sendRendererEvent( + mb: Menubar, + event: EventType, + data?: string, +) { + mb.window.webContents.send(event, data); +} diff --git a/src/main/first-run.test.ts b/src/main/first-run.test.ts new file mode 100644 index 000000000..f301037a2 --- /dev/null +++ b/src/main/first-run.test.ts @@ -0,0 +1,108 @@ +import path from 'node:path'; + +// Mocks +const existsSync = jest.fn(); +const mkdirSync = jest.fn(); +const writeFileSync = jest.fn(); +jest.mock('node:fs', () => ({ + __esModule: true, + default: { + existsSync: (...a: unknown[]) => existsSync(...a), + mkdirSync: (...a: unknown[]) => mkdirSync(...a), + writeFileSync: (...a: unknown[]) => writeFileSync(...a), + }, + existsSync: (...a: unknown[]) => existsSync(...a), + mkdirSync: (...a: unknown[]) => mkdirSync(...a), + writeFileSync: (...a: unknown[]) => writeFileSync(...a), +})); + +const moveToApplicationsFolder = jest.fn(); +const isInApplicationsFolder = jest.fn(() => false); +const getPath = jest.fn(() => '/User/Data'); + +const showMessageBox = jest.fn(async () => ({ response: 0 })); + +jest.mock('electron', () => ({ + app: { + getPath: () => getPath(), + isInApplicationsFolder: () => isInApplicationsFolder(), + moveToApplicationsFolder: () => moveToApplicationsFolder(), + }, + dialog: { showMessageBox: () => showMessageBox() }, +})); + +const logError = jest.fn(); +jest.mock('../shared/logger', () => ({ + logError: (...a: unknown[]) => logError(...a), +})); + +let mac = true; +jest.mock('../shared/platform', () => ({ isMacOS: () => mac })); + +import { APPLICATION } from '../shared/constants'; + +import { onFirstRunMaybe } from './first-run'; + +describe('main/first-run', () => { + beforeEach(() => { + jest.clearAllMocks(); + mac = true; + }); + + function configPath() { + return path.join('/User/Data', 'FirstRun', APPLICATION.FIRST_RUN_FOLDER); + } + + it('creates first-run marker when not existing and returns true', async () => { + existsSync.mockReturnValueOnce(false); // marker absent + existsSync.mockReturnValueOnce(false); // folder absent + await onFirstRunMaybe(); + expect(mkdirSync).toHaveBeenCalledWith(path.dirname(configPath())); + expect(writeFileSync).toHaveBeenCalledWith(configPath(), ''); + }); + + it('skips writing when marker exists', async () => { + existsSync.mockReturnValueOnce(true); // marker present + await onFirstRunMaybe(); + expect(writeFileSync).not.toHaveBeenCalled(); + expect(mkdirSync).not.toHaveBeenCalled(); + }); + + it('handles fs write error gracefully', async () => { + existsSync.mockReturnValueOnce(false); // marker absent + existsSync.mockReturnValueOnce(true); // folder exists + writeFileSync.mockImplementation(() => { + throw new Error('fail'); + }); + await onFirstRunMaybe(); + expect(logError).toHaveBeenCalledWith( + 'isFirstRun', + 'Unable to write firstRun file', + expect.any(Error), + ); + }); + + it('prompts and moves app on macOS when user accepts', async () => { + existsSync.mockReturnValueOnce(false); // marker + existsSync.mockReturnValueOnce(false); // folder + showMessageBox.mockResolvedValueOnce({ response: 0 }); + await onFirstRunMaybe(); + expect(moveToApplicationsFolder).toHaveBeenCalled(); + }); + + it('does not move when user declines', async () => { + existsSync.mockReturnValueOnce(false); + existsSync.mockReturnValueOnce(false); + showMessageBox.mockResolvedValueOnce({ response: 1 }); + await onFirstRunMaybe(); + expect(moveToApplicationsFolder).not.toHaveBeenCalled(); + }); + + it('skips prompt on non-macOS', async () => { + mac = false; + existsSync.mockReturnValueOnce(false); + existsSync.mockReturnValueOnce(false); + await onFirstRunMaybe(); + expect(showMessageBox).not.toHaveBeenCalled(); + }); +}); diff --git a/src/main/first-run.ts b/src/main/first-run.ts index 80ef60a65..c5fe6daeb 100644 --- a/src/main/first-run.ts +++ b/src/main/first-run.ts @@ -13,7 +13,9 @@ export async function onFirstRunMaybe() { } } -// Ask user if the app should be moved to the applications folder. +/** + * Ask user if the app should be moved to the applications folder (masOS). + */ async function promptMoveToApplicationsFolder() { if (!isMacOS()) return; @@ -37,7 +39,10 @@ const getConfigPath = () => { return path.join(userDataPath, 'FirstRun', APPLICATION.FIRST_RUN_FOLDER); }; -// Whether or not the app is being run for the first time. +/** + * Determine if this is the first run of the application by checking for the existence of a specific file. + * @returns true if this is the first run, false otherwise + */ function isFirstRun() { const configPath = getConfigPath(); diff --git a/src/main/index.ts b/src/main/index.ts index 9336e2c8d..5c3de8ec5 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,35 +1,64 @@ -import { app, globalShortcut, ipcMain as ipc, safeStorage } from 'electron'; +import path from 'path'; + +import { + app, + type BrowserWindowConstructorOptions, + globalShortcut, + safeStorage, + shell, +} from 'electron'; import log from 'electron-log'; import { menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; -import { namespacedEvent } from '../shared/events'; +import { + EVENTS, + type IAutoLaunch, + type IKeyboardShortcut, + type IOpenExternal, +} from '../shared/events'; import { logInfo, logWarn } from '../shared/logger'; -import { isLinux, isMacOS, isWindows } from '../shared/platform'; +import { isLinux, isWindows } from '../shared/platform'; + +import { handleMainEvent, onMainEvent, sendRendererEvent } from './events'; import { onFirstRunMaybe } from './first-run'; import { TrayIcons } from './icons'; import MenuBuilder from './menu'; -import Updater from './updater'; +import AppUpdater from './updater'; log.initialize(); -const browserWindowOpts = { +/** + * File paths + */ +const preloadFilePath = path.join(__dirname, 'preload.js'); +const indexHtmlFilePath = `file://${__dirname}/index.html`; +const notificationSoundFilePath = path.join( + __dirname, + '..', + 'assets', + 'sounds', + APPLICATION.NOTIFICATION_SOUND, +); +const twemojiDirPath = path.join(__dirname, 'images', 'twemoji'); + +const browserWindowOpts: BrowserWindowConstructorOptions = { width: 500, height: 400, minWidth: 500, minHeight: 400, resizable: false, skipTaskbar: true, // Hide the app from the Windows taskbar - // TODO #700 refactor to use preload script with a context bridge webPreferences: { - nodeIntegration: true, - contextIsolation: false, + preload: preloadFilePath, + contextIsolation: true, + nodeIntegration: false, }, }; const mb = menubar({ icon: TrayIcons.idle, - index: `file://${__dirname}/index.html`, + index: indexHtmlFilePath, browserWindow: browserWindowOpts, preloadWindow: true, showDockIcon: false, // Hide the app from the macOS dock @@ -43,20 +72,15 @@ const protocol = process.env.NODE_ENV === 'development' ? 'gitify-dev' : 'gitify'; app.setAsDefaultProtocolClient(protocol); -if (isMacOS() || isWindows()) { - /** - * Electron Auto Updater only supports macOS and Windows - * https://github.com/electron/update-electron-app - */ - const updater = new Updater(mb, menuBuilder); - updater.initialize(); -} +const appUpdater = new AppUpdater(mb, menuBuilder); let shouldUseAlternateIdleIcon = false; app.whenReady().then(async () => { await onFirstRunMaybe(); + appUpdater.start(); + mb.on('ready', () => { mb.app.setAppUserModelId(APPLICATION.ID); @@ -117,30 +141,32 @@ app.whenReady().then(async () => { } /** - * Gitify custom IPC events + * Gitify custom IPC events - no response expected */ - ipc.handle(namespacedEvent('version'), () => app.getVersion()); + onMainEvent(EVENTS.WINDOW_SHOW, () => mb.showWindow()); - ipc.on(namespacedEvent('window-show'), () => mb.showWindow()); + onMainEvent(EVENTS.WINDOW_HIDE, () => mb.hideWindow()); - ipc.on(namespacedEvent('window-hide'), () => mb.hideWindow()); + onMainEvent(EVENTS.QUIT, () => mb.app.quit()); - ipc.on(namespacedEvent('quit'), () => mb.app.quit()); + onMainEvent(EVENTS.OPEN_EXTERNAL, (_, { url, activate }: IOpenExternal) => + shell.openExternal(url, { activate: activate }), + ); - ipc.on( - namespacedEvent('use-alternate-idle-icon'), - (_, useAlternateIdleIcon) => { + onMainEvent( + EVENTS.USE_ALTERNATE_IDLE_ICON, + (_, useAlternateIdleIcon: boolean) => { shouldUseAlternateIdleIcon = useAlternateIdleIcon; }, ); - ipc.on(namespacedEvent('icon-error'), () => { + onMainEvent(EVENTS.ICON_ERROR, () => { if (!mb.tray.isDestroyed()) { mb.tray.setImage(TrayIcons.error); } }); - ipc.on(namespacedEvent('icon-active'), () => { + onMainEvent(EVENTS.ICON_ACTIVE, () => { if (!mb.tray.isDestroyed()) { mb.tray.setImage( menuBuilder.isUpdateAvailable() @@ -150,7 +176,7 @@ app.whenReady().then(async () => { } }); - ipc.on(namespacedEvent('icon-idle'), () => { + onMainEvent(EVENTS.ICON_IDLE, () => { if (!mb.tray.isDestroyed()) { if (shouldUseAlternateIdleIcon) { mb.tray.setImage( @@ -168,15 +194,15 @@ app.whenReady().then(async () => { } }); - ipc.on(namespacedEvent('update-title'), (_, title) => { + onMainEvent(EVENTS.UPDATE_TITLE, (_, title: string) => { if (!mb.tray.isDestroyed()) { mb.tray.setTitle(title); } }); - ipc.on( - namespacedEvent('update-keyboard-shortcut'), - (_, { enabled, keyboardShortcut }) => { + onMainEvent( + EVENTS.UPDATE_KEYBOARD_SHORTCUT, + (_, { enabled, keyboardShortcut }: IKeyboardShortcut) => { if (!enabled) { globalShortcut.unregister(keyboardShortcut); return; @@ -192,18 +218,31 @@ app.whenReady().then(async () => { }, ); - ipc.on(namespacedEvent('update-auto-launch'), (_, settings) => { + onMainEvent(EVENTS.UPDATE_AUTO_LAUNCH, (_, settings: IAutoLaunch) => { app.setLoginItemSettings(settings); }); -}); -// Safe Storage -ipc.handle(namespacedEvent('safe-storage-encrypt'), (_, settings) => { - return safeStorage.encryptString(settings).toString('base64'); -}); + /** + * Gitify custom IPC events - response expected + */ -ipc.handle(namespacedEvent('safe-storage-decrypt'), (_, settings) => { - return safeStorage.decryptString(Buffer.from(settings, 'base64')); + handleMainEvent(EVENTS.VERSION, () => app.getVersion()); + + handleMainEvent(EVENTS.NOTIFICATION_SOUND_PATH, () => { + return notificationSoundFilePath; + }); + + handleMainEvent(EVENTS.TWEMOJI_DIRECTORY, () => { + return twemojiDirPath; + }); + + handleMainEvent(EVENTS.SAFE_STORAGE_ENCRYPT, (_, value: string) => { + return safeStorage.encryptString(value).toString('base64'); + }); + + handleMainEvent(EVENTS.SAFE_STORAGE_DECRYPT, (_, value: string) => { + return safeStorage.decryptString(Buffer.from(value, 'base64')); + }); }); // Handle gitify:// custom protocol URL events for OAuth 2.0 callback @@ -216,6 +255,6 @@ app.on('open-url', (event, url) => { const handleURL = (url: string) => { if (url.startsWith(`${protocol}://`)) { logInfo('main:handleUrl', `forwarding URL ${url} to renderer process`); - mb.window.webContents.send(namespacedEvent('auth-callback'), url); + sendRendererEvent(mb, EVENTS.AUTH_CALLBACK, url); } }; diff --git a/src/main/menu.test.ts b/src/main/menu.test.ts index 5055c6b3b..31f65605c 100644 --- a/src/main/menu.test.ts +++ b/src/main/menu.test.ts @@ -1,100 +1,269 @@ -import { Menu, MenuItem } from 'electron'; +import { Menu, MenuItem, shell } from 'electron'; +import { autoUpdater } from 'electron-updater'; import type { Menubar } from 'menubar'; +import { APPLICATION } from '../shared/constants'; + import MenuBuilder from './menu'; +import { openLogsDirectory, resetApp, takeScreenshot } from './utils'; + +jest.mock('electron', () => { + const MenuItem = jest + .fn() + .mockImplementation((opts: Record) => opts); + return { + Menu: { + buildFromTemplate: jest.fn(), + }, + MenuItem, + shell: { openExternal: jest.fn() }, + }; +}); -jest.mock('electron', () => ({ - Menu: { - buildFromTemplate: jest.fn(), +jest.mock('electron-updater', () => ({ + autoUpdater: { + checkForUpdatesAndNotify: jest.fn(), + quitAndInstall: jest.fn(), }, - MenuItem: jest.fn(), +})); + +jest.mock('./utils', () => ({ + takeScreenshot: jest.fn(), + openLogsDirectory: jest.fn(), + resetApp: jest.fn(), })); describe('main/menu.ts', () => { let menubar: Menubar; let menuBuilder: MenuBuilder; + /** Helper: find MenuItem config captured via MenuItem mock by label */ + const getMenuItemConfigByLabel = (label: string) => + (MenuItem as unknown as jest.Mock).mock.calls.find( + ([arg]) => (arg as { label?: string }).label === label, + )?.[0] as + | { + label?: string; + enabled?: boolean; + visible?: boolean; + click?: () => void; + } + | undefined; + + /** Lightweight type describing the (subset) of fields we inspect on template items */ + type TemplateItem = { + label?: string; + role?: string; + accelerator?: string; + submenu?: TemplateItem[]; + click?: () => void; + }; + + /** Helper: build menu & return template (first arg passed to buildFromTemplate) */ + const buildAndGetTemplate = () => { + menuBuilder.buildMenu(); + return (Menu.buildFromTemplate as jest.Mock).mock.calls.slice( + -1, + )[0][0] as TemplateItem[]; + }; + beforeEach(() => { + jest.clearAllMocks(); + menubar = { app: { quit: jest.fn() } } as unknown as Menubar; menuBuilder = new MenuBuilder(menubar); }); - it('should create menu items correctly', () => { - expect(MenuItem).toHaveBeenCalledWith({ - label: 'Check for updates', - enabled: true, - click: expect.any(Function), + describe('checkForUpdatesMenuItem', () => { + it('default menu configuration', () => { + expect(MenuItem).toHaveBeenCalledWith({ + label: 'Check for updates', + enabled: true, + click: expect.any(Function), + }); }); - expect(MenuItem).toHaveBeenCalledWith({ - label: 'No updates available', - enabled: false, - visible: false, + it('should enable menu item', () => { + menuBuilder.setCheckForUpdatesMenuEnabled(true); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(true); }); - expect(MenuItem).toHaveBeenCalledWith({ - label: 'An update is available', - enabled: false, - visible: false, + it('should disable menu item', () => { + menuBuilder.setCheckForUpdatesMenuEnabled(false); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(false); }); + }); - expect(MenuItem).toHaveBeenCalledWith({ - label: 'Restart to install update', - enabled: true, - visible: false, - click: expect.any(Function), + describe('noUpdateAvailableMenuItem', () => { + it('default menu configuration', () => { + expect(MenuItem).toHaveBeenCalledWith({ + label: 'No updates available', + enabled: false, + visible: false, + }); }); - }); - it('should build menu correctly', () => { - menuBuilder.buildMenu(); - expect(Menu.buildFromTemplate).toHaveBeenCalledWith(expect.any(Array)); - }); + it('should show menu item', () => { + menuBuilder.setNoUpdateAvailableMenuVisibility(true); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(true); + }); - it('should enable check for updates menu item', () => { - menuBuilder.setCheckForUpdatesMenuEnabled(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(true); + it('should hide menu item', () => { + menuBuilder.setNoUpdateAvailableMenuVisibility(false); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(false); + }); }); - it('should disable check for updates menu item', () => { - menuBuilder.setCheckForUpdatesMenuEnabled(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(false); - }); + describe('updateAvailableMenuItem', () => { + it('default menu configuration', () => { + expect(MenuItem).toHaveBeenCalledWith({ + label: 'An update is available', + enabled: false, + visible: false, + }); + }); - it('should show no update available menu item', () => { - menuBuilder.setNoUpdateAvailableMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(true); - }); + it('should show menu item', () => { + menuBuilder.setUpdateAvailableMenuVisibility(true); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(true); + }); - it('should hide no update available menu item', () => { - menuBuilder.setNoUpdateAvailableMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(false); + it('should hide menu item', () => { + menuBuilder.setUpdateAvailableMenuVisibility(false); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(false); + }); }); - it('should show update available menu item', () => { - menuBuilder.setUpdateAvailableMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(true); - }); + describe('updateReadyForInstallMenuItem', () => { + it('default menu configuration', () => { + expect(MenuItem).toHaveBeenCalledWith({ + label: 'Restart to install update', + enabled: true, + visible: false, + click: expect.any(Function), + }); + }); - it('should hide update available menu item', () => { - menuBuilder.setUpdateAvailableMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(false); + it('should show menu item', () => { + menuBuilder.setUpdateReadyForInstallMenuVisibility(true); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(true); + }); + + it('should hide menu item', () => { + menuBuilder.setUpdateReadyForInstallMenuVisibility(false); + // biome-ignore lint/complexity/useLiteralKeys: This is a test + expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(false); + }); }); - it('should show update ready for install menu item', () => { - menuBuilder.setUpdateReadyForInstallMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(true); + describe('click handlers', () => { + it('invokes autoUpdater.checkForUpdatesAndNotify when clicking "Check for updates"', () => { + const cfg = getMenuItemConfigByLabel('Check for updates'); + expect(cfg).toBeDefined(); + cfg.click(); + expect(autoUpdater.checkForUpdatesAndNotify).toHaveBeenCalled(); + }); + + it('invokes autoUpdater.quitAndInstall when clicking "Restart to install update"', () => { + const cfg = getMenuItemConfigByLabel('Restart to install update'); + expect(cfg).toBeDefined(); + cfg.click(); + expect(autoUpdater.quitAndInstall).toHaveBeenCalled(); + }); + + it('developer submenu click actions execute expected functions', () => { + const template = buildAndGetTemplate(); + const devEntry = template.find( + (item) => item?.label === 'Developer', + ) as TemplateItem; + expect(devEntry).toBeDefined(); + const submenu = devEntry.submenu; + const clickByLabel = (label: string) => + submenu.find((i) => i.label === label)?.click?.(); + + clickByLabel('Take Screenshot'); + expect(takeScreenshot).toHaveBeenCalledWith(menubar); + + clickByLabel('View Application Logs'); + expect(openLogsDirectory).toHaveBeenCalled(); + + clickByLabel('Visit Repository'); + expect(shell.openExternal).toHaveBeenCalledWith( + `https://github.com/${APPLICATION.REPO_SLUG}`, + ); + + clickByLabel(`Reset ${APPLICATION.NAME}`); + expect(resetApp).toHaveBeenCalledWith(menubar); + }); + + it('website menu item opens external URL', () => { + const template = buildAndGetTemplate(); + const item = template.find((i) => i.label === 'Visit Website'); + item.click(); + expect(shell.openExternal).toHaveBeenCalledWith(APPLICATION.WEBSITE); + }); + + it('quit menu item quits the app', () => { + const template = buildAndGetTemplate(); + const item = template.find((i) => i.label === `Quit ${APPLICATION.NAME}`); + item.click(); + expect(menubar.app.quit).toHaveBeenCalled(); + }); + + it('developer submenu includes expected static accelerators', () => { + const template = buildAndGetTemplate(); + const devEntry = template.find( + (item) => item?.label === 'Developer', + ) as TemplateItem; + const reloadItem = devEntry.submenu.find((i) => i.role === 'reload'); + expect(reloadItem?.accelerator).toBe('CommandOrControl+R'); + }); }); - it('should show update ready for install menu item', () => { - menuBuilder.setUpdateReadyForInstallMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test - expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(false); + describe('platform-specific accelerators', () => { + // Use isolateModules so we can alter the isMacOS return value before importing MenuBuilder + const buildTemplateWithPlatform = (isMac: boolean) => { + jest.isolateModules(() => { + jest.doMock('../shared/platform', () => ({ isMacOS: () => isMac })); + // re-mock electron for isolated module context (shared mock factory already defined globally) + // Clear prior captured calls + (Menu.buildFromTemplate as jest.Mock).mockClear(); + const MB = require('./menu').default as typeof MenuBuilder; + const mb = new MB({ app: { quit: jest.fn() } } as unknown as Menubar); + mb.buildMenu(); + }); + // Return the newest template captured + return (Menu.buildFromTemplate as jest.Mock).mock.calls.slice( + -1, + )[0][0] as TemplateItem[]; + }; + + it('uses mac accelerator for toggleDevTools when on macOS', () => { + const template = buildTemplateWithPlatform(true); + const devEntry = template.find( + (i) => i?.label === 'Developer', + ) as TemplateItem; + const toggleItem = devEntry.submenu.find( + (i) => i.role === 'toggleDevTools', + ); + expect(toggleItem?.accelerator).toBe('Alt+Cmd+I'); + }); + + it('uses non-mac accelerator for toggleDevTools otherwise', () => { + const template = buildTemplateWithPlatform(false); + const devEntry = template.find( + (i) => i?.label === 'Developer', + ) as TemplateItem; + const toggleItem = devEntry.submenu.find( + (i) => i.role === 'toggleDevTools', + ); + expect(toggleItem?.accelerator).toBe('Ctrl+Shift+I'); + }); }); }); diff --git a/src/main/menu.ts b/src/main/menu.ts index 7465977e9..9caeae80e 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -3,7 +3,8 @@ import { autoUpdater } from 'electron-updater'; import type { Menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; -import { isMacOS, isWindows } from '../shared/platform'; +import { isMacOS } from '../shared/platform'; + import { openLogsDirectory, resetApp, takeScreenshot } from './utils'; export default class MenuBuilder { @@ -21,11 +22,7 @@ export default class MenuBuilder { label: 'Check for updates', enabled: true, click: () => { - if (isMacOS() || isWindows()) { - autoUpdater.checkForUpdatesAndNotify(); - } else { - shell.openExternal(APPLICATION.WEBSITE); - } + autoUpdater.checkForUpdatesAndNotify(); }, }); @@ -78,6 +75,12 @@ export default class MenuBuilder { label: 'View Application Logs', click: () => openLogsDirectory(), }, + { + label: 'Visit Repository', + click: () => { + shell.openExternal(`https://github.com/${APPLICATION.REPO_SLUG}`); + }, + }, { label: `Reset ${APPLICATION.NAME}`, click: () => { @@ -87,6 +90,12 @@ export default class MenuBuilder { ], }, { type: 'separator' }, + { + label: 'Visit Website', + click: () => { + shell.openExternal(APPLICATION.WEBSITE); + }, + }, { label: `Quit ${APPLICATION.NAME}`, accelerator: 'CommandOrControl+Q', diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts new file mode 100644 index 000000000..3ff570ebd --- /dev/null +++ b/src/main/updater.test.ts @@ -0,0 +1,229 @@ +import { dialog } from 'electron'; +import type { Menubar } from 'menubar'; + +import { APPLICATION } from '../shared/constants'; +import { logError, logInfo } from '../shared/logger'; + +jest.mock('../shared/logger', () => ({ + logInfo: jest.fn(), + logError: jest.fn(), +})); + +import MenuBuilder from './menu'; +import AppUpdater from './updater'; + +// Mock electron-updater with an EventEmitter-like interface +type UpdateDownloadedEvent = { releaseName: string }; +type ListenerArgs = UpdateDownloadedEvent | object | undefined; +type Listener = (arg: ListenerArgs) => void; +type ListenerMap = Record; +const listeners: ListenerMap = {}; + +jest.mock('electron-updater', () => ({ + autoUpdater: { + on: jest.fn((event: string, cb: Listener) => { + if (!listeners[event]) listeners[event] = []; + listeners[event].push(cb); + return this; + }), + checkForUpdatesAndNotify: jest.fn().mockResolvedValue(undefined), + quitAndInstall: jest.fn(), + }, +})); + +// Mock electron (dialog + basic Menu API used by MenuBuilder constructor) +jest.mock('electron', () => { + const MenuItem = jest.fn().mockImplementation((opts: unknown) => opts); + return { + dialog: { showMessageBox: jest.fn() }, + MenuItem, + Menu: { buildFromTemplate: jest.fn() }, + shell: { openExternal: jest.fn() }, + }; +}); + +// Utility to emit mocked autoUpdater events +const emit = (event: string, arg?: ListenerArgs) => { + (listeners[event] || []).forEach((cb) => { + cb(arg); + }); +}; + +// Re-import autoUpdater after mocking +import { autoUpdater } from 'electron-updater'; + +describe('main/updater.ts', () => { + let menubar: Menubar; + class TestMenuBuilder extends MenuBuilder { + public setCheckForUpdatesMenuEnabled = jest.fn(); + public setNoUpdateAvailableMenuVisibility = jest.fn(); + public setUpdateAvailableMenuVisibility = jest.fn(); + public setUpdateReadyForInstallMenuVisibility = jest.fn(); + constructor(mb: Menubar) { + super(mb); + } + } + let menuBuilder: TestMenuBuilder; + let updater: AppUpdater; + + beforeEach(() => { + jest.clearAllMocks(); + for (const k of Object.keys(listeners)) delete listeners[k]; + + menubar = { + app: { + isPackaged: true, + // updater.initialize is now only called after app is ready externally + on: jest.fn(), + }, + tray: { setToolTip: jest.fn() }, + } as unknown as Menubar; + + menuBuilder = new TestMenuBuilder(menubar); + updater = new AppUpdater(menubar, menuBuilder); + }); + + describe('update available dialog', () => { + it('shows dialog with expected message and does NOT install when user chooses Later', async () => { + (dialog.showMessageBox as jest.Mock).mockResolvedValue({ response: 1 }); // "Later" + + await updater.start(); + + // Simulate update downloaded event + const releaseName = 'v1.2.3'; + emit('update-downloaded', { releaseName }); + + expect(dialog.showMessageBox).toHaveBeenCalledWith( + expect.objectContaining({ + message: expect.stringContaining( + `${APPLICATION.NAME} ${releaseName} has been downloaded`, + ), + buttons: ['Restart', 'Later'], + }), + ); + expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled(); + // Menu state updates invoked + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setUpdateReadyForInstallMenuVisibility, + ).toHaveBeenCalledWith(true); + }); + + it('invokes quitAndInstall when user clicks Restart', async () => { + (dialog.showMessageBox as jest.Mock).mockResolvedValue({ response: 0 }); // "Restart" + + await updater.start(); + emit('update-downloaded', { releaseName: 'v9.9.9' }); + // Allow then() of showMessageBox promise to resolve + await Promise.resolve(); + + expect(autoUpdater.quitAndInstall).toHaveBeenCalled(); + }); + }); + + describe('update event handlers & scheduling', () => { + it('skips when app is not packaged', async () => { + Object.defineProperty(menubar.app, 'isPackaged', { value: false }); + await updater.start(); + expect(logInfo).toHaveBeenCalledWith( + 'app updater', + 'Skipping updater since app is in development mode', + ); + expect(autoUpdater.checkForUpdatesAndNotify).not.toHaveBeenCalled(); + }); + + it('handles checking-for-update', async () => { + await updater.start(); + emit('checking-for-update'); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setNoUpdateAvailableMenuVisibility, + ).toHaveBeenCalledWith(false); + }); + + it('handles update-available', async () => { + await updater.start(); + emit('update-available'); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + true, + ); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith( + expect.stringContaining('A new update is available'), + ); + }); + + it('handles download-progress', async () => { + await updater.start(); + emit('download-progress', { percent: 12.3456 }); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith( + expect.stringContaining('12.35%'), + ); + }); + + it('handles update-not-available', async () => { + await updater.start(); + emit('update-not-available'); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + true, + ); + expect( + menuBuilder.setNoUpdateAvailableMenuVisibility, + ).toHaveBeenCalledWith(true); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( + false, + ); + expect( + menuBuilder.setUpdateReadyForInstallMenuVisibility, + ).toHaveBeenCalledWith(false); + }); + + it('handles update-cancelled (reset state)', async () => { + await updater.start(); + emit('update-cancelled'); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( + true, + ); + }); + + it('handles error (reset + logError)', async () => { + await updater.start(); + const err = new Error('failure'); + emit('error', err); + expect(logError).toHaveBeenCalledWith( + 'auto updater', + 'Error checking for update', + err, + ); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); + }); + + it('performs initial check and schedules periodic checks', async () => { + const originalSetInterval = global.setInterval; + const setIntervalSpy = jest + .spyOn(global, 'setInterval') + .mockImplementation(((fn: () => void) => { + fn(); + return 0 as unknown as NodeJS.Timer; + }) as unknown as typeof setInterval); + try { + await updater.start(); + // initial + immediate scheduled invocation + expect( + (autoUpdater.checkForUpdatesAndNotify as jest.Mock).mock.calls.length, + ).toBe(2); + expect(setIntervalSpy).toHaveBeenCalledWith( + expect.any(Function), + APPLICATION.UPDATE_CHECK_INTERVAL_MS, + ); + } finally { + setIntervalSpy.mockRestore(); + global.setInterval = originalSetInterval; + } + }); + }); +}); diff --git a/src/main/updater.ts b/src/main/updater.ts index 2c80fb4b5..60dac989a 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -1,37 +1,65 @@ +import { dialog, type MessageBoxOptions } from 'electron'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import type { Menubar } from 'menubar'; -import { updateElectronApp } from 'update-electron-app'; import { APPLICATION } from '../shared/constants'; import { logError, logInfo } from '../shared/logger'; + import type MenuBuilder from './menu'; -export default class Updater { +/** + * Updater class for handling application updates. + * + * Supports scheduled and manual updates for all platforms. + * + * Documentation: https://www.electron.build/auto-update + * + * NOTE: previously used update-electron-app (Squirrel-focused, no Linux + NSIS). electron-updater gives cross-platform support. + * Caller guarantees app is ready before initialize() is invoked. + */ +export default class AppUpdater { private readonly menubar: Menubar; private readonly menuBuilder: MenuBuilder; + private started = false; constructor(menubar: Menubar, menuBuilder: MenuBuilder) { this.menubar = menubar; this.menuBuilder = menuBuilder; + autoUpdater.logger = log; } - initialize(): void { - updateElectronApp({ - updateInterval: '24 hours', - logger: log, - }); + async start(): Promise { + if (this.started) { + return; // idempotent + } + + if (!this.menubar.app.isPackaged) { + logInfo( + 'app updater', + 'Skipping updater since app is in development mode', + ); + return; + } + + logInfo('app updater', 'Starting updater'); + + this.registerListeners(); + await this.performInitialCheck(); + this.schedulePeriodicChecks(); + this.started = true; + } + + private registerListeners() { autoUpdater.on('checking-for-update', () => { logInfo('auto updater', 'Checking for update'); - this.menuBuilder.setCheckForUpdatesMenuEnabled(false); this.menuBuilder.setNoUpdateAvailableMenuVisibility(false); }); autoUpdater.on('update-available', () => { - logInfo('auto updater', 'New update available'); - + logInfo('auto updater', 'Update available'); this.setTooltipWithStatus('A new update is available'); this.menuBuilder.setUpdateAvailableMenuVisibility(true); }); @@ -42,17 +70,16 @@ export default class Updater { ); }); - autoUpdater.on('update-downloaded', () => { + autoUpdater.on('update-downloaded', (event) => { logInfo('auto updater', 'Update downloaded'); - this.setTooltipWithStatus('A new update is ready to install'); this.menuBuilder.setUpdateAvailableMenuVisibility(false); this.menuBuilder.setUpdateReadyForInstallMenuVisibility(true); + this.showUpdateReadyDialog(event.releaseName); }); autoUpdater.on('update-not-available', () => { logInfo('auto updater', 'Update not available'); - this.menuBuilder.setCheckForUpdatesMenuEnabled(true); this.menuBuilder.setNoUpdateAvailableMenuVisibility(true); this.menuBuilder.setUpdateAvailableMenuVisibility(false); @@ -61,17 +88,35 @@ export default class Updater { autoUpdater.on('update-cancelled', () => { logInfo('auto updater', 'Update cancelled'); - this.resetState(); }); autoUpdater.on('error', (err) => { logError('auto updater', 'Error checking for update', err); - this.resetState(); }); } + private async performInitialCheck() { + try { + logInfo('app updater', 'Checking for updates on application launch'); + await autoUpdater.checkForUpdatesAndNotify(); + } catch (e) { + logError('auto updater', 'Initial check failed', e as Error); + } + } + + private schedulePeriodicChecks() { + setInterval(async () => { + try { + logInfo('app updater', 'Checking for updates on a periodic schedule'); + await autoUpdater.checkForUpdatesAndNotify(); + } catch (e) { + logError('auto updater', 'Scheduled check failed', e as Error); + } + }, APPLICATION.UPDATE_CHECK_INTERVAL_MS); + } + private setTooltipWithStatus(status: string) { this.menubar.tray.setToolTip(`${APPLICATION.NAME}\n${status}`); } @@ -83,4 +128,19 @@ export default class Updater { this.menuBuilder.setUpdateAvailableMenuVisibility(false); this.menuBuilder.setUpdateReadyForInstallMenuVisibility(false); } + + private showUpdateReadyDialog(releaseName: string) { + const dialogOpts: MessageBoxOptions = { + type: 'info', + buttons: ['Restart', 'Later'], + title: 'Application Update', + message: `${APPLICATION.NAME} ${releaseName} has been downloaded`, + detail: + 'Restart to apply the update. You can also restart later from the tray menu.', + }; + + dialog.showMessageBox(dialogOpts).then((returnValue) => { + if (returnValue.response === 0) autoUpdater.quitAndInstall(); + }); + } } diff --git a/src/main/utils.ts b/src/main/utils.ts index 624a5ad9d..10754bfc1 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -7,9 +7,11 @@ import log from 'electron-log'; import type { Menubar } from 'menubar'; import { APPLICATION } from '../shared/constants'; -import { namespacedEvent } from '../shared/events'; +import { EVENTS } from '../shared/events'; import { logError, logInfo } from '../shared/logger'; +import { sendRendererEvent } from './events'; + export function takeScreenshot(mb: Menubar) { const date = new Date(); const dateStr = date.toISOString().replace(/:/g, '-'); @@ -39,7 +41,7 @@ export function resetApp(mb: Menubar) { }); if (response === resetButtonId) { - mb.window.webContents.send(namespacedEvent('reset-app')); + sendRendererEvent(mb, EVENTS.RESET_APP); mb.app.quit(); } } diff --git a/src/preload/index.test.ts b/src/preload/index.test.ts new file mode 100644 index 000000000..89a01dbe0 --- /dev/null +++ b/src/preload/index.test.ts @@ -0,0 +1,174 @@ +import { EVENTS } from '../shared/events'; + +// Mocks shared modules used inside preload +const sendMainEvent = jest.fn(); +const invokeMainEvent = jest.fn(); +const onRendererEvent = jest.fn(); +const logError = jest.fn(); + +jest.mock('./utils', () => ({ + sendMainEvent: (...args: unknown[]) => sendMainEvent(...args), + invokeMainEvent: (...args: unknown[]) => invokeMainEvent(...args), + onRendererEvent: (...args: unknown[]) => onRendererEvent(...args), +})); + +jest.mock('../shared/logger', () => ({ + logError: (...args: unknown[]) => logError(...args), +})); + +// We'll reconfigure the electron mock per context isolation scenario. +const exposeInMainWorld = jest.fn(); +const getZoomLevel = jest.fn(() => 1); +const setZoomLevel = jest.fn((_level: number) => undefined); + +jest.mock('electron', () => ({ + contextBridge: { + exposeInMainWorld: (key: string, value: unknown) => + exposeInMainWorld(key, value), + }, + webFrame: { + getZoomLevel: () => getZoomLevel(), + setZoomLevel: (level: number) => setZoomLevel(level), + }, +})); + +// Simple Notification stub +class MockNotification { + static instances: MockNotification[] = []; + public onclick: (() => void) | null = null; + constructor( + public title: string, + public options: { body: string; silent: boolean }, + ) { + MockNotification.instances.push(this); + } + triggerClick() { + this.onclick?.(); + } +} + +// Attach to global before importing preload +(global as unknown as { Notification: unknown }).Notification = + MockNotification; + +interface TestApi { + tray: { updateIcon: (n?: number) => void }; + openExternalLink: (u: string, f: boolean) => void; + app: { version: () => Promise; show?: () => void; hide?: () => void }; + onSystemThemeUpdate: (cb: (t: string) => void) => void; + raiseNativeNotification: (t: string, b: string, u?: string) => unknown; +} + +describe('preload/index', () => { + beforeEach(() => { + jest.clearAllMocks(); + // default to non-isolated environment for most tests + (process as unknown as { contextIsolated?: boolean }).contextIsolated = + false; + }); + + const importPreload = async () => { + // Ensure a fresh module instance each time + jest.resetModules(); + return await import('./index'); + }; + + it('exposes api on window when context isolation disabled', async () => { + await importPreload(); + const w = window as unknown as { gitify: Record }; + expect(w.gitify).toBeDefined(); + expect(exposeInMainWorld).not.toHaveBeenCalled(); + }); + + it('exposes api via contextBridge when context isolation enabled', async () => { + (process as unknown as { contextIsolated?: boolean }).contextIsolated = + true; + await importPreload(); + expect(exposeInMainWorld).toHaveBeenCalledTimes(1); + const [key, api] = exposeInMainWorld.mock.calls[0]; + expect(key).toBe('gitify'); + expect(api).toHaveProperty('openExternalLink'); + }); + + it('tray.updateIcon sends correct events', async () => { + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; // casting only in test boundary + api.tray.updateIcon(-1); + api.tray.updateIcon(5); + api.tray.updateIcon(0); + expect(sendMainEvent).toHaveBeenNthCalledWith(1, EVENTS.ICON_ERROR); + expect(sendMainEvent).toHaveBeenNthCalledWith(2, EVENTS.ICON_ACTIVE); + expect(sendMainEvent).toHaveBeenNthCalledWith(3, EVENTS.ICON_IDLE); + }); + + it('openExternalLink sends event with payload', async () => { + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + api.openExternalLink('https://example.com/', true); + expect(sendMainEvent).toHaveBeenCalledWith(EVENTS.OPEN_EXTERNAL, { + url: 'https://example.com/', + activate: true, + }); + }); + + it('app.version returns dev in development', async () => { + const originalEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + await expect(api.app.version()).resolves.toBe('dev'); + process.env.NODE_ENV = originalEnv; + }); + + it('app.version prefixes production version', async () => { + const originalEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + invokeMainEvent.mockResolvedValueOnce('1.2.3'); + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + await expect(api.app.version()).resolves.toBe('v1.2.3'); + process.env.NODE_ENV = originalEnv; + }); + + it('onSystemThemeUpdate registers listener', async () => { + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + const callback = jest.fn(); + api.onSystemThemeUpdate(callback); + expect(onRendererEvent).toHaveBeenCalledWith( + EVENTS.UPDATE_THEME, + expect.any(Function), + ); + // Simulate event + const listener = onRendererEvent.mock.calls[0][1]; + listener({}, 'dark'); + expect(callback).toHaveBeenCalledWith('dark'); + }); + + it('raiseNativeNotification without url calls app.show', async () => { + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + api.app.show = jest.fn(); + const notification = api.raiseNativeNotification( + 'Title', + 'Body', + ) as MockNotification; + notification.triggerClick(); + expect(api.app.show).toHaveBeenCalled(); + }); + + it('raiseNativeNotification with url hides app then opens link', async () => { + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + api.app.hide = jest.fn(); + api.openExternalLink = jest.fn(); + const notification = api.raiseNativeNotification( + 'Title', + 'Body', + 'https://x/', + ) as MockNotification; + notification.triggerClick(); + expect(api.app.hide).toHaveBeenCalled(); + expect(api.openExternalLink).toHaveBeenCalledWith('https://x/', true); + }); +}); diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 000000000..5948885f7 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,132 @@ +import { contextBridge, webFrame } from 'electron'; + +import { APPLICATION } from '../shared/constants'; +import { EVENTS } from '../shared/events'; +import { logError } from '../shared/logger'; +import { isLinux, isMacOS, isWindows } from '../shared/platform'; + +import { invokeMainEvent, onRendererEvent, sendMainEvent } from './utils'; + +export const api = { + openExternalLink: (url: string, openInForeground: boolean) => { + sendMainEvent(EVENTS.OPEN_EXTERNAL, { + url: url, + activate: openInForeground, + }); + }, + + encryptValue: (value: string) => + invokeMainEvent(EVENTS.SAFE_STORAGE_ENCRYPT, value), + + decryptValue: (value: string) => + invokeMainEvent(EVENTS.SAFE_STORAGE_DECRYPT, value), + + setAutoLaunch: (value: boolean) => + sendMainEvent(EVENTS.UPDATE_AUTO_LAUNCH, { + openAtLogin: value, + openAsHidden: value, + }), + + setKeyboardShortcut: (keyboardShortcut: boolean) => { + sendMainEvent(EVENTS.UPDATE_KEYBOARD_SHORTCUT, { + enabled: keyboardShortcut, + keyboardShortcut: APPLICATION.DEFAULT_KEYBOARD_SHORTCUT, + }); + }, + + tray: { + updateIcon: (notificationsLength = 0) => { + if (notificationsLength < 0) { + sendMainEvent(EVENTS.ICON_ERROR); + return; + } + + if (notificationsLength > 0) { + sendMainEvent(EVENTS.ICON_ACTIVE); + return; + } + + sendMainEvent(EVENTS.ICON_IDLE); + }, + + updateTitle: (title = '') => sendMainEvent(EVENTS.UPDATE_TITLE, title), + + useAlternateIdleIcon: (value: boolean) => + sendMainEvent(EVENTS.USE_ALTERNATE_IDLE_ICON, value), + }, + + notificationSoundPath: () => invokeMainEvent(EVENTS.NOTIFICATION_SOUND_PATH), + + twemojiDirectory: () => invokeMainEvent(EVENTS.TWEMOJI_DIRECTORY), + + platform: { + isLinux: () => isLinux(), + + isMacOS: () => isMacOS(), + + isWindows: () => isWindows(), + }, + + app: { + hide: () => sendMainEvent(EVENTS.WINDOW_HIDE), + + show: () => sendMainEvent(EVENTS.WINDOW_SHOW), + + quit: () => sendMainEvent(EVENTS.QUIT), + + version: async () => { + if (process.env.NODE_ENV === 'development') { + return 'dev'; + } + + const version = await invokeMainEvent(EVENTS.VERSION); + + return `v${version}`; + }, + }, + + zoom: { + getLevel: () => webFrame.getZoomLevel(), + + setLevel: (zoomLevel: number) => webFrame.setZoomLevel(zoomLevel), + }, + + onResetApp: (callback: () => void) => { + onRendererEvent(EVENTS.RESET_APP, () => callback()); + }, + + onAuthCallback: (callback: (url: string) => void) => { + onRendererEvent(EVENTS.AUTH_CALLBACK, (_, url) => callback(url)); + }, + + onSystemThemeUpdate: (callback: (theme: string) => void) => { + onRendererEvent(EVENTS.UPDATE_THEME, (_, theme) => callback(theme)); + }, + + raiseNativeNotification: (title: string, body: string, url?: string) => { + const notification = new Notification(title, { body: body, silent: true }); + notification.onclick = () => { + if (url) { + api.app.hide(); + api.openExternalLink(url, true); + } else { + api.app.show(); + } + }; + + return notification; + }, +}; + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('gitify', api); + } catch (error) { + logError('preload', 'Failed to expose API to renderer', error); + } +} else { + window.gitify = api; +} diff --git a/src/preload/preload.d.ts b/src/preload/preload.d.ts new file mode 100644 index 000000000..f66dd5101 --- /dev/null +++ b/src/preload/preload.d.ts @@ -0,0 +1,7 @@ +import type { GitifyAPI } from './types'; + +declare global { + interface Window { + gitify: GitifyAPI; + } +} diff --git a/src/preload/types.ts b/src/preload/types.ts new file mode 100644 index 000000000..9b3508825 --- /dev/null +++ b/src/preload/types.ts @@ -0,0 +1,3 @@ +import type { api } from '.'; + +export type GitifyAPI = typeof api; diff --git a/src/preload/utils.test.ts b/src/preload/utils.test.ts new file mode 100644 index 000000000..9af3be541 --- /dev/null +++ b/src/preload/utils.test.ts @@ -0,0 +1,69 @@ +import { EVENTS } from '../shared/events'; + +import { invokeMainEvent, onRendererEvent, sendMainEvent } from './utils'; + +jest.mock('electron', () => { + type Listener = (event: unknown, ...args: unknown[]) => void; + const listeners: Record = {}; + return { + ipcRenderer: { + send: jest.fn(), + invoke: jest.fn().mockResolvedValue('response'), + on: jest.fn((channel: string, listener: Listener) => { + if (!listeners[channel]) { + listeners[channel] = []; + } + listeners[channel].push(listener); + }), + __emit: (channel: string, ...args: unknown[]) => { + const list = listeners[channel] || []; + for (const l of list) { + l({}, ...args); + } + }, + __listeners: listeners, + }, + }; +}); + +import { ipcRenderer } from 'electron'; + +describe('preload/utils', () => { + afterEach(() => { + (ipcRenderer.send as jest.Mock).mockClear(); + (ipcRenderer.invoke as jest.Mock).mockClear(); + (ipcRenderer.on as jest.Mock).mockClear(); + }); + + it('sendMainEvent forwards to ipcRenderer.send', () => { + sendMainEvent(EVENTS.WINDOW_SHOW); + expect(ipcRenderer.send).toHaveBeenCalledWith( + EVENTS.WINDOW_SHOW, + undefined, + ); + }); + + it('invokeMainEvent forwards and resolves', async () => { + const result = await invokeMainEvent(EVENTS.VERSION, 'data'); + expect(ipcRenderer.invoke).toHaveBeenCalledWith(EVENTS.VERSION, 'data'); + expect(result).toBe('response'); + }); + + it('onRendererEvent registers listener and receives emitted data', () => { + const handler = jest.fn(); + onRendererEvent( + EVENTS.UPDATE_TITLE, + handler as unknown as ( + e: Electron.IpcRendererEvent, + args: string, + ) => void, + ); + ( + ipcRenderer as unknown as { + __emit: (channel: string, ...a: unknown[]) => void; + } + ).__emit(EVENTS.UPDATE_TITLE, 'payload'); + expect(ipcRenderer.on).toHaveBeenCalledWith(EVENTS.UPDATE_TITLE, handler); + expect(handler).toHaveBeenCalledWith({}, 'payload'); + }); +}); diff --git a/src/preload/utils.ts b/src/preload/utils.ts new file mode 100644 index 000000000..dcf6c681c --- /dev/null +++ b/src/preload/utils.ts @@ -0,0 +1,35 @@ +import { ipcRenderer } from 'electron'; + +import type { EventData, EventType } from '../shared/events'; + +/** + * Send renderer event without expecting a response + * @param event the type of event to send + * @param data the data to send with the event + */ +export function sendMainEvent(event: EventType, data?: EventData): void { + ipcRenderer.send(event, data); +} + +/** + * Send renderer event and expect a response + * @param event the type of event to send + * @param data the data to send with the event + * @returns + */ +export function invokeMainEvent( + event: EventType, + data?: string, +): Promise { + return ipcRenderer.invoke(event, data); +} + +/** + * Handle renderer event without expecting a response + */ +export function onRendererEvent( + event: EventType, + listener: (event: Electron.IpcRendererEvent, args: string) => void, +) { + ipcRenderer.on(event, listener); +} diff --git a/src/renderer/App.css b/src/renderer/App.css index dff880d28..dca3a8cb0 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -2,36 +2,7 @@ @import "https://github.com/tailwindcss"; /** GitHub Primer Design System */ -/* Size & Typography */ -@import "https://github.com/@primer/primitives/dist/css/primitives.css"; - -/* Base */ -@import "https://github.com/@primer/primitives/dist/css/base/motion/motion.css"; -@import "https://github.com/@primer/primitives/dist/css/base/size/size.css"; -@import "https://github.com/@primer/primitives/dist/css/base/typography/typography.css"; - -/* Functional */ -@import "https://github.com/@primer/primitives/dist/css/functional/size/border.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/breakpoints.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/size.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/size/viewport.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/typography/typography.css"; - -/* Themes and Colors */ -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-colorblind-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-colorblind.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-dimmed-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-dimmed.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-tritanopia-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark-tritanopia.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/dark.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-colorblind-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-colorblind.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-tritanopia-high-contrast.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light-tritanopia.css"; -@import "https://github.com/@primer/primitives/dist/css/functional/themes/light.css"; +@import "https://github.com/@primer/css/dist/primer.css"; /** Tailwind CSS Configuration */ @config '../../tailwind.config.ts'; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9f7f3694c..adf1f35b4 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -28,7 +28,7 @@ function RequireAuth({ children }) { return isLoggedIn ? ( children ) : ( - + ); } @@ -41,45 +41,45 @@ export const App = () => { } + path="/" /> } + path="/filters" /> } + path="/settings" /> } + path="/accounts" /> - } /> + } path="/login" /> } + path="/login-personal-access-token" /> } + path="/login-oauth-app" /> diff --git a/src/renderer/__helpers__/jest.setup.ts b/src/renderer/__helpers__/jest.setup.ts index 1f8c2ac4f..38e52824b 100644 --- a/src/renderer/__helpers__/jest.setup.ts +++ b/src/renderer/__helpers__/jest.setup.ts @@ -1,37 +1,69 @@ import '@testing-library/jest-dom'; -import { TextDecoder, TextEncoder } from 'node:util'; + +import { TextEncoder } from 'node:util'; /** - * Prevent the following errors with jest: - * - ReferenceError: TextEncoder is not defined - * - ReferenceError: TextDecoder is not defined + * Gitify context bridge API */ -if (!('TextEncoder' in globalThis)) { - (globalThis as unknown as { TextEncoder: typeof TextEncoder }).TextEncoder = - TextEncoder; -} -if (!('TextDecoder' in globalThis)) { - (globalThis as unknown as { TextDecoder: typeof TextDecoder }).TextDecoder = - TextDecoder; -} +window.gitify = { + app: { + version: jest.fn().mockResolvedValue('v0.0.1'), + hide: jest.fn(), + quit: jest.fn(), + show: jest.fn(), + }, + twemojiDirectory: jest.fn().mockResolvedValue('/mock/images/assets'), + openExternalLink: jest.fn(), + decryptValue: jest.fn().mockResolvedValue('decrypted'), + encryptValue: jest.fn().mockResolvedValue('encrypted'), + platform: { + isLinux: jest.fn().mockReturnValue(false), + isMacOS: jest.fn().mockReturnValue(true), + isWindows: jest.fn().mockReturnValue(false), + }, + zoom: { + getLevel: jest.fn(), + setLevel: jest.fn(), + }, + tray: { + updateIcon: jest.fn(), + updateTitle: jest.fn(), + useAlternateIdleIcon: jest.fn(), + }, + notificationSoundPath: jest.fn(), + onAuthCallback: jest.fn(), + onResetApp: jest.fn(), + onSystemThemeUpdate: jest.fn(), + setAutoLaunch: jest.fn(), + setKeyboardShortcut: jest.fn(), + raiseNativeNotification: jest.fn(), +}; // Mock OAuth client ID and secret process.env.OAUTH_CLIENT_ID = 'FAKE_CLIENT_ID_123'; process.env.OAUTH_CLIENT_SECRET = 'FAKE_CLIENT_SECRET_123'; /** - * Primer Setup + * Primer (@primer/react) Setup + * + * Borrowed from https://github.com/primer/react/blob/main/packages/react/src/utils/test-helpers.tsx */ -if (typeof CSS === 'undefined') { - global.CSS = {} as typeof CSS; -} +// @ts-expect-error: prevent ReferenceError: TextEncoder is not defined +global.TextEncoder = TextEncoder; -if (!CSS.supports) { - CSS.supports = () => true; -} +// JSDOM doesn't mock ResizeObserver +global.ResizeObserver = jest.fn().mockImplementation(() => { + return { + observe: jest.fn(), + disconnect: jest.fn(), + unobserve: jest.fn(), + }; +}); -global.ResizeObserver = class { - observe() {} - unobserve() {} - disconnect() {} +// @ts-expect-error only declare properties used internally +global.CSS = { + escape: jest.fn(), + supports: jest.fn().mockImplementation(() => { + return false; + }), }; diff --git a/src/renderer/__mocks__/@electron/remote.js b/src/renderer/__mocks__/@electron/remote.js deleted file mode 100644 index 6e148d15c..000000000 --- a/src/renderer/__mocks__/@electron/remote.js +++ /dev/null @@ -1,36 +0,0 @@ -let instance; - -class BrowserWindow { - constructor() { - if (!instance) { - instance = this; - } - // biome-ignore lint/correctness/noConstructorReturn: This is a mock class - return instance; - } - loadURL = jest.fn(); - webContents = { - on: () => {}, - session: { - clearStorageData: jest.fn(), - }, - }; - on() {} - close = jest.fn(); - hide = jest.fn(); - destroy = jest.fn(); -} - -const dialog = { - showErrorBox: jest.fn(), -}; - -module.exports = { - BrowserWindow: BrowserWindow, - dialog: dialog, - app: { - getLoginItemSettings: jest.fn(), - setLoginItemSettings: () => {}, - }, - getCurrentWindow: jest.fn(() => instance || new BrowserWindow()), -}; diff --git a/src/renderer/__mocks__/electron.js b/src/renderer/__mocks__/electron.js deleted file mode 100644 index 9754ee944..000000000 --- a/src/renderer/__mocks__/electron.js +++ /dev/null @@ -1,59 +0,0 @@ -const { namespacedEvent } = require('../../shared/events'); - -window.Notification = function (title) { - this.title = title; - - return { - onclick: jest.fn(), - }; -}; - -window.Audio = class Audio { - constructor(path) { - this.path = path; - } - - play() {} -}; - -window.localStorage = { - store: {}, - getItem: function (key) { - return this.store[key]; - }, - setItem: function (key, item) { - this.store[key] = item; - }, - removeItem: jest.fn(), -}; - -window.alert = jest.fn(); - -module.exports = { - ipcRenderer: { - send: jest.fn(), - on: jest.fn(), - sendSync: jest.fn(), - invoke: jest.fn((channel, ..._args) => { - switch (channel) { - case 'get-platform': - return Promise.resolve('darwin'); - case namespacedEvent('version'): - return Promise.resolve('0.0.1'); - case namespacedEvent('safe-storage-encrypt'): - return Promise.resolve('encrypted'); - case namespacedEvent('safe-storage-decrypt'): - return Promise.resolve('decrypted'); - default: - return Promise.reject(new Error(`Unknown channel: ${channel}`)); - } - }), - }, - shell: { - openExternal: jest.fn(), - }, - webFrame: { - setZoomLevel: jest.fn(), - getZoomLevel: jest.fn(), - }, -}; diff --git a/src/renderer/__mocks__/notifications-mocks.ts b/src/renderer/__mocks__/notifications-mocks.ts index 1bcae1fc1..d5522fa6a 100644 --- a/src/renderer/__mocks__/notifications-mocks.ts +++ b/src/renderer/__mocks__/notifications-mocks.ts @@ -3,6 +3,7 @@ import type { StateType, Subject, SubjectType } from '../typesGitHub'; import { mockEnterpriseNotifications, mockGitHubNotifications, + mockSingleNotification, } from '../utils/api/__mocks__/response-mocks'; import { mockGitHubCloudAccount, @@ -25,7 +26,7 @@ export const mockAccountNotifications: AccountNotifications[] = [ export const mockSingleAccountNotifications: AccountNotifications[] = [ { account: mockGitHubCloudAccount, - notifications: [mockGitHubNotifications[0]], + notifications: [mockSingleNotification], error: null, }, ]; diff --git a/src/renderer/__mocks__/partial-mocks.ts b/src/renderer/__mocks__/partial-mocks.ts index 04ff306bc..b98d1890e 100644 --- a/src/renderer/__mocks__/partial-mocks.ts +++ b/src/renderer/__mocks__/partial-mocks.ts @@ -1,6 +1,6 @@ +import { Constants } from '../constants'; import type { Hostname, Link } from '../types'; import type { Notification, Subject, User } from '../typesGitHub'; -import { Constants } from '../utils/constants'; import { mockGitifyUser, mockToken } from './state-mocks'; export function partialMockNotification( diff --git a/src/renderer/__mocks__/state-mocks.ts b/src/renderer/__mocks__/state-mocks.ts index 295c420a8..377000f3f 100644 --- a/src/renderer/__mocks__/state-mocks.ts +++ b/src/renderer/__mocks__/state-mocks.ts @@ -1,3 +1,4 @@ +import { Constants } from '../constants'; import { type Account, type AppearanceSettingsState, @@ -15,7 +16,6 @@ import { Theme, type Token, } from '../types'; -import { Constants } from '../utils/constants'; export const mockGitifyUser: GitifyUser = { login: 'octocat', @@ -111,6 +111,8 @@ const mockFilters: FilterSettingsState = { filterUserTypes: [], filterIncludeHandles: [], filterExcludeHandles: [], + filterIncludeOrganizations: [], + filterExcludeOrganizations: [], filterSubjectTypes: [], filterStates: [], filterReasons: [], diff --git a/src/renderer/components/AllRead.test.tsx b/src/renderer/components/AllRead.test.tsx index b43c833d7..10cc58cb9 100644 --- a/src/renderer/components/AllRead.test.tsx +++ b/src/renderer/components/AllRead.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { mockSettings } from '../__mocks__/state-mocks'; @@ -11,37 +11,45 @@ describe('renderer/components/AllRead.tsx', () => { ensureStableEmojis(); }); - it('should render itself & its children - no filters', () => { - const tree = render( - - + it('should render itself & its children - no filters', async () => { + let tree: ReturnType | null = null; + + await act(async () => { + tree = render( + - - , - ); + , + ); + }); expect(tree).toMatchSnapshot(); }); - it('should render itself & its children - with filters', () => { - const tree = render( - - - - - , - ); + it('should render itself & its children - with filters', async () => { + let tree: ReturnType | null = null; + + await act(async () => { + tree = render( + + + + + , + ); + }); expect(tree).toMatchSnapshot(); }); diff --git a/src/renderer/components/AllRead.tsx b/src/renderer/components/AllRead.tsx index 4109d4d33..7de8f6276 100644 --- a/src/renderer/components/AllRead.tsx +++ b/src/renderer/components/AllRead.tsx @@ -1,7 +1,7 @@ import { type FC, useContext, useMemo } from 'react'; +import { Constants } from '../constants'; import { AppContext } from '../context/App'; -import { Constants } from '../utils/constants'; import { hasAnyFiltersSet } from '../utils/notifications/filters/filter'; import { EmojiSplash } from './layout/EmojiSplash'; @@ -25,6 +25,6 @@ export const AllRead: FC = ({ fullHeight = true }: IAllRead) => { const heading = `No new ${hasFilters ? 'filtered ' : ''} notifications`; return ( - + ); }; diff --git a/src/renderer/components/Oops.test.tsx b/src/renderer/components/Oops.test.tsx index 4293911b1..9eb16be2f 100644 --- a/src/renderer/components/Oops.test.tsx +++ b/src/renderer/components/Oops.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { ensureStableEmojis } from '../__mocks__/utils'; import { Oops } from './Oops'; @@ -8,19 +8,28 @@ describe('renderer/components/Oops.tsx', () => { ensureStableEmojis(); }); - it('should render itself & its children - specified error', () => { + it('should render itself & its children - specified error', async () => { const mockError = { title: 'Error title', descriptions: ['Error description'], emojis: ['🔥'], }; - const tree = render(); + + let tree: ReturnType | null = null; + + await act(async () => { + tree = render(); + }); expect(tree).toMatchSnapshot(); }); - it('should render itself & its children - fallback to unknown error', () => { - const tree = render(); + it('should render itself & its children - fallback to unknown error', async () => { + let tree: ReturnType | null = null; + + await act(async () => { + tree = render(); + }); expect(tree).toMatchSnapshot(); }); diff --git a/src/renderer/components/Oops.tsx b/src/renderer/components/Oops.tsx index c6352b3da..38beccb1c 100644 --- a/src/renderer/components/Oops.tsx +++ b/src/renderer/components/Oops.tsx @@ -20,9 +20,9 @@ export const Oops: FC = ({ error, fullHeight = true }: IOops) => { return ( ); }; diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 49c511401..c599a7518 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -13,9 +13,10 @@ import { import { IconButton, Stack } from '@primer/react'; import { APPLICATION } from '../../shared/constants'; + +import { Constants } from '../constants'; import { AppContext } from '../context/App'; import { quitApp } from '../utils/comms'; -import { Constants } from '../utils/constants'; import { openGitHubIssues, openGitHubNotifications, @@ -72,129 +73,129 @@ export const Sidebar: FC = () => { return ( navigate('/', { replace: true })} size="small" - variant="invisible" tooltipDirection="e" - onClick={() => navigate('/', { replace: true })} - data-testid="sidebar-home" + unsafeDisableTooltip={false} + variant="invisible" /> 0 ? 'primary' : 'invisible'} - tooltipDirection="e" + icon={BellIcon} onClick={() => openGitHubNotifications(primaryAccountHostname)} - data-testid="sidebar-notifications" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant={notificationsCount > 0 ? 'primary' : 'invisible'} /> {isLoggedIn && ( toggleFilters()} - data-testid="sidebar-filter-notifications" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant={hasAnyFiltersSet(settings) ? 'primary' : 'invisible'} /> )} openGitHubIssues(primaryAccountHostname)} data-testid="sidebar-my-issues" + icon={IssueOpenedIcon} + onClick={() => openGitHubIssues(primaryAccountHostname)} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> openGitHubPulls(primaryAccountHostname)} data-testid="sidebar-my-pull-requests" + icon={GitPullRequestIcon} + onClick={() => openGitHubPulls(primaryAccountHostname)} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> {isLoggedIn && ( <> refreshNotifications()} - data-testid="sidebar-refresh" + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> toggleSettings()} data-testid="sidebar-settings" + icon={GearIcon} + onClick={() => toggleSettings()} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> )} {!isLoggedIn && ( quitApp()} data-testid="sidebar-quit" + icon={XCircleIcon} + onClick={() => quitApp()} + size="small" sx={sidebarButtonStyle} + tooltipDirection="e" + unsafeDisableTooltip={false} + variant="invisible" /> )} diff --git a/src/renderer/components/__snapshots__/AllRead.test.tsx.snap b/src/renderer/components/__snapshots__/AllRead.test.tsx.snap index b7c0b9505..cc16d56ca 100644 --- a/src/renderer/components/__snapshots__/AllRead.test.tsx.snap +++ b/src/renderer/components/__snapshots__/AllRead.test.tsx.snap @@ -38,7 +38,7 @@ exports[`renderer/components/AllRead.tsx should render itself & its children - n alt="🎊" class="emoji" draggable="false" - src="https://github.com/images/twemoji/1f38a.svg" + src="https://github.com/mock/images/assets/1f38a.svg" />
= ({ // TODO explore using AnchoredOverlay component (https://primer.style/components/anchored-overlay/react/alpha) to render Avatar Card on hover return ( {!src || isBroken ? ( ) : ( setIsBroken(true)} size={size} square={isNonHuman} - onError={() => setIsBroken(true)} + src={src} /> )} {name && ( - + {name} )} diff --git a/src/renderer/components/fields/Checkbox.test.tsx b/src/renderer/components/fields/Checkbox.test.tsx index 623a59d91..7d004caf7 100644 --- a/src/renderer/components/fields/Checkbox.test.tsx +++ b/src/renderer/components/fields/Checkbox.test.tsx @@ -33,12 +33,12 @@ describe('renderer/components/fields/Checkbox.tsx', () => { }); it('should render - positive counter unselected', () => { - const tree = render(); + const tree = render(); expect(tree).toMatchSnapshot(); }); it('should render - positive counter selected', () => { - const tree = render(); + const tree = render(); expect(tree).toMatchSnapshot(); }); diff --git a/src/renderer/components/fields/Checkbox.tsx b/src/renderer/components/fields/Checkbox.tsx index 4a7e77c55..f78e54c32 100644 --- a/src/renderer/components/fields/Checkbox.tsx +++ b/src/renderer/components/fields/Checkbox.tsx @@ -26,27 +26,27 @@ export const Checkbox: FC = ({ return ( visible && ( diff --git a/src/renderer/components/fields/FieldLabel.tsx b/src/renderer/components/fields/FieldLabel.tsx index 633d1225d..471dc0b7f 100644 --- a/src/renderer/components/fields/FieldLabel.tsx +++ b/src/renderer/components/fields/FieldLabel.tsx @@ -7,7 +7,7 @@ export interface IFieldLabel { export const FieldLabel: FC = (props: IFieldLabel) => { return ( -