diff --git a/.github/workflows/deploy-app-preprod.yml b/.github/workflows/deploy-app-preprod.yml new file mode 100644 index 00000000..d97e92f6 --- /dev/null +++ b/.github/workflows/deploy-app-preprod.yml @@ -0,0 +1,81 @@ +name: deploy-app-preprod +on: + push: + branches: + - develop + workflow_dispatch: + +# Pre-production deployment. Triggered on every push to `develop`, it deploys the app to the same +# GitHub Pages site as production, but under the `/preprod/` sub-path, so it never touches the +# production files (which live at the site root and are deployed from `main_jg`). +# +# URLs: +# - production: https://jeangarf.github.io/gephi-lite/ +# - pre-production: https://jeangarf.github.io/gephi-lite/preprod/ +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + env: + CI: false + VITE_GITHUB_PROXY: "https://githubapi.gephi.org" + # No Matomo analytics on pre-production, so dev testing does not pollute production stats. + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: "**/node_modules" + key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} + + - name: Install + run: npm install + + - uses: nyaa8/package-version@v1 + with: + path: "packages/gephi-lite/package.json" + + - name: Build + run: npm run build + env: + BASE_URL: ./ + + - name: Create site folder + run: mkdir -p website/v${{ env.PACKAGE_VERSION }} + + - name: Copy gephi lite files + run: cp -rf packages/gephi-lite/build/* website/v${{ env.PACKAGE_VERSION }} + + - name: Create index page with redirection + run: | + cat < website/index.html + + + + + Gephi Lite (pre-production) + + + + + + + If you are not redirected automatically, follow this link. + + + EOF + + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: website # The folder the action should deploy. + branch: gh-pages # The branch the action should deploy to. + target-folder: preprod # Deploy inside the /preprod/ sub-path, next to production. + clean: false # keep the production files (and anything else already on the branch) diff --git a/.github/workflows/deploy-app.yml b/.github/workflows/deploy-app.yml index a414e69b..ad7549cf 100644 --- a/.github/workflows/deploy-app.yml +++ b/.github/workflows/deploy-app.yml @@ -2,12 +2,14 @@ name: deploy-app on: push: branches: - - main + - main_jg workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest + permissions: + contents: write env: CI: false VITE_GITHUB_PROXY: "https://githubapi.gephi.org" @@ -41,9 +43,6 @@ jobs: - name: Copy gephi lite files run: cp -rf packages/gephi-lite/build/* website/v${{ env.PACKAGE_VERSION }} - - name: Copy CNAME files - run: cp CNAME website/CNAME - - name: Create index page with redirection run: | cat < website/index.html @@ -56,16 +55,15 @@ jobs: - - If you are not redirected automatically, follow this link. + If you are not redirected automatically, follow this link. EOF diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 1af039f1..b426a704 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -1,8 +1,5 @@ name: Publish Docker image on: - push: - branches: - - main workflow_dispatch: env: diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..9b263391 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,15 @@ +# Instructions pour Claude Code + +- Lors des commits, ne jamais ajouter de ligne "Co-Authored-By:". +- Éviter la duplication de code : toujours factoriser dans des constantes, des fonctions ou des méthodes partagées plutôt que copier-coller entre fichiers, pour que le code reste maintenable et qu'une même fonctionnalité n'ait jamais deux comportements différents selon l'endroit où elle est appelée. +- Une même action déclenchable de plusieurs façons (touche du clavier, bouton, entrée de menu, geste tactile, bouton Retour du système…) doit passer par un seul et même code. Si je demande une modification en désignant un seul de ces modes de déclenchement, elle s'applique à tous les autres, sauf si je dis explicitement le contraire. + +## Flux de déploiement + +- **Pré-production** (branche `develop`, déployée sur `https://jeangarf.github.io/gephi-lite/preprod/`) : je peux merger dans `develop` et pousser **sans demander**. +- **Production** (branche `main_jg`, déployée sur `https://jeangarf.github.io/gephi-lite/`) : je dois **toujours demander** avant de merger `develop` dans `main_jg` et de pousser. + +## Synchronisation des branches + +- Si une branche de feature est en retard sur `develop`, la mettre à jour avec un **merge `--no-ff`** de `develop` dans la branche de feature (jamais de fast-forward), pour conserver la visualisation parallèle des branches dans l'historique. +- Le fast-forward est réservé aux **pull** (synchronisation d'une branche locale avec son remote) — ce n'est pas un merge de feature. diff --git a/CNAME b/CNAME deleted file mode 100644 index cf8cb511..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -lite.gephi.org \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5516ecb9..21946d04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,32 +42,24 @@ "vitest": "^3.0.5" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.26.2", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -75,20 +67,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.7", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.26.7", - "@babel/types": "^7.26.7", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -104,13 +98,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -129,12 +125,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -194,6 +192,15 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.25.9", "dev": true, @@ -207,24 +214,28 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -245,7 +256,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -297,21 +310,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -332,22 +351,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.7", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -876,14 +899,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1478,46 +1503,45 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.7", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.7", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1590,31 +1614,6 @@ "react": ">= 16.8.0" } }, - "node_modules/@emnapi/core": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/wasi-threads": "1.0.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "license": "MIT", @@ -1719,9 +1718,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -1731,15 +1730,14 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -1749,15 +1747,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -1767,15 +1764,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -1785,15 +1781,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -1803,15 +1798,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -1821,15 +1815,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -1839,15 +1832,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -1857,15 +1849,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -1875,15 +1866,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -1893,15 +1883,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -1911,15 +1900,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -1929,15 +1917,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -1947,15 +1934,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -1965,15 +1951,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -1983,15 +1968,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -2001,13 +1985,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -2022,9 +2007,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -2034,15 +2019,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -2052,15 +2036,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -2070,15 +2053,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -2088,15 +2070,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -2106,15 +2087,14 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -2124,15 +2104,14 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -2142,15 +2121,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -2160,15 +2138,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -2178,13 +2155,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2236,20 +2214,37 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.10.0", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2260,18 +2255,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -2292,16 +2289,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@eslint/js": { - "version": "9.19.0", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2309,11 +2336,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -2396,7 +2425,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2409,6 +2440,8 @@ }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", + "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2495,6 +2528,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/figures": { "version": "1.0.10", "dev": true, @@ -2610,11 +2665,15 @@ }, "node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "dev": true, "license": "ISC" }, "node_modules/@jest/schemas": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { @@ -2625,26 +2684,28 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2664,7 +2725,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2672,7 +2735,10 @@ } }, "node_modules/@lerna/create": { - "version": "8.2.0", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-8.2.4.tgz", + "integrity": "sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==", + "deprecated": "This package is an implementation detail of Lerna and is no longer published separately.", "dev": true, "license": "MIT", "dependencies": { @@ -2681,7 +2747,7 @@ "@npmcli/run-script": "8.1.0", "@nx/devkit": ">=17.1.2 < 21", "@octokit/plugin-enterprise-rest": "6.0.1", - "@octokit/rest": "19.0.11", + "@octokit/rest": "20.1.2", "aproba": "2.0.0", "byte-size": "8.1.1", "chalk": "4.1.0", @@ -2699,7 +2765,6 @@ "get-stream": "6.0.0", "git-url-parse": "14.0.0", "glob-parent": "6.0.2", - "globby": "11.1.0", "graceful-fs": "4.2.11", "has-unicode": "2.0.1", "ini": "^1.3.8", @@ -2710,7 +2775,6 @@ "js-yaml": "4.1.0", "libnpmpublish": "9.0.9", "load-json-file": "6.2.0", - "lodash": "^4.17.21", "make-dir": "4.0.0", "minimatch": "3.0.5", "multimatch": "5.0.0", @@ -2734,9 +2798,10 @@ "slash": "^3.0.0", "ssri": "^10.0.6", "string-width": "^4.2.3", - "strong-log-transformer": "2.1.0", "tar": "6.2.1", "temp-dir": "1.0.0", + "through": "2.3.8", + "tinyglobby": "0.2.12", "upath": "2.0.1", "uuid": "^10.0.0", "validate-npm-package-license": "^3.0.4", @@ -2753,6 +2818,8 @@ }, "node_modules/@lerna/create/node_modules/byte-size": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-8.1.1.tgz", + "integrity": "sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==", "dev": true, "license": "MIT", "engines": { @@ -2761,6 +2828,8 @@ }, "node_modules/@lerna/create/node_modules/chalk": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "license": "MIT", "dependencies": { @@ -2776,6 +2845,8 @@ }, "node_modules/@lerna/create/node_modules/cosmiconfig": { "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", "dependencies": { @@ -2799,8 +2870,33 @@ } } }, + "node_modules/@lerna/create/node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@lerna/create/node_modules/fs-extra": { - "version": "11.3.0", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "dependencies": { @@ -2814,6 +2910,9 @@ }, "node_modules/@lerna/create/node_modules/glob": { "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -2830,7 +2929,9 @@ } }, "node_modules/@lerna/create/node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -2838,7 +2939,9 @@ } }, "node_modules/@lerna/create/node_modules/glob/node_modules/minimatch": { - "version": "8.0.4", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", "dev": true, "license": "ISC", "dependencies": { @@ -2853,6 +2956,8 @@ }, "node_modules/@lerna/create/node_modules/ignore-walk": { "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, "license": "ISC", "dependencies": { @@ -2863,7 +2968,9 @@ } }, "node_modules/@lerna/create/node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -2871,11 +2978,13 @@ } }, "node_modules/@lerna/create/node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2886,16 +2995,22 @@ }, "node_modules/@lerna/create/node_modules/ini": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, "node_modules/@lerna/create/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@lerna/create/node_modules/minimatch": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, "license": "ISC", "dependencies": { @@ -2907,6 +3022,8 @@ }, "node_modules/@lerna/create/node_modules/minipass": { "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "dev": true, "license": "ISC", "engines": { @@ -2915,6 +3032,8 @@ }, "node_modules/@lerna/create/node_modules/node-fetch": { "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2934,6 +3053,8 @@ }, "node_modules/@lerna/create/node_modules/npm-packlist": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "license": "ISC", "dependencies": { @@ -2945,6 +3066,8 @@ }, "node_modules/@lerna/create/node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2959,15 +3082,19 @@ } }, "node_modules/@lerna/create/node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/@lerna/create/node_modules/rimraf": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, "license": "ISC", "dependencies": { @@ -2984,7 +3111,9 @@ } }, "node_modules/@lerna/create/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2996,6 +3125,9 @@ }, "node_modules/@lerna/create/node_modules/uuid": { "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -3121,9 +3253,9 @@ "license": "MIT" }, "node_modules/@mdx-js/rollup/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -3181,16 +3313,6 @@ "node": ">=18" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/core": "^1.1.0", - "@emnapi/runtime": "^1.1.0", - "@tybys/wasm-util": "^0.9.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -3245,6 +3367,8 @@ }, "node_modules/@npmcli/arborist": { "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", + "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", "dev": true, "license": "ISC", "dependencies": { @@ -3292,7 +3416,9 @@ } }, "node_modules/@npmcli/arborist/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -3301,6 +3427,8 @@ }, "node_modules/@npmcli/arborist/node_modules/json-parse-even-better-errors": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -3309,15 +3437,19 @@ }, "node_modules/@npmcli/arborist/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/arborist/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3327,7 +3459,9 @@ } }, "node_modules/@npmcli/arborist/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3361,6 +3495,8 @@ }, "node_modules/@npmcli/git": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3379,20 +3515,26 @@ } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.1", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/git/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3404,6 +3546,8 @@ }, "node_modules/@npmcli/git/node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "license": "ISC", "dependencies": { @@ -3418,6 +3562,8 @@ }, "node_modules/@npmcli/installed-package-contents": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", "dev": true, "license": "ISC", "dependencies": { @@ -3433,6 +3579,8 @@ }, "node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3444,6 +3592,8 @@ }, "node_modules/@npmcli/installed-package-contents/node_modules/npm-normalize-package-bin": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -3452,6 +3602,8 @@ }, "node_modules/@npmcli/map-workspaces": { "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", "dev": true, "license": "ISC", "dependencies": { @@ -3465,7 +3617,9 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -3473,7 +3627,10 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3491,31 +3648,21 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/map-workspaces/node_modules/jackspeak": { - "version": "3.4.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/@npmcli/map-workspaces/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3526,6 +3673,8 @@ }, "node_modules/@npmcli/map-workspaces/node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3541,6 +3690,8 @@ }, "node_modules/@npmcli/metavuln-calculator": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", + "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", "dev": true, "license": "ISC", "dependencies": { @@ -3556,6 +3707,8 @@ }, "node_modules/@npmcli/metavuln-calculator/node_modules/json-parse-even-better-errors": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -3563,7 +3716,9 @@ } }, "node_modules/@npmcli/metavuln-calculator/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3575,6 +3730,8 @@ }, "node_modules/@npmcli/name-from-folder": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", "dev": true, "license": "ISC", "engines": { @@ -3583,6 +3740,8 @@ }, "node_modules/@npmcli/node-gyp": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", "dev": true, "license": "ISC", "engines": { @@ -3591,6 +3750,8 @@ }, "node_modules/@npmcli/package-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.0.tgz", + "integrity": "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3607,7 +3768,9 @@ } }, "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -3615,7 +3778,10 @@ } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3633,22 +3799,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/jackspeak": { - "version": "3.4.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -3657,15 +3811,19 @@ }, "node_modules/@npmcli/package-json/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3676,6 +3834,8 @@ }, "node_modules/@npmcli/package-json/node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3690,7 +3850,9 @@ } }, "node_modules/@npmcli/package-json/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3702,6 +3864,8 @@ }, "node_modules/@npmcli/promise-spawn": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3712,15 +3876,19 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "license": "ISC", "dependencies": { @@ -3735,6 +3903,8 @@ }, "node_modules/@npmcli/query": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", + "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3754,6 +3924,8 @@ }, "node_modules/@npmcli/run-script": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, "license": "ISC", "dependencies": { @@ -3769,15 +3941,19 @@ } }, "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/run-script/node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "license": "ISC", "dependencies": { @@ -3790,34 +3966,54 @@ "node": "^16.13.0 || >=18.0.0" } }, + "node_modules/@nrwl/devkit": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-18.3.5.tgz", + "integrity": "sha512-DIvChKMe4q8CtIsbrumL/aYgf85H5vlT6eF3jnCCWORj6LTwoHtK8Q9ky1+uM82KIM0gaKd32NVDw+w64scHyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "18.3.5" + } + }, + "node_modules/@nrwl/tao": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-17.2.8.tgz", + "integrity": "sha512-Qpk5YKeJ+LppPL/wtoDyNGbJs2MsTi6qyX/RdRrEc8lc4bk6Cw3Oul1qTXCI6jT0KzTz+dZtd0zYD/G7okkzvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "nx": "17.2.8", + "tslib": "^2.3.0" + }, + "bin": { + "tao": "index.js" + } + }, "node_modules/@nx/devkit": { - "version": "20.4.6", + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-18.3.5.tgz", + "integrity": "sha512-9I0L17t0MN87fL4m4MjDiBxJIx7h5RQY/pTYtt5TBjye0ANb165JeE4oh3ibzfjMzXv42Aej2Gm+cOuSPwzT9g==", "dev": true, "license": "MIT", "dependencies": { + "@nrwl/devkit": "18.3.5", "ejs": "^3.1.7", "enquirer": "~2.3.6", "ignore": "^5.0.4", - "minimatch": "9.0.3", "semver": "^7.5.3", "tmp": "~0.2.1", "tslib": "^2.3.0", "yargs-parser": "21.1.1" }, "peerDependencies": { - "nx": ">= 19 <= 21" - } - }, - "node_modules/@nx/devkit/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "nx": ">= 16 <= 19" } }, "node_modules/@nx/devkit/node_modules/enquirer": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "license": "MIT", "dependencies": { @@ -3827,22 +4023,10 @@ "node": ">=8.6" } }, - "node_modules/@nx/devkit/node_modules/minimatch": { - "version": "9.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3852,20 +4036,125 @@ "node": ">=10" } }, - "node_modules/@nx/devkit/node_modules/tmp": { - "version": "0.2.3", + "node_modules/@nx/nx-darwin-arm64": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-17.2.8.tgz", + "integrity": "sha512-dMb0uxug4hM7tusISAU1TfkDK3ixYmzc1zhHSZwpR7yKJIyKLtUpBTbryt8nyso37AS1yH+dmfh2Fj2WxfBHTg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14.14" + "node": ">= 10" + } + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-17.2.8.tgz", + "integrity": "sha512-0cXzp1tGr7/6lJel102QiLA4NkaLCkQJj6VzwbwuvmuCDxPbpmbz7HC1tUteijKBtOcdXit1/MEoEU007To8Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-17.2.8.tgz", + "integrity": "sha512-YFMgx5Qpp2btCgvaniDGdu7Ctj56bfFvbbaHQWmOeBPK1krNDp2mqp8HK6ZKOfEuDJGOYAp7HDtCLvdZKvJxzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-17.2.8.tgz", + "integrity": "sha512-iN2my6MrhLRkVDtdivQHugK8YmR7URo1wU9UDuHQ55z3tEcny7LV3W9NSsY9UYPK/FrxdDfevj0r2hgSSdhnzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-17.2.8.tgz", + "integrity": "sha512-Iy8BjoW6mOKrSMiTGujUcNdv+xSM1DALTH6y3iLvNDkGbjGK1Re6QNnJAzqcXyDpv32Q4Fc57PmuexyysZxIGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-17.2.8.tgz", + "integrity": "sha512-9wkAxWzknjpzdofL1xjtU6qPFF1PHlvKCZI3hgEYJDo4mQiatGI+7Ttko+lx/ZMP6v4+Umjtgq7+qWrApeKamQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "20.4.6", + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-17.2.8.tgz", + "integrity": "sha512-sjG1bwGsjLxToasZ3lShildFsF0eyeGu+pOQZIp9+gjFbeIkd19cTlCnHrOV9hoF364GuKSXQyUlwtFYFR4VTQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3876,11 +4165,16 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "20.4.6", + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-17.2.8.tgz", + "integrity": "sha512-QiakXZ1xBCIptmkGEouLHQbcM4klQkcr+kEaz2PlNwy/sW3gH1b/1c0Ed5J1AN9xgQxWspriAONpScYBRgxdhA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3890,6 +4184,40 @@ "node": ">= 10" } }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-17.2.8.tgz", + "integrity": "sha512-XBWUY/F/GU3vKN9CAxeI15gM4kr3GOBqnzFZzoZC4qJt2hKSSUEWsMgeZtsMgeqEClbi4ZyCCkY7YJgU32WUGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-17.2.8.tgz", + "integrity": "sha512-HTqDv+JThlLzbcEm/3f+LbS5/wYQWzb5YDXbP1wi7nlCTihNZOLNqGOkEmwlrR5tAdNHPRpHSmkYg4305W0CtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@octokit/auth-oauth-device": { "version": "7.1.2", "license": "MIT", @@ -3927,16 +4255,33 @@ } }, "node_modules/@octokit/endpoint": { - "version": "10.1.2", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2", + "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" }, "engines": { "node": ">= 18" } }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, "node_modules/@octokit/graphql": { "version": "8.1.2", "license": "MIT", @@ -3970,228 +4315,247 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "23.0.1", + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", "license": "MIT" }, "node_modules/@octokit/plugin-enterprise-rest": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz", + "integrity": "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==", "dev": true, "license": "MIT" }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "6.1.2", - "dev": true, + "node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", "license": "MIT", "dependencies": { - "@octokit/tsconfig": "^1.0.2", - "@octokit/types": "^9.2.3" + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "@octokit/core": ">=4" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "18.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "9.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^18.0.0" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@octokit/core": ">=3" + "node": ">= 18" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "7.2.3", - "dev": true, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", "license": "MIT", "dependencies": { - "@octokit/types": "^10.0.0" + "@octokit/types": "^14.0.0" }, "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "@octokit/core": ">=3" + "node": ">= 18" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "18.1.1", - "dev": true, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "10.0.0", - "dev": true, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^18.0.0" + "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/@octokit/request": { - "version": "9.2.0", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.6.2", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" }, - "node_modules/@octokit/request-error": { - "version": "6.1.6", + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2" - }, - "engines": { - "node": ">= 18" + "@octokit/openapi-types": "^25.1.0" } }, "node_modules/@octokit/rest": { - "version": "19.0.11", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", + "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^4.2.1", - "@octokit/plugin-paginate-rest": "^6.1.2", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^7.1.2" + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" }, "engines": { - "node": ">= 14" + "node": ">= 18" } }, "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { - "version": "3.0.4", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">= 18" } }, "node_modules/@octokit/rest/node_modules/@octokit/core": { - "version": "4.2.4", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/auth-token": "^3.0.0", - "@octokit/graphql": "^5.0.0", - "@octokit/request": "^6.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^9.0.0", + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 18" } }, "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { - "version": "7.0.6", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^9.0.0", - "is-plain-object": "^5.0.0", + "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 18" } }, "node_modules/@octokit/rest/node_modules/@octokit/graphql": { - "version": "5.0.6", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^6.0.0", - "@octokit/types": "^9.0.0", + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/openapi-types": { - "version": "18.1.1", + "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.4-cjs.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", + "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } }, - "node_modules/@octokit/rest/node_modules/@octokit/request": { - "version": "6.2.8", + "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.3.2-cjs.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", + "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^9.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" + "@octokit/types": "^13.8.0" }, "engines": { - "node": ">= 14" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" } }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "3.0.3", + "node_modules/@octokit/rest/node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^9.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/types": { - "version": "9.3.2", + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^18.0.0" + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/@octokit/rest/node_modules/before-after-hook": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@octokit/rest/node_modules/universal-user-agent": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", "dev": true, "license": "ISC" }, - "node_modules/@octokit/tsconfig": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/@octokit/types": { - "version": "13.7.0", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^24.2.0" } }, "node_modules/@open-draft/deferred-promise": { @@ -4252,11 +4616,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.50.1", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.50.1" + "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -4451,161 +4817,536 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.32.1", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ] }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.32.1", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ] }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@ryoppippi/unplugin-typia": { - "version": "1.2.0", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.3", - "consola": "^3.2.3", - "defu": "^6.1.4", - "diff-match-patch": "^1.0.5", - "find-cache-dir": "^5.0.0", - "magic-string": "^0.30.14", - "pathe": "^1.1.2", - "pkg-types": "^1.2.1", - "type-fest": "^4.30.0", - "typescript": "~5.6.3", - "typia": "^7.6.0", - "unplugin": "^1.16.0", - "vite": "^6.0.2" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@ryoppippi/unplugin-typia/node_modules/@rollup/pluginutils": { - "version": "5.1.4", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@ryoppippi/unplugin-typia/node_modules/@types/estree": { - "version": "1.0.6", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@ryoppippi/unplugin-typia/node_modules/picomatch": { - "version": "4.0.2", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@ryoppippi/unplugin-typia/node_modules/typescript": { - "version": "5.6.3", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@samchon/openapi": { - "version": "2.4.1", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@sigma/node-image": { - "version": "3.0.0", + "libc": [ + "glibc" + ], "license": "MIT", - "peerDependencies": { - "sigma": ">=3.0.0-beta.10" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sigma/utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sigma/utils/-/utils-3.0.0.tgz", - "integrity": "sha512-JCMjj3CHpA8yh8HcWm1YBs4Zdo2s0YUR2YKZ2NUlM2Purkdd8odLuaGQ/R/guSrBz/y+tOLSaQX7hKF6fkUG1g==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "peerDependencies": { - "sigma": ">=3.0.0-beta.29" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sigstore/bundle": { - "version": "2.3.2", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sigstore/core": { - "version": "1.1.0", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sigstore/sign": { - "version": "2.3.2", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@ryoppippi/unplugin-typia": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "consola": "^3.2.3", + "defu": "^6.1.4", + "diff-match-patch": "^1.0.5", + "find-cache-dir": "^5.0.0", + "magic-string": "^0.30.14", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "type-fest": "^4.30.0", + "typescript": "~5.6.3", + "typia": "^7.6.0", + "unplugin": "^1.16.0", + "vite": "^6.0.2" + } + }, + "node_modules/@ryoppippi/unplugin-typia/node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@ryoppippi/unplugin-typia/node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@ryoppippi/unplugin-typia/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@ryoppippi/unplugin-typia/node_modules/typescript": { + "version": "5.6.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@samchon/openapi": { + "version": "2.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@sigma/node-image": { + "version": "3.0.0", + "license": "MIT", + "peerDependencies": { + "sigma": ">=3.0.0-beta.10" + } + }, + "node_modules/@sigma/utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigma/utils/-/utils-3.0.0.tgz", + "integrity": "sha512-JCMjj3CHpA8yh8HcWm1YBs4Zdo2s0YUR2YKZ2NUlM2Purkdd8odLuaGQ/R/guSrBz/y+tOLSaQX7hKF6fkUG1g==", + "license": "MIT", + "peerDependencies": { + "sigma": ">=3.0.0-beta.29" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", + "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", "proc-log": "^4.2.0", "promise-retry": "^2.0.1" }, @@ -4615,6 +5356,8 @@ }, "node_modules/@sigstore/tuf": { "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4627,6 +5370,8 @@ }, "node_modules/@sigstore/verify": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4639,7 +5384,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, @@ -4906,7 +5653,98 @@ } } }, - "node_modules/@swc/core-linux-x64-gnu": { + "node_modules/@swc/core-darwin-arm64": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.11.tgz", + "integrity": "sha512-ZpgEaNcx2e5D+Pd0yZGVbpSrEDOEubn7r2JXoNBf0O85lPjUm3HDzGRfLlV/MwxRPAkwm93eLP4l7gYnc50l3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.11.tgz", + "integrity": "sha512-szObinnq2o7spXMDU5pdunmUeLrfV67Q77rV+DyojAiGJI1RSbEQotLOk+ONOLpoapwGUxOijFG4IuX1xiwQ2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.11.tgz", + "integrity": "sha512-tVE8aXQwd8JUB9fOGLawFJa76nrpvp3dvErjozMmWSKWqtoeO7HV83aOrVtc8G66cj4Vq7FjTE9pOJeV1FbKRw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.11.tgz", + "integrity": "sha512-geFkENU5GMEKO7FqHOaw9HVlpQEW10nICoM6ubFc0hXBv8dwRXU4vQbh9s/isLSFRftw1m4jEEWixAnXSw8bxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.11.tgz", + "integrity": "sha512-2mMscXe/ivq8c4tO3eQSbQDFBvagMJGlalXCspn0DgDImLYTEnt/8KHMUMGVfh0gMJTZ9q4FlGLo7mlnbx99MQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { "version": "1.10.11", "cpu": [ "x64" @@ -4917,7 +5755,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -4933,7 +5770,57 @@ "os": [ "linux" ], - "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.11.tgz", + "integrity": "sha512-7+bMSIoqcbXKosIVd314YjckDRPneA4OpG1cb3/GrkQTEDXmWT3pFBBlJf82hzJfw7b6lfv6rDVEFBX7/PJoLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.11.tgz", + "integrity": "sha512-6hkLl4+3KjP/OFTryWxpW7YFN+w4R689TSPwiII4fFgsFNupyEmLWWakKfkGgV2JVA59L4Oi02elHy/O1sbgtw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.11.tgz", + "integrity": "sha512-kKNE2BGu/La2k2WFHovenqZvGQAHRIU+rd2/6a7D6EiQ6EyimtbhUqjCCZ+N1f5fIAnvM+sMdLiQJq4jdd/oOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=10" } @@ -5096,6 +5983,8 @@ }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, "license": "MIT", "engines": { @@ -5104,6 +5993,8 @@ }, "node_modules/@tufjs/models": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", "dev": true, "license": "MIT", "dependencies": { @@ -5115,7 +6006,9 @@ } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -5123,11 +6016,13 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5136,14 +6031,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, @@ -5182,6 +6069,7 @@ }, "node_modules/@types/cookie": { "version": "0.6.0", + "dev": true, "license": "MIT" }, "node_modules/@types/debug": { @@ -5285,11 +6173,15 @@ }, "node_modules/@types/minimatch": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true, "license": "MIT" }, "node_modules/@types/minimist": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true, "license": "MIT" }, @@ -5310,6 +6202,8 @@ }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true, "license": "MIT" }, @@ -5555,7 +6449,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -5563,11 +6459,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5801,7 +6699,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.10", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5809,11 +6709,15 @@ }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/@yarnpkg/parsers": { - "version": "3.0.2", + "version": "3.0.0-rc.46", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", + "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5821,11 +6725,13 @@ "tslib": "^2.4.0" }, "engines": { - "node": ">=18.12.0" + "node": ">=14.15.0" } }, "node_modules/@yarnpkg/parsers/node_modules/argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -5833,7 +6739,9 @@ } }, "node_modules/@yarnpkg/parsers/node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -5844,17 +6752,14 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@yarnpkg/parsers/node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@yomguithereal/helpers": { "version": "1.1.1", "license": "MIT" }, "node_modules/@zkochan/js-yaml": { - "version": "0.0.7", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", + "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", "dev": true, "license": "MIT", "dependencies": { @@ -5866,6 +6771,8 @@ }, "node_modules/abbrev": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, "license": "ISC", "engines": { @@ -5873,7 +6780,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -5904,6 +6813,8 @@ }, "node_modules/add-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", "dev": true, "license": "MIT" }, @@ -5928,7 +6839,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -6016,6 +6929,8 @@ }, "node_modules/aproba": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, "license": "ISC" }, @@ -6054,6 +6969,8 @@ }, "node_modules/array-differ": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", "dev": true, "license": "MIT", "engines": { @@ -6091,6 +7008,8 @@ }, "node_modules/array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { @@ -6206,6 +7125,8 @@ }, "node_modules/arrify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, "license": "MIT", "engines": { @@ -6232,6 +7153,8 @@ }, "node_modules/async": { "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true, "license": "MIT" }, @@ -6245,6 +7168,8 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, "license": "MIT" }, @@ -6278,13 +7203,43 @@ } }, "node_modules/axios": { - "version": "1.8.1", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, "node_modules/babel-plugin-macros": { @@ -6377,6 +7332,8 @@ }, "node_modules/bin-links": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", "dev": true, "license": "ISC", "dependencies": { @@ -6391,6 +7348,8 @@ }, "node_modules/bin-links/node_modules/npm-normalize-package-bin": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -6571,7 +7530,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -6579,7 +7540,10 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6597,31 +7561,19 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/jackspeak": { - "version": "3.4.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", "dev": true, "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6709,6 +7661,8 @@ }, "node_modules/camelcase-keys": { "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -6725,6 +7679,8 @@ }, "node_modules/camelcase-keys/node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -6733,6 +7689,8 @@ }, "node_modules/camelcase-keys/node_modules/quick-lru": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true, "license": "MIT", "engines": { @@ -6846,7 +7804,9 @@ } }, "node_modules/chardet": { - "version": "0.7.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "dev": true, "license": "MIT" }, @@ -6957,6 +7917,8 @@ }, "node_modules/clone-deep": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6970,6 +7932,8 @@ }, "node_modules/clone-deep/node_modules/is-plain-object": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "license": "MIT", "dependencies": { @@ -6981,6 +7945,8 @@ }, "node_modules/cmd-shim": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", "dev": true, "license": "ISC", "engines": { @@ -7033,6 +7999,8 @@ }, "node_modules/color-support": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true, "license": "ISC", "bin": { @@ -7041,6 +8009,8 @@ }, "node_modules/columnify": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", + "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7053,6 +8023,8 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { @@ -7095,6 +8067,8 @@ }, "node_modules/common-ancestor-path": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", "dev": true, "license": "ISC" }, @@ -7124,6 +8098,8 @@ }, "node_modules/concat-stream": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "dev": true, "engines": [ "node >= 6.0" @@ -7151,6 +8127,8 @@ }, "node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, "license": "ISC" }, @@ -7167,6 +8145,9 @@ }, "node_modules/conventional-changelog-core": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-5.0.1.tgz", + "integrity": "sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==", + "deprecated": "Deprecated and no longer maintained. Please use conventional-changelog instead.", "dev": true, "license": "MIT", "dependencies": { @@ -7188,6 +8169,8 @@ }, "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -7199,6 +8182,8 @@ }, "node_modules/conventional-changelog-core/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -7210,6 +8195,8 @@ }, "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7223,7 +8210,9 @@ } }, "node_modules/conventional-changelog-core/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -7235,11 +8224,15 @@ }, "node_modules/conventional-changelog-core/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/conventional-changelog-preset-loader": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-3.0.0.tgz", + "integrity": "sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==", "dev": true, "license": "MIT", "engines": { @@ -7248,6 +8241,8 @@ }, "node_modules/conventional-changelog-writer": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz", + "integrity": "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7268,6 +8263,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -7280,6 +8277,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -7291,6 +8290,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -7302,6 +8303,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -7313,6 +8316,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/meow": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7337,6 +8342,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7351,6 +8358,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -7365,6 +8374,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -7376,6 +8387,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "license": "MIT", "dependencies": { @@ -7390,6 +8403,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg-up": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", "dependencies": { @@ -7406,6 +8421,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7414,11 +8431,15 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/conventional-changelog-writer/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7430,6 +8451,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -7438,6 +8461,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7445,7 +8470,9 @@ } }, "node_modules/conventional-changelog-writer/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -7457,6 +8484,8 @@ }, "node_modules/conventional-changelog-writer/node_modules/type-fest": { "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7468,11 +8497,15 @@ }, "node_modules/conventional-changelog-writer/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/conventional-changelog-writer/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -7481,6 +8514,8 @@ }, "node_modules/conventional-commits-filter": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7493,6 +8528,8 @@ }, "node_modules/conventional-commits-parser": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", "dev": true, "license": "MIT", "dependencies": { @@ -7510,6 +8547,8 @@ }, "node_modules/conventional-commits-parser/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -7522,6 +8561,8 @@ }, "node_modules/conventional-commits-parser/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -7533,6 +8574,8 @@ }, "node_modules/conventional-commits-parser/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -7544,6 +8587,8 @@ }, "node_modules/conventional-commits-parser/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -7555,6 +8600,8 @@ }, "node_modules/conventional-commits-parser/node_modules/meow": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7579,6 +8626,8 @@ }, "node_modules/conventional-commits-parser/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7593,6 +8642,8 @@ }, "node_modules/conventional-commits-parser/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -7607,6 +8658,8 @@ }, "node_modules/conventional-commits-parser/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -7618,6 +8671,8 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "license": "MIT", "dependencies": { @@ -7632,6 +8687,8 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg-up": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", "dependencies": { @@ -7648,6 +8705,8 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7656,11 +8715,15 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/conventional-commits-parser/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7672,6 +8735,8 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -7680,6 +8745,8 @@ }, "node_modules/conventional-commits-parser/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7687,7 +8754,9 @@ } }, "node_modules/conventional-commits-parser/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -7699,6 +8768,8 @@ }, "node_modules/conventional-commits-parser/node_modules/type-fest": { "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7710,11 +8781,15 @@ }, "node_modules/conventional-commits-parser/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/conventional-commits-parser/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -7723,6 +8798,8 @@ }, "node_modules/conventional-recommended-bump": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz", + "integrity": "sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==", "dev": true, "license": "MIT", "dependencies": { @@ -7743,6 +8820,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -7755,6 +8834,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -7766,6 +8847,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -7777,6 +8860,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -7788,6 +8873,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/meow": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7812,6 +8899,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7826,6 +8915,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -7840,6 +8931,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -7851,6 +8944,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "license": "MIT", "dependencies": { @@ -7865,6 +8960,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg-up": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", "dependencies": { @@ -7881,6 +8978,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7889,11 +8988,15 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/conventional-recommended-bump/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7905,6 +9008,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -7913,6 +9018,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7920,7 +9027,9 @@ } }, "node_modules/conventional-recommended-bump/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -7932,6 +9041,8 @@ }, "node_modules/conventional-recommended-bump/node_modules/type-fest": { "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7943,11 +9054,15 @@ }, "node_modules/conventional-recommended-bump/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/conventional-recommended-bump/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -8005,7 +9120,9 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -8031,6 +9148,8 @@ }, "node_modules/cssesc": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", "bin": { @@ -8046,6 +9165,8 @@ }, "node_modules/dargs": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true, "license": "MIT", "engines": { @@ -8107,6 +9228,8 @@ }, "node_modules/dateformat": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true, "license": "MIT", "engines": { @@ -8130,6 +9253,8 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { @@ -8138,6 +9263,8 @@ }, "node_modules/decamelize-keys": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "license": "MIT", "dependencies": { @@ -8153,6 +9280,8 @@ }, "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "license": "MIT", "engines": { @@ -8175,6 +9304,8 @@ }, "node_modules/dedent": { "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -8244,6 +9375,8 @@ }, "node_modules/define-lazy-prop": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", "engines": { @@ -8267,12 +9400,16 @@ } }, "node_modules/defu": { - "version": "6.1.4", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "dev": true, "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { @@ -8281,6 +9418,8 @@ }, "node_modules/deprecation": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true, "license": "ISC" }, @@ -8315,7 +9454,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -8329,23 +9470,14 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "2.1.0", "dev": true, @@ -8391,28 +9523,26 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", + "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dotenv-expand": { - "version": "11.0.7", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "dev": true, "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" } }, "node_modules/drange": { @@ -8438,6 +9568,8 @@ }, "node_modules/duplexer": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true, "license": "MIT" }, @@ -8448,6 +9580,8 @@ }, "node_modules/ejs": { "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8490,7 +9624,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", "dependencies": { @@ -8746,450 +9882,45 @@ } }, "node_modules/esbuild": { - "version": "0.24.2", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" - } - }, - "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -9211,30 +9942,32 @@ } }, "node_modules/eslint": { - "version": "9.19.0", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -9246,7 +9979,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -9479,7 +10212,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9494,7 +10229,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9515,13 +10252,15 @@ "license": "MIT" }, "node_modules/espree": { - "version": "10.3.0", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9555,6 +10294,8 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9728,6 +10469,8 @@ }, "node_modules/execa": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9757,7 +10500,9 @@ } }, "node_modules/exponential-backoff": { - "version": "3.1.2", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, @@ -9768,19 +10513,6 @@ "dev": true, "license": "MIT" }, - "node_modules/external-editor": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/fast-content-type-parse": { "version": "2.0.1", "funding": [ @@ -9897,7 +10629,9 @@ } }, "node_modules/filelist": { - "version": "1.0.4", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -9905,7 +10639,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -9913,7 +10649,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -9970,6 +10708,8 @@ }, "node_modules/flat": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "license": "BSD-3-Clause", "bin": { @@ -9989,12 +10729,16 @@ } }, "node_modules/flatted": { - "version": "3.3.2", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -10027,11 +10771,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -10053,54 +10799,26 @@ } }, "node_modules/form-data": { - "version": "4.0.2", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, - "node_modules/front-matter": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1" - } - }, - "node_modules/front-matter/node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/front-matter/node_modules/js-yaml": { - "version": "3.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/front-matter/node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/fs-constants": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, "license": "MIT" }, @@ -10224,6 +10942,8 @@ }, "node_modules/get-pkg-repo": { "version": "4.2.1", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", + "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", "dev": true, "license": "MIT", "dependencies": { @@ -10241,6 +10961,8 @@ }, "node_modules/get-pkg-repo/node_modules/cliui": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "license": "ISC", "dependencies": { @@ -10251,6 +10973,8 @@ }, "node_modules/get-pkg-repo/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -10260,13 +10984,10 @@ "node": ">=10" } }, - "node_modules/get-pkg-repo/node_modules/isarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/get-pkg-repo/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -10276,44 +10997,10 @@ "node": ">=10" } }, - "node_modules/get-pkg-repo/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/get-pkg-repo/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/get-pkg-repo/node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10330,11 +11017,15 @@ }, "node_modules/get-pkg-repo/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/get-pkg-repo/node_modules/yargs": { - "version": "16.2.0", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "dependencies": { @@ -10352,6 +11043,8 @@ }, "node_modules/get-pkg-repo/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -10383,6 +11076,8 @@ }, "node_modules/get-stream": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", "dev": true, "license": "MIT", "engines": { @@ -10423,6 +11118,9 @@ }, "node_modules/git-raw-commits": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", + "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", + "deprecated": "Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -10439,6 +11137,8 @@ }, "node_modules/git-raw-commits/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10451,6 +11151,8 @@ }, "node_modules/git-raw-commits/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -10462,6 +11164,8 @@ }, "node_modules/git-raw-commits/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -10473,6 +11177,8 @@ }, "node_modules/git-raw-commits/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -10484,6 +11190,8 @@ }, "node_modules/git-raw-commits/node_modules/meow": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10508,6 +11216,8 @@ }, "node_modules/git-raw-commits/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10522,6 +11232,8 @@ }, "node_modules/git-raw-commits/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -10536,6 +11248,8 @@ }, "node_modules/git-raw-commits/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -10547,6 +11261,8 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "license": "MIT", "dependencies": { @@ -10561,6 +11277,8 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg-up": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", "dependencies": { @@ -10577,6 +11295,8 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10585,11 +11305,15 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10601,6 +11325,8 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -10609,6 +11335,8 @@ }, "node_modules/git-raw-commits/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10616,7 +11344,9 @@ } }, "node_modules/git-raw-commits/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -10628,6 +11358,8 @@ }, "node_modules/git-raw-commits/node_modules/type-fest": { "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10639,11 +11371,15 @@ }, "node_modules/git-raw-commits/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/git-raw-commits/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -10652,6 +11388,8 @@ }, "node_modules/git-remote-origin-url": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", "dev": true, "license": "MIT", "dependencies": { @@ -10664,6 +11402,8 @@ }, "node_modules/git-remote-origin-url/node_modules/pify": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, "license": "MIT", "engines": { @@ -10672,6 +11412,9 @@ }, "node_modules/git-semver-tags": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-5.0.1.tgz", + "integrity": "sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==", + "deprecated": "Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -10687,6 +11430,8 @@ }, "node_modules/git-semver-tags/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10699,6 +11444,8 @@ }, "node_modules/git-semver-tags/node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -10710,6 +11457,8 @@ }, "node_modules/git-semver-tags/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -10721,6 +11470,8 @@ }, "node_modules/git-semver-tags/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -10732,6 +11483,8 @@ }, "node_modules/git-semver-tags/node_modules/meow": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10756,6 +11509,8 @@ }, "node_modules/git-semver-tags/node_modules/normalize-package-data": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10770,6 +11525,8 @@ }, "node_modules/git-semver-tags/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -10784,6 +11541,8 @@ }, "node_modules/git-semver-tags/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -10795,6 +11554,8 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "license": "MIT", "dependencies": { @@ -10809,6 +11570,8 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg-up": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", "dependencies": { @@ -10825,6 +11588,8 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10833,11 +11598,15 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/git-semver-tags/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10849,6 +11618,8 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -10857,6 +11628,8 @@ }, "node_modules/git-semver-tags/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10864,7 +11637,9 @@ } }, "node_modules/git-semver-tags/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -10876,6 +11651,8 @@ }, "node_modules/git-semver-tags/node_modules/type-fest": { "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10887,11 +11664,15 @@ }, "node_modules/git-semver-tags/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/git-semver-tags/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -10900,6 +11681,8 @@ }, "node_modules/git-up": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10909,6 +11692,8 @@ }, "node_modules/git-url-parse": { "version": "14.0.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-14.0.0.tgz", + "integrity": "sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10917,6 +11702,8 @@ }, "node_modules/gitconfiglocal": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", "dev": true, "license": "BSD", "dependencies": { @@ -10925,6 +11712,8 @@ }, "node_modules/gitconfiglocal/node_modules/ini": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, @@ -11058,25 +11847,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "dev": true, @@ -11277,7 +12047,9 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11298,6 +12070,8 @@ }, "node_modules/hard-rejection": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, "license": "MIT", "engines": { @@ -11383,11 +12157,15 @@ }, "node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, "license": "ISC" }, "node_modules/hasown": { - "version": "2.0.2", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -11554,7 +12332,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.3", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.7.tgz", + "integrity": "sha512-iwbQltVlx8bCrqePUM8C+hllHvdawVhQJaLrj1X7qllkvFQdXFsr16pW/mo9+JDVjN+QO2XUx9jd8SmoFkE5qw==", "dev": true, "license": "MIT", "dependencies": { @@ -11566,7 +12346,7 @@ "micromatch": "^4.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.18.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/https-proxy-agent": { @@ -11583,6 +12363,8 @@ }, "node_modules/human-signals": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -11628,14 +12410,20 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ieee754": { @@ -11673,6 +12461,13 @@ "minimatch": "^3.0.4" } }, + "node_modules/immutable": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", + "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "license": "MIT", @@ -11811,6 +12606,8 @@ }, "node_modules/init-package-json": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-6.0.3.tgz", + "integrity": "sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==", "dev": true, "license": "ISC", "dependencies": { @@ -11827,7 +12624,9 @@ } }, "node_modules/init-package-json/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -11845,15 +12644,17 @@ "license": "MIT" }, "node_modules/inquirer": { - "version": "8.2.6", + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", "dev": true, "license": "MIT", "dependencies": { + "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", - "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", @@ -11883,13 +12684,11 @@ } }, "node_modules/ip-address": { - "version": "9.0.5", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -12011,6 +12810,8 @@ }, "node_modules/is-ci": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12077,6 +12878,8 @@ }, "node_modules/is-docker": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", "bin": { @@ -12233,6 +13036,8 @@ }, "node_modules/is-plain-obj": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "license": "MIT", "engines": { @@ -12299,6 +13104,8 @@ }, "node_modules/is-ssh": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", "dev": true, "license": "MIT", "dependencies": { @@ -12307,6 +13114,8 @@ }, "node_modules/is-stream": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, "license": "MIT", "engines": { @@ -12346,6 +13155,8 @@ }, "node_modules/is-text-path": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "license": "MIT", "dependencies": { @@ -12426,6 +13237,8 @@ }, "node_modules/is-wsl": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", "dependencies": { @@ -12447,6 +13260,8 @@ }, "node_modules/isobject": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "license": "MIT", "engines": { @@ -12485,28 +13300,31 @@ } }, "node_modules/jackspeak": { - "version": "4.0.2", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jake": { - "version": "10.9.2", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" @@ -12522,6 +13340,8 @@ }, "node_modules/jest-diff": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { @@ -12536,6 +13356,8 @@ }, "node_modules/jest-diff/node_modules/ansi-styles": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -12547,6 +13369,8 @@ }, "node_modules/jest-diff/node_modules/pretty-format": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12560,11 +13384,15 @@ }, "node_modules/jest-diff/node_modules/react-is": { "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, "node_modules/jest-get-type": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { @@ -12599,11 +13427,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "license": "MIT", @@ -12628,6 +13451,8 @@ }, "node_modules/json-parse-better-errors": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true, "license": "MIT" }, @@ -12647,6 +13472,8 @@ }, "node_modules/json-stringify-nice": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", "dev": true, "license": "ISC", "funding": { @@ -12655,6 +13482,8 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC" }, @@ -12671,6 +13500,8 @@ }, "node_modules/jsonc-parser": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true, "license": "MIT" }, @@ -12695,6 +13526,8 @@ }, "node_modules/JSONStream": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "license": "(MIT OR Apache-2.0)", "dependencies": { @@ -12724,11 +13557,15 @@ }, "node_modules/just-diff": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", "dev": true, "license": "MIT" }, "node_modules/just-diff-apply": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", "dev": true, "license": "MIT" }, @@ -12749,17 +13586,19 @@ } }, "node_modules/lerna": { - "version": "8.2.0", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.2.4.tgz", + "integrity": "sha512-0gaVWDIVT7fLfprfwpYcQajb7dBJv3EGavjG7zvJ+TmGx3/wovl5GklnSwM2/WeE0Z2wrIz7ndWhBcDUHVjOcQ==", "dev": true, "license": "MIT", "dependencies": { - "@lerna/create": "8.2.0", + "@lerna/create": "8.2.4", "@npmcli/arborist": "7.5.4", "@npmcli/package-json": "5.2.0", "@npmcli/run-script": "8.1.0", "@nx/devkit": ">=17.1.2 < 21", "@octokit/plugin-enterprise-rest": "6.0.1", - "@octokit/rest": "19.0.11", + "@octokit/rest": "20.1.2", "aproba": "2.0.0", "byte-size": "8.1.1", "chalk": "4.1.0", @@ -12780,7 +13619,6 @@ "get-stream": "6.0.0", "git-url-parse": "14.0.0", "glob-parent": "6.0.2", - "globby": "11.1.0", "graceful-fs": "4.2.11", "has-unicode": "2.0.1", "import-local": "3.1.0", @@ -12794,7 +13632,6 @@ "libnpmaccess": "8.0.6", "libnpmpublish": "9.0.9", "load-json-file": "6.2.0", - "lodash": "^4.17.21", "make-dir": "4.0.0", "minimatch": "3.0.5", "multimatch": "5.0.0", @@ -12820,9 +13657,10 @@ "slash": "3.0.0", "ssri": "^10.0.6", "string-width": "^4.2.3", - "strong-log-transformer": "2.1.0", "tar": "6.2.1", "temp-dir": "1.0.0", + "through": "2.3.8", + "tinyglobby": "0.2.12", "typescript": ">=3 < 6", "upath": "2.0.1", "uuid": "^10.0.0", @@ -12889,6 +13727,29 @@ } } }, + "node_modules/lerna/node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/lerna/node_modules/fs-extra": { "version": "11.3.0", "dev": true, @@ -12920,7 +13781,9 @@ } }, "node_modules/lerna/node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -12928,7 +13791,9 @@ } }, "node_modules/lerna/node_modules/glob/node_modules/minimatch": { - "version": "8.0.4", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", "dev": true, "license": "ISC", "dependencies": { @@ -12953,7 +13818,9 @@ } }, "node_modules/lerna/node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -12961,11 +13828,13 @@ } }, "node_modules/lerna/node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -13122,6 +13991,8 @@ }, "node_modules/libnpmpublish": { "version": "9.0.9", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-9.0.9.tgz", + "integrity": "sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==", "dev": true, "license": "ISC", "dependencies": { @@ -13139,7 +14010,9 @@ } }, "node_modules/libnpmpublish/node_modules/ci-info": { - "version": "4.1.0", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -13153,7 +14026,9 @@ } }, "node_modules/libnpmpublish/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -13176,6 +14051,8 @@ }, "node_modules/load-json-file": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", + "integrity": "sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13190,6 +14067,8 @@ }, "node_modules/load-json-file/node_modules/type-fest": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -13211,11 +14090,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -13225,6 +14108,8 @@ }, "node_modules/lodash.ismatch": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", "dev": true, "license": "MIT" }, @@ -13317,6 +14202,8 @@ }, "node_modules/make-dir": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -13330,7 +14217,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -13369,6 +14258,8 @@ }, "node_modules/map-obj": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, "license": "MIT", "engines": { @@ -13525,9 +14416,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "dev": true, "license": "MIT", "dependencies": { @@ -14272,6 +15163,8 @@ }, "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { @@ -14280,6 +15173,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { @@ -14299,6 +15194,8 @@ }, "node_modules/min-indent": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", "engines": { @@ -14306,7 +15203,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -14326,6 +15225,8 @@ }, "node_modules/minimist-options": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "license": "MIT", "dependencies": { @@ -14521,6 +15422,8 @@ }, "node_modules/modify-values": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true, "license": "MIT", "engines": { @@ -14584,6 +15487,8 @@ }, "node_modules/multimatch": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", "dev": true, "license": "MIT", "dependencies": { @@ -14602,6 +15507,8 @@ }, "node_modules/multimatch/node_modules/arrify": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true, "license": "MIT", "engines": { @@ -14614,7 +15521,9 @@ "license": "ISC" }, "node_modules/nanoid": { - "version": "3.3.8", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -14645,6 +15554,8 @@ }, "node_modules/neo-async": { "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, @@ -14677,6 +15588,8 @@ }, "node_modules/node-gyp": { "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14699,7 +15612,9 @@ } }, "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -14707,7 +15622,10 @@ } }, "node_modules/node-gyp/node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -14726,38 +15644,30 @@ } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/node-gyp/node_modules/jackspeak": { - "version": "3.4.3", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": ">=18" } }, "node_modules/node-gyp/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -14768,6 +15678,8 @@ }, "node_modules/node-gyp/node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -14782,7 +15694,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -14794,6 +15708,8 @@ }, "node_modules/node-gyp/node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "license": "ISC", "dependencies": { @@ -14808,6 +15724,8 @@ }, "node_modules/node-machine-id": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true, "license": "MIT" }, @@ -14818,6 +15736,8 @@ }, "node_modules/nopt": { "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "license": "ISC", "dependencies": { @@ -14832,6 +15752,8 @@ }, "node_modules/normalize-package-data": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -14844,7 +15766,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -14872,6 +15796,8 @@ }, "node_modules/npm-install-checks": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -14882,7 +15808,9 @@ } }, "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -14941,6 +15869,8 @@ }, "node_modules/npm-pick-manifest": { "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", "dev": true, "license": "ISC", "dependencies": { @@ -14955,6 +15885,8 @@ }, "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -14962,7 +15894,9 @@ } }, "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -15002,43 +15936,45 @@ } }, "node_modules/nx": { - "version": "20.4.6", + "version": "17.2.8", + "resolved": "https://registry.npmjs.org/nx/-/nx-17.2.8.tgz", + "integrity": "sha512-rM5zXbuXLEuqQqcjVjClyvHwRJwt+NVImR2A6KFNG40Z60HP6X12wAxxeLHF5kXXTDRU0PFhf/yACibrpbPrAw==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@napi-rs/wasm-runtime": "0.2.4", + "@nrwl/tao": "17.2.8", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.2", - "@zkochan/js-yaml": "0.0.7", - "axios": "^1.7.4", + "@yarnpkg/parsers": "3.0.0-rc.46", + "@zkochan/js-yaml": "0.0.6", + "axios": "^1.5.1", "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", "cliui": "^8.0.1", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", + "dotenv": "~16.3.1", + "dotenv-expand": "~10.0.0", "enquirer": "~2.3.6", "figures": "3.2.0", "flat": "^5.0.2", - "front-matter": "^4.0.2", + "fs-extra": "^11.1.0", + "glob": "7.1.4", "ignore": "^5.0.4", "jest-diff": "^29.4.1", + "js-yaml": "4.1.0", "jsonc-parser": "3.2.0", - "lines-and-columns": "2.0.3", - "minimatch": "9.0.3", + "lines-and-columns": "~2.0.3", + "minimatch": "3.0.5", "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", "open": "^8.4.0", - "ora": "5.3.0", - "resolve.exports": "2.0.3", - "semver": "^7.5.3", + "semver": "7.5.3", "string-width": "^4.2.3", + "strong-log-transformer": "^2.1.0", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", - "yaml": "^2.6.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, @@ -15047,19 +15983,19 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "20.4.6", - "@nx/nx-darwin-x64": "20.4.6", - "@nx/nx-freebsd-x64": "20.4.6", - "@nx/nx-linux-arm-gnueabihf": "20.4.6", - "@nx/nx-linux-arm64-gnu": "20.4.6", - "@nx/nx-linux-arm64-musl": "20.4.6", - "@nx/nx-linux-x64-gnu": "20.4.6", - "@nx/nx-linux-x64-musl": "20.4.6", - "@nx/nx-win32-arm64-msvc": "20.4.6", - "@nx/nx-win32-x64-msvc": "20.4.6" + "@nx/nx-darwin-arm64": "17.2.8", + "@nx/nx-darwin-x64": "17.2.8", + "@nx/nx-freebsd-x64": "17.2.8", + "@nx/nx-linux-arm-gnueabihf": "17.2.8", + "@nx/nx-linux-arm64-gnu": "17.2.8", + "@nx/nx-linux-arm64-musl": "17.2.8", + "@nx/nx-linux-x64-gnu": "17.2.8", + "@nx/nx-linux-x64-musl": "17.2.8", + "@nx/nx-win32-arm64-msvc": "17.2.8", + "@nx/nx-win32-x64-msvc": "17.2.8" }, "peerDependencies": { - "@swc-node/register": "^1.8.0", + "@swc-node/register": "^1.6.7", "@swc/core": "^1.3.85" }, "peerDependenciesMeta": { @@ -15071,16 +16007,10 @@ } } }, - "node_modules/nx/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/nx/node_modules/cli-spinners": { "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", "dev": true, "license": "MIT", "engines": { @@ -15092,6 +16022,8 @@ }, "node_modules/nx/node_modules/enquirer": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "license": "MIT", "dependencies": { @@ -15101,53 +16033,85 @@ "node": ">=8.6" } }, - "node_modules/nx/node_modules/lines-and-columns": { - "version": "2.0.3", + "node_modules/nx/node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=14.14" } }, - "node_modules/nx/node_modules/minimatch": { - "version": "9.0.3", + "node_modules/nx/node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, - "node_modules/nx/node_modules/ora": { - "version": "5.3.0", + "node_modules/nx/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "dev": true, "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/nx/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", "dependencies": { - "bl": "^4.0.3", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "log-symbols": "^4.0.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nx/node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, "node_modules/nx/node_modules/semver": { - "version": "7.7.1", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -15155,13 +16119,12 @@ "node": ">=10" } }, - "node_modules/nx/node_modules/tmp": { - "version": "0.2.3", + "node_modules/nx/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } + "license": "ISC" }, "node_modules/object-assign": { "version": "4.1.1", @@ -15296,6 +16259,8 @@ }, "node_modules/open": { "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15348,14 +16313,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/outvariant": { "version": "1.4.3", "dev": true, @@ -15379,6 +16336,8 @@ }, "node_modules/p-finally": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true, "license": "MIT", "engines": { @@ -15429,6 +16388,8 @@ }, "node_modules/p-map-series": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-2.1.0.tgz", + "integrity": "sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==", "dev": true, "license": "MIT", "engines": { @@ -15448,6 +16409,8 @@ }, "node_modules/p-queue": { "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15471,6 +16434,8 @@ }, "node_modules/p-timeout": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "dev": true, "license": "MIT", "dependencies": { @@ -15514,6 +16479,8 @@ }, "node_modules/pacote": { "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, "license": "ISC", "dependencies": { @@ -15543,7 +16510,9 @@ } }, "node_modules/pacote/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -15552,6 +16521,8 @@ }, "node_modules/pacote/node_modules/ignore-walk": { "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, "license": "ISC", "dependencies": { @@ -15562,11 +16533,13 @@ } }, "node_modules/pacote/node_modules/minimatch": { - "version": "9.0.5", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15577,6 +16550,8 @@ }, "node_modules/pacote/node_modules/npm-packlist": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "license": "ISC", "dependencies": { @@ -15605,6 +16580,8 @@ }, "node_modules/parse-conflict-json": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", "dev": true, "license": "ISC", "dependencies": { @@ -15618,6 +16595,8 @@ }, "node_modules/parse-conflict-json/node_modules/json-parse-even-better-errors": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -15701,7 +16680,9 @@ } }, "node_modules/parse-path": { - "version": "7.0.1", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", + "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", "dev": true, "license": "MIT", "dependencies": { @@ -15710,6 +16691,8 @@ }, "node_modules/parse-url": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "license": "MIT", "dependencies": { @@ -15802,7 +16785,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -15814,6 +16799,8 @@ }, "node_modules/pify": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", "dev": true, "license": "MIT", "engines": { @@ -15937,11 +16924,13 @@ "license": "MIT" }, "node_modules/playwright": { - "version": "1.50.1", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.50.1" + "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -15954,7 +16943,9 @@ } }, "node_modules/playwright-core": { - "version": "1.50.1", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -15988,7 +16979,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", + "version": "8.5.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz", + "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==", "dev": true, "funding": [ { @@ -16006,7 +16999,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -16015,7 +17008,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16098,11 +17093,15 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT" }, "node_modules/proggy": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", + "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", "dev": true, "license": "ISC", "engines": { @@ -16111,6 +17110,8 @@ }, "node_modules/promise-all-reject-late": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", "dev": true, "license": "ISC", "funding": { @@ -16119,6 +17120,8 @@ }, "node_modules/promise-call-limit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", "dev": true, "license": "ISC", "funding": { @@ -16127,6 +17130,8 @@ }, "node_modules/promise-inflight": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, "license": "ISC" }, @@ -16144,6 +17149,8 @@ }, "node_modules/promzard": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.2.tgz", + "integrity": "sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==", "dev": true, "license": "ISC", "dependencies": { @@ -16175,13 +17182,20 @@ }, "node_modules/protocols": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", + "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", "dev": true, "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/psl": { "version": "1.15.0", @@ -16438,13 +17452,13 @@ } }, "node_modules/react-router": { - "version": "7.1.3", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.6.0", "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" + "set-cookie-parser": "^2.6.0" }, "engines": { "node": ">=20.0.0" @@ -16460,10 +17474,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.1.3", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", "license": "MIT", "dependencies": { - "react-router": "7.1.3" + "react-router": "7.18.1" }, "engines": { "node": ">=20.0.0" @@ -16555,6 +17571,8 @@ }, "node_modules/read": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/read/-/read-3.0.1.tgz", + "integrity": "sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==", "dev": true, "license": "ISC", "dependencies": { @@ -16566,6 +17584,8 @@ }, "node_modules/read-cmd-shim": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", "dev": true, "license": "ISC", "engines": { @@ -16574,6 +17594,8 @@ }, "node_modules/read-package-json-fast": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, "license": "ISC", "dependencies": { @@ -16586,6 +17608,8 @@ }, "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -16594,6 +17618,8 @@ }, "node_modules/read-package-json-fast/node_modules/npm-normalize-package-bin": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -16602,6 +17628,8 @@ }, "node_modules/read-pkg": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", "dev": true, "license": "MIT", "dependencies": { @@ -16615,6 +17643,8 @@ }, "node_modules/read-pkg-up": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", "dev": true, "license": "MIT", "dependencies": { @@ -16627,6 +17657,8 @@ }, "node_modules/read-pkg-up/node_modules/find-up": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16638,6 +17670,8 @@ }, "node_modules/read-pkg-up/node_modules/locate-path": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, "license": "MIT", "dependencies": { @@ -16650,6 +17684,8 @@ }, "node_modules/read-pkg-up/node_modules/p-limit": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -16661,6 +17697,8 @@ }, "node_modules/read-pkg-up/node_modules/p-locate": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, "license": "MIT", "dependencies": { @@ -16672,6 +17710,8 @@ }, "node_modules/read-pkg-up/node_modules/p-try": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, "license": "MIT", "engines": { @@ -16680,6 +17720,8 @@ }, "node_modules/read-pkg-up/node_modules/path-exists": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", "engines": { @@ -16688,11 +17730,15 @@ }, "node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, "license": "ISC" }, "node_modules/read-pkg/node_modules/load-json-file": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "license": "MIT", "dependencies": { @@ -16707,6 +17753,8 @@ }, "node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -16718,6 +17766,8 @@ }, "node_modules/read-pkg/node_modules/parse-json": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "license": "MIT", "dependencies": { @@ -16730,6 +17780,8 @@ }, "node_modules/read-pkg/node_modules/path-type": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "license": "MIT", "dependencies": { @@ -16741,6 +17793,8 @@ }, "node_modules/read-pkg/node_modules/pify": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", "engines": { @@ -16749,6 +17803,8 @@ }, "node_modules/read-pkg/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -16757,6 +17813,8 @@ }, "node_modules/read-pkg/node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -16765,6 +17823,8 @@ }, "node_modules/read/node_modules/mute-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, "license": "ISC", "engines": { @@ -16875,6 +17935,8 @@ }, "node_modules/redent": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "license": "MIT", "dependencies": { @@ -17133,14 +18195,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "dev": true, @@ -17196,22 +18250,50 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/rimraf/node_modules/glob": { - "version": "11.0.1", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -17226,12 +18308,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/cliui": "^9.0.0" }, "engines": { "node": "20 || >=22" @@ -17240,6 +18324,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "2.79.2", "dev": true, @@ -17393,6 +18493,8 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "license": "ISC" }, @@ -17445,6 +18547,8 @@ }, "node_modules/shallow-clone": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "license": "MIT", "dependencies": { @@ -17563,6 +18667,8 @@ }, "node_modules/sigstore": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -17628,11 +18734,13 @@ } }, "node_modules/socks": { - "version": "2.8.4", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -17655,6 +18763,8 @@ }, "node_modules/sort-keys": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", "dev": true, "license": "MIT", "dependencies": { @@ -17707,6 +18817,8 @@ }, "node_modules/spdx-correct": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -17716,11 +18828,15 @@ }, "node_modules/spdx-exceptions": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -17729,12 +18845,16 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, "node_modules/split": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "license": "MIT", "dependencies": { @@ -17746,6 +18866,8 @@ }, "node_modules/split2": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dev": true, "license": "ISC", "dependencies": { @@ -17753,7 +18875,9 @@ } }, "node_modules/sprintf-js": { - "version": "1.1.3", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, @@ -17958,6 +19082,8 @@ }, "node_modules/strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -17966,6 +19092,8 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -17974,6 +19102,8 @@ }, "node_modules/strip-indent": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17996,6 +19126,8 @@ }, "node_modules/strong-log-transformer": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", + "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -18093,6 +19225,8 @@ }, "node_modules/tar-stream": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -18143,6 +19277,8 @@ }, "node_modules/temp-dir": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", "dev": true, "license": "MIT", "engines": { @@ -18172,6 +19308,8 @@ }, "node_modules/text-extensions": { "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true, "license": "MIT", "engines": { @@ -18190,6 +19328,57 @@ "dev": true, "license": "MIT" }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "dev": true, @@ -18209,6 +19398,54 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { "version": "1.0.2", "dev": true, @@ -18243,14 +19480,13 @@ } }, "node_modules/tmp": { - "version": "0.0.33", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/to-regex-range": { @@ -18304,6 +19540,8 @@ }, "node_modules/treeverse": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", "dev": true, "license": "ISC", "engines": { @@ -18323,6 +19561,8 @@ }, "node_modules/trim-newlines": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true, "license": "MIT", "engines": { @@ -18423,6 +19663,8 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "license": "MIT", "dependencies": { @@ -18436,6 +19678,8 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -18466,68 +19710,10 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" - } - }, "node_modules/tuf-js": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", "dev": true, "license": "MIT", "dependencies": { @@ -18539,10 +19725,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "license": "ISC" - }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -18637,6 +19819,8 @@ }, "node_modules/typedarray": { "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true, "license": "MIT" }, @@ -18755,6 +19939,8 @@ }, "node_modules/uglify-js": { "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -18991,6 +20177,8 @@ }, "node_modules/upath": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", "dev": true, "license": "MIT", "engines": { @@ -19062,7 +20250,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.5", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -19084,6 +20274,8 @@ }, "node_modules/validate-npm-package-license": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -19130,13 +20322,18 @@ } }, "node_modules/vite": { - "version": "6.0.11", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.2", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -19396,7 +20593,9 @@ "license": "MIT" }, "node_modules/vite-plugin-svgr/node_modules/picomatch": { - "version": "4.0.2", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -19407,16 +20606,51 @@ } }, "node_modules/vite/node_modules/@types/estree": { - "version": "1.0.6", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vite/node_modules/rollup": { - "version": "4.32.1", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -19426,28 +20660,51 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.32.1", - "@rollup/rollup-android-arm64": "4.32.1", - "@rollup/rollup-darwin-arm64": "4.32.1", - "@rollup/rollup-darwin-x64": "4.32.1", - "@rollup/rollup-freebsd-arm64": "4.32.1", - "@rollup/rollup-freebsd-x64": "4.32.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.32.1", - "@rollup/rollup-linux-arm-musleabihf": "4.32.1", - "@rollup/rollup-linux-arm64-gnu": "4.32.1", - "@rollup/rollup-linux-arm64-musl": "4.32.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.32.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.32.1", - "@rollup/rollup-linux-riscv64-gnu": "4.32.1", - "@rollup/rollup-linux-s390x-gnu": "4.32.1", - "@rollup/rollup-linux-x64-gnu": "4.32.1", - "@rollup/rollup-linux-x64-musl": "4.32.1", - "@rollup/rollup-win32-arm64-msvc": "4.32.1", - "@rollup/rollup-win32-ia32-msvc": "4.32.1", - "@rollup/rollup-win32-x64-msvc": "4.32.1", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/vitest": { "version": "3.0.5", "dev": true, @@ -19597,6 +20854,8 @@ }, "node_modules/walk-up-path": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", "dev": true, "license": "ISC" }, @@ -19740,6 +20999,8 @@ }, "node_modules/wide-align": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, "license": "ISC", "dependencies": { @@ -19756,6 +21017,8 @@ }, "node_modules/wordwrap": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, @@ -19796,6 +21059,8 @@ }, "node_modules/write-file-atomic": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -19808,6 +21073,8 @@ }, "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -19819,6 +21086,8 @@ }, "node_modules/write-json-file": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz", + "integrity": "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -19835,6 +21104,8 @@ }, "node_modules/write-json-file/node_modules/detect-indent": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", "dev": true, "license": "MIT", "engines": { @@ -19843,6 +21114,8 @@ }, "node_modules/write-json-file/node_modules/make-dir": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "license": "MIT", "dependencies": { @@ -19855,6 +21128,8 @@ }, "node_modules/write-json-file/node_modules/pify": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "license": "MIT", "engines": { @@ -19863,6 +21138,8 @@ }, "node_modules/write-json-file/node_modules/semver": { "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -19871,6 +21148,8 @@ }, "node_modules/write-json-file/node_modules/write-file-atomic": { "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "license": "ISC", "dependencies": { @@ -19881,6 +21160,8 @@ }, "node_modules/write-pkg": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-4.0.0.tgz", + "integrity": "sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==", "dev": true, "license": "MIT", "dependencies": { @@ -19894,6 +21175,8 @@ }, "node_modules/write-pkg/node_modules/type-fest": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", + "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -19901,7 +21184,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { @@ -19929,6 +21214,8 @@ }, "node_modules/xtend": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, "license": "MIT", "engines": { @@ -19948,17 +21235,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.7.0", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "dev": true, @@ -20208,15 +21484,20 @@ "graphology-types": ">=0.24.0" } }, - "packages/gephi-lite/node_modules/immutable": { - "version": "4.3.7", - "dev": true, - "license": "MIT" - }, "packages/gephi-lite/node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" diff --git a/packages/gephi-lite/src/components/ColorPicker.tsx b/packages/gephi-lite/src/components/ColorPicker.tsx index 2fd4b728..6740307d 100644 --- a/packages/gephi-lite/src/components/ColorPicker.tsx +++ b/packages/gephi-lite/src/components/ColorPicker.tsx @@ -1,4 +1,4 @@ -import { FC, useRef } from "react"; +import { FC, useEffect, useRef } from "react"; import { SketchPicker } from "react-color"; import { hexToRgba, rgbaToHex } from "../utils/colors"; @@ -34,13 +34,18 @@ const ColorPicker: FC< ( | { color: string | undefined; onChange: (color: string | undefined) => void; clearable: true } | { color: string; onChange: (color: string) => void; clearable?: false } - ) & { className?: string } -> = ({ color, onChange, clearable, className }) => { + ) & { className?: string; autoFocus?: boolean } +> = ({ color, onChange, clearable, className, autoFocus }) => { const tooltipRef = useRef(null); + const buttonRef = useRef(null); + useEffect(() => { + if (buttonRef.current && autoFocus) buttonRef.current.focus(); + }, [autoFocus]); return ( + + + + )} + {props.children} + + ), + NoOptionsMessage: (props: { selectProps: { inputValue: string } }) => { + const { t } = useTranslation(); + return ( +
+ {props.selectProps.inputValue.length > 0 ? ( + {t(`search.${type || "graph"}.no_result`)} + ) : ( + {t(`search.${type || "graph"}.help`)} + )} +
+ ); + }, + }, + }; + + return isControlled ? ( + {...sharedProps} options={restoredOptions ?? []} isLoading={false} /> + ) : ( - className={className} - isClearable - controlShouldRenderValue={!!value} - placeholder={t(`search.${type || "graph"}.placeholder`)} - value={value || null} + {...sharedProps} + defaultInputValue={defaultInputValue} loadOptions={debounce(loadOptions, 200)} - onChange={onChange} - components={{ - SingleValue, - Option: OptionComponent, - DropdownIndicator: IndicatorComponent, - NoOptionsMessage: (props) => { - const { t } = useTranslation(); - return ( -
- {props.selectProps.inputValue.length > 0 ? ( - {t(`search.${type || "graph"}.no_result`)} - ) : ( - {t(`search.${type || "graph"}.help`)} - )} -
- ); - }, - }} /> ); }; diff --git a/packages/gephi-lite/src/components/GraphSearchSelection.tsx b/packages/gephi-lite/src/components/GraphSearchSelection.tsx index 5130202d..cd91c347 100644 --- a/packages/gephi-lite/src/components/GraphSearchSelection.tsx +++ b/packages/gephi-lite/src/components/GraphSearchSelection.tsx @@ -1,14 +1,23 @@ import { type FC, useCallback } from "react"; -import { useSelectionActions } from "../core/context/dataContexts"; +import { useSearchActions, useSearchQuery, useSelectionActions } from "../core/context/dataContexts"; import { EVENTS, useEventsContext } from "../core/context/eventsContext"; +import { useModal } from "../core/modals"; import { GraphSearch, type Option, type OptionItem } from "./GraphSearch"; const RESULT_MAX_SIZE = 25; -export const GraphSearchSelection: FC<{ className?: string }> = ({ className }) => { +export const GraphSearchSelection: FC<{ className?: string; visible?: boolean }> = ({ className, visible = true }) => { const { emitter } = useEventsContext(); const { select } = useSelectionActions(); + const { setQuery } = useSearchActions(); + // A modal (create node/edge, save as...) always covers the whole page: the portaled results + // dropdown must not keep floating on top of it - see the `visible` prop's doc on GraphSearch. + const { modal } = useModal(); + // Controlled input text, kept in an atom rather than inside react-select: the left panel unmounts + // whenever it is collapsed (or a tool takes its place), so an internal value would be lost every + // time. Restoring it also brings its results back, see GraphSearch. + const searchQuery = useSearchQuery(); const onChange = useCallback( (option: Option | null) => { @@ -84,5 +93,15 @@ export const GraphSearchSelection: FC<{ className?: string }> = ({ className }) [emitter, select], ); - return ; + return ( + + ); }; diff --git a/packages/gephi-lite/src/components/GraphSummary.tsx b/packages/gephi-lite/src/components/GraphSummary.tsx index f4baaf3b..53717b33 100644 --- a/packages/gephi-lite/src/components/GraphSummary.tsx +++ b/packages/gephi-lite/src/components/GraphSummary.tsx @@ -1,28 +1,54 @@ import type { ItemType } from "@gephi/gephi-lite-sdk"; import cx from "classnames"; import { capitalize } from "lodash"; -import { type FC, useMemo } from "react"; +import { type FC, type ReactNode, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useFilteredGraph, useFilters, useGraphDataset, usePreferences } from "../core/context/dataContexts"; +import { getMostRecentlyUpdatedItem } from "../core/graph/dates"; import { useModal } from "../core/modals"; -import { EditIcon, FiltersIconFill } from "./common-icons"; +import { CreateEdgeIcon, CreateNodeIcon, EditIcon, FiltersIcon, FiltersIconFill } from "./common-icons"; +import { EdgeComponentById } from "./data/Edge"; +import { EditEdgeModal } from "./data/EditEdge"; +import { EditNodeModal } from "./data/EditNode"; +import { NodeComponentById } from "./data/Node"; import { GraphMetadataModal } from "./modals/GraphMetadataModal"; -const GraphStat: FC<{ className?: string; type: ItemType; current: number; total: number }> = ({ - className, - type, - current, - total, -}) => { +const GraphStat: FC<{ + className?: string; + type: ItemType; + current: number; + total: number; + // The item of this type whose update date is the most recent, if any: shown (locatable) with its + // date under the count, so the last edited node/edge is always one click away from the summary. + mostRecent: { id: string; date: string } | null; + // Extra content shown below the most-recent item's date (used to slot the graph type under the + // nodes column, roughly level with the edge's 3-line source/target preview in the edges column). + footer?: ReactNode; +}> = ({ className, type, current, total, mostRecent, footer }) => { const { locale } = usePreferences(); const { t } = useTranslation(); + const { openModal } = useModal(); const isFiltered = useMemo(() => current !== total, [current, total]); return ( -
-
{capitalize(t(`graph.model.${type}`))}
+
+
+ {capitalize(t(`graph.model.${type}`))} + +
{current.toLocaleString(locale)} @@ -30,6 +56,17 @@ const GraphStat: FC<{ className?: string; type: ItemType; current: number; total {isFiltered &&
of {total.toLocaleString(locale)}
}
+ {mostRecent && ( +
+ {type === "nodes" ? ( + + ) : ( + + )} +
{mostRecent.date}
+
+ )} + {footer}
); }; @@ -54,25 +91,43 @@ const GraphTitle: FC<{ title?: string }> = ({ title }) => { ); }; -export const GraphSummary: FC<{ className?: string }> = ({ className }) => { +export const GraphSummary: FC<{ className?: string; onOpenFilters?: () => void }> = ({ className, onOpenFilters }) => { const { t } = useTranslation(); const filterState = useFilters(); const filteredGraph = useFilteredGraph(); - const { metadata, fullGraph } = useGraphDataset(); + const { metadata, fullGraph, nodeData, edgeData } = useGraphDataset(); - const hasFilters = useMemo(() => !!filterState.filters.length, [filterState.filters]); + // A filter that has been switched off no longer restricts the graph, so it must not keep the + // badge filled: only the filters still applied count as "active". + const hasFilters = useMemo(() => filterState.filters.some((filter) => !filter.disabled), [filterState.filters]); + const mostRecentNode = useMemo(() => getMostRecentlyUpdatedItem(nodeData), [nodeData]); + const mostRecentEdge = useMemo(() => getMostRecentlyUpdatedItem(edgeData), [edgeData]); return (
-
- - +
+ {t(`graph.model.${fullGraph.type}_graph`)}} + /> +
- {t(`graph.model.${fullGraph.type}_graph`)} - {hasFilters && } +
); diff --git a/packages/gephi-lite/src/components/PreproductionBanner.tsx b/packages/gephi-lite/src/components/PreproductionBanner.tsx new file mode 100644 index 00000000..7f9be1d8 --- /dev/null +++ b/packages/gephi-lite/src/components/PreproductionBanner.tsx @@ -0,0 +1,16 @@ +import { FC } from "react"; + +/** + * A fixed badge shown only on the pre-production instance, to make it obvious at a glance that this + * is not production. Detected purely from the deployed URL: the pre-production workflow publishes + * the app under a `/preprod/` sub-path (see deploy-app-preprod.yml), which production never has. + */ +export const PreproductionBanner: FC = () => { + if (!window.location.pathname.includes("/preprod/")) return null; + + return ( +
+ PRÉ-PRODUCTION +
+ ); +}; diff --git a/packages/gephi-lite/src/components/SideMenu.tsx b/packages/gephi-lite/src/components/SideMenu.tsx index af2a7d58..3f648647 100644 --- a/packages/gephi-lite/src/components/SideMenu.tsx +++ b/packages/gephi-lite/src/components/SideMenu.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import { IconType } from "react-icons"; import TetherComponent from "react-tether"; +import { useMobile } from "../hooks/useMobile"; import { CaretDownIcon, CaretRightIcon } from "./common-icons"; type MenuCommon = { @@ -83,11 +84,17 @@ function ExpandableItem({ selected?: string; onSelectedChange: (item: MenuItem) => void; }) { + const isMobile = useMobile(); const [isExpanded, setIsExpanded] = useState(false); const [isHovered, setIsHovered] = useState(false); const [showTooltip, setShowTooltip] = useState(false); useEffect(() => { + // On touch devices, taps fire a "mouseenter" with no matching "mouseleave", which would + // otherwise leave this hover preview (meant for desktop only) stuck open indefinitely, + // floating on top of whichever panel is displayed next (see #reported bug). + if (isMobile) return; + if (isHovered) { setShowTooltip(true); return; @@ -99,7 +106,7 @@ function ExpandableItem({ return () => { clearTimeout(id); }; - }, [isHovered]); + }, [isHovered, isMobile]); const childrenMenuContent = item.children.map((item) => (
  • @@ -129,6 +136,7 @@ function ExpandableItem({ )} renderElement={(ref) => + !isMobile && showTooltip && !isExpanded && (
    = { @@ -222,3 +248,4 @@ export const STATUS_ICONS = { warning: PiWarning, error: PiWarningOctagon, } as const; +export const WarningIcon = PiWarning; diff --git a/packages/gephi-lite/src/components/data/Attribute.tsx b/packages/gephi-lite/src/components/data/Attribute.tsx index 46295dfc..17842288 100644 --- a/packages/gephi-lite/src/components/data/Attribute.tsx +++ b/packages/gephi-lite/src/components/data/Attribute.tsx @@ -16,14 +16,19 @@ import ReactLinkify from "react-linkify"; import { MultiValueProps, OptionProps, SingleValueProps, components } from "react-select"; import { GroupBase } from "react-select/dist/declarations/src/types"; -import { castScalarToModelValue, serializeModelValueToScalar } from "../../core/graph/fieldModel"; +import { + castScalarToEditableValue, + castScalarToModelValue, + serializeModelValueToScalar, +} from "../../core/graph/fieldModel"; import { useDataCollection } from "../../hooks/useDataCollection"; import { prettifyURL } from "../../utils/linkify"; import { DEFAULT_LINKIFY_PROPS } from "../../utils/url"; import ColorPicker, { InlineColorPicker } from "../ColorPicker"; import MessageTooltip from "../MessageTooltip"; -import { FieldModelIcon, InvalidDataIcon } from "../common-icons"; +import { CancelIcon, FieldModelIcon, InvalidDataIcon } from "../common-icons"; import { Checkbox } from "../forms/Checkbox"; +import { NumberInput } from "../forms/NumberInput"; import { CreatableSelect, StringOption, optionize } from "../forms/Select"; /** @@ -96,6 +101,35 @@ export const RenderKeywords = AttributeRenderers.keywords; export const RenderDate = AttributeRenderers.date; export const RenderColor = AttributeRenderers.color; +// Order-sensitive "first empty field" autofocus: a value is considered empty when it's the +// field's own no-value state (unset, blank, or an empty list) - 0 and false are real values, not emptiness. +export const isEmptyFieldValue = (value: unknown): boolean => + value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0); + +export const getFirstEmptyValueIndex = (values: unknown[]): number => values.findIndex(isEmptyFieldValue); + +/** + * The "no value" marker to store in a react-hook-form field when the user empties it. + * + * It cannot be `undefined`: react-hook-form falls back to a field's defaultValue whenever its value + * is `undefined`, so the original value would pop straight back the moment the input becomes empty - + * making the field impossible to clear (and thus to retype). `null` means the same thing, but being + * a *defined* value it is kept as-is, and every `castScalarToModelValue` branch reads it as empty. + */ +export const EMPTY_FIELD_VALUE = null; + +/** Normalizes what an editor emits into a value react-hook-form will not silently revert. */ +export const toFormFieldValue = (value: Scalar): Scalar => value ?? EMPTY_FIELD_VALUE; + +/** + * Whether a value can be stored in the given field. Emptiness is always valid (the attribute is + * simply not set); anything else must survive the field's cast, ie. actually be a URL, a number, a + * date... Used to validate on submit rather than while typing, so an incomplete entry never fights + * the user mid-input. + */ +export const isValidFieldValue = (scalar: Scalar, field: FieldModelTypeSpec): boolean => + isEmptyFieldValue(scalar) || castScalarToModelValue(scalar, field) !== undefined; + export const RenderItemAttribute: FC<{ field: FieldModelTypeSpec; value: Scalar }> = ({ field, value }) => { const castValue = castScalarToModelValue(value, field); const AttributeRenderer = AttributeRenderers[field.type] as FC<{ value?: ModelValueType }>; @@ -132,6 +166,9 @@ const StringEditor = ({ ref={ref} className="form-control" type="string" + // The browser's own "previously typed values" dropdown would cover the suggestions this form + // shows underneath (similar nodes, matching edges...), hiding the very thing the user needs. + autoComplete="off" value={value ?? ""} placeholder={placeholder} onChange={(e) => onChange(e.target.value || undefined)} @@ -152,25 +189,9 @@ export const AttributeEditors: { } = { text: StringEditor, url: StringEditor, - number: ({ value, onChange, id, autoFocus, placeholder }) => { - const ref = useRef(null); - useEffect(() => { - if (ref.current && autoFocus) ref.current.focus(); - }, [autoFocus]); - - return ( - onChange(e.target.value ? +e.target.value : undefined)} - /> - ); - }, + number: ({ value, onChange, id, autoFocus, placeholder }) => ( + + ), boolean: ({ value, onChange, id, autoFocus }) => (
    { @@ -311,14 +333,14 @@ export const AttributeEditors: { /> ); }, - color: ({ value, onChange, inTooltip }) => { + color: ({ value, onChange, inTooltip, autoFocus }) => { return inTooltip ? (
    onChange(v)} />
    ) : (
    - onChange(v)} /> + onChange(v)} autoFocus={autoFocus} />
    ); }, @@ -330,6 +352,11 @@ export const EditCategory = AttributeEditors.category; export const EditKeywords = AttributeEditors.keywords; export const EditDate = AttributeEditors.date; +// Field types whose editor is a plain input, with no built-in way to empty it in one gesture: the +// selects (category, keywords) and the color picker already provide their own clear control, and a +// checkbox is never "empty". +const CLEARABLE_WITH_BUTTON: FieldModelType[] = ["text", "url", "number", "date"]; + export const EditItemAttribute: FC<{ field: FieldModel; scalar: Scalar; @@ -338,7 +365,15 @@ export const EditItemAttribute: FC<{ autoFocus?: boolean; inTooltip?: boolean; placeholder?: string; -}> = ({ field, scalar, onChange, id, autoFocus, inTooltip, placeholder }) => { + /** + * Adds a button emptying the field in one click, for the plain-input types that have no such + * control of their own. Opt-in: it is meant for form-like editors (node/edge edition), and would + * be ambiguous next to a cancel button of the same shape (see the data table cell editor). + */ + clearable?: boolean; +}> = ({ field, scalar, onChange, id, autoFocus, inTooltip, placeholder, clearable }) => { + const { t } = useTranslation(); + const editorWrapper = useRef(null); const EditComponent = AttributeEditors[field.type] as FC<{ field: FieldModel; onChange: (value?: FieldModelAbstraction[FieldModelType]["expectedOutput"]) => void; @@ -349,17 +384,45 @@ export const EditItemAttribute: FC<{ inTooltip?: boolean; }>; - return ( + const editor = ( onChange(serializeModelValueToScalar(value, field, scalar))} + value={castScalarToEditableValue(scalar, field)} + onChange={(value) => onChange(toFormFieldValue(serializeModelValueToScalar(value, field, scalar)))} /> ); + + if (!clearable || !CLEARABLE_WITH_BUTTON.includes(field.type)) return editor; + + return ( +
    + {/* min-width:0 lets the editor shrink inside the flex row instead of overflowing it. */} +
    + {editor} +
    + +
    + ); }; /** diff --git a/packages/gephi-lite/src/components/data/CreateScriptedFieldModel.tsx b/packages/gephi-lite/src/components/data/CreateScriptedFieldModel.tsx index b2eb1f9b..cc7328df 100644 --- a/packages/gephi-lite/src/components/data/CreateScriptedFieldModel.tsx +++ b/packages/gephi-lite/src/components/data/CreateScriptedFieldModel.tsx @@ -3,7 +3,7 @@ import cx from "classnames"; import { FC, useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useFilteredGraph, useGraphDataset, useGraphDatasetActions } from "../../core/context/dataContexts"; +import { useGraphDataset, useGraphDatasetActions } from "../../core/context/dataContexts"; import { graphDatasetAtom } from "../../core/graph"; import { inferFieldType } from "../../core/graph/fieldModel"; import { dataGraphToFullGraph } from "../../core/graph/utils"; @@ -20,6 +20,8 @@ export type CreateScriptedFieldModelFormProps = { onCancel: () => void; type: ItemType; insertAt?: { id: string; pos: "before" | "after" }; + // When set, the form edits the script of an existing formula field instead of creating a new one: + fieldModelId?: string; }; type ScriptedFieldModelFunction = (id: string, attributes: ItemData, index: number, graph: FullGraph) => Scalar; @@ -78,13 +80,19 @@ export const useCreateScriptedFieldModelForm = ({ onSubmitted, type, insertAt, + fieldModelId, }: CreateScriptedFieldModelFormProps) => { const { t } = useTranslation(); const { notify } = useNotifications(); const dataset = useGraphDataset(); - const filteredGraph = useFilteredGraph(); - const { createFieldModel } = useGraphDatasetActions(); + const { createFieldModel, setFieldModel } = useGraphDatasetActions(); const { nodeFields, edgeFields } = dataset; + const fields = type === "nodes" ? nodeFields : edgeFields; + + // Edit mode: we are changing the script of an existing formula field. + const editedField = useMemo(() => fields.find((f) => f.id === fieldModelId), [fields, fieldModelId]); + const isEditing = !!editedField; + const checkFunction = useCallback( (fn: ScriptedFieldModelFunction) => { if (!fn) throw new Error("Function is not defined"); @@ -98,79 +106,79 @@ export const useCreateScriptedFieldModelForm = ({ [type], ); - const [newId, setNewId] = useState(""); - // TODO: Add input for scope - const [scope, _setScope] = useState<"all" | "filtered" /* | "selected"*/>("all"); + const [newId, setNewId] = useState(editedField?.id ?? ""); + const [newLabel, setNewLabel] = useState(editedField?.label ?? ""); - const fields = type === "nodes" ? nodeFields : edgeFields; - const existingField = useMemo(() => fields.find((f) => f.id === newId), [fields, newId]); + const existingField = useMemo( + () => (isEditing ? undefined : fields.find((f) => f.id === newId)), + [fields, isEditing, newId], + ); const isFormValid = useMemo(() => !!newId && !existingField, [existingField, newId]); const onSubmit = useCallback( (script: ScriptedFieldModelFunction) => { try { - let graph: FullGraph; - switch (scope) { - // TODO: - // case "selected": - // graph = dataGraphToFullGraph(dataset, dataset); - // break; - case "filtered": - graph = dataGraphToFullGraph(dataset, filteredGraph); - break; - case "all": - default: - graph = dataGraphToFullGraph(dataset); - // graph = dataGraphToFullGraph(graphDatasetAtom.get()); - break; - } - + // The values are NOT stored: the script is persisted in the field model and recomputed on + // the fly. We still run it once over the whole graph to validate it and infer the field type. + const graph: FullGraph = dataGraphToFullGraph(dataset); Object.freeze(graph); - const values: Record = {}; - if (type === "nodes") { - graph.nodes().forEach((id, index) => { - values[id] = script(id, graph.getNodeAttributes(id), index, graph); - }); - } else { - graph.edges().forEach((id, index) => { - values[id] = script(id, graph.getEdgeAttributes(id), index, graph); - }); - } - - const valuesArray = Object.values(values); + const ids = type === "nodes" ? graph.nodes() : graph.edges(); + const sampleValues: Scalar[] = ids.map((id, index) => + script(id, type === "nodes" ? graph.getNodeAttributes(id) : graph.getEdgeAttributes(id), index, graph), + ); const fieldModel: FieldModel = { id: newId, itemType: type, - ...inferFieldType(newId, valuesArray, valuesArray.length), + label: newLabel || undefined, + script, + ...inferFieldType(newId, sampleValues, sampleValues.length), }; - const index = insertAt - ? fields.findIndex((f) => f.id === insertAt.id) + (insertAt.pos === "before" ? -1 : 1) - : undefined; - createFieldModel(fieldModel, { index, values }); + + if (isEditing) { + setFieldModel(fieldModel); + } else { + const index = insertAt + ? fields.findIndex((f) => f.id === insertAt.id) + (insertAt.pos === "before" ? -1 : 1) + : undefined; + createFieldModel(fieldModel, { index }); + } notify({ type: "success", - title: t(`edition.create_${type}_scripted_field`), - message: t(`edition.create_${type}_scripted_field_success`), + title: t(`edition.${isEditing ? "update" : "create"}_${type}_scripted_field`), + message: t(`edition.${isEditing ? "update" : "create"}_${type}_scripted_field_success`), }); } catch (e) { notify({ type: "error", - title: t(`edition.create_${type}_scripted_field`), + title: t(`edition.${isEditing ? "update" : "create"}_${type}_scripted_field`), message: (e as Error).message || t("error.unknown"), }); } if (onSubmitted) onSubmitted(); }, - [createFieldModel, dataset, fields, filteredGraph, insertAt, newId, notify, onSubmitted, scope, t, type], + [ + createFieldModel, + dataset, + fields, + insertAt, + isEditing, + newId, + newLabel, + notify, + onSubmitted, + setFieldModel, + t, + type, + ], ); const { content: editorContent, getFunction } = useFunctionEditor({ checkFunction, functionJsDoc: BASE_JS[type].doc, - initialFunctionCode: BASE_JS[type].baseFn, + initialFunctionCode: editedField?.script?.toString() ?? BASE_JS[type].baseFn, onSubmit: isFormValid ? onSubmit : undefined, - saveAndRunI18nKey: "datatable.save_and_create_column", + saveAndRunI18nKey: isEditing ? "datatable.save_and_update_column" : "datatable.save_and_create_column", }); const submit = useCallback(() => { const fn = getFunction(); @@ -181,7 +189,7 @@ export const useCreateScriptedFieldModelForm = ({ submit, main: (
    -

    {t(`edition.create_${type}_scripted_field`)}

    +

    {t(`edition.${isEditing ? "update" : "create"}_${type}_scripted_field`)}

    +
    + + setNewLabel(e.target.value)} + /> +
    + {editorContent}
    ), @@ -216,7 +238,7 @@ export const useCreateScriptedFieldModelForm = ({
    @@ -241,7 +263,7 @@ export const CreateScriptedFieldModelModal: FC< return ( cancel()} className="modal-lg edit-attribute" onSubmit={submitForm} diff --git a/packages/gephi-lite/src/components/data/DataCreationMenu.tsx b/packages/gephi-lite/src/components/data/DataCreationMenu.tsx new file mode 100644 index 00000000..3fb7a5a9 --- /dev/null +++ b/packages/gephi-lite/src/components/data/DataCreationMenu.tsx @@ -0,0 +1,48 @@ +import { ComponentType } from "react"; + +import { MenuItem } from "../SideMenu"; +import { DataCreationIcon, DataCreationIconFill } from "../common-icons"; +import { CreateScriptedFieldModelForm } from "./CreateScriptedFieldModel"; +import { EditEdgeForm } from "./EditEdge"; +import { EditFieldModelForm } from "./EditFieldModel"; +import { EditNodeForm } from "./EditNode"; + +export type Panel = ComponentType<{ close: () => void }>; + +export const DATA_CREATION_MENU_ITEM: MenuItem<{ panel?: Panel }> = { + id: "data-creation", + i18nKey: "edition.data_creation", + icon: { normal: DataCreationIcon, fill: DataCreationIconFill }, + children: [ + { + id: "data-creation-node", + i18nKey: "edition.create_nodes", + panel: ({ close }) => , + }, + { + id: "data-creation-edge", + i18nKey: "edition.create_edges", + panel: ({ close }) => , + }, + { + id: "data-creation-node-field", + i18nKey: "edition.create_nodes_field", + panel: ({ close }) => , + }, + { + id: "data-creation-edge-field", + i18nKey: "edition.create_edges_field", + panel: ({ close }) => , + }, + { + id: "data-creation-node-scripted-field", + i18nKey: "edition.create_nodes_scripted_field", + panel: ({ close }) => , + }, + { + id: "data-creation-edge-scripted-field", + i18nKey: "edition.create_edges_scripted_field", + panel: ({ close }) => , + }, + ], +}; diff --git a/packages/gephi-lite/src/components/data/Edge.tsx b/packages/gephi-lite/src/components/data/Edge.tsx index ad108bc9..fe66ad7b 100644 --- a/packages/gephi-lite/src/components/data/Edge.tsx +++ b/packages/gephi-lite/src/components/data/Edge.tsx @@ -1,5 +1,6 @@ import cx from "classnames"; import { FC, ReactNode, useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { getItemAttributes } from "../../core/appearance/utils"; import { @@ -9,7 +10,8 @@ import { useVisualGetters, } from "../../core/context/dataContexts"; import { mergeStaticDynamicData } from "../../core/graph/dynamicAttributes"; -import { NodeComponent } from "./Node"; +import { useLocateInGraph } from "../../hooks/useLocateInGraph"; +import { FILTERED_ITEM_COLOR, NodeComponent } from "./Node"; export const EdgeComponent: FC<{ source: { label: ReactNode; color: string; hidden?: boolean }; @@ -19,38 +21,92 @@ export const EdgeComponent: FC<{ hidden?: boolean; directed?: boolean; className?: string; -}> = ({ label, color, source, target, hidden, directed, className }) => { + onSourceClick?: () => void; + onTargetClick?: () => void; + onEdgeClick?: () => void; + nodeButtonTitle?: string; + edgeButtonTitle?: string; +}> = ({ + label, + color, + source, + target, + hidden, + directed, + className, + onSourceClick, + onTargetClick, + onEdgeClick, + nodeButtonTitle, + edgeButtonTitle, +}) => { + // A filtered out edge is drawn dotted and grey (see FILTERED_ITEM_COLOR) rather than keeping its + // appearance color, and so are both its extremities (see below), even when those nodes pass the + // filters themselves: the whole row is absent from the graph, so it reads as filtered as a whole. + const strokeColor = hidden ? FILTERED_ITEM_COLOR : color; + const middle = ( +
    +
    + {" "} + {directed && } +
    + {label && ( + + )} +
    + ); + return (
    - -
    -
    -
    - {" "} - {directed && } -
    - {label && ( - - )} +
    + {onEdgeClick ? ( + + ) : ( + middle + )}
    - +
    ); }; -export const EdgeComponentById: FC<{ id: string }> = ({ id }) => { +export const EdgeComponentById: FC<{ id: string; locatable?: boolean }> = ({ id, locatable }) => { + const { t } = useTranslation(); const graphDataset = useGraphDataset(); const { dynamicNodeData, dynamicEdgeData } = useDynamicItemData(); const visualGetters = useVisualGetters(); const filteredGraph = useFilteredGraph(); + const { locateNode, locateEdge } = useLocateInGraph(); + // A filtered out edge is rendered too (dotted and greyed out, see FILTERED_ITEM_COLOR): its + // data, and its extremities', come from the dataset, which holds every item whatever the filters. + // Only an edge missing from the dataset entirely (just deleted) renders nothing. const data = useMemo(() => { - if (!filteredGraph.hasEdge(id)) return null; + if (!graphDataset.fullGraph.hasEdge(id)) return null; const source = getItemAttributes( "nodes", @@ -83,14 +139,21 @@ export const EdgeComponentById: FC<{ id: string }> = ({ id }) => { }; }, [id, graphDataset, visualGetters, dynamicNodeData, dynamicEdgeData, filteredGraph]); - return data ? ( - - ) : ( - ?} - color="lightgrey" - source={{ label: ?, color: "lightgrey" }} - target={{ label: ?, color: "lightgrey" }} - /> - ); + if (!data) return null; + + // When locatable, clicking the source/target nodes locates them and clicking the middle + // (connector/arrow) locates the edge — each navigating to the graph and centering the camera. + // A filtered out edge is absent from the rendering, so there is nothing to locate. + const locateProps = + locatable && !data.hidden + ? { + nodeButtonTitle: t("selection.locate_on_graph"), + edgeButtonTitle: t("selection.locate_on_graph"), + onSourceClick: () => locateNode(graphDataset.fullGraph.source(id), { navigateToGraph: true }), + onTargetClick: () => locateNode(graphDataset.fullGraph.target(id), { navigateToGraph: true }), + onEdgeClick: () => locateEdge(id, { navigateToGraph: true }), + } + : {}; + + return ; }; diff --git a/packages/gephi-lite/src/components/data/EditEdge.tsx b/packages/gephi-lite/src/components/data/EditEdge.tsx index 46d970d8..249cd0d3 100644 --- a/packages/gephi-lite/src/components/data/EditEdge.tsx +++ b/packages/gephi-lite/src/components/data/EditEdge.tsx @@ -1,20 +1,35 @@ import { FieldModelTypeSpec, toNumber } from "@gephi/gephi-lite-sdk"; import cx from "classnames"; -import { fromPairs, keyBy, pick } from "lodash"; -import { FC, useMemo } from "react"; +import { fromPairs, isEmpty, keyBy, pick } from "lodash"; +import { FC, ReactNode, useCallback, useEffect, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router"; -import { useGraphDataset, useGraphDatasetActions, useSelectionActions } from "../../core/context/dataContexts"; +import { useRemoteFileFreshnessCheck } from "../../core/cloud/useRemoteFileGuard"; +import { + useGraphDataset, + useGraphDatasetActions, + useSearchQuery, + useSelectionActions, +} from "../../core/context/dataContexts"; import { EVENTS, useEventsContext } from "../../core/context/eventsContext"; import { ModalProps } from "../../core/modals/types"; import { useNotifications } from "../../core/notifications"; +import { focusCameraOnEdges, requestFocusOnReady } from "../../core/sigma"; import { Scalar } from "../../core/types"; import { GraphSearch } from "../GraphSearch"; -import { CancelIcon, FieldModelIcon } from "../common-icons"; +import { CancelIcon, FieldModelIcon, SwapIcon, WarningIcon } from "../common-icons"; import { Select } from "../forms/Select"; -import { Modal } from "../modals"; -import { EditItemAttribute } from "./Attribute"; +import { CloseModalButton, Modal } from "../modals"; +import { + EditItemAttribute, + getFirstEmptyValueIndex, + isEmptyFieldValue, + isValidFieldValue, + toFormFieldValue, +} from "./Attribute"; +import { EdgeComponentById } from "./Edge"; interface UpdatedEdgeState { id: string; @@ -24,32 +39,77 @@ interface UpdatedEdgeState { attributes: ({ key: string; value: Scalar } & FieldModelTypeSpec)[]; } +// One or more edges already connect the selected source & target (in either direction): surfaced +// so the user notices before creating an accidental duplicate. Clicking cancels the creation and +// selects/locates the existing edge(s) instead. +const DuplicateEdgeWarning: FC<{ edgeIds: string[]; onClick: () => void }> = ({ edgeIds, onClick }) => { + const { t } = useTranslation(); + return ( +
    +
    + + {t("edition.duplicate_edge_warning", { count: edgeIds.length })} +
    +
      + {edgeIds.map((id) => ( +
    • + +
    • + ))} +
    +
    + ); +}; + const useEditEdgeForm = ({ edgeId, + source: initialSource, + target: initialTarget, onSubmitted, onCancel, + submitLabel, + submitFirst, }: { edgeId?: string; + source?: string; + target?: string; onSubmitted: () => void; onCancel: () => void; + // The modal usage (see EditEdgeModal) also shows a copy of this same submit button in its + // header, since on mobile the on-screen keyboard can cover the footer while a field is focused; + // there, it overrides the label to a short "OK" and is placed before the cancel button, to match + // the header's [submit, close] order. + submitLabel?: ReactNode; + submitFirst?: boolean; }) => { const { t } = useTranslation(); const { notify } = useNotifications(); const { emitter } = useEventsContext(); const { select } = useSelectionActions(); const { createEdge, updateEdge } = useGraphDatasetActions(); - const { edgeData, layout, fullGraph, edgeFields } = useGraphDataset(); + const { edgeData, layout, fullGraph, edgeFields: allEdgeFields } = useGraphDataset(); + // Read-only (eg. system date) and formula (scripted) fields are managed automatically, so they + // are not editable through this form: + const edgeFields = useMemo(() => allEdgeFields.filter((ef) => !ef.readOnly && !ef.script), [allEdgeFields]); const edgeFieldsIndex = useMemo(() => keyBy(edgeFields, "id"), [edgeFields]); + const searchQuery = useSearchQuery(); const isNew = typeof edgeId === "undefined"; const defaultValues = useMemo(() => { if (isNew) return { - weight: 1, + source: initialSource, + target: initialTarget, isDirected: fullGraph.type !== "undirected", attributes: edgeFields.map((nf) => ({ key: nf.id, - value: undefined, + value: nf.defaultValue, ...pick(nf, ["type", "format", "separator"]), })), }; @@ -67,18 +127,57 @@ const useEditEdgeForm = ({ ...pick(nf, ["type", "format", "separator"]), })), }; - }, [edgeData, edgeId, fullGraph, isNew, edgeFields]); + }, [edgeData, edgeId, fullGraph, isNew, edgeFields, initialSource, initialTarget]); const { register, handleSubmit, control, setValue, + getValues, watch, - formState: { errors }, + formState: { errors, dirtyFields }, } = useForm({ defaultValues, }); const attributes = watch("attributes"); + const source = watch("source"); + const target = watch("target"); + const location = useLocation(); + const navigate = useNavigate(); + + // Any edge already connecting these two nodes, in either direction (graphology's `edges(a, b)` + // already checks both, see its `in`/`out` adjacencies): + const existingEdgeIds = useMemo(() => { + if (!isNew || !source || !target || !fullGraph.hasNode(source) || !fullGraph.hasNode(target)) return []; + return fullGraph.edges(source, target); + }, [isNew, source, target, fullGraph]); + + const selectExistingEdges = useCallback(() => { + select({ type: "edges", items: new Set(existingEdgeIds), replace: true }); + // Only focus right away if already on the graph page: navigating there (even by "replace", + // see useLocateInGraph) only remounts sigma - and thus replays a pending focus - when it + // wasn't already mounted. + if (location.pathname === "/") { + focusCameraOnEdges(existingEdgeIds); + } else { + requestFocusOnReady("edges", existingEdgeIds[0]); + navigate("/", { replace: true }); + } + onCancel(); + }, [select, existingEdgeIds, location.pathname, navigate, onCancel]); + + // Autofocus the first empty field on mount, in render order (source, target, attributes, then + // id): computed once from the initial values, so filling a field never steals focus elsewhere. + const autoFocusIndex = useMemo(() => { + const { + source: defaultSource, + target: defaultTarget, + attributes: defaultAttributes, + id, + } = defaultValues as UpdatedEdgeState; + return getFirstEmptyValueIndex([defaultSource, defaultTarget, ...defaultAttributes.map((a) => a.value), id]); + }, [defaultValues]); + const submit = useMemo( () => handleSubmit((data) => { @@ -96,19 +195,18 @@ const useEditEdgeForm = ({ return; } - const allAttributes = { - ...fromPairs( - data.attributes - .filter(({ value }) => value !== "" || value === undefined) - .map(({ key, value }) => { - // value are all string because input are all text whatever the data model - // for now we cast value as number if they are number to help downstream algo to create appropriate data model - const valueAsNumber = toNumber(value); - return [key, valueAsNumber ? valueAsNumber : value]; - }), - ), - ...pick(data, "label", "color", "weight"), - }; + const allAttributes = fromPairs( + data.attributes.map(({ key, value }) => { + // An emptied field is written back as undefined rather than skipped: updateEdge merges + // into the existing data, so dropping the key would silently keep the previous value and + // make clearing a field impossible to save. + if (isEmptyFieldValue(value)) return [key, undefined]; + // value are all string because input are all text whatever the data model + // for now we cast value as number if they are number to help downstream algo to create appropriate data model + const valueAsNumber = toNumber(value); + return [key, valueAsNumber ? valueAsNumber : value]; + }), + ); // Create new edge: if (isNew) { @@ -134,7 +232,7 @@ const useEditEdgeForm = ({ // Update existing edge: else { try { - updateEdge(id, allAttributes, { directed: data.isDirected }); + updateEdge(id, allAttributes, { directed: data.isDirected, source: data.source, target: data.target }); select({ type: "edges", items: new Set([id]), replace: true }); notify({ type: "success", @@ -156,31 +254,13 @@ const useEditEdgeForm = ({ return { submit, + // Whether the user actually changed something, so closing the form would throw it away (the + // modal usage below turns that into a confirmation rather than a silent loss). Read from + // `dirtyFields` and not `formState.isDirty`: the latter is already true on opening, because a + // field whose default is undefined reports "" as soon as its input is registered. + hasUserInput: !isEmpty(dirtyFields), main: ( <> -
    -
    - - !isNew || (!!value && !edgeData[value]) || (!value && isNew), - })} - /> - {errors.id && ( -
    - {t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)} -
    - )} -
    -
    - {/* Extremities */}
    @@ -203,11 +283,28 @@ const useEditEdgeForm = ({ }} value={typeof value === "string" ? { type: "nodes", id: value } : null} type="nodes" + autoFocus={autoFocusIndex === 0} + defaultInputValue={isNew && !initialSource ? searchQuery : undefined} /> )} /> {errors.source &&
    {t(`error.form.${errors.source.type}`)}
    }
    +
    + +
    + + {isNew && existingEdgeIds.length > 0 && ( + + )} + {fullGraph.type === "mixed" && (
    isValidFieldValue(value, edgeFieldsIndex[field.key]) }} render={(props) => ( props.field.onChange(v)} + onChange={(v) => props.field.onChange(toFormFieldValue(v))} + autoFocus={2 + i === autoFocusIndex} /> )} /> + {(errors.attributes || [])[i]?.value && ( +
    + {t("error.form.invalid_value", { type: edgeFieldsIndex[field.key].type })} +
    + )} {(errors.attributes || [])[i]?.key && (
    {t( @@ -299,33 +412,82 @@ const useEditEdgeForm = ({
    ))}
    + + {/* ID */} +
    +
    + + !isNew || (!!value && !edgeData[value]) || (!value && isNew), + })} + /> + {errors.id && ( +
    + {t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)} +
    + )} +
    +
    ), footer: (
    - + )} + {/* Same close request as the modal's own cross, so both get the unsaved-input + confirmation; falls back to onCancel when this form is rendered in a side panel. */} + - - - + + {!submitFirst && ( + + )}
    ), }; }; -export const EditEdgeModal: FC> = ({ cancel, submit, arguments: { edgeId } }) => { +export const EditEdgeModal: FC> = ({ + cancel, + submit, + arguments: { edgeId, source, target }, +}) => { const { t } = useTranslation(); const isNew = typeof edgeId === "undefined"; + + // Probe the remote GitHub version as soon as the popup opens, so the user is warned before + // filling it in (rather than only when validating), avoiding losing their input on a reload. + const { check: checkRemoteFreshness } = useRemoteFileFreshnessCheck(); + useEffect(() => checkRemoteFreshness(), [checkRemoteFreshness]); + const { main, footer, submit: submitForm, + hasUserInput, } = useEditEdgeForm({ edgeId, + source, + target, onSubmitted: () => submit({}), onCancel: () => cancel(), + submitLabel: t("common.ok"), + submitFirst: true, }); return ( @@ -334,6 +496,9 @@ export const EditEdgeModal: FC> = ({ cancel, sub onClose={() => cancel()} className="modal-lg edit-edge" onSubmit={submitForm} + submitLabel={t("common.ok")} + doNotPreserveData + hasUnsavedInput={hasUserInput} >
    {main}
    diff --git a/packages/gephi-lite/src/components/data/EditFieldModel.tsx b/packages/gephi-lite/src/components/data/EditFieldModel.tsx index 6f7a3950..43405a18 100644 --- a/packages/gephi-lite/src/components/data/EditFieldModel.tsx +++ b/packages/gephi-lite/src/components/data/EditFieldModel.tsx @@ -12,6 +12,7 @@ import { DATE_FORMATS } from "../../utils/date"; import { CancelIcon } from "../common-icons"; import { BaseOption, Select } from "../forms/Select"; import { Modal } from "../modals"; +import { EditItemAttribute } from "./Attribute"; export type EditFieldModelFormProps = { onSubmitted: () => void; @@ -96,6 +97,12 @@ export const useEditFieldModelForm = ({ const idCollisionLabel = errors.id ? fields.find((f) => f.id === fieldModel.id)?.label : undefined; + // The default value editor needs the type's required options (date format, keywords + // separator) to render and serialize correctly, so only show it once they are set. + const canEditDefaultValue = + (fieldModel.type !== "date" || ("format" in fieldModel && !!fieldModel.format)) && + (fieldModel.type !== "keywords" || ("separator" in fieldModel && !!fieldModel.separator)); + return { submit, main: ( @@ -221,6 +228,21 @@ export const useEditFieldModelForm = ({ )}
  • )} + + {/* Default value, used to initialize the field when creating a new node/edge */} + {canEditDefaultValue && ( +
    + + setValue("defaultValue", value)} + /> +
    + )}
    ), footer: ( diff --git a/packages/gephi-lite/src/components/data/EditMultipleItems.tsx b/packages/gephi-lite/src/components/data/EditMultipleItems.tsx index 0a5297e3..16aa66db 100644 --- a/packages/gephi-lite/src/components/data/EditMultipleItems.tsx +++ b/packages/gephi-lite/src/components/data/EditMultipleItems.tsx @@ -31,15 +31,18 @@ export const useEditMultipleItemsForm = ({ onSubmitted, onCancel, type, items }: const fieldOptions = useMemo( () => - (type === "nodes" ? nodeFields : edgeFields).map((field) => ({ - field, - value: field.id, - label: ( - <> - {field.label || field.id} - - ), - })), + (type === "nodes" ? nodeFields : edgeFields) + // Read-only (eg. system date) and formula (scripted) fields are computed, so they cannot be bulk-edited: + .filter((field) => !field.readOnly && !field.script) + .map((field) => ({ + field, + value: field.id, + label: ( + <> + {field.label || field.id} + + ), + })), [edgeFields, nodeFields, type], ); @@ -103,6 +106,7 @@ export const useEditMultipleItemsForm = ({ onSubmitted, onCancel, type, items }: scalar={state.value} onChange={(value) => setState((state) => ({ ...state, value }))} id="multi-edit-value" + autoFocus /> )} diff --git a/packages/gephi-lite/src/components/data/EditNode.tsx b/packages/gephi-lite/src/components/data/EditNode.tsx index 1c60f407..493cf05a 100644 --- a/packages/gephi-lite/src/components/data/EditNode.tsx +++ b/packages/gephi-lite/src/components/data/EditNode.tsx @@ -1,17 +1,55 @@ import { FieldModelTypeSpec, NodeCoordinates, Scalar, toNumber } from "@gephi/gephi-lite-sdk"; import cx from "classnames"; -import { fromPairs, keyBy, pick } from "lodash"; -import { FC, useMemo } from "react"; +import { fromPairs, isEmpty, keyBy, pick } from "lodash"; +import { FC, ReactNode, useEffect, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { useGraphDataset, useGraphDatasetActions, useSelectionActions } from "../../core/context/dataContexts"; +import { useRemoteFileFreshnessCheck } from "../../core/cloud/useRemoteFileGuard"; +import { + useAppearance, + useGraphDataset, + useGraphDatasetActions, + useSearch, + useSearchQuery, + useSelectionActions, +} from "../../core/context/dataContexts"; import { EVENTS, useEventsContext } from "../../core/context/eventsContext"; import { ModalProps } from "../../core/modals/types"; import { useNotifications } from "../../core/notifications"; -import { CancelIcon, FieldModelIcon } from "../common-icons"; -import { Modal } from "../modals"; -import { EditItemAttribute } from "./Attribute"; +import { CancelIcon, FieldModelIcon, WarningIcon } from "../common-icons"; +import { CloseModalButton, Modal } from "../modals"; +import { + EditItemAttribute, + getFirstEmptyValueIndex, + isEmptyFieldValue, + isValidFieldValue, + toFormFieldValue, +} from "./Attribute"; +import { NodeComponentById } from "./Node"; + +// Existing nodes whose name (the current label field, or the id as a fallback) fuzzy-matches what +// is being typed for a new node: surfaced so the user notices before creating an accidental +// duplicate. Clicking one cancels the creation and locates that existing node instead, exactly +// like picking a result from the main fuzzy search box. +const SimilarNodesWarning: FC<{ nodeIds: string[]; onPick: () => void }> = ({ nodeIds, onPick }) => { + const { t } = useTranslation(); + return ( +
    +
    + + {t("edition.similar_nodes_warning")} +
    +
      + {nodeIds.map((id) => ( +
    • + +
    • + ))} +
    +
    + ); +}; interface UpdatedNodeState extends NodeCoordinates { id?: string; @@ -22,28 +60,47 @@ const useEditNodeForm = ({ nodeId, onSubmitted, onCancel, + submitLabel, + submitFirst, }: { nodeId?: string; onSubmitted: () => void; onCancel: () => void; + // The modal usage (see EditNodeModal) also shows a copy of this same submit button in its + // header, since on mobile the on-screen keyboard can cover the footer while a field is focused; + // there, it overrides the label to a short "OK" and is placed before the cancel button, to match + // the header's [submit, close] order. + submitLabel?: ReactNode; + submitFirst?: boolean; }) => { const { t } = useTranslation(); const { notify } = useNotifications(); const { emitter } = useEventsContext(); const { select } = useSelectionActions(); const { createNode, updateNode } = useGraphDatasetActions(); - const { nodeData, layout, nodeFields } = useGraphDataset(); + const { nodeData, layout, nodeFields: allNodeFields } = useGraphDataset(); + // Read-only (eg. system date) and formula (scripted) fields are managed automatically, so they + // are not editable through this form: + const nodeFields = useMemo(() => allNodeFields.filter((nf) => !nf.readOnly && !nf.script), [allNodeFields]); const nodeFieldsIndex = useMemo(() => keyBy(nodeFields, "id"), [nodeFields]); + // Which field currently drives the node label (falling back to the id, when there is no label + // field configured): used both to fuzzy-match similar existing nodes below, and to pre-fill a + // new node's label with whatever was typed in the main fuzzy search box before hitting "+". + const { nodesLabel } = useAppearance(); + const labelFieldId = nodesLabel.type === "field" ? nodesLabel.field.id : "id"; + const searchQuery = useSearchQuery(); + const isNew = typeof nodeId === "undefined"; const defaultValues = useMemo(() => { if (isNew) return { x: 0, y: 0, + id: labelFieldId === "id" ? searchQuery || undefined : undefined, attributes: nodeFields.map((nf) => ({ key: nf.id, - value: undefined, + value: nf.id === labelFieldId && searchQuery ? searchQuery : nf.defaultValue, ...pick(nf, ["type", "format", "separator"]), })), }; @@ -57,18 +114,47 @@ const useEditNodeForm = ({ ...pick(nf, ["type", "format", "separator"]), })), }; - }, [isNew, nodeId, layout, nodeData, nodeFields]); + }, [isNew, nodeId, layout, nodeData, nodeFields, labelFieldId, searchQuery]); const { register, handleSubmit, setValue, watch, control, - formState: { errors }, + formState: { errors, dirtyFields }, } = useForm({ defaultValues, }); const attributes = watch("attributes"); + const idValue = watch("id"); + + // Fuzzy-match existing nodes' names against what is being typed for this new node, using the + // same search index (and settings) as the main search box. + const { index } = useSearch(); + // Extracted as a scalar (rather than depending on the whole `attributes` array below) because + // react-hook-form's `watch("attributes")` can mutate the same array reference in place instead + // of returning a fresh one, which would make the memo below miss the update. + const labelAttrValue = attributes.find((a) => a.key === labelFieldId)?.value; + const nameQuery = useMemo(() => { + if (!isNew) return ""; + if (labelFieldId === "id") return idValue || ""; + return labelAttrValue != null ? String(labelAttrValue) : ""; + }, [isNew, labelFieldId, idValue, labelAttrValue]); + const similarNodeIds = useMemo(() => { + if (nameQuery.trim().length < 2) return []; + return index + .search(nameQuery, { prefix: true, fuzzy: 0.2, filter: (result) => result.type === "nodes" }) + .slice(0, 5) + .map((result) => result.id as string); + }, [nameQuery, index]); + + // Autofocus the first empty field on mount, in render order (attributes, then position, then + // id): computed once from the initial values, so filling a field never steals focus elsewhere. + const autoFocusIndex = useMemo(() => { + const { attributes: defaultAttributes, x, y, id } = defaultValues as UpdatedNodeState; + return getFirstEmptyValueIndex([...defaultAttributes.map((a) => a.value), x, y, id]); + }, [defaultValues]); + const submit = useMemo( () => handleSubmit((data) => { @@ -88,14 +174,16 @@ const useEditNodeForm = ({ const allAttributes = { ...fromPairs( - data.attributes - .filter(({ value }) => value !== "" || value === undefined) - .map(({ key, value }) => { - // value are all string because input are all text whatever the data model - // for now we cast value as number if they are number to help downstream algo to create appropriate data model - const valueAsNumber = toNumber(value); - return [key, valueAsNumber ? valueAsNumber : value]; - }), + data.attributes.map(({ key, value }) => { + // An emptied field is written back as undefined rather than skipped: updateNode merges + // into the existing data, so dropping the key would silently keep the previous value + // and make clearing a field impossible to save. + if (isEmptyFieldValue(value)) return [key, undefined]; + // value are all string because input are all text whatever the data model + // for now we cast value as number if they are number to help downstream algo to create appropriate data model + const valueAsNumber = toNumber(value); + return [key, valueAsNumber ? valueAsNumber : value]; + }), ), ...pick(data, "x", "y"), }; @@ -146,59 +234,13 @@ const useEditNodeForm = ({ return { submit, + // Whether the user actually changed something, so closing the form would throw it away (the + // modal usage below turns that into a confirmation rather than a silent loss). Read from + // `dirtyFields` and not `formState.isDirty`: the latter is already true on opening, because a + // field whose default is undefined reports "" as soon as its input is registered. + hasUserInput: !isEmpty(dirtyFields), main: ( <> -
    -
    - - !isNew || (!!value && !nodeData[value]) || (!value && isNew), - })} - /> - {errors.id && ( -
    - {t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)} -
    - )} -
    -
    - - {/* Rendering attributes */} -
    -
    - - -
    -
    - - -
    -
    - {/* Other attributes */}
    {attributes.map((field, i) => ( @@ -209,15 +251,25 @@ const useEditNodeForm = ({ isValidFieldValue(value, nodeFieldsIndex[field.key]) }} render={(props) => ( props.field.onChange(v)} + onChange={(v) => props.field.onChange(toFormFieldValue(v))} + autoFocus={i === autoFocusIndex} /> )} /> + {(errors.attributes || [])[i]?.value && ( +
    + {t("error.form.invalid_value", { type: nodeFieldsIndex[field.key].type })} +
    + )} {(errors.attributes || [])[i]?.key && (
    {t( @@ -229,20 +281,92 @@ const useEditNodeForm = ({ )}
    )} + {isNew && field.key === labelFieldId && similarNodeIds.length > 0 && ( + + )}
    ))} + + {/* Position */} +
    +
    + + +
    +
    + + +
    +
    + + {/* ID */} +
    +
    + + !isNew || (!!value && !nodeData[value]) || (!value && isNew), + })} + /> + {errors.id && ( +
    + {t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)} +
    + )} + {isNew && labelFieldId === "id" && similarNodeIds.length > 0 && ( + + )} +
    +
    ), footer: (
    - + )} + {/* Same close request as the modal's own cross, so both get the unsaved-input + confirmation; falls back to onCancel when this form is rendered in a side panel. */} + - - - + + {!submitFirst && ( + + )}
    ), }; @@ -251,14 +375,23 @@ const useEditNodeForm = ({ export const EditNodeModal: FC> = ({ cancel, submit, arguments: { nodeId } }) => { const { t } = useTranslation(); const isNew = typeof nodeId === "undefined"; + + // Probe the remote GitHub version as soon as the popup opens, so the user is warned before + // filling it in (rather than only when validating), avoiding losing their input on a reload. + const { check: checkRemoteFreshness } = useRemoteFileFreshnessCheck(); + useEffect(() => checkRemoteFreshness(), [checkRemoteFreshness]); + const { main, footer, submit: submitForm, + hasUserInput, } = useEditNodeForm({ nodeId, onSubmitted: () => submit({}), onCancel: () => cancel(), + submitLabel: t("common.ok"), + submitFirst: true, }); return ( @@ -267,6 +400,9 @@ export const EditNodeModal: FC> = ({ cancel, sub onClose={() => cancel()} className="modal-lg edit-node" onSubmit={submitForm} + submitLabel={t("common.ok")} + doNotPreserveData + hasUnsavedInput={hasUserInput} >
    {main}
    diff --git a/packages/gephi-lite/src/components/data/Node.tsx b/packages/gephi-lite/src/components/data/Node.tsx index 150e9851..cc21f339 100644 --- a/packages/gephi-lite/src/components/data/Node.tsx +++ b/packages/gephi-lite/src/components/data/Node.tsx @@ -10,46 +10,78 @@ import { useVisualGetters, } from "../../core/context/dataContexts"; import { mergeStaticDynamicData } from "../../core/graph/dynamicAttributes"; +import { useLocateInGraph } from "../../hooks/useLocateInGraph"; -export const NodeComponent: FC<{ label: ReactNode; color: string; hidden?: boolean }> = ({ label, color, hidden }) => { +/** + * Grey used to render an item the current filters exclude, wherever it still has to be shown + * (selection panel, graph summary, most recently updated item...). A filtered item keeps its real + * label - hiding it behind a placeholder would make it unidentifiable - but is always drawn hollow + * (nodes) or dotted (edges) in this grey, with a muted italic label, so "this is filtered out" + * reads identically everywhere. + */ +export const FILTERED_ITEM_COLOR = "#adb5bd"; + +export const NodeComponent: FC<{ + label: ReactNode; + color: string; + hidden?: boolean; + /** + * When provided, the node is rendered as a button that locates it (see useLocateInGraph): + * shared by every place a node is displayed (data table, edge source/target, selection panel), + * so "clicking a node locates it" always behaves identically wherever it's used. + */ + onClick?: () => void; + buttonTitle?: string; +}> = ({ label, color, hidden, onClick, buttonTitle }) => { const { t } = useTranslation(); - return ( + const content = (
    ); + + return onClick ? ( + + ) : ( + content + ); }; -export const NodeComponentById: FC<{ id: string }> = ({ id }) => { +export const NodeComponentById: FC<{ id: string; locatable?: boolean }> = ({ id, locatable }) => { + const { t } = useTranslation(); const graphDataset = useGraphDataset(); const dynamicItemData = useDynamicItemData(); const visualGetters = useVisualGetters(); const filteredGraph = useFilteredGraph(); + const { locateNode } = useLocateInGraph(); - const data = useMemo( - () => - filteredGraph.hasNode(id) - ? getItemAttributes( - "nodes", - id, - filteredGraph, - mergeStaticDynamicData(graphDataset.nodeData, dynamicItemData.dynamicNodeData)[id], - graphDataset, - visualGetters, - ) - : null, - [id, graphDataset, visualGetters, dynamicItemData, filteredGraph], - ); + // A filtered out node is rendered too (greyed out, see FILTERED_ITEM_COLOR): `getItemAttributes` + // flags it as `hidden`, and its data is read from the dataset, which holds every node whatever + // the filters. Only a node missing from the dataset entirely (just deleted) renders nothing. + const data = useMemo(() => { + const itemData = mergeStaticDynamicData(graphDataset.nodeData, dynamicItemData.dynamicNodeData)[id]; + if (!itemData) return null; + return getItemAttributes("nodes", id, filteredGraph, itemData, graphDataset, visualGetters); + }, [id, graphDataset, visualGetters, dynamicItemData, filteredGraph]); - return data ? ( - - ) : ( - ?} color="lightgrey" /> + if (!data) return null; + + // When locatable, clicking the node locates it: navigate to the graph and center the camera on + // it. A filtered out node is absent from the rendering, so there is nothing to locate. + const isLocatable = locatable && !data.hidden; + return ( + locateNode(id, { navigateToGraph: true }) : undefined} + buttonTitle={isLocatable ? t("selection.locate_on_graph") : undefined} + /> ); }; diff --git a/packages/gephi-lite/src/components/forms/NumberInput.tsx b/packages/gephi-lite/src/components/forms/NumberInput.tsx new file mode 100644 index 00000000..6c3a3b6b --- /dev/null +++ b/packages/gephi-lite/src/components/forms/NumberInput.tsx @@ -0,0 +1,60 @@ +import { FC, useEffect, useRef } from "react"; +import { PiMinus, PiPlus } from "react-icons/pi"; + +/** + * A number input with explicit −/+ stepper buttons. + * + * We can't rely on the browser's native spin buttons: mobile browsers (Android, iOS) simply don't + * render them, so a `type="number"` field has no visible way to increment/decrement there. The + * native buttons are hidden via CSS (see `.gl-number-input` in `_forms.scss`) and replaced by these + * buttons, so the stepper looks and behaves the same on every platform. + */ +export const NumberInput: FC<{ + value?: number; + onChange: (value?: number) => void; + id?: string; + autoFocus?: boolean; + placeholder?: string; + step?: number; +}> = ({ value, onChange, id, autoFocus, placeholder, step = 1 }) => { + const ref = useRef(null); + useEffect(() => { + if (ref.current && autoFocus) ref.current.focus(); + }, [autoFocus]); + + const bump = (delta: number) => onChange((typeof value === "number" ? value : 0) + delta); + + return ( +
    + + onChange(e.target.value ? +e.target.value : undefined)} + /> + +
    + ); +}; diff --git a/packages/gephi-lite/src/components/forms/Select.tsx b/packages/gephi-lite/src/components/forms/Select.tsx index 60e188bc..d7e70709 100644 --- a/packages/gephi-lite/src/components/forms/Select.tsx +++ b/packages/gephi-lite/src/components/forms/Select.tsx @@ -1,5 +1,5 @@ import { isNil } from "lodash"; -import { KeyboardEvent, LegacyRef, ReactNode, useContext, useState } from "react"; +import { KeyboardEvent, ReactNode, Ref, forwardRef, useContext, useState } from "react"; import ReactSelect, { Props, type SelectInstance } from "react-select"; import AsyncReactSelect, { AsyncProps } from "react-select/async"; import AsyncCreatableReactSelect, { AsyncCreatableProps } from "react-select/async-creatable"; @@ -9,8 +9,24 @@ import { GroupBase } from "react-select/dist/declarations/src/types"; import { UIContext } from "../../core/context/uiContext"; import { CaretDownIcon } from "../common-icons"; -const useDefaultSelectProps = () => { - const [isMenuOpen, setIsMenuOpen] = useState(false); +// `menuIsOpen` is kept controlled here (so Escape can be swallowed while the menu is open, see +// onKeyDown below), which means react-select's own `defaultMenuIsOpen` would be ignored. Seed the +// initial state from it instead, so that prop keeps its standard meaning for our callers. +// +// `forceMenuOpen`, when true, keeps the menu open regardless of react-select's own open/close +// triggers (blur, Escape, selecting an option...) - the normal open/close bookkeeping keeps running +// underneath, so it takes back over the moment the caller stops forcing. Used by the main fuzzy +// search box (see GraphSearch) to keep its results visible even through an involuntary blur: hiding +// a focused element via CSS (eg. collapsing a mobile panel) makes the browser blur it, which would +// otherwise close the menu until the field is clicked again. +// +// `forceMenuClosed`, when true, overrides both `forceMenuOpen` and the internal open state: the menu +// is portaled (see `menuPortalTarget` below), so it keeps floating on top of everything even while +// its field is hidden via CSS - this is what lets the caller explicitly hide it in that case, as +// opposed to the involuntary blur above which must NOT close it. +const useDefaultSelectProps = (defaultMenuIsOpen?: boolean, forceMenuOpen?: boolean, forceMenuClosed?: boolean) => { + const [isMenuOpen, setIsMenuOpen] = useState(!!defaultMenuIsOpen); + const menuIsOpen = !forceMenuClosed && (!!forceMenuOpen || isMenuOpen); return { classNamePrefix: "react-select", @@ -21,9 +37,9 @@ const useDefaultSelectProps = () => { }, onMenuOpen: () => setIsMenuOpen(true), onMenuClose: () => setIsMenuOpen(false), - menuIsOpen: isMenuOpen, + menuIsOpen, onKeyDown: (event: KeyboardEvent) => { - if (event.key === "Escape" && isMenuOpen) event.stopPropagation(); + if (event.key === "Escape" && menuIsOpen) event.stopPropagation(); }, }; }; @@ -41,12 +57,22 @@ export function optionize(value?: V): BaseOption | undefined { return !isNil(value) ? { value, label: value + "" } : undefined; } -export function Select({ - ref, - ...props -}: Props & { ref?: LegacyRef> }) { +// React 18 (unlike 19) never forwards a `ref` prop to a plain function component: without +// `forwardRef`, passing `ref` here would silently stay `null` forever (with a dev warning), as if +// every caller who needed the underlying select instance - eg. to blur/focus it - had no ref at +// all. `forwardRef` itself only produces a non-generic component type, so each wrapper is cast back +// to a generic function signature to keep `` inference working at call sites. + +export const Select = forwardRef(function Select( + { + forceMenuOpen, + forceMenuClosed, + ...props + }: Props & { forceMenuOpen?: boolean; forceMenuClosed?: boolean }, + ref: Ref>, +) { const { portalTarget } = useContext(UIContext); - const defaultProps = useDefaultSelectProps(); + const defaultProps = useDefaultSelectProps(props.defaultMenuIsOpen, forceMenuOpen, forceMenuClosed); return ( menuPortalTarget={portalTarget} @@ -59,14 +85,24 @@ export function Select({ ref={ref} /> ); -} +}) as ( + props: Props & { + forceMenuOpen?: boolean; + forceMenuClosed?: boolean; + ref?: Ref>; + }, +) => ReturnType; -export function AsyncSelect({ - ref, - ...props -}: AsyncProps> & { ref?: LegacyRef> }) { +export const AsyncSelect = forwardRef(function AsyncSelect( + { + forceMenuOpen, + forceMenuClosed, + ...props + }: AsyncProps> & { forceMenuOpen?: boolean; forceMenuClosed?: boolean }, + ref: Ref>, +) { const { portalTarget } = useContext(UIContext); - const defaultProps = useDefaultSelectProps(); + const defaultProps = useDefaultSelectProps(props.defaultMenuIsOpen, forceMenuOpen, forceMenuClosed); return ( menuPortalTarget={portalTarget} @@ -79,14 +115,20 @@ export function AsyncSelect({ ref={ref} /> ); -} +}) as ( + props: AsyncProps> & { + forceMenuOpen?: boolean; + forceMenuClosed?: boolean; + ref?: Ref>; + }, +) => ReturnType; -export function CreatableSelect({ - ref, - ...props -}: CreatableProps> & { ref?: LegacyRef> }) { +export const CreatableSelect = forwardRef(function CreatableSelect( + { forceMenuOpen, ...props }: CreatableProps> & { forceMenuOpen?: boolean }, + ref: Ref>, +) { const { portalTarget } = useContext(UIContext); - const defaultProps = useDefaultSelectProps(); + const defaultProps = useDefaultSelectProps(props.defaultMenuIsOpen, forceMenuOpen); return ( menuPortalTarget={portalTarget} @@ -99,14 +141,19 @@ export function CreatableSelect({ ref={ref} /> ); -} +}) as ( + props: CreatableProps> & { + forceMenuOpen?: boolean; + ref?: Ref>; + }, +) => ReturnType; -export function AsyncCreatableSelect({ - ref, - ...props -}: AsyncCreatableProps> & { ref?: LegacyRef> }) { +export const AsyncCreatableSelect = forwardRef(function AsyncCreatableSelect( + { forceMenuOpen, ...props }: AsyncCreatableProps> & { forceMenuOpen?: boolean }, + ref: Ref>, +) { const { portalTarget } = useContext(UIContext); - const defaultProps = useDefaultSelectProps(); + const defaultProps = useDefaultSelectProps(props.defaultMenuIsOpen, forceMenuOpen); return ( menuPortalTarget={portalTarget} @@ -119,4 +166,9 @@ export function AsyncCreatableSelect({ ref={ref} /> ); -} +}) as ( + props: AsyncCreatableProps> & { + forceMenuOpen?: boolean; + ref?: Ref>; + }, +) => ReturnType; diff --git a/packages/gephi-lite/src/components/modals.tsx b/packages/gephi-lite/src/components/modals.tsx index fcc2af45..fb89f939 100644 --- a/packages/gephi-lite/src/components/modals.tsx +++ b/packages/gephi-lite/src/components/modals.tsx @@ -1,11 +1,48 @@ import cx from "classnames"; -import React, { FC, PropsWithChildren, ReactNode } from "react"; +import React, { + FC, + PropsWithChildren, + ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; import { useTranslation } from "react-i18next"; import { useModal } from "../core/modals"; import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts"; import { CloseIcon } from "./common-icons"; +/** + * The open modal's "please close" entry point, so that every way of closing it - the header cross, + * the form footer's cancel button, Escape, a click outside, the Android back button - goes through + * the very same code, and in particular through the unsaved-input confirmation below. Null outside + * of a modal: the node/edge forms are also rendered in a side panel, where there is nothing to + * close. + */ +const ModalCloseContext = createContext<(() => void) | null>(null); + +/** + * Button closing the modal it is rendered in, falling back to `onCancel` when there is no modal + * around (see ModalCloseContext). + */ +export const CloseModalButton: FC void; className?: string; title?: string }>> = ({ + onCancel, + className, + title, + children, +}) => { + const requestClose = useContext(ModalCloseContext); + return ( + + ); +}; + interface Props { title?: ReactNode; onClose?: () => void; @@ -13,6 +50,15 @@ interface Props { doNotPreserveData?: boolean; // if set, even if a onSubmit is set it's possible to close the modal by escape/click outside to cancel showHeader?: boolean; footerAlignLeft?: boolean; + // When set (together with onSubmit), a submit button with this label is shown in the header, + // left of the close button: on mobile, the on-screen keyboard can cover the footer's own submit + // button while a field is focused, so this copy always stays reachable. + submitLabel?: ReactNode; + // Set while the modal holds user input that closing would discard. Every close that is not an + // explicit click on the close/cancel buttons - the Android back button, Escape, a click outside - + // then goes through a confirmation whose default answer is to keep editing, so a stray press + // cannot wipe a form that was being filled in. + hasUnsavedInput?: boolean; className?: string; bodyClassName?: string; contentClassName?: string; @@ -27,39 +73,78 @@ export const Modal: FC> = ({ children, showHeader = true, footerAlignLeft = false, + submitLabel, + hasUnsavedInput, className, bodyClassName, contentClassName, }) => { const { t } = useTranslation(); + const { closeRequestId, setUnsavedInput } = useModal(); const childrenArray = Array.isArray(children) ? children : [children]; const body = childrenArray[0]; const footer = childrenArray[1]; + + // Let a close request coming from outside (the Android back button, see Initialize) know that + // this modal has something to lose. Cleared on unmount, so the next modal starts fresh. + useEffect(() => { + setUnsavedInput(!!hasUnsavedInput); + }, [hasUnsavedInput, setUnsavedInput]); + useEffect(() => () => setUnsavedInput(false), [setUnsavedInput]); + + const [confirmingClose, setConfirmingClose] = useState(false); + const requestClose = useCallback(() => { + if (!onClose) return; + if (hasUnsavedInput) setConfirmingClose(true); + else onClose(); + }, [onClose, hasUnsavedInput]); + + // The back button asks through the modal state rather than closing directly, so the confirmation + // above can happen. The first render is not a request, hence the remembered token. + const lastCloseRequestId = useRef(closeRequestId); + useEffect(() => { + if (closeRequestId === lastCloseRequestId.current) return; + lastCloseRequestId.current = closeRequestId; + requestClose(); + }, [closeRequestId, requestClose]); + useKeyboardShortcuts([ { code: "Escape", handler: () => { + // While confirming, Escape means "no, keep editing": + if (confirmingClose) { + setConfirmingClose(false); + return; + } // don't close the modal on click outside if there is a form in it to avoid data loss - if (onClose && (doNotPreserveData || !onSubmit)) onClose(); + if (onClose && (doNotPreserveData || !onSubmit)) requestClose(); }, }, ]); const content = ( - <> + {showHeader && (
    {title &&
    {title}
    } - +
    + {submitLabel && onSubmit && ( + + )} + +
    )} {body && ( @@ -71,7 +156,7 @@ export const Modal: FC> = ({ title={t("common.close").toString()} className="gl-btn gl-btn-icon d-inline-flex" aria-label="Close" - onClick={() => onClose && onClose()} + onClick={requestClose} disabled={!onClose} > @@ -91,7 +176,7 @@ export const Modal: FC> = ({ {footer} )} - +
    ); return ( @@ -103,7 +188,7 @@ export const Modal: FC> = ({ onClick={(e) => { // don't close the modal on click outside if there is a form in it to avoid data loss // we could do better bu tracking changes but there are already a cancel AND a x icon to close the modal - if (onClose && (doNotPreserveData || !onSubmit) && e.target === e.currentTarget) onClose(); + if (onClose && (doNotPreserveData || !onSubmit) && e.target === e.currentTarget) requestClose(); }} >
    > = ({
    + + {confirmingClose && ( + <> +
    +
    +
    +
    +
    {t("common.discard_input.title")}
    +
    +
    {t("common.discard_input.message")}
    +
    +
    + {/* Keeping the input is the default: first, filled, and focused. */} + + +
    +
    +
    +
    +
    +
    + + )} ); }; diff --git a/packages/gephi-lite/src/components/modals/ConfirmModal.tsx b/packages/gephi-lite/src/components/modals/ConfirmModal.tsx index db640b6b..8c88bb69 100644 --- a/packages/gephi-lite/src/components/modals/ConfirmModal.tsx +++ b/packages/gephi-lite/src/components/modals/ConfirmModal.tsx @@ -24,7 +24,9 @@ const ConfirmModal: FC< doNotPreserveData onSubmit={() => { submit({}); - notify({ message: successMsg, type: "success" }); + // Some callers (e.g. the remote-change guard) handle their own post-submit feedback and + // pass no successMsg: don't emit an empty success toast in that case. + if (successMsg) notify({ message: successMsg, type: "success" }); }} > <>{message} diff --git a/packages/gephi-lite/src/components/modals/RemoteSaveConflictModal.tsx b/packages/gephi-lite/src/components/modals/RemoteSaveConflictModal.tsx new file mode 100644 index 00000000..d3d570ef --- /dev/null +++ b/packages/gephi-lite/src/components/modals/RemoteSaveConflictModal.tsx @@ -0,0 +1,44 @@ +import { FC } from "react"; +import { useTranslation } from "react-i18next"; + +import { ModalProps } from "../../core/modals/types"; +import { Modal } from "../modals"; + +export type RemoteSaveConflictAction = "reload" | "overwrite"; + +/** + * Shown when the user asks to save a GitHub file whose remote version has been updated (by another + * user or session) since it was opened here. Unlike the passive edit-time warning (reload / keep), + * this one also offers to overwrite, so the user is never stuck unable to save: keeping cancels the + * save, reloading discards local changes for the remote version, overwriting saves anyway. + */ +const RemoteSaveConflictModal: FC> = ({ + cancel, + submit, + arguments: { filename }, +}) => { + const { t } = useTranslation(); + + return ( + cancel()} doNotPreserveData> + <>{t("graph.remote_changed.save_conflict_message", { filename })} + {/* Full width + flex-wrap so all three buttons stay visible (and wrap) on narrow/mobile + screens. "Keep my changes" is the default (highlighted and focused): it is the safest + option, the two others (reload discards local changes, overwrite discards the remote) are + de-emphasized. */} +
    + + + +
    +
    + ); +}; + +export default RemoteSaveConflictModal; diff --git a/packages/gephi-lite/src/components/modals/WelcomeModal.tsx b/packages/gephi-lite/src/components/modals/WelcomeModal.tsx index a93cc616..ea6a6a31 100644 --- a/packages/gephi-lite/src/components/modals/WelcomeModal.tsx +++ b/packages/gephi-lite/src/components/modals/WelcomeModal.tsx @@ -4,11 +4,13 @@ import { useTranslation } from "react-i18next"; import { version } from "../../../package.json"; import GephiLiteReversedLogo from "../../assets/gephi-lite-logo-reversed.svg?react"; import GephiLiteLogo from "../../assets/gephi-lite-logo.svg?react"; -import { useFile, useFileActions, useGraphDatasetActions, usePreferences } from "../../core/context/dataContexts"; +import { config } from "../../config"; +import { resetStates, useFile, useFileActions, usePreferences } from "../../core/context/dataContexts"; import { useModal } from "../../core/modals"; import { ModalProps } from "../../core/modals/types"; import { useNotifications } from "../../core/notifications"; import { getAppliedTheme } from "../../core/preferences/utils"; +import { displayDateTime } from "../../utils/date"; import { Loader } from "../Loader"; import { GitHubIcon } from "../common-icons"; import { Modal } from "../modals"; @@ -21,7 +23,6 @@ export const WelcomeModal: FC> = ({ cancel, submit }) => { const { openModal } = useModal(); const { notify } = useNotifications(); const { theme } = usePreferences(); - const { resetGraph } = useGraphDatasetActions(); const { status: { type: fileStateType }, } = useFile(); @@ -53,6 +54,13 @@ export const WelcomeModal: FC> = ({ cancel, submit }) => { changelog + {/* Discreet on purpose (see text-muted): only useful to confirm which build is actually + running, not something that should draw the eye like the version number above. */} + {config.gitCommitHash && config.buildDate && ( +
    + {config.gitCommitHash} - {displayDateTime(config.buildDate)} +
    + )}
    {t("welcome.website")} @@ -100,7 +108,9 @@ export const WelcomeModal: FC> = ({ cancel, submit }) => { className="gl-btn text-start" title={t(`graph.open.github.title`)} onClick={() => { - resetGraph(); + // Blank the whole workspace, current file pointer included (see resetStates), + // so a later save does not overwrite the previously opened file. + resetStates(false); submit({}); }} > diff --git a/packages/gephi-lite/src/components/modals/open/CloudFileModal.tsx b/packages/gephi-lite/src/components/modals/open/CloudFileModal.tsx index e243737c..8a245159 100644 --- a/packages/gephi-lite/src/components/modals/open/CloudFileModal.tsx +++ b/packages/gephi-lite/src/components/modals/open/CloudFileModal.tsx @@ -1,10 +1,11 @@ import byteSize from "byte-size"; import cx from "classnames"; -import { FC, useCallback, useEffect, useState } from "react"; +import { FC, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { CloudFile } from "../../../core/cloud/types"; import { useCloudProvider } from "../../../core/cloud/useCloudProvider"; +import { useFile } from "../../../core/context/dataContexts"; import { errorToString } from "../../../core/errors"; import { useNotifications } from "../../../core/notifications"; import { useConnectedUser } from "../../../core/user"; @@ -27,6 +28,7 @@ export const OpenCloudFileForm: FC = ({ id, onStatusChan const { t } = useTranslation(); const { notify } = useNotifications(); const { loading, getFiles, openFile } = useCloudProvider(); + const { current } = useFile(); // list files retrived from the cloud const [files, setFiles] = useState>>([]); // the selected file by the user @@ -48,6 +50,20 @@ export const OpenCloudFileForm: FC = ({ id, onStatusChan } }, [getFiles, page, user]); + // Preselect the file currently open in the workspace, so it's immediately possible to hit "Open" + // again without having to click it in the list first - it looked selected already (it's the file + // you're working on), it should actually be. Only attempted once, right after the list first + // loads: this must never override a choice the user makes while browsing afterwards. + const triedPreselect = useRef(false); + useEffect(() => { + if (triedPreselect.current || files.length === 0) return; + triedPreselect.current = true; + if (current?.type === "cloud") { + const currentInList = files.find((file) => file.id === current.id); + if (currentInList) setSelected(currentInList); + } + }, [files, current]); + const onSubmit = useCallback( async (selected: Omit | null) => { if (selected) { diff --git a/packages/gephi-lite/src/config.ts b/packages/gephi-lite/src/config.ts index 1e6c36c3..60e2e2be 100644 --- a/packages/gephi-lite/src/config.ts +++ b/packages/gephi-lite/src/config.ts @@ -2,8 +2,20 @@ import { parse } from "semver"; import { version } from "../package.json"; +// __GIT_COMMIT_HASH__/__BUILD_DATE__ are replaced at build time (see vite.config.mts) wherever +// Vite actually transforms this module for the browser bundle. Some non-browser transform paths +// (namely vitest running tests in Node rather than in a real browser) skip that substitution, in +// which case the bare identifier would throw a ReferenceError as soon as this module is evaluated; +// `typeof` guards against that without needing "unused expression" workarounds. +const gitCommitHash = typeof __GIT_COMMIT_HASH__ !== "undefined" ? __GIT_COMMIT_HASH__ : null; +const buildDate = typeof __BUILD_DATE__ !== "undefined" ? __BUILD_DATE__ : null; + export const config = { version: parse(version)!, + // null both in an environment with no git checkout to read from (a source tarball, some Docker + // builds...) and wherever the build-time substitution above did not happen. + gitCommitHash, + buildDate, website_url: "https://github.com/gephi/gephi-lite#readme", notificationTimeoutMs: 3000, github_proxy: import.meta.env.VITE_GITHUB_PROXY || "/_github", diff --git a/packages/gephi-lite/src/core/Initialize.tsx b/packages/gephi-lite/src/core/Initialize.tsx index d6e16b56..c5c5d341 100644 --- a/packages/gephi-lite/src/core/Initialize.tsx +++ b/packages/gephi-lite/src/core/Initialize.tsx @@ -1,5 +1,5 @@ import { parseAppearanceState } from "@gephi/gephi-lite-sdk"; -import { FC, PropsWithChildren, useCallback, useEffect, useState } from "react"; +import { FC, PropsWithChildren, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import useKonami from "react-use-konami"; @@ -9,10 +9,12 @@ import { sessionStorage } from "../utils/storage"; import { extractFilename } from "../utils/url"; import { appearanceAtom } from "./appearance"; import { useBroadcast } from "./broadcast/useBroadcast"; -import { useFileActions, useGraphDataset, useGraphDatasetActions } from "./context/dataContexts"; +import { useRemoteFileGuard } from "./cloud/useRemoteFileGuard"; +import { resetStates, useFile, useFileActions, useGraphDataset } from "./context/dataContexts"; import { filtersAtom } from "./filters"; import { parseFiltersState } from "./filters/utils"; import { graphDatasetAtom } from "./graph"; +import { ensureSystemDatesInDataset } from "./graph/dates"; import { parseDataset } from "./graph/utils"; import { useModal } from "./modals"; import { useNotifications } from "./notifications"; @@ -31,13 +33,80 @@ let isInitialized = false; export const Initialize: FC> = ({ children }) => { const { t } = useTranslation(); const { notify } = useNotifications(); - const { openModal } = useModal(); - const { open } = useFileActions(); + const { modal, openModal, requestCloseModal } = useModal(); + const { open, clearDirty } = useFileActions(); const { metadata } = useGraphDataset(); - const { resetGraph } = useGraphDatasetActions(); + const { isDirty } = useFile(); const [broadcastID, setBroadcastID] = useState(null); useBroadcast(broadcastID); + // Warn when starting to edit a GitHub graph whose remote version has changed since it was opened: + useRemoteFileGuard(); + + // The back-button guard below is set up once on mount; it reads the always-current modal / + // dirty / t through refs instead of re-subscribing on every change. + const modalRef = useRef(modal); + modalRef.current = modal; + const isDirtyRef = useRef(isDirty); + isDirtyRef.current = isDirty; + const requestCloseModalRef = useRef(requestCloseModal); + requestCloseModalRef.current = requestCloseModal; + const tRef = useRef(t); + tRef.current = t; + + /** + * Keep the browser/Android back button from leaving the app (and losing unsaved work): + * - A "guard" history entry is kept on top of the stack, so a back press lands on a popstate we + * control instead of navigating away or stepping through the router's Graph/Data history. + * - When a modal is open, back closes it (and we keep guarding) - unless it holds unsaved input, + * in which case it raises its own confirmation instead (see `requestCloseModal`). + * - Otherwise, back only leaves the app after a confirmation when there are unsaved changes; + * with nothing to save it leaves normally. + * A beforeunload handler additionally covers reload / tab close (where mobile browsers, e.g. + * Firefox Android, do not fire the back-button popstate at all). + */ + useEffect(() => { + const pushGuard = () => window.history.pushState({ gephiLiteBackGuard: true }, ""); + pushGuard(); + let leaving = false; + + const handlePopState = () => { + // A back navigation just consumed our guard entry. + if (modalRef.current) { + // Priority: close an open modal, and keep guarding. + requestCloseModalRef.current(); + pushGuard(); + return; + } + if (isDirtyRef.current && !window.confirm(tRef.current("workspace.confirm_leave_unsaved"))) { + // Unsaved changes and the user chose to stay: keep guarding. + pushGuard(); + return; + } + // Let the app be left for real (nothing unsaved, or the user confirmed): stop guarding and + // replay the back so the browser actually leaves. + leaving = true; + window.removeEventListener("popstate", handlePopState); + window.history.back(); + }; + + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + // Skipped when we are intentionally leaving (the popstate handler already confirmed): + if (leaving || !isDirtyRef.current) return; + e.preventDefault(); + // Legacy browsers require returnValue to be set for the prompt to show: + e.returnValue = ""; + }; + + window.addEventListener("popstate", handlePopState); + window.addEventListener("beforeunload", handleBeforeUnload); + return () => { + window.removeEventListener("popstate", handlePopState); + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + // Set up once; current values are read through refs. + }, []); + useKonami( () => { notify({ @@ -93,10 +162,14 @@ export const Initialize: FC> = ({ children }) => { // If query params has new // => empty graph & open welcome modal if (url.searchParams.has("new") || broadcastID) { - resetGraph(); + // Full workspace reset (file pointer included), so a fresh/broadcast tab never inherits and + // overwrites a file left over from a previous session. + resetStates(false); graphFound = true; url.searchParams.delete("new"); - window.history.pushState({}, "", url); + // replaceState (not pushState): just clean the URL, without adding a back-navigable entry + // that would also bury the back-button guard entry (see the guard effect above). + window.history.replaceState({}, "", url); showWelcomeModal = false; } @@ -116,9 +189,9 @@ export const Initialize: FC> = ({ children }) => { }); graphFound = true; showWelcomeModal = false; - // remove param in url + // remove param in url (replaceState, not pushState: see the "new" branch above) url.searchParams.delete("file"); - window.history.pushState({}, "", url); + window.history.replaceState({}, "", url); } catch (e) { console.error(e); notify({ @@ -142,10 +215,14 @@ export const Initialize: FC> = ({ children }) => { const appearance = rawAppearance ? parseAppearanceState(rawAppearance) : null; const filters = rawFilters ? parseFiltersState(rawFilters) : null; - graphDatasetAtom.set(dataset); + graphDatasetAtom.set(ensureSystemDatesInDataset(dataset)); filtersAtom.set((prev) => filters || prev); appearanceAtom.set((prev) => appearance || prev); resetCamera({ forceRefresh: true }); + // Restoring the previous session's state (e.g. after a page reload) is not a user edit: + // the atom updates above just flipped isDirty back to true through the markDirty + // bindings (new object references), so it must be cleared again here. + clearDirty(); if (dataset.fullGraph.order > 0) showWelcomeModal = false; } diff --git a/packages/gephi-lite/src/core/Root.tsx b/packages/gephi-lite/src/core/Root.tsx index b56ecd06..4b806041 100755 --- a/packages/gephi-lite/src/core/Root.tsx +++ b/packages/gephi-lite/src/core/Root.tsx @@ -4,6 +4,7 @@ import { HashRouter, Route, Routes } from "react-router-dom"; import { ErrorComponent } from "../components/Error"; import { MatomoProvider } from "../components/Matomo"; +import { PreproductionBanner } from "../components/PreproductionBanner"; import DesignSystemPage from "../designSystem"; import { NotFoundPage } from "../views/NotFoundPage"; import { DataPage } from "../views/dataPage"; @@ -23,6 +24,7 @@ export const Root: FC = () => { console.debug(details); }} > + dataset.fullGraph.addNode(n)); + dataset.fullGraph.addEdge("n0", "n1"); + dataset.fullGraph.addEdge("n0", "n2"); + dataset.fullGraph.addEdge("n0", "n3"); + dataset.fullGraph.addEdge("n0", "n4"); + // A plain static attribute, present on every node regardless of any filter: + ["n0", "n1", "n2", "n3", "n4"].forEach((n, i) => { + dataset.nodeData[n] = { weight: (i + 1) * 10 }; + }); + dataset.nodeFields = [{ id: "weight", itemType: "nodes", type: "number" }]; + return dataset; +} + +const degreeField: FieldModel<"nodes", true> = { id: "degree", itemType: "nodes", type: "number", dynamic: true }; +const weightField: FieldModel<"nodes", false> = { id: "weight", itemType: "nodes", type: "number" }; + +describe("appearance utilities", () => { + describe("#makeGetNumberAttr ranking size, filterAware toggle", () => { + const dataset = buildDataset(); + // Simulates a Filters panel keeping only the two leaves n1 and n2: they're only connected to + // each other through the hub n0, which the filter excludes, so the filtered induced subgraph + // has no edge between them - both read as degree 0 there. The dynamic data channel is scoped to + // just those two, exactly like the real `dynamicItemDataAtom`. + const filteredDynamicNodeData: DynamicItemData["dynamicNodeData"] = { + n1: { degree: 0 }, + n2: { degree: 0 }, + }; + const dynamicItemData: DynamicItemData = { + dynamicNodeData: filteredDynamicNodeData, + dynamicNodeFields: [degreeField], + dynamicEdgeData: {}, + dynamicEdgeFields: [], + }; + + it("default (unchecked): dynamic field scale spans the whole graph, not just the filtered items", () => { + const appearance = { + ...getEmptyAppearanceState(), + nodesSize: { type: "ranking" as const, field: degreeField, missingSize: 1, minSize: 10, maxSize: 100 }, + }; + const getSize = makeGetNumberAttr("nodes", "size", dataset, dynamicItemData, appearance); + expect(getSize).not.toBeNull(); + // The scale must span the real, whole-graph degree range (1 to 4: n1-n4 are leaves, n0 is the + // hub), not the filtered pair's own values (which are both 0 - that would collapse the scale + // to a single degenerate point instead of a 1-to-4 range). + expect(getSize!({ static: {}, dynamic: { degree: 1 } })).toBeCloseTo(10); + expect(getSize!({ static: {}, dynamic: { degree: 4 } })).toBeCloseTo(100); + expect(getSize!({ static: {}, dynamic: { degree: 2.5 } })).toBeCloseTo(55); + }); + + it("checked: dynamic field scale is restricted to the currently filtered items", () => { + const appearance = { + ...getEmptyAppearanceState(), + nodesSize: { + type: "ranking" as const, + field: degreeField, + missingSize: 1, + minSize: 10, + maxSize: 100, + filterAware: true, + }, + }; + const getSize = makeGetNumberAttr("nodes", "size", dataset, dynamicItemData, appearance); + // Both filtered items have degree 0 -> delta is 0 -> falls back to the historical `|| 1` guard, + // so a degree-0 item lands exactly on minSize, unlike the whole-graph scale above where it + // would map to something below minSize (0 sits under the real range's floor of 1). + expect(getSize!({ static: {}, dynamic: { degree: 0 } })).toBeCloseTo(10); + }); + + it("default (unchecked): a plain static field is unaffected either way (already whole-dataset)", () => { + const appearance = { + ...getEmptyAppearanceState(), + nodesSize: { type: "ranking" as const, field: weightField, missingSize: 1, minSize: 10, maxSize: 100 }, + }; + const getSize = makeGetNumberAttr("nodes", "size", dataset, dynamicItemData, appearance); + // weight spans 10 (n0) to 50 (n4) across the whole dataset. + expect(getSize!({ static: { weight: 10 }, dynamic: {} })).toBeCloseTo(10); + expect(getSize!({ static: { weight: 50 }, dynamic: {} })).toBeCloseTo(100); + }); + + it("checked: a static field's scale becomes restricted to the currently filtered items too", () => { + const appearance = { + ...getEmptyAppearanceState(), + nodesSize: { + type: "ranking" as const, + field: weightField, + missingSize: 1, + minSize: 10, + maxSize: 100, + filterAware: true, + }, + }; + const getSize = makeGetNumberAttr("nodes", "size", dataset, dynamicItemData, appearance); + // Filtered subset is n1 (weight 20) and n2 (weight 30): the scale must span only [20, 30], so + // n2's weight (30) now maps to maxSize, unlike the whole-dataset case above where 30 would sit + // well below the top (50). + expect(getSize!({ static: { weight: 20 }, dynamic: {} })).toBeCloseTo(10); + expect(getSize!({ static: { weight: 30 }, dynamic: {} })).toBeCloseTo(100); + }); + }); +}); diff --git a/packages/gephi-lite/src/core/appearance/utils.ts b/packages/gephi-lite/src/core/appearance/utils.ts index 1ff43714..ea818ef1 100644 --- a/packages/gephi-lite/src/core/appearance/utils.ts +++ b/packages/gephi-lite/src/core/appearance/utils.ts @@ -9,11 +9,11 @@ import { } from "@gephi/gephi-lite-sdk"; import chroma from "chroma-js"; import { Attributes } from "graphology-types"; -import { forEach, identity, isNil, keyBy } from "lodash"; +import { forEach, identity, isNil, keyBy, pick } from "lodash"; import { EdgeLabelDrawingFunction, NodeLabelDrawingFunction } from "sigma/rendering"; import { EdgeDisplayData, NodeDisplayData } from "sigma/types"; -import { mergeStaticDynamicData } from "../graph/dynamicAttributes"; +import { isComputedField, mergeStaticDynamicData } from "../graph/dynamicAttributes"; import { getFieldValue, getFieldValueForQuantification } from "../graph/fieldModel"; import { DatalessGraph, @@ -23,6 +23,7 @@ import { NodeRenderingData, SigmaGraph, } from "../graph/types"; +import { computeAllComputedAttributes } from "../graph/utils"; import { ItemType } from "../types"; import { AppearanceState, @@ -72,10 +73,11 @@ export function makeGetNumberAttr< >( itemType: T["itemType"], itemKey: "size" | "zIndex", - { nodeData, edgeData }: GraphDataset, + dataset: GraphDataset, { dynamicNodeData, dynamicEdgeData }: DynamicItemData, { nodesSize, edgesSize, edgesZIndex }: AppearanceState, ): null | NumberGetter { + const { nodeData, edgeData } = dataset; const numberAttrDef = itemKey === "zIndex" ? edgesZIndex : itemType === "nodes" ? nodesSize : edgesSize; const itemsValues = itemType === "nodes" @@ -94,10 +96,27 @@ export function makeGetNumberAttr< const minSize = numberAttrDef.minSize as number; const maxSize = numberAttrDef.maxSize as number; + // The min/max SCALE used to interpolate sizes is scanned from a different item set than + // `itemsValues` above (which always reflects each item's own current value, used by the + // getter below): filtered-out items must not silently become the extremum of the scale. + // - filterAware (checked): restricted to the items currently kept by the filters - the + // dynamic data channel is itself already scoped to those, so its keys give that set. + // - default (unchecked): the whole dataset, recomputing computed fields (dynamic + // attributes, formula columns) over the full graph, since the passed-in dynamic data is + // itself filtered-scoped and would otherwise silently apply the same restriction. + const scaleItemsValues = numberAttrDef.filterAware + ? pick(itemsValues, itemType === "nodes" ? Object.keys(dynamicNodeData) : Object.keys(dynamicEdgeData)) + : isComputedField(numberAttrDef.field) + ? mergeStaticDynamicData( + itemType === "nodes" ? nodeData : edgeData, + computeAllComputedAttributes(itemType, dataset), + ) + : itemsValues; + const transformValue = makeTransformValue(numberAttrDef.transformationMethod); let min = Infinity, max = -Infinity; - forEach(itemsValues, (data) => { + forEach(scaleItemsValues, (data) => { const valueAsNumber = getFieldValueForQuantification(data, numberAttrDef.field); const transformedValue = transformValue(valueAsNumber); if (typeof transformedValue === "number") { @@ -281,7 +300,10 @@ export function makeGetStringAttr< getLabel = () => stringAttrDef.value; break; case "field": - getLabel = (data) => { + getLabel = (data, id) => { + // "id" is a reserved virtual field (see StringAttrItem): it is not stored in the item's + // data, it always resolves to the item's own id. + if (stringAttrDef.field.id === "id") return id || ""; const label = toString( stringAttrDef.field.dynamic ? data.dynamic[stringAttrDef.field.id] : data.static[stringAttrDef.field.id], ); @@ -335,7 +357,7 @@ export function applyVisualProperties( attr.rawSize = attr.size; } if (getters.getNodeColor) attr.color = getters.getNodeColor(nodeData); - if (getters.getNodeLabel) attr.label = getters.getNodeLabel(nodeData); + if (getters.getNodeLabel) attr.label = getters.getNodeLabel(nodeData, node); if (getters.getNodeImage) attr.image = getters.getNodeImage(nodeData); graph.mergeNodeAttributes(node, attr); }); @@ -352,7 +374,7 @@ export function applyVisualProperties( attr.rawWeight = attr.weight; } if (getters.getEdgeColor) attr.color = getters.getEdgeColor(edgeData, edge); - if (getters.getEdgeLabel) attr.label = getters.getEdgeLabel(edgeData); + if (getters.getEdgeLabel) attr.label = getters.getEdgeLabel(edgeData, edge); if (getters.getEdgeZIndex) attr.zIndex = getters.getEdgeZIndex(edgeData); graph.mergeEdgeAttributes(edge, attr); }); @@ -429,7 +451,7 @@ export function getItemAttributes( const hidden = type === "nodes" ? !filteredGraph.hasNode(id) : !filteredGraph.hasEdge(id); return { - label: (getLabel && getLabel(itemData)) || undefined, + label: (getLabel && getLabel(itemData, id)) || undefined, color: (getColor && getColor(itemData, id)) || defaultColor, hidden, directed: type === "edges" ? graphDataset.fullGraph.isDirected(id) : undefined, diff --git a/packages/gephi-lite/src/core/cloud/github/provider.tsx b/packages/gephi-lite/src/core/cloud/github/provider.tsx index 94a3f2e8..986e7bbc 100644 --- a/packages/gephi-lite/src/core/cloud/github/provider.tsx +++ b/packages/gephi-lite/src/core/cloud/github/provider.tsx @@ -63,43 +63,61 @@ export class GithubProvider implements CloudProvider { } /** - * Get a file by id (without content) + * Request options forcing a fresh read of the gist, bypassing any HTTP cache. GitHub serves gists + * with a short-lived cache, so without this a change made seconds ago from another tab/session + * would be hidden behind a stale response — which would defeat the whole remote-freshness guard + * (stale updatedAt / stale content). The unique query param defeats the URL-keyed browser cache; + * the header asks any cache to revalidate with the origin. */ - async getFile(id: string): Promise | null> { + private noCacheRequestOptions() { + return { + // A unique query param defeats the URL-keyed HTTP cache (fetch ignores a Cache-Control request + // header for its own cache decisions, so that alone would not help), and request.cache tells + // fetch itself not to reuse a cached response. + _bust: Date.now(), + request: { cache: "no-store" as RequestCache }, + }; + } + + /** + * One read of the gist, returning both its metadata and the content of its graph file. + * + * Reading the two together matters for the freshness guard: it compares the remote content + * against a fingerprint taken alongside a reference date, and mixing a date from one read with + * a content from another would compare two potentially different versions. It also saves a + * round-trip where both are needed at once (right after a write, see saveFile). + */ + private async readGist(id: string): Promise<{ file: Omit; content: string } | null> { const response = await this.octokit.request("GET /gists/{gist_id}", { gist_id: id, + ...this.noCacheRequestOptions(), }); + if (!response.data) return null; - if (response.data) { - const file = this.getGraphFile(response.data); - if (file) return this.gistToCloudFile(response.data); - } + const gistFile = this.getGraphFile(response.data); + if (!gistFile) return null; - return null; + // Over ~1MB the API only sends a truncated content, the whole file being served by raw_url. + const file = response.data.files?.[gistFile.filename]; + const content = file?.truncated && file?.raw_url ? await (await fetch(file.raw_url)).text() : file?.content || ""; + + return { file: this.gistToCloudFile(response.data), content }; + } + + /** + * Get a file by id (without content) + */ + async getFile(id: string): Promise | null> { + return (await this.readGist(id))?.file || null; } /** * Retrieve the content of file by its id. */ async getFileContent(id: string): Promise { - const response = await this.octokit.request("GET /gists/{gist_id}", { - gist_id: id, - }); - - if (response.data && response.data.files) { - const gistFile = this.getGraphFile(response.data); - if (gistFile) { - const file = response.data.files[gistFile.filename]; - if (file?.truncated && file?.raw_url) { - const response = await fetch(file?.raw_url); - const content = await response.text(); - return content; - } - return file?.content || ""; - } - } - - throw new Error("not found"); + const read = await this.readGist(id); + if (!read) throw new Error("not found"); + return read.content; } /** @@ -123,7 +141,7 @@ export class GithubProvider implements CloudProvider { /** * Save (ie.update) a file on github */ - async saveFile(file: CloudFile, content: string): Promise { + async saveFile(file: CloudFile, content: string): Promise<{ file: CloudFile; content: string }> { const body = { description: file.description, public: file.isPublic || false, @@ -137,7 +155,22 @@ export class GithubProvider implements CloudProvider { gist_id: file.id, ...body, }); - return { ...this.gistToCloudFile(result.data), format: file.format }; + + // Read the gist back from the same "detail" endpoint the freshness guard polls, rather than + // trusting the PATCH response or the bytes we just sent: + // - the two endpoints can report a different `updated_at` for the very version just written, + // and memorizing the write one makes every later check believe the remote moved on its own + // (same reasoning as the open path, see `open` in core/file); + // - the content the gist ends up holding is what later checks will download and compare, so + // that is what has to be fingerprinted - anything GitHub might do to the bytes on the way in + // would otherwise make every comparison report a difference that does not exist. + // Falls back to the PATCH response and our own bytes when the re-read is unavailable (offline + // right after the write...), which is no worse than not re-reading at all. + const fresh = await this.readGist(file.id).catch(() => null); + return { + file: { ...this.gistToCloudFile(result.data), ...(fresh?.file || {}), format: file.format }, + content: fresh?.content ?? content, + }; } /** diff --git a/packages/gephi-lite/src/core/cloud/remoteContent.spec.ts b/packages/gephi-lite/src/core/cloud/remoteContent.spec.ts new file mode 100644 index 00000000..b7f46ed5 --- /dev/null +++ b/packages/gephi-lite/src/core/cloud/remoteContent.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; + +import { fingerprintContent } from "./remoteContent"; + +describe("Remote content fingerprint", () => { + it("should be stable for the same content", () => { + expect(fingerprintContent('{"a":1}')).toBe(fingerprintContent('{"a":1}')); + }); + + it("should differ for different contents", () => { + expect(fingerprintContent('{"a":1}')).not.toBe(fingerprintContent('{"a":2}')); + // Same length, one byte apart: the length prefix alone would not catch it. + expect(fingerprintContent("abcdef")).not.toBe(fingerprintContent("abcdeg")); + // Same characters, different order. + expect(fingerprintContent("ab")).not.toBe(fingerprintContent("ba")); + }); + + it("should handle an empty content", () => { + expect(fingerprintContent("")).toBe(fingerprintContent("")); + expect(fingerprintContent("")).not.toBe(fingerprintContent(" ")); + }); + + it("should not collide on a realistic pair of graph files", () => { + const graph = (weight: number) => JSON.stringify({ type: "gephi-lite", edges: [{ id: "e", weight }] }); + expect(fingerprintContent(graph(1))).not.toBe(fingerprintContent(graph(2))); + }); +}); diff --git a/packages/gephi-lite/src/core/cloud/remoteContent.ts b/packages/gephi-lite/src/core/cloud/remoteContent.ts new file mode 100644 index 00000000..d4beee88 --- /dev/null +++ b/packages/gephi-lite/src/core/cloud/remoteContent.ts @@ -0,0 +1,21 @@ +/** + * Fingerprint of a remote file's content. + * + * The freshness guard cannot rely on the remote's `updated_at` alone: a gist's timestamp moves for + * reasons that leave the file untouched (a star, a fork, a comment, or two reads of the same + * version simply disagreeing), and warning on the timestamp alone turns any of those into an + * alarming "someone changed your file" modal in the middle of an editing session. So the guard + * compares the remote's content against the fingerprint of what we last synced (see + * `remoteContentFingerprint` in the file state): only genuinely different bytes count as a change. + * + * FNV-1a over the content, prefixed by its length. Not a cryptographic hash: this only has to tell + * "same bytes" from "different bytes", cheaply and without keeping a whole graph file around. + */ +export function fingerprintContent(content: string): string { + let hash = 0x811c9dc5; + for (let i = 0; i < content.length; i++) { + hash ^= content.charCodeAt(i); + hash = Math.imul(hash, 0x01000193); + } + return `${content.length}:${(hash >>> 0).toString(16)}`; +} diff --git a/packages/gephi-lite/src/core/cloud/types.ts b/packages/gephi-lite/src/core/cloud/types.ts index acd98f6c..3af79b21 100644 --- a/packages/gephi-lite/src/core/cloud/types.ts +++ b/packages/gephi-lite/src/core/cloud/types.ts @@ -45,9 +45,11 @@ export interface CloudProvider { ): Promise; /** - * Save/Update a file. + * Save/Update a file. Returns what the remote holds once written - its refreshed metadata, and + * the content read back from it, which may not be byte-identical to what was sent and is what + * later freshness checks will compare against. */ - saveFile(file: CloudFile, content: string): Promise; + saveFile(file: CloudFile, content: string): Promise<{ file: CloudFile; content: string }>; /** * Delete a file. diff --git a/packages/gephi-lite/src/core/cloud/useCloudProvider.ts b/packages/gephi-lite/src/core/cloud/useCloudProvider.ts index 88dfeb00..19b6309b 100644 --- a/packages/gephi-lite/src/core/cloud/useCloudProvider.ts +++ b/packages/gephi-lite/src/core/cloud/useCloudProvider.ts @@ -3,13 +3,14 @@ import { useCallback, useState } from "react"; import { useFile, useFileActions } from "../context/dataContexts"; import { useConnectedUser } from "../user"; +import { fingerprintContent } from "./remoteContent"; import { CloudFile } from "./types"; // TODO: need to be refacto by atom/action/producer pattern export function useCloudProvider() { const [user] = useConnectedUser(); const { current: currentFile } = useFile(); - const { open, exportAsGephiLite, setCurrentFile } = useFileActions(); + const { open, exportAsGephiLite, setCurrentFile, setRemoteContentFingerprint } = useFileActions(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -57,7 +58,15 @@ export function useCloudProvider() { if (isNil(user)) throw new Error("You must be logged !"); if (!currentFile || currentFile.type !== "cloud") throw new Error("Not a cloud graph"); await exportAsGephiLite(async (content) => { - await user.provider.saveFile(currentFile as CloudFile, content); + // Keep the current file pointer in sync with the freshly saved remote version: its new + // updatedAt becomes the reference date for the remote-change guard (see useRemoteFileGuard), + // otherwise the very next edit would compare against a stale date and warn about our own save. + const saved = await user.provider.saveFile(currentFile as CloudFile, content); + setCurrentFile(saved.file); + // Fingerprint what the remote reports holding, not the bytes we sent: the guard downloads + // the remote content to compare, so the reference has to be on the same side of the wire + // (see core/cloud/remoteContent). + setRemoteContentFingerprint(fingerprintContent(saved.content)); }); } catch (e) { setError(e as Error); @@ -65,7 +74,7 @@ export function useCloudProvider() { } finally { setLoading(false); } - }, [user, exportAsGephiLite, currentFile]); + }, [user, exportAsGephiLite, currentFile, setCurrentFile, setRemoteContentFingerprint]); /** * Save the current graph in the provider. diff --git a/packages/gephi-lite/src/core/cloud/useRemoteFileGuard.ts b/packages/gephi-lite/src/core/cloud/useRemoteFileGuard.ts new file mode 100644 index 00000000..5f18b53d --- /dev/null +++ b/packages/gephi-lite/src/core/cloud/useRemoteFileGuard.ts @@ -0,0 +1,234 @@ +import { useCallback, useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; + +import ConfirmModal from "../../components/modals/ConfirmModal"; +import { useFile, useFileActions } from "../context/dataContexts"; +import { FileType } from "../file/types"; +import { useModal } from "../modals"; +import { useNotifications } from "../notifications"; +import { useConnectedUser } from "../user"; +import { fingerprintContent } from "./remoteContent"; +import { CloudFile } from "./types"; + +// The passive freshness check (popup open / first modification / periodic tick) probes GitHub at +// most once per this window; after it, the next trigger probes again. It is also the period of the +// background re-check, so a long editing session keeps catching remote updates. +const RECHECK_INTERVAL = 60 * 1000; // ~1 minute + +// Timestamp (ms) of the last probe for the current file, or null when it must be (re)checked. Reset +// whenever the current file changes (open/save/reload). Shared across every hook instance. +let lastCheckedAt: number | null = null; + +// The remote version (its `updated_at`) the user was warned about and explicitly chose to keep +// their own changes over. Without this the passive check would raise the very same warning again +// at the next tick, and every minute after that, since nothing about the remote has changed. +// Only silences the passive warning: the pre-save probe still confirms before overwriting. +let acknowledgedRemoteUpdatedAt: string | null = null; + +// Ignore a remote that is newer by at most this margin. GitHub's gist `updated_at` has second-level +// precision, and even after normalizing every read on the same "detail" endpoint a transient +// inconsistency could report a one-second-off timestamp for the same version; this absorbs it so no +// spurious "remote changed" warning appears. Trade-off: a genuine concurrent change that is only +// within this margin of our reference is not detected — kept small on purpose, and acceptable since +// the reference is refreshed on every open/save/reload/periodic check anyway. +const FRESHNESS_TOLERANCE = 1000; // ms + +function isRemoteNewer(remoteUpdatedAt: Date | string, knownUpdatedAt: Date | string): boolean { + // Dates may be plain strings after a localStorage rehydration, hence the new Date() on both sides. + return new Date(remoteUpdatedAt).getTime() - new Date(knownUpdatedAt).getTime() > FRESHNESS_TOLERANCE; +} + +/** + * Hook exposing the remote-freshness guards for the currently open GitHub file: + * - `check()`: passive, fire-and-forget probe (popup open / first modification / periodic tick). + * Deduped to at most one network round-trip per RECHECK_INTERVAL, never blocks nor delays editing, + * silently ignores errors (offline...), and on a newer remote shows a reload/keep warning. + * - `probeRemoteIsNewer()`: awaited pre-save probe returning the current file when the remote is + * strictly newer (so the save flow can offer reload/overwrite/keep), else null. It shows no modal + * itself and never throws (returns null on error/offline, so the save can proceed). + * - `reloadFile(file)`: reload the given file (discarding local changes for the up-to-date remote). + */ +export function useRemoteFileFreshnessCheck() { + const { t } = useTranslation(); + const { notify } = useNotifications(); + const { current, remoteContentFingerprint } = useFile(); + const { open, setCurrentFile, setRemoteContentFingerprint } = useFileActions(); + const { openModal } = useModal(); + const [user] = useConnectedUser(); + + // Read the always-current file/user through refs, so the returned callbacks stay stable. + const currentRef = useRef(current); + currentRef.current = current; + const userRef = useRef(user); + userRef.current = user; + const fingerprintRef = useRef(remoteContentFingerprint); + fingerprintRef.current = remoteContentFingerprint; + + const reload = useCallback( + async (file: FileType) => { + try { + // `open` re-reads the fresh remote metadata (detail endpoint) and memorizes THAT as the new + // reference date, so we can pass the file we hold as-is: no need to pre-fetch here, and the + // memorized date won't stay behind the remote (which would otherwise re-trigger the warning + // in a loop right after each reload). + await open(file); + notify({ type: "success", message: t("graph.remote_changed.reload_success", { filename: file.filename }) }); + } catch (e) { + console.error(e); + notify({ type: "error", message: t("graph.remote_changed.reload_error") }); + } + }, + [open, notify, t], + ); + + const warnRemoteChanged = useCallback( + (file: FileType, messageKey: string, remoteUpdatedAt: Date | string) => { + openModal({ + component: ConfirmModal, + arguments: { + title: t("graph.remote_changed.title"), + message: t(messageKey, { filename: file.filename }), + confirmMsg: t("graph.remote_changed.reload"), + cancelMsg: t("graph.remote_changed.keep"), + }, + afterSubmit: () => reload(file), + // "Keep my changes": the user has seen this remote version and decided against it, so stop + // raising it again every minute. A later, genuinely different remote version still warns. + afterCancel: () => { + acknowledgedRemoteUpdatedAt = new Date(remoteUpdatedAt).toISOString(); + }, + }); + }, + [openModal, reload, t], + ); + + /** + * Second opinion on an apparently newer remote: download its content and check that it really + * differs from what we last synced. A gist's `updated_at` moves for reasons that leave the file + * untouched (a star, a fork, a comment, or two reads of the same version simply disagreeing), and + * those must not raise an alarm in the middle of an editing session. + * + * Returns true when the remote must be reported as changed - including when the check itself + * cannot be made (nothing to compare against, or the download failed): the guard exists to + * prevent silently overwriting someone else's work, so doubt has to fall on the warning side. + */ + const isRemoteContentReallyDifferent = useCallback( + async (file: FileType, remote: Pick): Promise => { + const provider = userRef.current?.provider; + const knownFingerprint = fingerprintRef.current; + if (!provider || file.type !== "cloud") return true; + + let remoteFingerprint: string; + try { + remoteFingerprint = fingerprintContent(await provider.getFileContent(file.id)); + } catch (e) { + console.error(e); + return true; + } + + // Nothing to compare against (state saved by a version that did not record it yet, or a + // workspace restored from before this guard existed): the question cannot be answered, so + // warn - but memorize what the remote holds, so the next check can decide properly instead + // of asking again forever. + if (!knownFingerprint) { + setRemoteContentFingerprint(remoteFingerprint); + return true; + } + + if (remoteFingerprint !== knownFingerprint) return true; + + // Same bytes: adopt the new timestamp as our reference, so this bump is not re-examined (and + // the content re-downloaded) on every subsequent tick. + setCurrentFile({ ...file, updatedAt: remote.updatedAt }); + return false; + }, + [setCurrentFile, setRemoteContentFingerprint], + ); + + const check = useCallback(() => { + const now = Date.now(); + if (lastCheckedAt !== null && now - lastCheckedAt < RECHECK_INTERVAL) return; + + const file = currentRef.current; + const provider = userRef.current?.provider; + // Scope: only GitHub ("cloud") files, the ones another user/session can concurrently change. + if (!provider || !file || file.type !== "cloud") return; + + // Mark as checked right away, so concurrent triggers (popup open, then first edit) don't each + // fire a request within the window. + lastCheckedAt = now; + provider + .getFile(file.id) + .then(async (remote) => { + if (!remote || !isRemoteNewer(remote.updatedAt, file.updatedAt)) return; + // Already shown for this exact remote version, and dismissed with "keep my changes": + if (new Date(remote.updatedAt).toISOString() === acknowledgedRemoteUpdatedAt) return; + if (await isRemoteContentReallyDifferent(file, remote)) + warnRemoteChanged(file, "graph.remote_changed.message", remote.updatedAt); + }) + .catch((e) => { + // GitHub unreachable (offline...) or any error: never hinder editing, and re-arm so a later + // trigger can retry. + lastCheckedAt = null; + console.error(e); + }); + }, [warnRemoteChanged, isRemoteContentReallyDifferent]); + + const probeRemoteIsNewer = useCallback(async (): Promise => { + const file = currentRef.current; + const provider = userRef.current?.provider; + if (!provider || !file || file.type !== "cloud") return null; + try { + const remote = await provider.getFile(file.id); + lastCheckedAt = Date.now(); + if (!remote || !isRemoteNewer(remote.updatedAt, file.updatedAt)) return null; + return (await isRemoteContentReallyDifferent(file, remote)) ? file : null; + } catch (e) { + // Freshness can't be confirmed (offline...): treat as "not newer" so the save proceeds (it + // fails on its own if the network is really down). Never block saving on a failed pre-check. + console.error(e); + return null; + } + }, [isRemoteContentReallyDifferent]); + + return { check, probeRemoteIsNewer, reloadFile: reload }; +} + +/** + * Mounted once (in Initialize): + * - re-arms the freshness check whenever a new file becomes the current one (open / save / reload + * all replace the `current` object reference), + * - probes on the first modification that did not go through a create/edit popup (layout, metric, + * appearance, filter, direct table edit, delete...), + * - re-checks periodically (every RECHECK_INTERVAL), so a long editing session still catches a + * remote update even without any further user action. + * + * The create/edit popups probe on their own opening, see useRemoteFileFreshnessCheck used in + * EditNodeModal / EditEdgeModal; the save flow probes before overwriting, see the Header. + */ +export function useRemoteFileGuard() { + const { check } = useRemoteFileFreshnessCheck(); + const { current, isDirty } = useFile(); + + const prevCurrent = useRef(current); + useEffect(() => { + if (current !== prevCurrent.current) { + prevCurrent.current = current; + lastCheckedAt = null; + acknowledgedRemoteUpdatedAt = null; + } + }, [current]); + + const wasDirty = useRef(isDirty); + useEffect(() => { + const previouslyDirty = wasDirty.current; + wasDirty.current = isDirty; + // Only react to the clean -> dirty transition (the moment the "unsaved changes" star appears): + if (isDirty && !previouslyDirty) check(); + }, [isDirty, check]); + + useEffect(() => { + const id = window.setInterval(() => check(), RECHECK_INTERVAL); + return () => window.clearInterval(id); + }, [check]); +} diff --git a/packages/gephi-lite/src/core/context/dataContexts.tsx b/packages/gephi-lite/src/core/context/dataContexts.tsx index 9d6795a0..59881e9f 100644 --- a/packages/gephi-lite/src/core/context/dataContexts.tsx +++ b/packages/gephi-lite/src/core/context/dataContexts.tsx @@ -17,7 +17,7 @@ import { } from "../graph"; import { layoutActions, layoutStateAtom } from "../layouts"; import { preferencesActions, preferencesAtom } from "../preferences"; -import { searchActions, searchAtom } from "../search"; +import { searchActions, searchAtom, searchQueryAtom } from "../search"; import { selectionActions, selectionAtom } from "../selection"; import { sessionActions, sessionAtom } from "../session"; import { sigmaActions, sigmaAtom, sigmaStateAtom } from "../sigma"; @@ -62,6 +62,7 @@ const ATOMS = { visualGetters: visualGettersAtom, topologicalFilters: topologicalFiltersAtom, search: searchAtom, + searchQuery: searchQueryAtom, layoutState: layoutStateAtom, session: sessionAtom, user: userAtom, @@ -81,6 +82,7 @@ const CONTEXTS = { layoutState: createContext(ATOMS.layoutState), preferences: createContext(ATOMS.preferences), search: createContext(ATOMS.search), + searchQuery: createContext(ATOMS.searchQuery), selection: createContext(ATOMS.selection), session: createContext(ATOMS.session), sigma: createContext(ATOMS.sigma), @@ -119,6 +121,7 @@ export const resetStates: Action<[boolean]> = (full = false) => { appearanceActions.resetState(); sigmaActions.resetState(); searchActions.reset(); + searchActions.resetQuery(); graphDatasetActions.resetGraph(); fileActions.reset(full); layoutActions.stopLayout(); @@ -144,6 +147,7 @@ export const useFilteredGraph = makeUseAtom(CONTEXTS.filteredGraph); export const useVisualGetters = makeUseAtom(CONTEXTS.visualGetters); export const useTopologicalFilters = makeUseAtom(CONTEXTS.topologicalFilters); export const useSearch = makeUseAtom(CONTEXTS.search); +export const useSearchQuery = makeUseAtom(CONTEXTS.searchQuery); export const useLayoutState = makeUseAtom(CONTEXTS.layoutState); export const useUser = makeUseAtom(CONTEXTS.user); export const useDynamicItemData = makeUseAtom(CONTEXTS.dynamicItemData); diff --git a/packages/gephi-lite/src/core/file/index.ts b/packages/gephi-lite/src/core/file/index.ts index ec41e725..2d314a39 100644 --- a/packages/gephi-lite/src/core/file/index.ts +++ b/packages/gephi-lite/src/core/file/index.ts @@ -1,4 +1,4 @@ -import { gephiLiteStringify } from "@gephi/gephi-lite-sdk"; +import { FieldModel, ItemData, ItemType, Scalar, gephiLiteStringify } from "@gephi/gephi-lite-sdk"; import { Producer, asyncAction, atom, producerToAction } from "@ouestware/atoms"; import Graph from "graphology"; import { write } from "graphology-gexf"; @@ -8,6 +8,7 @@ import { config } from "../../config"; import { localStorage } from "../../utils/storage"; import { appearanceActions, appearanceAtom } from "../appearance"; import { applyVisualProperties, inferAppearanceState } from "../appearance/utils"; +import { fingerprintContent } from "../cloud/remoteContent"; import { resetStates } from "../context/dataContexts"; import { filtersActions, filtersAtom } from "../filters"; import { @@ -18,12 +19,14 @@ import { visualGettersAtom, } from "../graph"; import { dataGraphToFullGraph, initializeGraphDataset } from "../graph/utils"; +import { sessionActions, sessionAtom } from "../session"; import { resetCamera } from "../sigma"; +import { userAtom } from "../user"; import { FileState, FileType, FileTypeWithoutFormat, GephiLiteFileFormat } from "./types"; import { openAndParseFile } from "./utils"; function getEmptyFileState(): FileState { - return { current: null, recentFiles: [], status: { type: "idle" } }; + return { current: null, recentFiles: [], status: { type: "idle" }, isDirty: false, remoteContentFingerprint: null }; } function getLocalStorageFileState(): FileState { @@ -33,6 +36,7 @@ function getLocalStorageFileState(): FileState { ...getEmptyFileState(), ...state, status: { type: "idle" }, + isDirty: false, }; } @@ -40,12 +44,34 @@ function geFullDataGraph(): Graph { // get the full graph const graphDataset = graphDatasetAtom.get(); const filteredGraph = filteredGraphAtom.get(); - const dynamicNodeData = dynamicItemDataAtom.get(); + const dynamicItemData = dynamicItemDataAtom.get(); const fullDataGraph = dataGraphToFullGraph(graphDataset, filteredGraph); // apply current appearance on the graph const visualGetters = visualGettersAtom.get(); - applyVisualProperties(fullDataGraph, graphDataset, dynamicNodeData, visualGetters); + applyVisualProperties(fullDataGraph, graphDataset, dynamicItemData, visualGetters); + + // Materialize the computed values of "formula" (scripted) fields as static attributes on the + // exported graph. This makes them part of exports (eg. GEXF), while only mutating this freshly + // built export graph — the dataset (and thus the formula definitions and the native gephi-lite + // save) are left untouched. + const materializeScriptFields = (itemType: ItemType, fields: FieldModel[], dynamicData: Record) => { + const scriptFields = fields.filter((f) => f.script); + if (!scriptFields.length) return; + const itemIds = itemType === "nodes" ? fullDataGraph.nodes() : fullDataGraph.edges(); + const setAttribute = (id: string, key: string, value: Scalar) => + itemType === "nodes" + ? fullDataGraph.setNodeAttribute(id, key, value) + : fullDataGraph.setEdgeAttribute(id, key, value); + itemIds.forEach((id) => { + scriptFields.forEach((field) => { + const value = dynamicData[id]?.[field.id]; + if (value !== undefined && value !== null) setAttribute(id, field.id, value); + }); + }); + }; + materializeScriptFields("nodes", graphDataset.nodeFields, dynamicItemData.dynamicNodeData); + materializeScriptFields("edges", graphDataset.edgeFields, dynamicItemData.dynamicEdgeData); return fullDataGraph; } @@ -71,10 +97,25 @@ const setCurrentFile: Producer = (file) => { }; }; +// Records the content the remote holds as of the last open/save, so the freshness guard can check +// that an apparently newer remote really differs before warning (see core/cloud/remoteContent). +export const setRemoteContentFingerprint: Producer = (fingerprint) => { + return (prev) => ({ ...prev, remoteContentFingerprint: fingerprint }); +}; + +// Clears isDirty without touching the current file pointer: used after the graph dataset, +// appearance or filters atoms get bulk-replaced by something other than an actual user edit (e.g. +// the sessionStorage rehydration on page reload), which would otherwise flip isDirty back to true +// through the markDirty bindings below, even though nothing was actually modified. +export const clearDirty: Producer = () => (prev) => (prev.isDirty ? { ...prev, isDirty: false } : prev); + export const reset: Producer = (full) => { return (prev) => { if (full) return getEmptyFileState(); - return { ...prev, current: null }; + // A blank workspace has nothing unsaved yet: isDirty must be cleared here, since it runs + // after the graph/appearance/filters atoms were just reset to their own blank state, which + // (being a new value reference) already flipped it back to true via the markDirty bindings. + return { ...prev, current: null, isDirty: false, remoteContentFingerprint: null }; }; }; @@ -88,12 +129,12 @@ export const open = asyncAction(async (file: FileTypeWithoutFormat) => { try { // Parse the file - const { data, metadata, format } = await openAndParseFile(file); + const { data, metadata, format, content } = await openAndParseFile(file); // Do the import resetStates(false); if (format === "gephi-lite") { - const { graphDataset, appearance, filters } = data; + const { graphDataset, appearance, filters, session } = data; // Load the graph const { setGraphDataset } = graphDatasetActions; setGraphDataset(graphDataset); @@ -103,6 +144,9 @@ export const open = asyncAction(async (file: FileTypeWithoutFormat) => { // Load filters const { setFilters } = filtersActions; setFilters(filters); + // Load the session (layouts & metrics parameters), when the file carries one. Older files + // predate this field: their session is left as-is (the current tab's one). + if (session) sessionActions.setFullState(session); } else { const { setGraphDataset } = graphDatasetActions; const { mergeState } = appearanceActions; @@ -115,12 +159,27 @@ export const open = asyncAction(async (file: FileTypeWithoutFormat) => { if (!isEmpty(appearanceState)) mergeState(appearanceState); } - // Add the new file in the history list - fileActions.setCurrentFile({ ...file, format }); + // Add the new file in the history list. + // For a cloud (GitHub) file, memorize the metadata (esp. updatedAt) read from the SAME "detail" + // endpoint the freshness guard uses (getFile → GET /gists/{id}), not the one that brought us here + // (the "list" endpoint GET /gists, used by getFiles, or a stale localStorage entry). The two can + // report a slightly different updated_at for the very same gist version; memorizing the list one + // then comparing against the detail one would make the guard warn "remote is newer" on every tick + // without any real change. Falls back to the passed file if the detail read is unavailable. + let fileToMemorize: FileTypeWithoutFormat = file; + if (file.type === "cloud") { + const user = userAtom.get(); + const fresh = user ? await user.provider.getFile(file.id) : null; + if (fresh) fileToMemorize = { ...file, ...fresh }; + } + // Remember what the remote held when we opened it, so the freshness guard can tell a real + // remote change from a timestamp that merely moved (see core/cloud/remoteContent). + fileActions.setRemoteContentFingerprint(file.type === "cloud" ? fingerprintContent(content) : null); + fileActions.setCurrentFile({ ...fileToMemorize, format }); // Reset the camera resetCamera({ forceRefresh: true }); - fileAtom.set((prev) => ({ ...prev, status: { type: "idle" } })); + fileAtom.set((prev) => ({ ...prev, status: { type: "idle" }, isDirty: false })); } catch (e) { fileAtom.set((prev) => ({ ...prev, status: { type: "error", message: (e as Error).message } })); throw e; @@ -137,11 +196,12 @@ export const exportAsGephiLite = asyncAction(async (callback: (data: string) => graphDataset: graphDatasetAtom.get(), filters: filtersAtom.get(), appearance: appearanceAtom.get(), + session: sessionAtom.get(), }; const content = gephiLiteStringify(data); await callback(content); - // idle state - fileAtom.set((prev) => ({ ...prev, status: { type: "idle" } })); + // idle state, and the current file now matches what was just exported + fileAtom.set((prev) => ({ ...prev, status: { type: "idle" }, isDirty: false })); } catch (e) { fileAtom.set((prev) => ({ ...prev, status: { type: "error", message: (e as Error).message } })); } @@ -169,12 +229,21 @@ export const fileActions = { exportAsGexf, reset: producerToAction(reset, fileAtom), setCurrentFile: producerToAction(setCurrentFile, fileAtom), + clearDirty: producerToAction(clearDirty, fileAtom), + setRemoteContentFingerprint: producerToAction(setRemoteContentFingerprint, fileAtom), }; /** * Bindings: * ********* */ + +// Mark the current file as dirty as soon as the graph dataset, appearance or filters change: +const markDirty = () => fileAtom.set((prev) => (prev.isDirty ? prev : { ...prev, isDirty: true })); +graphDatasetAtom.bind(markDirty); +appearanceAtom.bind(markDirty); +filtersAtom.bind(markDirty); + fileAtom.bind((file) => { localStorage.setItem("file", gephiLiteStringify(file)); }); diff --git a/packages/gephi-lite/src/core/file/types.ts b/packages/gephi-lite/src/core/file/types.ts index d3918331..3347e819 100644 --- a/packages/gephi-lite/src/core/file/types.ts +++ b/packages/gephi-lite/src/core/file/types.ts @@ -3,6 +3,7 @@ import { AppearanceState } from "@gephi/gephi-lite-sdk"; import { CloudFile } from "../cloud/types"; import { FiltersState } from "../filters/types"; import { GraphDataset } from "../graph/types"; +import { Session } from "../session/types"; /** * A serializable structure, to allow Gephi Lite to load and save graphs, with their surrounding context. @@ -10,6 +11,7 @@ import { GraphDataset } from "../graph/types"; * - The full graph dataset * - The filters state * - The appearance state + * - The session (layouts & metrics parameters) */ export type GephiLiteFileFormat = { type: "gephi-lite"; @@ -17,6 +19,8 @@ export type GephiLiteFileFormat = { graphDataset: GraphDataset; filters: FiltersState; appearance: AppearanceState; + // Optional: files exported before this field existed simply don't carry it (backward compatible). + session?: Session; }; export type FileFormat = "gexf" | "gephi-lite" | "graphology" | "graphml"; @@ -50,6 +54,12 @@ export type FileState = { current: FileType | null; recentFiles: Array; status: { type: "idle" } | { type: "loading" } | { type: "error"; message?: string }; + // Whether the graph dataset, appearance or filters have changed since the current file was opened/saved: + isDirty: boolean; + // Fingerprint of the remote content as of the last open/save of the current cloud file, used by + // the freshness guard to tell a real remote change from a timestamp that merely moved (see + // core/cloud/remoteContent). Persisted with the rest of the file state, so it survives a reload. + remoteContentFingerprint: string | null; }; export type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }; diff --git a/packages/gephi-lite/src/core/file/utils.ts b/packages/gephi-lite/src/core/file/utils.ts index eb707e2e..fe778da3 100644 --- a/packages/gephi-lite/src/core/file/utils.ts +++ b/packages/gephi-lite/src/core/file/utils.ts @@ -140,15 +140,21 @@ export async function extractGraphFromFile( * Parse the content of the given file and returns its data and its type. */ export async function openAndParseFile(file: FileTypeWithoutFormat): Promise< - | { - format: "gexf" | "graphml" | "graphology"; - data: Graph; - metadata?: { nodeFields?: FieldModel<"nodes">[]; edgeFields?: FieldModel<"edges">[] }; - } - | { format: "gephi-lite"; data: GephiLiteFileFormat; metadata?: undefined } + ( + | { + format: "gexf" | "graphml" | "graphology"; + data: Graph; + metadata?: { nodeFields?: FieldModel<"nodes">[]; edgeFields?: FieldModel<"edges">[] }; + } + | { format: "gephi-lite"; data: GephiLiteFileFormat; metadata?: undefined } + ) & { + // The raw file content, returned alongside the parsed graph so the caller can fingerprint it + // (see core/cloud/remoteContent) without downloading it a second time. + content: string; + } > { const content = await getFileContent(file); - return extractGraphFromFile(content, file.filename); + return { ...(await extractGraphFromFile(content, file.filename)), content }; } /** diff --git a/packages/gephi-lite/src/core/filters/index.ts b/packages/gephi-lite/src/core/filters/index.ts index 2d50dcec..f5bc12a3 100644 --- a/packages/gephi-lite/src/core/filters/index.ts +++ b/packages/gephi-lite/src/core/filters/index.ts @@ -1,5 +1,5 @@ import { Producer, atom, producerToAction } from "@ouestware/atoms"; -import { dropRight, inRange } from "lodash"; +import { clamp, dropRight, inRange } from "lodash"; import { sessionStorage } from "../../utils/storage"; import { FilterType, FiltersState } from "./types"; @@ -55,6 +55,26 @@ export const deleteFilter: Producer = (index) => { }; }; +/** + * Moves a filter up (negative offset) or down (positive offset) in the stack. Filters apply in + * order, each one on the graph produced by the previous ones, so reordering them changes what the + * downstream filters see - hence a full recomputation, handled by the filters atom binding. + */ +export const moveFilter: Producer = (index, offset) => { + return (state) => { + if (!inRange(index, 0, state.filters.length)) throw new Error(`moveFilter: Index ${index} is out of bounds.`); + + const newIndex = clamp(index + offset, 0, state.filters.length - 1); + if (newIndex === index) return state; + + const filters = state.filters.slice(0); + const [filter] = filters.splice(index, 1); + filters.splice(newIndex, 0, filter); + + return { ...state, filters }; + }; +}; + export const updateFilter: Producer = (index, newFilter) => { return (state) => ({ ...state, @@ -80,6 +100,7 @@ export const filtersActions = { addFilter: producerToAction(addFilter, filtersAtom), resetFilters: producerToAction(resetFilters, filtersAtom), updateFilter: producerToAction(updateFilter, filtersAtom), + moveFilter: producerToAction(moveFilter, filtersAtom), deleteFilter: producerToAction(deleteFilter, filtersAtom), disableFiltersFrom: producerToAction(disableFiltersFrom, filtersAtom), } as const; diff --git a/packages/gephi-lite/src/core/filters/utils.ts b/packages/gephi-lite/src/core/filters/utils.ts index 88d4f71e..338774e0 100644 --- a/packages/gephi-lite/src/core/filters/utils.ts +++ b/packages/gephi-lite/src/core/filters/utils.ts @@ -3,14 +3,10 @@ import { subgraph } from "graphology-operators"; import { isNil, isNumber } from "lodash"; import { DateTime } from "luxon"; -import { - computeAllDynamicAttributes, - getScalarFromStaticDynamicData, - mergeStaticDynamicData, -} from "../graph/dynamicAttributes"; +import { getScalarFromStaticDynamicData, isComputedField, mergeStaticDynamicData } from "../graph/dynamicAttributes"; import { castScalarToModelValue, castScalarToQuantifiableValue } from "../graph/fieldModel"; import { DatalessGraph, GraphDataset, SigmaGraph } from "../graph/types"; -import { dataGraphToFullGraph } from "../graph/utils"; +import { computeAllComputedAttributes, dataGraphToFullGraph } from "../graph/utils"; import { FilterType, RangeFilterType, TermsFilterType, TopologicalFilterDefinition } from "./types"; export { getEmptyFiltersState, parseFiltersState, serializeFiltersState } from "@gephi/gephi-lite-sdk"; @@ -108,7 +104,11 @@ export function filterGraph( ); } else { if (filter.field) { - const dynamicNodeData = filter.field.dynamic ? computeAllDynamicAttributes("nodes", graph) : {}; + // Computed fields (dynamic and formula ones) hold no stored value: they have to be evaluated + // on the graph this filter applies to, or every item would read as a missing value. + const dynamicNodeData = isComputedField(filter.field) + ? computeAllComputedAttributes("nodes", dataset, graph) + : {}; const staticDynamicNodeData = mergeStaticDynamicData(nodeData, dynamicNodeData); const field = filter.field; nodes = graph.filterNodes((nodeID) => { @@ -131,7 +131,9 @@ export function filterGraph( ); } else { if (filter.field) { - const dynamicEdgeData = filter.field.dynamic ? computeAllDynamicAttributes("edges", graph) : {}; + const dynamicEdgeData = isComputedField(filter.field) + ? computeAllComputedAttributes("edges", dataset, graph) + : {}; const staticDynamicEdgeData = mergeStaticDynamicData(edgeData, dynamicEdgeData); const field = filter.field; edges = graph.filterEdges((edgeID) => { diff --git a/packages/gephi-lite/src/core/graph/dates.ts b/packages/gephi-lite/src/core/graph/dates.ts new file mode 100644 index 00000000..87ee676f --- /dev/null +++ b/packages/gephi-lite/src/core/graph/dates.ts @@ -0,0 +1,92 @@ +import { FieldModel, GraphDataset, ItemData, ItemType } from "@gephi/gephi-lite-sdk"; +import { mapValues } from "lodash"; +import { DateTime } from "luxon"; + +/** + * Automatic "creation date" / "update date" fields, set on every node and + * edge, and kept in sync whenever their data is created or updated. + */ + +export const SYSTEM_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + +export const CREATION_DATE_FIELD_ID = "creation_date"; +export const UPDATE_DATE_FIELD_ID = "update_date"; + +export function nowAsSystemDateScalar(): string { + return DateTime.now().toFormat(SYSTEM_DATE_FORMAT); +} + +// Order matters: this is also the order in which the fields end up in the data table (always last). +export function getSystemDateFieldModels(itemType: T): FieldModel[] { + return [ + { + id: UPDATE_DATE_FIELD_ID, + itemType, + type: "date", + format: SYSTEM_DATE_FORMAT, + label: "Update Date", + readOnly: true, + }, + { + id: CREATION_DATE_FIELD_ID, + itemType, + type: "date", + format: SYSTEM_DATE_FORMAT, + label: "Creation Date", + readOnly: true, + }, + ]; +} + +// Appends the system date fields at the end of the given fields list, unless they are already there: +export function ensureSystemDateFields(itemType: T, fields: FieldModel[]): FieldModel[] { + const missing = getSystemDateFieldModels(itemType).filter( + (systemField) => !fields.some((f) => f.id === systemField.id), + ); + return missing.length ? [...fields, ...missing] : fields; +} + +// Stamps creation and update date on brand-new item data: +export function stampCreationDates(data: ItemData): ItemData { + const now = nowAsSystemDateScalar(); + return { ...data, [CREATION_DATE_FIELD_ID]: now, [UPDATE_DATE_FIELD_ID]: now }; +} + +// Stamps update date on existing item data, preserving its original creation date: +export function stampUpdateDate(previousData: ItemData | undefined, data: ItemData): ItemData { + return { + ...data, + [CREATION_DATE_FIELD_ID]: previousData?.[CREATION_DATE_FIELD_ID], + [UPDATE_DATE_FIELD_ID]: nowAsSystemDateScalar(), + }; +} + +// Finds the item (node or edge) whose update date is the most recent, among the given item data. +// Update dates are stored in the fixed SYSTEM_DATE_FORMAT ("yyyy-MM-dd HH:mm:ss"), which sorts +// lexicographically, so a plain string comparison is enough to order them. Returns null when no +// item carries an update date (eg. an empty graph). +export function getMostRecentlyUpdatedItem(itemData: Record): { id: string; date: string } | null { + let mostRecent: { id: string; date: string } | null = null; + for (const [id, data] of Object.entries(itemData)) { + const date = data[UPDATE_DATE_FIELD_ID]; + if (typeof date === "string" && (mostRecent === null || date > mostRecent.date)) { + mostRecent = { id, date }; + } + } + return mostRecent; +} + +// Backfills the system date fields (and field models) on a dataset that predates this feature, eg. one +// restored from local storage or an older saved project. Items that already have both dates are left untouched: +export function ensureSystemDatesInDataset(dataset: GraphDataset): GraphDataset { + const stampIfMissing = (data: ItemData) => + data[CREATION_DATE_FIELD_ID] && data[UPDATE_DATE_FIELD_ID] ? data : stampCreationDates(data); + + return { + ...dataset, + nodeFields: ensureSystemDateFields("nodes", dataset.nodeFields), + edgeFields: ensureSystemDateFields("edges", dataset.edgeFields), + nodeData: mapValues(dataset.nodeData, stampIfMissing), + edgeData: mapValues(dataset.edgeData, stampIfMissing), + }; +} diff --git a/packages/gephi-lite/src/core/graph/dynamicAttributes.ts b/packages/gephi-lite/src/core/graph/dynamicAttributes.ts index 0ba783e0..c6bd003c 100644 --- a/packages/gephi-lite/src/core/graph/dynamicAttributes.ts +++ b/packages/gephi-lite/src/core/graph/dynamicAttributes.ts @@ -2,6 +2,7 @@ import { DynamicItemDataSpec, DynamicItemsDataSpec, FieldModel, + FullGraph, ItemData, ItemType, Scalar, @@ -59,6 +60,38 @@ export const computeAllDynamicAttributes = (itemType: T, gra ]), ); +/** + * Computes the value of every "formula" (scripted) field, for every item of the given full graph. + * Returns a map { [itemId]: { [fieldId]: value } }, so it can be merged with the topology-based + * dynamic attributes. A failing script never breaks the whole computation: the faulty cell is left + * undefined. + */ +export const computeScriptFieldsData = ( + itemType: T, + fields: FieldModel[], + fullGraph: FullGraph, +): Record => { + const scriptFields = fields.filter((field) => !!field.script); + const ids = itemType === "nodes" ? fullGraph.nodes() : fullGraph.edges(); + const getAttributes = (id: string) => + itemType === "nodes" ? fullGraph.getNodeAttributes(id) : fullGraph.getEdgeAttributes(id); + + return fromPairs( + ids.map((id, index) => { + const attributes = getAttributes(id); + const values: ItemData = {}; + scriptFields.forEach((field) => { + try { + values[field.id] = field.script!(id, attributes, index, fullGraph); + } catch (_e) { + values[field.id] = undefined; + } + }); + return [id, values]; + }), + ); +}; + export const mergeStaticDynamicData = ( staticData: Record, dynamicData: Record, @@ -69,8 +102,13 @@ export const mergeStaticDynamicData = ( })); }; +// A formula (scripted) field is computed on the fly and stored in the "dynamic" data channel, just +// like the topology-based dynamic attributes (degree, ...): +export const isComputedField = (field: Pick, "dynamic" | "script">) => + !!field.dynamic || !!field.script; + export const staticDynamicAttributeKey = (field: FieldModel) => - `${field.dynamic ? "dynamic" : "static"}.${field.id}`; + `${isComputedField(field) ? "dynamic" : "static"}.${field.id}`; export const staticDynamicAttributeLabel = (field: FieldModel) => { if (field.dynamic) { @@ -81,7 +119,7 @@ export const staticDynamicAttributeLabel = (field: FieldModel export function getScalarFromStaticDynamicData( data: StaticDynamicItemData, - field: Pick, "id" | "dynamic">, + field: Pick, "id" | "dynamic" | "script">, ): Scalar { - return field.dynamic ? data.dynamic[field.id] : data.static[field.id]; + return isComputedField(field) ? data.dynamic[field.id] : data.static[field.id]; } diff --git a/packages/gephi-lite/src/core/graph/fieldModel.ts b/packages/gephi-lite/src/core/graph/fieldModel.ts index dbe25c6b..88213b6b 100644 --- a/packages/gephi-lite/src/core/graph/fieldModel.ts +++ b/packages/gephi-lite/src/core/graph/fieldModel.ts @@ -197,6 +197,25 @@ export function castScalarToModelValue( + scalar: Scalar, + fieldModel: FieldModelTypeSpecCollection[T], +): FieldModelAbstraction[T]["expectedOutput"] | undefined { + // Only "url" needs it: "text" is already raw, and the other types are edited through widgets + // (pickers, selects) that never see a partially typed value. + if (fieldModel.type === "url") return toString(scalar); + return castScalarToModelValue(scalar, fieldModel); +} + export function castScalarToQuantifiableValue( scalar: Scalar, field: FieldModel, diff --git a/packages/gephi-lite/src/core/graph/index.ts b/packages/gephi-lite/src/core/graph/index.ts index 5bafaef3..d731376c 100644 --- a/packages/gephi-lite/src/core/graph/index.ts +++ b/packages/gephi-lite/src/core/graph/index.ts @@ -19,7 +19,7 @@ import { } from "@ouestware/atoms"; import { MultiGraph } from "graphology"; import { Attributes, GraphType } from "graphology-types"; -import { clamp, forEach, isNil, isString, keyBy, keys, last, map, mapValues, omit, omitBy } from "lodash"; +import { clamp, forEach, isNil, isString, keyBy, keys, last, map, mapValues, omit, omitBy, pickBy } from "lodash"; import { Coordinates } from "sigma/types"; import { getPalette } from "../../components/GraphAppearance/color/utils"; @@ -38,11 +38,13 @@ import { selectionAtom } from "../selection"; import { SelectionState } from "../selection/types"; import { getEmptySelectionState } from "../selection/utils"; import { ItemType } from "../types"; -import { DYNAMIC_ATTRIBUTES, computeAllDynamicAttributes } from "./dynamicAttributes"; +import { ensureSystemDateFields, ensureSystemDatesInDataset, stampCreationDates, stampUpdateDate } from "./dates"; +import { DYNAMIC_ATTRIBUTES } from "./dynamicAttributes"; import { FieldModel, GraphDataset, SigmaGraph } from "./types"; import { cleanEdge, cleanNode, + computeAllComputedAttributes, dataGraphToSigmaGraph, datasetToString, getEmptyGraphDataset, @@ -80,7 +82,10 @@ const GRAPH_TRANSFORMATION_METHODS: Record Data * ********** */ const setGraphDataset: Producer = (dataset) => { - return () => dataset; + // Guarantee the system creation/update date fields on every full-dataset replacement, so all load + // paths (native file open, graphology import, broadcast) behave identically. Idempotent: datasets + // whose items already carry both dates are left untouched. + return () => ensureSystemDatesInDataset(dataset); }; const setGraphMeta: Producer = (metadata) => { return (state) => ({ @@ -156,7 +161,11 @@ const createFieldModel: Producer { const { data, position } = cleanNode(node, attributes); state.fullGraph.addNode(node); - const newNodeFieldModel = newItemModel<"nodes">("nodes", data, state.nodeFields); + const newNodeFieldModel = ensureSystemDateFields("nodes", newItemModel<"nodes">("nodes", data, state.nodeFields)); return { ...state, nodeFields: newNodeFieldModel, - nodeData: { ...state.nodeData, [node]: data }, + nodeData: { ...state.nodeData, [node]: stampCreationDates(data) }, layout: { ...state.layout, [node]: position }, }; }, @@ -314,14 +323,14 @@ const createEdge: MultiProducer<[GraphDataset, SearchState], [string, Attributes state.fullGraph.addUndirectedEdgeWithKey(edge, source, target); } - const newEdgeFieldModel = newItemModel<"edges">("edges", data, state.edgeFields); + const newEdgeFieldModel = ensureSystemDateFields("edges", newItemModel<"edges">("edges", data, state.edgeFields)); // Index the edge searchActions.edgeIndex(edge); return { ...state, edgeFields: newEdgeFieldModel, - edgeData: { ...state.edgeData, [edge]: data }, + edgeData: { ...state.edgeData, [edge]: stampCreationDates(data) }, }; }, edgeIndex(edge), @@ -335,11 +344,11 @@ const updateNode: MultiProducer<[GraphDataset, SearchState], [string, Attributes return [ (state) => { const { data, position } = cleanNode(node, merge ? { ...state.nodeData[node], ...attributes } : attributes); - const newNodeFieldModel = newItemModel<"nodes">("nodes", data, state.nodeFields); + const newNodeFieldModel = ensureSystemDateFields("nodes", newItemModel<"nodes">("nodes", data, state.nodeFields)); return { ...state, nodeFields: newNodeFieldModel, - nodeData: { ...state.nodeData, [node]: data }, + nodeData: { ...state.nodeData, [node]: stampUpdateDate(state.nodeData[node], data) }, layout: { ...state.layout, [node]: position }, }; }, @@ -348,26 +357,32 @@ const updateNode: MultiProducer<[GraphDataset, SearchState], [string, Attributes }; const updateEdge: MultiProducer< [GraphDataset, SearchState], - [string, Attributes, { merge?: boolean; directed?: boolean }?] -> = (edge, attributes, { merge, directed } = {}) => { + [string, Attributes, { merge?: boolean; directed?: boolean; source?: string; target?: string }?] +> = (edge, attributes, { merge, directed, source, target } = {}) => { return [ (state) => { const { data } = cleanEdge(edge, merge ? { ...state.edgeData[edge], ...attributes } : attributes); - const newEdgeFieldModel = newItemModel<"edges">("edges", data, state.edgeFields); + const newEdgeFieldModel = ensureSystemDateFields("edges", newItemModel<"edges">("edges", data, state.edgeFields)); - // Validate new edge direction: + // Validate new edge direction and extremities: let fullGraph = state.fullGraph; const graphType = fullGraph.type; const newDirected = graphType === "mixed" ? directed : graphType === "directed"; + const newSource = source ?? fullGraph.source(edge); + const newTarget = target ?? fullGraph.target(edge); + const directionChanges = !isNil(newDirected) && fullGraph.isDirected(edge) !== directed; + const extremitiesChange = newSource !== fullGraph.source(edge) || newTarget !== fullGraph.target(edge); - if (!isNil(newDirected) && fullGraph.isDirected(edge) !== directed) { + if (directionChanges || extremitiesChange) { const newFullGraph = fullGraph.emptyCopy(); - fullGraph.forEachEdge((e, _, source, target) => { - const isEdgeDirected = e === edge ? newDirected : fullGraph.isDirected(e); + fullGraph.forEachEdge((e, _, edgeSource, edgeTarget) => { + const isCurrentEdge = e === edge; + const isEdgeDirected = isCurrentEdge && directionChanges ? newDirected : fullGraph.isDirected(e); + const [s, t] = isCurrentEdge ? [newSource, newTarget] : [edgeSource, edgeTarget]; if (isEdgeDirected) { - newFullGraph.addDirectedEdgeWithKey(e, source, target); + newFullGraph.addDirectedEdgeWithKey(e, s, t); } else { - newFullGraph.addUndirectedEdgeWithKey(e, source, target); + newFullGraph.addUndirectedEdgeWithKey(e, s, t); } }); fullGraph = newFullGraph; @@ -379,7 +394,7 @@ const updateEdge: MultiProducer< ...state, fullGraph, edgeFields: newEdgeFieldModel, - edgeData: { ...state.edgeData, [edge]: data }, + edgeData: { ...state.edgeData, [edge]: stampUpdateDate(state.edgeData[edge], data) }, }; }, edgeIndex(edge), @@ -400,7 +415,7 @@ const updateItems: MultiProducer<[GraphDataset, SearchState], [ItemType, Set { if (!data[itemId]) throw new Error(`The ${type} collection does not have any item with "${itemId}" id.`); - return { ...acc, [itemId]: { ...data[itemId], [fieldId]: value } }; + return { ...acc, [itemId]: stampUpdateDate(data[itemId], { ...data[itemId], [fieldId]: value }) }; }, {}); return { @@ -433,7 +448,11 @@ export const filteredGraphsAtom = atom([]); export const filteredGraphAtom = derivedAtom( [filteredGraphsAtom, graphDatasetAtom], (filteredGraphCache, graphDataset) => last(filteredGraphCache)?.graph || graphDataset.fullGraph, - { checkInput: false }, + // checkOutput must stay disabled: the output is a mutable graphology graph, and a deep + // isEqual comparison can wrongly report "no change" (e.g. after an edge source/target + // swap, where nodes and edge keys are unchanged), leaving downstream atoms — and the + // Sigma rendering — stuck on a stale graph. + { checkInput: false, checkOutput: false }, ); export const useFilteredGraphAt = (index: number) => { const graphDataset = useGraphDataset(); @@ -442,10 +461,10 @@ export const useFilteredGraphAt = (index: number) => { }; export const dynamicItemDataAtom = derivedAtom( [filteredGraphAtom, graphDatasetAtom], - (filteredGraphCache) => ({ - dynamicNodeData: computeAllDynamicAttributes("nodes", filteredGraphCache), + (filteredGraphCache, graphDataset) => ({ + dynamicNodeData: computeAllComputedAttributes("nodes", graphDataset, filteredGraphCache), dynamicNodeFields: map(DYNAMIC_ATTRIBUTES.nodes, ({ field }) => field) || [], - dynamicEdgeData: computeAllDynamicAttributes("edges", filteredGraphCache), + dynamicEdgeData: computeAllComputedAttributes("edges", graphDataset, filteredGraphCache), dynamicEdgeFields: map(DYNAMIC_ATTRIBUTES.edges, ({ field }) => field) || [], }), { checkInput: false }, @@ -517,8 +536,16 @@ graphDatasetAtom.bind((graphDataset, previousGraphDataset) => { ), ); - // When the fullGraph ref changes, reindex everything: - if (updatedKeys.has("fullGraph") || updatedKeys.has("layout")) { + // When the graph content changes, reindex everything: + // (note: fullGraph is mutated in place by most producers, so its reference rarely + // changes; nodeData/edgeData always get a fresh reference on every edit, so we rely + // on those too, to avoid the filtered graph cache going stale after edits.) + if ( + updatedKeys.has("fullGraph") || + updatedKeys.has("layout") || + updatedKeys.has("nodeData") || + updatedKeys.has("edgeData") + ) { const filtersState = filtersAtom.get(); const newCache = applyFilters(graphDataset, filtersState.filters, [], topologicalFiltersAtom.get()); filteredGraphsAtom.set(newCache); @@ -529,9 +556,16 @@ graphDatasetAtom.bind((graphDataset, previousGraphDataset) => { searchActions.indexAll(); } - // When fields changed, check if filter or appearance use it + // When fields or data changed, check if filter or appearance use it + // (data changes are included because a partition's set of values can change + // without the field model itself changing, e.g. adding/removing an item) // here we test only static field - if (updatedKeys.has("edgeFields") || updatedKeys.has("nodeFields")) { + if ( + updatedKeys.has("edgeFields") || + updatedKeys.has("nodeFields") || + updatedKeys.has("nodeData") || + updatedKeys.has("edgeData") + ) { const nodeFields = graphDataset.nodeFields.map((nf) => nf.id); const edgeFields = graphDataset.edgeFields.map((nf) => nf.id); @@ -581,20 +615,23 @@ graphDatasetAtom.bind((graphDataset, previousGraphDataset) => { switch (appearanceElement.type) { // - if partitions palette are still in sync with the field values - case "partition": + case "partition": { // check if deprecated appearance state values = uniqFieldValuesAsStrings(itemsData, appearanceElement.field.id); - // checking with the actual palette miss some values. It's ok if it has more available. - if ( - keys(appearanceElement.colorPalette).length < values.length || - values.some((v) => appearanceElement.colorPalette[v] === undefined) - ) { - // new palette - // TODO: merge existing palette with the new values, i.e. keep existing colors - appearanceElement.colorPalette = getPalette(values); + // keep existing (and possibly user-customized) colors untouched: drop + // categories no longer present in the data, and only generate colors + // for categories missing from the palette + const prunedPalette = pickBy(appearanceElement.colorPalette, (_c, v) => values.includes(v)); + const missingValues = values.filter((v) => prunedPalette[v] === undefined); + if (missingValues.length > 0 || keys(prunedPalette).length !== keys(appearanceElement.colorPalette).length) { + appearanceElement.colorPalette = { + ...prunedPalette, + ...getPalette(missingValues), + }; } break; + } // nothing to do for other cases // TODO: check if other cases need edits. } diff --git a/packages/gephi-lite/src/core/graph/shortestPath.spec.ts b/packages/gephi-lite/src/core/graph/shortestPath.spec.ts new file mode 100644 index 00000000..0103eb84 --- /dev/null +++ b/packages/gephi-lite/src/core/graph/shortestPath.spec.ts @@ -0,0 +1,79 @@ +import Graph, { DirectedGraph, MultiGraph } from "graphology"; +import { describe, expect, it } from "vitest"; + +import { getEdgesBetween, getShortestPathEdges } from "./utils"; + +/** + * a - b - c - d + * \_____/ + * (ac) + * plus an isolated node "z". + */ +function buildGraph() { + const graph = new Graph(); + ["a", "b", "c", "d", "z"].forEach((node) => graph.addNode(node)); + graph.addEdgeWithKey("ab", "a", "b"); + graph.addEdgeWithKey("bc", "b", "c"); + graph.addEdgeWithKey("cd", "c", "d"); + graph.addEdgeWithKey("ac", "a", "c"); + return graph; +} + +describe("Graph utilities", () => { + describe("#getEdgesBetween", () => { + it("should find the edges linking two nodes, in either order", () => { + const graph = buildGraph(); + expect(getEdgesBetween(graph, "a", "b")).toEqual(["ab"]); + expect(getEdgesBetween(graph, "b", "a")).toEqual(["ab"]); + expect(getEdgesBetween(graph, "a", "d")).toEqual([]); + }); + + it("should return every parallel edge of a multigraph", () => { + const graph = new MultiGraph(); + graph.addNode("a"); + graph.addNode("b"); + graph.addEdgeWithKey("ab1", "a", "b"); + graph.addEdgeWithKey("ab2", "a", "b"); + expect(getEdgesBetween(graph, "a", "b").sort()).toEqual(["ab1", "ab2"]); + }); + }); + + describe("#getShortestPathEdges", () => { + it("should return the single edge between two neighbours", () => { + expect(getShortestPathEdges(buildGraph(), "a", "b")).toEqual(["ab"]); + }); + + it("should return the shortest of several paths", () => { + // a-b-c-d is longer than a-c-d, which is the one expected. + expect(getShortestPathEdges(buildGraph(), "a", "d")).toEqual(["ac", "cd"]); + }); + + it("should find the same path both ways, ordered from the node asked first", () => { + expect(getShortestPathEdges(buildGraph(), "d", "a")).toEqual(["cd", "ac"]); + }); + + it("should return null when no path links the two nodes", () => { + expect(getShortestPathEdges(buildGraph(), "a", "z")).toBe(null); + }); + + it("should return null when one of the nodes is unknown", () => { + expect(getShortestPathEdges(buildGraph(), "a", "nope")).toBe(null); + }); + + it("should ignore edge direction", () => { + // Only reachable from "a" by walking "ba" and "cb" backwards. + const graph = new DirectedGraph(); + ["a", "b", "c"].forEach((node) => graph.addNode(node)); + graph.addEdgeWithKey("ba", "b", "a"); + graph.addEdgeWithKey("cb", "c", "b"); + expect(getShortestPathEdges(graph, "a", "c")).toEqual(["ba", "cb"]); + }); + + it("should return the self-loops of a node asked for a path to itself", () => { + const graph = buildGraph(); + graph.addEdgeWithKey("aa", "a", "a"); + expect(getShortestPathEdges(graph, "a", "a")).toEqual(["aa"]); + expect(getShortestPathEdges(graph, "b", "b")).toEqual([]); + }); + }); +}); diff --git a/packages/gephi-lite/src/core/graph/utils.ts b/packages/gephi-lite/src/core/graph/utils.ts index 56f0584e..ddcdc6e6 100644 --- a/packages/gephi-lite/src/core/graph/utils.ts +++ b/packages/gephi-lite/src/core/graph/utils.ts @@ -4,6 +4,8 @@ import { Attributes } from "graphology-types"; import { flatMap, forEach, isNil, isNumber, keyBy, keys, mapValues, omit, sortBy, uniq, values } from "lodash"; import { ItemType, Scalar } from "../types"; +import { CREATION_DATE_FIELD_ID, UPDATE_DATE_FIELD_ID, ensureSystemDateFields, stampCreationDates } from "./dates"; +import { computeAllDynamicAttributes, computeScriptFieldsData } from "./dynamicAttributes"; import { inferFieldType } from "./fieldModel"; import { DataGraph, @@ -60,7 +62,11 @@ export function initializeGraphDataset( const nodeAttributeValues: Record = {}; graph.forEachNode((node, attributes) => { - const { data, position } = cleanNode(node, attributes); + const { data: cleanedData, position } = cleanNode(node, attributes); + const data = + cleanedData[CREATION_DATE_FIELD_ID] && cleanedData[UPDATE_DATE_FIELD_ID] + ? cleanedData + : stampCreationDates(cleanedData); for (const key in data) { nodeAttributeValues[key] = nodeAttributeValues[key] || []; @@ -74,7 +80,11 @@ export function initializeGraphDataset( const edgeAttributeValues: Record = {}; graph.forEachEdge((edge, attributes, source, target) => { - const { data } = cleanEdge(edge, attributes); + const { data: cleanedData } = cleanEdge(edge, attributes); + const data = + cleanedData[CREATION_DATE_FIELD_ID] && cleanedData[UPDATE_DATE_FIELD_ID] + ? cleanedData + : stampCreationDates(cleanedData); for (const key in data) { edgeAttributeValues[key] = edgeAttributeValues[key] || []; @@ -117,6 +127,16 @@ export function initializeGraphDataset( dataset.nodeFields = sortBy(dataset.nodeFields, getFieldScore) as typeof dataset.nodeFields; dataset.edgeFields = sortBy(dataset.edgeFields, getFieldScore) as typeof dataset.edgeFields; + // Enforce the canonical, read-only field models for the system date fields (always last): + dataset.nodeFields = ensureSystemDateFields( + "nodes", + dataset.nodeFields.filter((f) => f.id !== CREATION_DATE_FIELD_ID && f.id !== UPDATE_DATE_FIELD_ID), + ); + dataset.edgeFields = ensureSystemDateFields( + "edges", + dataset.edgeFields.filter((f) => f.id !== CREATION_DATE_FIELD_ID && f.id !== UPDATE_DATE_FIELD_ID), + ); + return dataset; } @@ -143,6 +163,31 @@ export function dataGraphToFullGraph( return res; } +/** + * Values of every attribute that is *not* stored in the item data but recomputed on the fly: the + * topology-based dynamic attributes (degree...) and the formula (scripted) fields. Both kinds live + * in the same "dynamic" data channel (see `isComputedField`), so anything that reads attribute + * values (filters, histograms, term lists...) must build that channel through this single helper: + * computing only the topology-based half leaves every formula field undefined, ie. indistinguishable + * from a missing value. + * + * The scripts are always taken from the dataset's *current* field models, so an edited formula is + * picked up even by a filter created before the edit (filters keep a snapshot of the field model). + */ +export function computeAllComputedAttributes( + itemType: T, + dataset: GraphDataset, + graph: DatalessGraph = dataset.fullGraph, +): Record { + const dynamicData = computeAllDynamicAttributes(itemType, graph); + + const fields = (itemType === "nodes" ? dataset.nodeFields : dataset.edgeFields) as FieldModel[]; + if (!fields.some((field) => field.script)) return dynamicData; + + const scriptData = computeScriptFieldsData(itemType, fields, dataGraphToFullGraph(dataset, graph)); + return mapValues(dynamicData, (data, id) => ({ ...data, ...scriptData[id] })); +} + /** * This function takes a graph dataset (and optionally a DatalessGraph as input) * and returns a SigmaGraph: @@ -260,3 +305,49 @@ export function uniqFieldValuesAsStrings(items: Record, field: }), ) as string[]; } + +/** + * All the edges linking two given nodes, whichever way they point (and however many there are, in + * a multigraph). + */ +export function getEdgesBetween(graph: DatalessGraph, source: string, target: string): string[] { + return graph.edges(source).filter((edge) => graph.opposite(source, edge) === target); +} + +/** + * Edges of a shortest path between two nodes, or `null` when no path links them. + * + * Edge direction is ignored (graphology's `neighbors` yields both the inbound and the outbound + * ones): the point is to show *how* two nodes are connected, so a path that only exists against + * the arrows is still an answer worth showing. Every parallel edge of a hop is returned, since + * they are drawn one on top of the other anyway and picking one arbitrarily would hide the others. + */ +export function getShortestPathEdges(graph: DatalessGraph, source: string, target: string): string[] | null { + if (!graph.hasNode(source) || !graph.hasNode(target)) return null; + if (source === target) return getEdgesBetween(graph, source, source); + + // Breadth-first search, level by level, keeping for each visited node the one it was reached + // from: the first time the target is visited it is through one of the shortest paths. + const previous: Record = {}; + const visited = new Set([source]); + let fringe = [source]; + while (fringe.length && !visited.has(target)) { + const nextFringe: string[] = []; + fringe.forEach((node) => + graph.neighbors(node).forEach((neighbor) => { + if (visited.has(neighbor)) return; + visited.add(neighbor); + previous[neighbor] = node; + nextFringe.push(neighbor); + }), + ); + fringe = nextFringe; + } + if (!visited.has(target)) return null; + + // Walk the path back from the target, collecting the edges of each hop. + const edges: string[] = []; + for (let node = target; node !== source; node = previous[node]) + edges.unshift(...getEdgesBetween(graph, previous[node], node)); + return edges; +} diff --git a/packages/gephi-lite/src/core/modals/index.ts b/packages/gephi-lite/src/core/modals/index.ts index 276413c3..e1c0076e 100644 --- a/packages/gephi-lite/src/core/modals/index.ts +++ b/packages/gephi-lite/src/core/modals/index.ts @@ -16,12 +16,36 @@ export function useModal() { ); const closeModal = useCallback(() => { - setModalState((modalState) => ({ ...modalState, modal: undefined })); + setModalState((modalState) => ({ ...modalState, modal: undefined, hasUnsavedInput: false })); + }, [setModalState]); + + // Declared by the open modal, so a close request coming from outside of it knows whether + // anything would be lost. + const setUnsavedInput = useCallback( + (hasUnsavedInput: boolean) => { + setModalState((modalState) => + !!modalState.hasUnsavedInput === hasUnsavedInput ? modalState : { ...modalState, hasUnsavedInput }, + ); + }, + [setModalState], + ); + + // Close asked for from outside the modal (the Android back button). A modal holding unsaved + // input is asked to confirm instead of being closed outright; every other one closes as before. + const requestCloseModal = useCallback(() => { + setModalState((modalState) => + modalState.hasUnsavedInput + ? { ...modalState, closeRequestId: (modalState.closeRequestId || 0) + 1 } + : { ...modalState, modal: undefined, hasUnsavedInput: false }, + ); }, [setModalState]); return { modal: modalState.modal, + closeRequestId: modalState.closeRequestId, openModal, closeModal, + requestCloseModal, + setUnsavedInput, }; } diff --git a/packages/gephi-lite/src/core/modals/types.ts b/packages/gephi-lite/src/core/modals/types.ts index 36e69666..11cb433e 100644 --- a/packages/gephi-lite/src/core/modals/types.ts +++ b/packages/gephi-lite/src/core/modals/types.ts @@ -18,4 +18,12 @@ export interface ModalRequest; + // Declared by the open modal while it holds user input that closing would discard. A close + // coming from outside of it (the Android back button) then asks it to confirm rather than + // throwing the input away, see `requestCloseModal`. + hasUnsavedInput?: boolean; + // Bumped by `requestCloseModal` when the open modal has unsaved input: the modal watches this + // token and raises its own confirmation. A counter rather than a flag, so two requests in a row + // are distinguishable. + closeRequestId?: number; } diff --git a/packages/gephi-lite/src/core/preferences/index.ts b/packages/gephi-lite/src/core/preferences/index.ts index 581b74a6..d31c70e0 100644 --- a/packages/gephi-lite/src/core/preferences/index.ts +++ b/packages/gephi-lite/src/core/preferences/index.ts @@ -1,7 +1,8 @@ import { Producer, atom, producerToAction } from "@ouestware/atoms"; import { localStorage } from "../../utils/storage"; -import { Preferences } from "./types"; +import { ItemType } from "../types"; +import { Preferences, SelectionSortMode } from "./types"; import { getAppliedTheme, getCurrentPreferences, serializePreferences } from "./utils"; /** @@ -23,6 +24,13 @@ const changeTheme: Producer = (theme) => { }); }; +const changeSelectionSort: Producer = (itemType, mode) => { + return (preferences) => ({ + ...preferences, + selectionSort: { ...preferences.selectionSort, [itemType]: mode }, + }); +}; + /** * Public API: * *********** @@ -32,6 +40,7 @@ export const preferencesAtom = atom(getCurrentPreferences()); export const preferencesActions = { changeLocale: producerToAction(changeLocale, preferencesAtom), changeTheme: producerToAction(changeTheme, preferencesAtom), + changeSelectionSort: producerToAction(changeSelectionSort, preferencesAtom), }; /** diff --git a/packages/gephi-lite/src/core/preferences/types.ts b/packages/gephi-lite/src/core/preferences/types.ts index d129d090..f260e5d9 100644 --- a/packages/gephi-lite/src/core/preferences/types.ts +++ b/packages/gephi-lite/src/core/preferences/types.ts @@ -1,3 +1,13 @@ +import { ItemType } from "../types"; + +/** + * How the selection panel orders the items it lists. Persisted per item type: a node list usually + * reads best alphabetically, while an edge list is often more useful ordered by weight, so the two + * are remembered separately. + */ +export const SELECTION_SORT_MODES = ["alphabetical", "size"] as const; +export type SelectionSortMode = (typeof SELECTION_SORT_MODES)[number]; + export interface Preferences { // for each layout, we save the parameters layoutsParameters: { [layout: string]: Record }; @@ -12,4 +22,6 @@ export interface Preferences { locale: string; // theme theme: "light" | "dark" | "auto"; + // sort order of the selection panel's list, for each item type + selectionSort: Record; } diff --git a/packages/gephi-lite/src/core/preferences/utils.ts b/packages/gephi-lite/src/core/preferences/utils.ts index 238e0388..0c0c86a6 100644 --- a/packages/gephi-lite/src/core/preferences/utils.ts +++ b/packages/gephi-lite/src/core/preferences/utils.ts @@ -11,6 +11,7 @@ export function getEmptyPreferences(): Preferences { // default is the local detected by i18n locale: i18n.language, theme: "auto", + selectionSort: { nodes: "alphabetical", edges: "alphabetical" }, }; } diff --git a/packages/gephi-lite/src/core/search/index.ts b/packages/gephi-lite/src/core/search/index.ts index 5b8ab3a8..1747eef7 100644 --- a/packages/gephi-lite/src/core/search/index.ts +++ b/packages/gephi-lite/src/core/search/index.ts @@ -113,11 +113,18 @@ export const reset: Producer = () => { return () => getEmptySearchState(); }; +// Raw text currently typed in the main fuzzy search box (see GraphSearchSelection): kept in its +// own atom, separate from the MiniSearch index above, so that typing does not depend on - or get +// wiped out by - index rebuilds (eg. `indexAll`, which replaces the whole SearchState). +export const setQuery: Producer = (query) => () => query; +export const resetQuery: Producer = () => () => ""; + /** * Public API: * *********** */ export const searchAtom = atom(getEmptySearchState()); +export const searchQueryAtom = atom(""); export const searchActions = { indexAll: producerToAction(indexAll, searchAtom), @@ -130,4 +137,6 @@ export const searchActions = { itemsRemove: producerToAction(itemsRemove, searchAtom), itemsIndex: producerToAction(itemsIndex, searchAtom), reset: producerToAction(reset, searchAtom), + setQuery: producerToAction(setQuery, searchQueryAtom), + resetQuery: producerToAction(resetQuery, searchQueryAtom), } as const; diff --git a/packages/gephi-lite/src/core/session/index.ts b/packages/gephi-lite/src/core/session/index.ts index a0e625b1..5615a5e7 100644 --- a/packages/gephi-lite/src/core/session/index.ts +++ b/packages/gephi-lite/src/core/session/index.ts @@ -19,8 +19,13 @@ export const reset: Producer = () => { return () => getEmptySession(); }; +const setFullState: Producer = (newState) => { + return () => newState; +}; + export const sessionActions = { reset: producerToAction(reset, sessionAtom), + setFullState: producerToAction(setFullState, sessionAtom), }; /** diff --git a/packages/gephi-lite/src/core/sigma/index.ts b/packages/gephi-lite/src/core/sigma/index.ts index 93d6c6a9..c783eb35 100644 --- a/packages/gephi-lite/src/core/sigma/index.ts +++ b/packages/gephi-lite/src/core/sigma/index.ts @@ -3,6 +3,7 @@ import Graph from "graphology"; import { Extent } from "graphology-metrics/graph/extent"; import { max } from "lodash"; import Sigma from "sigma"; +import { CameraState } from "sigma/types"; import { filteredGraphAtom, graphDatasetAtom, sigmaGraphAtom } from "../graph"; import { SigmaState } from "./types"; @@ -181,6 +182,223 @@ export const sigmaActions = { const ANIMATION_DURATION = 500; const HIGHLIGHT_DURATION = 2000; let focusTimeOutId: number | null = null; + +// A focus request that must be replayed once the graph page (and its sigma instance) is mounted +// and ready. Used when "locating" an item from another page (e.g. the data table): we navigate to +// the graph, and the graph page consumes this pending focus once ready (see consumePendingFocus). +let pendingFocus: { type: "nodes" | "edges"; id: string } | null = null; + +/** + * Runs `run` once the graph is ready to be framed on the given nodes, i.e. once BOTH: + * - the graph area (".filler") has stopped resizing, and + * - sigma has framed the nodes (their display coordinates are normalized, not raw). + * + * On mobile, selecting an item deploys a panel that takes half of the height, and the ".filler" + * (the visible graph band) shrinks over a CSS transition. Framing synchronously would then be + * computed against the *pre-transition* band (full height) and end up hidden behind the panel / + * zoomed for the wrong area, so we wait for the band to settle. And when arriving from another page + * (e.g. the data table), the sigma instance has just mounted and briefly reports raw coordinates as + * display data, so we also wait until the nodes are actually framed. On desktop, already mounted, + * both conditions hold within a couple of frames. Gives up after ~1s so a focus always happens. + */ +function runWhenReadyToFrame(nodeIds: string[], run: () => void) { + const getHeight = () => document.querySelector(".filler")?.getBoundingClientRect().height ?? 0; + const start = performance.now(); + let lastHeight = getHeight(); + let stableFrames = 0; + const tick = () => { + // Read the live sigma each frame: when arriving from another page the atom may still point to + // the previous instance for a frame or two while the graph page mounts. + const sigma = sigmaAtom.get(); + const height = getHeight(); + stableFrames = Math.abs(height - lastHeight) < 0.5 ? stableFrames + 1 : 0; + lastHeight = height; + const bandSettled = stableFrames >= 2; + const framed = nodeIds.some((id) => isNodeFramed(sigma, id)); + if ((bandSettled && framed) || performance.now() - start > 1000) run(); + else requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); +} + +/** + * Computes a camera state that frames the given nodes so that their disks AND their labels + * fit entirely inside the *visible* part of the graph. + * + * The sigma canvas spans more than what the user sees: the header and the left/right panels + * are drawn on top of it. The ".filler" element is exactly the graph rectangle left visible + * between them, whatever panels happen to be open or closed, so we frame the nodes inside it. + * + * Node sizes are expressed in position units (itemSizesReference: "positions"), so the disks + * scale with the zoom, whereas labels are drawn at a fixed pixel size. We therefore reason in + * viewport pixels: for each node we split its footprint into the part that scales with the + * zoom (the disk) and the fixed part (its label), then solve for the zoom factor that keeps + * everything within the visible band (minus some padding), and finally pan so the nodes are + * centered inside that band rather than inside the whole canvas. + */ +function getCameraStateToFrameNodes(sigma: Sigma, nodeIds: string[]): CameraState { + const graph = sigma.getGraph(); + const camera = sigma.getCamera(); + const ids = Array.from(new Set(nodeIds)).filter((id) => graph.hasNode(id) && !!sigma.getNodeDisplayData(id)); + if (!ids.length) return camera.getState(); + + const { width, height } = sigma.getDimensions(); + const currentRatio = camera.ratio || 1; + const PADDING = 28; // px of breathing room kept on each side of the visible band + + // Visible band, in viewport coordinates (0,0 = canvas top-left). ".filler" is the graph + // area left uncovered by the panels/header; fall back to the whole canvas if absent. + const containerRect = sigma.getContainer().getBoundingClientRect(); + const fillerRect = document.querySelector(".filler")?.getBoundingClientRect(); + const bandLeft = fillerRect ? fillerRect.left - containerRect.left : 0; + const bandTop = fillerRect ? fillerRect.top - containerRect.top : 0; + const bandWidth = fillerRect ? fillerRect.width : width; + const bandHeight = fillerRect ? fillerRect.height : height; + const bandCenterX = bandLeft + bandWidth / 2; + const bandCenterY = bandTop + bandHeight / 2; + + // Labels are rendered at a fixed pixel size: prepare a canvas to measure their width. + const ctx = document.createElement("canvas").getContext("2d"); + const labelSize = (sigma.getSetting("labelSize") as number) || 14; + const labelFont = (sigma.getSetting("labelFont") as string) || "sans-serif"; + const labelWeight = (sigma.getSetting("labelWeight") as string) || "normal"; + if (ctx) ctx.font = `${labelWeight} ${labelSize}px ${labelFont}`; + + // Node coordinates: framed (what camera.x/y use) and raw (what graphToViewport projects). + const framed = ids.map((id) => sigma.getNodeDisplayData(id) as { x: number; y: number }); + const raws = ids.map((id) => graph.getNodeAttributes(id) as { x: number; y: number; size?: number }); + const midFramedX = (Math.min(...framed.map((d) => d.x)) + Math.max(...framed.map((d) => d.x))) / 2; + const midFramedY = (Math.min(...framed.map((d) => d.y)) + Math.max(...framed.map((d) => d.y))) / 2; + const midRawX = (Math.min(...raws.map((a) => a.x)) + Math.max(...raws.map((a) => a.x))) / 2; + const midRawY = (Math.min(...raws.map((a) => a.y)) + Math.max(...raws.map((a) => a.y))) / 2; + const centerVp = sigma.graphToViewport({ x: midRawX, y: midRawY }); + + const availHalfW = Math.max(1, bandWidth / 2 - PADDING); + const availHalfH = Math.max(1, bandHeight / 2 - PADDING); + + // Zoom factor relative to the current zoom (> 1 zooms in). We keep the most constraining node. + let factor = Infinity; + ids.forEach((id, i) => { + const a = raws[i]; + const nodeVp = sigma.graphToViewport({ x: a.x, y: a.y }); + const border = sigma.graphToViewport({ x: a.x + (a.size || 0), y: a.y }); + const radiusPx = Math.abs(border.x - nodeVp.x); + const labelPx = ctx ? ctx.measureText(sigma.getNodeDisplayData(id)?.label || "").width : 0; + + // Half-extent (px) from the target center that scales with the zoom (the disk): + const scalingX = Math.abs(nodeVp.x - centerVp.x) + radiusPx; + const scalingY = Math.abs(nodeVp.y - centerVp.y) + radiusPx; + // Labels are drawn to the RIGHT of the node, so only nodes on the right half push against the + // right edge of the band. Reserving their (fixed, non-scaling) width for *every* node — including + // far-left ones whose label extends inward — would over-constrain the zoom and dezoom far too much + // on a narrow (mobile) band. Cap the reservation so a very long label cannot collapse the zoom. + const labelReserve = nodeVp.x >= centerVp.x ? Math.min(labelPx, availHalfW * 0.6) : 0; + if (scalingX > 0) factor = Math.min(factor, Math.max(availHalfW - labelReserve, 1) / scalingX); + if (scalingY > 0) factor = Math.min(factor, Math.max(availHalfH - labelSize / 2, 1) / scalingY); + }); + if (!isFinite(factor) || factor <= 0) factor = 1; + + // Safety floor: framing a subset of nodes must never zoom out further than showing the whole + // rendered graph — otherwise a bad measurement (or a very spread selection) could leave the graph + // barely visible. Compute the zoom that fits every rendered node in the band and never zoom out + // beyond it. + let allMinX = Infinity; + let allMaxX = -Infinity; + let allMinY = Infinity; + let allMaxY = -Infinity; + graph.forEachNode((n) => { + if (!sigma.getNodeDisplayData(n)) return; + const attrs = graph.getNodeAttributes(n) as { x: number; y: number }; + const v = sigma.graphToViewport({ x: attrs.x, y: attrs.y }); + allMinX = Math.min(allMinX, v.x); + allMaxX = Math.max(allMaxX, v.x); + allMinY = Math.min(allMinY, v.y); + allMaxY = Math.max(allMaxY, v.y); + }); + if (isFinite(allMinX)) { + const allHalfW = Math.max((allMaxX - allMinX) / 2, 1); + const allHalfH = Math.max((allMaxY - allMinY) / 2, 1); + const factorFitAll = Math.min(availHalfW / allHalfW, availHalfH / allHalfH); + factor = Math.max(factor, factorFitAll); + } + + // Pan so the nodes are centered inside the visible band (and not the whole canvas). We + // convert the pixel offset (band center vs canvas center) into framed units. The framed + // distance per viewport pixel is derived from two distinct nodes and scaled to the new zoom. + let camX = midFramedX; + let camY = midFramedY; + if (ids.length >= 2) { + const vp0 = sigma.graphToViewport({ x: raws[0].x, y: raws[0].y }); + let far = 1; + let farDist = -1; + for (let i = 1; i < ids.length; i++) { + const v = sigma.graphToViewport({ x: raws[i].x, y: raws[i].y }); + const d = Math.hypot(v.x - vp0.x, v.y - vp0.y); + if (d > farDist) [farDist, far] = [d, i]; + } + const vpFar = sigma.graphToViewport({ x: raws[far].x, y: raws[far].y }); + const dvx = vpFar.x - vp0.x; + const dvy = vpFar.y - vp0.y; + const magnitude = farDist > 1e-6 ? Math.hypot(framed[far].x - framed[0].x, framed[far].y - framed[0].y) / farDist : 0; + // Per-axis signed framed-units-per-pixel (same magnitude on both axes; sigma flips Y). + const framedPerPxX = Math.abs(dvx) > 1e-6 ? (framed[far].x - framed[0].x) / dvx : magnitude; + const framedPerPxY = Math.abs(dvy) > 1e-6 ? (framed[far].y - framed[0].y) / dvy : -magnitude; + camX = midFramedX - ((bandCenterX - width / 2) * framedPerPxX) / factor; + camY = midFramedY - ((bandCenterY - height / 2) * framedPerPxY) / factor; + } + + return { ...camera.getState(), angle: 0, x: camX, y: camY, ratio: currentRatio / factor }; +} + +/** + * Finds the node whose rendered label is under the given viewport position, if any. + * + * Sigma only hit-tests the node's disk when detecting clicks (clickNode); its label is drawn on a + * separate canvas and is otherwise unclickable, even though it often extends well beyond the disk. + * We replicate here the same geometry sigma itself uses to draw labels (drawDiscNodeLabel: text + * starts at `nodeRadiusPx + 3` to the right of the node, vertically centered near it), so that + * clicking a label behaves exactly like clicking its node. + */ +export function findNodeAtLabel(sigma: Sigma, viewportX: number, viewportY: number): string | null { + const graph = sigma.getGraph(); + // Only test labels sigma actually rendered this frame (respects the density/size-threshold + // culling), falling back to every visible node if that internal, undocumented set is absent. + // Note: this set is populated regardless of the app's own custom "hideLabel" appearance flag + // (e.g. every non-emphasized node's label while another item is selected) — that flag is only + // applied afterwards, inside the wrapped draw function — so it must be re-checked below. + const displayedLabels = (sigma as unknown as { displayedNodeLabels?: Set }).displayedNodeLabels; + const candidates = displayedLabels ? Array.from(displayedLabels) : graph.nodes(); + + const labelSize = (sigma.getSetting("labelSize") as number) || 14; + const labelFont = (sigma.getSetting("labelFont") as string) || "sans-serif"; + const labelWeight = (sigma.getSetting("labelWeight") as string) || "normal"; + const ctx = document.createElement("canvas").getContext("2d"); + if (ctx) ctx.font = `${labelWeight} ${labelSize}px ${labelFont}`; + + // Candidates are in draw order (earliest-drawn first): keep the LAST match, since a later-drawn + // label visually overlaps/wins over an earlier one at the same pixel. + let found: string | null = null; + for (const id of candidates) { + const displayData = sigma.getNodeDisplayData(id) as { hideLabel?: boolean; hidden?: boolean; label?: string }; + if (!displayData || displayData.hidden || displayData.hideLabel || !displayData.label) continue; + + const raw = graph.getNodeAttributes(id) as { x: number; y: number; size?: number }; + const nodeVp = sigma.graphToViewport({ x: raw.x, y: raw.y }); + const border = sigma.graphToViewport({ x: raw.x + (raw.size || 0), y: raw.y }); + const radiusPx = Math.abs(border.x - nodeVp.x); + const textWidth = ctx ? ctx.measureText(displayData.label).width : 0; + + const left = nodeVp.x + radiusPx + 3; + const right = left + textWidth; + const baselineY = nodeVp.y + labelSize / 3; + const top = baselineY - labelSize * 0.8; + const bottom = baselineY + labelSize * 0.3; + + if (viewportX >= left && viewportX <= right && viewportY >= top && viewportY <= bottom) found = id; + } + return found; +} + export function focusCameraOnNode(id: string) { if (focusTimeOutId) clearTimeout(focusTimeOutId); sigmaActions.resetHighlightedNodes(); @@ -211,45 +429,90 @@ export function focusCameraOnNode(id: string) { }, HIGHLIGHT_DURATION); } -export function focusCameraOnEdge(id: string) { +// Frames the camera on a set of nodes so their disks and labels fit within the *visible* graph +// band (between the panels), highlighting them for a short while. Used to "locate" a multi-item +// selection, whether triggered from the graph (selection panel) or from the data table. The framing +// is band-aware (accounts for the mobile selection panel) and waits for the graph to be ready, so +// it behaves the same regardless of the entry point. +export function focusCameraOnNodes(ids: string[]) { if (focusTimeOutId) clearTimeout(focusTimeOutId); sigmaActions.resetHighlightedNodes(); + if (!ids.length) return; - const sigma = sigmaAtom.get(); - const sourceId = sigma.getGraph().source(id); - const sourceDisplayData = sigma.getNodeDisplayData(sourceId); - const sourceData = sigma.getGraph().getNodeAttributes(sourceId); - - const targetId = sigma.getGraph().target(id); - const targetDisplayData = sigma.getNodeDisplayData(targetId); - const targetData = sigma.getGraph().getNodeAttributes(targetId); - - if (sourceData && targetData && targetDisplayData && sourceDisplayData) { - // margin is the size of the biggest node; - const margin = max([sourceDisplayData?.size, targetDisplayData?.size, 10]) as number; - - // we compute the width/height of the edge (with margin) in the graph referencial - const focusWidth = Math.abs(targetData.x - sourceData.x) + margin * 2; - const focusHeight = Math.abs(targetData.y - sourceData.y) + margin * 2; - - // we compute the zoom ratio (in the graph ref, which should be the same in the viewport) - const graphDimensions = sigma.getGraphDimensions(); - const focusRatio = max([focusHeight / graphDimensions.height, focusWidth / graphDimensions.width]) as number; - - sigma.getCamera().animate( - { - x: (sourceDisplayData.x + targetDisplayData.x) / 2, - y: (sourceDisplayData.y + targetDisplayData.y) / 2, - ratio: focusRatio, - }, - { duration: ANIMATION_DURATION }, - ); - } + runWhenReadyToFrame(ids, () => { + const sigma = sigmaAtom.get(); + // Keep only the nodes actually rendered right now, and frame them within the visible band. + const list = ids.filter((id) => sigma.getGraph().hasNode(id) && !!sigma.getNodeDisplayData(id)); + if (!list.length) return; + sigma.getCamera().animate(getCameraStateToFrameNodes(sigma, list), { duration: ANIMATION_DURATION }); + }); // Higlight nodes during X seconds - sigmaActions.setHighlightedNodes(new Set([sourceId, targetId])); + sigmaActions.setHighlightedNodes(new Set(ids)); focusTimeOutId = window.setTimeout(() => { sigmaActions.resetHighlightedNodes(); focusTimeOutId = null; }, HIGHLIGHT_DURATION); } + +export function focusCameraOnEdge(id: string) { + focusCameraOnEdges([id]); +} + +export function focusCameraOnEdges(ids: string[]) { + const graph = sigmaAtom.get().getGraph(); + + // Frame every endpoint of the given edges (framing readiness is handled downstream). + const endpoints: string[] = []; + ids.forEach((id) => { + if (!graph.hasEdge(id)) return; + endpoints.push(graph.source(id), graph.target(id)); + }); + focusCameraOnNodes(endpoints); +} + +// Register a focus to be replayed once the graph page's sigma instance is mounted and ready. +// Used to "locate" an item from a page where sigma is not mounted (e.g. the data table). +export function requestFocusOnReady(type: "nodes" | "edges", id: string) { + pendingFocus = { type, id }; +} + +// True once sigma has rendered/normalized the graph on its current instance: the node's display +// (framed) coordinates then differ from its raw layout coordinates. Right after navigation, sigma +// briefly reports raw coordinates as display data; focusing then would frame against unnormalized +// coordinates and send the camera into empty space. (When the graph is already normalized the two +// coincide, in which case focusing on the raw coordinates is correct anyway.) +function isNodeFramed(sigma: GephiLiteSigma, nodeId: string): boolean { + const graph = sigma.getGraph(); + if (!graph.hasNode(nodeId)) return false; + const dd = sigma.getNodeDisplayData(nodeId); + if (!dd) return false; + return dd.x !== graph.getNodeAttribute(nodeId, "x") || dd.y !== graph.getNodeAttribute(nodeId, "y"); +} + +// Replay any pending focus request. Called by the graph page once its sigma instance is ready. +// The focus is deferred (via animation frames) until the container has real pixel dimensions and +// sigma has framed the target item, so the camera animates to the right place. +export function consumePendingFocus() { + if (!pendingFocus) return; + const focus = pendingFocus; + pendingFocus = null; + + let attempts = 0; + const tryFocus = () => { + const sigma = sigmaAtom.get(); + const graph = sigma.getGraph(); + const { width, height } = sigma.getDimensions(); + const anchorNode = focus.type === "nodes" ? focus.id : graph.hasEdge(focus.id) ? graph.source(focus.id) : null; + const ready = width > 0 && height > 0 && anchorNode !== null && isNodeFramed(sigma, anchorNode); + + // Give up gracefully after ~1s (60 frames): focus best-effort rather than never. + if (ready || attempts++ >= 60) { + if (focus.type === "nodes") focusCameraOnNode(focus.id); + else focusCameraOnEdge(focus.id); + } else { + requestAnimationFrame(tryFocus); + } + }; + requestAnimationFrame(tryFocus); +} diff --git a/packages/gephi-lite/src/hooks/useKeyboardShortcuts.ts b/packages/gephi-lite/src/hooks/useKeyboardShortcuts.ts index 2ad72028..8a18fed8 100644 --- a/packages/gephi-lite/src/hooks/useKeyboardShortcuts.ts +++ b/packages/gephi-lite/src/hooks/useKeyboardShortcuts.ts @@ -47,8 +47,10 @@ export function useKeyboardShortcuts(shortcuts?: Shortcut[]) { // Listen keyboard event on document const handleKeyboardEvents = (event: KeyboardEvent) => { if (event.target instanceof Element) { - // prevent keyboard shortcut execution in text inputs. - if (event.target.matches("input, textarea")) return; + // prevent keyboard shortcut execution in text inputs, except Escape: it's the universal + // cancel/close key, and modals are full of text inputs, so blocking it there would make + // Escape unable to close a modal as soon as a field has focus. + if (event.code !== "Escape" && event.target.matches("input, textarea")) return; // disable the handler for chrome developer tools if (event.code === "KeyI" && event.metaKey && event.altKey) return; diff --git a/packages/gephi-lite/src/hooks/useLocateInGraph.ts b/packages/gephi-lite/src/hooks/useLocateInGraph.ts new file mode 100644 index 00000000..e303bf6f --- /dev/null +++ b/packages/gephi-lite/src/hooks/useLocateInGraph.ts @@ -0,0 +1,47 @@ +import { useCallback } from "react"; +import { useNavigate } from "react-router"; + +import { useSelectionActions } from "../core/context/dataContexts"; +import { focusCameraOnEdge, focusCameraOnNode, requestFocusOnReady } from "../core/sigma"; + +/** + * "Locate" an item in the graph: replace the current selection with that single item, then center + * the camera on it. + * + * When called with `navigateToGraph`, the caller is on a page where sigma is not mounted (e.g. the + * data table): we register the focus as pending and navigate to the graph page, which replays it + * once its sigma instance is ready (see requestFocusOnReady / consumePendingFocus). Otherwise the + * camera is focused right away (caller already on the graph page). + */ +export function useLocateInGraph() { + const { select } = useSelectionActions(); + const navigate = useNavigate(); + + const locateNode = useCallback( + (id: string, options?: { navigateToGraph?: boolean }) => { + select({ type: "nodes", items: new Set([id]), replace: true }); + if (options?.navigateToGraph) { + requestFocusOnReady("nodes", id); + navigate("/", { replace: true }); + } else { + focusCameraOnNode(id); + } + }, + [select, navigate], + ); + + const locateEdge = useCallback( + (id: string, options?: { navigateToGraph?: boolean }) => { + select({ type: "edges", items: new Set([id]), replace: true }); + if (options?.navigateToGraph) { + requestFocusOnReady("edges", id); + navigate("/", { replace: true }); + } else { + focusCameraOnEdge(id); + } + }, + [select, navigate], + ); + + return { locateNode, locateEdge }; +} diff --git a/packages/gephi-lite/src/locales/dev.json b/packages/gephi-lite/src/locales/dev.json index ae548785..81d7a250 100644 --- a/packages/gephi-lite/src/locales/dev.json +++ b/packages/gephi-lite/src/locales/dev.json @@ -45,6 +45,7 @@ "fixed_label": "Label for every {{items}}", "fixed_label_size": "Label size for every {{items}}", "fixed_size": "Fixed size", + "id": "Id", "none": "No label", "none_description": "All {{items}} have no displayed label.", "set_labels_from": "Set label from...", @@ -61,6 +62,7 @@ "show_edges": "Show edges", "size": { "default_value": "Size for {{items}} with no valid value", + "filter_aware": "Based on filtered values", "fixed": "A constant size", "interpolate": "Interpolate between custom min and max values", "set_size_from": "Set size from...", @@ -116,6 +118,12 @@ "created": "Created", "delete": "Delete", "disabled": "Disabled", + "discard_input": { + "title": "Discard your changes?", + "message": "You have started filling this form in. Closing it now will discard what you typed.", + "keep": "Keep editing", + "discard": "Discard" + }, "download": "Download", "edit": "Edit", "enabled": "Enabled", @@ -124,11 +132,13 @@ "filename": "Filename", "from": "From", "hide": "Hide", + "indeterminate": "Indeterminate", "load-more": "Load more", "loading": "Loading", "max": "Max", "min": "Min", "none": "None", + "ok": "OK", "open": "Open", "open_code_editor": "Open code editor", "open_file": "Open {{filename}}", @@ -146,12 +156,17 @@ "submit": "Submit", "to": "To", "true": "True", - "indeterminate": "Indeterminate", "updated": "Updated" }, "datatable": { + "cancel_cell": "Cancel cell edition", "create_column": "Create column", "duplicate_column": "Duplicate column", + "freeze_values": "Freeze values", + "freeze_values_confirm": "Freeze values", + "freeze_values_message": "This will delete the script and freeze the current computed values into a regular attribute. The values will no longer be recomputed. Do you want to continue?", + "freeze_values_success": "The values of \"{{name}}\" have been frozen.", + "freeze_values_title": "Freeze the values of \"{{name}}\"?", "edges_matching": "{{count, number}} edges matching the search query", "edges_matching_one": "{{count, number}} edge matching the search query", "edges_matching_zero": "No edge matching the search query", @@ -176,9 +191,12 @@ "preview": "Preview", "selected": "Selected?", "sourceId": "Source ID", - "targetId": "Target ID" + "sourceLabel": "Source label", + "targetId": "Target ID", + "targetLabel": "Target label" }, "save_and_create_column": "Save and create column", + "save_and_update_column": "Save and update column", "save_cell": "Save cell", "sort_asc": "Sort A→Z (0→9)", "sort_desc": "Sort Z→A (9→0)" @@ -205,7 +223,7 @@ "create_nodes_scripted_field_success": "The scripted node attribute has successfully been created.", "create_nodes_success": "The node has successfully been created.", "data_creation": "Data creation", - "delete_attribute": "Delete attribute", + "delete_attribute": "Delete column", "delete_attributes_success": "The '{{name}}' attribute has been deleted.", "delete_edges": "Delete {{count, number}} edges", "delete_edges_attributes": "Delete the '{{name}}' edge attribute", @@ -225,12 +243,15 @@ "delete_selected_nodes_one": "Delete selected node", "delete_this_edges": "Delete this edge", "delete_this_nodes": "Delete this node", + "duplicate_edge_warning": "{{count}} edges already connect these two nodes:", + "duplicate_edge_warning_one": "An edge already connects these two nodes:", "edit_selected_edges": "Edit selected edges", "edit_selected_edges_one": "Edit selected edge", "edit_selected_nodes": "Edit selected nodes", "edit_selected_nodes_one": "Edit selected node", "filtered_edges": "Filtered edges only", "filtered_nodes": "Filtered nodes only", + "invert_edge_direction": "Invert arrow's direction", "merge_selected_nodes": "Merge selected nodes", "multi": { "select_attribute": "Select the attribute to edit for the selection", @@ -239,9 +260,13 @@ "search_nodes": "Search on node labels...", "selected_edges": "Selected edges only", "selected_nodes": "Selected nodes only", + "similar_nodes_warning": "Nodes with a similar name already exist:", + "swap_extremities": "Swap source and target", "update_edges": "Edit existing edge", "update_edges_field": "Edit edges attribute", "update_edges_field_success": "The edge attribute has successfully been updated.", + "update_edges_scripted_field": "Edit edges scripted attribute", + "update_edges_scripted_field_success": "The scripted edge attribute has successfully been updated.", "update_edges_success": "The edge has successfully been updated.", "update_multiple_edges": "Edit existing edges", "update_multiple_edges_success": "The {{count, number}} edges have successfully been updated.", @@ -250,6 +275,8 @@ "update_nodes": "Edit existing node", "update_nodes_field": "Edit nodes attribute", "update_nodes_field_success": "The node attribute has successfully been updated.", + "update_nodes_scripted_field": "Edit nodes scripted attribute", + "update_nodes_scripted_field_success": "The scripted node attribute has successfully been updated.", "update_nodes_success": "The node has successfully been updated.", "update_this_edges": "Edit this edge", "update_this_nodes": "Edit this node" @@ -262,6 +289,7 @@ "IMPORT_BAD_VERSION": "Your file is from an older version of Gephi-Lite ({{version}}) which is not compatible with the actual version", "form": { "field_already_exists": "The column with label \"{{label}}\" already uses the id \"{{id}}\".", + "invalid_value": "This value is not a valid {{type}}.", "max": "Field {{name}} should be less than or equal to {{max}}", "min": "Field {{name}} should be greater than or equal to {{min}}", "required": "Field {{name}} is required", @@ -280,6 +308,7 @@ "unknown": "An unknown error has occurred." }, "filters": { + "active": "Filters are active - click to open the Filters panel", "add_filter": "Add filter", "create": { "custom_script_description": "These filters allow writing a JavaScript function that will tell for each node or edge if it should be kept.", @@ -297,10 +326,13 @@ "disable_following_subtitle": "eg. disable all filters after", "edges_fields": "Edges attributes", "full_graph": "Full graph", + "inactive": "No filter is active - click to open the Filters panel", "inapplicable": "Range is only displayed when there is at least two values to filter on.", "insert": "Insert filter", "intermediate_graph": "Intermediate graph", "keepMissingValues": "Keep missing values", + "move_down": "Move the filter down", + "move_up": "Move the filter up", "nodes_fields": "Nodes attributes", "script": "Custom filter", "title": "Filters", @@ -393,7 +425,8 @@ "edges_zero": "edge", "field": { "date-format": "Date format", - "id": "Identifier", + "default_value": "Default value", + "id": "ID", "label": "Label", "separator": "Keywords separator", "type": "Type", @@ -402,6 +435,7 @@ "dates_formats_link_message": "Read more about Luxon's parsing tokens" }, "mixed_graph": "Mixed graph", + "most_recently_updated": "Most recently updated", "nodes": "nodes", "nodes-data": { "id": "Node ID", @@ -444,6 +478,16 @@ "success": "File {{filename}} has been successfully loaded" } }, + "remote_changed": { + "keep": "Keep my changes", + "message": "The file \"{{filename}}\" has been modified on GitHub (by another user or session) since you opened it. If you keep editing and save, you will overwrite those changes. Do you want to reload the up-to-date version? Your local changes will be lost.", + "reload": "Reload from GitHub", + "overwrite": "Overwrite", + "reload_error": "An error occurred while reloading the file from GitHub.", + "reload_success": "File \"{{filename}}\" has been reloaded from GitHub.", + "save_conflict_message": "The file \"{{filename}}\" has been modified on GitHub (by another user or session) since you opened it. You can reload the up-to-date version (your local changes will be lost), overwrite it with your version, or keep your changes without saving.", + "title": "This graph has changed on GitHub" + }, "save": { "github": { "error": "An error occurred while saving your graph. Please check your connectivity or try later.", @@ -801,6 +845,7 @@ "github": "GitHub" }, "search": { + "clear": "Clear the search", "edges": { "help": "Type something to find an edge", "no_result": "No edge found", @@ -825,6 +870,7 @@ } }, "selection": { + "create_edge_from_node": "Create an edge from this node", "cursor": "Simple selection", "edge_no_label": "(Empty label)", "edges": "{{count, number}} selected edges", @@ -832,18 +878,21 @@ "edges_zero": "No selected edge", "empty_edges": "This edge has no attribute", "empty_nodes": "This node has no attribute", + "filtered": "{{count, number}} filtered", + "filtered_edges": "{{count, number}} filtered edges", + "filtered_edges_one": "{{count, number}} filtered edge", + "filtered_nodes": "{{count, number}} filtered nodes", + "filtered_nodes_one": "{{count, number}} filtered node", + "filtered_one": "{{count, number}} filtered", "focus_edges": "Select only this edge", "focus_nodes": "Select only this node", - "hidden": "{{count, number}} hidden", - "hidden_edges": "{{count, number}} hidden selected edges", - "hidden_edges_one": "One hidden selected edge", - "hidden_nodes": "{{count, number}} hidden selected nodes", - "hidden_nodes_one": "One hidden selected node", - "hidden_one": "one hidden", - "hidden_zero": "no hidden", + "hide_panel": "Hide the selection panel (keeps the selection)", "lasso": "Lasso selection", "locate_on_graph": "Locate on the graph", + "locate_selected_edges": "Zoom on all selected edges", + "locate_selected_nodes": "Zoom on all selected nodes", "marquee": "Rectangle selection", + "no_path_between_nodes": "No path links these two nodes", "no_value": "No value", "node_no_label": "(Empty label)", "nodes": "{{count, number}} selected nodes", @@ -852,20 +901,22 @@ "open_in_data": "Show in Data", "open_in_graph": "Show in Graph", "select_all": "Select all", + "select_node_edges": "Select node's edges", "select_node_neighbors": "Select node's neighbors", + "select_path_between_nodes": "Select the edges of a shortest path between the two nodes", + "selected_edges": "Selected edges", + "selected_nodes": "Selected nodes", + "sort": { + "alphabetical": "Sorted alphabetically - click to sort by decreasing size", + "size": "Sorted by decreasing size - click to sort alphabetically" + }, "unselect_all": "Unselect all", "unselect_all_hidden_edges": "Unselect all hidden edges", "unselect_all_hidden_nodes": "Unselect all hidden edges", "unselect_edges": "Unselect this edge", "unselect_nodes": "Unselect this node", "visible": "{{count, number}} visible", - "visible_edges": "{{count, number}} visible selected edges", - "visible_edges_one": "One visible selected edge", - "visible_edges_zero": "No visible selected edge", - "visible_nodes": "{{count, number}} visible selected nodes", - "visible_nodes_one": "One visible selected node", - "visible_nodes_zero": "No visible selected node", - "visible_one": "one visible", + "visible_one": "{{count, number}} visible", "visible_zero": "no visible" }, "settings": { @@ -886,6 +937,7 @@ "website": "website" }, "workspace": { + "confirm_leave_unsaved": "Your graph has unsaved changes. Leave without saving?", "menu": { "duplicate": "Clone workspace", "export_graph_file": "Export graph file", @@ -895,7 +947,8 @@ "new": "Create a new workspace", "open": "Open...", "save": "Save", - "save_as": "Save as..." + "save_as": "Save as...", + "unsaved_changes": "Unsaved changes" } } } diff --git a/packages/gephi-lite/src/locales/en.json b/packages/gephi-lite/src/locales/en.json index b36e66d8..020d025c 100644 --- a/packages/gephi-lite/src/locales/en.json +++ b/packages/gephi-lite/src/locales/en.json @@ -708,6 +708,7 @@ } }, "selection": { + "create_edge_from_node": "Create an edge from this node", "edge_no_label": "(Empty label)", "edges": "{{count, number}} selected edges", "edges_one": "One selected edge", @@ -721,11 +722,15 @@ "hidden_nodes": "{{count, number}} hidden selected nodes", "hidden_nodes_one": "One hidden selected node", "locate_on_graph": "Locate on the graph", + "locate_selected_edges": "Zoom on all selected edges", "node_no_label": "(Empty label)", "nodes": "{{count, number}} selected nodes", "nodes_one": "One selected node", "nodes_zero": "No selected node", "select_all": "Select all", + "select_node_edges": "Select node's edges", + "selected_edges": "Selected edges", + "selected_nodes": "Selected nodes", "unselect_all": "Unselect all", "unselect_all_hidden_edges": "Unselect all hidden edges", "unselect_all_hidden_nodes": "Unselect all hidden edges", diff --git a/packages/gephi-lite/src/locales/fr.json b/packages/gephi-lite/src/locales/fr.json index cb350e6d..368d8842 100644 --- a/packages/gephi-lite/src/locales/fr.json +++ b/packages/gephi-lite/src/locales/fr.json @@ -53,6 +53,7 @@ "fixed_label": "Libellé pour chaque {{items}}", "fixed_label_size": "Taille du libellé pour chaque {{items}}", "fixed_size": "Taille fixe", + "id": "Identifiant", "none": "Pas de libellé", "none_description": "Tous les {{items}} n'ont pas de libellé affiché.", "set_labels_from": "Définir le libellé à partir de...", @@ -75,6 +76,7 @@ "data": "Fichier de données", "data_description": "Toutes les tailles des {{items}} sont celles du fichier de données d'entrée.", "default_value": "Taille pour {{items}} sans valeur valide", + "filter_aware": "Basé sur les valeurs filtrées", "fixed": "Une taille constante", "interpolate": "Interpoler entre les valeurs personnalisées min et max", "set_size_from": "Définir la taille à partir de...", @@ -138,6 +140,12 @@ "created": "Créé", "delete": "Supprimer", "disabled": "Désactivé", + "discard_input": { + "title": "Abandonner votre saisie ?", + "message": "Vous avez commencé à remplir ce formulaire. Le fermer maintenant effacera ce que vous avez saisi.", + "keep": "Continuer la saisie", + "discard": "Abandonner" + }, "download": "Télécharger", "edit": "Modifier", "enabled": "Activé", @@ -150,6 +158,7 @@ "max": "Max", "min": "Min", "none": "Aucun", + "ok": "OK", "open": "Ouvrir", "open_code_editor": "Ouvrir l'éditeur de code", "open_file": "Ouvrir {{filename}}", @@ -172,8 +181,14 @@ "title": "Aperçu des données" }, "datatable": { + "cancel_cell": "Annuler la saisie", "create_column": "Créer la colonne", "duplicate_column": "Dupliquer la colonne", + "freeze_values": "Figer les valeurs", + "freeze_values_confirm": "Figer les valeurs", + "freeze_values_message": "Cela va supprimer le script et figer les valeurs calculées actuelles dans un attribut normal. Les valeurs ne pourront plus être recalculées. Voulez-vous continuer ?", + "freeze_values_success": "Les valeurs de « {{name}} » ont été figées.", + "freeze_values_title": "Figer les valeurs de « {{name}} » ?", "edges_matching": "{{count, number}} liens correspondent à la recherche", "edges_matching_one": "{{count, number}} lien correspond à la recherche", "edges_matching_zero": "Aucun lien correspond à la recherche", @@ -201,6 +216,7 @@ "targetId": "Cible ID" }, "save_and_create_column": "Enregistrer et créer la colonne", + "save_and_update_column": "Enregistrer et mettre à jour la colonne", "save_cell": "Enregistrer la cellule", "sort_asc": "Tri A→Z (0→9)", "sort_desc": "Tri Z→A (9→0)" @@ -227,7 +243,7 @@ "create_nodes_scripted_field_success": "L'attribut de nœud scripté a été créé.", "create_nodes_success": "Le nœud a été créé avec succès.", "data_creation": "Création de données", - "delete_attribute": "Supprimer l'attribut", + "delete_attribute": "Supprimer la colonne", "delete_attributes_success": "L'attribut '{{name}}' a bien été supprimé.", "delete_edges": "Supprimer {{count, number}} liens", "delete_edges_attributes": "Supprimer l'attribut '{{name}}' des liens", @@ -247,6 +263,8 @@ "delete_selected_nodes_one": "Supprimer le nœud sélectionné", "delete_this_edges": "Supprimer ce lien", "delete_this_nodes": "Supprimer ce nœud", + "duplicate_edge_warning": "{{count}} liens relient déjà ces deux nœuds :", + "duplicate_edge_warning_one": "Un lien relie déjà ces deux nœuds :", "edit_selected_edges": "Éditer les liens sélectionnés", "edit_selected_edges_one": "Éditer le lien sélectionné", "edit_selected_nodes": "Éditer les nœuds sélectionnés", @@ -261,9 +279,12 @@ "search_nodes": "Rechercher par libellé de nœud...", "selected_edges": "Liens sélectionnés seulement", "selected_nodes": "Nœuds sélectionnées seulement", + "similar_nodes_warning": "Des nœuds avec un nom similaire existent déjà :", "update_edges": "Éditer le lien existant", "update_edges_field": "Éditer l'attribut de liens", "update_edges_field_success": "L'attribut de lien a bien été mis à jour.", + "update_edges_scripted_field": "Éditer l'attribut de lien scripté", + "update_edges_scripted_field_success": "L'attribut de lien scripté a été mis à jour.", "update_edges_success": "Le lien a été mis à jour avec succès.", "update_multiple_edges": "Éditer les liens existants", "update_multiple_edges_success": "Les {{count,number}} liens ont bien été mis à jour.", @@ -272,6 +293,8 @@ "update_nodes": "Éditer le nœud existant", "update_nodes_field": "Éditer l'attribut des nœuds", "update_nodes_field_success": "L'attribut de nœud a bien été mis à jour.", + "update_nodes_scripted_field": "Éditer l'attribut de nœud scripté", + "update_nodes_scripted_field_success": "L'attribut de nœud scripté a été mis à jour.", "update_nodes_success": "Le nœud a été mis à jour avec succès.", "update_this_edges": "Éditer ce lien", "update_this_nodes": "Éditer ce nœud" @@ -284,6 +307,7 @@ "IMPORT_BAD_VERSION": "Votre fichier provient d'une version précédente de Gephi-Lite ({{version}}) qui n'est pas compatible avec la version courante", "form": { "field_already_exists": "La colonne avec le label \"{{label}}\" utilise déjà l'identifiant \"{{id}}\".", + "invalid_value": "Cette valeur n'est pas un « {{type}} » valide.", "max": "Le champ {{name}} doit être inférieur ou égal à {{max}}", "min": "Le champ {{name}} doit être supérieur ou égal à {{min}}", "required": "Le champ {{name}} est requis", @@ -306,6 +330,7 @@ "title": "Gestion des données" }, "filters": { + "active": "Des filtres sont actifs - cliquer pour ouvrir le panneau Filtres", "activated": "Filtre activé", "add_filter": "Ajouter un filtre", "create": { @@ -326,10 +351,13 @@ "edges_fields": "Attributs des liens", "filter": "Filtre", "full_graph": "Graphe entier", + "inactive": "Aucun filtre actif - cliquer pour ouvrir le panneau Filtres", "inapplicable": "Filtre désactivé : une seule valeur disponible.", "insert": "Insérer un filtre", "intermediate_graph": "Réseau intermédiaire", "keepMissingValues": "Conserver les valeurs manquantes", + "move_down": "Descendre le filtre", + "move_up": "Monter le filtre", "noValueOption": "Pas de valeur", "no_concurrent_edit": "Un seul filtre peut être modifié à la fois. Confirmez l'édition en cours pour modifier ce filtre.", "nodes_fields": "Attributs des nœuds", @@ -443,7 +471,8 @@ "edges_zero": "lien", "field": { "date-format": "Format de date", - "id": "Identifiant", + "default_value": "Valeur par défaut", + "id": "ID", "label": "Label", "separator": "Séparateur de mots-clefs", "type": "Type", @@ -452,6 +481,7 @@ "graph": "graph", "mixed": "Mixte", "mixed_graph": "Réseau mixte", + "most_recently_updated": "Modifié le plus récemment", "no_attributes": "Il n'y a pas encore d'attribut dans le modèle.", "nodes": "nœuds", "nodes-data": { @@ -507,6 +537,16 @@ }, "title": "Ouvrir un fichier de graphe" }, + "remote_changed": { + "keep": "Conserver mes modifications", + "message": "Le fichier « {{filename}} » a été modifié sur GitHub (par un autre utilisateur ou une autre session) depuis que vous l'avez ouvert. Si vous continuez à l'éditer puis l'enregistrez, vous écraserez ces modifications. Voulez-vous recharger la version à jour ? Vos modifications locales seront perdues.", + "reload": "Recharger depuis GitHub", + "overwrite": "Écraser", + "reload_error": "Une erreur s'est produite lors du rechargement du fichier depuis GitHub.", + "reload_success": "Le fichier « {{filename}} » a été rechargé depuis GitHub.", + "save_conflict_message": "Le fichier « {{filename}} » a été modifié sur GitHub (par un autre utilisateur ou une autre session) depuis que vous l'avez ouvert. Vous pouvez recharger la version à jour (vos modifications locales seront perdues), l'écraser avec votre version, ou conserver vos modifications sans enregistrer.", + "title": "Ce graphe a changé sur GitHub" + }, "partitioning": { "description": "Pour appliquer l'apparence ou les filtres différemment sur différents groupes de {{items}}, partitionnez votre graphe en utilisant n'importe quel attribut qualitatif des nœuds.", "missing_partition": "Ils sont regroupés dans une partition 'valeur manquante'.", @@ -891,6 +931,7 @@ "github": "GitHub" }, "search": { + "clear": "Vider la recherche", "edges": { "help": "Écrire quelque chose pour trouver un lien", "no_result": "Aucun lien trouvée", @@ -915,6 +956,7 @@ } }, "selection": { + "create_edge_from_node": "Créer un lien depuis ce nœud", "cursor": "Sélection simple", "edge_no_label": "(libellé vide)", "edges": "{{count, number}} liens sélectionnés", @@ -922,18 +964,21 @@ "edges_zero": "Aucun lien sélectionné", "empty_edges": "Ce lien n'a pas d'attribut", "empty_nodes": "Ce nœud n'a pas d'attribut", + "filtered": "{{count, number}} filtrés", + "filtered_edges": "{{count, number}} liens filtrés", + "filtered_edges_one": "{{count, number}} lien filtré", + "filtered_nodes": "{{count, number}} nœuds filtrés", + "filtered_nodes_one": "{{count, number}} nœud filtré", + "filtered_one": "{{count, number}} filtré", "focus_edges": "Sélectionner uniquement ce lien", "focus_nodes": "Sélectionner uniquement ce nœud", - "hidden": "{{count, number}} cachés", - "hidden_edges": "{{count, number}} liens sélectionnés masqués", - "hidden_edges_one": "Un lien sélectionné masqué", - "hidden_nodes": "{{count, number}} nœuds sélectionnés masqués", - "hidden_nodes_one": "Un nœud sélectionné masqué", - "hidden_one": "un caché", - "hidden_zero": "aucun caché", + "hide_panel": "Masquer le volet de sélection (conserve la sélection)", "lasso": "Sélection par lasso", "locate_on_graph": "Localiser sur le graphe", + "locate_selected_edges": "Zoomer sur tous les liens sélectionnés", + "locate_selected_nodes": "Zoomer sur tous les nœuds sélectionnés", "marquee": "Sélection par rectangle", + "no_path_between_nodes": "Aucun chemin ne relie ces deux nœuds", "no_value": "Pas de valeur", "node_no_label": "(libellé vide)", "nodes": "{{count, number}} nœuds sélectionnés", @@ -942,20 +987,22 @@ "open_in_data": "Voir dans Données", "open_in_graph": "Voir dans le Réseau", "select_all": "Sélectionner tout", + "select_node_edges": "Sélectionner les liens du nœud", "select_node_neighbors": "Sélectionner les voisins du nœud", + "select_path_between_nodes": "Sélectionner les liens d'un plus court chemin entre les deux nœuds", + "selected_edges": "Liens sélectionnés", + "selected_nodes": "Nœuds sélectionnés", + "sort": { + "alphabetical": "Tri alphabétique - cliquer pour trier par taille décroissante", + "size": "Tri par taille décroissante - cliquer pour trier alphabétiquement" + }, "unselect_all": "Tout désélectionner", "unselect_all_hidden_edges": "Tout désélectionner pour les liens masqués", "unselect_all_hidden_nodes": "Tout désélectionner pour les nœuds masqués", "unselect_edges": "Désélectionner ce lien", "unselect_nodes": "Désélectionner ce nœud", "visible": "{{count, number}} visibles", - "visible_edges": "{{count, number}} liens sélectionnés visibles", - "visible_edges_one": "Un lien sélectionné visible", - "visible_edges_zero": "Aucun lien sélectionné visible", - "visible_nodes": "{{count, number}} nœuds sélectionnés visibles", - "visible_nodes_one": "Un nœud sélectionné visible", - "visible_nodes_zero": "Aucun nœud sélectionné visible", - "visible_one": "un visible", + "visible_one": "{{count, number}} visible", "visible_zero": "aucun visible" }, "settings": { @@ -994,6 +1041,7 @@ "website": "Site internet" }, "workspace": { + "confirm_leave_unsaved": "Votre graphe a des modifications non enregistrées. Quitter sans enregistrer ?", "menu": { "duplicate": "Dupliquer dans un nouvel onglet", "export_graph_file": "Exporter en fichier graph", @@ -1003,7 +1051,8 @@ "new": "Créer un nouvel espace de travail", "open": "Ouvrir ...", "save": "Enregistrer", - "save_as": "Enregistrer sous..." + "save_as": "Enregistrer sous...", + "unsaved_changes": "Modifications non enregistrées" } } } diff --git a/packages/gephi-lite/src/locales/hu.json b/packages/gephi-lite/src/locales/hu.json index 0bbdb194..7eb7fae2 100644 --- a/packages/gephi-lite/src/locales/hu.json +++ b/packages/gephi-lite/src/locales/hu.json @@ -650,6 +650,7 @@ } }, "selection": { + "create_edge_from_node": "Él létrehozása ebből a csomópontból", "edge_no_label": "(Üres címke)", "edges": "{{count, number}} kiválasztott él", "edges_one": "Egy kiválasztott él", @@ -663,11 +664,15 @@ "hidden_nodes": "{{count, number}} rejtett kiválasztott csomópont", "hidden_nodes_one": "Egy rejtett kiválasztott csomópont", "locate_on_graph": "Keresse meg a grafikonon", + "locate_selected_edges": "Nagyítás az összes kijelölt élre", "node_no_label": "(Üres címke)", "nodes": "{{count, number}} kiválasztott csomópont", "nodes_one": "Egy kiválasztott csomópont", "nodes_zero": "Nincs kiválasztott csomópont", "select_all": "Mindet kiválaszt", + "select_node_edges": "Csomópont éleinek kiválasztása", + "selected_edges": "Kijelölt élek", + "selected_nodes": "Kijelölt csomópontok", "unselect_all": "Minden kijelölés megszüntetése", "unselect_all_hidden_edges": "Törölje az összes rejtett él kijelölését", "unselect_all_hidden_nodes": "Törölje az összes rejtett él kijelölését", diff --git a/packages/gephi-lite/src/locales/ko.json b/packages/gephi-lite/src/locales/ko.json index 307ab564..b08af064 100644 --- a/packages/gephi-lite/src/locales/ko.json +++ b/packages/gephi-lite/src/locales/ko.json @@ -650,6 +650,7 @@ } }, "selection": { + "create_edge_from_node": "이 노드에서 엣지 만들기", "edge_no_label": "(빈 라벨)", "edges": "{{count, number}} 개의 선택된 엣지", "edges_one": "1개의 선택된 엣지", @@ -663,11 +664,15 @@ "hidden_nodes": "{{count, number}} 개의 선택된 숨김 노드", "hidden_nodes_one": "1개의 선택된 숨김 노드", "locate_on_graph": "그래프에서 찾기", + "locate_selected_edges": "선택한 모든 엣지로 확대", "node_no_label": "(빈 라벨)", "nodes": "{{count, number}} 개의 선택된 노드", "nodes_one": "1개의 선택된 노드", "nodes_zero": "선택된 노드 없음", "select_all": "전체 선택", + "select_node_edges": "노드의 엣지 선택", + "selected_edges": "선택된 엣지", + "selected_nodes": "선택된 노드", "unselect_all": "전체 선택 해제", "unselect_all_hidden_edges": "전체 숨김 엣지 선택 해제", "unselect_all_hidden_nodes": "전체 숨김 엣지 선택 해제", diff --git a/packages/gephi-lite/src/locales/uk.json b/packages/gephi-lite/src/locales/uk.json index 0540e870..222092d3 100644 --- a/packages/gephi-lite/src/locales/uk.json +++ b/packages/gephi-lite/src/locales/uk.json @@ -395,6 +395,7 @@ "edges_zero": "край", "field": { "date-format": "Формат дати", + "default_value": "Значення за замовчуванням", "dates_formats_link_message": "Дізнайтеся більше про парсинг токенів Luxon", "dates_formats_message": "Дати аналізуються за допомогою бібліотеки JavaScript з відкритим кодом Luxon.", "id": "Ідентифікатор", @@ -831,6 +832,7 @@ } }, "selection": { + "create_edge_from_node": "Створити ребро з цього вузла", "cursor": "Простий вибір", "edge_no_label": "(Порожня етикетка)", "edges": "{{count, number}} вибрані краї", @@ -849,6 +851,7 @@ "hidden_zero": "немає прихованих", "lasso": "Вибір ласо", "locate_on_graph": "Знайдіть на графіку", + "locate_selected_edges": "Масштабувати до всіх вибраних зв'язків", "marquee": "Вибір прямокутника", "no_value": "Без значення", "node_no_label": "(Порожня етикетка)", @@ -858,7 +861,10 @@ "open_in_data": "Показати в даних", "open_in_graph": "Показати на графіку", "select_all": "Вибрати все", + "select_node_edges": "Вибрати ребра вузла", "select_node_neighbors": "Вибрати сусідів вузла", + "selected_edges": "Вибрані ребра", + "selected_nodes": "Вибрані вузли", "unselect_all": "Зняти вибір усіх", "unselect_all_hidden_edges": "Зняти вибір із усіх прихованих країв", "unselect_all_hidden_nodes": "Зняти вибір із усіх прихованих країв", diff --git a/packages/gephi-lite/src/styles/_base.scss b/packages/gephi-lite/src/styles/_base.scss index 0a5f2026..195a89c1 100644 --- a/packages/gephi-lite/src/styles/_base.scss +++ b/packages/gephi-lite/src/styles/_base.scss @@ -69,6 +69,15 @@ .over-modal { z-index: $zindex-modal + 1 !important; } +// A confirmation dialog stacked over an already open modal (see the Modal component): its own +// backdrop has to cover the modal underneath, and the dialog itself to sit above that backdrop - +// hence two levels rather than the single one `.over-modal` provides for tooltips and dropdowns. +.stacked-modal-backdrop { + z-index: $zindex-modal + 1 !important; +} +.stacked-modal { + z-index: $zindex-modal + 2 !important; +} .rectangle { height: 1em; width: 0.25em; diff --git a/packages/gephi-lite/src/styles/_data-table.scss b/packages/gephi-lite/src/styles/_data-table.scss index 1d545adb..7e10302c 100644 --- a/packages/gephi-lite/src/styles/_data-table.scss +++ b/packages/gephi-lite/src/styles/_data-table.scss @@ -167,7 +167,12 @@ $table-editable-columns-cell-hover-bg: color-mix( form { display: flex; flex-direction: row; - align-items: center; + // Align to the top, not center: the actions column (check + cancel, stacked) is taller than + // the single-line field editor. Centering it would place the field's controls (eg. the number + // stepper's "+") level with the gap between check and cancel, right in the way of a misclick + // toward cancel. Top-aligning instead lines the field up with the check button, leaving cancel + // clearly below the whole field. + align-items: flex-start; gap: $gl-spacers-2; & > *:first-child { @@ -183,6 +188,15 @@ $table-editable-columns-cell-hover-bg: color-mix( gap: $gl-spacers-2; } } + + // Save (top) and cancel (bottom) buttons, stacked with extra gap so the cancel button - a + // destructive-ish action that discards the edit - isn't at risk of being clicked by mistake right + // after the save button. + .data-cell-edition-actions { + display: flex; + flex-direction: column; + gap: $gl-spacers-3; + } } .data-cell { diff --git a/packages/gephi-lite/src/styles/_data.scss b/packages/gephi-lite/src/styles/_data.scss index fe04ec7e..1143f8da 100644 --- a/packages/gephi-lite/src/styles/_data.scss +++ b/packages/gephi-lite/src/styles/_data.scss @@ -20,6 +20,24 @@ } } +// Shared style for "locate" buttons that wrap a node/edge preview (data table, edge cards…): +// resets the native button styling so only the preview shows, and hints clickability on hover. +.gl-locate-button { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + border: none; + background: none; + text-align: left; + color: inherit; + cursor: pointer; + + &:hover span:last-child { + text-decoration: underline; + } +} + .edge-component { display: flex; flex-direction: column; diff --git a/packages/gephi-lite/src/styles/_forms.scss b/packages/gephi-lite/src/styles/_forms.scss index 2a52c6a9..81dbc203 100644 --- a/packages/gephi-lite/src/styles/_forms.scss +++ b/packages/gephi-lite/src/styles/_forms.scss @@ -9,3 +9,22 @@ box-shadow: none; } } + +// Number input with custom −/+ stepper buttons. We hide the browser's native spin buttons (which, +// besides, are not rendered at all on mobile browsers) so only the custom buttons remain, giving a +// consistent stepper on every platform. +.gl-number-input { + flex-wrap: nowrap; + + input[type="number"] { + appearance: textfield; + -moz-appearance: textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + appearance: none; + -webkit-appearance: none; + margin: 0; + } + } +} diff --git a/packages/gephi-lite/src/styles/_header.scss b/packages/gephi-lite/src/styles/_header.scss index 1ed718a8..b1a4b876 100644 --- a/packages/gephi-lite/src/styles/_header.scss +++ b/packages/gephi-lite/src/styles/_header.scss @@ -2,4 +2,11 @@ header { .lang-switcher-btn { line-height: 1 !important; } + + // Small badge flagging unsaved changes, shown right next to the save button: + .gl-unsaved-changes-star { + display: inline-flex; + color: var(--bs-warning, #f0ad4e); + font-size: 0.6em; + } } diff --git a/packages/gephi-lite/src/styles/_preprod.scss b/packages/gephi-lite/src/styles/_preprod.scss new file mode 100644 index 00000000..65df8e24 --- /dev/null +++ b/packages/gephi-lite/src/styles/_preprod.scss @@ -0,0 +1,31 @@ +// Pre-production badge (shown only when the app is built with VITE_PREPRODUCTION). +// Anchored to the top: on mobile, the header/toolbar is pinned to the bottom of the viewport +// (#root uses flex-direction: column-reverse there, see _base.scss), so the graph fills the top +// and a bottom-anchored badge used to collide with the zoom/selection controls (bottom-right of +// the graph canvas). The top of the graph has nothing else on it, on mobile or desktop. +.preprod-banner { + position: fixed; + z-index: 3000; + left: 50%; + top: 0.5rem; + transform: translateX(-50%); + pointer-events: none; // purely informative: never intercept clicks/taps + + padding: 0.15rem 0.85rem; + border-radius: 999px; + background: #e67e22; + color: #fff; + font-weight: 700; + font-size: 0.7rem; + letter-spacing: 0.12em; + white-space: nowrap; + box-shadow: 0 1px 4px rgb(0 0 0 / 35%); +} + +// On tablet/desktop, the header sits at the top of the viewport instead (normal flex order): +// push the banner below it so it doesn't overlap the Workspace/Graph/Data toolbar. +@include media-breakpoint-up(sm) { + .preprod-banner { + top: 3rem; + } +} diff --git a/packages/gephi-lite/src/styles/index.scss b/packages/gephi-lite/src/styles/index.scss index 21d2eea1..b8ee425d 100644 --- a/packages/gephi-lite/src/styles/index.scss +++ b/packages/gephi-lite/src/styles/index.scss @@ -72,6 +72,7 @@ @import "menu"; @import "side-menu"; @import "toasts"; +@import "preprod"; @import "dark"; @import "modals"; @import "header"; diff --git a/packages/gephi-lite/src/utils/search.ts b/packages/gephi-lite/src/utils/search.ts index bf1472fd..587f3a0e 100644 --- a/packages/gephi-lite/src/utils/search.ts +++ b/packages/gephi-lite/src/utils/search.ts @@ -12,12 +12,14 @@ export function doesItemMatch( data: ItemData, fields: FieldModel[], query: string, + extraLabels: (string | null | undefined)[] = [], ): boolean { const normalizedQuery = normalize(query); return ( normalize(id).includes(normalizedQuery) || (label && normalize(label).includes(normalizedQuery)) || + extraLabels.some((extraLabel) => extraLabel && normalize(extraLabel).includes(normalizedQuery)) || fields.some( ({ type, id }) => SEARCHABLE_TYPES.has(type) && typeof data[id] === "string" && normalize(data[id]).includes(normalizedQuery), diff --git a/packages/gephi-lite/src/views/dataPage/TopBar.tsx b/packages/gephi-lite/src/views/dataPage/TopBar.tsx index adb7814b..640167b5 100644 --- a/packages/gephi-lite/src/views/dataPage/TopBar.tsx +++ b/packages/gephi-lite/src/views/dataPage/TopBar.tsx @@ -103,7 +103,7 @@ export const TopBar: FC = () => { @@ -117,7 +117,10 @@ export const TopBar: FC = () => { {" "} ( ,{" "} - ) + + + + ) )} {visible.length > 0 && t("common.colon")} @@ -129,7 +132,7 @@ export const TopBar: FC = () => { disabled={selectionActionDisabled} title={t("selection.open_in_graph")} onClick={() => { - navigate(`/`); + navigate(`/`, { replace: true }); emitter.once(EVENTS.sigmaMounted, () => { emitter.emit(EVENTS.focusNodes, { nodes: diff --git a/packages/gephi-lite/src/views/dataPage/dataTable/DataCell.tsx b/packages/gephi-lite/src/views/dataPage/dataTable/DataCell.tsx index b8259519..94d8cc44 100644 --- a/packages/gephi-lite/src/views/dataPage/dataTable/DataCell.tsx +++ b/packages/gephi-lite/src/views/dataPage/dataTable/DataCell.tsx @@ -1,13 +1,27 @@ import { FieldModel, ItemType, Scalar } from "@gephi/gephi-lite-sdk"; import cx from "classnames"; import { isNil } from "lodash"; -import { FC, MouseEventHandler, forwardRef, useEffect, useRef, useState } from "react"; +import { FC, MouseEventHandler, forwardRef, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { PiCheck } from "react-icons/pi"; import TetherComponent from "react-tether"; +import { CancelIcon } from "../../../components/common-icons"; import { EditItemAttribute, RenderItemAttribute } from "../../../components/data/Attribute"; import { useGraphDatasetActions } from "../../../core/context/dataContexts"; +import { CellDirection, useDataCellNavigation } from "./dataCellNavigation"; + +// Arrow-key navigation is only taken over for field types where arrow keys have no built-in meaning +// of their own: overriding them for "category"/"keywords" would break react-select's own option +// navigation, and for "color" the color picker's own key handling. +const ARROW_NAV_FIELD_TYPES: FieldModel["type"][] = ["text", "url", "number", "date"]; + +const ARROW_KEY_DIRECTIONS: Record = { + ArrowUp: "up", + ArrowDown: "down", + ArrowLeft: "left", + ArrowRight: "right", +}; export const ReadDataCell = forwardRef< HTMLSpanElement, @@ -56,16 +70,29 @@ export const EditDataCell: FC<{ id: string; field: FieldModel; value: Scalar; + columnId: string; close: () => void; -}> = ({ type, id, field, close, value: initialValue }) => { +}> = ({ type, id, field, columnId, close, value: initialValue }) => { const { t } = useTranslation(); const [value, setValue] = useState(initialValue); const { updateNode, updateEdge } = useGraphDatasetActions(); const update = type === "nodes" ? updateNode : updateEdge; + const { moveTo } = useDataCellNavigation(); const targetWrapper = useRef(null); const elementWrapper = useRef(null); + const commit = useCallback( + (committedValue: Scalar) => { + if (field.dynamic) { + update(id, {}, { merge: true, [field.id]: committedValue }); + } else { + update(id, { [field.id]: committedValue }, { merge: true }); + } + }, + [field, id, update], + ); + // Handle interactions: useEffect(() => { const handleClickBody = (e: MouseEvent) => { @@ -107,23 +134,37 @@ export const EditDataCell: FC<{ ref={elementWrapper} onSubmit={(e) => { e.preventDefault(); - if (field.dynamic) { - update(id, {}, { merge: true, [field.id]: value }); - } else { - update(id, { [field.id]: value }, { merge: true }); - } + commit(value); close(); }} onKeyDown={(e) => { if (e.key === "Escape") { close(); + return; + } + const direction = ARROW_KEY_DIRECTIONS[e.key]; + if (direction && ARROW_NAV_FIELD_TYPES.includes(field.type)) { + e.preventDefault(); + commit(value); + close(); + moveTo({ rowId: id, columnId }, direction); } }} > setValue(value)} /> - +
    + + +
    )} @@ -137,12 +178,25 @@ export const DataCell: FC<{ field: FieldModel; value: Scalar; readOnly?: boolean; + columnId: string; }> = (props) => { - const { readOnly } = props; + const { readOnly, id, columnId, field } = props; const [isEditing, setIsEditing] = useState(false); + const { activeCell, clearActiveCell } = useDataCellNavigation(); + + // Auto-enter edit mode when arrow-key navigation (see EditDataCell) targets this cell - including + // when it was just mounted after being scrolled into view (see DataTable's moveTo). Clearing the + // request right after consuming it keeps it from lingering: react-virtual recycles row DOM nodes, + // so an un-cleared request could pop this same cell back into edit mode after an unrelated remount. + useEffect(() => { + if (!readOnly && activeCell?.rowId === id && activeCell?.columnId === columnId) { + setIsEditing(true); + clearActiveCell(); + } + }, [activeCell, readOnly, id, columnId, clearActiveCell]); // Editable boolean fields are directly edited inline: - if (props.field.type === "boolean" && !readOnly) { + if (field.type === "boolean" && !readOnly) { return ; } diff --git a/packages/gephi-lite/src/views/dataPage/dataTable/DataTable.tsx b/packages/gephi-lite/src/views/dataPage/dataTable/DataTable.tsx index 4af216ef..4eb06706 100644 --- a/packages/gephi-lite/src/views/dataPage/dataTable/DataTable.tsx +++ b/packages/gephi-lite/src/views/dataPage/dataTable/DataTable.tsx @@ -2,7 +2,7 @@ import { ItemType, Scalar } from "@gephi/gephi-lite-sdk"; import { Row, flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table"; import { VirtualItem, Virtualizer, useVirtualizer } from "@tanstack/react-virtual"; import cx from "classnames"; -import { FC, useEffect, useMemo, useRef } from "react"; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ScrollSyncPane } from "react-scroll-sync"; import { @@ -15,6 +15,7 @@ import { import { EVENTS, useEventsContext } from "../../../core/context/eventsContext"; import { DynamicEdgeAttributeId, DynamicNodeAttributeId } from "../../../core/graph/dynamicAttributes"; import { ItemRow, SPECIFIC_COLUMNS, getCommonPinningStyles } from "./consts"; +import { CellCoord, DataCellNavigationContext } from "./dataCellNavigation"; import { useDataTableColumns } from "./useDataTableColumns"; const TableBodyRow: FC<{ @@ -48,7 +49,7 @@ export const DataTable: FC<{ itemIDs: string[] }> = ({ itemIDs }) => { const { type, dataTableState } = useDataTable(); const { updateColumnSizing, updateColumnSizingInfo, setSort } = useDataTableActions(); - const { columns, columnPinningState } = useDataTableColumns(itemIDs); + const { columns, columnPinningState, editableColumnIds } = useDataTableColumns(itemIDs); const data = useMemo(() => (type === "nodes" ? nodeData : edgeData), [edgeData, nodeData, type]); const dynamicData = useMemo( @@ -110,6 +111,33 @@ export const DataTable: FC<{ itemIDs: string[] }> = ({ itemIDs }) => { : undefined, }); + // Arrow-key navigation while editing a cell (see DataCell/EditDataCell): up/down keep the same + // column and move to the previous/next row (using the current sorted row order, so it works + // whichever row is currently virtualized in or out), left/right keep the same row and move to the + // previous/next editable column. Setting activeCell is enough even when the target row isn't + // currently mounted: the scroll below makes react-virtual mount it, and DataCell picks up the + // matching activeCell as soon as it renders. + const [activeCell, setActiveCell] = useState(null); + const moveTo = useCallback( + (from: CellCoord, direction: "up" | "down" | "left" | "right") => { + if (direction === "up" || direction === "down") { + const rowIds = table.getRowModel().rows.map((r) => r.id); + const index = rowIds.indexOf(from.rowId); + const nextIndex = direction === "up" ? index - 1 : index + 1; + if (index === -1 || nextIndex < 0 || nextIndex >= rowIds.length) return; + rowVirtualizer.scrollToIndex(nextIndex, { align: "auto" }); + setActiveCell({ rowId: rowIds[nextIndex], columnId: from.columnId }); + } else { + const index = editableColumnIds.indexOf(from.columnId); + const nextIndex = direction === "left" ? index - 1 : index + 1; + if (index === -1 || nextIndex < 0 || nextIndex >= editableColumnIds.length) return; + setActiveCell({ rowId: from.rowId, columnId: editableColumnIds[nextIndex] }); + } + }, + [table, rowVirtualizer, editableColumnIds], + ); + const clearActiveCell = useCallback(() => setActiveCell(null), []); + useEffect(() => { const scrollToID = (id: string) => { const sortedRows = table.getRowModel().rows; @@ -144,68 +172,70 @@ export const DataTable: FC<{ itemIDs: string[] }> = ({ itemIDs }) => { }, [emitter, rowVirtualizer, setSort, table, type]); return ( -
    - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - + ); + })} + + ))} + + + {rowVirtualizer.getVirtualItems().map((virtualRow) => ( + + ))} + +
    -
    +
    + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + - ); - })} - - ))} - - - {rowVirtualizer.getVirtualItems().map((virtualRow) => ( - - ))} - -
    - {flexRender(header.column.columnDef.header, header.getContext())} +
    + {flexRender(header.column.columnDef.header, header.getContext())} - {header.column.getCanResize() && ( -
    header.column.resetSize()} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseDown={(e) => { - e.preventDefault(); - header.getResizeHandler()(e); - }} - /> - )} -
    -
    -
    -
    + {header.column.getCanResize() && ( +
    header.column.resetSize()} + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + onMouseDown={(e) => { + e.preventDefault(); + header.getResizeHandler()(e); + }} + /> + )} +
    +
    +
    +
    + ); }; diff --git a/packages/gephi-lite/src/views/dataPage/dataTable/consts.tsx b/packages/gephi-lite/src/views/dataPage/dataTable/consts.tsx index 37503ca9..fcbbaad5 100644 --- a/packages/gephi-lite/src/views/dataPage/dataTable/consts.tsx +++ b/packages/gephi-lite/src/views/dataPage/dataTable/consts.tsx @@ -16,6 +16,8 @@ export const SPECIFIC_COLUMNS = { preview: "preview", sourceId: "sourceId", targetId: "targetId", + sourceLabel: "sourceLabel", + targetLabel: "targetLabel", // add dynamic ...mapValues(DYNAMIC_ATTRIBUTES.nodes, identity), ...mapValues(DYNAMIC_ATTRIBUTES.edges, identity), diff --git a/packages/gephi-lite/src/views/dataPage/dataTable/dataCellNavigation.ts b/packages/gephi-lite/src/views/dataPage/dataTable/dataCellNavigation.ts new file mode 100644 index 00000000..790c4e75 --- /dev/null +++ b/packages/gephi-lite/src/views/dataPage/dataTable/dataCellNavigation.ts @@ -0,0 +1,26 @@ +import { createContext, useContext } from "react"; + +export type CellCoord = { rowId: string; columnId: string }; +export type CellDirection = "up" | "down" | "left" | "right"; + +export type DataCellNavigationApi = { + // The cell that should switch to edit mode, set by moveTo below. Read by DataCell to auto-enter + // edit mode once the target row/column is (re)rendered, which also covers the case where the + // target row was virtualized out and gets mounted only after the requested scroll completes. + activeCell: CellCoord | null; + // Moves from a cell being edited to its neighbor (up/down: same column, previous/next row; + // left/right: same row, previous/next editable column) and requests edit mode there. A no-op at + // the table's edges (first/last row or column) or when the origin cell can't be located anymore. + moveTo: (from: CellCoord, direction: CellDirection) => void; + // Must be called by the DataCell that consumes activeCell (ie. the one that just auto-entered edit + // mode because of it), so the request doesn't linger: react-virtual recycles row DOM nodes while + // scrolling, so without this, remounting that same row/column later would find the stale activeCell + // still matching and pop it back into edit mode out of nowhere. + clearActiveCell: () => void; +}; + +const noop: DataCellNavigationApi = { activeCell: null, moveTo: () => {}, clearActiveCell: () => {} }; + +export const DataCellNavigationContext = createContext(noop); + +export const useDataCellNavigation = () => useContext(DataCellNavigationContext); diff --git a/packages/gephi-lite/src/views/dataPage/dataTable/useDataTableColumns.tsx b/packages/gephi-lite/src/views/dataPage/dataTable/useDataTableColumns.tsx index 52835403..d84ccf66 100644 --- a/packages/gephi-lite/src/views/dataPage/dataTable/useDataTableColumns.tsx +++ b/packages/gephi-lite/src/views/dataPage/dataTable/useDataTableColumns.tsx @@ -1,12 +1,13 @@ -import { DynamicItemDataSpec, ItemType } from "@gephi/gephi-lite-sdk"; +import { DynamicItemDataSpec, FieldModel, ItemType, Scalar } from "@gephi/gephi-lite-sdk"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; -import { isBoolean, size, values } from "lodash"; +import { isBoolean, mapValues, size, values } from "lodash"; import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import Dropdown from "../../../components/Dropdown"; import { ThreeDotsVerticalIcon } from "../../../components/common-icons"; import { AttributeLabel } from "../../../components/data/Attribute"; +import { CreateScriptedFieldModelModal } from "../../../components/data/CreateScriptedFieldModel"; import { EdgeComponentById } from "../../../components/data/Edge"; import { EditFieldModelModal } from "../../../components/data/EditFieldModel"; import { NodeComponentById } from "../../../components/data/Node"; @@ -14,28 +15,73 @@ import ConfirmModal from "../../../components/modals/ConfirmModal"; import { useDataTable, useDataTableActions, + useDynamicItemData, useGraphDataset, useGraphDatasetActions, useSelectionActions, + useVisualGetters, } from "../../../core/context/dataContexts"; -import { DYNAMIC_ATTRIBUTES } from "../../../core/graph/dynamicAttributes"; +import { + DYNAMIC_ATTRIBUTES, + computeScriptFieldsData, + mergeStaticDynamicData, +} from "../../../core/graph/dynamicAttributes"; +import { dataGraphToFullGraph } from "../../../core/graph/utils"; import { useModal } from "../../../core/modals"; import { useMobile } from "../../../hooks/useMobile"; import { DataCell } from "./DataCell"; -import { Arrow, ItemRow, SPECIFIC_COLUMNS } from "./consts"; +import { Arrow, EdgeItemRow, ItemRow, SPECIFIC_COLUMNS } from "./consts"; export const useDataTableColumns = (itemIDs: string[]) => { const { t } = useTranslation(); const { type } = useDataTable(); const { openModal } = useModal(); - const { nodeFields, edgeFields, nodeData, edgeData, fullGraph } = useGraphDataset(); + const dataset = useGraphDataset(); + const { nodeFields, edgeFields, nodeData, edgeData, fullGraph } = dataset; + const { dynamicNodeData } = useDynamicItemData(); + const { getNodeLabel } = useVisualGetters(); const isMobile = useMobile(); + const nodeAllData = useMemo(() => mergeStaticDynamicData(nodeData, dynamicNodeData), [nodeData, dynamicNodeData]); const { setSort } = useDataTableActions(); const { toggle, select, unselect } = useSelectionActions(); - const { moveFieldModel, deleteFieldModel, duplicateFieldModel } = useGraphDatasetActions(); + const { moveFieldModel, deleteFieldModel, duplicateFieldModel, setFieldModel } = useGraphDatasetActions(); + + // "Freeze" a formula (scripted) field: recompute its current values over the whole graph, then + // replace it by a static (non-scripted) field carrying those values. It becomes a normal, + // editable attribute whose values will no longer be recomputed. + const freezeScriptedField = useCallback( + (field: FieldModel) => { + const graph = dataGraphToFullGraph(dataset); + const computed = computeScriptFieldsData(field.itemType, [field], graph); + const frozenValues: Record = mapValues(computed, (fieldValues) => fieldValues[field.id]); + setFieldModel({ ...field, script: undefined }, frozenValues); + }, + [dataset, setFieldModel], + ); const fields = useMemo(() => (type === "nodes" ? nodeFields : edgeFields), [edgeFields, nodeFields, type]); + + // Ordered (left-to-right, matching the `columns` below) ids of the columns arrow-key navigation + // can land on: editable dataset fields and editable dynamic attributes, excluding boolean ones + // (edited inline via a checkbox, with no separate edit session to navigate in/out of - see + // DataCell). Non-editable/protected columns (id, preview, selected...) are deliberately excluded + // too, so left/right silently hops over them instead of getting stuck. + const editableColumnIds = useMemo(() => { + const dynamicSpecs = + type === "nodes" + ? values(DYNAMIC_ATTRIBUTES.nodes) + : values(DYNAMIC_ATTRIBUTES.edges).filter((f) => + isBoolean(f.showInDataTable) ? f.showInDataTable : f.showInDataTable(fullGraph), + ); + const dynamicIds = dynamicSpecs + .filter((f) => f.editable && f.field.type !== "boolean") + .map((f) => `dynamic::${f.field.id}`); + const fieldIds = fields + .filter((f) => !f.readOnly && !f.script && f.type !== "boolean") + .map((f) => `field::${f.id}`); + return [...dynamicIds, ...fieldIds]; + }, [type, fields, fullGraph]); const columnHelper = useMemo(() => createColumnHelper(), []); const getSpecificRow = useCallback( (field: keyof ItemRow, options?: { size?: number; label?: string }): ColumnDef => { @@ -80,6 +126,7 @@ export const useDataTableColumns = (itemIDs: string[]) => { field={dynamicField.field} value={props.row.getValue(`dynamic::${dynamicField.field.id}`)} readOnly={!dynamicField.editable} + columnId={`dynamic::${dynamicField.field.id}`} /> ), meta: { @@ -108,6 +155,41 @@ export const useDataTableColumns = (itemIDs: string[]) => { }, [t, type], ); + const getExtremityLabelColumn = useCallback( + (extremity: "sourceId" | "targetId", field: "sourceLabel" | "targetLabel"): ColumnDef => { + return { + id: field, + accessorFn: (row) => { + const nodeId = (row as EdgeItemRow)[extremity]; + return (getNodeLabel && getNodeLabel(nodeAllData[nodeId], nodeId)) || nodeId; + }, + cell: (props) => {props.row.getValue(field)}, + meta: { + protected: true, + }, + header: ({ header }) => ( + <> + + {t(`datatable.protected_columns.${field}`)} + + + ( +
    + +
    + )} + /> + + ), + size: 180, + }; + }, + [t, getNodeLabel, nodeAllData], + ); const columns = useMemo[]>( () => [ // Agnostic columns; @@ -175,28 +257,31 @@ export const useDataTableColumns = (itemIDs: string[]) => { }, cell: (props) => type === "nodes" ? ( - + ) : ( - + ), }), + // Source/target node labels, right after the preview column: + ...(type === "edges" + ? [getExtremityLabelColumn("sourceId", "sourceLabel"), getExtremityLabelColumn("targetId", "targetLabel")] + : []), + // Type specific dynamic / read-only columns: - getSpecificRow("id"), ...(type === "nodes" ? values(DYNAMIC_ATTRIBUTES.nodes).map((f) => getDynamicColumn(f)) - : [ - getSpecificRow(SPECIFIC_COLUMNS.sourceId as keyof ItemRow), - getSpecificRow(SPECIFIC_COLUMNS.targetId as keyof ItemRow), - ...values(DYNAMIC_ATTRIBUTES.edges) - .filter((f) => (isBoolean(f.showInDataTable) ? f.showInDataTable : f.showInDataTable(fullGraph))) - .map((f) => getDynamicColumn(f)), - ]), + : values(DYNAMIC_ATTRIBUTES.edges) + .filter((f) => (isBoolean(f.showInDataTable) ? f.showInDataTable : f.showInDataTable(fullGraph))) + .map((f) => getDynamicColumn(f))), // Dataset-specific columns; ...fields.map>((field, i, a) => ({ id: `field::${field.id}`, - accessorFn: ({ data }: ItemRow) => data[field.id], + // Formula (scripted) fields are computed on the fly and live in the dynamic data channel + // (spread at the row's top level), while regular fields are stored in `data`: + accessorFn: (row: ItemRow) => + field.script ? (row as unknown as Record)[field.id] : row.data[field.id], header: ({ header }) => ( <> @@ -212,99 +297,126 @@ export const useDataTableColumns = (itemIDs: string[]) => { )} /> - { - openModal({ component: EditFieldModelModal, arguments: { fieldModelId: field.id, type } }); + {!field.readOnly && ( + { + if (field.script) + openModal({ + component: CreateScriptedFieldModelModal, + arguments: { type, fieldModelId: field.id }, + }); + else openModal({ component: EditFieldModelModal, arguments: { fieldModelId: field.id, type } }); + }, }, - }, - { - type: "divider", - }, - { - label: t("datatable.duplicate_column"), - onClick: () => duplicateFieldModel(field), - }, - { - label: t("datatable.move_left"), - disabled: !i, - onClick: () => moveFieldModel(type, field.id, -1), - }, - { - label: t("datatable.move_right"), - disabled: i === a.length - 1, - onClick: () => moveFieldModel(type, field.id, +1), - }, - { - label: t("datatable.insert_left"), - onClick: () => - openModal({ - component: EditFieldModelModal, - arguments: { insertAt: { pos: "before", id: field.id }, type }, - }), - }, - { - label: t("datatable.insert_right"), - onClick: () => - openModal({ - component: EditFieldModelModal, - arguments: { insertAt: { pos: "after", id: field.id }, type }, - }), - }, - { - type: "divider", - }, - { - label: t("datatable.sort_asc"), - onClick: () => { - setSort([ - { - id: header.id, - desc: false, - }, - ]); + { + type: "divider", }, - }, - { - label: t("datatable.sort_desc"), - onClick: () => { - setSort([ - { - id: header.id, - desc: true, - }, - ]); + { + label: t("datatable.duplicate_column"), + onClick: () => duplicateFieldModel(field), }, - }, - { - type: "divider", - }, - { - label: t("edition.delete_attribute"), - onClick: () => - openModal({ - component: ConfirmModal, - arguments: { - title: t(`edition.delete_${type}_attributes`, { name: field.id }), - message: t("edition.confirm_delete_attributes", { - nbValues: size(type === "nodes" ? nodeData : edgeData), - name: field.id, - }), - successMsg: t(`edition.delete_attributes_success`, { name: field.id }), - }, - afterSubmit: () => { - deleteFieldModel(field); - }, - }), - }, - ]} - > - - + { + label: t("datatable.move_left"), + disabled: !i, + onClick: () => moveFieldModel(type, field.id, -1), + }, + { + label: t("datatable.move_right"), + disabled: i === a.length - 1 || !!a[i + 1]?.readOnly, + onClick: () => moveFieldModel(type, field.id, +1), + }, + { + label: t("datatable.insert_left"), + onClick: () => + openModal({ + component: EditFieldModelModal, + arguments: { insertAt: { pos: "before", id: field.id }, type }, + }), + }, + { + label: t("datatable.insert_right"), + onClick: () => + openModal({ + component: EditFieldModelModal, + arguments: { insertAt: { pos: "after", id: field.id }, type }, + }), + }, + { + type: "divider", + }, + { + label: t("datatable.sort_asc"), + onClick: () => { + setSort([ + { + id: header.id, + desc: false, + }, + ]); + }, + }, + { + label: t("datatable.sort_desc"), + onClick: () => { + setSort([ + { + id: header.id, + desc: true, + }, + ]); + }, + }, + { + type: "divider", + }, + // Formula (scripted) fields only: freeze the computed values into a plain, static + // attribute (the script is dropped and values can no longer be recomputed). + ...(field.script + ? [ + { + label: t("datatable.freeze_values"), + onClick: () => + openModal({ + component: ConfirmModal, + arguments: { + title: t("datatable.freeze_values_title", { name: field.label || field.id }), + message: t("datatable.freeze_values_message"), + confirmMsg: t("datatable.freeze_values_confirm"), + successMsg: t("datatable.freeze_values_success", { name: field.label || field.id }), + }, + afterSubmit: () => freezeScriptedField(field), + }), + }, + ] + : []), + { + label: t("edition.delete_attribute"), + onClick: () => + openModal({ + component: ConfirmModal, + arguments: { + title: t(`edition.delete_${type}_attributes`, { name: field.id }), + message: t("edition.confirm_delete_attributes", { + nbValues: size(type === "nodes" ? nodeData : edgeData), + name: field.id, + }), + successMsg: t(`edition.delete_attributes_success`, { name: field.id }), + }, + afterSubmit: () => { + deleteFieldModel(field); + }, + }), + }, + ]} + > + + + )} ), cell: (props) => ( @@ -313,14 +425,27 @@ export const useDataTableColumns = (itemIDs: string[]) => { id={props.row.getValue("id")} field={field} value={props.row.getValue(`field::${field.id}`)} + // Read-only (system date) and formula (scripted) fields are not editable in the table: + readOnly={field.readOnly || !!field.script} + columnId={`field::${field.id}`} /> ), })), + + // ID (and, for edges, source/target) are pushed to the very end: + ...(type === "nodes" + ? [getSpecificRow("id")] + : [ + getSpecificRow("id"), + getSpecificRow(SPECIFIC_COLUMNS.sourceId as keyof ItemRow), + getSpecificRow(SPECIFIC_COLUMNS.targetId as keyof ItemRow), + ]), ], [ columnHelper, isMobile, getSpecificRow, + getExtremityLabelColumn, type, fields, select, @@ -337,6 +462,7 @@ export const useDataTableColumns = (itemIDs: string[]) => { nodeData, edgeData, deleteFieldModel, + freezeScriptedField, ], ); @@ -347,5 +473,6 @@ export const useDataTableColumns = (itemIDs: string[]) => { left: isMobile ? [] : [SPECIFIC_COLUMNS.selected, SPECIFIC_COLUMNS.preview], }, }, + editableColumnIds, }; }; diff --git a/packages/gephi-lite/src/views/dataPage/index.tsx b/packages/gephi-lite/src/views/dataPage/index.tsx index ece4ba40..53b92006 100644 --- a/packages/gephi-lite/src/views/dataPage/index.tsx +++ b/packages/gephi-lite/src/views/dataPage/index.tsx @@ -1,6 +1,6 @@ import { ItemType } from "@gephi/gephi-lite-sdk"; import cx from "classnames"; -import { type ComponentType, FC, ReactNode, useEffect, useMemo, useState } from "react"; +import { FC, ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { PiCaretDown, PiCaretRight } from "react-icons/pi"; import { ScrollSync } from "react-scroll-sync"; @@ -12,8 +12,6 @@ import { type MenuItem, SideMenu } from "../../components/SideMenu"; import Transition from "../../components/Transition"; import { CloseIcon, - DataCreationIcon, - DataCreationIconFill, FiltersIcon, FiltersIconFill, MenuCollapseIcon, @@ -22,10 +20,7 @@ import { MetricsIcon, MetricsIconFill, } from "../../components/common-icons"; -import { CreateScriptedFieldModelForm } from "../../components/data/CreateScriptedFieldModel"; -import { EditEdgeForm } from "../../components/data/EditEdge"; -import { EditFieldModelForm } from "../../components/data/EditFieldModel"; -import { EditNodeForm } from "../../components/data/EditNode"; +import { DATA_CREATION_MENU_ITEM, type Panel } from "../../components/data/DataCreationMenu"; import { useDataTable, useDataTableActions, @@ -37,6 +32,7 @@ import { } from "../../core/context/dataContexts"; import { mergeStaticDynamicData } from "../../core/graph/dynamicAttributes"; import { EDGE_METRICS, MIXED_METRICS, NODE_METRICS } from "../../core/metrics/collections"; +import { useMobile } from "../../hooks/useMobile"; import { doesItemMatch } from "../../utils/search"; import { MetricsPanel } from "../graphPage/panels/MetricsPanel"; import { Layout } from "../layout"; @@ -44,46 +40,8 @@ import { Header } from "../layout/Header"; import { TopBar } from "./TopBar"; import { DataTable } from "./dataTable/DataTable"; -type Panel = ComponentType<{ close: () => void }>; - const MENU: MenuItem<{ panel?: Panel }>[] = [ - { - id: "data-creation", - i18nKey: "edition.data_creation", - icon: { normal: DataCreationIcon, fill: DataCreationIconFill }, - children: [ - { - id: "data-creation-node", - i18nKey: "edition.create_nodes", - panel: ({ close }) => , - }, - { - id: "data-creation-edge", - i18nKey: "edition.create_edges", - panel: ({ close }) => , - }, - { - id: "data-creation-node-field", - i18nKey: "edition.create_nodes_field", - panel: ({ close }) => , - }, - { - id: "data-creation-edge-field", - i18nKey: "edition.create_edges_field", - panel: ({ close }) => , - }, - { - id: "data-creation-node-scripted-field", - i18nKey: "edition.create_nodes_scripted_field", - panel: ({ close }) => , - }, - { - id: "data-creation-edge-scripted-field", - i18nKey: "edition.create_edges_scripted_field", - panel: ({ close }) => , - }, - ], - }, + DATA_CREATION_MENU_ITEM, { id: "filters", i18nKey: "filters.title", @@ -166,10 +124,14 @@ export const DataPage: FC<{ type: ItemType }> = ({ type: inputType }) => { const allData = mergeStaticDynamicData(data, dynamicData); const getLabel = type === "nodes" ? getNodeLabel : getEdgeLabel; const fields = type === "nodes" ? nodeFields : edgeFields; + const nodeAllData = type === "edges" ? mergeStaticDynamicData(nodeData, dynamicNodeData) : null; if (search) { allIDs.forEach((id) => { const label = getLabel?.(allData[id]); - if (doesItemMatch(id, label, data[id], fields, search)) { + const extremityLabels = nodeAllData + ? [getNodeLabel?.(nodeAllData[graph.source(id)]), getNodeLabel?.(nodeAllData[graph.target(id)])] + : []; + if (doesItemMatch(id, label, data[id], fields, search, extremityLabels)) { matchingItems.push(id); } else { otherItems.push(id); @@ -195,9 +157,20 @@ export const DataPage: FC<{ type: ItemType }> = ({ type: inputType }) => { ]); const [selectedTool, setSelectedTool] = useState(undefined); + const isMobile = useMobile(); // Mobile display: const [expanded, setExpanded] = useState(false); + // The search box's panel (panel-main) only actually collapses on mobile (see the panel-collapsed + // rule in _panel.scss, scoped to that breakpoint) - on desktop it stays visible regardless of + // `expanded`/`selectedTool`, which only affect the extended panel next to it. + const isSearchPanelVisible = !isMobile || (expanded && !selectedTool); + + // Lets the filters badge in GraphSummary open the Filters panel directly, reusing the same id as + // its entry in MENU so the side menu highlights it as selected, like clicking it there would. + const openFilters = useCallback(() => { + setSelectedTool({ id: "filters", panel: () => }); + }, []); useEffect(() => { setType(inputType); @@ -223,8 +196,8 @@ export const DataPage: FC<{ type: ItemType }> = ({ type: inputType }) => { {/* Menu panel on left*/}
    - - + + ( () => [ - { label: t(`graph.model.${type}-data.id`), value: id }, ...fields.map((field) => ({ label: staticDynamicAttributeLabel(field), field, @@ -91,22 +107,41 @@ function SelectedItem< value, field: { type: "number", id: key, itemType: type } as FieldModel, })), + { label: t(`graph.model.${type}-data.id`), value: id }, ], [data.dynamic, data.static, fields, id, renderingData, t, type], ); const item = getItemAttributes(type, id, filteredGraph, data, graphDataset, visualGetters); + + // Node's edges/neighbors are taken from the full graph, so the filtered out ones stay part of the + // resulting selection (reported as filtered) instead of being silently dropped. A node without + // any edge has nothing to select at all: keep both buttons disabled, so clicking them cannot + // replace this panel by an empty edges selection. + const isIsolated = type === "nodes" && fullGraph.hasNode(id) && !fullGraph.degree(id); let content: ReactNode; if (type === "nodes") { - content =