From 7148258e5e77cc217117eb6c974626599e1b9a8f Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 04:15:41 +0200 Subject: [PATCH 01/33] feat(ui): enable Changes/commit tab in the timeline webview The commit workflow (ChangesView, stage/unstage, commit, push/pull) was fully implemented on both the webview and the message-handler side but the tab switcher was commented out, leaving it unreachable. - Timeline.tsx: re-enable - useTimelineState.ts: default activeTab to "changes" - volta: pin node 22 Co-Authored-By: Claude Sonnet 5 --- package-lock.json | 8514 +++++++++++++++++ package.json | 6 +- .../app/components/Timeline/Timeline.tsx | 4 +- .../Timeline/hooks/useTimelineState.ts | 2 +- 4 files changed, 8521 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..72e5eec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8514 @@ +{ + "name": "vscode-github-desktop", + "version": "1.1.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vscode-github-desktop", + "version": "1.1.4", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.2", + "@mui/material": "^7.3.2", + "@octokit/auth-oauth-device": "^4.0.3", + "@octokit/rest": "^20.0.2", + "@tanstack/react-query": "^5.89.0", + "axios": "^1.12.2", + "dayjs": "^1.11.18", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "simple-git": "^3.21.0" + }, + "devDependencies": { + "@types/node": "18.x", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@types/vscode": "^1.84.0", + "@vitejs/plugin-react": "^5.0.3", + "@vscode/vsce": "^3.6.0", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.4", + "ts-node": "^10.9.2", + "typescript": "^5.3.3", + "vite": "^7.1.6", + "webpack": "^5.101.3", + "webpack-cli": "^6.0.1" + }, + "engines": { + "node": ">=20.0.0", + "vscode": ">=1.84.0" + } + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", + "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.0.tgz", + "integrity": "sha512-JjQWO6akOck45PH/XBrxzsQGAiKrfFl4m5iggJ0ItMIz5omRufOXWpqCPpdjKN3vKDzlSUvFjaMb7Zwf0gvAdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-process": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz", + "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", + "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", + "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", + "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.2.tgz", + "integrity": "sha512-NXL2/pCJctLxgw8bvrwwgge743kEq8LBT+O1pmV0vyUwetzFPH9auP6jhkU/cgZCPPtWoewAe3ncaGCgPo07fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-process": "^1.0.0", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.5", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", + "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.21.0.tgz", + "integrity": "sha512-80OcuXDErmcEDAIH9pBtSqBsed2sPT/IWmbG3xHLoPMl5zc8TINd6SlJAbVSmN5huGa3xGAg5qR7VnpaIEK0Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.14.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.0.tgz", + "integrity": "sha512-A4rb55hI86Q9tBl/+jBj7TMz7iX2RFgQs/nExFzcAtoI/BFRVdaH5SL/MivrYD7qvweMpN8AgVvVMHV8UBYxew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.6.0.tgz", + "integrity": "sha512-uFY9NxrWHw8PwZx7gAX6PDn+9vdfS05+levc/kwkx77IkjfaldnQbbcQzzDIZ5Hq5Zdr6/z92oAIoRWKp6MnOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.13.0", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.13.0.tgz", + "integrity": "sha512-rOAy0KUcyBbdwVJ+f3uPpthXatFLLZN+/KWAsTLzk1aB23Xl9DRmmXYwSvBFOZyXj4jUQQ5FKxxRkhAFW1fOow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame": { + "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.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "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": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "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", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "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.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.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-module-imports": { + "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.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "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.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "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": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "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.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.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": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "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.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "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.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "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/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "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", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz", + "integrity": "sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.11.tgz", + "integrity": "sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.11", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz", + "integrity": "sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/core-downloads-tracker": "^7.3.11", + "@mui/system": "^7.3.11", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.11", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.3", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.11", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz", + "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "^7.3.11", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz", + "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz", + "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/private-theming": "^7.3.11", + "@mui/styled-engine": "^7.3.10", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.11", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz", + "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-4.0.5.tgz", + "integrity": "sha512-XyhoWRTzf2ZX0aZ52a6Ew5S5VBAfwwx1QnC2Np6Et3MWQpZjlREIcbcvVZtkNuXp6Z9EeiSLSDUqm3C+aMEHzQ==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^2.0.0", + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/auth-token": { + "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==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", + "dependencies": { + "@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": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/endpoint": { + "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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "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/core/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==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "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": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/endpoint": { + "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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "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/graphql/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==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "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": "^24.2.0" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-5.0.0.tgz", + "integrity": "sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-2.0.6.tgz", + "integrity": "sha512-l9Uml2iGN2aTWLZcm8hV+neBiFXAQ9+3sKiQe/sgumHlL6HDg0AQ8/l16xX/5jJvfxueqTW5CWbzd0MjnlfHZw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^5.0.0", + "@octokit/request": "^6.2.3", + "@octokit/request-error": "^3.0.3", + "@octokit/types": "^9.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "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-paginate-rest/node_modules/@octokit/types": { + "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": "^24.2.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.8.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "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-rest-endpoint-methods/node_modules/@octokit/types": { + "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": "^24.2.0" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "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" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/rest": { + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", + "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", + "license": "MIT", + "dependencies": { + "@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": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", + "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", + "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.102.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.8.0.tgz", + "integrity": "sha512-5CiH9COYmovWmExQgs7763DzX6Gy9zjkjJ7JxCC95wyTcjwQn/8poNF6fv3qzRlmx8CRRde8DHr9FcgAAiPzgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.8.0.tgz", + "integrity": "sha512-+oU3A235NATv6Lzi4xa4kJ65PuNJlIxesaO4AvDhDWA9FWm7y4XKWaoQCW1esgaQQ6dwnUiFKArQ8TcJ86mC4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.8.0", + "@textlint/resolver": "15.8.0", + "@textlint/types": "15.8.0", + "debug": "^4.4.3", + "js-yaml": "^4.3.0", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=20.18.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.8.0.tgz", + "integrity": "sha512-rt+OR1WYGoLOY8HkA/aBPrqufF6yUUEsKEAh7XohTsT3lp9IyZFT6zOIbjul9P4FAzsmSPkcrYjVx3Bz/IUfkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.8.0.tgz", + "integrity": "sha512-E88tzfX3K8Jykk+38aJ9cy8RquD8ABVOPTO2rFEESq0wcg8x6/ypdAS8ZgR7OKiGqlRF0hkO/m5PbQwVfKM3VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.8.0.tgz", + "integrity": "sha512-Anhc6y5736YIsvqae0U6k0YmB2M/QVHkEeOv2aydAn/WIkdI69dCOiDbe3/+RagS3qstFTSFWJzNRA2lUjv19w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.8.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "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/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.136.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.136.0.tgz", + "integrity": "sha512-DO746VAfnHk8+Y8CsUKYMm2rjWD0mxEuYBZI+ssaqJY7yvLJPf9lcwxSSz9W60gbdvdcGyvHQxHTyByfF9mVqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.8.tgz", + "integrity": "sha512-bLMpVcWZNzq6lYOybwFwOAR1IXKcHnhUNqYeHjl1bET/qE3jFPFH+p8Wrh3rU4xwdnifPxmKNESBYnvnmc75aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.1.0.tgz", + "integrity": "sha512-9AQrqazrBgTgRSuwleLVXUrIUphY02/SFCh2TKYoLV/xifJAdblhdmEmw5gUrYSPQ3sRwNs9iyCMD14sATEE6g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/semver": { + "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": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "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": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "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": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "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", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "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": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=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": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "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.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "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", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "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": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/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": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/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/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "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", + "optional": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz", + "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "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": { + "node": ">=0.10.0" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "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": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "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" + } + }, + "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": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/markdown-it": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.1.tgz", + "integrity": "sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz", + "integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "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" + }, + "node_modules/node-abi": { + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "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", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "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": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/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/normalize-package-data/node_modules/semver": { + "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": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.7.tgz", + "integrity": "sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/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": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "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": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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/rimraf/node_modules/minimatch": { + "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.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "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": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "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/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "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", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "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/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-loader": { + "version": "9.6.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.6.2.tgz", + "integrity": "sha512-R4iuczmtgxvtuI556s+hTZ6/7Ee03VCAk/l/M8LY1OAsUgB7YydsCxkgq9D9pKRaD7GJqUi2u8fp9zZP/ufjKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "picomatch": "^4.0.0", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "loader-utils": "*", + "typescript": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "loader-utils": { + "optional": true + } + } + }, + "node_modules/ts-loader/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "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": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.110.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.3.tgz", + "integrity": "sha512-GuizBzRvo9YPpyoNMf3ag7AzxbaW85qrRSqTha345KyJbAFPt3/cMzBM0h+RWg7SK/7DdzRLINP3LvQ0hvr4hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.7.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "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" + } + }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json index b91a533..f774071 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.4", + "version": "1.1.5", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { @@ -244,6 +244,8 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "simple-git": "^3.21.0" + }, + "volta": { + "node": "22.23.2" } } - diff --git a/src/webviews/app/components/Timeline/Timeline.tsx b/src/webviews/app/components/Timeline/Timeline.tsx index 107befd..55449d8 100644 --- a/src/webviews/app/components/Timeline/Timeline.tsx +++ b/src/webviews/app/components/Timeline/Timeline.tsx @@ -65,12 +65,12 @@ export const Timeline: React.FC = ({ /> {/* Tabs */} - {/* */} + /> {/* Content */} diff --git a/src/webviews/app/components/Timeline/hooks/useTimelineState.ts b/src/webviews/app/components/Timeline/hooks/useTimelineState.ts index 77e0e71..7097e22 100644 --- a/src/webviews/app/components/Timeline/hooks/useTimelineState.ts +++ b/src/webviews/app/components/Timeline/hooks/useTimelineState.ts @@ -11,7 +11,7 @@ export const useTimelineState = ( history: GitCommit[], commitsOffset: number, ) => { - const [activeTab, setActiveTab] = useState("history"); + const [activeTab, setActiveTab] = useState("changes"); const [commitMessage, setCommitMessage] = useState(""); const [selectedFiles, setSelectedFiles] = useState>(new Set()); const [contextMenu, setContextMenu] = useState(null); From 23404964e87af04d522006705ad7a3295425e3ae Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 04:22:10 +0200 Subject: [PATCH 02/33] fix(ui): keep branch dropdown on-screen in short containers When the timeline view is docked in the bottom panel the Popover was taller than the viewport and got pushed off the top edge. Make the Paper a flex column capped at the viewport height, let the branch list shrink, and add marginThreshold so MUI keeps it within bounds. Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../app/components/Timeline/BranchDropdown.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f774071..804a262 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.5", + "version": "1.1.6", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/app/components/Timeline/BranchDropdown.tsx b/src/webviews/app/components/Timeline/BranchDropdown.tsx index 9c66035..e2e4000 100644 --- a/src/webviews/app/components/Timeline/BranchDropdown.tsx +++ b/src/webviews/app/components/Timeline/BranchDropdown.tsx @@ -331,6 +331,7 @@ export const BranchDropdown: React.FC = ({ open={open} anchorEl={anchorEl} onClose={handleClose} + marginThreshold={8} anchorOrigin={{ vertical: "bottom", horizontal: "left", @@ -342,7 +343,9 @@ export const BranchDropdown: React.FC = ({ PaperProps={{ sx: { width: 320, - maxHeight: 450, + maxHeight: "calc(100vh - 16px)", + display: "flex", + flexDirection: "column", bgcolor: "var(--vscode-sideBar-background)", border: "1px solid var(--vscode-sideBarSectionHeader-border)", borderRadius: 1, @@ -354,6 +357,7 @@ export const BranchDropdown: React.FC = ({ {/* Search */} = ({ {/* Content */} = ({ Date: Thu, 3 Sep 2026 04:39:58 +0200 Subject: [PATCH 03/33] feat(timeline): rebuild the view as a self-contained GitHub Desktop layout The React/vite webview rendered blank when docked in the panel. Replace WebviewHtmlService with an inlined (no bundle, nonce CSP) UI that mirrors GitHub Desktop: repository / branch / sync toolbar, Changes + History tabs, filter, per-file checkboxes with status squares, commit box, and a unified diff pane on the right. Backend: add commitFiles (stage + commit a pathspec), discardFiles, and getWorkingDiff message handlers. Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../services/message-handler.service.ts | 82 ++ .../timeline/services/webview-html.service.ts | 741 ++++++++++++++++-- 3 files changed, 757 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index 804a262..5687304 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.6", + "version": "1.1.7", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/message-handler.service.ts b/src/webviews/timeline/services/message-handler.service.ts index 0792798..4bd2a7b 100644 --- a/src/webviews/timeline/services/message-handler.service.ts +++ b/src/webviews/timeline/services/message-handler.service.ts @@ -36,6 +36,18 @@ export class MessageHandlerService { case "commit": await this.handleCommit(message.message); break; + case "commitFiles": + await this.handleCommitFiles( + (message as any).message, + (message as any).files, + ); + break; + case "discardFiles": + await this.handleDiscardFiles((message as any).files); + break; + case "getWorkingDiff": + await this.handleGetWorkingDiff((message as any).filePath); + break; case "push": await this.handlePush(); break; @@ -343,6 +355,76 @@ export class MessageHandlerService { } } + // Commit exactly the given paths (GitHub Desktop model: checked files are the + // commit, no separate staging step). Stages then commits with a pathspec. + private async handleCommitFiles( + message: string, + files: string[], + ): Promise { + const repository = getPrimaryRepository(this.repositories); + if (!repository || !message?.trim() || !files?.length) return; + + try { + const git = simpleGit(repository.localPath); + await git.add(files); + await git.commit(message, files); + await this.handleRefresh(); + vscode.window.showInformationMessage("Commit created successfully"); + } catch (error) { + vscode.window.showErrorMessage(`Failed to commit: ${error}`); + } + } + + private async handleDiscardFiles(files: string[]): Promise { + const repository = getPrimaryRepository(this.repositories); + if (!repository || !files?.length) return; + + const confirm = await vscode.window.showWarningMessage( + files.length === 1 + ? `Discard changes to ${files[0]}?` + : `Discard changes to ${files.length} files?`, + { modal: true }, + "Discard", + ); + if (confirm !== "Discard") return; + + try { + const git = simpleGit(repository.localPath); + await git.raw(["checkout", "HEAD", "--", ...files]).catch(() => undefined); + await git.raw(["clean", "-fd", "--", ...files]).catch(() => undefined); + await this.handleRefresh(); + } catch (error) { + vscode.window.showErrorMessage(`Failed to discard: ${error}`); + } + } + + // Diff of an uncommitted working-tree change (staged + unstaged, plus new files). + private async handleGetWorkingDiff(filePath: string): Promise { + const repository = getPrimaryRepository(this.repositories); + if (!repository) return; + + try { + const git = simpleGit(repository.localPath); + let diff = await git.raw(["diff", "HEAD", "--", filePath]); + if (!diff.trim()) { + // Untracked file: show it as an addition. + diff = await git + .raw(["diff", "--no-index", "--", "/dev/null", filePath]) + .catch(() => ""); + } + this.view.webview.postMessage({ + type: "workingDiff", + payload: { path: filePath, diff }, + }); + } catch (error) { + this.view.webview.postMessage({ + type: "error", + message: + error instanceof Error ? error.message : "Failed to load file diff.", + }); + } + } + private async handlePush(): Promise { const repository = getPrimaryRepository(this.repositories); if (!repository) { diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 714d20d..6d4a799 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -3,6 +3,14 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../../../core/repositories/repository-manager"; import { getPrimaryRepository } from "../../../shared/utils/repo-selection"; +/** + * Renders the timeline webview as a self-contained GitHub Desktop-style UI: + * a three-cell toolbar (repository / branch / sync), a left column with the + * Changes and History tabs plus the commit box, and a diff pane on the right. + * + * Everything is inlined (no bundle, no CDN, nonce-based CSP) so the view keeps + * working regardless of the webview module loader. + */ export class WebviewHtmlService { constructor( private readonly context: vscode.ExtensionContext, @@ -10,21 +18,9 @@ export class WebviewHtmlService { ) {} generateHtml(webview: vscode.Webview): string { - const reactAppUri = webview.asWebviewUri( - vscode.Uri.joinPath( - this.context.extensionUri, - "out", - "webview", - "main.js", - ), - ); - + const nonce = getNonce(); const repository = getPrimaryRepository(this.repositories); const initialData = { - changes: [], - history: [], - branches: [], - currentBranch: null, repository: repository ? { name: path.basename(repository.localPath), @@ -32,67 +28,678 @@ export class WebviewHtmlService { remote: repository.remoteUrl, } : null, - accounts: [], - activeAccount: null, }; + const csp = [ + `default-src 'none'`, + `style-src 'nonce-${nonce}'`, + `script-src 'nonce-${nonce}'`, + `img-src ${webview.cspSource} https: data:`, + `font-src ${webview.cspSource}`, + ].join("; "); + return ` - - - - GitHub Desktop Timeline - - + + + +GitHub Desktop + - -
- - + +
+ + + +
+ +
+
+
+ + +
+ +
+
+ +
+ +
+
+
${ICON.check}
+
No local changes
+
There are no uncommitted changes in this repository.
+
+
+
+ ? + +
+ + +
+
+ + +
+ + +
+ + + + `; } } + +function getNonce(): string { + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +const ICON = { + repo: ``, + branch: ``, + fetch: ``, + push: ``, + pull: ``, + publish: ``, + caret: ``, + check: ``, + file: ``, +}; + +const STYLES = ` +* { box-sizing: border-box; margin: 0; padding: 0; } +:root { color-scheme: light dark; } +html, body { height: 100%; } +body { + display: flex; flex-direction: column; + font-family: var(--vscode-font-family, "Segoe UI", system-ui, sans-serif); + font-size: var(--vscode-font-size, 13px); + color: var(--vscode-foreground); + background: var(--vscode-editor-background); + overflow: hidden; +} +button, input, textarea { font: inherit; color: inherit; } + +/* ---- toolbar ---- */ +#toolbar { + display: flex; flex: 0 0 auto; height: 50px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-panel-border); +} +.cell { + display: flex; align-items: center; gap: 8px; + flex: 1 1 0; min-width: 0; padding: 0 12px; + background: transparent; border: 0; + border-right: 1px solid var(--vscode-panel-border); + cursor: pointer; text-align: left; +} +.cell:last-child { border-right: 0; } +.cell:hover { background: var(--vscode-list-hoverBackground); } +.cell-ico { flex: 0 0 auto; display: flex; opacity: .8; } +.cell-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; } +.cell-label { font-size: 11px; color: var(--vscode-descriptionForeground); } +.cell-value { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.cell-sub { font-weight: 400; font-size: 11px; color: var(--vscode-descriptionForeground); } +.cell-caret { flex: 0 0 auto; opacity: .6; } +.cell-count { + flex: 0 0 auto; font-size: 11px; padding: 1px 5px; border-radius: 8px; + background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); +} + +/* ---- body split ---- */ +#body { flex: 1 1 auto; display: flex; min-height: 0; } +#left { + flex: 0 0 272px; display: flex; flex-direction: column; min-height: 0; + background: var(--vscode-sideBar-background); + border-right: 1px solid var(--vscode-panel-border); +} +#right { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; } + +/* ---- tabs ---- */ +#tabs { display: flex; flex: 0 0 auto; border-bottom: 1px solid var(--vscode-panel-border); } +.tab { + flex: 1 1 0; padding: 8px 4px; background: transparent; border: 0; + border-bottom: 2px solid transparent; cursor: pointer; + color: var(--vscode-descriptionForeground); font-weight: 500; +} +.tab:hover { background: var(--vscode-list-hoverBackground); } +.tab.is-active { color: var(--vscode-foreground); border-bottom-color: var(--vscode-focusBorder); } +.badge { + font-size: 11px; padding: 0 5px; border-radius: 8px; + background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); +} +.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; } + +/* ---- changed files ---- */ +#filterWrap { flex: 0 0 auto; padding: 8px; } +#filter { + width: 100%; padding: 4px 8px; border-radius: 2px; + background: var(--vscode-input-background); color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, transparent); +} +#filter:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } +#allRow { + flex: 0 0 auto; display: flex; align-items: center; gap: 8px; + padding: 5px 10px; font-size: 12px; color: var(--vscode-descriptionForeground); + border-bottom: 1px solid var(--vscode-panel-border); cursor: pointer; +} +#fileList { flex: 1 1 auto; overflow: auto; min-height: 0; } +.file-row { + display: flex; align-items: center; gap: 8px; padding: 3px 10px; cursor: pointer; + white-space: nowrap; +} +.file-row:hover { background: var(--vscode-list-hoverBackground); } +.file-row.is-selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } +.file-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; } +.file-dir { opacity: .6; } +.status-sq { + flex: 0 0 auto; width: 14px; height: 14px; border-radius: 2px; + display: flex; align-items: center; justify-content: center; + font-size: 9px; font-weight: 700; line-height: 1; + border: 1px solid currentColor; +} +.st-M { color: var(--vscode-gitDecoration-modifiedResourceForeground, #e2c08d); } +.st-A, .st-U { color: var(--vscode-gitDecoration-untrackedResourceForeground, #73c991); } +.st-D { color: var(--vscode-gitDecoration-deletedResourceForeground, #f14c4c); } +.st-R { color: var(--vscode-gitDecoration-renamedResourceForeground, #73c991); } +.st-C { color: var(--vscode-gitDecoration-conflictingResourceForeground, #e4676b); } +.file-x { + flex: 0 0 auto; opacity: 0; padding: 0 2px; border: 0; background: transparent; + cursor: pointer; color: inherit; font-size: 13px; +} +.file-row:hover .file-x { opacity: .7; } +.file-x:hover { opacity: 1; } + +/* ---- commit box ---- */ +#commitBox { + flex: 0 0 auto; padding: 8px; border-top: 1px solid var(--vscode-panel-border); + display: flex; flex-direction: column; gap: 6px; +} +.commit-summary { display: flex; align-items: center; gap: 6px; } +.avatar { + flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + font-size: 10px; font-weight: 700; text-transform: uppercase; + background: var(--vscode-button-background); color: var(--vscode-button-foreground); +} +#summary, #description { + width: 100%; padding: 5px 8px; border-radius: 2px; + background: var(--vscode-input-background); color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, transparent); +} +#summary:focus, #description:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } +#description { resize: vertical; min-height: 48px; max-height: 140px; } +#commitBtn { + width: 100%; padding: 6px 10px; border: 0; border-radius: 2px; cursor: pointer; + background: var(--vscode-button-background); color: var(--vscode-button-foreground); +} +#commitBtn:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } +#commitBtn:disabled { opacity: .5; cursor: default; } +#commitBtn strong { font-weight: 700; } + +/* ---- history ---- */ +#commitList { flex: 1 1 auto; overflow: auto; min-height: 0; } +.commit-row { + display: flex; flex-direction: column; gap: 2px; padding: 7px 12px; cursor: pointer; + border-bottom: 1px solid var(--vscode-panel-border); +} +.commit-row:hover { background: var(--vscode-list-hoverBackground); } +.commit-row.is-selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } +.commit-msg { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.commit-meta { font-size: 11px; color: var(--vscode-descriptionForeground); display: flex; align-items: center; gap: 6px; } +.unpushed-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--vscode-charts-blue, #4daafc); flex: 0 0 auto; } + +/* ---- diff ---- */ +#diffHeader { + flex: 0 0 auto; padding: 6px 12px; font-size: 12px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-panel-border); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +#diffBody { + flex: 1 1 auto; overflow: auto; min-height: 0; + background: var(--vscode-editor-background); + font-family: var(--vscode-editor-font-family, ui-monospace, monospace); + font-size: var(--vscode-editor-font-size, 12px); +} +.diff-line { display: flex; white-space: pre; } +.diff-gutter { + flex: 0 0 auto; width: 44px; padding: 0 6px; text-align: right; + color: var(--vscode-editorLineNumber-foreground); opacity: .6; + user-select: none; border-right: 1px solid var(--vscode-panel-border); +} +.diff-text { flex: 1 1 auto; padding: 0 8px; } +.diff-add { background: var(--vscode-diffEditor-insertedTextBackground, rgba(63,185,80,.15)); } +.diff-del { background: var(--vscode-diffEditor-removedTextBackground, rgba(248,81,73,.15)); } +.diff-hunk { color: var(--vscode-descriptionForeground); background: var(--vscode-editor-inactiveSelectionBackground); } +.diff-meta { color: var(--vscode-descriptionForeground); } + +/* ---- empty states ---- */ +.empty-block { + flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; + justify-content: center; text-align: center; padding: 24px; gap: 6px; +} +.empty-emoji { opacity: .5; } +.empty-title { font-size: 14px; } +.empty-sub { font-size: 12px; color: var(--vscode-descriptionForeground); max-width: 240px; } + +/* ---- popup menu ---- */ +.menu { + position: fixed; z-index: 50; min-width: 220px; max-height: 60vh; overflow: auto; + background: var(--vscode-menu-background, var(--vscode-sideBar-background)); + border: 1px solid var(--vscode-menu-border, var(--vscode-panel-border)); + border-radius: 4px; box-shadow: 0 4px 14px rgba(0,0,0,.4); padding: 4px; +} +.menu input.menu-filter { + width: 100%; margin-bottom: 4px; padding: 4px 6px; border-radius: 2px; + background: var(--vscode-input-background); color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, transparent); +} +.menu-item { + display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 2px; + cursor: pointer; white-space: nowrap; +} +.menu-item:hover { background: var(--vscode-list-hoverBackground); } +.menu-item.is-current { color: var(--vscode-descriptionForeground); } +.menu-sep { height: 1px; margin: 4px 0; background: var(--vscode-panel-border); } +.menu-empty { padding: 6px 8px; color: var(--vscode-descriptionForeground); } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: var(--vscode-scrollbarSlider-background); border-radius: 5px; } +::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground); } +`; + +const SCRIPT = String.raw` +const vscode = acquireVsCodeApi(); +const post = (command, extra) => vscode.postMessage(Object.assign({ command }, extra || {})); +const $ = (id) => document.getElementById(id); +const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ( + { "&": "&", "<": "<", ">": ">", '"': """ }[c] +)); + +const state = { + repository: (window.__INITIAL__ && window.__INITIAL__.repository) || null, + changes: [], + history: [], + branches: [], + currentBranch: null, + branchActivity: {}, + remote: null, + tab: "changes", + selectedFiles: new Set(), + selectedPath: null, + selectedCommit: null, + filter: "", +}; + +/* ---------- toolbar ---------- */ +function renderToolbar() { + $("repoName").textContent = state.repository ? state.repository.name : "No repository"; + $("branchName").textContent = state.currentBranch || "—"; + $("commitBranch").textContent = state.currentBranch || "branch"; + + const r = state.remote || {}; + const ico = $("syncIco"), label = $("syncLabel"), sub = $("syncSub"), count = $("syncCount"); + const svg = { fetch: ${JSON.stringify(ICON.fetch)}, push: ${JSON.stringify(ICON.push)}, pull: ${JSON.stringify(ICON.pull)}, publish: ${JSON.stringify(ICON.publish)} }; + let mode = "fetch"; + if (!r.hasRemote || r.isPublished === false) mode = "publish"; + else if (r.behind > 0) mode = "pull"; + else if (r.ahead > 0) mode = "push"; + + ico.innerHTML = svg[mode]; + count.hidden = true; + if (mode === "publish") { label.textContent = "Publish branch"; sub.textContent = "This branch is not on the remote yet"; } + else if (mode === "pull") { label.textContent = "Pull origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↓ " + r.behind; } + else if (mode === "push") { label.textContent = "Push origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↑ " + r.ahead; } + else { label.textContent = "Fetch origin"; sub.textContent = relFetched(r.lastFetched); } + $("syncCell").dataset.mode = mode; +} +function relFetched(d) { + if (!d) return "Never fetched"; + const t = new Date(d).getTime(); + if (isNaN(t)) return "Last fetched recently"; + const mins = Math.round((Date.now() - t) / 60000); + if (mins < 1) return "Last fetched just now"; + if (mins < 60) return "Last fetched " + mins + "m ago"; + const hrs = Math.round(mins / 60); + if (hrs < 24) return "Last fetched " + hrs + "h ago"; + return "Last fetched " + Math.round(hrs / 24) + "d ago"; +} + +$("syncCell").onclick = () => { + const mode = $("syncCell").dataset.mode; + if (mode === "publish") post("publish"); + else if (mode === "pull") post("pull"); + else if (mode === "push") post("push"); + else post("fetch"); +}; + +/* ---------- tabs ---------- */ +document.querySelectorAll(".tab").forEach((btn) => { + btn.onclick = () => { + state.tab = btn.dataset.tab; + document.querySelectorAll(".tab").forEach((b) => b.classList.toggle("is-active", b === btn)); + $("pane-changes").hidden = state.tab !== "changes"; + $("pane-history").hidden = state.tab !== "history"; + }; +}); + +/* ---------- changed files ---------- */ +function fileParts(p) { + const i = p.lastIndexOf("/"); + return i < 0 ? { dir: "", name: p } : { dir: p.slice(0, i + 1), name: p.slice(i + 1) }; +} +function statusLetter(st) { + const s = (st || "").replace(/\s/g, ""); + if (s.indexOf("D") >= 0) return "D"; + if (s.indexOf("A") >= 0 || s === "??" || s.indexOf("?") >= 0) return "A"; + if (s.indexOf("R") >= 0) return "R"; + if (s.indexOf("U") >= 0 || s.indexOf("C") >= 0) return "C"; + return "M"; +} +function visibleChanges() { + const f = state.filter.trim().toLowerCase(); + return state.changes.filter((c) => !f || c.path.toLowerCase().indexOf(f) >= 0); +} +function renderChanges() { + const list = $("fileList"); + const vis = visibleChanges(); + $("changesBadge").textContent = state.changes.length; + $("noChanges").hidden = state.changes.length > 0; + $("commitBox").style.display = state.changes.length > 0 ? "" : "none"; + $("allRow").style.display = state.changes.length > 0 ? "" : "none"; + $("filterWrap").style.display = state.changes.length > 0 ? "" : "none"; + + const n = state.selectedFiles.size; + $("allText").textContent = n + " of " + state.changes.length + " file" + (state.changes.length === 1 ? "" : "s") + " to commit"; + $("allCheck").checked = state.changes.length > 0 && n === state.changes.length; + $("allCheck").indeterminate = n > 0 && n < state.changes.length; + + list.innerHTML = ""; + for (const c of vis) { + const parts = fileParts(c.path); + const L = statusLetter(c.status); + const row = document.createElement("div"); + row.className = "file-row" + (state.selectedPath === c.path ? " is-selected" : ""); + row.innerHTML = + '' + + '' + L + '' + + '' + esc(parts.dir) + '' + esc(parts.name) + '' + + ''; + const cb = row.querySelector("input"); + cb.onclick = (e) => { + e.stopPropagation(); + if (cb.checked) state.selectedFiles.add(c.path); else state.selectedFiles.delete(c.path); + renderChanges(); + }; + row.querySelector(".file-x").onclick = (e) => { + e.stopPropagation(); + post("discardFiles", { files: [c.path] }); + }; + row.onclick = () => selectFile(c.path); + list.appendChild(row); + } + updateCommitBtn(); +} +function selectFile(p) { + state.selectedPath = p; + state.selectedCommit = null; + renderChanges(); + $("diffHeader").hidden = false; + $("diffPath").textContent = p; + $("diffBody").innerHTML = '
Loading…
'; + post("getWorkingDiff", { filePath: p }); +} +$("allCheck").onclick = () => { + if ($("allCheck").checked) state.changes.forEach((c) => state.selectedFiles.add(c.path)); + else state.selectedFiles.clear(); + renderChanges(); +}; +$("filter").oninput = (e) => { state.filter = e.target.value; renderChanges(); }; + +/* ---------- commit ---------- */ +function updateCommitBtn() { + const ok = state.selectedFiles.size > 0 && $("summary").value.trim().length > 0; + const btn = $("commitBtn"); + btn.disabled = !ok; + const n = state.selectedFiles.size; + btn.innerHTML = "Commit " + (n > 0 ? n + " file" + (n === 1 ? "" : "s") + " " : "") + + "to " + esc(state.currentBranch || "branch") + ""; +} +$("summary").oninput = updateCommitBtn; +function doCommit() { + const summary = $("summary").value.trim(); + if (!summary || state.selectedFiles.size === 0) return; + const desc = $("description").value.trim(); + const message = desc ? summary + "\n\n" + desc : summary; + post("commitFiles", { message: message, files: Array.from(state.selectedFiles) }); + $("summary").value = ""; $("description").value = ""; + state.selectedFiles.clear(); + updateCommitBtn(); +} +$("commitBtn").onclick = doCommit; +["summary", "description"].forEach((id) => { + $(id).addEventListener("keydown", (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { e.preventDefault(); doCommit(); } + }); +}); + +/* ---------- history ---------- */ +function renderHistory() { + const list = $("commitList"); + $("noHistory").hidden = state.history.length > 0; + list.innerHTML = ""; + for (const c of state.history) { + const row = document.createElement("div"); + row.className = "commit-row" + (state.selectedCommit === c.hash ? " is-selected" : ""); + row.innerHTML = + '
' + esc((c.message || "").split("\n")[0]) + "
" + + '
' + (c.isPushed === false ? '' : "") + + esc(c.authorName || c.author || "") + " · " + esc(c.relativeTime || "") + "
"; + row.onclick = () => { + state.selectedCommit = c.hash; + state.selectedPath = null; + renderHistory(); + post("openCommitDetail", { hash: c.hash }); + }; + list.appendChild(row); + } +} + +/* ---------- diff rendering ---------- */ +function renderDiff(text) { + const body = $("diffBody"); + if (!text || !text.trim()) { + body.innerHTML = '
No textual changes (binary file or whitespace only).
'; + return; + } + const lines = text.split("\n"); + let out = ""; + let oldNo = 0, newNo = 0; + for (const raw of lines) { + let cls = "", gutter = ""; + if (raw.indexOf("@@") === 0) { + const m = raw.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/); + if (m) { oldNo = +m[1]; newNo = +m[2]; } + cls = "diff-hunk"; + } else if (/^(diff |index |--- |\+\+\+ |new file|deleted file|similarity |rename )/.test(raw)) { + cls = "diff-meta"; + } else if (raw.charAt(0) === "+") { + cls = "diff-add"; gutter = "+" + (newNo++); + } else if (raw.charAt(0) === "-") { + cls = "diff-del"; gutter = "-" + (oldNo++); + } else { + gutter = String(newNo); oldNo++; newNo++; + } + out += '
' + esc(gutter) + + '' + esc(raw || " ") + "
"; + } + body.innerHTML = out; + body.scrollTop = 0; +} + +/* ---------- dropdown menus ---------- */ +const menu = $("menu"); +function closeMenu() { menu.hidden = true; menu.innerHTML = ""; document.removeEventListener("mousedown", onDocDown, true); } +function onDocDown(e) { if (!menu.contains(e.target)) closeMenu(); } +function openMenu(anchor, build) { + menu.innerHTML = ""; build(menu); + menu.hidden = false; + const r = anchor.getBoundingClientRect(); + menu.style.visibility = "hidden"; + requestAnimationFrame(() => { + const mw = menu.offsetWidth, mh = menu.offsetHeight; + let x = r.left, y = r.bottom + 2; + if (x + mw > window.innerWidth - 8) x = window.innerWidth - 8 - mw; + if (y + mh > window.innerHeight - 8) y = Math.max(8, r.top - 2 - mh); + menu.style.left = Math.max(8, x) + "px"; + menu.style.top = Math.max(8, y) + "px"; + menu.style.visibility = ""; + }); + setTimeout(() => document.addEventListener("mousedown", onDocDown, true), 0); +} + +$("branchCell").onclick = () => { + openMenu($("branchCell"), (m) => { + const filter = document.createElement("input"); + filter.className = "menu-filter"; filter.placeholder = "Find a branch…"; + m.appendChild(filter); + const holder = document.createElement("div"); + m.appendChild(holder); + const draw = () => { + const q = filter.value.trim().toLowerCase(); + holder.innerHTML = ""; + const items = state.branches.filter((b) => !q || b.toLowerCase().indexOf(q) >= 0); + if (!items.length) { holder.innerHTML = ''; return; } + for (const b of items) { + const it = document.createElement("div"); + it.className = "menu-item" + (b === state.currentBranch ? " is-current" : ""); + it.textContent = b + (b === state.currentBranch ? " (current)" : ""); + it.onclick = () => { closeMenu(); if (b !== state.currentBranch) post("checkoutBranch", { branch: b }); }; + holder.appendChild(it); + } + }; + filter.oninput = draw; draw(); + setTimeout(() => filter.focus(), 0); + }); +}; + +$("repoCell").onclick = () => { + openMenu($("repoCell"), (m) => { + const it = document.createElement("div"); + it.className = "menu-item is-current"; + it.textContent = state.repository ? state.repository.path : "No repository open"; + m.appendChild(it); + }); +}; + +/* ---------- inbound messages ---------- */ +window.addEventListener("message", (ev) => { + const msg = ev.data || {}; + switch (msg.command || msg.type) { + case "updateChanges": { + state.changes = msg.changes || []; + const paths = new Set(state.changes.map((c) => c.path)); + if (state.selectedFiles.size === 0) state.changes.forEach((c) => state.selectedFiles.add(c.path)); + else state.selectedFiles = new Set(Array.from(state.selectedFiles).filter((p) => paths.has(p))); + if (state.selectedPath && !paths.has(state.selectedPath)) { + state.selectedPath = null; + $("diffHeader").hidden = true; + $("diffBody").innerHTML = '
No file selected
'; + } + renderChanges(); + break; + } + case "updateHistory": + state.history = msg.history || []; + renderHistory(); + break; + case "updateBranches": + state.branches = msg.branches || []; + state.currentBranch = msg.currentBranch || null; + state.branchActivity = msg.branchActivity || {}; + renderToolbar(); updateCommitBtn(); + break; + case "updateRepository": + state.repository = msg.repository || null; + renderToolbar(); setAvatar(); + break; + case "updateRemoteStatus": + state.remote = msg.remoteStatus || null; + renderToolbar(); + break; + case "workingDiff": + if (msg.payload && msg.payload.path === state.selectedPath) renderDiff(msg.payload.diff); + break; + case "fileDiff": + if (msg.payload) renderDiff(msg.payload.diff); + break; + case "error": + if (state.selectedPath) $("diffBody").innerHTML = '
' + esc(msg.message || "Error") + "
"; + break; + } +}); + +/* ---------- avatar ---------- */ +function setAvatar() { + const s = (state.repository && state.repository.name) || "?"; + $("avatar").textContent = s.slice(0, 2); +} +setAvatar(); +renderToolbar(); +post("ready"); +`; From f538082fa196912d7e75b1080c7d0e6c75551943 Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 04:44:21 +0200 Subject: [PATCH 04/33] fix(timeline): stop tab panes overlapping; collapse diff pane when idle - global [hidden] rule so .tabpane display:flex no longer defeats it (Changes and History were rendering on top of each other) - hide the right pane and let the left column go full-width when no file is selected, instead of showing an empty "No file selected" block Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../timeline/services/webview-html.service.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5687304..8ffe3ed 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.7", + "version": "1.1.8", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 6d4a799..4aa76b4 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -163,6 +163,7 @@ const ICON = { const STYLES = ` * { box-sizing: border-box; margin: 0; padding: 0; } +[hidden] { display: none !important; } :root { color-scheme: light dark; } html, body { height: 100%; } body { @@ -208,6 +209,7 @@ button, input, textarea { font: inherit; color: inherit; } background: var(--vscode-sideBar-background); border-right: 1px solid var(--vscode-panel-border); } +#body.no-diff #left { flex: 1 1 auto; border-right: 0; } #right { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; } /* ---- tabs ---- */ @@ -495,10 +497,16 @@ function renderChanges() { } updateCommitBtn(); } +function updateLayout() { + const show = !!state.selectedPath; + $("right").hidden = !show; + $("body").classList.toggle("no-diff", !show); +} function selectFile(p) { state.selectedPath = p; state.selectedCommit = null; renderChanges(); + updateLayout(); $("diffHeader").hidden = false; $("diffPath").textContent = p; $("diffBody").innerHTML = '
Loading…
'; @@ -659,9 +667,9 @@ window.addEventListener("message", (ev) => { if (state.selectedPath && !paths.has(state.selectedPath)) { state.selectedPath = null; $("diffHeader").hidden = true; - $("diffBody").innerHTML = '
No file selected
'; } renderChanges(); + updateLayout(); break; } case "updateHistory": @@ -701,5 +709,6 @@ function setAvatar() { } setAvatar(); renderToolbar(); +updateLayout(); post("ready"); `; From c4ffd184b6ebae971eff60cd4ac0463c366e0f21 Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 04:48:44 +0200 Subject: [PATCH 05/33] feat(timeline): GitHub account avatar + keep file list visible - send updateAccounts (active account login/avatar) on refresh and show the GitHub avatar image in the commit box, falling back to initials - give #fileList a min-height and make the Changes pane scroll, so the file list stays visible even when the view is docked in a short panel - shrink the default Description height; pin the commit box to the bottom - check newly appeared changed files by default, preserve prior choices Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../services/message-handler.service.ts | 12 ++++++ .../timeline/services/webview-html.service.ts | 38 +++++++++++++++---- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 8ffe3ed..0e1a346 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.8", + "version": "1.1.9", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/message-handler.service.ts b/src/webviews/timeline/services/message-handler.service.ts index 4bd2a7b..2379f5d 100644 --- a/src/webviews/timeline/services/message-handler.service.ts +++ b/src/webviews/timeline/services/message-handler.service.ts @@ -305,6 +305,18 @@ export class MessageHandlerService { repository: data.repository, }); + const active = this.accounts.getActiveAccount(); + this.view.webview.postMessage({ + command: "updateAccounts", + activeAccount: active + ? { + login: active.login, + name: active.name, + avatarUrl: active.avatarUrl, + } + : null, + }); + this.view.webview.postMessage({ command: "updateRemoteStatus", remoteStatus: data.remoteStatus, diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 4aa76b4..c50628a 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -225,7 +225,7 @@ button, input, textarea { font: inherit; color: inherit; } font-size: 11px; padding: 0 5px; border-radius: 8px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } -.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; } +.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; } /* ---- changed files ---- */ #filterWrap { flex: 0 0 auto; padding: 8px; } @@ -240,7 +240,7 @@ button, input, textarea { font: inherit; color: inherit; } padding: 5px 10px; font-size: 12px; color: var(--vscode-descriptionForeground); border-bottom: 1px solid var(--vscode-panel-border); cursor: pointer; } -#fileList { flex: 1 1 auto; overflow: auto; min-height: 0; } +#fileList { flex: 1 1 auto; overflow: auto; min-height: 84px; } .file-row { display: flex; align-items: center; gap: 8px; padding: 3px 10px; cursor: pointer; white-space: nowrap; @@ -271,21 +271,23 @@ button, input, textarea { font: inherit; color: inherit; } #commitBox { flex: 0 0 auto; padding: 8px; border-top: 1px solid var(--vscode-panel-border); display: flex; flex-direction: column; gap: 6px; + background: var(--vscode-sideBar-background); position: sticky; bottom: 0; } .commit-summary { display: flex; align-items: center; gap: 6px; } .avatar { - flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; + flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; text-transform: uppercase; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } +.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } #summary, #description { width: 100%; padding: 5px 8px; border-radius: 2px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border, transparent); } #summary:focus, #description:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } -#description { resize: vertical; min-height: 48px; max-height: 140px; } +#description { resize: vertical; min-height: 34px; max-height: 140px; } #commitBtn { width: 100%; padding: 6px 10px; border: 0; border-radius: 2px; cursor: pointer; background: var(--vscode-button-background); color: var(--vscode-button-foreground); @@ -375,6 +377,7 @@ const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ( const state = { repository: (window.__INITIAL__ && window.__INITIAL__.repository) || null, + account: null, changes: [], history: [], branches: [], @@ -660,10 +663,17 @@ window.addEventListener("message", (ev) => { const msg = ev.data || {}; switch (msg.command || msg.type) { case "updateChanges": { + const prevPaths = new Set(state.changes.map((c) => c.path)); state.changes = msg.changes || []; const paths = new Set(state.changes.map((c) => c.path)); - if (state.selectedFiles.size === 0) state.changes.forEach((c) => state.selectedFiles.add(c.path)); - else state.selectedFiles = new Set(Array.from(state.selectedFiles).filter((p) => paths.has(p))); + // Auto-check newly appeared files (GitHub Desktop checks everything by default), + // keep the user's choices for files that were already listed. + state.selectedFiles = new Set( + Array.from(state.selectedFiles).filter((p) => paths.has(p)), + ); + state.changes.forEach((c) => { + if (!prevPaths.has(c.path)) state.selectedFiles.add(c.path); + }); if (state.selectedPath && !paths.has(state.selectedPath)) { state.selectedPath = null; $("diffHeader").hidden = true; @@ -686,6 +696,10 @@ window.addEventListener("message", (ev) => { state.repository = msg.repository || null; renderToolbar(); setAvatar(); break; + case "updateAccounts": + state.account = msg.activeAccount || null; + setAvatar(); + break; case "updateRemoteStatus": state.remote = msg.remoteStatus || null; renderToolbar(); @@ -704,8 +718,16 @@ window.addEventListener("message", (ev) => { /* ---------- avatar ---------- */ function setAvatar() { - const s = (state.repository && state.repository.name) || "?"; - $("avatar").textContent = s.slice(0, 2); + const a = state.account; + const el = $("avatar"); + if (a && a.avatarUrl) { + el.innerHTML = ''; + el.title = a.login || ""; + return; + } + const s = (a && (a.name || a.login)) || (state.repository && state.repository.name) || "?"; + el.textContent = s.slice(0, 2); + el.title = (a && a.login) || ""; } setAvatar(); renderToolbar(); From 65bc993aa5e3177b03b2fb19aeed3ab532451029 Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 04:53:04 +0200 Subject: [PATCH 06/33] style(timeline): compact density pass so the file list has room Match GitHub Desktop's tighter spacing: shorter toolbar (42px), smaller tab/label/input fonts, less padding everywhere, 20px avatar, 30px min Description. File list keeps flex priority with a small 44px floor; hide filter / summary rows entirely when there are no changes. Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../timeline/services/webview-html.service.ts | 56 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 0e1a346..456b741 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.9", + "version": "1.1.10", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index c50628a..5cdc5bb 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -178,13 +178,13 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- toolbar ---- */ #toolbar { - display: flex; flex: 0 0 auto; height: 50px; + display: flex; flex: 0 0 auto; height: 42px; background: var(--vscode-sideBar-background); border-bottom: 1px solid var(--vscode-panel-border); } .cell { - display: flex; align-items: center; gap: 8px; - flex: 1 1 0; min-width: 0; padding: 0 12px; + display: flex; align-items: center; gap: 7px; + flex: 1 1 0; min-width: 0; padding: 0 10px; background: transparent; border: 0; border-right: 1px solid var(--vscode-panel-border); cursor: pointer; text-align: left; @@ -193,9 +193,9 @@ button, input, textarea { font: inherit; color: inherit; } .cell:hover { background: var(--vscode-list-hoverBackground); } .cell-ico { flex: 0 0 auto; display: flex; opacity: .8; } .cell-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; } -.cell-label { font-size: 11px; color: var(--vscode-descriptionForeground); } -.cell-value { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.cell-sub { font-weight: 400; font-size: 11px; color: var(--vscode-descriptionForeground); } +.cell-label { font-size: 10px; color: var(--vscode-descriptionForeground); } +.cell-value { font-weight: 600; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.cell-sub { font-weight: 400; font-size: 10px; color: var(--vscode-descriptionForeground); } .cell-caret { flex: 0 0 auto; opacity: .6; } .cell-count { flex: 0 0 auto; font-size: 11px; padding: 1px 5px; border-radius: 8px; @@ -215,34 +215,34 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- tabs ---- */ #tabs { display: flex; flex: 0 0 auto; border-bottom: 1px solid var(--vscode-panel-border); } .tab { - flex: 1 1 0; padding: 8px 4px; background: transparent; border: 0; - border-bottom: 2px solid transparent; cursor: pointer; + flex: 1 1 0; padding: 5px 4px; background: transparent; border: 0; + border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; color: var(--vscode-descriptionForeground); font-weight: 500; } .tab:hover { background: var(--vscode-list-hoverBackground); } .tab.is-active { color: var(--vscode-foreground); border-bottom-color: var(--vscode-focusBorder); } .badge { - font-size: 11px; padding: 0 5px; border-radius: 8px; + font-size: 10px; padding: 0 5px; border-radius: 8px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } -.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; } +.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow: hidden; } /* ---- changed files ---- */ -#filterWrap { flex: 0 0 auto; padding: 8px; } +#filterWrap { flex: 0 0 auto; padding: 6px; } #filter { - width: 100%; padding: 4px 8px; border-radius: 2px; + width: 100%; padding: 3px 7px; border-radius: 2px; font-size: 12px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border, transparent); } #filter:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } #allRow { - flex: 0 0 auto; display: flex; align-items: center; gap: 8px; - padding: 5px 10px; font-size: 12px; color: var(--vscode-descriptionForeground); + flex: 0 0 auto; display: flex; align-items: center; gap: 7px; + padding: 3px 8px; font-size: 11px; color: var(--vscode-descriptionForeground); border-bottom: 1px solid var(--vscode-panel-border); cursor: pointer; } -#fileList { flex: 1 1 auto; overflow: auto; min-height: 84px; } +#fileList { flex: 1 1 0; overflow: auto; min-height: 44px; } .file-row { - display: flex; align-items: center; gap: 8px; padding: 3px 10px; cursor: pointer; + display: flex; align-items: center; gap: 7px; padding: 2px 8px; cursor: pointer; white-space: nowrap; } .file-row:hover { background: var(--vscode-list-hoverBackground); } @@ -269,27 +269,27 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- commit box ---- */ #commitBox { - flex: 0 0 auto; padding: 8px; border-top: 1px solid var(--vscode-panel-border); - display: flex; flex-direction: column; gap: 6px; - background: var(--vscode-sideBar-background); position: sticky; bottom: 0; + flex: 0 0 auto; padding: 6px; border-top: 1px solid var(--vscode-panel-border); + display: flex; flex-direction: column; gap: 5px; + background: var(--vscode-sideBar-background); } .commit-summary { display: flex; align-items: center; gap: 6px; } .avatar { - flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; overflow: hidden; + flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; text-transform: uppercase; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } #summary, #description { - width: 100%; padding: 5px 8px; border-radius: 2px; + width: 100%; padding: 4px 7px; border-radius: 2px; font-size: 12px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border, transparent); } #summary:focus, #description:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } -#description { resize: vertical; min-height: 34px; max-height: 140px; } +#description { resize: vertical; min-height: 30px; max-height: 140px; } #commitBtn { - width: 100%; padding: 6px 10px; border: 0; border-radius: 2px; cursor: pointer; + width: 100%; padding: 5px 10px; border: 0; border-radius: 2px; cursor: pointer; font-size: 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } #commitBtn:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } @@ -463,11 +463,13 @@ function visibleChanges() { function renderChanges() { const list = $("fileList"); const vis = visibleChanges(); + const has = state.changes.length > 0; $("changesBadge").textContent = state.changes.length; - $("noChanges").hidden = state.changes.length > 0; - $("commitBox").style.display = state.changes.length > 0 ? "" : "none"; - $("allRow").style.display = state.changes.length > 0 ? "" : "none"; - $("filterWrap").style.display = state.changes.length > 0 ? "" : "none"; + $("noChanges").hidden = has; + $("fileList").hidden = !has; + $("commitBox").hidden = !has; + $("allRow").hidden = !has; + $("filterWrap").hidden = !has; const n = state.selectedFiles.size; $("allText").textContent = n + " of " + state.changes.length + " file" + (state.changes.length === 1 ? "" : "s") + " to commit"; From 9fc874ecf90a32a48cf9da090b993bfb54631d1a Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 05:02:38 +0200 Subject: [PATCH 07/33] fix(branches): never check out a remote ref directly (detached HEAD) The branch dropdown was fed git.branch().all, which includes remotes/origin/* tracking refs. Selecting one ran `git checkout remotes/origin/dev` and detached HEAD. - git-operations: expose local branches plus remote-only branches by their short name, never the remotes// form - message-handler: strip any remotes// prefix before checkout Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../timeline/services/git-operations.service.ts | 16 ++++++++++++++-- .../timeline/services/message-handler.service.ts | 10 ++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 456b741..1617680 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.10", + "version": "1.1.11", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/git-operations.service.ts b/src/webviews/timeline/services/git-operations.service.ts index 42347ff..bb5ef18 100644 --- a/src/webviews/timeline/services/git-operations.service.ts +++ b/src/webviews/timeline/services/git-operations.service.ts @@ -85,9 +85,21 @@ export class GitOperationsService { const hasMoreCommits = log.all.length === 50; + // Local branches, plus remote branches that have no local counterpart yet + // (shown by their short name). Never expose "remotes//" refs + // directly: checking one out puts the repo in detached HEAD. + const localBranches = branch.all.filter((b) => !b.startsWith("remotes/")); + const localSet = new Set(localBranches); + const remoteOnly = new Set(); + for (const b of branch.all) { + const m = b.match(/^remotes\/[^/]+\/(.+)$/); + if (m && m[1] !== "HEAD" && !localSet.has(m[1])) remoteOnly.add(m[1]); + } + const branchList = [...localBranches, ...remoteOnly]; + // Get branch activity dates const branchActivity: Record = {}; - for (const branchName of branch.all) { + for (const branchName of branchList) { try { const lastCommit = await git.raw([ "log", @@ -105,7 +117,7 @@ export class GitOperationsService { changes, commits, hasMoreCommits, - branches: branch.all, + branches: branchList, currentBranch: branch.current, branchActivity, repository: { diff --git a/src/webviews/timeline/services/message-handler.service.ts b/src/webviews/timeline/services/message-handler.service.ts index 2379f5d..d178b81 100644 --- a/src/webviews/timeline/services/message-handler.service.ts +++ b/src/webviews/timeline/services/message-handler.service.ts @@ -499,11 +499,17 @@ export class MessageHandlerService { const repository = getPrimaryRepository(this.repositories); if (!repository || !branchName) return; + // Guard against detached HEAD: never check out a "remotes//" + // ref directly. Fall back to the short name so git creates/switches to the + // local tracking branch instead. + const remoteMatch = branchName.match(/^remotes\/[^/]+\/(.+)$/); + const target = remoteMatch ? remoteMatch[1] : branchName; + try { const git = simpleGit(repository.localPath); - await git.checkout(branchName); + await git.checkout(target); await this.handleRefresh(); - vscode.window.showInformationMessage(`Checked out branch: ${branchName}`); + vscode.window.showInformationMessage(`Checked out branch: ${target}`); } catch (error) { vscode.window.showErrorMessage(`Failed to checkout branch: ${error}`); } From c77d8541952735849e2648b42bca3ff54006c3dc Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 05:05:43 +0200 Subject: [PATCH 08/33] fix(remote-status): detect upstream via rev-parse @{upstream} The old `git branch -vv` bracket parser missed the tracking branch in some formats (and could false-positive on brackets in commit subjects), so a published branch showed "Publish branch / not on the remote yet". Use `git rev-parse @{upstream}` and `git rev-list --left-right --count`. Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- .../services/git-operations.service.ts | 56 +++++++++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 1617680..ba90f60 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.11", + "version": "1.1.12", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/git-operations.service.ts b/src/webviews/timeline/services/git-operations.service.ts index bb5ef18..e4e8524 100644 --- a/src/webviews/timeline/services/git-operations.service.ts +++ b/src/webviews/timeline/services/git-operations.service.ts @@ -430,34 +430,42 @@ export class GitOperationsService { } try { - // Check if current branch has a remote tracking branch - const trackingInfo = await git.raw(["branch", "-vv", "--no-color"]); - const lines = trackingInfo.split("\n"); + // Ask git directly whether the current branch has an upstream. This is + // reliable regardless of `git branch -vv` formatting or brackets in commit + // subjects (the old parser could misread both ways). let remoteBranch: string | null = null; let ahead = 0; let behind = 0; - for (const line of lines) { - if (line.trim().startsWith("*")) { - // Parse remote tracking info - const match = line.match(/\[([^\]]+)\]/); - if (match) { - const trackingPart = match[1]; - const remoteParts = trackingPart.split(":"); - remoteBranch = remoteParts[0].trim(); - - // Parse ahead/behind - const aheadMatch = trackingPart.match(/ahead (\d+)/); - const behindMatch = trackingPart.match(/behind (\d+)/); - - if (aheadMatch) { - ahead = parseInt(aheadMatch[1], 10); - } - if (behindMatch) { - behind = parseInt(behindMatch[1], 10); - } - } - break; + try { + remoteBranch = ( + await git.raw([ + "rev-parse", + "--abbrev-ref", + "--symbolic-full-name", + "@{upstream}", + ]) + ).trim() || null; + } catch { + remoteBranch = null; + } + + if (remoteBranch) { + try { + const counts = ( + await git.raw([ + "rev-list", + "--left-right", + "--count", + "@{upstream}...HEAD", + ]) + ) + .trim() + .split(/\s+/); + behind = parseInt(counts[0], 10) || 0; + ahead = parseInt(counts[1], 10) || 0; + } catch { + // leave ahead/behind at 0 } } From b1e42c2bf9b71900526ff9a74efd978fd4e40a2c Mon Sep 17 00:00:00 2001 From: AlyNotMe Date: Thu, 3 Sep 2026 13:28:45 +0200 Subject: [PATCH 09/33] fix(toolbar): default sync cell to Fetch unless positively unpublished Show "Publish branch" only when a remote exists, git reported no upstream, and there is nothing ahead/behind. Any uncertain state (status not yet received) now falls back to "Fetch origin". Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- src/webviews/timeline/services/webview-html.service.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ba90f60..916e467 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-github-desktop", "displayName": "GitHub Desktop", "description": "A comprehensive VS Code extension that brings GitHub Desktop's complete workflow experience, featuring React-based UI, advanced commit management, branch operations, and multi-account support.", - "version": "1.1.12", + "version": "1.1.13", "publisher": "BetaVersion", "icon": "media/icon.png", "repository": { diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 5cdc5bb..7847307 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -400,8 +400,11 @@ function renderToolbar() { const r = state.remote || {}; const ico = $("syncIco"), label = $("syncLabel"), sub = $("syncSub"), count = $("syncCount"); const svg = { fetch: ${JSON.stringify(ICON.fetch)}, push: ${JSON.stringify(ICON.push)}, pull: ${JSON.stringify(ICON.pull)}, publish: ${JSON.stringify(ICON.publish)} }; + // Only claim "not published" when we positively know: a remote exists and + // git reported no upstream. Anything uncertain (status not received yet) + // defaults to Fetch rather than a misleading "Publish branch". let mode = "fetch"; - if (!r.hasRemote || r.isPublished === false) mode = "publish"; + if (r.hasRemote === true && r.isPublished === false && r.ahead === 0 && r.behind === 0) mode = "publish"; else if (r.behind > 0) mode = "pull"; else if (r.ahead > 0) mode = "push"; From d7b2dc27ce78055d91d86794ae8fb8f15743b3ad Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:50:38 +0200 Subject: [PATCH 10/33] chore: remove dead React timeline superseded by vanilla webview The timeline webview was rebuilt as a self-contained vanilla HTML/JS view (webview-html.service.ts). The old React app (bootstrap.tsx -> app/App.tsx -> components/Timeline/**), its hooks, the mock git.service, the DI/domain layers, and the unregistered changes/history tree providers were still built but never loaded. Removed. Kept: app/bridge.ts and app/components/CommitDetailPanel/** (used by the commit-detail and history-explorer bundles), and the History Explorer feature untouched. Co-Authored-By: Claude Sonnet 5 --- src/ui/tree-views/changes-provider.ts | 98 --- src/ui/tree-views/history-provider.ts | 101 --- src/webviews/app/App.tsx | 105 --- .../components/Timeline/BranchDropdown.tsx | 748 ------------------ .../app/components/Timeline/Timeline.tsx | 133 ---- .../Timeline/components/ChangesView.tsx | 201 ----- .../Timeline/components/CommitListItem.tsx | 180 ----- .../Timeline/components/CommitSection.tsx | 107 --- .../Timeline/components/ContextMenu.tsx | 191 ----- .../components/ContextMenuAdjusted.tsx | 225 ------ .../Timeline/components/FileChangesList.tsx | 213 ----- .../Timeline/components/HistoryView.tsx | 90 --- .../Timeline/components/LoadMoreSection.tsx | 50 -- .../Timeline/components/NewBranchDialog.tsx | 77 -- .../Timeline/components/TimelineHeader.tsx | 395 --------- .../Timeline/components/TimelineTabs.tsx | 57 -- .../components/UncommittedChangesDialog.tsx | 80 -- .../Timeline/hooks/useTimelineActions.ts | 200 ----- .../Timeline/hooks/useTimelineState.ts | 76 -- src/webviews/app/components/Timeline/index.ts | 24 - .../Timeline/types/timeline.types.ts | 41 - .../Timeline/utils/timeline.utils.ts | 54 -- .../app/domain/entities/branch.entity.ts | 36 - .../app/domain/entities/commit.entity.ts | 62 -- .../app/domain/entities/file-change.entity.ts | 52 -- .../app/domain/value-objects/author.vo.ts | 38 - .../domain/value-objects/branch-name.vo.ts | 63 -- .../domain/value-objects/commit-date.vo.ts | 54 -- .../app/domain/value-objects/commit-id.vo.ts | 26 - .../domain/value-objects/commit-message.vo.ts | 38 - .../app/domain/value-objects/file-path.vo.ts | 45 -- .../domain/value-objects/file-status.vo.ts | 76 -- src/webviews/app/hooks/useGitOperations.ts | 80 -- src/webviews/app/services/git.service.ts | 29 - .../bridge/vscode-bridge.interface.ts | 53 -- .../config/service-registration.ts | 24 - .../infrastructure/di/service-container.ts | 49 -- .../infrastructure/di/service-context.tsx | 20 - .../infrastructure/di/service-locator.ts | 57 -- src/webviews/app/shared/utils/date.utils.ts | 73 -- src/webviews/app/shared/utils/file.utils.ts | 84 -- src/webviews/bootstrap.tsx | 36 - vite.webview.config.ts | 1 - 43 files changed, 4442 deletions(-) delete mode 100644 src/ui/tree-views/changes-provider.ts delete mode 100644 src/ui/tree-views/history-provider.ts delete mode 100644 src/webviews/app/App.tsx delete mode 100644 src/webviews/app/components/Timeline/BranchDropdown.tsx delete mode 100644 src/webviews/app/components/Timeline/Timeline.tsx delete mode 100644 src/webviews/app/components/Timeline/components/ChangesView.tsx delete mode 100644 src/webviews/app/components/Timeline/components/CommitListItem.tsx delete mode 100644 src/webviews/app/components/Timeline/components/CommitSection.tsx delete mode 100644 src/webviews/app/components/Timeline/components/ContextMenu.tsx delete mode 100644 src/webviews/app/components/Timeline/components/ContextMenuAdjusted.tsx delete mode 100644 src/webviews/app/components/Timeline/components/FileChangesList.tsx delete mode 100644 src/webviews/app/components/Timeline/components/HistoryView.tsx delete mode 100644 src/webviews/app/components/Timeline/components/LoadMoreSection.tsx delete mode 100644 src/webviews/app/components/Timeline/components/NewBranchDialog.tsx delete mode 100644 src/webviews/app/components/Timeline/components/TimelineHeader.tsx delete mode 100644 src/webviews/app/components/Timeline/components/TimelineTabs.tsx delete mode 100644 src/webviews/app/components/Timeline/components/UncommittedChangesDialog.tsx delete mode 100644 src/webviews/app/components/Timeline/hooks/useTimelineActions.ts delete mode 100644 src/webviews/app/components/Timeline/hooks/useTimelineState.ts delete mode 100644 src/webviews/app/components/Timeline/index.ts delete mode 100644 src/webviews/app/components/Timeline/types/timeline.types.ts delete mode 100644 src/webviews/app/components/Timeline/utils/timeline.utils.ts delete mode 100644 src/webviews/app/domain/entities/branch.entity.ts delete mode 100644 src/webviews/app/domain/entities/commit.entity.ts delete mode 100644 src/webviews/app/domain/entities/file-change.entity.ts delete mode 100644 src/webviews/app/domain/value-objects/author.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/branch-name.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/commit-date.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/commit-id.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/commit-message.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/file-path.vo.ts delete mode 100644 src/webviews/app/domain/value-objects/file-status.vo.ts delete mode 100644 src/webviews/app/hooks/useGitOperations.ts delete mode 100644 src/webviews/app/services/git.service.ts delete mode 100644 src/webviews/app/shared/infrastructure/bridge/vscode-bridge.interface.ts delete mode 100644 src/webviews/app/shared/infrastructure/config/service-registration.ts delete mode 100644 src/webviews/app/shared/infrastructure/di/service-container.ts delete mode 100644 src/webviews/app/shared/infrastructure/di/service-context.tsx delete mode 100644 src/webviews/app/shared/infrastructure/di/service-locator.ts delete mode 100644 src/webviews/app/shared/utils/date.utils.ts delete mode 100644 src/webviews/app/shared/utils/file.utils.ts delete mode 100644 src/webviews/bootstrap.tsx diff --git a/src/ui/tree-views/changes-provider.ts b/src/ui/tree-views/changes-provider.ts deleted file mode 100644 index ec2f446..0000000 --- a/src/ui/tree-views/changes-provider.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as path from "path"; -import * as vscode from "vscode"; -import simpleGit from "simple-git"; -import { RepositoryManager } from "../../core/repositories/repository-manager"; -import { getPrimaryRepository } from "../../shared/utils/repo-selection"; - -class ChangeItem extends vscode.TreeItem { - constructor( - label: string, - description: string | undefined, - resourceUri: vscode.Uri | undefined, - ) { - super(label, vscode.TreeItemCollapsibleState.None); - this.description = description; - this.resourceUri = resourceUri; - this.iconPath = new vscode.ThemeIcon("git-commit"); - this.contextValue = "githubDesktop.change"; - } -} - -export class ChangesProvider - implements vscode.TreeDataProvider -{ - private readonly _onDidChangeTreeData = new vscode.EventEmitter(); - readonly onDidChangeTreeData = this._onDidChangeTreeData.event; - - constructor(private readonly repositories: RepositoryManager) { - this.repositories.onDidChangeRepositories(() => this.refresh()); - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: vscode.TreeItem): vscode.TreeItem { - return element; - } - - async getChildren(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - return [ - this.createInfoItem("Open a Git repository to see local changes."), - ]; - } - - try { - const git = simpleGit(repository.localPath); - const status = await git.status(); - const items: vscode.TreeItem[] = []; - - const add = (filePath: string, state: string) => { - const absolutePath = path.join(repository.localPath, filePath); - const relPath = - path.relative(repository.localPath, absolutePath) || filePath; - const label = `${state} ${relPath}`.trim(); - const item = new ChangeItem( - label, - undefined, - vscode.Uri.file(absolutePath), - ); - items.push(item); - }; - - if (status.files.length === 0) { - return [this.createInfoItem("Working tree clean.")]; - } - - for (const file of status.files) { - const state = this.formatStatusCode(file.index, file.working_dir); - add(file.path, state); - } - - return items; - } catch (error) { - return [ - this.createInfoItem( - "Unable to read git status for the current repository.", - ), - ]; - } - } - - private formatStatusCode(index: string, workingDir: string): string { - const combined = `${index ?? ""}${workingDir ?? ""}`.trim(); - return combined.length > 0 ? combined : "�"; - } - - private createInfoItem(message: string): vscode.TreeItem { - const item = new vscode.TreeItem( - message, - vscode.TreeItemCollapsibleState.None, - ); - item.iconPath = new vscode.ThemeIcon("info"); - item.contextValue = "githubDesktop.info"; - return item; - } -} diff --git a/src/ui/tree-views/history-provider.ts b/src/ui/tree-views/history-provider.ts deleted file mode 100644 index a220753..0000000 --- a/src/ui/tree-views/history-provider.ts +++ /dev/null @@ -1,101 +0,0 @@ -import * as vscode from "vscode"; -import simpleGit from "simple-git"; -import { RepositoryManager } from "../../core/repositories/repository-manager"; -import { getPrimaryRepository } from "../../shared/utils/repo-selection"; - -const MAX_COMMITS = 50; - -class HistoryItem extends vscode.TreeItem { - constructor(label: string, description: string, tooltip: string) { - super(label, vscode.TreeItemCollapsibleState.None); - this.description = description; - this.tooltip = tooltip; - this.iconPath = new vscode.ThemeIcon("history"); - this.contextValue = "githubDesktop.historyEntry"; - } -} - -export class HistoryProvider - implements vscode.TreeDataProvider -{ - private readonly _onDidChangeTreeData = new vscode.EventEmitter(); - readonly onDidChangeTreeData = this._onDidChangeTreeData.event; - - constructor(private readonly repositories: RepositoryManager) { - this.repositories.onDidChangeRepositories(() => this.refresh()); - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: vscode.TreeItem): vscode.TreeItem { - return element; - } - - async getChildren(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - return [ - this.createInfoItem("Open a Git repository to see commit history."), - ]; - } - - try { - const git = simpleGit(repository.localPath); - const log = await git.log({ maxCount: MAX_COMMITS }); - if (log.all.length === 0) { - return [this.createInfoItem("No commits found.")]; - } - - return log.all.map((commit) => { - const when = this.formatRelativeTime(new Date(commit.date)); - const description = `${commit.author_name} � ${when}`; - const tooltip = `${commit.hash}\n${commit.author_name} <${commit.author_email}>\n${commit.date}`; - return new HistoryItem(commit.message, description, tooltip); - }); - } catch (error) { - return [ - this.createInfoItem( - "Unable to read git history for the current repository.", - ), - ]; - } - } - - private createInfoItem(message: string): vscode.TreeItem { - const item = new vscode.TreeItem( - message, - vscode.TreeItemCollapsibleState.None, - ); - item.iconPath = new vscode.ThemeIcon("info"); - item.contextValue = "githubDesktop.info"; - return item; - } - - private formatRelativeTime(date: Date): string { - const diff = Date.now() - date.getTime(); - const seconds = Math.round(diff / 1000); - if (seconds < 60) { - return "just now"; - } - const minutes = Math.round(seconds / 60); - if (minutes < 60) { - return `${minutes} minute${minutes === 1 ? "" : "s"} ago`; - } - const hours = Math.round(minutes / 60); - if (hours < 24) { - return `${hours} hour${hours === 1 ? "" : "s"} ago`; - } - const days = Math.round(hours / 24); - if (days < 30) { - return `${days} day${days === 1 ? "" : "s"} ago`; - } - const months = Math.round(days / 30); - if (months < 12) { - return `${months} month${months === 1 ? "" : "s"} ago`; - } - const years = Math.round(months / 12); - return `${years} year${years === 1 ? "" : "s"} ago`; - } -} diff --git a/src/webviews/app/App.tsx b/src/webviews/app/App.tsx deleted file mode 100644 index c5c453b..0000000 --- a/src/webviews/app/App.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { ThemeProvider, createTheme } from '@mui/material'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { VSCodeBridge, InitialWebviewState } from './bridge'; -import { Timeline } from './components/Timeline'; - -interface AppProps { - bridge: VSCodeBridge; - initialState: InitialWebviewState; -} - -const queryClient = new QueryClient(); - -export const App: React.FC = ({ bridge, initialState }) => { - const [state, setState] = useState(initialState); - const [branchActivity, setBranchActivity] = useState>({}); - const [remoteStatus, setRemoteStatus] = useState(initialState.remoteStatus || null); - - useEffect(() => { - // Send ready message to request initial data - bridge.sendMessage('ready'); - - bridge.onMessage((message) => { - switch (message.command) { - case 'updateChanges': - setState((prev) => ({ ...prev, changes: message.changes })); - break; - case 'updateHistory': - setState((prev) => ({ - ...prev, - history: message.history, - hasMoreCommits: message.hasMoreCommits, - commitsOffset: message.offset - })); - break; - case 'updateBranches': - setState((prev) => ({ - ...prev, - branches: message.branches, - currentBranch: message.currentBranch, - })); - if (message.branchActivity) { - setBranchActivity(message.branchActivity); - } - break; - case 'updateRepository': - setState((prev) => ({ ...prev, repository: message.repository })); - break; - case 'updateRemoteStatus': - setRemoteStatus(message.remoteStatus); - break; - case 'updateAccounts': - setState((prev) => ({ - ...prev, - accounts: message.accounts, - activeAccount: message.activeAccount, - })); - break; - } - }); - }, [bridge]); - - // Create a minimal theme that respects VS Code CSS variables - const theme = createTheme({ - palette: { - mode: 'dark', // We'll use CSS variables instead of theme colors - }, - typography: { - fontFamily: 'var(--vscode-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif)', - }, - components: { - MuiCssBaseline: { - styleOverrides: { - body: { - margin: 0, - padding: 0, - backgroundColor: 'var(--vscode-sideBar-background)', - color: 'var(--vscode-foreground)', - fontFamily: 'var(--vscode-font-family)', - fontSize: 'var(--vscode-font-size, 13px)', - }, - }, - }, - }, - }); - - return ( - - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/BranchDropdown.tsx b/src/webviews/app/components/Timeline/BranchDropdown.tsx deleted file mode 100644 index e2e4000..0000000 --- a/src/webviews/app/components/Timeline/BranchDropdown.tsx +++ /dev/null @@ -1,748 +0,0 @@ -import React, { useState, useRef, useEffect } from "react"; -import { - Box, - Typography, - TextField, - List, - ListItem, - ListItemText, - ListItemIcon, - Button, - Divider, - Popover, - IconButton, - InputAdornment, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from "@mui/material"; -import { - AccountTree as BranchIcon, - KeyboardArrowDown as ArrowDownIcon, - Search as SearchIcon, - MergeType as MergeIcon, - GitHub as PRIcon, - Close as CloseIcon, - Add as AddIcon, -} from "@mui/icons-material"; -import { VSCodeBridge } from "../../bridge"; -import { NewBranchDialog } from "./components/NewBranchDialog"; - -interface BranchDropdownProps { - currentBranch: string | null; - branches: string[]; - branchActivity: Record; - bridge: VSCodeBridge; -} - -interface BranchGroups { - default: string[]; - recent: string[]; - other: string[]; -} - -export const BranchDropdown: React.FC = ({ - currentBranch, - branches, - branchActivity, - bridge, -}) => { - const [anchorEl, setAnchorEl] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); - const [mergeDialogOpen, setMergeDialogOpen] = useState(false); - const [mergeSearchTerm, setMergeSearchTerm] = useState(""); - const [selectedMergeBranch, setSelectedMergeBranch] = useState( - null - ); - const [newBranchDialogOpen, setNewBranchDialogOpen] = useState(false); - const [newBranchName, setNewBranchName] = useState(""); - const buttonRef = useRef(null); - - const open = Boolean(anchorEl); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - setSearchTerm(""); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleBranchSelect = (branch: string) => { - if (branch !== currentBranch) { - bridge.sendMessage("checkoutBranch", { branch }); - } - handleClose(); - }; - - const handleMergeDialogOpen = () => { - setMergeDialogOpen(true); - setMergeSearchTerm(""); - setSelectedMergeBranch(null); - handleClose(); - }; - - const handleMergeDialogClose = () => { - setMergeDialogOpen(false); - setSelectedMergeBranch(null); - setMergeSearchTerm(""); - }; - - const handleMerge = () => { - if (selectedMergeBranch && currentBranch) { - bridge.sendMessage("mergeBranch", { - fromBranch: selectedMergeBranch, - toBranch: currentBranch, - }); - handleMergeDialogClose(); - } - }; - - const handleCreatePR = () => { - if (currentBranch) { - bridge.sendMessage("createPullRequest", { branch: currentBranch }); - } - handleClose(); - }; - - const handleNewBranchDialogOpen = () => { - setNewBranchDialogOpen(true); - setNewBranchName(""); - }; - - const handleNewBranchDialogClose = () => { - setNewBranchDialogOpen(false); - setNewBranchName(""); - }; - - const handleCreateNewBranch = () => { - if (newBranchName.trim()) { - bridge.sendMessage("createBranch", { - branchName: newBranchName.trim(), - fromBranch: currentBranch - }); - handleNewBranchDialogClose(); - handleClose(); - } - }; - - // Group branches - const groupBranches = (): BranchGroups => { - // Handle undefined or null branches array - const safeBranches = branches || []; - const safeBranchActivity = branchActivity || {}; - - const defaultBranches = safeBranches.filter((branch) => - ["main", "master", "develop", "dev"].includes(branch.toLowerCase()) - ); - - const nonDefaultBranches = safeBranches.filter( - (branch) => !defaultBranches.includes(branch) && branch !== currentBranch - ); - - // Sort by activity date for recent branches - const sortedByActivity = nonDefaultBranches.sort((a, b) => { - const aActivity = safeBranchActivity[a] || ""; - const bActivity = safeBranchActivity[b] || ""; - return aActivity.localeCompare(bActivity); - }); - - const recentBranches = sortedByActivity.slice(0, 3); - const otherBranches = sortedByActivity.slice(3); - - return { - default: defaultBranches, - recent: recentBranches, - other: otherBranches, - }; - }; - - const filteredBranches = () => { - if (!searchTerm) return groupBranches(); - - const filtered = (branches || []).filter((branch) => - (branch || '').toLowerCase().includes((searchTerm || '').toLowerCase()) - ); - - return { - default: [], - recent: [], - other: filtered, - }; - }; - - const filteredMergeBranches = () => { - return (branches || []).filter( - (branch) => - branch !== currentBranch && - (branch || '').toLowerCase().includes((mergeSearchTerm || '').toLowerCase()) - ); - }; - - const branchGroups = filteredBranches(); - - const renderBranchGroup = ( - title: string, - branches: string[], - showDivider: boolean = true - ) => { - if (branches.length === 0) return null; - - return ( - - {showDivider && ( - - )} - - {title} - - {branches.map((branch) => ( - handleBranchSelect(branch)} - > - - - - - - {branch} - - {branchActivity[branch] && ( - - {branchActivity[branch]} - - )} - - } - /> - - ))} -
- ); - }; - - return ( - <> - - - - - Current branch - - - {currentBranch || 'No branch'} - - - - - - - {/* Search */} - - setSearchTerm(e.target.value)} - size="small" - InputProps={{ - startAdornment: ( - - - - ), - endAdornment: searchTerm && ( - - setSearchTerm("")} - sx={{ p: 0.25 }} - > - - - - ), - }} - sx={{ - "& .MuiOutlinedInput-root": { - bgcolor: "var(--vscode-input-background)", - color: "var(--vscode-input-foreground)", - fontSize: "13px", - "& fieldset": { - borderColor: "var(--vscode-input-border)", - }, - "&:hover fieldset": { - borderColor: "var(--vscode-input-border)", - }, - "&.Mui-focused fieldset": { - borderColor: "var(--vscode-focusBorder)", - }, - }, - "& .MuiInputBase-input::placeholder": { - color: "var(--vscode-input-placeholderForeground)", - opacity: 1, - }, - }} - /> - - - - - - {/* Content */} - - {currentBranch && ( - - - - - - {currentBranch} (current) - - } - /> - - )} - - {!searchTerm && ( - <> - {renderBranchGroup("Default", branchGroups.default)} - {renderBranchGroup("Recent", branchGroups.recent)} - {renderBranchGroup("Other", branchGroups.other)} - - )} - - {searchTerm && - renderBranchGroup("Search Results", branchGroups.other, false)} - - - {/* Actions */} - - - - {currentBranch && - currentBranch !== "main" && - currentBranch !== "master" && ( - - )} - - - - {/* Merge Dialog */} - - - Merge into {currentBranch} - - - setMergeSearchTerm(e.target.value)} - size="small" - sx={{ - mb: 2, - "& .MuiOutlinedInput-root": { - bgcolor: "var(--vscode-input-background)", - color: "var(--vscode-input-foreground)", - fontSize: "13px", - "& fieldset": { - borderColor: "var(--vscode-input-border)", - }, - "&:hover fieldset": { - borderColor: "var(--vscode-input-border)", - }, - "&.Mui-focused fieldset": { - borderColor: "var(--vscode-focusBorder)", - }, - }, - "& .MuiInputBase-input::placeholder": { - color: "var(--vscode-input-placeholderForeground)", - opacity: 1, - }, - }} - /> - - - {filteredMergeBranches().map((branch) => ( - setSelectedMergeBranch(branch)} - > - - - - - - {branch} - - {branchActivity[branch] && ( - - {branchActivity[branch]} - - )} - - } - /> - - ))} - -
- - - - - - - - {/* New Branch Dialog */} - - - ); -}; diff --git a/src/webviews/app/components/Timeline/Timeline.tsx b/src/webviews/app/components/Timeline/Timeline.tsx deleted file mode 100644 index 55449d8..0000000 --- a/src/webviews/app/components/Timeline/Timeline.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { TimelineProps } from './types/timeline.types'; -import { useTimelineState } from './hooks/useTimelineState'; -import { useTimelineActions } from './hooks/useTimelineActions'; -import { TimelineHeader } from './components/TimelineHeader'; -import { TimelineTabs } from './components/TimelineTabs'; -import { ChangesView } from './components/ChangesView'; -import { HistoryView } from './components/HistoryView'; -import { ContextMenuAdjusted } from './components/ContextMenuAdjusted'; -import { NewBranchDialog } from './components/NewBranchDialog'; -import { UncommittedChangesDialog } from './components/UncommittedChangesDialog'; - -export const Timeline: React.FC = ({ - changes, - history, - branches, - branchActivity, - currentBranch, - repository, - bridge, - hasMoreCommits = false, - commitsOffset = 0, - remoteStatus, -}) => { - const { state, actions } = useTimelineState(bridge, history, commitsOffset); - - const timelineActions = useTimelineActions({ - bridge, - changes, - selectedFiles: state.selectedFiles, - commitMessage: state.commitMessage, - newBranchName: state.newBranchName, - allCommits: state.allCommits, - hasMoreCommits, - isLoadingMore: state.isLoadingMore, - setSelectedFiles: actions.setSelectedFiles, - setCommitMessage: actions.setCommitMessage, - setContextMenu: actions.setContextMenu, - setNewBranchDialog: actions.setNewBranchDialog, - setNewBranchName: actions.setNewBranchName, - setUncommittedChangesDialog: actions.setUncommittedChangesDialog, - setIsLoadingMore: actions.setIsLoadingMore, - setSelectedCommitHash: actions.setSelectedCommitHash, - }); - - return ( - - {/* Header */} - - - {/* Tabs */} - - - {/* Content */} - - {state.activeTab === 'changes' ? ( - - ) : ( - - )} - - - {/* Context Menu */} - {state.contextMenu && ( - - )} - - {/* New Branch Dialog */} - actions.setNewBranchDialog(false)} - onBranchNameChange={actions.setNewBranchName} - onConfirm={timelineActions.handleConfirmCreateBranch} - /> - - {/* Uncommitted Changes Dialog */} - actions.setUncommittedChangesDialog(false)} - onBranchNameChange={actions.setNewBranchName} - onLeaveChanges={() => timelineActions.handleCreateBranchWithChanges(false)} - onBringChanges={() => timelineActions.handleCreateBranchWithChanges(true)} - /> - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/ChangesView.tsx b/src/webviews/app/components/Timeline/components/ChangesView.tsx deleted file mode 100644 index 565a346..0000000 --- a/src/webviews/app/components/Timeline/components/ChangesView.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React from 'react'; -import { Box, Typography, TextField, Button, Divider } from '@mui/material'; -import { - Add as AddIcon, - Remove as RemoveIcon, - Check as CheckIcon, - Sync as SyncIcon, - MoreVert as MoreIcon -} from '@mui/icons-material'; -import { GitChange } from '../../../bridge'; -import { FileChangesList } from './FileChangesList'; - -interface ChangesViewProps { - changes: GitChange[]; - selectedFiles: Set; - commitMessage: string; - currentBranch: string | null; - onFileToggle: (filePath: string) => void; - onStageFiles: () => void; - onUnstageFiles: () => void; - onCommitMessageChange: (message: string) => void; - onCommit: () => void; - onPush: () => void; - onPull: () => void; -} - -export const ChangesView: React.FC = ({ - changes, - selectedFiles, - commitMessage, - currentBranch, - onFileToggle, - onStageFiles, - onUnstageFiles, - onCommitMessageChange, - onCommit, - onPush, - onPull, -}) => { - const stagedChanges = (changes || []).filter(c => c.staged); - const unstagedChanges = (changes || []).filter(c => !c.staged); - - return ( - - {/* Commit Message Section */} - - onCommitMessageChange(e.target.value)} - variant="outlined" - size="small" - minRows={2} - maxRows={4} - sx={{ - '& .MuiOutlinedInput-root': { - fontSize: '13px', - bgcolor: 'var(--vscode-input-background)', - color: 'var(--vscode-input-foreground)', - '& fieldset': { - borderColor: 'var(--vscode-input-border)', - }, - '&:hover fieldset': { - borderColor: 'var(--vscode-input-border)', - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--vscode-focusBorder)', - } - }, - '& .MuiInputBase-input::placeholder': { - color: 'var(--vscode-input-placeholderForeground)', - opacity: 1, - } - }} - /> - - {/* Commit Actions */} - - - - - - - - - - {/* Changes List */} - - {stagedChanges.length > 0 && ( - } - /> - )} - - {unstagedChanges.length > 0 && ( - } - /> - )} - - {changes.length === 0 && ( - - - - No changes - - - The working tree is clean. There are no changes to commit. - - - )} - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/CommitListItem.tsx b/src/webviews/app/components/Timeline/components/CommitListItem.tsx deleted file mode 100644 index eda0c75..0000000 --- a/src/webviews/app/components/Timeline/components/CommitListItem.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from "react"; -import { - ListItem, - ListItemText, - Typography, - Box, - Avatar, - Chip, -} from "@mui/material"; -import { - LocalOffer as TagIcon, - ArrowUpward as UpArrowIcon, -} from "@mui/icons-material"; -import { GitCommit } from "../../../bridge"; -import { formatCommitDate } from "../utils/timeline.utils"; -import { generateAvatarUrl } from ".."; - -interface CommitListItemProps { - commit: GitCommit; - isSelected: boolean; - onSelect: (commit: GitCommit) => void; - onContextMenu: (event: React.MouseEvent, commit: GitCommit) => void; -} - -export const CommitListItem: React.FC = ({ - commit, - isSelected, - onSelect, - onContextMenu, -}) => { - return ( - onSelect(commit)} - onContextMenu={(e) => onContextMenu(e, commit)} - > - - {commit.message} - - } - secondary={ - - { - // Fallback if avatar fails to load - console.log("Avatar failed to load for:", commit.author); - (e.target as HTMLImageElement).src = ""; - }} - > - {(commit.author || "U").charAt(0).toUpperCase()} - - - {commit.author} - - - • - - - {commit.relativeTime || formatCommitDate(commit.date)} - - - {commit.tags && commit.tags.length > 0 && ( - - {commit.tags.map((tag) => ( - } - sx={{ - height: "18px", - fontSize: "11px", - bgcolor: "var(--vscode-badge-background)", - color: "var(--vscode-badge-foreground)", - borderRadius: "10px", - "& .MuiChip-icon": { - fontSize: "12px", - marginLeft: "4px", - color: "var(--vscode-badge-foreground)", - }, - "& .MuiChip-label": { - px: 0.8, - py: 0, - }, - }} - /> - ))} - - )} - - {commit.isPushed === false && ( - - - - )} - - {/* - {(commit.hash || "").substring(0, 7)} - */} - - } - /> - - ); -}; diff --git a/src/webviews/app/components/Timeline/components/CommitSection.tsx b/src/webviews/app/components/Timeline/components/CommitSection.tsx deleted file mode 100644 index 33dabdb..0000000 --- a/src/webviews/app/components/Timeline/components/CommitSection.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import { Box, Button, TextField, IconButton } from '@mui/material'; -import { CloudUpload as PushIcon, CloudDownload as PullIcon } from '@mui/icons-material'; - -interface CommitSectionProps { - commitMessage: string; - currentBranch: string | null; - onCommitMessageChange: (message: string) => void; - onCommit: () => void; - onPush: () => void; - onPull: () => void; -} - -export const CommitSection: React.FC = ({ - commitMessage, - currentBranch, - onCommitMessageChange, - onCommit, - onPush, - onPull, -}) => { - return ( - - onCommitMessageChange(e.target.value)} - variant="outlined" - size="small" - sx={{ - mb: 2, - '& .MuiOutlinedInput-root': { - bgcolor: 'var(--vscode-input-background)', - color: 'var(--vscode-input-foreground)', - fontSize: '13px', - '& fieldset': { - borderColor: 'var(--vscode-input-border)' - }, - '&:hover fieldset': { - borderColor: 'var(--vscode-inputOption-hoverBackground)' - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--vscode-focusBorder)' - } - }, - '& .MuiInputBase-input::placeholder': { - color: 'var(--vscode-input-placeholderForeground)', - opacity: 1 - } - }} - /> - - - - - - - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/ContextMenu.tsx b/src/webviews/app/components/Timeline/components/ContextMenu.tsx deleted file mode 100644 index eaf55d8..0000000 --- a/src/webviews/app/components/Timeline/components/ContextMenu.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import React from 'react'; -import { - Box, - Paper, - MenuList, - MenuItem, - ListItemIcon, - Divider, -} from '@mui/material'; -import { - RestartAlt as ResetIcon, - CheckCircle as CheckoutIcon, - Reorder as ReorderIcon, - Undo as RevertIcon, - AccountTree as BranchIcon, - LocalOffer as TagIcon, - CallMerge as CherryPickIcon, - ContentCopy as CopyIcon, - OpenInNew as ViewIcon, -} from '@mui/icons-material'; -import { ContextMenuState } from '../types/timeline.types'; -import { GitCommit } from '../../../bridge'; - -interface ContextMenuProps { - contextMenu: ContextMenuState; - onClose: () => void; - onAction: (action: string, commit: GitCommit) => void; -} - -export const ContextMenu: React.FC = ({ - contextMenu, - onClose, - onAction, -}) => { - const menuItemStyles = { - fontSize: '13px', - color: 'var(--vscode-menu-foreground, #cccccc)', - px: 2, - py: 0.5, - minHeight: 'auto', - '&:hover': { - bgcolor: 'var(--vscode-menu-selectionBackground, #094771)', - color: 'var(--vscode-menu-selectionForeground, #ffffff)' - }, - }; - - return ( - <> - {/* Backdrop */} - - - {/* Menu */} - - - onAction('reset', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Reset to commit... - - - onAction('checkout', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Checkout commit - - - onAction('reorder', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Reorder commit - - - onAction('revert', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Revert changes in commit - - - - - onAction('createBranch', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Create branch from commit - - - onAction('createTag', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Create tag... - - - onAction('cherryPick', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Cherry-pick commit... - - - - - onAction('copySha', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Copy SHA - - - onAction('copyTag', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Copy tag - - - onAction('viewOnGitHub', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - View on GitHub - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/ContextMenuAdjusted.tsx b/src/webviews/app/components/Timeline/components/ContextMenuAdjusted.tsx deleted file mode 100644 index bfd1f86..0000000 --- a/src/webviews/app/components/Timeline/components/ContextMenuAdjusted.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Box, - Paper, - MenuList, - MenuItem, - ListItemIcon, - Divider, -} from '@mui/material'; -import { - RestartAlt as ResetIcon, - CheckCircle as CheckoutIcon, - Reorder as ReorderIcon, - Undo as RevertIcon, - AccountTree as BranchIcon, - LocalOffer as TagIcon, - CallMerge as CherryPickIcon, - ContentCopy as CopyIcon, - OpenInNew as ViewIcon, -} from '@mui/icons-material'; -import { ContextMenuState } from '../types/timeline.types'; -import { GitCommit } from '../../../bridge'; - -interface ContextMenuProps { - contextMenu: ContextMenuState; - onClose: () => void; - onAction: (action: string, commit: GitCommit) => void; -} - -export const ContextMenuAdjusted: React.FC = ({ - contextMenu, - onClose, - onAction, -}) => { - const menuRef = useRef(null); - const [position, setPosition] = useState({ top: 0, left: 0 }); - - useEffect(() => { - if (menuRef.current) { - const menuRect = menuRef.current.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - let top = contextMenu.mouseY; - let left = contextMenu.mouseX; - - // Adjust horizontal position if menu would overflow right edge - if (left + menuRect.width > viewportWidth) { - left = viewportWidth - menuRect.width - 10; - } - - // Adjust vertical position if menu would overflow bottom edge - if (top + menuRect.height > viewportHeight) { - top = viewportHeight - menuRect.height - 10; - } - - // Ensure menu doesn't go off left or top edges - if (left < 10) left = 10; - if (top < 10) top = 10; - - setPosition({ top, left }); - } - }, [contextMenu.mouseX, contextMenu.mouseY]); - - const menuItemStyles = { - fontSize: '13px', - color: 'var(--vscode-menu-foreground, #cccccc)', - px: 2, - py: 0.5, - minHeight: 'auto', - '&:hover': { - bgcolor: 'var(--vscode-menu-selectionBackground, #094771)', - color: 'var(--vscode-menu-selectionForeground, #ffffff)' - }, - }; - - return ( - <> - {/* Backdrop */} - - - {/* Menu */} - - - onAction('reset', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Reset to commit... - - - onAction('checkout', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Checkout commit - - - onAction('reorder', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Reorder commit - - - onAction('revert', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Revert changes in commit - - - - - onAction('createBranch', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Create branch from commit - - - onAction('createTag', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Create tag... - - - onAction('cherryPick', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Cherry-pick commit... - - - - - onAction('copySha', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Copy SHA - - - onAction('copyTag', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - Copy tag - - - onAction('viewOnGitHub', contextMenu.commit!)} - sx={menuItemStyles} - > - - - - View on GitHub - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/FileChangesList.tsx b/src/webviews/app/components/Timeline/components/FileChangesList.tsx deleted file mode 100644 index b86ceb3..0000000 --- a/src/webviews/app/components/Timeline/components/FileChangesList.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import React, { useState } from 'react'; -import { - Box, - Typography, - IconButton, - List, - ListItem, - ListItemText, - ListItemIcon, - Collapse, -} from '@mui/material'; -import { - Add as AddIcon, - Remove as RemoveIcon, - KeyboardArrowDown as ArrowDownIcon, - KeyboardArrowRight as ArrowRightIcon, - InsertDriveFile as FileIcon, -} from '@mui/icons-material'; -import { GitChange } from '../../../bridge'; - -interface FileChangesListProps { - title: string; - changes: GitChange[]; - selectedFiles: Set; - onFileToggle: (filePath: string) => void; - onActionClick?: () => void; - actionLabel?: string; - actionIcon?: React.ReactNode; -} - -const getStatusIndicator = (status: string) => { - switch (status.toUpperCase()) { - case 'M': - case 'MODIFIED': - return { label: 'M', color: '#569cd6' }; // Blue - case 'A': - case 'ADDED': - return { label: 'A', color: '#4fc1ff' }; // Light blue - case 'D': - case 'DELETED': - return { label: 'D', color: '#f85149' }; // Red - case 'R': - case 'RENAMED': - return { label: 'R', color: '#a5a5a5' }; // Gray - case 'U': - case 'UNTRACKED': - return { label: 'U', color: '#73c991' }; // Green - default: - return { label: '?', color: '#a5a5a5' }; - } -}; - -export const FileChangesList: React.FC = ({ - title, - changes, - selectedFiles, - onFileToggle, - onActionClick, - actionLabel, - actionIcon, -}) => { - const [collapsed, setCollapsed] = useState(false); - - if (changes.length === 0) return null; - - const getFileName = (path: string) => { - return path.split('/').pop() || path; - }; - - const getFileDirectory = (path: string) => { - const parts = path.split('/'); - return parts.length > 1 ? parts.slice(0, -1).join('/') : ''; - }; - - return ( - - {/* Section Header */} - setCollapsed(!collapsed)} - > - - {collapsed ? : } - - - - {title} - - - - {changes.length} - - - {onActionClick && actionLabel && ( - { - e.stopPropagation(); - onActionClick(); - }} - size="small" - sx={{ - p: 0.25, - color: 'var(--vscode-sideBarSectionHeader-foreground)', - '&:hover': { - bgcolor: 'var(--vscode-toolbar-hoverBackground)' - } - }} - > - {actionIcon} - - )} - - - {/* File List */} - - - {changes.map((change) => { - const status = getStatusIndicator(change.status); - const fileName = getFileName(change.path); - const directory = getFileDirectory(change.path); - - return ( - onFileToggle(change.path)} - > - - - - - - - {fileName} - - {directory && ( - - {directory} - - )} - - } - sx={{ my: 0 }} - /> - - - {status.label} - - - ); - })} - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/HistoryView.tsx b/src/webviews/app/components/Timeline/components/HistoryView.tsx deleted file mode 100644 index ba102d1..0000000 --- a/src/webviews/app/components/Timeline/components/HistoryView.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Box, Typography, List } from '@mui/material'; -import { GitCommit } from '../../../bridge'; -import { CommitListItem } from './CommitListItem'; -import { LoadMoreSection } from './LoadMoreSection'; - -interface HistoryViewProps { - allCommits: GitCommit[]; - selectedCommitHash: string | null; - hasMoreCommits: boolean; - isLoadingMore: boolean; - onCommitSelect: (commit: GitCommit) => void; - onCommitContextMenu: (event: React.MouseEvent, commit: GitCommit) => void; - onLoadMoreCommits: () => void; -} - -export const HistoryView: React.FC = ({ - allCommits, - selectedCommitHash, - hasMoreCommits, - isLoadingMore, - onCommitSelect, - onCommitContextMenu, - onLoadMoreCommits, -}) => { - return ( - { - const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; - // Load more when scrolled to bottom - if (scrollHeight - scrollTop <= clientHeight + 100 && hasMoreCommits && !isLoadingMore) { - onLoadMoreCommits(); - } - }} - > - - {allCommits.map((commit) => ( - - ))} - - - - - {allCommits.length === 0 && !isLoadingMore && ( - - - No commits yet - - - Create your first commit to see history - - - )} - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/LoadMoreSection.tsx b/src/webviews/app/components/Timeline/components/LoadMoreSection.tsx deleted file mode 100644 index ece8156..0000000 --- a/src/webviews/app/components/Timeline/components/LoadMoreSection.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { Box, Typography, Button } from '@mui/material'; - -interface LoadMoreSectionProps { - hasMoreCommits: boolean; - isLoading: boolean; - onLoadMore: () => void; -} - -export const LoadMoreSection: React.FC = ({ - hasMoreCommits, - isLoading, - onLoadMore, -}) => { - if (!hasMoreCommits) return null; - - return ( - - {isLoading ? ( - - Loading more commits... - - ) : ( - - )} - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/NewBranchDialog.tsx b/src/webviews/app/components/Timeline/components/NewBranchDialog.tsx deleted file mode 100644 index 95b7e2c..0000000 --- a/src/webviews/app/components/Timeline/components/NewBranchDialog.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - DialogContentText, - TextField, - Button, -} from '@mui/material'; - -interface NewBranchDialogProps { - open: boolean; - branchName: string; - onClose: () => void; - onBranchNameChange: (name: string) => void; - onConfirm: () => void; -} - -export const NewBranchDialog: React.FC = ({ - open, - branchName, - onClose, - onBranchNameChange, - onConfirm, -}) => { - const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - onConfirm(); - } - }; - - return ( - - Create New Branch - - - Enter a name for the new branch: - - onBranchNameChange(e.target.value)} - placeholder="feature/new-feature" - onKeyPress={handleKeyPress} - /> - - - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/components/TimelineHeader.tsx b/src/webviews/app/components/Timeline/components/TimelineHeader.tsx deleted file mode 100644 index ed6cc52..0000000 --- a/src/webviews/app/components/Timeline/components/TimelineHeader.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { - Box, - Typography, - Popover, - MenuList, - MenuItem, - ListItemIcon, - ListItemText, -} from "@mui/material"; -import { - CallSplit as BranchIcon, - KeyboardArrowDown as DropdownIcon, - ArrowDownward as PullIcon, - CloudUpload as PublishIcon, - CloudDownload as FetchIcon, - ArrowUpward as PushIcon, - FlashOn as ForceIcon, -} from "@mui/icons-material"; -import { BranchDropdown } from "../BranchDropdown"; -import { VSCodeBridge, RemoteStatus } from "../../../bridge"; -import { useGitOperations } from "../../../hooks/useGitOperations"; - -interface TimelineHeaderProps { - currentBranch: string | null; - branches: string[]; - branchActivity: Record; - remoteStatus?: RemoteStatus | null; - bridge: VSCodeBridge; - onCreateNewBranch: () => void; - onRefresh: () => void; -} - -export const TimelineHeader: React.FC = ({ - currentBranch, - branches, - branchActivity, - remoteStatus, - bridge, - onCreateNewBranch, - onRefresh, -}) => { - const [syncAnchorEl, setSyncAnchorEl] = useState(null); - const [lastFetchedText, setLastFetchedText] = useState(""); - const gitOps = useGitOperations(bridge); - - const syncOpen = Boolean(syncAnchorEl); - - useEffect(() => { - if (!remoteStatus) return; - - const updateLastFetchedText = () => { - if (remoteStatus.lastFetched) { - const fetchedDate = new Date(remoteStatus.lastFetched); - const diff = Date.now() - fetchedDate.getTime(); - const seconds = Math.round(diff / 1000); - - if (seconds < 60) { - setLastFetchedText("just now"); - } else { - const minutes = Math.round(seconds / 60); - if (minutes < 60) { - setLastFetchedText( - `${minutes} minute${minutes === 1 ? "" : "s"} ago` - ); - } else { - const hours = Math.round(minutes / 60); - if (hours < 24) { - setLastFetchedText(`${hours} hour${hours === 1 ? "" : "s"} ago`); - } else { - const days = Math.round(hours / 24); - setLastFetchedText(`${days} day${days === 1 ? "" : "s"} ago`); - } - } - } - } else { - setLastFetchedText("Never"); - } - }; - - updateLastFetchedText(); - const interval = setInterval(updateLastFetchedText, 60000); - return () => clearInterval(interval); - }, [remoteStatus]); - - const handleSyncClick = (event: React.MouseEvent) => { - // Perform sync action based on remote status - if (!remoteStatus) return; - - if (!remoteStatus.isPublished) { - gitOps.publish.mutate(); - } else if (remoteStatus.behind > 0 && remoteStatus.ahead > 0) { - gitOps.pull.mutate(); - } else if (remoteStatus.behind > 0) { - gitOps.pull.mutate(); - } else if (remoteStatus.ahead > 0) { - gitOps.push.mutate(); - } else { - gitOps.fetch.mutate(); - } - }; - - const handleSyncDropdownClick = (event: React.MouseEvent) => { - event.stopPropagation(); - setSyncAnchorEl(event.currentTarget); - }; - - const handleSyncClose = () => { - setSyncAnchorEl(null); - }; - - const getSyncStatus = () => { - if (!remoteStatus) - return { title: "Sync", icon: FetchIcon, showCounts: false }; - - if (!remoteStatus.isPublished) { - return { title: "Publish branch", icon: PublishIcon, showCounts: false }; - } else if (remoteStatus.behind > 0 && remoteStatus.ahead > 0) { - return { title: "Pull origin", icon: PullIcon, showCounts: true }; - } else if (remoteStatus.behind > 0) { - return { title: "Pull origin", icon: PullIcon, showCounts: true }; - } else if (remoteStatus.ahead > 0) { - return { title: "Push origin", icon: PushIcon, showCounts: true }; - } else { - return { title: "Fetch origin", icon: FetchIcon, showCounts: false }; - } - }; - - const syncStatus = getSyncStatus(); - const isLoading = - gitOps.push.isLoading || - gitOps.pull.isLoading || - gitOps.fetch.isLoading || - gitOps.publish.isLoading; - - // Show dropdown only when there are remote changes or commits to push - const shouldShowDropdown = - remoteStatus && (remoteStatus.behind > 0 || remoteStatus.ahead > 0); - - const handleFetch = () => { - gitOps.fetch.mutate(); - handleSyncClose(); - }; - - const handleForcePush = () => { - bridge.sendMessage("forcePush"); - handleSyncClose(); - }; - - return ( - <> - - {/* Branch Section */} - - - - - {/* Sync Section */} - - - - - {syncStatus.title} - - - Last fetched {lastFetchedText} - - - - {/* Sync Counters */} - {remoteStatus && syncStatus.showCounts && ( - - {remoteStatus.behind > 0 && ( - <> - - {remoteStatus.behind} - - - - )} - {remoteStatus.ahead > 0 && ( - <> - - {remoteStatus.ahead} - - - - )} - - )} - - {shouldShowDropdown && ( - - - - )} - - - - {/* Sync Dropdown */} - - - - - - - - - - {remoteStatus && remoteStatus.ahead > 0 && ( - - - - - - - )} - - - - ); -}; diff --git a/src/webviews/app/components/Timeline/components/TimelineTabs.tsx b/src/webviews/app/components/Timeline/components/TimelineTabs.tsx deleted file mode 100644 index 153d919..0000000 --- a/src/webviews/app/components/Timeline/components/TimelineTabs.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; -import { Box, Button } from "@mui/material"; -import { TabType } from "../types/timeline.types"; -import { GitChange, GitCommit } from "../../../bridge"; - -interface TimelineTabsProps { - activeTab: TabType; - changes: GitChange[]; - history: GitCommit[]; - onTabChange: (tab: TabType) => void; -} - -export const TimelineTabs: React.FC = ({ - activeTab, - changes, - history, - onTabChange, -}) => { - const getTabButtonStyles = (isActive: boolean) => ({ - flex: 1, - py: 0.75, - px: 0, - color: isActive - ? "var(--vscode-foreground)" - : "var(--vscode-descriptionForeground)", - borderBottom: isActive ? "2px solid var(--vscode-focusBorder)" : "none", - borderRadius: 0, - fontSize: "13px", - fontWeight: 500, - textTransform: "none" as const, - "&:hover": { - bgcolor: "var(--vscode-list-hoverBackground)", - }, - }); - - return ( - - - - - ); -}; diff --git a/src/webviews/app/components/Timeline/components/UncommittedChangesDialog.tsx b/src/webviews/app/components/Timeline/components/UncommittedChangesDialog.tsx deleted file mode 100644 index f93c14c..0000000 --- a/src/webviews/app/components/Timeline/components/UncommittedChangesDialog.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - DialogContentText, - TextField, - Button, -} from '@mui/material'; - -interface UncommittedChangesDialogProps { - open: boolean; - branchName: string; - onClose: () => void; - onBranchNameChange: (name: string) => void; - onLeaveChanges: () => void; - onBringChanges: () => void; -} - -export const UncommittedChangesDialog: React.FC = ({ - open, - branchName, - onClose, - onBranchNameChange, - onLeaveChanges, - onBringChanges, -}) => { - return ( - - Uncommitted Changes - - - You have uncommitted changes. What would you like to do with them? - - onBranchNameChange(e.target.value)} - placeholder="feature/new-feature" - label="New branch name" - sx={{ mb: 2 }} - /> - - - - - - - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/components/Timeline/hooks/useTimelineActions.ts b/src/webviews/app/components/Timeline/hooks/useTimelineActions.ts deleted file mode 100644 index 801075e..0000000 --- a/src/webviews/app/components/Timeline/hooks/useTimelineActions.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { VSCodeBridge, GitChange, GitCommit } from "../../../bridge"; -import { ContextMenuState } from "../types/timeline.types"; - -interface UseTimelineActionsProps { - bridge: VSCodeBridge; - changes: GitChange[]; - selectedFiles: Set; - commitMessage: string; - newBranchName: string; - allCommits: GitCommit[]; - hasMoreCommits: boolean; - isLoadingMore: boolean; - setSelectedFiles: (files: Set) => void; - setCommitMessage: (message: string) => void; - setContextMenu: (menu: ContextMenuState | null) => void; - setNewBranchDialog: (open: boolean) => void; - setNewBranchName: (name: string) => void; - setUncommittedChangesDialog: (open: boolean) => void; - setIsLoadingMore: (loading: boolean) => void; - setSelectedCommitHash: (hash: string | null) => void; -} - -export const useTimelineActions = ({ - bridge, - changes, - selectedFiles, - commitMessage, - newBranchName, - allCommits, - hasMoreCommits, - isLoadingMore, - setSelectedFiles, - setCommitMessage, - setContextMenu, - setNewBranchDialog, - setNewBranchName, - setUncommittedChangesDialog, - setIsLoadingMore, - setSelectedCommitHash, -}: UseTimelineActionsProps) => { - const handleFileToggle = (filePath: string) => { - const newSelected = new Set(selectedFiles); - if (newSelected.has(filePath)) { - newSelected.delete(filePath); - } else { - newSelected.add(filePath); - } - setSelectedFiles(newSelected); - }; - - const handleStageFiles = () => { - bridge.sendMessage("stageFiles", { files: Array.from(selectedFiles) }); - setSelectedFiles(new Set()); - }; - - const handleUnstageFiles = () => { - bridge.sendMessage("unstageFiles", { files: Array.from(selectedFiles) }); - setSelectedFiles(new Set()); - }; - - const handleCommit = () => { - if (commitMessage.trim()) { - bridge.sendMessage("commit", { message: commitMessage }); - setCommitMessage(""); - } - }; - - const handlePush = () => { - bridge.sendMessage("push"); - }; - - const handlePull = () => { - bridge.sendMessage("pull"); - }; - - const handleRefresh = () => { - bridge.sendMessage("refresh"); - }; - - const handleCreateNewBranch = () => { - const hasUncommittedChanges = changes.length > 0; - if (hasUncommittedChanges) { - setUncommittedChangesDialog(true); - } else { - setNewBranchDialog(true); - } - }; - - const handleCreateBranchWithChanges = (bringChanges: boolean) => { - setUncommittedChangesDialog(false); - - if (bringChanges) { - bridge.sendMessage("createBranchWithChanges", { - branchName: newBranchName, - bringChanges: true, - }); - } else { - bridge.sendMessage("createBranchWithChanges", { - branchName: newBranchName, - bringChanges: false, - }); - } - - setNewBranchName(""); - setNewBranchDialog(true); - }; - - const handleConfirmCreateBranch = () => { - if (newBranchName.trim()) { - bridge.sendMessage("createBranch", { branchName: newBranchName.trim() }); - setNewBranchDialog(false); - setNewBranchName(""); - } - }; - - const handleLoadMoreCommits = () => { - if (!isLoadingMore && hasMoreCommits) { - setIsLoadingMore(true); - bridge.sendMessage("loadMoreCommits", { offset: allCommits.length }); - } - }; - - const handleCommitSelect = (commit: GitCommit) => { - console.log("Selecting commit:", commit.hash); - setSelectedCommitHash(commit.hash); - bridge.sendMessage("openCommitDetail", { hash: commit.hash }); - }; - - const handleCommitContextMenu = ( - event: React.MouseEvent, - commit: GitCommit, - ) => { - event.preventDefault(); - setContextMenu({ - mouseX: event.clientX + 2, - mouseY: event.clientY - 6, - commit, - }); - }; - - const handleContextMenuClose = () => { - setContextMenu(null); - }; - - const handleContextMenuAction = (action: string, commit: GitCommit) => { - switch (action) { - case "reset": - bridge.sendMessage("resetToCommit", { hash: commit.hash }); - break; - case "checkout": - bridge.sendMessage("checkoutCommit", { hash: commit.hash }); - break; - case "reorder": - bridge.sendMessage("reorderCommit", { hash: commit.hash }); - break; - case "revert": - bridge.sendMessage("revertCommit", { hash: commit.hash }); - break; - case "createBranch": - bridge.sendMessage("createBranchFromCommit", { hash: commit.hash }); - break; - case "createTag": - bridge.sendMessage("createTagFromCommit", { hash: commit.hash }); - break; - case "cherryPick": - bridge.sendMessage("cherryPickCommit", { hash: commit.hash }); - break; - case "copySha": - navigator.clipboard.writeText(commit.hash); - break; - case "copyTag": - navigator.clipboard.writeText( - `${commit.message || "No message"} (${(commit.hash || "").substring(0, 7)})`, - ); - break; - case "viewOnGitHub": - bridge.sendMessage("viewCommitOnGitHub", { hash: commit.hash }); - break; - } - handleContextMenuClose(); - }; - - return { - handleFileToggle, - handleStageFiles, - handleUnstageFiles, - handleCommit, - handlePush, - handlePull, - handleRefresh, - handleCreateNewBranch, - handleCreateBranchWithChanges, - handleConfirmCreateBranch, - handleLoadMoreCommits, - handleCommitSelect, - handleCommitContextMenu, - handleContextMenuClose, - handleContextMenuAction, - }; -}; diff --git a/src/webviews/app/components/Timeline/hooks/useTimelineState.ts b/src/webviews/app/components/Timeline/hooks/useTimelineState.ts deleted file mode 100644 index 7097e22..0000000 --- a/src/webviews/app/components/Timeline/hooks/useTimelineState.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { useState, useEffect } from "react"; -import { - TimelineState, - TabType, - ContextMenuState, -} from "../types/timeline.types"; -import { GitCommit, VSCodeBridge } from "../../../bridge"; - -export const useTimelineState = ( - bridge: VSCodeBridge, - history: GitCommit[], - commitsOffset: number, -) => { - const [activeTab, setActiveTab] = useState("changes"); - const [commitMessage, setCommitMessage] = useState(""); - const [selectedFiles, setSelectedFiles] = useState>(new Set()); - const [contextMenu, setContextMenu] = useState(null); - const [newBranchDialog, setNewBranchDialog] = useState(false); - const [newBranchName, setNewBranchName] = useState(""); - const [uncommittedChangesDialog, setUncommittedChangesDialog] = - useState(false); - const [isLoadingMore, setIsLoadingMore] = useState(false); - const [allCommits, setAllCommits] = useState([]); - const [selectedCommitHash, setSelectedCommitHash] = useState( - null, - ); - - // Update allCommits when history changes - useEffect(() => { - if (commitsOffset === 0) { - setAllCommits(history); - } - }, [history, commitsOffset]); - - // Handle load more commits response - useEffect(() => { - const handleMessage = (message: any) => { - if (message.command === "loadMoreCommitsResponse") { - setAllCommits((prev) => [...prev, ...message.history]); - setIsLoadingMore(false); - } - }; - - bridge.onMessage(handleMessage); - return () => { - // Cleanup if needed - }; - }, [bridge]); - - return { - state: { - activeTab, - commitMessage, - selectedFiles, - contextMenu, - newBranchDialog, - newBranchName, - uncommittedChangesDialog, - isLoadingMore, - allCommits, - selectedCommitHash, - }, - actions: { - setActiveTab, - setCommitMessage, - setSelectedFiles, - setContextMenu, - setNewBranchDialog, - setNewBranchName, - setUncommittedChangesDialog, - setIsLoadingMore, - setAllCommits, - setSelectedCommitHash, - }, - }; -}; diff --git a/src/webviews/app/components/Timeline/index.ts b/src/webviews/app/components/Timeline/index.ts deleted file mode 100644 index 96aea5e..0000000 --- a/src/webviews/app/components/Timeline/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { Timeline } from "./Timeline"; -export type { - TimelineProps, - TabType, - ContextMenuState, - TimelineState, -} from "./types/timeline.types"; -export { formatCommitDate, getStatusColor } from "./utils/timeline.utils"; -export { useTimelineState } from "./hooks/useTimelineState"; -export { useTimelineActions } from "./hooks/useTimelineActions"; -export { generateAvatarUrl } from "../CommitDetailPanel/utils/avatar.utils"; - -// Component exports -export { TimelineHeader } from "./components/TimelineHeader"; -export { TimelineTabs } from "./components/TimelineTabs"; -export { ChangesView } from "./components/ChangesView"; -export { HistoryView } from "./components/HistoryView"; -export { ContextMenu } from "./components/ContextMenu"; -export { NewBranchDialog } from "./components/NewBranchDialog"; -export { UncommittedChangesDialog } from "./components/UncommittedChangesDialog"; -export { FileChangesList } from "./components/FileChangesList"; -export { CommitSection } from "./components/CommitSection"; -export { CommitListItem } from "./components/CommitListItem"; -export { LoadMoreSection } from "./components/LoadMoreSection"; diff --git a/src/webviews/app/components/Timeline/types/timeline.types.ts b/src/webviews/app/components/Timeline/types/timeline.types.ts deleted file mode 100644 index b504aa3..0000000 --- a/src/webviews/app/components/Timeline/types/timeline.types.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - VSCodeBridge, - GitChange, - GitCommit, - Repository, - RemoteStatus, -} from "../../../bridge"; - -export interface TimelineProps { - changes: GitChange[]; - history: GitCommit[]; - branches: string[]; - branchActivity: Record; - currentBranch: string | null; - repository: Repository | null; - bridge: VSCodeBridge; - hasMoreCommits?: boolean; - commitsOffset?: number; - remoteStatus?: RemoteStatus | null; -} - -export interface ContextMenuState { - mouseX: number; - mouseY: number; - commit: GitCommit | null; -} - -export type TabType = "changes" | "history"; - -export interface TimelineState { - activeTab: TabType; - commitMessage: string; - selectedFiles: Set; - contextMenu: ContextMenuState | null; - newBranchDialog: boolean; - newBranchName: string; - uncommittedChangesDialog: boolean; - isLoadingMore: boolean; - allCommits: GitCommit[]; - selectedCommitHash: string | null; -} diff --git a/src/webviews/app/components/Timeline/utils/timeline.utils.ts b/src/webviews/app/components/Timeline/utils/timeline.utils.ts deleted file mode 100644 index b04996b..0000000 --- a/src/webviews/app/components/Timeline/utils/timeline.utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import dayjs from "dayjs"; -import relativeTime from "dayjs/plugin/relativeTime"; - -export const formatCommitDate = (dateString: string): string => { - // Initialize dayjs plugins - dayjs.extend(relativeTime); - - // Handle undefined or null dateString - const safeDateString = dateString || ""; - - // If it's already a relative format (contains "ago"), return as is - if (safeDateString.includes("ago") || safeDateString.includes("just now")) { - return safeDateString; - } - - // Parse the date using dayjs - const date = dayjs(safeDateString); - - // Check if it's a valid date - if (!date.isValid()) { - return safeDateString || "Invalid date"; - } - - const now = dayjs(); - const diffInDays = now.diff(date, "day"); - - // For dates within the last 7 days, show relative format - if (diffInDays <= 7) { - if (diffInDays === 0) return "today"; - if (diffInDays === 1) return "yesterday"; - return `${diffInDays} days ago`; - } - - // For older dates, show formatted date with time - return date.format("D MMMM, YYYY [at] HH:mm"); -}; - -export const getStatusColor = (status: string): string => { - const safeStatus = status || ""; - switch (safeStatus.charAt(0)) { - case "M": - return "#f59e0b"; // Modified - amber - case "A": - return "#10b981"; // Added - green - case "D": - return "#ef4444"; // Deleted - red - case "R": - return "#8b5cf6"; // Renamed - purple - case "?": - return "#6b7280"; // Untracked - gray - default: - return "#6b7280"; - } -}; diff --git a/src/webviews/app/domain/entities/branch.entity.ts b/src/webviews/app/domain/entities/branch.entity.ts deleted file mode 100644 index 56a532a..0000000 --- a/src/webviews/app/domain/entities/branch.entity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { BranchName } from "../value-objects/branch-name.vo"; -import { CommitDate } from "../value-objects/commit-date.vo"; - -export class Branch { - constructor( - public readonly name: BranchName, - public readonly isCurrent: boolean = false, - public readonly lastActivity?: CommitDate, - public readonly isRemote: boolean = false, - ) {} - - get displayName(): string { - return this.name.displayName; - } - - get shortName(): string { - return this.name.shortName; - } - - get activityText(): string { - return this.lastActivity?.relative ?? "No activity"; - } - - get isLocal(): boolean { - return !this.isRemote; - } - - toJSON() { - return { - name: this.name.value, - isCurrent: this.isCurrent, - lastActivity: this.lastActivity?.formatted, - isRemote: this.isRemote, - }; - } -} diff --git a/src/webviews/app/domain/entities/commit.entity.ts b/src/webviews/app/domain/entities/commit.entity.ts deleted file mode 100644 index 9478e84..0000000 --- a/src/webviews/app/domain/entities/commit.entity.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { CommitId } from "../value-objects/commit-id.vo"; -import { CommitMessage } from "../value-objects/commit-message.vo"; -import { Author } from "../value-objects/author.vo"; -import { CommitDate } from "../value-objects/commit-date.vo"; -import { FileChange } from "./file-change.entity"; - -export class Commit { - constructor( - public readonly id: CommitId, - public readonly message: CommitMessage, - public readonly author: Author, - public readonly date: CommitDate, - public readonly files: FileChange[] = [], - public readonly avatarUrl?: string, - public readonly tags?: string[], - public readonly isPushed: boolean = true, - ) {} - - get shortHash(): string { - return this.id.shortHash; - } - - get fullHash(): string { - return this.id.value; - } - - get totalAdditions(): number { - return this.files.reduce((sum, file) => sum + file.additions, 0); - } - - get totalDeletions(): number { - return this.files.reduce((sum, file) => sum + file.deletions, 0); - } - - get fileCount(): number { - return this.files.length; - } - - get formattedDate(): string { - return this.date.formatted; - } - - get relativeDate(): string { - return this.date.relative; - } - - toJSON() { - return { - hash: this.id.value, - message: this.message.value, - author: this.author.name, - email: this.author.email, - date: this.date.formatted, - files: this.files.map((f) => f.toJSON()), - totalAdditions: this.totalAdditions, - totalDeletions: this.totalDeletions, - avatarUrl: this.avatarUrl, - tags: this.tags, - isPushed: this.isPushed, - }; - } -} diff --git a/src/webviews/app/domain/entities/file-change.entity.ts b/src/webviews/app/domain/entities/file-change.entity.ts deleted file mode 100644 index fa4fdc4..0000000 --- a/src/webviews/app/domain/entities/file-change.entity.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { FilePath } from "../value-objects/file-path.vo"; -import { FileStatus } from "../value-objects/file-status.vo"; - -export class FileChange { - constructor( - public readonly path: FilePath, - public readonly status: FileStatus, - public readonly additions: number = 0, - public readonly deletions: number = 0, - public readonly iconClass?: string, - public readonly iconUri?: string, - ) {} - - get fileName(): string { - return this.path.fileName; - } - - get directory(): string { - return this.path.directory; - } - - get extension(): string { - return this.path.extension; - } - - get statusColor(): string { - return this.status.color; - } - - get statusLabel(): string { - return this.status.label; - } - - get hasAdditions(): boolean { - return this.additions > 0; - } - - get hasDeletions(): boolean { - return this.deletions > 0; - } - - toJSON() { - return { - path: this.path.value, - status: this.status.value, - additions: this.additions, - deletions: this.deletions, - iconClass: this.iconClass, - iconUri: this.iconUri, - }; - } -} diff --git a/src/webviews/app/domain/value-objects/author.vo.ts b/src/webviews/app/domain/value-objects/author.vo.ts deleted file mode 100644 index 7a36dd1..0000000 --- a/src/webviews/app/domain/value-objects/author.vo.ts +++ /dev/null @@ -1,38 +0,0 @@ -export class Author { - constructor( - public readonly name: string, - public readonly email: string, - ) { - if (!name || name.trim().length === 0) { - throw new Error("Author name cannot be empty"); - } - if (!email || !this.isValidEmail(email)) { - throw new Error("Invalid email address"); - } - } - - get initials(): string { - return this.name - .split(" ") - .map((part) => (part || "").charAt(0).toUpperCase()) - .join("") - .substring(0, 2); - } - - get displayName(): string { - return this.name; - } - - private isValidEmail(email: string): boolean { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - } - - equals(other: Author): boolean { - return this.name === other.name && this.email === other.email; - } - - toString(): string { - return `${this.name} <${this.email}>`; - } -} diff --git a/src/webviews/app/domain/value-objects/branch-name.vo.ts b/src/webviews/app/domain/value-objects/branch-name.vo.ts deleted file mode 100644 index 491c3d2..0000000 --- a/src/webviews/app/domain/value-objects/branch-name.vo.ts +++ /dev/null @@ -1,63 +0,0 @@ -export class BranchName { - private readonly _value: string; - - constructor(value: string) { - if (!value || value.trim().length === 0) { - throw new Error("Branch name cannot be empty"); - } - - if (!this.isValidBranchName(value)) { - throw new Error("Invalid branch name format"); - } - - this._value = value.trim(); - } - - get value(): string { - return this._value; - } - - get displayName(): string { - // Remove origin/ prefix for remote branches - return this._value.replace(/^origin\//, ""); - } - - get shortName(): string { - // Get just the branch name without any prefixes - const parts = this._value.split("/"); - return parts[parts.length - 1]; - } - - get isRemote(): boolean { - return ( - this._value.startsWith("origin/") || this._value.startsWith("remotes/") - ); - } - - get isFeatureBranch(): boolean { - return this._value.includes("feature/") || this._value.includes("feat/"); - } - - get isBugfixBranch(): boolean { - return this._value.includes("bugfix/") || this._value.includes("fix/"); - } - - private isValidBranchName(name: string): boolean { - // Basic git branch name validation - const invalidChars = /[\s~^:?*[\]\\]/; - return ( - !invalidChars.test(name) && - !name.startsWith(".") && - !name.endsWith(".") && - !name.includes("..") - ); - } - - equals(other: BranchName): boolean { - return this._value === other._value; - } - - toString(): string { - return this._value; - } -} diff --git a/src/webviews/app/domain/value-objects/commit-date.vo.ts b/src/webviews/app/domain/value-objects/commit-date.vo.ts deleted file mode 100644 index 306cde8..0000000 --- a/src/webviews/app/domain/value-objects/commit-date.vo.ts +++ /dev/null @@ -1,54 +0,0 @@ -import dayjs from "dayjs"; -import relativeTime from "dayjs/plugin/relativeTime"; - -export class CommitDate { - private readonly _date: dayjs.Dayjs; - - constructor(date: string | Date | dayjs.Dayjs) { - dayjs.extend(relativeTime); - this._date = dayjs(date); - - if (!this._date.isValid()) { - throw new Error("Invalid date provided"); - } - } - - get value(): Date { - return this._date.toDate(); - } - - get formatted(): string { - const now = dayjs(); - const diffInDays = now.diff(this._date, "day"); - - // For dates within the last 7 days, show relative format - if (diffInDays <= 7) { - if (diffInDays === 0) return "today"; - if (diffInDays === 1) return "yesterday"; - return `${diffInDays} days ago`; - } - - // For older dates, show formatted date with time - return this._date.format("D MMMM, YYYY [at] HH:mm"); - } - - get relative(): string { - return this._date.fromNow(); - } - - get iso(): string { - return this._date.toISOString(); - } - - isAfter(other: CommitDate): boolean { - return this._date.isAfter(other._date); - } - - isBefore(other: CommitDate): boolean { - return this._date.isBefore(other._date); - } - - equals(other: CommitDate): boolean { - return this._date.isSame(other._date); - } -} diff --git a/src/webviews/app/domain/value-objects/commit-id.vo.ts b/src/webviews/app/domain/value-objects/commit-id.vo.ts deleted file mode 100644 index cee405e..0000000 --- a/src/webviews/app/domain/value-objects/commit-id.vo.ts +++ /dev/null @@ -1,26 +0,0 @@ -export class CommitId { - private readonly _value: string; - - constructor(value: string) { - if (!value || value.length < 7) { - throw new Error("Invalid commit ID: must be at least 7 characters"); - } - this._value = value; - } - - get value(): string { - return this._value; - } - - get shortHash(): string { - return this._value.substring(0, 7); - } - - equals(other: CommitId): boolean { - return this._value === other._value; - } - - toString(): string { - return this._value; - } -} diff --git a/src/webviews/app/domain/value-objects/commit-message.vo.ts b/src/webviews/app/domain/value-objects/commit-message.vo.ts deleted file mode 100644 index b950868..0000000 --- a/src/webviews/app/domain/value-objects/commit-message.vo.ts +++ /dev/null @@ -1,38 +0,0 @@ -export class CommitMessage { - private readonly _value: string; - - constructor(value: string) { - if (!value || value.trim().length === 0) { - throw new Error("Commit message cannot be empty"); - } - this._value = value.trim(); - } - - get value(): string { - return this._value; - } - - get summary(): string { - const firstLine = this._value.split("\n")[0]; - return firstLine.length > 72 - ? firstLine.substring(0, 69) + "..." - : firstLine; - } - - get hasBody(): boolean { - return this._value.includes("\n"); - } - - get body(): string { - const lines = this._value.split("\n"); - return lines.slice(1).join("\n").trim(); - } - - equals(other: CommitMessage): boolean { - return this._value === other._value; - } - - toString(): string { - return this._value; - } -} diff --git a/src/webviews/app/domain/value-objects/file-path.vo.ts b/src/webviews/app/domain/value-objects/file-path.vo.ts deleted file mode 100644 index 8d6ff1f..0000000 --- a/src/webviews/app/domain/value-objects/file-path.vo.ts +++ /dev/null @@ -1,45 +0,0 @@ -export class FilePath { - private readonly _value: string; - - constructor(value: string) { - if (!value || value.trim().length === 0) { - throw new Error("File path cannot be empty"); - } - this._value = value.trim(); - } - - get value(): string { - return this._value; - } - - get fileName(): string { - return this._value.split("/").pop() || this._value; - } - - get directory(): string { - const parts = this._value.split("/"); - return parts.length > 1 ? parts.slice(0, -1).join("/") : ""; - } - - get extension(): string { - const fileName = this.fileName; - const lastDot = fileName.lastIndexOf("."); - return lastDot > 0 ? fileName.substring(lastDot + 1).toLowerCase() : ""; - } - - get depth(): number { - return this._value.split("/").length; - } - - isInDirectory(directory: string): boolean { - return this._value.startsWith(directory + "/"); - } - - equals(other: FilePath): boolean { - return this._value === other._value; - } - - toString(): string { - return this._value; - } -} diff --git a/src/webviews/app/domain/value-objects/file-status.vo.ts b/src/webviews/app/domain/value-objects/file-status.vo.ts deleted file mode 100644 index 5417bb1..0000000 --- a/src/webviews/app/domain/value-objects/file-status.vo.ts +++ /dev/null @@ -1,76 +0,0 @@ -export type FileStatusType = "M" | "A" | "D" | "R" | "C" | "??"; - -export class FileStatus { - private readonly _value: FileStatusType; - - constructor(value: string) { - const safeValue = value || ""; - const statusCode = safeValue.charAt(0) as FileStatusType; - if (!this.isValidStatus(statusCode)) { - throw new Error(`Invalid file status: ${value}`); - } - this._value = statusCode; - } - - get value(): FileStatusType { - return this._value; - } - - get label(): string { - switch (this._value) { - case "M": - return "Modified"; - case "A": - return "Added"; - case "D": - return "Deleted"; - case "R": - return "Renamed"; - case "C": - return "Copied"; - case "??": - return "Untracked"; - default: - return "Unknown"; - } - } - - get color(): string { - switch (this._value) { - case "M": - return "#f59e0b"; // Modified - amber - case "A": - return "#10b981"; // Added - green - case "D": - return "#ef4444"; // Deleted - red - case "R": - return "#8b5cf6"; // Renamed - purple - case "C": - return "#06b6d4"; // Copied - cyan - case "??": - return "#6b7280"; // Untracked - gray - default: - return "#6b7280"; - } - } - - get isModified(): boolean { - return this._value === "M"; - } - - get isAdded(): boolean { - return this._value === "A"; - } - - get isDeleted(): boolean { - return this._value === "D"; - } - - private isValidStatus(status: string): status is FileStatusType { - return ["M", "A", "D", "R", "C", "??"].includes(status); - } - - equals(other: FileStatus): boolean { - return this._value === other._value; - } -} diff --git a/src/webviews/app/hooks/useGitOperations.ts b/src/webviews/app/hooks/useGitOperations.ts deleted file mode 100644 index 3ae1fde..0000000 --- a/src/webviews/app/hooks/useGitOperations.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { GitService } from "../services/git.service"; -import { VSCodeBridge } from "../bridge"; - -export const useGitOperations = (bridge: VSCodeBridge) => { - const queryClient = useQueryClient(); - const gitService = new GitService(bridge); - - const pushMutation = useMutation({ - mutationFn: () => gitService.push(), - onSuccess: () => { - // Invalidate and refetch remote status - queryClient.invalidateQueries({ queryKey: ["remoteStatus"] }); - // Trigger a refresh of commits to update isPushed status - queryClient.invalidateQueries({ queryKey: ["commits"] }); - }, - onError: (error) => { - console.error("Push failed:", error); - }, - }); - - const pullMutation = useMutation({ - mutationFn: () => gitService.pull(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["remoteStatus"] }); - queryClient.invalidateQueries({ queryKey: ["commits"] }); - queryClient.invalidateQueries({ queryKey: ["changes"] }); - }, - onError: (error) => { - console.error("Pull failed:", error); - }, - }); - - const fetchMutation = useMutation({ - mutationFn: () => gitService.fetch(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["remoteStatus"] }); - }, - onError: (error) => { - console.error("Fetch failed:", error); - }, - }); - - const publishMutation = useMutation({ - mutationFn: () => gitService.publish(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["remoteStatus"] }); - }, - onError: (error) => { - console.error("Publish failed:", error); - }, - }); - - return { - push: { - mutate: pushMutation.mutate, - isLoading: pushMutation.isPending, - error: pushMutation.error, - isError: pushMutation.isError, - }, - pull: { - mutate: pullMutation.mutate, - isLoading: pullMutation.isPending, - error: pullMutation.error, - isError: pullMutation.isError, - }, - fetch: { - mutate: fetchMutation.mutate, - isLoading: fetchMutation.isPending, - error: fetchMutation.error, - isError: fetchMutation.isError, - }, - publish: { - mutate: publishMutation.mutate, - isLoading: publishMutation.isPending, - error: publishMutation.error, - isError: publishMutation.isError, - }, - }; -}; diff --git a/src/webviews/app/services/git.service.ts b/src/webviews/app/services/git.service.ts deleted file mode 100644 index a5f5c4e..0000000 --- a/src/webviews/app/services/git.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { VSCodeBridge } from "../bridge"; - -export class GitService { - constructor(private bridge: VSCodeBridge) {} - - async push(): Promise { - this.bridge.sendMessage("push"); - // Simple delay to allow the operation to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - } - - async pull(): Promise { - this.bridge.sendMessage("pull"); - // Simple delay to allow the operation to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - } - - async fetch(): Promise { - this.bridge.sendMessage("fetch"); - // Simple delay to allow the operation to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - } - - async publish(): Promise { - this.bridge.sendMessage("publish"); - // Simple delay to allow the operation to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - } -} diff --git a/src/webviews/app/shared/infrastructure/bridge/vscode-bridge.interface.ts b/src/webviews/app/shared/infrastructure/bridge/vscode-bridge.interface.ts deleted file mode 100644 index c1b9f7a..0000000 --- a/src/webviews/app/shared/infrastructure/bridge/vscode-bridge.interface.ts +++ /dev/null @@ -1,53 +0,0 @@ -export interface VSCodeMessage { - command: string; - [key: string]: any; -} - -export interface VSCodeBridge { - sendMessage(command: string, data?: any): void; - onMessage(handler: (message: VSCodeMessage) => void): void; - offMessage(handler: (message: VSCodeMessage) => void): void; - getState(): any; - setState(state: any): void; -} - -export class VSCodeBridgeImpl implements VSCodeBridge { - private messageHandlers: Set<(message: VSCodeMessage) => void> = new Set(); - private vscodeApi: any; - - constructor() { - if (typeof window !== "undefined" && (window as any).vscodeApi) { - this.vscodeApi = (window as any).vscodeApi; - this.setupMessageListener(); - } - } - - sendMessage(command: string, data: any = {}): void { - if (this.vscodeApi) { - this.vscodeApi.postMessage({ command, ...data }); - } - } - - onMessage(handler: (message: VSCodeMessage) => void): void { - this.messageHandlers.add(handler); - } - - offMessage(handler: (message: VSCodeMessage) => void): void { - this.messageHandlers.delete(handler); - } - - getState(): any { - return this.vscodeApi?.getState(); - } - - setState(state: any): void { - this.vscodeApi?.setState(state); - } - - private setupMessageListener(): void { - window.addEventListener("message", (event) => { - const message = event.data; - this.messageHandlers.forEach((handler) => handler(message)); - }); - } -} diff --git a/src/webviews/app/shared/infrastructure/config/service-registration.ts b/src/webviews/app/shared/infrastructure/config/service-registration.ts deleted file mode 100644 index 0c9354b..0000000 --- a/src/webviews/app/shared/infrastructure/config/service-registration.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ServiceContainer } from "../di/service-container"; -import { VSCodeBridgeImpl } from "../bridge/vscode-bridge.interface"; -import { CommitServiceImpl } from "../../../features/commits/services/commit.service"; -import { BranchServiceImpl } from "../../../features/branches/services/branch.service"; - -export const configureServices = (): ServiceContainer => { - const container = new ServiceContainer(); - - // Register bridge - container.register("VSCodeBridge", () => new VSCodeBridgeImpl()); - - // Register feature services - container.register("CommitService", () => { - const bridge = container.get("VSCodeBridge"); - return new CommitServiceImpl(bridge); - }); - - container.register("BranchService", () => { - const bridge = container.get("VSCodeBridge"); - return new BranchServiceImpl(bridge); - }); - - return container; -}; diff --git a/src/webviews/app/shared/infrastructure/di/service-container.ts b/src/webviews/app/shared/infrastructure/di/service-container.ts deleted file mode 100644 index 737a1ad..0000000 --- a/src/webviews/app/shared/infrastructure/di/service-container.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ServiceKey, ServiceFactory } from "./service-locator"; - -export class ServiceContainer { - private services = new Map(); - private factories = new Map(); - - register(key: ServiceKey, factory: ServiceFactory): ServiceContainer { - this.factories.set(key, factory); - return this; - } - - registerInstance(key: ServiceKey, instance: T): ServiceContainer { - this.services.set(key, instance); - return this; - } - - get(key: ServiceKey): T { - // Return existing instance if available - if (this.services.has(key)) { - return this.services.get(key); - } - - // Create new instance using factory - const factory = this.factories.get(key); - if (!factory) { - throw new Error(`Service '${key}' not registered`); - } - - const instance = factory(); - this.services.set(key, instance); - return instance; - } - - has(key: ServiceKey): boolean { - return this.services.has(key) || this.factories.has(key); - } - - clear(): void { - this.services.clear(); - this.factories.clear(); - } - - clone(): ServiceContainer { - const container = new ServiceContainer(); - container.factories = new Map(this.factories); - container.services = new Map(this.services); - return container; - } -} diff --git a/src/webviews/app/shared/infrastructure/di/service-context.tsx b/src/webviews/app/shared/infrastructure/di/service-context.tsx deleted file mode 100644 index d355ea3..0000000 --- a/src/webviews/app/shared/infrastructure/di/service-context.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { createContext, ReactNode } from 'react'; -import { ServiceContainer } from './service-container'; - -export const ServiceContext = createContext(null); - -interface ServiceProviderProps { - container: ServiceContainer; - children: ReactNode; -} - -export const ServiceProvider: React.FC = ({ - container, - children -}) => { - return ( - - {children} - - ); -}; \ No newline at end of file diff --git a/src/webviews/app/shared/infrastructure/di/service-locator.ts b/src/webviews/app/shared/infrastructure/di/service-locator.ts deleted file mode 100644 index 5b15b46..0000000 --- a/src/webviews/app/shared/infrastructure/di/service-locator.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useContext } from "react"; -import { ServiceContainer } from "./service-container"; -import { ServiceContext } from "./service-context"; - -export type ServiceKey = string; -export type ServiceFactory = () => T; - -export class ServiceLocator { - private static instance: ServiceLocator; - private services = new Map(); - private factories = new Map(); - - static getInstance(): ServiceLocator { - if (!ServiceLocator.instance) { - ServiceLocator.instance = new ServiceLocator(); - } - return ServiceLocator.instance; - } - - register(key: ServiceKey, factory: ServiceFactory): void { - this.factories.set(key, factory); - } - - registerInstance(key: ServiceKey, instance: T): void { - this.services.set(key, instance); - } - - get(key: ServiceKey): T { - // Return existing instance if available - if (this.services.has(key)) { - return this.services.get(key); - } - - // Create new instance using factory - const factory = this.factories.get(key); - if (!factory) { - throw new Error(`Service '${key}' not registered`); - } - - const instance = factory(); - this.services.set(key, instance); - return instance; - } - - clear(): void { - this.services.clear(); - this.factories.clear(); - } -} - -export const useService = (key: ServiceKey): T => { - const container = useContext(ServiceContext); - if (!container) { - throw new Error("useService must be used within ServiceProvider"); - } - return container.get(key); -}; diff --git a/src/webviews/app/shared/utils/date.utils.ts b/src/webviews/app/shared/utils/date.utils.ts deleted file mode 100644 index 73e3d03..0000000 --- a/src/webviews/app/shared/utils/date.utils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import dayjs from "dayjs"; -import relativeTime from "dayjs/plugin/relativeTime"; - -// Initialize dayjs plugins -dayjs.extend(relativeTime); - -export class DateUtils { - static formatRelativeTime(date: string | Date | dayjs.Dayjs): string { - const parsedDate = dayjs(date); - - if (!parsedDate.isValid()) { - return "Invalid date"; - } - - const now = dayjs(); - const diffInDays = now.diff(parsedDate, "day"); - - // For dates within the last 7 days, show relative format - if (diffInDays <= 7) { - if (diffInDays === 0) return "today"; - if (diffInDays === 1) return "yesterday"; - return `${diffInDays} days ago`; - } - - // For older dates, show formatted date with time - return parsedDate.format("D MMMM, YYYY [at] HH:mm"); - } - - static formatAbsoluteTime(date: string | Date | dayjs.Dayjs): string { - const parsedDate = dayjs(date); - - if (!parsedDate.isValid()) { - return "Invalid date"; - } - - return parsedDate.format("D MMMM, YYYY [at] HH:mm"); - } - - static fromNow(date: string | Date | dayjs.Dayjs): string { - const parsedDate = dayjs(date); - - if (!parsedDate.isValid()) { - return "Invalid date"; - } - - return parsedDate.fromNow(); - } - - static isValid(date: string | Date | dayjs.Dayjs): boolean { - return dayjs(date).isValid(); - } - - static isSameDay( - date1: string | Date | dayjs.Dayjs, - date2: string | Date | dayjs.Dayjs, - ): boolean { - return dayjs(date1).isSame(dayjs(date2), "day"); - } - - static isAfter( - date1: string | Date | dayjs.Dayjs, - date2: string | Date | dayjs.Dayjs, - ): boolean { - return dayjs(date1).isAfter(dayjs(date2)); - } - - static isBefore( - date1: string | Date | dayjs.Dayjs, - date2: string | Date | dayjs.Dayjs, - ): boolean { - return dayjs(date1).isBefore(dayjs(date2)); - } -} diff --git a/src/webviews/app/shared/utils/file.utils.ts b/src/webviews/app/shared/utils/file.utils.ts deleted file mode 100644 index 8b47ea9..0000000 --- a/src/webviews/app/shared/utils/file.utils.ts +++ /dev/null @@ -1,84 +0,0 @@ -export class FileUtils { - static getFileName(path: string): string { - return path.split("/").pop() || path; - } - - static getDirectory(path: string): string { - const parts = path.split("/"); - return parts.length > 1 ? parts.slice(0, -1).join("/") : ""; - } - - static getExtension(path: string): string { - const fileName = FileUtils.getFileName(path); - const lastDot = fileName.lastIndexOf("."); - return lastDot > 0 ? fileName.substring(lastDot + 1).toLowerCase() : ""; - } - - static getFileNameWithoutExtension(path: string): string { - const fileName = FileUtils.getFileName(path); - const lastDot = fileName.lastIndexOf("."); - return lastDot > 0 ? fileName.substring(0, lastDot) : fileName; - } - - static isInDirectory(filePath: string, directory: string): boolean { - return filePath.startsWith(directory + "/"); - } - - static getDepth(path: string): number { - return path.split("/").length; - } - - static normalizePath(path: string): string { - return path.replace(/\\/g, "/").replace(/\/+/g, "/"); - } - - static isImageFile(path: string): boolean { - const extension = FileUtils.getExtension(path); - return ["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"].includes( - extension, - ); - } - - static isTextFile(path: string): boolean { - const extension = FileUtils.getExtension(path); - return ["txt", "md", "json", "xml", "yml", "yaml", "csv"].includes( - extension, - ); - } - - static isCodeFile(path: string): boolean { - const extension = FileUtils.getExtension(path); - return [ - "js", - "jsx", - "ts", - "tsx", - "vue", - "py", - "java", - "c", - "cpp", - "h", - "hpp", - "cs", - "php", - "rb", - "go", - "rs", - "swift", - "kt", - "scala", - "html", - "css", - "scss", - "sass", - "less", - "sql", - "sh", - "bash", - "ps1", - "r", - "matlab", - ].includes(extension); - } -} diff --git a/src/webviews/bootstrap.tsx b/src/webviews/bootstrap.tsx deleted file mode 100644 index c783549..0000000 --- a/src/webviews/bootstrap.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import { createRoot } from "react-dom/client"; -import { App } from "./app/App"; -import { createVSCodeBridge, InitialWebviewState, VSCodeApi } from "./app/bridge"; - -declare global { - interface Window { - vscodeApi: VSCodeApi; - initialData?: InitialWebviewState; - } -} - -const container = document.getElementById("root"); - -if (!container) { - throw new Error("Failed to locate root element for GitHub Desktop UI"); -} - -const bridge = createVSCodeBridge(window.vscodeApi); -const fallbackState: InitialWebviewState = { - changes: [], - history: [], - branches: [], - currentBranch: null, - repository: null, - accounts: [], - activeAccount: null, -}; -const initialState: InitialWebviewState = { ...fallbackState, ...(window.initialData ?? {}) }; - -const root = createRoot(container); -root.render( - - - -); \ No newline at end of file diff --git a/vite.webview.config.ts b/vite.webview.config.ts index 9262944..85d1bc7 100644 --- a/vite.webview.config.ts +++ b/vite.webview.config.ts @@ -17,7 +17,6 @@ export default defineConfig({ emptyOutDir: true, rollupOptions: { input: { - main: path.resolve(__dirname, "src/webviews/bootstrap.tsx"), 'commit-detail': path.resolve(__dirname, "src/webviews/commitDetail/index.tsx"), 'history-explorer': path.resolve(__dirname, "src/webviews/historyExplorerApp/index.tsx"), }, From 039a24a15f2afba404cb98ad9e57f6cd67b6e5f9 Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:57:13 +0200 Subject: [PATCH 11/33] refactor(timeline): split the message handler into SOLID services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MessageHandlerService (944 lines, 40-case switch, god object) is replaced by: - GitClientFactory / AccountGitClientFactory: single owner of git client creation and credentials. withAuth() injects the token via ephemeral GIT_CONFIG_* env vars, so it is never written to .git/config and there is no shared remote URL to race on (fixes the token-leak + race bugs). - InboundMessage / OutboundMessage: discriminated unions, no `any`, no casts. - MessageRouter: Map; adding a command no longer edits a switch (Open/Closed). - Cohesive services, each with one reason to change: SyncService, WorkingTreeService, BranchService, CommitActionsService, PullRequestService, DiffService, RepositoryDataService (read model). - Ports (RepositoryContext, Notifier, Browser, WebviewChannel, Refresher) with VS Code adapters — services no longer import `vscode` or simpleGit. - TimelineController: composition root + the one place that recomputes and broadcasts state. Behaviour changes: - pull / merge / revert / cherry-pick now detect conflicts and emit a `mergeConflict` message instead of swallowing the error. - publish uses authentication (was a plain client -> first publish failed). - commit only clears the input on a `commitSucceeded` ack. - branch activity is one `for-each-ref` call, not one `git log` per branch. - the provider debounces refresh (400ms) so save-all triggers one recompute. Co-Authored-By: Claude Sonnet 5 --- src/core/git/git-authenticator.ts | 86 ++ src/shared/utils/github-url.ts | 18 + .../timeline/adapters/vscode-adapters.ts | 78 ++ .../interfaces/repository-snapshot.ts | 41 + src/webviews/timeline/message-router.ts | 54 + src/webviews/timeline/messages.ts | 101 ++ src/webviews/timeline/ports.ts | 67 ++ .../timeline/services/branch.service.ts | 102 ++ .../services/commit-actions.service.ts | 146 +++ .../timeline/services/diff.service.ts | 46 + .../timeline/services/file-icon.service.ts | 109 -- .../services/git-operations.service.ts | 593 ----------- .../services/message-handler.service.ts | 944 ------------------ .../timeline/services/pull-request.service.ts | 29 + .../services/repository-data.service.ts | 435 ++++++++ .../services/repository-scoped.service.ts | 29 + .../timeline/services/sync.service.ts | 106 ++ .../timeline/services/working-tree.service.ts | 113 +++ src/webviews/timeline/timeline-controller.ts | 193 ++++ .../timeline/timeline-view-provider.ts | 101 +- 20 files changed, 1686 insertions(+), 1705 deletions(-) create mode 100644 src/core/git/git-authenticator.ts create mode 100644 src/shared/utils/github-url.ts create mode 100644 src/webviews/timeline/adapters/vscode-adapters.ts create mode 100644 src/webviews/timeline/interfaces/repository-snapshot.ts create mode 100644 src/webviews/timeline/message-router.ts create mode 100644 src/webviews/timeline/messages.ts create mode 100644 src/webviews/timeline/ports.ts create mode 100644 src/webviews/timeline/services/branch.service.ts create mode 100644 src/webviews/timeline/services/commit-actions.service.ts create mode 100644 src/webviews/timeline/services/diff.service.ts delete mode 100644 src/webviews/timeline/services/file-icon.service.ts delete mode 100644 src/webviews/timeline/services/git-operations.service.ts delete mode 100644 src/webviews/timeline/services/message-handler.service.ts create mode 100644 src/webviews/timeline/services/pull-request.service.ts create mode 100644 src/webviews/timeline/services/repository-data.service.ts create mode 100644 src/webviews/timeline/services/repository-scoped.service.ts create mode 100644 src/webviews/timeline/services/sync.service.ts create mode 100644 src/webviews/timeline/services/working-tree.service.ts create mode 100644 src/webviews/timeline/timeline-controller.ts diff --git a/src/core/git/git-authenticator.ts b/src/core/git/git-authenticator.ts new file mode 100644 index 0000000..c2bda0a --- /dev/null +++ b/src/core/git/git-authenticator.ts @@ -0,0 +1,86 @@ +import simpleGit, { SimpleGit } from "simple-git"; +import { AccountManager } from "../accounts/account-manager"; + +/** + * @description + * Abstraction over the creation of `simple-git` clients. High-level services + * depend on this port instead of importing `simpleGit` directly, which keeps + * them testable (Dependency Inversion) and confines all credential handling to + * a single place. + */ +export interface GitClientFactory { + /** + * A plain client with no credentials attached. Use for read-only or purely + * local operations (status, log, checkout, merge, reset...). + * + * @param repoPath - Absolute path to the repository working directory + */ + plain(repoPath: string): SimpleGit; + + /** + * Runs `fn` with a client whose network operations carry the active GitHub + * account's token. + * + * The token is injected through the `GIT_CONFIG_*` environment variables of + * the spawned git process as an ephemeral `http.extraheader`. It is **never** + * written to `.git/config`, so there is nothing to clean up and no shared + * state to race on. + * + * If no account is active or no token is available, `fn` still runs with a + * plain client (git may then fall back to a system credential helper). + * + * @param repoPath - Absolute path to the repository working directory + * @param fn - Callback receiving the (possibly authenticated) client + * @returns Whatever `fn` resolves to + * @throws Propagates any error thrown by `fn` + * + * @example + * await gitFactory.withAuth(repo.localPath, (git) => git.push(["-u", "origin", branch])); + */ + withAuth(repoPath: string, fn: (git: SimpleGit) => Promise): Promise; +} + +/** + * @description Default {@link GitClientFactory} backed by the extension's + * {@link AccountManager}. Resolves the token of the currently active account. + */ +export class AccountGitClientFactory implements GitClientFactory { + constructor(private readonly accounts: AccountManager) {} + + plain(repoPath: string): SimpleGit { + return simpleGit(repoPath); + } + + async withAuth( + repoPath: string, + fn: (git: SimpleGit) => Promise, + ): Promise { + const git = simpleGit(repoPath); + const token = await this.resolveToken(); + if (!token) { + return fn(git); + } + + const header = `Authorization: Basic ${Buffer.from( + `x-access-token:${token}`, + ).toString("base64")}`; + + git.env({ + ...process.env, + GIT_CONFIG_COUNT: "1", + GIT_CONFIG_KEY_0: "http.https://github.com/.extraheader", + GIT_CONFIG_VALUE_0: header, + GIT_TERMINAL_PROMPT: "0", + }); + + return fn(git); + } + + private async resolveToken(): Promise { + const active = this.accounts.getActiveAccount(); + if (!active) { + return undefined; + } + return (await this.accounts.getToken(active.id)) ?? undefined; + } +} diff --git a/src/shared/utils/github-url.ts b/src/shared/utils/github-url.ts new file mode 100644 index 0000000..27bcc5f --- /dev/null +++ b/src/shared/utils/github-url.ts @@ -0,0 +1,18 @@ +/** + * @description Normalises a git remote URL (SSH or HTTPS, with or without a + * trailing `.git`) into its browsable `https://github.com/owner/repo` form. + * + * @param remoteUrl - The value of `remote.origin.url`, or `undefined` + * @returns The web URL, or `null` when `remoteUrl` is missing + * + * @example + * toGitHubWebUrl("git@github.com:owner/repo.git"); // "https://github.com/owner/repo" + */ +export function toGitHubWebUrl(remoteUrl: string | undefined): string | null { + if (!remoteUrl) { + return null; + } + return remoteUrl + .replace(/^git@github\.com:/, "https://github.com/") + .replace(/\.git$/, ""); +} diff --git a/src/webviews/timeline/adapters/vscode-adapters.ts b/src/webviews/timeline/adapters/vscode-adapters.ts new file mode 100644 index 0000000..98eef27 --- /dev/null +++ b/src/webviews/timeline/adapters/vscode-adapters.ts @@ -0,0 +1,78 @@ +import * as vscode from "vscode"; +import { RepositoryManager } from "../../../core/repositories/repository-manager"; +import { getPrimaryRepository } from "../../../shared/utils/repo-selection"; +import { TrackedRepository } from "../../../shared/types"; +import { OutboundMessage } from "../messages"; +import { Browser, Notifier, RepositoryContext, WebviewChannel } from "../ports"; + +/** + * @description {@link RepositoryContext} backed by the extension's + * {@link RepositoryManager} and the VS Code workspace. + */ +export class WorkspaceRepositoryContext implements RepositoryContext { + constructor(private readonly repositories: RepositoryManager) {} + + getPrimary(): TrackedRepository | undefined { + return getPrimaryRepository(this.repositories); + } +} + +/** + * @description {@link Notifier} backed by `vscode.window`. + */ +export class VsCodeNotifier implements Notifier { + info(message: string): void { + void vscode.window.showInformationMessage(message); + } + + warn(message: string): void { + void vscode.window.showWarningMessage(message); + } + + error(message: string): void { + void vscode.window.showErrorMessage(message); + } + + async confirm(message: string, confirmLabel: string): Promise { + const choice = await vscode.window.showWarningMessage( + message, + { modal: true }, + confirmLabel, + ); + return choice === confirmLabel; + } + + async prompt(options: { + prompt: string; + placeHolder?: string; + validateInput?: (value: string) => string | null; + }): Promise { + const value = await vscode.window.showInputBox({ + prompt: options.prompt, + placeHolder: options.placeHolder, + ignoreFocusOut: true, + validateInput: options.validateInput, + }); + return value?.trim() || undefined; + } +} + +/** + * @description {@link Browser} backed by `vscode.env.openExternal`. + */ +export class VsCodeBrowser implements Browser { + open(url: string): void { + void vscode.env.openExternal(vscode.Uri.parse(url)); + } +} + +/** + * @description {@link WebviewChannel} backed by a concrete `vscode.Webview`. + */ +export class VsCodeWebviewChannel implements WebviewChannel { + constructor(private readonly webview: vscode.Webview) {} + + post(message: OutboundMessage): void { + void this.webview.postMessage(message); + } +} diff --git a/src/webviews/timeline/interfaces/repository-snapshot.ts b/src/webviews/timeline/interfaces/repository-snapshot.ts new file mode 100644 index 0000000..906f7eb --- /dev/null +++ b/src/webviews/timeline/interfaces/repository-snapshot.ts @@ -0,0 +1,41 @@ +import { ChangeEntry, CommitEntry } from "./timeline-view-provider.interface"; + +/** + * @description Tracking state of the current branch against its upstream. + */ +export interface RemoteStatus { + hasRemote: boolean; + isPublished: boolean; + ahead: number; + behind: number; + lastFetched: Date | null; + remoteBranch: string | null; +} + +/** + * @description Minimal repository identity shown in the timeline toolbar. + */ +export interface RepositorySummary { + name: string; + path: string; + remote?: string; +} + +/** + * @description Everything the timeline needs for one render pass, assembled by + * the repository read model in a single call. + */ +export interface RepositorySnapshot { + changes: ChangeEntry[]; + commits: CommitEntry[]; + hasMoreCommits: boolean; + branches: string[]; + currentBranch: string | null; + branchActivity: Record; + repository: RepositorySummary; + remoteStatus: RemoteStatus; + tags: Record; +} + +/** Shared page size for commit history queries. */ +export const COMMITS_PAGE_SIZE = 50; diff --git a/src/webviews/timeline/message-router.ts b/src/webviews/timeline/message-router.ts new file mode 100644 index 0000000..51e2391 --- /dev/null +++ b/src/webviews/timeline/message-router.ts @@ -0,0 +1,54 @@ +import { InboundCommand, InboundMessage } from "./messages"; +import { Notifier } from "./ports"; + +/** + * @description Handler for one inbound command. The message is narrowed to the + * matching union member by {@link MessageRouter.on}. + */ +export type CommandHandler = ( + message: Extract, +) => Promise | void; + +/** + * @description Maps inbound commands to handlers. Adding a command means + * registering a handler — the router itself never changes (Open/Closed). + */ +export class MessageRouter { + private readonly handlers = new Map< + InboundCommand, + CommandHandler + >(); + + constructor(private readonly notifier: Notifier) {} + + /** + * Registers `handler` for `command`, replacing any previous registration. + * + * @returns `this`, for chaining. + */ + on(command: C, handler: CommandHandler): this { + this.handlers.set( + command, + handler as unknown as CommandHandler, + ); + return this; + } + + /** + * Dispatches `message` to its handler. Unknown commands are ignored; handler + * errors are surfaced to the user rather than swallowed silently. + */ + async dispatch(message: InboundMessage): Promise { + const handler = this.handlers.get(message.command); + if (!handler) { + return; + } + try { + await handler(message); + } catch (error) { + this.notifier.error( + error instanceof Error ? error.message : String(error), + ); + } + } +} diff --git a/src/webviews/timeline/messages.ts b/src/webviews/timeline/messages.ts new file mode 100644 index 0000000..8ab947a --- /dev/null +++ b/src/webviews/timeline/messages.ts @@ -0,0 +1,101 @@ +import { + ChangeEntry, + CommitEntry, + CommitDetail, +} from "./interfaces/timeline-view-provider.interface"; +import { + RemoteStatus, + RepositorySummary, +} from "./interfaces/repository-snapshot"; + +/* ------------------------------------------------------------------ * + * Inbound: webview -> extension + * ------------------------------------------------------------------ */ + +/** + * @description Every message the timeline webview can send, as a discriminated + * union keyed on `command`. Handlers narrow on `command` and get fully typed + * payloads — no `any`, no manual casts. + */ +export type InboundMessage = + | { command: "ready" } + | { command: "refresh" } + | { command: "stageFiles"; files: string[] } + | { command: "unstageFiles"; files: string[] } + | { command: "commit"; message: string } + | { command: "commitFiles"; message: string; files: string[] } + | { command: "discardFiles"; files: string[] } + | { command: "getWorkingDiff"; filePath: string } + | { command: "fetch" } + | { command: "pull" } + | { command: "push" } + | { command: "publish" } + | { command: "checkoutBranch"; branch: string } + | { command: "createBranch"; branchName: string } + | { + command: "createBranchWithChanges"; + branchName: string; + bringChanges: boolean; + } + | { command: "mergeBranch"; fromBranch: string; toBranch: string } + | { command: "createPullRequest"; branch: string } + | { command: "loadMoreCommits"; offset: number } + | { command: "openCommitDetail"; hash: string } + | { command: "getCommitDetails"; hash: string } + | { command: "getFileDiff"; hash: string; filePath: string } + | { command: "selectCommit"; hash: string } + | { command: "selectFile"; hash: string; path: string } + | { command: "resetToCommit"; hash: string } + | { command: "checkoutCommit"; hash: string } + | { command: "revertCommit"; hash: string } + | { command: "cherryPickCommit"; hash: string } + | { command: "createBranchFromCommit"; hash: string } + | { command: "createTagFromCommit"; hash: string } + | { command: "viewCommitOnGitHub"; hash: string }; + +export type InboundCommand = InboundMessage["command"]; + +/* ------------------------------------------------------------------ * + * Outbound: extension -> webview + * ------------------------------------------------------------------ */ + +export type OutboundMessage = + | { command: "updateRepository"; repository: RepositorySummary | null } + | { command: "updateChanges"; changes: ChangeEntry[] } + | { + command: "updateHistory"; + history: CommitEntry[]; + hasMoreCommits: boolean; + offset: number; + } + | { + command: "updateBranches"; + branches: string[]; + currentBranch: string | null; + branchActivity: Record; + } + | { + command: "updateAccounts"; + activeAccount: { + login: string; + name?: string; + avatarUrl?: string; + } | null; + } + | { + command: "updateRemoteStatus"; + remoteStatus: RemoteStatus; + tags: Record; + } + | { + command: "loadMoreCommitsResponse"; + history: CommitEntry[]; + hasMoreCommits: boolean; + offset: number; + } + | { command: "commitSucceeded" } + | { command: "mergeConflict"; operation: string; files: string[] } + | { command: "workingDiff"; payload: { path: string; diff: string } } + | { command: "fileDiff"; payload: { path: string; diff: string } } + | { command: "commitDetail"; payload: CommitDetail } + | { command: "error"; message: string }; diff --git a/src/webviews/timeline/ports.ts b/src/webviews/timeline/ports.ts new file mode 100644 index 0000000..dc7e420 --- /dev/null +++ b/src/webviews/timeline/ports.ts @@ -0,0 +1,67 @@ +import { TrackedRepository } from "../../shared/types"; +import { OutboundMessage } from "./messages"; + +/** + * @description Resolves which repository the timeline currently operates on. + * Abstracts {@link getPrimaryRepository} so services never touch the + * repository manager or the VS Code workspace API directly. + */ +export interface RepositoryContext { + /** The active repository, or `undefined` when the workspace has none. */ + getPrimary(): TrackedRepository | undefined; +} + +/** + * @description Thin abstraction over the user-facing notification surface + * (`vscode.window.*`). Keeps services free of the `vscode` namespace and + * trivially mockable in tests. + */ +export interface Notifier { + info(message: string): void; + warn(message: string): void; + error(message: string): void; + + /** + * Asks the user to confirm a destructive action. + * + * @param message - The question to display (modal) + * @param confirmLabel - Label of the confirming button + * @returns `true` only when the user picked the confirming button + */ + confirm(message: string, confirmLabel: string): Promise; + + /** + * Prompts the user for a single line of text. + * + * @returns The trimmed input, or `undefined` when cancelled + */ + prompt(options: { + prompt: string; + placeHolder?: string; + validateInput?: (value: string) => string | null; + }): Promise; +} + +/** + * @description Outbound half of the webview message channel. Services push + * {@link OutboundMessage}s; they never receive through this port. + */ +export interface WebviewChannel { + post(message: OutboundMessage): void; +} + +/** + * @description Opens URLs in the user's external browser. + */ +export interface Browser { + open(url: string): void; +} + +/** + * @description Triggers a full recompute-and-broadcast of the timeline state. + * Mutating services call this after a successful operation instead of pushing + * partial updates themselves. + */ +export interface Refresher { + refresh(): Promise; +} diff --git a/src/webviews/timeline/services/branch.service.ts b/src/webviews/timeline/services/branch.service.ts new file mode 100644 index 0000000..138eac5 --- /dev/null +++ b/src/webviews/timeline/services/branch.service.ts @@ -0,0 +1,102 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { + Notifier, + RepositoryContext, + Refresher, + WebviewChannel, +} from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +/** + * @description Branch lifecycle for the branch dropdown: switching, creating + * (optionally carrying or stashing uncommitted work), and merging. + */ +export class BranchService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly channel: WebviewChannel, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + checkout(branch: string): Promise { + return this.withRepo(async (repo) => { + if (!branch) { + return; + } + // Never check out a `remotes//` ref directly: that detaches + // HEAD. Fall back to the short name so git sets up local tracking. + const remoteMatch = branch.match(/^remotes\/[^/]+\/(.+)$/); + const target = remoteMatch ? remoteMatch[1] : branch; + await this.git.plain(repo.localPath).checkout(target); + this.notifier.info(`Switched to ${target}`); + await this.refresher.refresh(); + }); + } + + create(branchName: string): Promise { + return this.withRepo(async (repo) => { + if (!branchName) { + return; + } + await this.git.plain(repo.localPath).checkoutLocalBranch(branchName); + this.notifier.info(`Created and switched to '${branchName}'`); + await this.refresher.refresh(); + }); + } + + createWithChanges( + branchName: string, + bringChanges: boolean, + ): Promise { + return this.withRepo(async (repo) => { + if (!branchName) { + return; + } + const git = this.git.plain(repo.localPath); + if (!bringChanges) { + await git.stash(["push", "-m", `Stash before creating ${branchName}`]); + } + await git.checkoutLocalBranch(branchName); + this.notifier.info( + bringChanges + ? `Created '${branchName}' with your changes` + : `Created '${branchName}' and stashed your changes`, + ); + await this.refresher.refresh(); + }); + } + + merge(fromBranch: string, toBranch: string): Promise { + return this.withRepo(async (repo) => { + if (!fromBranch || !toBranch) { + return; + } + const git = this.git.plain(repo.localPath); + await git.checkout(toBranch); + try { + await git.merge([fromBranch]); + } catch (error) { + const status = await git.status(); + if (status.conflicted.length > 0) { + this.channel.post({ + command: "mergeConflict", + operation: `merge ${fromBranch}`, + files: status.conflicted, + }); + this.notifier.warn( + `Merge stopped: ${status.conflicted.length} file(s) in conflict`, + ); + await this.refresher.refresh(); + return; + } + throw error; + } + this.notifier.info(`Merged ${fromBranch} into ${toBranch}`); + await this.refresher.refresh(); + }); + } +} diff --git a/src/webviews/timeline/services/commit-actions.service.ts b/src/webviews/timeline/services/commit-actions.service.ts new file mode 100644 index 0000000..02f3e6c --- /dev/null +++ b/src/webviews/timeline/services/commit-actions.service.ts @@ -0,0 +1,146 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { toGitHubWebUrl } from "../../../shared/utils/github-url"; +import { Browser, Notifier, RepositoryContext, Refresher } from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +const shortHash = (hash: string): string => hash.substring(0, 7); + +/** + * @description Actions available from a commit's context menu in the History + * tab: reset, checkout, revert, branch-from, tag-from, cherry-pick and + * "view on GitHub". + */ +export class CommitActionsService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly browser: Browser, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + reset(hash: string): Promise { + return this.withRepo(async (repo) => { + const confirmed = await this.notifier.confirm( + `Reset to ${shortHash(hash)}? All changes after this commit are discarded.`, + "Reset", + ); + if (!confirmed) { + return; + } + await this.git.plain(repo.localPath).reset(["--hard", hash]); + this.notifier.info(`Reset to ${shortHash(hash)}`); + await this.refresher.refresh(); + }); + } + + checkout(hash: string): Promise { + return this.withRepo(async (repo) => { + await this.git.plain(repo.localPath).checkout(hash); + this.notifier.info(`Checked out ${shortHash(hash)} (detached HEAD)`); + await this.refresher.refresh(); + }); + } + + revert(hash: string): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + try { + await git.revert(hash, ["--no-edit"]); + } catch (error) { + if ( + await this.reportConflict(repo.localPath, `revert ${shortHash(hash)}`) + ) { + return; + } + throw error; + } + this.notifier.info(`Reverted ${shortHash(hash)}`); + await this.refresher.refresh(); + }); + } + + cherryPick(hash: string): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + try { + await git.raw(["cherry-pick", hash]); + } catch (error) { + if ( + await this.reportConflict( + repo.localPath, + `cherry-pick ${shortHash(hash)}`, + ) + ) { + return; + } + throw error; + } + this.notifier.info(`Cherry-picked ${shortHash(hash)}`); + await this.refresher.refresh(); + }); + } + + branchFrom(hash: string): Promise { + return this.withRepo(async (repo) => { + const name = await this.notifier.prompt({ + prompt: "New branch name", + placeHolder: "feature/my-branch", + }); + if (!name) { + return; + } + await this.git.plain(repo.localPath).checkoutBranch(name, hash); + this.notifier.info(`Created '${name}' from ${shortHash(hash)}`); + await this.refresher.refresh(); + }); + } + + tagFrom(hash: string): Promise { + return this.withRepo(async (repo) => { + const name = await this.notifier.prompt({ + prompt: "Tag name", + placeHolder: "v1.0.0", + validateInput: (v) => + /^[A-Za-z0-9._/-]+$/.test(v) ? null : "Invalid tag name", + }); + if (!name) { + return; + } + await this.git.withAuth(repo.localPath, async (g) => { + await g.raw(["tag", "-a", name, hash, "-m", `Release ${name}`]); + await g.raw(["push", "origin", `refs/tags/${name}`]); + }); + this.notifier.info(`Created and pushed tag '${name}'`); + await this.refresher.refresh(); + }); + } + + viewOnGitHub(hash: string): Promise { + return this.withRepo(async (repo) => { + const base = toGitHubWebUrl(repo.remoteUrl); + if (!base) { + this.notifier.warn("No GitHub remote configured"); + return; + } + this.browser.open(`${base}/commit/${hash}`); + }); + } + + private async reportConflict( + repoPath: string, + operation: string, + ): Promise { + const status = await this.git.plain(repoPath).status(); + if (status.conflicted.length === 0) { + return false; + } + this.notifier.warn( + `${operation} stopped: ${status.conflicted.length} file(s) in conflict`, + ); + await this.refresher.refresh(); + return true; + } +} diff --git a/src/webviews/timeline/services/diff.service.ts b/src/webviews/timeline/services/diff.service.ts new file mode 100644 index 0000000..1286850 --- /dev/null +++ b/src/webviews/timeline/services/diff.service.ts @@ -0,0 +1,46 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { Notifier, RepositoryContext, WebviewChannel } from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; +import { RepositoryDataService } from "./repository-data.service"; + +/** + * @description Read-only diff/detail lookups requested from the History tab: + * a commit's aggregate detail and the patch of one file within a commit. + */ +export class DiffService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly data: RepositoryDataService, + private readonly channel: WebviewChannel, + ) { + super(repos, notifier); + } + + async commitDetail(hash: string): Promise { + try { + const payload = await this.data.getCommitDetail(hash); + this.channel.post({ command: "commitDetail", payload }); + } catch (error) { + this.channel.post({ + command: "error", + message: error instanceof Error ? error.message : String(error), + }); + } + } + + fileDiff(hash: string, filePath: string): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + let diff = await git.raw(["show", hash, "--patch", "--", filePath]); + if (!diff.trim()) { + diff = await git.raw(["show", hash, "--", filePath]); + } + this.channel.post({ + command: "fileDiff", + payload: { path: filePath, diff }, + }); + }); + } +} diff --git a/src/webviews/timeline/services/file-icon.service.ts b/src/webviews/timeline/services/file-icon.service.ts deleted file mode 100644 index b194d57..0000000 --- a/src/webviews/timeline/services/file-icon.service.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as path from "path"; -import { FileIconInfo } from "../interfaces/timeline-view-provider.interface"; - -export class FileIconService { - getFileIconInfo(filePath: string): FileIconInfo { - try { - const ext = path.extname(filePath).toLowerCase(); - const baseName = path.basename(filePath).toLowerCase(); - - let iconName = "file"; - - // Special file names - if (baseName === "package.json") iconName = "package"; - else if (baseName === "tsconfig.json") iconName = "gear"; - else if (baseName === ".gitignore") iconName = "git-commit"; - else if (baseName.startsWith(".env")) iconName = "gear"; - else if (baseName === "readme.md") iconName = "book"; - else if (baseName.startsWith("dockerfile")) iconName = "file-binary"; - // File extensions - else { - switch (ext) { - case ".ts": - iconName = "symbol-class"; - break; - case ".tsx": - iconName = "symbol-class"; - break; - case ".js": - iconName = "symbol-method"; - break; - case ".jsx": - iconName = "symbol-method"; - break; - case ".json": - iconName = "json"; - break; - case ".html": - iconName = "code"; - break; - case ".css": - iconName = "symbol-color"; - break; - case ".scss": - iconName = "symbol-color"; - break; - case ".md": - iconName = "markdown"; - break; - case ".yml": - case ".yaml": - iconName = "settings-gear"; - break; - case ".xml": - iconName = "code"; - break; - case ".svg": - iconName = "file-media"; - break; - case ".png": - case ".jpg": - case ".jpeg": - case ".gif": - iconName = "file-media"; - break; - case ".pdf": - iconName = "file-pdf"; - break; - case ".zip": - iconName = "file-zip"; - break; - case ".py": - iconName = "symbol-namespace"; - break; - case ".java": - iconName = "symbol-class"; - break; - case ".go": - iconName = "symbol-method"; - break; - case ".rs": - iconName = "symbol-misc"; - break; - case ".vue": - iconName = "symbol-namespace"; - break; - case ".sql": - iconName = "database"; - break; - case ".lock": - iconName = "lock"; - break; - case ".txt": - iconName = "file-text"; - break; - case ".log": - iconName = "output"; - break; - default: - iconName = "file"; - break; - } - } - - return { iconClass: `codicon codicon-${iconName}` }; - } catch (error) { - return { iconClass: "codicon codicon-file" }; - } - } -} diff --git a/src/webviews/timeline/services/git-operations.service.ts b/src/webviews/timeline/services/git-operations.service.ts deleted file mode 100644 index e4e8524..0000000 --- a/src/webviews/timeline/services/git-operations.service.ts +++ /dev/null @@ -1,593 +0,0 @@ -import * as path from "path"; -import * as vscode from "vscode"; -import simpleGit from "simple-git"; -import { RepositoryManager } from "../../../core/repositories/repository-manager"; -import { getPrimaryRepository } from "../../../shared/utils/repo-selection"; -import { - ChangeEntry, - CommitEntry, - CommitDetail, - CommitDetailFile, -} from "../interfaces/timeline-view-provider.interface"; -const dayjs = require("dayjs"); -const relativeTime = require("dayjs/plugin/relativeTime"); - -dayjs.extend(relativeTime); - -export class GitOperationsService { - constructor(private readonly repositories: RepositoryManager) {} - - async getRepositoryData(): Promise<{ - changes: ChangeEntry[]; - commits: CommitEntry[]; - hasMoreCommits: boolean; - branches: string[]; - currentBranch: string | null; - branchActivity: Record; - repository: any; - remoteStatus: { - hasRemote: boolean; - isPublished: boolean; - ahead: number; - behind: number; - lastFetched: Date | null; - remoteBranch: string | null; - }; - tags: Record; - } | null> { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - return null; - } - - try { - const git = simpleGit(repository.localPath); - const [status, log, branch, remotes, tags] = await Promise.all([ - git.status(), - git.log({ maxCount: 50 }), - git.branch(), - git.getRemotes(true), - git.tags(), - ]); - - const changes: ChangeEntry[] = status.files.map((file) => ({ - path: file.path, - status: this.formatStatusCode(file.index, file.working_dir), - staged: file.index !== " " && file.index !== "?", - })); - - // Get tags for each commit - const tagMap = await this.getTagsForCommits(git); - - // Get remote tracking status to determine which commits are pushed - const remoteStatus = await this.getRemoteStatus( - git, - branch.current, - remotes, - ); - - const commits: CommitEntry[] = log.all.map((commit, index) => ({ - hash: commit.hash || "", - shortHash: (commit.hash || "").slice(0, 7), - message: commit.message || "", - author: commit.author_name || "Unknown", - email: commit.author_email || "", - authorName: commit.author_name || "", - authorEmail: commit.author_email || "", - relativeTime: this.formatRelativeTime(commit.date), - committedAt: commit.date ? String(commit.date) : "", - tags: tagMap[commit.hash || ""] || [], - // First 'ahead' commits are unpushed - isPushed: remoteStatus.isPublished - ? index >= remoteStatus.ahead - : false, - })); - - const hasMoreCommits = log.all.length === 50; - - // Local branches, plus remote branches that have no local counterpart yet - // (shown by their short name). Never expose "remotes//" refs - // directly: checking one out puts the repo in detached HEAD. - const localBranches = branch.all.filter((b) => !b.startsWith("remotes/")); - const localSet = new Set(localBranches); - const remoteOnly = new Set(); - for (const b of branch.all) { - const m = b.match(/^remotes\/[^/]+\/(.+)$/); - if (m && m[1] !== "HEAD" && !localSet.has(m[1])) remoteOnly.add(m[1]); - } - const branchList = [...localBranches, ...remoteOnly]; - - // Get branch activity dates - const branchActivity: Record = {}; - for (const branchName of branchList) { - try { - const lastCommit = await git.raw([ - "log", - "-1", - "--format=%cr", - branchName, - ]); - branchActivity[branchName] = lastCommit.trim(); - } catch (error) { - // If we can't get the date, skip it - } - } - - return { - changes, - commits, - hasMoreCommits, - branches: branchList, - currentBranch: branch.current, - branchActivity, - repository: { - name: path.basename(repository.localPath), - path: repository.localPath, - remote: repository.remoteUrl, - }, - remoteStatus, - tags: tagMap, - }; - } catch (error) { - throw new Error(`Failed to load git data: ${error}`); - } - } - - async getCommitDetail(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - throw new Error("No repository found"); - } - - try { - const git = simpleGit(repository.localPath); - const raw = await git.raw([ - "show", - hash, - "--numstat", - "--pretty=format:%H%n%an%n%ae%n%ad%n%s", - "--date=iso", - ]); - - const lines = raw.split("\n"); - const summaryHash = lines.shift() ?? hash; - const authorName = lines.shift() ?? ""; - const authorEmail = lines.shift() ?? ""; - const committedAt = lines.shift() ?? ""; - const message = lines.shift() ?? ""; - if (lines[0] === "") { - lines.shift(); - } - - const files: CommitDetail["files"] = []; - let additions = 0; - let deletions = 0; - - for (const line of lines) { - if (!line.trim()) continue; - const [addStr, delStr, filePath] = line.split("\t"); - if (!filePath) continue; - - const addVal = addStr === "-" ? null : Number.parseInt(addStr, 10); - const delVal = delStr === "-" ? null : Number.parseInt(delStr, 10); - - if (typeof addVal === "number" && !Number.isNaN(addVal)) { - additions += addVal; - } - if (typeof delVal === "number" && !Number.isNaN(delVal)) { - deletions += delVal; - } - - files.push({ - path: filePath, - status: "", - additions: addVal ?? null, - deletions: delVal ?? null, - }); - } - - // Get file status information - const statusRaw = await git.raw([ - "show", - hash, - "--name-status", - "--pretty=format:", - ]); - const statusMap = new Map(); - for (const line of statusRaw.split("\n")) { - if (!line.trim()) continue; - const [state, filePath] = line.split("\t"); - if (filePath) { - statusMap.set(filePath, state); - } - } - - // Update file status - for (const file of files) { - file.status = statusMap.get(file.path) ?? "M"; - } - - return { - summary: { - hash: summaryHash, - shortHash: summaryHash.slice(0, 7), - message, - authorName, - authorEmail, - committedAt, - relativeTime: this.formatRelativeTime(new Date(committedAt)), - additions, - deletions, - fileCount: files.length, - }, - files, - }; - } catch (error) { - throw new Error(`Failed to load commit details: ${error}`); - } - } - - async loadMoreCommits(offset: number): Promise<{ - commits: CommitEntry[]; - hasMoreCommits: boolean; - newOffset: number; - }> { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - throw new Error("No repository found"); - } - - try { - const git = simpleGit(repository.localPath); - const [logOutput, branch, remotes] = await Promise.all([ - git.raw([ - "log", - "--oneline", - "--format=%H%x09%an%x09%ae%x09%ad%x09%s", - "--date=iso", - "--max-count=50", - `--skip=${offset}`, - ]), - git.branch(), - git.getRemotes(true), - ]); - - // Get remote status to determine which commits are pushed - const remoteStatus = await this.getRemoteStatus( - git, - branch.current, - remotes, - ); - - const lines = logOutput - .trim() - .split("\n") - .filter((line) => line.trim()); - const commits: CommitEntry[] = lines.map((line, index) => { - const [hash, author, email, date, ...messageParts] = line.split("\t"); - const message = messageParts.join("\t"); - - return { - hash: hash || "", - shortHash: (hash || "").slice(0, 7), - message: message || "", - author: author || "Unknown", - email: email || "", - authorName: author || "", - authorEmail: email || "", - relativeTime: this.formatRelativeTime(this.parseGitDate(date)), - committedAt: date || "", - // Commits loaded after offset are likely pushed (since first 'ahead' commits from offset 0 are unpushed) - isPushed: remoteStatus.isPublished - ? offset + index >= remoteStatus.ahead - : false, - }; - }); - - const hasMoreCommits = commits.length === 50; - - return { - commits, - hasMoreCommits, - newOffset: offset + 50, - }; - } catch (error) { - throw new Error(`Failed to load more commits: ${error}`); - } - } - - private formatStatusCode(index: string, workingDir: string): string { - const combined = `${index ?? ""}${workingDir ?? ""}`.trim(); - return combined.length > 0 ? combined : "--"; - } - - private parseGitDate(dateInput: string | Date | any): Date { - // If it's already a Date object (from simple-git), return it - if (dateInput instanceof Date) { - return Number.isNaN(dateInput.getTime()) - ? new Date("1970-01-01") - : dateInput; - } - - // If it's not a string, convert to string - const dateString = String(dateInput || ""); - - if ( - !dateString || - dateString.trim() === "" || - dateString === "undefined" || - dateString === "null" - ) { - return new Date("1970-01-01"); // Return epoch as fallback - } - - // Try to parse the date string directly - const parsedDate = new Date(dateString); - if (!Number.isNaN(parsedDate.getTime())) { - return parsedDate; - } - - // If standard parsing fails, try to handle Git's ISO format - try { - // Git typically returns dates in ISO format like "2024-01-15 10:30:45 +0000" - const isoMatch = dateString.match( - /(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/, - ); - if (isoMatch) { - const [, datePart, timePart] = isoMatch; - return new Date(`${datePart}T${timePart}Z`); - } - - // Try Unix timestamp (if it's just numbers) - const timestamp = parseInt(dateString, 10); - if (!Number.isNaN(timestamp) && timestamp > 0) { - // If it looks like a Unix timestamp (seconds), convert to milliseconds - const date = new Date( - timestamp > 1000000000000 ? timestamp : timestamp * 1000, - ); - if (!Number.isNaN(date.getTime())) { - return date; - } - } - } catch (error) { - // Fall through to fallback - } - - // Return epoch as final fallback - return new Date("1970-01-01"); - } - - private async getTagsForCommits(git: any): Promise> { - const tagMap: Record = {}; - - try { - const tagList = await git.raw(["show-ref", "--tags"]); - const lines = tagList.split("\n").filter((line: string) => line.trim()); - - for (const line of lines) { - const [hash, ref] = line.split(" "); - if (!hash || !ref) continue; - - const tagName = ref.replace("refs/tags/", ""); - if (!tagMap[hash]) { - tagMap[hash] = []; - } - tagMap[hash].push(tagName); - } - - // Also get annotated tags pointing to commits - const annotatedTags = await git.raw([ - "for-each-ref", - "--format=%(objectname) %(refname:short) %(object)", - "refs/tags", - ]); - const annotatedLines = annotatedTags - .split("\n") - .filter((line: string) => line.trim()); - - for (const line of annotatedLines) { - const parts = line.split(" "); - if (parts.length >= 3) { - const [, tagName, commitHash] = parts; - if (commitHash && commitHash.length === 40) { - if (!tagMap[commitHash]) { - tagMap[commitHash] = []; - } - if (!tagMap[commitHash].includes(tagName)) { - tagMap[commitHash].push(tagName); - } - } - } - } - } catch (error) { - // Silently fail if tags cannot be retrieved - } - - return tagMap; - } - - private async getRemoteStatus( - git: any, - currentBranch: string | null, - remotes: any[], - ): Promise<{ - hasRemote: boolean; - isPublished: boolean; - ahead: number; - behind: number; - lastFetched: Date | null; - remoteBranch: string | null; - }> { - if (!currentBranch || remotes.length === 0) { - return { - hasRemote: false, - isPublished: false, - ahead: 0, - behind: 0, - lastFetched: null, - remoteBranch: null, - }; - } - - try { - // Ask git directly whether the current branch has an upstream. This is - // reliable regardless of `git branch -vv` formatting or brackets in commit - // subjects (the old parser could misread both ways). - let remoteBranch: string | null = null; - let ahead = 0; - let behind = 0; - - try { - remoteBranch = ( - await git.raw([ - "rev-parse", - "--abbrev-ref", - "--symbolic-full-name", - "@{upstream}", - ]) - ).trim() || null; - } catch { - remoteBranch = null; - } - - if (remoteBranch) { - try { - const counts = ( - await git.raw([ - "rev-list", - "--left-right", - "--count", - "@{upstream}...HEAD", - ]) - ) - .trim() - .split(/\s+/); - behind = parseInt(counts[0], 10) || 0; - ahead = parseInt(counts[1], 10) || 0; - } catch { - // leave ahead/behind at 0 - } - } - - // Get last fetch time from git reflog - let lastFetched: Date | null = null; - try { - // Try to get fetch time from reflog entries for origin - const fetchLog = await git.raw([ - "reflog", - "show", - "refs/remotes/origin/HEAD", - "--date=iso", - "-n", - "1", - "--format=%cd", - ]); - if (fetchLog.trim()) { - const fetchDate = new Date(fetchLog.trim()); - if (!isNaN(fetchDate.getTime())) { - lastFetched = fetchDate; - } - } - } catch { - // Fallback: Try to get from main/master branch reflog - try { - const branches = [remoteBranch, "main", "master"]; - for (const branch of branches) { - if (!branch) continue; - try { - const branchLog = await git.raw([ - "reflog", - "show", - `refs/remotes/origin/${branch.replace('origin/', '')}`, - "--date=iso", - "-n", - "1", - "--format=%cd", - ]); - if (branchLog.trim()) { - const branchDate = new Date(branchLog.trim()); - if (!isNaN(branchDate.getTime())) { - lastFetched = branchDate; - break; - } - } - } catch { - // Continue to next branch - } - } - } catch { - // Final fallback: check FETCH_HEAD file modification time - try { - const fs = require("fs"); - const fetchHeadPath = path.join(git.gitDir || ".git", "FETCH_HEAD"); - const stats = fs.statSync(fetchHeadPath); - // Only use FETCH_HEAD if it's recent (within last 30 days) - const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000); - if (stats.mtime.getTime() > thirtyDaysAgo) { - lastFetched = stats.mtime; - } - } catch { - // Ignore if FETCH_HEAD doesn't exist or is inaccessible - } - } - } - - return { - hasRemote: remotes.length > 0, - isPublished: remoteBranch !== null, - ahead, - behind, - lastFetched, - remoteBranch, - }; - } catch (error) { - return { - hasRemote: remotes.length > 0, - isPublished: false, - ahead: 0, - behind: 0, - lastFetched: null, - remoteBranch: null, - }; - } - } - - private formatRelativeTime(date: Date | string | any): string { - let actualDate: Date; - - if (date instanceof Date) { - actualDate = date; - } else { - actualDate = this.parseGitDate(date); - } - - if (Number.isNaN(actualDate.getTime())) { - return "Unknown date"; - } - - // Use simple relative time calculation instead of dayjs - const diff = Date.now() - actualDate.getTime(); - const seconds = Math.round(diff / 1000); - if (seconds < 60) { - return "just now"; - } - const minutes = Math.round(seconds / 60); - if (minutes < 60) { - return `${minutes} minute${minutes === 1 ? "" : "s"} ago`; - } - const hours = Math.round(minutes / 60); - if (hours < 24) { - return `${hours} hour${hours === 1 ? "" : "s"} ago`; - } - const days = Math.round(hours / 24); - if (days < 30) { - return `${days} day${days === 1 ? "" : "s"} ago`; - } - const months = Math.round(days / 30); - if (months < 12) { - return `${months} month${months === 1 ? "" : "s"} ago`; - } - const years = Math.round(months / 12); - return `${years} year${years === 1 ? "" : "s"} ago`; - } -} diff --git a/src/webviews/timeline/services/message-handler.service.ts b/src/webviews/timeline/services/message-handler.service.ts deleted file mode 100644 index d178b81..0000000 --- a/src/webviews/timeline/services/message-handler.service.ts +++ /dev/null @@ -1,944 +0,0 @@ -import * as path from "path"; -import * as vscode from "vscode"; -import simpleGit from "simple-git"; -import { RepositoryManager } from "../../../core/repositories/repository-manager"; -import { AccountManager } from "../../../core/accounts/account-manager"; -import { getPrimaryRepository } from "../../../shared/utils/repo-selection"; -import { CommitDetailViewProvider } from "../../commitDetail/commit-detail-view-provider"; -import { - WebviewMessage, - FileIconInfo, -} from "../interfaces/timeline-view-provider.interface"; -import { GitOperationsService } from "./git-operations.service"; - -export class MessageHandlerService { - constructor( - private readonly context: vscode.ExtensionContext, - private readonly repositories: RepositoryManager, - private readonly accounts: AccountManager, - private readonly commitDetailProvider: CommitDetailViewProvider, - private readonly gitService: GitOperationsService, - private readonly view: vscode.WebviewView, - ) {} - - async handleMessage(message: WebviewMessage): Promise { - switch (message.command) { - case "ready": - case "refresh": - await this.handleRefresh(); - break; - case "stageFiles": - await this.handleStageFiles(message.files); - break; - case "unstageFiles": - await this.handleUnstageFiles(message.files); - break; - case "commit": - await this.handleCommit(message.message); - break; - case "commitFiles": - await this.handleCommitFiles( - (message as any).message, - (message as any).files, - ); - break; - case "discardFiles": - await this.handleDiscardFiles((message as any).files); - break; - case "getWorkingDiff": - await this.handleGetWorkingDiff((message as any).filePath); - break; - case "push": - await this.handlePush(); - break; - case "pull": - await this.handlePull(); - break; - case "checkoutBranch": - await this.handleCheckoutBranch(message.branch); - break; - case "mergeBranch": - await this.handleMergeBranch(message.fromBranch, message.toBranch); - break; - case "createPullRequest": - await this.handleCreatePullRequest(message.branch); - break; - case "getCommitDetails": - await this.handleGetCommitDetails(message.hash); - break; - case "getFileDiff": - await this.handleGetFileDiff(message.hash, message.filePath); - break; - case "openCommitDetail": - if (typeof message.hash === "string") { - await this.commitDetailProvider.showCommitDetails(message.hash); - } - break; - case "resetToCommit": - if (typeof message.hash === "string") { - await this.handleResetToCommit(message.hash); - } - break; - case "checkoutCommit": - if (typeof message.hash === "string") { - await this.handleCheckoutCommit(message.hash); - } - break; - case "revertCommit": - if (typeof message.hash === "string") { - await this.handleRevertCommit(message.hash); - } - break; - case "createBranchFromCommit": - if (typeof message.hash === "string") { - await this.handleCreateBranchFromCommit(message.hash); - } - break; - case "createTagFromCommit": - if (typeof message.hash === "string") { - await this.handleCreateTagFromCommit(message.hash); - } - break; - case "cherryPickCommit": - if (typeof message.hash === "string") { - await this.handleCherryPickCommit(message.hash); - } - break; - case "viewCommitOnGitHub": - if (typeof message.hash === "string") { - await this.handleViewCommitOnGitHub(message.hash); - } - break; - case "createBranch": - if (typeof message.branchName === "string") { - await this.handleCreateBranch(message.branchName); - } - break; - case "createBranchWithChanges": - if ( - typeof message.branchName === "string" && - typeof message.bringChanges === "boolean" - ) { - await this.handleCreateBranchWithChanges( - message.branchName, - message.bringChanges, - ); - } - break; - case "loadMoreCommits": - if (typeof message.offset === "number") { - await this.handleLoadMoreCommits(message.offset); - } - break; - case "fetch": - await this.handleFetch(); - break; - case "publish": - await this.handlePublish(); - break; - case "selectCommit": - if (typeof message.hash === "string") { - await this.handleSelectCommit(message.hash); - } - break; - case "selectFile": - if ( - typeof message.hash === "string" && - typeof message.path === "string" - ) { - await this.handleSelectFile(message.hash, message.path); - } - break; - default: - break; - } - } - - private async configureGitWithAuth( - repositoryPath: string, - ): Promise { - const activeAccount = this.accounts.getActiveAccount(); - if (!activeAccount) { - return null; - } - - const token = await this.accounts.getToken(activeAccount.id); - if (!token) { - return null; - } - - const git = simpleGit(repositoryPath); - - // Configure git with authentication for GitHub operations - try { - // Get the repository info to determine the remote URL format - const remotes = await git.getRemotes(true); - const origin = remotes.find((remote) => remote.name === "origin"); - - if (origin && origin.refs.fetch) { - let remoteUrl = origin.refs.fetch; - - // If it's a GitHub HTTPS URL, update it with the token - if (remoteUrl.includes("github.com")) { - // Convert SSH to HTTPS if necessary - if (remoteUrl.startsWith("git@github.com:")) { - remoteUrl = remoteUrl.replace( - "git@github.com:", - "https://github.com/", - ); - if (!remoteUrl.endsWith(".git")) { - remoteUrl += ".git"; - } - } - - // Add authentication to HTTPS URL - if (remoteUrl.startsWith("https://github.com/")) { - // For GitHub tokens, use the token as the password with any username (or 'x-access-token') - // GitHub accepts both formats: username:token or x-access-token:token - const urlWithAuth = remoteUrl.replace( - "https://github.com/", - `https://x-access-token:${encodeURIComponent(token)}@github.com/`, - ); - - console.log( - "Configuring auth for:", - activeAccount.login, - "on repo:", - remoteUrl, - ); - - // Temporarily update the remote URL for this operation - await git.remote(["set-url", "origin", urlWithAuth]); - - // Store original URL for cleanup - const originalUrl = remoteUrl; - - // Create a wrapper that ensures URL cleanup after operations - const gitWithAuth = Object.create(git); - - // Override push method - gitWithAuth.push = async function (...args: any[]) { - try { - const result = await git.push(...args); - // Restore original URL after successful push - await git.remote(["set-url", "origin", originalUrl]); - return result; - } catch (error) { - // Restore original URL even on error - await git.remote(["set-url", "origin", originalUrl]); - throw error; - } - }; - - // Override pull method - gitWithAuth.pull = async function (...args: any[]) { - try { - const result = await git.pull(...args); - // Restore original URL after successful pull - await git.remote(["set-url", "origin", originalUrl]); - return result; - } catch (error) { - // Restore original URL even on error - await git.remote(["set-url", "origin", originalUrl]); - throw error; - } - }; - - // Override fetch method - gitWithAuth.fetch = async function (...args: any[]) { - try { - const result = await git.fetch(...args); - // Restore original URL after successful fetch - await git.remote(["set-url", "origin", originalUrl]); - return result; - } catch (error) { - // Restore original URL even on error - await git.remote(["set-url", "origin", originalUrl]); - throw error; - } - }; - - return gitWithAuth; - } - } - } - } catch (error) { - console.error("Failed to configure git with auth:", error); - } - - // Return regular git instance if no special auth needed - return git; - } - - private async handleRefresh(): Promise { - try { - const data = await this.gitService.getRepositoryData(); - if (!data) { - this.view.webview.postMessage({ - command: "updateRepository", - repository: null, - }); - return; - } - - this.view.webview.postMessage({ - command: "updateChanges", - changes: data.changes, - }); - - this.view.webview.postMessage({ - command: "updateHistory", - history: data.commits, - hasMoreCommits: data.hasMoreCommits, - offset: 0, - }); - - this.view.webview.postMessage({ - command: "updateBranches", - branches: data.branches, - currentBranch: data.currentBranch, - branchActivity: data.branchActivity, - }); - - this.view.webview.postMessage({ - command: "updateRepository", - repository: data.repository, - }); - - const active = this.accounts.getActiveAccount(); - this.view.webview.postMessage({ - command: "updateAccounts", - activeAccount: active - ? { - login: active.login, - name: active.name, - avatarUrl: active.avatarUrl, - } - : null, - }); - - this.view.webview.postMessage({ - command: "updateRemoteStatus", - remoteStatus: data.remoteStatus, - tags: data.tags, - }); - } catch (error) { - vscode.window.showErrorMessage(`Failed to refresh: ${error}`); - } - } - - private async handleStageFiles(files: string[]): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !files?.length) return; - - try { - const git = simpleGit(repository.localPath); - await git.add(files); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to stage files: ${error}`); - } - } - - private async handleUnstageFiles(files: string[]): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !files?.length) return; - - try { - const git = simpleGit(repository.localPath); - await git.reset(["HEAD", ...files]); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to unstage files: ${error}`); - } - } - - private async handleCommit(message: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !message?.trim()) return; - - try { - const git = simpleGit(repository.localPath); - await git.commit(message); - await this.handleRefresh(); - vscode.window.showInformationMessage("Commit created successfully"); - } catch (error) { - vscode.window.showErrorMessage(`Failed to commit: ${error}`); - } - } - - // Commit exactly the given paths (GitHub Desktop model: checked files are the - // commit, no separate staging step). Stages then commits with a pathspec. - private async handleCommitFiles( - message: string, - files: string[], - ): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !message?.trim() || !files?.length) return; - - try { - const git = simpleGit(repository.localPath); - await git.add(files); - await git.commit(message, files); - await this.handleRefresh(); - vscode.window.showInformationMessage("Commit created successfully"); - } catch (error) { - vscode.window.showErrorMessage(`Failed to commit: ${error}`); - } - } - - private async handleDiscardFiles(files: string[]): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !files?.length) return; - - const confirm = await vscode.window.showWarningMessage( - files.length === 1 - ? `Discard changes to ${files[0]}?` - : `Discard changes to ${files.length} files?`, - { modal: true }, - "Discard", - ); - if (confirm !== "Discard") return; - - try { - const git = simpleGit(repository.localPath); - await git.raw(["checkout", "HEAD", "--", ...files]).catch(() => undefined); - await git.raw(["clean", "-fd", "--", ...files]).catch(() => undefined); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to discard: ${error}`); - } - } - - // Diff of an uncommitted working-tree change (staged + unstaged, plus new files). - private async handleGetWorkingDiff(filePath: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - let diff = await git.raw(["diff", "HEAD", "--", filePath]); - if (!diff.trim()) { - // Untracked file: show it as an addition. - diff = await git - .raw(["diff", "--no-index", "--", "/dev/null", filePath]) - .catch(() => ""); - } - this.view.webview.postMessage({ - type: "workingDiff", - payload: { path: filePath, diff }, - }); - } catch (error) { - this.view.webview.postMessage({ - type: "error", - message: - error instanceof Error ? error.message : "Failed to load file diff.", - }); - } - } - - private async handlePush(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - vscode.window.showErrorMessage( - "No repository found in the current workspace", - ); - return; - } - - try { - // Log repository info for debugging - console.log( - "Pushing to repository:", - repository.name, - "at", - repository.localPath, - ); - - const git = await this.configureGitWithAuth(repository.localPath); - if (!git) { - vscode.window.showErrorMessage( - "No authenticated GitHub account found. Please sign in first.", - ); - return; - } - - await git.push(); - await this.handleRefresh(); // Refresh all data including remote status - vscode.window.showInformationMessage("Pushed successfully"); - } catch (error: any) { - console.error("Push failed with error:", error); - vscode.window.showErrorMessage( - `Failed to push: ${error.message || error}`, - ); - } - } - - private async handlePull(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = await this.configureGitWithAuth(repository.localPath); - if (!git) { - vscode.window.showErrorMessage( - "No authenticated GitHub account found. Please sign in first.", - ); - return; - } - - await git.pull(); - await this.handleRefresh(); - vscode.window.showInformationMessage("Pulled successfully"); - } catch (error) { - vscode.window.showErrorMessage(`Failed to pull: ${error}`); - } - } - - private async handleCheckoutBranch(branchName: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !branchName) return; - - // Guard against detached HEAD: never check out a "remotes//" - // ref directly. Fall back to the short name so git creates/switches to the - // local tracking branch instead. - const remoteMatch = branchName.match(/^remotes\/[^/]+\/(.+)$/); - const target = remoteMatch ? remoteMatch[1] : branchName; - - try { - const git = simpleGit(repository.localPath); - await git.checkout(target); - await this.handleRefresh(); - vscode.window.showInformationMessage(`Checked out branch: ${target}`); - } catch (error) { - vscode.window.showErrorMessage(`Failed to checkout branch: ${error}`); - } - } - - private async handleMergeBranch( - fromBranch: string, - toBranch: string, - ): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !fromBranch || !toBranch) return; - - try { - const git = simpleGit(repository.localPath); - await git.checkout(toBranch); - await git.merge([fromBranch]); - await this.handleRefresh(); - vscode.window.showInformationMessage( - `Successfully merged ${fromBranch} into ${toBranch}`, - ); - } catch (error) { - vscode.window.showErrorMessage(`Failed to merge branches: ${error}`); - } - } - - private async handleCreatePullRequest(branchName: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !branchName) return; - - try { - const repoUrl = repository.remoteUrl; - if (repoUrl) { - const githubUrl = repoUrl - .replace(/^git@github\.com:/, "https://github.com/") - .replace(/\.git$/, ""); - - const prUrl = `${githubUrl}/compare/${branchName}?expand=1`; - vscode.env.openExternal(vscode.Uri.parse(prUrl)); - vscode.window.showInformationMessage( - `Opening pull request creation for ${branchName}`, - ); - } else { - vscode.window.showWarningMessage("No remote repository configured"); - } - } catch (error) { - vscode.window.showErrorMessage(`Failed to create pull request: ${error}`); - } - } - - private async handleGetCommitDetails(hash: string): Promise { - try { - const detail = await this.gitService.getCommitDetail(hash); - this.view.webview.postMessage({ - type: "commitDetail", - payload: detail, - }); - } catch (error) { - this.view.webview.postMessage({ - type: "error", - message: - error instanceof Error - ? error.message - : "Failed to load commit details.", - }); - } - } - - private async handleGetFileDiff( - hash: string, - filePath: string, - ): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - let diff = await git.raw(["show", hash, "--patch", "--", filePath]); - if (!diff.trim()) { - diff = await git.raw(["show", hash, "--", filePath]); - } - - this.view.webview.postMessage({ - type: "fileDiff", - payload: { - path: filePath, - diff, - }, - }); - } catch (error) { - this.view.webview.postMessage({ - type: "error", - message: - error instanceof Error ? error.message : "Failed to load file diff.", - }); - } - } - - private async handleResetToCommit(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - const confirm = await vscode.window.showWarningMessage( - `Are you sure you want to reset to commit ${hash.substring(0, 7)}? This will discard all changes after this commit.`, - { modal: true }, - "Reset", - "Cancel", - ); - - if (confirm === "Reset") { - try { - const git = simpleGit(repository.localPath); - await git.reset(["--hard", hash]); - vscode.window.showInformationMessage( - `Reset to commit ${hash.substring(0, 7)}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to reset: ${error}`); - } - } - } - - private async handleCheckoutCommit(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - await git.checkout(hash); - vscode.window.showInformationMessage( - `Checked out commit ${hash.substring(0, 7)}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to checkout commit: ${error}`); - } - } - - private async handleRevertCommit(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - await git.revert(hash, ["--no-edit"]); - vscode.window.showInformationMessage( - `Reverted commit ${hash.substring(0, 7)}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to revert commit: ${error}`); - } - } - - private async handleCreateBranchFromCommit(hash: string): Promise { - const branchName = await vscode.window.showInputBox({ - prompt: "Enter new branch name", - placeHolder: "feature-branch-name", - ignoreFocusOut: true, - }); - - if (!branchName) return; - - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - await git.checkoutBranch(branchName, hash); - vscode.window.showInformationMessage( - `Created branch '${branchName}' from commit ${hash.substring(0, 7)}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to create branch: ${error}`); - } - } - - private async handleCreateTagFromCommit(hash: string): Promise { - const tagName = await vscode.window.showInputBox({ - prompt: "Enter tag name (e.g., v1.0.0)", - placeHolder: "v1.0.0", - ignoreFocusOut: true, - validateInput: (value) => { - if (!value) return "Tag name is required"; - if (!/^[a-zA-Z0-9._-]+$/.test(value)) - return "Tag name contains invalid characters"; - return null; - }, - }); - - if (!tagName) return; - - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = await this.configureGitWithAuth(repository.localPath); - if (!git) { - vscode.window.showErrorMessage( - "No authenticated GitHub account found. Please sign in first.", - ); - return; - } - - // Create annotated tag with default message - await git.raw(["tag", "-a", tagName, hash, "-m", `Release ${tagName}`]); - - // Push the tag to remote - await git.pushTags("origin"); - - vscode.window.showInformationMessage( - `Created and pushed tag '${tagName}' to GitHub`, - ); - } catch (error) { - vscode.window.showErrorMessage(`Failed to create tag: ${error}`); - } - } - - private async handleCherryPickCommit(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - await git.raw(["cherry-pick", hash]); - vscode.window.showInformationMessage( - `Cherry-picked commit ${hash.substring(0, 7)}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to cherry-pick commit: ${error}`); - } - } - - private async handleViewCommitOnGitHub(hash: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository?.remoteUrl) { - vscode.window.showWarningMessage("No GitHub remote found"); - return; - } - - try { - const githubUrl = repository.remoteUrl - .replace(/^git@github\.com:/, "https://github.com/") - .replace(/\.git$/, ""); - - const commitUrl = `${githubUrl}/commit/${hash}`; - vscode.env.openExternal(vscode.Uri.parse(commitUrl)); - } catch (error) { - vscode.window.showErrorMessage(`Failed to open GitHub: ${error}`); - } - } - - private async handleCreateBranch(branchName: string): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !branchName) return; - - try { - const git = simpleGit(repository.localPath); - await git.checkoutLocalBranch(branchName); - vscode.window.showInformationMessage( - `Created and switched to branch '${branchName}'`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to create branch: ${error}`); - } - } - - private async handleCreateBranchWithChanges( - branchName: string, - bringChanges: boolean, - ): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository || !branchName) return; - - try { - const git = simpleGit(repository.localPath); - - if (bringChanges) { - await git.checkoutLocalBranch(branchName); - vscode.window.showInformationMessage( - `Created branch '${branchName}' with uncommitted changes`, - ); - } else { - await git.stash(["push", "-m", "Auto-stash before branch creation"]); - await git.checkoutLocalBranch(branchName); - vscode.window.showInformationMessage( - `Created branch '${branchName}' and stashed changes`, - ); - } - - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage( - `Failed to create branch with changes: ${error}`, - ); - } - } - - private async handleLoadMoreCommits(offset: number): Promise { - try { - const result = await this.gitService.loadMoreCommits(offset); - this.view.webview.postMessage({ - command: "loadMoreCommitsResponse", - history: result.commits, - hasMoreCommits: result.hasMoreCommits, - offset: result.newOffset, - }); - } catch (error) { - vscode.window.showErrorMessage(`Failed to load more commits: ${error}`); - } - } - - private async handleSelectCommit(hash: string): Promise { - try { - const detail = await this.gitService.getCommitDetail(hash); - this.view.webview.postMessage({ - type: "commitDetail", - payload: detail, - }); - } catch (error) { - this.view.webview.postMessage({ - type: "error", - message: - error instanceof Error - ? error.message - : "Failed to load commit details.", - }); - } - } - - private async handleSelectFile( - hash: string, - filePath: string, - ): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - let diff = await git.raw(["show", hash, "--patch", "--", filePath]); - if (!diff.trim()) { - diff = await git.raw(["show", hash, "--", filePath]); - } - - this.view.webview.postMessage({ - type: "fileDiff", - payload: { - path: filePath, - diff, - }, - }); - } catch (error) { - this.view.webview.postMessage({ - type: "error", - message: - error instanceof Error ? error.message : "Failed to load file diff.", - }); - } - } - - private async handleFetch(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) { - vscode.window.showErrorMessage( - "No repository found in the current workspace", - ); - return; - } - - try { - // Log repository info for debugging - console.log( - "Fetching from repository:", - repository.name, - "at", - repository.localPath, - ); - - const git = await this.configureGitWithAuth(repository.localPath); - if (!git) { - vscode.window.showErrorMessage( - "No authenticated GitHub account found. Please sign in first.", - ); - return; - } - - await git.fetch(); - vscode.window.showInformationMessage("Fetched from remote successfully"); - await this.handleRefresh(); - } catch (error: any) { - console.error("Fetch failed with error:", error); - vscode.window.showErrorMessage( - `Failed to fetch: ${error.message || error}`, - ); - } - } - - private async handlePublish(): Promise { - const repository = getPrimaryRepository(this.repositories); - if (!repository) return; - - try { - const git = simpleGit(repository.localPath); - const branch = await git.branch(); - const currentBranch = branch.current; - - if (!currentBranch) { - vscode.window.showErrorMessage("No branch selected"); - return; - } - - // Check if we have a remote - const remotes = await git.getRemotes(true); - if (remotes.length === 0) { - vscode.window.showErrorMessage("No remote repository configured"); - return; - } - - const defaultRemote = remotes[0].name; - await git.push(["-u", defaultRemote, currentBranch]); - vscode.window.showInformationMessage( - `Published branch '${currentBranch}' to ${defaultRemote}`, - ); - await this.handleRefresh(); - } catch (error) { - vscode.window.showErrorMessage(`Failed to publish branch: ${error}`); - } - } -} diff --git a/src/webviews/timeline/services/pull-request.service.ts b/src/webviews/timeline/services/pull-request.service.ts new file mode 100644 index 0000000..3ac6dda --- /dev/null +++ b/src/webviews/timeline/services/pull-request.service.ts @@ -0,0 +1,29 @@ +import { toGitHubWebUrl } from "../../../shared/utils/github-url"; +import { Browser, Notifier, RepositoryContext } from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +/** + * @description Opens GitHub's "create pull request" page for a branch. The + * extension does not create PRs through the API — it hands off to the browser, + * matching GitHub Desktop. + */ +export class PullRequestService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly browser: Browser, + ) { + super(repos, notifier); + } + + openCompare(branch: string): Promise { + return this.withRepo(async (repo) => { + const base = toGitHubWebUrl(repo.remoteUrl); + if (!base) { + this.notifier.warn("No GitHub remote configured"); + return; + } + this.browser.open(`${base}/compare/${branch}?expand=1`); + }); + } +} diff --git a/src/webviews/timeline/services/repository-data.service.ts b/src/webviews/timeline/services/repository-data.service.ts new file mode 100644 index 0000000..a3e800c --- /dev/null +++ b/src/webviews/timeline/services/repository-data.service.ts @@ -0,0 +1,435 @@ +import * as path from "path"; +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { RepositoryContext } from "../ports"; +import { + ChangeEntry, + CommitEntry, + CommitDetail, +} from "../interfaces/timeline-view-provider.interface"; +import { + COMMITS_PAGE_SIZE, + RemoteStatus, + RepositorySnapshot, +} from "../interfaces/repository-snapshot"; + +/** + * @description Read model for the timeline. Given the active repository it + * assembles a {@link RepositorySnapshot} (changes, history, branches, remote + * status, tags) in one pass, and answers per-commit detail lookups. + * + * Purely read-only: it never mutates the repository. + */ +export class RepositoryDataService { + constructor( + private readonly repos: RepositoryContext, + private readonly git: GitClientFactory, + ) {} + + /** + * Builds a full snapshot for the current repository. + * + * @returns The snapshot, or `null` when no repository is open + */ + async getSnapshot(): Promise { + const repo = this.repos.getPrimary(); + if (!repo) { + return null; + } + + const git = this.git.plain(repo.localPath); + const [status, log, branch, remotes] = await Promise.all([ + git.status(), + git.log({ maxCount: COMMITS_PAGE_SIZE }), + git.branch(), + git.getRemotes(true), + ]); + + const tags = await this.getTagsForCommits(git); + const remoteStatus = await this.getRemoteStatus( + git, + branch.current, + remotes, + ); + + const changes: ChangeEntry[] = status.files.map((file) => ({ + path: file.path, + status: `${file.index ?? ""}${file.working_dir ?? ""}`.trim() || "--", + staged: file.index !== " " && file.index !== "?", + })); + + const commits: CommitEntry[] = log.all.map((commit, index) => + this.toCommitEntry(commit, index, remoteStatus), + ); + + const branchList = this.listBranches(branch.all); + + return { + changes, + commits, + hasMoreCommits: log.all.length === COMMITS_PAGE_SIZE, + branches: branchList, + currentBranch: branch.current || null, + branchActivity: await this.getBranchActivity(git), + repository: { + name: path.basename(repo.localPath), + path: repo.localPath, + remote: repo.remoteUrl, + }, + remoteStatus, + tags, + }; + } + + /** + * Loads a further page of history. + * + * @param offset - Number of commits already shown + * @param remoteStatus - Reused from the last snapshot so ahead/behind need + * not be recomputed + */ + async loadMoreCommits( + offset: number, + remoteStatus: RemoteStatus, + ): Promise<{ + commits: CommitEntry[]; + hasMoreCommits: boolean; + offset: number; + }> { + const repo = this.repos.getPrimary(); + if (!repo) { + return { commits: [], hasMoreCommits: false, offset }; + } + + const raw = await this.git + .plain(repo.localPath) + .raw([ + "log", + "--format=%H%x09%an%x09%ae%x09%ad%x09%s", + "--date=iso", + `--max-count=${COMMITS_PAGE_SIZE}`, + `--skip=${offset}`, + ]); + + const commits = raw + .trim() + .split("\n") + .filter((line) => line.trim()) + .map((line, index) => { + const [hash, author, email, date, ...rest] = line.split("\t"); + return this.toCommitEntry( + { + hash, + author_name: author, + author_email: email, + date, + message: rest.join("\t"), + }, + offset + index, + remoteStatus, + ); + }); + + return { + commits, + hasMoreCommits: commits.length === COMMITS_PAGE_SIZE, + offset: offset + COMMITS_PAGE_SIZE, + }; + } + + /** + * Aggregate detail (stats + per-file status) for a single commit. + * + * @throws When the commit cannot be read + */ + async getCommitDetail(hash: string): Promise { + const repo = this.repos.getPrimary(); + if (!repo) { + throw new Error("No repository open"); + } + const git = this.git.plain(repo.localPath); + + const raw = await git.raw([ + "show", + hash, + "--numstat", + "--pretty=format:%H%n%an%n%ae%n%ad%n%s", + "--date=iso", + ]); + const lines = raw.split("\n"); + const summaryHash = lines.shift() ?? hash; + const authorName = lines.shift() ?? ""; + const authorEmail = lines.shift() ?? ""; + const committedAt = lines.shift() ?? ""; + const message = lines.shift() ?? ""; + if (lines[0] === "") { + lines.shift(); + } + + const files: CommitDetail["files"] = []; + let additions = 0; + let deletions = 0; + for (const line of lines) { + if (!line.trim()) { + continue; + } + const [addStr, delStr, filePath] = line.split("\t"); + if (!filePath) { + continue; + } + const add = addStr === "-" ? null : Number.parseInt(addStr, 10); + const del = delStr === "-" ? null : Number.parseInt(delStr, 10); + if (typeof add === "number" && !Number.isNaN(add)) { + additions += add; + } + if (typeof del === "number" && !Number.isNaN(del)) { + deletions += del; + } + files.push({ + path: filePath, + status: "M", + additions: add, + deletions: del, + }); + } + + const statusRaw = await git.raw([ + "show", + hash, + "--name-status", + "--pretty=format:", + ]); + const statusMap = new Map(); + for (const line of statusRaw.split("\n")) { + const [state, filePath] = line.split("\t"); + if (filePath) { + statusMap.set(filePath, state); + } + } + for (const file of files) { + file.status = statusMap.get(file.path) ?? "M"; + } + + return { + summary: { + hash: summaryHash, + shortHash: summaryHash.slice(0, 7), + message, + authorName, + authorEmail, + relativeTime: formatRelativeTime(committedAt), + committedAt, + additions, + deletions, + fileCount: files.length, + }, + files, + }; + } + + /* ---------------- internals ---------------- */ + + private toCommitEntry( + commit: { + hash?: string; + author_name?: string; + author_email?: string; + date?: string; + message?: string; + }, + index: number, + remoteStatus: RemoteStatus, + ): CommitEntry { + const hash = commit.hash ?? ""; + return { + hash, + shortHash: hash.slice(0, 7), + message: commit.message ?? "", + authorName: commit.author_name ?? "", + authorEmail: commit.author_email ?? "", + relativeTime: formatRelativeTime(commit.date ?? ""), + committedAt: commit.date ?? "", + isPushed: remoteStatus.isPublished ? index >= remoteStatus.ahead : false, + }; + } + + private listBranches(all: string[]): string[] { + // Local branches, plus remote-only branches by their short name. Never + // expose `remotes//` refs (checking one out detaches HEAD). + const local = all.filter((b) => !b.startsWith("remotes/")); + const localSet = new Set(local); + const remoteOnly = new Set(); + for (const b of all) { + const m = b.match(/^remotes\/[^/]+\/(.+)$/); + if (m && m[1] !== "HEAD" && !localSet.has(m[1])) { + remoteOnly.add(m[1]); + } + } + return [...local, ...remoteOnly]; + } + + /** One `for-each-ref` call instead of one `git log` per branch. */ + private async getBranchActivity( + git: ReturnType, + ): Promise> { + const activity: Record = {}; + try { + const raw = await git.raw([ + "for-each-ref", + "--format=%(refname:short)%09%(committerdate:relative)", + "refs/heads", + ]); + for (const line of raw.split("\n")) { + const [name, when] = line.split("\t"); + if (name && when) { + activity[name] = when; + } + } + } catch { + // Non-fatal: the dropdown just omits the "updated N ago" hint. + } + return activity; + } + + private async getTagsForCommits( + git: ReturnType, + ): Promise> { + const map: Record = {}; + const add = (hash: string, tag: string) => { + (map[hash] ??= []).includes(tag) || map[hash].push(tag); + }; + try { + const raw = await git.raw([ + "for-each-ref", + "--format=%(objectname)%09%(refname:short)%09%(*objectname)", + "refs/tags", + ]); + for (const line of raw.split("\n")) { + if (!line.trim()) { + continue; + } + const [objectName, tagName, peeled] = line.split("\t"); + const commitHash = peeled || objectName; + if (commitHash && tagName) { + add(commitHash, tagName); + } + } + } catch { + // Non-fatal. + } + return map; + } + + private async getRemoteStatus( + git: ReturnType, + currentBranch: string | undefined, + remotes: Array<{ name: string }>, + ): Promise { + const empty: RemoteStatus = { + hasRemote: remotes.length > 0, + isPublished: false, + ahead: 0, + behind: 0, + lastFetched: null, + remoteBranch: null, + }; + if (!currentBranch || remotes.length === 0) { + return { ...empty, hasRemote: false }; + } + + let remoteBranch: string | null = null; + try { + remoteBranch = + ( + await git.raw([ + "rev-parse", + "--abbrev-ref", + "--symbolic-full-name", + "@{upstream}", + ]) + ).trim() || null; + } catch { + remoteBranch = null; + } + if (!remoteBranch) { + return empty; + } + + let ahead = 0; + let behind = 0; + try { + const [b, a] = ( + await git.raw([ + "rev-list", + "--left-right", + "--count", + "@{upstream}...HEAD", + ]) + ) + .trim() + .split(/\s+/); + behind = Number.parseInt(b, 10) || 0; + ahead = Number.parseInt(a, 10) || 0; + } catch { + // leave at 0 + } + + let lastFetched: Date | null = null; + try { + const raw = await git.raw([ + "reflog", + "show", + "--date=iso", + "-n", + "1", + "--format=%cd", + `refs/remotes/${remoteBranch}`, + ]); + const date = new Date(raw.trim()); + if (!Number.isNaN(date.getTime())) { + lastFetched = date; + } + } catch { + // unknown + } + + return { + hasRemote: true, + isPublished: true, + ahead, + behind, + lastFetched, + remoteBranch, + }; + } +} + +/** + * @description Formats a git date as a coarse "N minutes ago" string. + * + * @param input - Any date string git may emit (ISO, RFC 2822, epoch...) + * @returns A relative phrase, or "unknown" when unparseable + */ +export function formatRelativeTime(input: string): string { + const date = new Date(input); + if (Number.isNaN(date.getTime())) { + return "unknown"; + } + const seconds = Math.round((Date.now() - date.getTime()) / 1000); + const units: Array<[number, string]> = [ + [60, "second"], + [60, "minute"], + [24, "hour"], + [30, "day"], + [12, "month"], + [Number.POSITIVE_INFINITY, "year"], + ]; + let value = Math.max(seconds, 0); + for (const [size, name] of units) { + if (value < size) { + const rounded = Math.round(value); + return `${rounded} ${name}${rounded === 1 ? "" : "s"} ago`; + } + value /= size; + } + return "just now"; +} diff --git a/src/webviews/timeline/services/repository-scoped.service.ts b/src/webviews/timeline/services/repository-scoped.service.ts new file mode 100644 index 0000000..3bcf7a4 --- /dev/null +++ b/src/webviews/timeline/services/repository-scoped.service.ts @@ -0,0 +1,29 @@ +import { TrackedRepository } from "../../../shared/types"; +import { Notifier, RepositoryContext } from "../ports"; + +/** + * @description Base for services that operate on the current repository. + * Centralises the "no repository open" guard so each concrete method stays a + * single responsibility. + */ +export abstract class RepositoryScopedService { + protected constructor( + protected readonly repos: RepositoryContext, + protected readonly notifier: Notifier, + ) {} + + /** + * Runs `fn` with the active repository, or notifies the user and resolves to + * `undefined` when there is none. + */ + protected async withRepo( + fn: (repo: TrackedRepository) => Promise, + ): Promise { + const repo = this.repos.getPrimary(); + if (!repo) { + this.notifier.error("No repository found in the current workspace"); + return undefined; + } + return fn(repo); + } +} diff --git a/src/webviews/timeline/services/sync.service.ts b/src/webviews/timeline/services/sync.service.ts new file mode 100644 index 0000000..ec7cc78 --- /dev/null +++ b/src/webviews/timeline/services/sync.service.ts @@ -0,0 +1,106 @@ +import { SimpleGit } from "simple-git"; +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { + Notifier, + RepositoryContext, + Refresher, + WebviewChannel, +} from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +/** + * @description Owns the four remote-sync operations behind the toolbar's sync + * cell: fetch, pull, push and publish. Authentication is delegated to + * {@link GitClientFactory.withAuth} (no token ever touches `.git/config`). + */ +export class SyncService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly channel: WebviewChannel, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + fetch(): Promise { + return this.withRepo(async (repo) => { + await this.git.withAuth(repo.localPath, (g) => g.fetch()); + this.notifier.info("Fetched from remote"); + await this.refresher.refresh(); + }); + } + + pull(): Promise { + return this.withRepo(async (repo) => { + try { + await this.git.withAuth(repo.localPath, (g) => g.pull()); + } catch (error) { + if (await this.reportIfConflict(repo.localPath, "pull")) { + return; + } + throw error; + } + this.notifier.info("Pulled from remote"); + await this.refresher.refresh(); + }); + } + + push(): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + const { current, tracking } = await git.status(); + await this.git.withAuth(repo.localPath, (g) => + tracking ? g.push() : g.push(["-u", "origin", current ?? "HEAD"]), + ); + this.notifier.info("Pushed to remote"); + await this.refresher.refresh(); + }); + } + + publish(): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + const { current } = await git.branch(); + if (!current) { + this.notifier.error("No branch to publish"); + return; + } + const remotes = await git.getRemotes(); + if (remotes.length === 0) { + this.notifier.error("No remote repository configured"); + return; + } + const remote = remotes[0].name; + await this.git.withAuth(repo.localPath, (g) => + g.push(["-u", remote, current]), + ); + this.notifier.info(`Published '${current}' to ${remote}`); + await this.refresher.refresh(); + }); + } + + /** + * If the working tree is now in a conflicted state, tells the webview and + * returns `true`. Otherwise returns `false` and the caller rethrows. + */ + private async reportIfConflict( + repoPath: string, + operation: string, + ): Promise { + const status = await this.git.plain(repoPath).status(); + if (status.conflicted.length === 0) { + return false; + } + this.channel.post({ + command: "mergeConflict", + operation, + files: status.conflicted, + }); + this.notifier.warn( + `${operation} stopped: ${status.conflicted.length} file(s) in conflict`, + ); + return true; + } +} diff --git a/src/webviews/timeline/services/working-tree.service.ts b/src/webviews/timeline/services/working-tree.service.ts new file mode 100644 index 0000000..ecd5fd3 --- /dev/null +++ b/src/webviews/timeline/services/working-tree.service.ts @@ -0,0 +1,113 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { + Notifier, + RepositoryContext, + Refresher, + WebviewChannel, +} from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +/** + * @description Working-tree operations of the Changes tab: staging, committing + * a chosen set of files (the GitHub Desktop model), discarding, and producing + * the diff of an uncommitted file. + */ +export class WorkingTreeService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly channel: WebviewChannel, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + stage(files: string[]): Promise { + return this.mutate(files, (g) => g.add(files)); + } + + unstage(files: string[]): Promise { + return this.mutate(files, (g) => g.reset(["HEAD", "--", ...files])); + } + + /** Commits exactly `files` (stages them first, then commits with a pathspec). */ + commitFiles(message: string, files: string[]): Promise { + return this.withRepo(async (repo) => { + if (!message.trim() || files.length === 0) { + return; + } + const git = this.git.plain(repo.localPath); + await git.add(files); + await git.commit(message, files); + this.channel.post({ command: "commitSucceeded" }); + this.notifier.info("Commit created"); + await this.refresher.refresh(); + }); + } + + /** Commits whatever is already staged. */ + commit(message: string): Promise { + return this.withRepo(async (repo) => { + if (!message.trim()) { + return; + } + await this.git.plain(repo.localPath).commit(message); + this.channel.post({ command: "commitSucceeded" }); + this.notifier.info("Commit created"); + await this.refresher.refresh(); + }); + } + + discard(files: string[]): Promise { + return this.withRepo(async (repo) => { + if (files.length === 0) { + return; + } + const confirmed = await this.notifier.confirm( + files.length === 1 + ? `Discard changes to ${files[0]}?` + : `Discard changes to ${files.length} files?`, + "Discard", + ); + if (!confirmed) { + return; + } + const git = this.git.plain(repo.localPath); + await git + .raw(["checkout", "HEAD", "--", ...files]) + .catch(() => undefined); + await git.raw(["clean", "-fd", "--", ...files]).catch(() => undefined); + await this.refresher.refresh(); + }); + } + + workingDiff(filePath: string): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + let diff = await git.raw(["diff", "HEAD", "--", filePath]); + if (!diff.trim()) { + diff = await git + .raw(["diff", "--no-index", "--", "/dev/null", filePath]) + .catch(() => ""); + } + this.channel.post({ + command: "workingDiff", + payload: { path: filePath, diff }, + }); + }); + } + + private mutate( + files: string[], + op: (git: ReturnType) => Promise, + ): Promise { + return this.withRepo(async (repo) => { + if (files.length === 0) { + return; + } + await op(this.git.plain(repo.localPath)); + await this.refresher.refresh(); + }); + } +} diff --git a/src/webviews/timeline/timeline-controller.ts b/src/webviews/timeline/timeline-controller.ts new file mode 100644 index 0000000..1fd15bf --- /dev/null +++ b/src/webviews/timeline/timeline-controller.ts @@ -0,0 +1,193 @@ +import { AccountManager } from "../../core/accounts/account-manager"; +import { GitClientFactory } from "../../core/git/git-authenticator"; +import { CommitDetailViewProvider } from "../commitDetail/commit-detail-view-provider"; +import { InboundMessage } from "./messages"; +import { RemoteStatus } from "./interfaces/repository-snapshot"; +import { MessageRouter } from "./message-router"; +import { + Browser, + Notifier, + RepositoryContext, + Refresher, + WebviewChannel, +} from "./ports"; +import { BranchService } from "./services/branch.service"; +import { CommitActionsService } from "./services/commit-actions.service"; +import { DiffService } from "./services/diff.service"; +import { PullRequestService } from "./services/pull-request.service"; +import { RepositoryDataService } from "./services/repository-data.service"; +import { SyncService } from "./services/sync.service"; +import { WorkingTreeService } from "./services/working-tree.service"; + +/** + * @description Composition root for the timeline webview's message handling. + * + * It owns nothing git-specific itself: it wires the cohesive services to the + * {@link MessageRouter}, implements {@link Refresher} (the single place that + * recomputes and broadcasts timeline state), and validates raw webview + * payloads into a typed {@link InboundMessage} before dispatch. + */ +export class TimelineController implements Refresher { + private readonly router: MessageRouter; + private readonly data: RepositoryDataService; + private lastRemoteStatus: RemoteStatus = { + hasRemote: false, + isPublished: false, + ahead: 0, + behind: 0, + lastFetched: null, + remoteBranch: null, + }; + + constructor( + private readonly deps: { + repos: RepositoryContext; + notifier: Notifier; + channel: WebviewChannel; + browser: Browser; + git: GitClientFactory; + accounts: AccountManager; + commitDetail: CommitDetailViewProvider; + }, + ) { + const { repos, notifier, channel, browser, git } = deps; + + this.data = new RepositoryDataService(repos, git); + + const working = new WorkingTreeService(repos, notifier, git, channel, this); + const sync = new SyncService(repos, notifier, git, channel, this); + const branch = new BranchService(repos, notifier, git, channel, this); + const commitActions = new CommitActionsService( + repos, + notifier, + git, + browser, + this, + ); + const pr = new PullRequestService(repos, notifier, browser); + const diff = new DiffService(repos, notifier, git, this.data, channel); + + this.router = new MessageRouter(notifier) + .on("ready", () => this.refresh()) + .on("refresh", () => this.refresh()) + .on("stageFiles", (m) => working.stage(m.files)) + .on("unstageFiles", (m) => working.unstage(m.files)) + .on("commit", (m) => working.commit(m.message)) + .on("commitFiles", (m) => working.commitFiles(m.message, m.files)) + .on("discardFiles", (m) => working.discard(m.files)) + .on("getWorkingDiff", (m) => working.workingDiff(m.filePath)) + .on("fetch", () => sync.fetch()) + .on("pull", () => sync.pull()) + .on("push", () => sync.push()) + .on("publish", () => sync.publish()) + .on("checkoutBranch", (m) => branch.checkout(m.branch)) + .on("createBranch", (m) => branch.create(m.branchName)) + .on("createBranchWithChanges", (m) => + branch.createWithChanges(m.branchName, m.bringChanges), + ) + .on("mergeBranch", (m) => branch.merge(m.fromBranch, m.toBranch)) + .on("createPullRequest", (m) => pr.openCompare(m.branch)) + .on("loadMoreCommits", (m) => this.loadMore(m.offset)) + .on("openCommitDetail", (m) => + deps.commitDetail.showCommitDetails(m.hash), + ) + .on("getCommitDetails", (m) => diff.commitDetail(m.hash)) + .on("selectCommit", (m) => diff.commitDetail(m.hash)) + .on("getFileDiff", (m) => diff.fileDiff(m.hash, m.filePath)) + .on("selectFile", (m) => diff.fileDiff(m.hash, m.path)) + .on("resetToCommit", (m) => commitActions.reset(m.hash)) + .on("checkoutCommit", (m) => commitActions.checkout(m.hash)) + .on("revertCommit", (m) => commitActions.revert(m.hash)) + .on("cherryPickCommit", (m) => commitActions.cherryPick(m.hash)) + .on("createBranchFromCommit", (m) => commitActions.branchFrom(m.hash)) + .on("createTagFromCommit", (m) => commitActions.tagFrom(m.hash)) + .on("viewCommitOnGitHub", (m) => commitActions.viewOnGitHub(m.hash)); + } + + /** + * Validates a raw `postMessage` payload and dispatches it. Malformed + * payloads are dropped. + */ + handle(raw: unknown): Promise { + const message = coerceInbound(raw); + if (!message) { + return Promise.resolve(); + } + return this.router.dispatch(message); + } + + /** Recomputes the whole snapshot and broadcasts it to the webview. */ + async refresh(): Promise { + const { channel, accounts } = this.deps; + const snapshot = await this.data.getSnapshot(); + + if (!snapshot) { + channel.post({ command: "updateRepository", repository: null }); + return; + } + + this.lastRemoteStatus = snapshot.remoteStatus; + + channel.post({ command: "updateChanges", changes: snapshot.changes }); + channel.post({ + command: "updateHistory", + history: snapshot.commits, + hasMoreCommits: snapshot.hasMoreCommits, + offset: 0, + }); + channel.post({ + command: "updateBranches", + branches: snapshot.branches, + currentBranch: snapshot.currentBranch, + branchActivity: snapshot.branchActivity, + }); + channel.post({ + command: "updateRepository", + repository: snapshot.repository, + }); + + const active = accounts.getActiveAccount(); + channel.post({ + command: "updateAccounts", + activeAccount: active + ? { + login: active.login, + name: active.name, + avatarUrl: active.avatarUrl, + } + : null, + }); + + channel.post({ + command: "updateRemoteStatus", + remoteStatus: snapshot.remoteStatus, + tags: snapshot.tags, + }); + } + + private async loadMore(offset: number): Promise { + const page = await this.data.loadMoreCommits(offset, this.lastRemoteStatus); + this.deps.channel.post({ + command: "loadMoreCommitsResponse", + history: page.commits, + hasMoreCommits: page.hasMoreCommits, + offset: page.offset, + }); + } +} + +/** + * @description Narrows an untrusted `postMessage` value to an + * {@link InboundMessage}. Only the `command` discriminant is checked here; each + * service still guards its own inputs. + */ +function coerceInbound(raw: unknown): InboundMessage | null { + if ( + typeof raw === "object" && + raw !== null && + typeof (raw as { command?: unknown }).command === "string" + ) { + return raw as InboundMessage; + } + return null; +} diff --git a/src/webviews/timeline/timeline-view-provider.ts b/src/webviews/timeline/timeline-view-provider.ts index 19d3266..a53d0ac 100644 --- a/src/webviews/timeline/timeline-view-provider.ts +++ b/src/webviews/timeline/timeline-view-provider.ts @@ -1,22 +1,29 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../../core/repositories/repository-manager"; import { AccountManager } from "../../core/accounts/account-manager"; +import { AccountGitClientFactory } from "../../core/git/git-authenticator"; import { CommitDetailViewProvider } from "../commitDetail/commit-detail-view-provider"; +import { ITimelineViewProvider } from "./interfaces/timeline-view-provider.interface"; import { - ITimelineViewProvider, - WebviewMessage, -} from "./interfaces/timeline-view-provider.interface"; -import { GitOperationsService } from "./services/git-operations.service"; + VsCodeBrowser, + VsCodeNotifier, + VsCodeWebviewChannel, + WorkspaceRepositoryContext, +} from "./adapters/vscode-adapters"; +import { TimelineController } from "./timeline-controller"; import { WebviewHtmlService } from "./services/webview-html.service"; -import { MessageHandlerService } from "./services/message-handler.service"; -import { FileIconService } from "./services/file-icon.service"; +const REFRESH_DEBOUNCE_MS = 400; + +/** + * @description Binds the timeline webview to a {@link TimelineController}. + * Owns only VS Code lifecycle concerns: HTML, message plumbing, visibility and + * a debounced refresh so a burst of file saves triggers one recompute. + */ export class TimelineViewProvider implements ITimelineViewProvider { - private view: vscode.WebviewView | undefined; - private gitService: GitOperationsService; - private htmlService: WebviewHtmlService; - private messageHandler: MessageHandlerService | undefined; - private fileIconService: FileIconService; + private controller: TimelineController | undefined; + private readonly htmlService: WebviewHtmlService; + private refreshTimer: ReturnType | undefined; constructor( private readonly context: vscode.ExtensionContext, @@ -24,76 +31,52 @@ export class TimelineViewProvider implements ITimelineViewProvider { private readonly accounts: AccountManager, private readonly commitDetailProvider: CommitDetailViewProvider, ) { - this.gitService = new GitOperationsService(repositories); this.htmlService = new WebviewHtmlService(context, repositories); - this.fileIconService = new FileIconService(); - } - - resolveWebviewView(webviewView: vscode.WebviewView): void | Thenable { - this.view = webviewView; - this.setupWebview(webviewView); - this.setupMessageHandler(); - this.setupEventHandlers(webviewView); - return this.refreshData(); } - async refresh(): Promise { - if (this.view && this.messageHandler) { - await this.messageHandler.handleMessage({ command: "refresh" }); - } - } - - private setupWebview(webviewView: vscode.WebviewView): void { + resolveWebviewView(webviewView: vscode.WebviewView): void { webviewView.webview.options = { enableScripts: true, localResourceRoots: [ - vscode.Uri.joinPath(this.context.extensionUri, "out", "webview"), - vscode.Uri.joinPath( - this.context.extensionUri, - "out", - "webview", - "assets", - ), vscode.Uri.joinPath(this.context.extensionUri, "out"), ], }; webviewView.webview.html = this.htmlService.generateHtml( webviewView.webview, ); - } - private setupMessageHandler(): void { - if (!this.view) return; + this.controller = new TimelineController({ + repos: new WorkspaceRepositoryContext(this.repositories), + notifier: new VsCodeNotifier(), + channel: new VsCodeWebviewChannel(webviewView.webview), + browser: new VsCodeBrowser(), + git: new AccountGitClientFactory(this.accounts), + accounts: this.accounts, + commitDetail: this.commitDetailProvider, + }); - this.messageHandler = new MessageHandlerService( - this.context, - this.repositories, - this.accounts, - this.commitDetailProvider, - this.gitService, - this.view, + webviewView.webview.onDidReceiveMessage((message) => + this.controller?.handle(message), ); - } - - private setupEventHandlers(webviewView: vscode.WebviewView): void { - // Handle webview becoming visible/hidden webviewView.onDidChangeVisibility(() => { if (webviewView.visible) { - this.refreshData(); + void this.controller?.refresh(); } }); - // Handle messages from webview - webviewView.webview.onDidReceiveMessage(async (message: WebviewMessage) => { - if (this.messageHandler) { - await this.messageHandler.handleMessage(message); - } - }); + void this.controller.refresh(); } - private async refreshData(): Promise { - if (this.messageHandler) { - await this.messageHandler.handleMessage({ command: "refresh" }); + /** Debounced: coalesces bursts (e.g. save-all) into a single recompute. */ + refresh(): Promise { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); } + return new Promise((resolve) => { + this.refreshTimer = setTimeout(() => { + this.refreshTimer = undefined; + void this.controller?.refresh().finally(resolve); + }, REFRESH_DEBOUNCE_MS); + }); } } From 3538b20f071c1e241185417bbb6d02a1873d2aa2 Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:57:36 +0200 Subject: [PATCH 12/33] style(timeline): density pass on the toolbar sync cell Replace the two-line sync cell (value + sub-label span) with a single value plus a title tooltip; shrink toolbar font/padding. Co-Authored-By: Claude Sonnet 5 --- package-lock.json | 4 +- .../timeline/services/webview-html.service.ts | 73 +++++++++---------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72e5eec..4de4734 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-github-desktop", - "version": "1.1.4", + "version": "1.1.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-github-desktop", - "version": "1.1.4", + "version": "1.1.13", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 7847307..ce946cb 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -51,27 +51,24 @@ ${STYLES}
- - - @@ -178,13 +175,13 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- toolbar ---- */ #toolbar { - display: flex; flex: 0 0 auto; height: 42px; + display: flex; flex: 0 0 auto; height: 26px; background: var(--vscode-sideBar-background); border-bottom: 1px solid var(--vscode-panel-border); } .cell { - display: flex; align-items: center; gap: 7px; - flex: 1 1 0; min-width: 0; padding: 0 10px; + display: flex; align-items: center; gap: 5px; + flex: 1 1 0; min-width: 0; padding: 0 7px; background: transparent; border: 0; border-right: 1px solid var(--vscode-panel-border); cursor: pointer; text-align: left; @@ -192,13 +189,13 @@ button, input, textarea { font: inherit; color: inherit; } .cell:last-child { border-right: 0; } .cell:hover { background: var(--vscode-list-hoverBackground); } .cell-ico { flex: 0 0 auto; display: flex; opacity: .8; } -.cell-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; } -.cell-label { font-size: 10px; color: var(--vscode-descriptionForeground); } -.cell-value { font-weight: 600; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.cell-sub { font-weight: 400; font-size: 10px; color: var(--vscode-descriptionForeground); } +.cell-ico svg { width: 14px; height: 14px; } +.cell-text { display: flex; flex-direction: column; min-width: 0; } +.cell-value { font-weight: 600; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .cell-caret { flex: 0 0 auto; opacity: .6; } +.cell-caret svg { width: 10px; height: 10px; } .cell-count { - flex: 0 0 auto; font-size: 11px; padding: 1px 5px; border-radius: 8px; + flex: 0 0 auto; font-size: 10px; padding: 0 4px; border-radius: 8px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } @@ -215,8 +212,8 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- tabs ---- */ #tabs { display: flex; flex: 0 0 auto; border-bottom: 1px solid var(--vscode-panel-border); } .tab { - flex: 1 1 0; padding: 5px 4px; background: transparent; border: 0; - border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; + flex: 1 1 0; padding: 3px 4px; background: transparent; border: 0; + border-bottom: 2px solid transparent; cursor: pointer; font-size: 11px; color: var(--vscode-descriptionForeground); font-weight: 500; } .tab:hover { background: var(--vscode-list-hoverBackground); } @@ -225,25 +222,25 @@ button, input, textarea { font: inherit; color: inherit; } font-size: 10px; padding: 0 5px; border-radius: 8px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } -.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow: hidden; } +.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; overflow-x: hidden; } /* ---- changed files ---- */ -#filterWrap { flex: 0 0 auto; padding: 6px; } +#filterWrap { flex: 0 0 auto; padding: 4px; } #filter { - width: 100%; padding: 3px 7px; border-radius: 2px; font-size: 12px; + width: 100%; padding: 2px 6px; border-radius: 2px; font-size: 11px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border, transparent); } #filter:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } #allRow { - flex: 0 0 auto; display: flex; align-items: center; gap: 7px; - padding: 3px 8px; font-size: 11px; color: var(--vscode-descriptionForeground); + flex: 0 0 auto; display: flex; align-items: center; gap: 6px; + padding: 2px 8px; font-size: 10px; color: var(--vscode-descriptionForeground); border-bottom: 1px solid var(--vscode-panel-border); cursor: pointer; } #fileList { flex: 1 1 0; overflow: auto; min-height: 44px; } .file-row { - display: flex; align-items: center; gap: 7px; padding: 2px 8px; cursor: pointer; - white-space: nowrap; + display: flex; align-items: center; gap: 6px; padding: 1px 8px; cursor: pointer; + white-space: nowrap; font-size: 12px; } .file-row:hover { background: var(--vscode-list-hoverBackground); } .file-row.is-selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } @@ -269,27 +266,27 @@ button, input, textarea { font: inherit; color: inherit; } /* ---- commit box ---- */ #commitBox { - flex: 0 0 auto; padding: 6px; border-top: 1px solid var(--vscode-panel-border); - display: flex; flex-direction: column; gap: 5px; + flex: 0 0 auto; padding: 5px; border-top: 1px solid var(--vscode-panel-border); + display: flex; flex-direction: column; gap: 4px; background: var(--vscode-sideBar-background); } -.commit-summary { display: flex; align-items: center; gap: 6px; } +.commit-summary { display: flex; align-items: center; gap: 5px; } .avatar { - flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; overflow: hidden; + flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; - font-size: 10px; font-weight: 700; text-transform: uppercase; + font-size: 9px; font-weight: 700; text-transform: uppercase; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } #summary, #description { - width: 100%; padding: 4px 7px; border-radius: 2px; font-size: 12px; + width: 100%; padding: 3px 6px; border-radius: 2px; font-size: 11px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border, transparent); } #summary:focus, #description:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } -#description { resize: vertical; min-height: 30px; max-height: 140px; } +#description { resize: vertical; min-height: 22px; max-height: 100px; } #commitBtn { - width: 100%; padding: 5px 10px; border: 0; border-radius: 2px; cursor: pointer; font-size: 12px; + width: 100%; padding: 4px 10px; border: 0; border-radius: 2px; cursor: pointer; font-size: 11px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } #commitBtn:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } @@ -398,7 +395,7 @@ function renderToolbar() { $("commitBranch").textContent = state.currentBranch || "branch"; const r = state.remote || {}; - const ico = $("syncIco"), label = $("syncLabel"), sub = $("syncSub"), count = $("syncCount"); + const ico = $("syncIco"), label = $("syncLabel"), count = $("syncCount"), cell = $("syncCell"); const svg = { fetch: ${JSON.stringify(ICON.fetch)}, push: ${JSON.stringify(ICON.push)}, pull: ${JSON.stringify(ICON.pull)}, publish: ${JSON.stringify(ICON.publish)} }; // Only claim "not published" when we positively know: a remote exists and // git reported no upstream. Anything uncertain (status not received yet) @@ -410,11 +407,11 @@ function renderToolbar() { ico.innerHTML = svg[mode]; count.hidden = true; - if (mode === "publish") { label.textContent = "Publish branch"; sub.textContent = "This branch is not on the remote yet"; } - else if (mode === "pull") { label.textContent = "Pull origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↓ " + r.behind; } - else if (mode === "push") { label.textContent = "Push origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↑ " + r.ahead; } - else { label.textContent = "Fetch origin"; sub.textContent = relFetched(r.lastFetched); } - $("syncCell").dataset.mode = mode; + if (mode === "publish") { label.textContent = "Publish branch"; cell.title = "This branch is not on the remote yet"; } + else if (mode === "pull") { label.textContent = "Pull origin"; cell.title = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↓ " + r.behind; } + else if (mode === "push") { label.textContent = "Push origin"; cell.title = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↑ " + r.ahead; } + else { label.textContent = "Fetch origin"; cell.title = relFetched(r.lastFetched); } + cell.dataset.mode = mode; } function relFetched(d) { if (!d) return "Never fetched"; From 9561980406c9e4eaa038f6ae3634dfd414c1031c Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:59:59 +0200 Subject: [PATCH 13/33] feat(timeline): restore commit context menu, new-branch & load-more MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewire the features that existed in the old React timeline onto the vanilla webview, now that the backend handlers are reachable again: - History rows: right-click context menu (checkout / branch-from / tag / cherry-pick / revert / reset / copy SHA / view on GitHub). - Branch dropdown: "New branch…" (with a Bring-changes / Stash choice when the working tree is dirty) and "Create pull request…". - History: "Load more commits" button + scroll-to-bottom paging, wired to loadMoreCommitsResponse with de-duplication. - commitSucceeded ack clears the commit inputs; a failed commit keeps them. - mergeConflict switches to the Changes tab and shows a warning bar. Co-Authored-By: Claude Sonnet 5 --- .../timeline/services/webview-html.service.ts | 165 +++++++++++++++++- 1 file changed, 162 insertions(+), 3 deletions(-) diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index ce946cb..43ae489 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -82,6 +82,7 @@ ${STYLES}
+
@@ -359,6 +360,23 @@ button, input, textarea { font: inherit; color: inherit; } .menu-item.is-current { color: var(--vscode-descriptionForeground); } .menu-sep { height: 1px; margin: 4px 0; background: var(--vscode-panel-border); } .menu-empty { padding: 6px 8px; color: var(--vscode-descriptionForeground); } +.menu-item.is-danger { color: var(--vscode-errorForeground, #f14c4c); } +.menu-btn { + flex: 1 1 0; padding: 4px 6px; border: 0; border-radius: 2px; cursor: pointer; font-size: 11px; + background: var(--vscode-button-secondaryBackground, var(--vscode-button-background)); + color: var(--vscode-button-secondaryForeground, var(--vscode-button-foreground)); +} +.menu-btn:hover { background: var(--vscode-button-hoverBackground); } +.load-more { + width: 100%; padding: 6px; border: 0; background: transparent; cursor: pointer; + color: var(--vscode-textLink-foreground); font-size: 11px; +} +.load-more:hover:not(:disabled) { background: var(--vscode-list-hoverBackground); } +#conflictBar { + flex: 0 0 auto; padding: 5px 8px; font-size: 11px; + background: var(--vscode-inputValidation-warningBackground, rgba(228,103,107,.15)); + border-bottom: 1px solid var(--vscode-inputValidation-warningBorder, #e4676b); +} ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-thumb { background: var(--vscode-scrollbarSlider-background); border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground); } @@ -386,6 +404,8 @@ const state = { selectedPath: null, selectedCommit: null, filter: "", + hasMore: false, + loadingMore: false, }; /* ---------- toolbar ---------- */ @@ -540,9 +560,9 @@ function doCommit() { const desc = $("description").value.trim(); const message = desc ? summary + "\n\n" + desc : summary; post("commitFiles", { message: message, files: Array.from(state.selectedFiles) }); - $("summary").value = ""; $("description").value = ""; - state.selectedFiles.clear(); - updateCommitBtn(); + $("commitBtn").disabled = true; + // Inputs are cleared only once the extension acks with "commitSucceeded", + // so a failed commit doesn't lose the typed message. } $("commitBtn").onclick = doCommit; ["summary", "description"].forEach((id) => { @@ -569,8 +589,65 @@ function renderHistory() { renderHistory(); post("openCommitDetail", { hash: c.hash }); }; + row.oncontextmenu = (e) => { e.preventDefault(); openCommitMenu(e.clientX, e.clientY, c); }; list.appendChild(row); } + if (state.hasMore) { + const more = document.createElement("button"); + more.className = "load-more"; more.type = "button"; + more.textContent = state.loadingMore ? "Loading…" : "Load more commits"; + more.disabled = state.loadingMore; + more.onclick = loadMore; + list.appendChild(more); + } +} +function loadMore() { + if (state.loadingMore || !state.hasMore) return; + state.loadingMore = true; + renderHistory(); + post("loadMoreCommits", { offset: state.history.length }); +} +$("commitList").addEventListener("scroll", () => { + const el = $("commitList"); + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) loadMore(); +}); + +const COMMIT_MENU = [ + ["checkout", "Checkout this commit"], + ["createBranch", "Create branch from commit…"], + ["createTag", "Create tag…"], + ["sep"], + ["cherryPick", "Cherry-pick to current branch"], + ["revert", "Revert this commit"], + ["reset", "Reset current branch to here"], + ["sep"], + ["copySha", "Copy SHA"], + ["viewOnGitHub", "View on GitHub"], +]; +function openCommitMenu(x, y, commit) { + openMenuAt(x, y, (m) => { + for (const entry of COMMIT_MENU) { + if (entry[0] === "sep") { const s = document.createElement("div"); s.className = "menu-sep"; m.appendChild(s); continue; } + const it = document.createElement("div"); + it.className = "menu-item" + (entry[0] === "reset" ? " is-danger" : ""); + it.textContent = entry[1]; + it.onclick = () => { closeMenu(); runCommitAction(entry[0], commit); }; + m.appendChild(it); + } + }); +} +function runCommitAction(action, commit) { + const h = commit.hash; + switch (action) { + case "checkout": post("checkoutCommit", { hash: h }); break; + case "createBranch": post("createBranchFromCommit", { hash: h }); break; + case "createTag": post("createTagFromCommit", { hash: h }); break; + case "cherryPick": post("cherryPickCommit", { hash: h }); break; + case "revert": post("revertCommit", { hash: h }); break; + case "reset": post("resetToCommit", { hash: h }); break; + case "copySha": navigator.clipboard && navigator.clipboard.writeText(h); break; + case "viewOnGitHub": post("viewCommitOnGitHub", { hash: h }); break; + } } /* ---------- diff rendering ---------- */ @@ -625,9 +702,34 @@ function openMenu(anchor, build) { }); setTimeout(() => document.addEventListener("mousedown", onDocDown, true), 0); } +function openMenuAt(x, y, build) { + menu.innerHTML = ""; build(menu); + menu.hidden = false; + menu.style.visibility = "hidden"; + requestAnimationFrame(() => { + const mw = menu.offsetWidth, mh = menu.offsetHeight; + let nx = x, ny = y; + if (nx + mw > window.innerWidth - 8) nx = window.innerWidth - 8 - mw; + if (ny + mh > window.innerHeight - 8) ny = Math.max(8, window.innerHeight - 8 - mh); + menu.style.left = Math.max(8, nx) + "px"; + menu.style.top = Math.max(8, ny) + "px"; + menu.style.visibility = ""; + }); + setTimeout(() => document.addEventListener("mousedown", onDocDown, true), 0); +} $("branchCell").onclick = () => { openMenu($("branchCell"), (m) => { + const mkAction = (label, fn) => { + const it = document.createElement("div"); + it.className = "menu-item"; it.textContent = label; + it.onclick = () => { closeMenu(); fn(); }; + m.appendChild(it); + }; + mkAction("+ New branch…", startNewBranch); + if (state.currentBranch) mkAction("⇡ Create pull request…", () => post("createPullRequest", { branch: state.currentBranch })); + const sep = document.createElement("div"); sep.className = "menu-sep"; m.appendChild(sep); + const filter = document.createElement("input"); filter.className = "menu-filter"; filter.placeholder = "Find a branch…"; m.appendChild(filter); @@ -651,6 +753,38 @@ $("branchCell").onclick = () => { }); }; +function startNewBranch() { + openMenu($("branchCell"), (m) => { + const input = document.createElement("input"); + input.className = "menu-filter"; input.placeholder = "New branch name"; + m.appendChild(input); + const dirty = state.changes.length > 0; + const hint = document.createElement("div"); + hint.className = "menu-empty"; + hint.textContent = dirty + ? "You have uncommitted changes — choose what to do:" + : "Press Enter to create from " + (state.currentBranch || "HEAD"); + m.appendChild(hint); + const submit = (bringChanges) => { + const name = input.value.trim(); + if (!name) return; + closeMenu(); + if (dirty) post("createBranchWithChanges", { branchName: name, bringChanges: bringChanges }); + else post("createBranch", { branchName: name }); + }; + if (dirty) { + const row = document.createElement("div"); row.style.display = "flex"; row.style.gap = "4px"; row.style.padding = "4px"; + const a = document.createElement("button"); a.className = "menu-btn"; a.type = "button"; a.textContent = "Bring changes"; + a.onclick = () => submit(true); + const b = document.createElement("button"); b.className = "menu-btn"; b.type = "button"; b.textContent = "Stash them"; + b.onclick = () => submit(false); + row.appendChild(a); row.appendChild(b); m.appendChild(row); + } + input.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); submit(true); } }); + setTimeout(() => input.focus(), 0); + }); +} + $("repoCell").onclick = () => { openMenu($("repoCell"), (m) => { const it = document.createElement("div"); @@ -680,14 +814,39 @@ window.addEventListener("message", (ev) => { state.selectedPath = null; $("diffHeader").hidden = true; } + if (!state.changes.some((c) => /[UC]/.test(c.status || ""))) $("conflictBar").hidden = true; renderChanges(); updateLayout(); break; } case "updateHistory": state.history = msg.history || []; + state.hasMore = !!msg.hasMoreCommits; + state.loadingMore = false; + renderHistory(); + break; + case "loadMoreCommitsResponse": { + const seen = new Set(state.history.map((c) => c.hash)); + state.history = state.history.concat((msg.history || []).filter((c) => !seen.has(c.hash))); + state.hasMore = !!msg.hasMoreCommits; + state.loadingMore = false; renderHistory(); break; + } + case "commitSucceeded": + $("summary").value = ""; $("description").value = ""; + state.selectedFiles.clear(); + updateCommitBtn(); + break; + case "mergeConflict": + state.tab = "changes"; + $("pane-changes").hidden = false; $("pane-history").hidden = true; + document.querySelectorAll(".tab").forEach((b) => b.classList.toggle("is-active", b.dataset.tab === "changes")); + $("conflictBar").hidden = false; + $("conflictBar").textContent = "⚠ " + (msg.operation || "operation") + " stopped — " + + (msg.files || []).length + " file(s) in conflict. Resolve them, then commit."; + renderChanges(); + break; case "updateBranches": state.branches = msg.branches || []; state.currentBranch = msg.currentBranch || null; From 5313534b290f586254fd3dc32d45d8c505b9f182 Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:00:43 +0200 Subject: [PATCH 14/33] refactor(timeline): trim dead interface types Remove FileIconInfo / CommitDetailFile / the untyped WebviewMessage from the timeline interfaces barrel (superseded by the discriminated unions in messages.ts); document the survivors. Co-Authored-By: Claude Sonnet 5 --- .../timeline-view-provider.interface.ts | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/webviews/timeline/interfaces/timeline-view-provider.interface.ts b/src/webviews/timeline/interfaces/timeline-view-provider.interface.ts index 85af762..f2150ea 100644 --- a/src/webviews/timeline/interfaces/timeline-view-provider.interface.ts +++ b/src/webviews/timeline/interfaces/timeline-view-provider.interface.ts @@ -1,11 +1,17 @@ import * as vscode from "vscode"; +/** + * @description One entry in the Changes tab: a path and its git status code. + */ export interface ChangeEntry { path: string; status: string; staged?: boolean; } +/** + * @description One row of the History tab. + */ export interface CommitEntry { hash: string; shortHash: string; @@ -15,9 +21,13 @@ export interface CommitEntry { relativeTime: string; committedAt: string; tags?: string[]; + /** `false` when the commit is known to be ahead of the upstream. */ isPushed?: boolean; } +/** + * @description Aggregate detail of a single commit shown in the diff pane. + */ export interface CommitDetail { summary: CommitEntry & { additions: number; @@ -32,25 +42,9 @@ export interface CommitDetail { }>; } -export interface FileIconInfo { - iconClass?: string; - iconUri?: string; -} - -export interface CommitDetailFile { - path: string; - status: string; - additions: number; - deletions: number; - iconClass?: string; - iconUri?: string; -} - -export interface WebviewMessage { - command: string; - [key: string]: any; -} - +/** + * @description Contract the extension host relies on to refresh the timeline. + */ export interface ITimelineViewProvider extends vscode.WebviewViewProvider { refresh(): Promise; } From 26f3d1e80d63917b8de94676ff892edc9cb168d0 Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:35:08 +0200 Subject: [PATCH 15/33] style(timeline): rework the webview to match GitHub Desktop - 50px toolbar with stacked label/value cells (Current repository / Current branch / sync) and a sub-line on the sync cell. - 41px tabs with an accent underline on the active tab. - Roomier file rows (28px) with filled octicon-style status squares. - Commit box on its own chrome panel: 28px avatar, rounded inputs, full-width accent "Commit to " button, taller description. - Larger empty states, softer menus (8px radius, deeper shadow), accent-tinted diff gutters, consistent 6px control radius. Co-Authored-By: Claude Sonnet 5 --- .../timeline/services/webview-html.service.ts | 246 ++++++++++-------- 1 file changed, 137 insertions(+), 109 deletions(-) diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 43ae489..10818c8 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -51,24 +51,27 @@ ${STYLES}
- - - @@ -162,224 +165,247 @@ const ICON = { const STYLES = ` * { box-sizing: border-box; margin: 0; padding: 0; } [hidden] { display: none !important; } -:root { color-scheme: light dark; } +:root { + color-scheme: light dark; + --gd-accent: var(--vscode-button-background, #1f6feb); + --gd-accent-fg: var(--vscode-button-foreground, #fff); + --gd-chrome: var(--vscode-sideBarSectionHeader-background, var(--vscode-sideBar-background)); + --gd-border: var(--vscode-panel-border, rgba(128,128,128,.28)); + --gd-radius: 6px; +} html, body { height: 100%; } body { display: flex; flex-direction: column; - font-family: var(--vscode-font-family, "Segoe UI", system-ui, sans-serif); - font-size: var(--vscode-font-size, 13px); + font-family: var(--vscode-font-family, -apple-system, "Segoe UI", system-ui, sans-serif); + font-size: 12px; line-height: 1.4; color: var(--vscode-foreground); - background: var(--vscode-editor-background); + background: var(--vscode-sideBar-background); overflow: hidden; } button, input, textarea { font: inherit; color: inherit; } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: var(--vscode-scrollbarSlider-background); border-radius: 5px; background-clip: padding-box; border: 2px solid transparent; } +::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground); } /* ---- toolbar ---- */ #toolbar { - display: flex; flex: 0 0 auto; height: 26px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--vscode-panel-border); + display: flex; flex: 0 0 auto; height: 50px; + background: var(--gd-chrome); + border-bottom: 1px solid var(--gd-border); } .cell { - display: flex; align-items: center; gap: 5px; - flex: 1 1 0; min-width: 0; padding: 0 7px; + display: flex; align-items: center; gap: 8px; + flex: 1 1 0; min-width: 0; padding: 0 12px; background: transparent; border: 0; - border-right: 1px solid var(--vscode-panel-border); + border-right: 1px solid var(--gd-border); cursor: pointer; text-align: left; } .cell:last-child { border-right: 0; } .cell:hover { background: var(--vscode-list-hoverBackground); } -.cell-ico { flex: 0 0 auto; display: flex; opacity: .8; } -.cell-ico svg { width: 14px; height: 14px; } -.cell-text { display: flex; flex-direction: column; min-width: 0; } -.cell-value { font-weight: 600; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.cell-caret { flex: 0 0 auto; opacity: .6; } -.cell-caret svg { width: 10px; height: 10px; } +.cell:active { background: var(--vscode-list-activeSelectionBackground); } +.cell-ico { flex: 0 0 auto; display: flex; opacity: .85; } +.cell-ico svg { width: 16px; height: 16px; } +.cell-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; } +.cell-label { + font-size: 10px; line-height: 1.2; color: var(--vscode-descriptionForeground); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.cell-value { + font-weight: 600; font-size: 12px; line-height: 1.3; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.cell-caret { flex: 0 0 auto; opacity: .55; } +.cell-caret svg { width: 12px; height: 12px; } .cell-count { - flex: 0 0 auto; font-size: 10px; padding: 0 4px; border-radius: 8px; + flex: 0 0 auto; display: inline-flex; align-items: center; gap: 2px; + font-size: 11px; font-weight: 600; padding: 1px 6px; border-radius: 10px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } /* ---- body split ---- */ #body { flex: 1 1 auto; display: flex; min-height: 0; } #left { - flex: 0 0 272px; display: flex; flex-direction: column; min-height: 0; + flex: 0 0 300px; display: flex; flex-direction: column; min-height: 0; background: var(--vscode-sideBar-background); - border-right: 1px solid var(--vscode-panel-border); + border-right: 1px solid var(--gd-border); } #body.no-diff #left { flex: 1 1 auto; border-right: 0; } -#right { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; } +#right { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; background: var(--vscode-editor-background); } /* ---- tabs ---- */ -#tabs { display: flex; flex: 0 0 auto; border-bottom: 1px solid var(--vscode-panel-border); } +#tabs { display: flex; flex: 0 0 auto; background: var(--gd-chrome); border-bottom: 1px solid var(--gd-border); } .tab { - flex: 1 1 0; padding: 3px 4px; background: transparent; border: 0; - border-bottom: 2px solid transparent; cursor: pointer; font-size: 11px; + flex: 1 1 0; height: 41px; background: transparent; border: 0; + border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; color: var(--vscode-descriptionForeground); font-weight: 500; + display: flex; align-items: center; justify-content: center; gap: 6px; } -.tab:hover { background: var(--vscode-list-hoverBackground); } -.tab.is-active { color: var(--vscode-foreground); border-bottom-color: var(--vscode-focusBorder); } +.tab:hover { color: var(--vscode-foreground); } +.tab.is-active { color: var(--vscode-foreground); border-bottom-color: var(--gd-accent); } .badge { - font-size: 10px; padding: 0 5px; border-radius: 8px; + font-size: 10px; font-weight: 600; min-width: 16px; padding: 1px 5px; border-radius: 9px; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); } -.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; overflow-x: hidden; } +.tabpane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; } /* ---- changed files ---- */ -#filterWrap { flex: 0 0 auto; padding: 4px; } +#filterWrap { flex: 0 0 auto; padding: 8px 10px 4px; } #filter { - width: 100%; padding: 2px 6px; border-radius: 2px; font-size: 11px; + width: 100%; padding: 5px 8px; border-radius: var(--gd-radius); font-size: 12px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); - border: 1px solid var(--vscode-input-border, transparent); + border: 1px solid var(--vscode-input-border, var(--gd-border)); } -#filter:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } +#filter:focus { outline: 0; border-color: var(--vscode-focusBorder); } #allRow { - flex: 0 0 auto; display: flex; align-items: center; gap: 6px; - padding: 2px 8px; font-size: 10px; color: var(--vscode-descriptionForeground); - border-bottom: 1px solid var(--vscode-panel-border); cursor: pointer; + flex: 0 0 auto; display: flex; align-items: center; gap: 8px; + padding: 6px 12px; font-size: 11px; color: var(--vscode-descriptionForeground); + cursor: pointer; } +#allRow input, .file-row input[type=checkbox] { width: 13px; height: 13px; accent-color: var(--gd-accent); cursor: pointer; } #fileList { flex: 1 1 0; overflow: auto; min-height: 44px; } .file-row { - display: flex; align-items: center; gap: 6px; padding: 1px 8px; cursor: pointer; + display: flex; align-items: center; gap: 8px; height: 28px; padding: 0 12px; cursor: pointer; white-space: nowrap; font-size: 12px; } .file-row:hover { background: var(--vscode-list-hoverBackground); } -.file-row.is-selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } +.file-row.is-selected { background: var(--vscode-list-inactiveSelectionBackground); } .file-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; } -.file-dir { opacity: .6; } +.file-dir { opacity: .55; } .status-sq { - flex: 0 0 auto; width: 14px; height: 14px; border-radius: 2px; + flex: 0 0 auto; width: 16px; height: 16px; border-radius: 3px; display: flex; align-items: center; justify-content: center; - font-size: 9px; font-weight: 700; line-height: 1; - border: 1px solid currentColor; -} -.st-M { color: var(--vscode-gitDecoration-modifiedResourceForeground, #e2c08d); } -.st-A, .st-U { color: var(--vscode-gitDecoration-untrackedResourceForeground, #73c991); } -.st-D { color: var(--vscode-gitDecoration-deletedResourceForeground, #f14c4c); } -.st-R { color: var(--vscode-gitDecoration-renamedResourceForeground, #73c991); } -.st-C { color: var(--vscode-gitDecoration-conflictingResourceForeground, #e4676b); } + font-size: 10px; font-weight: 700; line-height: 1; color: #fff; +} +.st-M { background: var(--vscode-gitDecoration-modifiedResourceForeground, #d0a215); } +.st-A, .st-U { background: var(--vscode-gitDecoration-untrackedResourceForeground, #2ea043); } +.st-D { background: var(--vscode-gitDecoration-deletedResourceForeground, #d73a49); } +.st-R { background: var(--vscode-gitDecoration-renamedResourceForeground, #8250df); } +.st-C { background: var(--vscode-gitDecoration-conflictingResourceForeground, #e4676b); } .file-x { - flex: 0 0 auto; opacity: 0; padding: 0 2px; border: 0; background: transparent; - cursor: pointer; color: inherit; font-size: 13px; + flex: 0 0 auto; opacity: 0; width: 18px; height: 18px; border: 0; border-radius: 4px; + background: transparent; cursor: pointer; color: inherit; font-size: 14px; line-height: 1; } -.file-row:hover .file-x { opacity: .7; } -.file-x:hover { opacity: 1; } +.file-row:hover .file-x { opacity: .6; } +.file-x:hover { opacity: 1; background: var(--vscode-toolbar-hoverBackground, rgba(128,128,128,.2)); } /* ---- commit box ---- */ #commitBox { - flex: 0 0 auto; padding: 5px; border-top: 1px solid var(--vscode-panel-border); - display: flex; flex-direction: column; gap: 4px; - background: var(--vscode-sideBar-background); + flex: 0 0 auto; padding: 10px; border-top: 1px solid var(--gd-border); + display: flex; flex-direction: column; gap: 8px; + background: var(--gd-chrome); } -.commit-summary { display: flex; align-items: center; gap: 5px; } +.commit-summary { display: flex; align-items: center; gap: 8px; } .avatar { - flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; overflow: hidden; + flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; - font-size: 9px; font-weight: 700; text-transform: uppercase; - background: var(--vscode-button-background); color: var(--vscode-button-foreground); + font-size: 11px; font-weight: 700; text-transform: uppercase; + background: var(--gd-accent); color: var(--gd-accent-fg); } .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } #summary, #description { - width: 100%; padding: 3px 6px; border-radius: 2px; font-size: 11px; + width: 100%; padding: 6px 9px; border-radius: var(--gd-radius); font-size: 12px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); - border: 1px solid var(--vscode-input-border, transparent); + border: 1px solid var(--vscode-input-border, var(--gd-border)); } -#summary:focus, #description:focus { outline: 1px solid var(--vscode-focusBorder); border-color: var(--vscode-focusBorder); } -#description { resize: vertical; min-height: 22px; max-height: 100px; } +#summary { font-weight: 600; } +#summary:focus, #description:focus { outline: 0; border-color: var(--vscode-focusBorder); } +#description { resize: vertical; min-height: 52px; max-height: 160px; } #commitBtn { - width: 100%; padding: 4px 10px; border: 0; border-radius: 2px; cursor: pointer; font-size: 11px; - background: var(--vscode-button-background); color: var(--vscode-button-foreground); + width: 100%; padding: 8px 12px; border: 0; border-radius: var(--gd-radius); cursor: pointer; + font-size: 12px; font-weight: 600; + background: var(--gd-accent); color: var(--gd-accent-fg); } -#commitBtn:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } -#commitBtn:disabled { opacity: .5; cursor: default; } +#commitBtn:hover:not(:disabled) { background: var(--vscode-button-hoverBackground, var(--gd-accent)); filter: brightness(1.08); } +#commitBtn:disabled { opacity: .45; cursor: default; } #commitBtn strong { font-weight: 700; } /* ---- history ---- */ #commitList { flex: 1 1 auto; overflow: auto; min-height: 0; } .commit-row { - display: flex; flex-direction: column; gap: 2px; padding: 7px 12px; cursor: pointer; - border-bottom: 1px solid var(--vscode-panel-border); + display: flex; flex-direction: column; gap: 3px; padding: 9px 12px; cursor: pointer; + border-bottom: 1px solid var(--gd-border); } .commit-row:hover { background: var(--vscode-list-hoverBackground); } -.commit-row.is-selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } -.commit-msg { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.commit-row.is-selected { background: var(--vscode-list-inactiveSelectionBackground); box-shadow: inset 2px 0 0 var(--gd-accent); } +.commit-msg { font-weight: 500; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .commit-meta { font-size: 11px; color: var(--vscode-descriptionForeground); display: flex; align-items: center; gap: 6px; } -.unpushed-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--vscode-charts-blue, #4daafc); flex: 0 0 auto; } +.unpushed-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--gd-accent); flex: 0 0 auto; } /* ---- diff ---- */ #diffHeader { - flex: 0 0 auto; padding: 6px 12px; font-size: 12px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--vscode-panel-border); + flex: 0 0 auto; padding: 9px 14px; font-size: 12px; font-weight: 600; + background: var(--gd-chrome); + border-bottom: 1px solid var(--gd-border); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #diffBody { flex: 1 1 auto; overflow: auto; min-height: 0; background: var(--vscode-editor-background); - font-family: var(--vscode-editor-font-family, ui-monospace, monospace); - font-size: var(--vscode-editor-font-size, 12px); + font-family: var(--vscode-editor-font-family, ui-monospace, "SF Mono", Menlo, monospace); + font-size: var(--vscode-editor-font-size, 12px); line-height: 1.5; } .diff-line { display: flex; white-space: pre; } .diff-gutter { - flex: 0 0 auto; width: 44px; padding: 0 6px; text-align: right; + flex: 0 0 auto; width: 46px; padding: 0 8px; text-align: right; color: var(--vscode-editorLineNumber-foreground); opacity: .6; - user-select: none; border-right: 1px solid var(--vscode-panel-border); + user-select: none; } -.diff-text { flex: 1 1 auto; padding: 0 8px; } -.diff-add { background: var(--vscode-diffEditor-insertedTextBackground, rgba(63,185,80,.15)); } +.diff-text { flex: 1 1 auto; padding: 0 10px; } +.diff-add { background: var(--vscode-diffEditor-insertedTextBackground, rgba(46,160,67,.15)); } +.diff-add .diff-gutter { background: var(--vscode-diffEditor-insertedLineBackground, rgba(46,160,67,.1)); } .diff-del { background: var(--vscode-diffEditor-removedTextBackground, rgba(248,81,73,.15)); } +.diff-del .diff-gutter { background: var(--vscode-diffEditor-removedLineBackground, rgba(248,81,73,.1)); } .diff-hunk { color: var(--vscode-descriptionForeground); background: var(--vscode-editor-inactiveSelectionBackground); } .diff-meta { color: var(--vscode-descriptionForeground); } /* ---- empty states ---- */ .empty-block { flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; - justify-content: center; text-align: center; padding: 24px; gap: 6px; + justify-content: center; text-align: center; padding: 32px; gap: 10px; } -.empty-emoji { opacity: .5; } -.empty-title { font-size: 14px; } -.empty-sub { font-size: 12px; color: var(--vscode-descriptionForeground); max-width: 240px; } +.empty-emoji { opacity: .35; } +.empty-emoji svg { width: 48px; height: 48px; } +.empty-title { font-size: 15px; font-weight: 400; color: var(--vscode-foreground); } +.empty-sub { font-size: 12px; color: var(--vscode-descriptionForeground); max-width: 260px; line-height: 1.5; } /* ---- popup menu ---- */ .menu { - position: fixed; z-index: 50; min-width: 220px; max-height: 60vh; overflow: auto; - background: var(--vscode-menu-background, var(--vscode-sideBar-background)); - border: 1px solid var(--vscode-menu-border, var(--vscode-panel-border)); - border-radius: 4px; box-shadow: 0 4px 14px rgba(0,0,0,.4); padding: 4px; + position: fixed; z-index: 50; min-width: 240px; max-height: 66vh; overflow: auto; + background: var(--vscode-menu-background, var(--vscode-dropdown-background, var(--gd-chrome))); + border: 1px solid var(--vscode-menu-border, var(--gd-border)); + border-radius: 8px; box-shadow: 0 8px 28px rgba(0,0,0,.45); padding: 5px; } .menu input.menu-filter { - width: 100%; margin-bottom: 4px; padding: 4px 6px; border-radius: 2px; + width: 100%; margin-bottom: 4px; padding: 6px 8px; border-radius: var(--gd-radius); background: var(--vscode-input-background); color: var(--vscode-input-foreground); - border: 1px solid var(--vscode-input-border, transparent); + border: 1px solid var(--vscode-input-border, var(--gd-border)); } +.menu input.menu-filter:focus { outline: 0; border-color: var(--vscode-focusBorder); } .menu-item { - display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 2px; - cursor: pointer; white-space: nowrap; + display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: var(--gd-radius); + cursor: pointer; white-space: nowrap; font-size: 12px; } .menu-item:hover { background: var(--vscode-list-hoverBackground); } .menu-item.is-current { color: var(--vscode-descriptionForeground); } -.menu-sep { height: 1px; margin: 4px 0; background: var(--vscode-panel-border); } -.menu-empty { padding: 6px 8px; color: var(--vscode-descriptionForeground); } +.menu-sep { height: 1px; margin: 5px 4px; background: var(--gd-border); } +.menu-empty { padding: 7px 9px; color: var(--vscode-descriptionForeground); font-size: 12px; } .menu-item.is-danger { color: var(--vscode-errorForeground, #f14c4c); } .menu-btn { - flex: 1 1 0; padding: 4px 6px; border: 0; border-radius: 2px; cursor: pointer; font-size: 11px; - background: var(--vscode-button-secondaryBackground, var(--vscode-button-background)); - color: var(--vscode-button-secondaryForeground, var(--vscode-button-foreground)); + flex: 1 1 0; padding: 6px 8px; border: 0; border-radius: var(--gd-radius); cursor: pointer; font-size: 11px; font-weight: 600; + background: var(--vscode-button-secondaryBackground, var(--gd-accent)); + color: var(--vscode-button-secondaryForeground, var(--gd-accent-fg)); } .menu-btn:hover { background: var(--vscode-button-hoverBackground); } .load-more { - width: 100%; padding: 6px; border: 0; background: transparent; cursor: pointer; - color: var(--vscode-textLink-foreground); font-size: 11px; + width: 100%; padding: 9px; border: 0; background: transparent; cursor: pointer; + color: var(--vscode-textLink-foreground); font-size: 12px; } .load-more:hover:not(:disabled) { background: var(--vscode-list-hoverBackground); } #conflictBar { - flex: 0 0 auto; padding: 5px 8px; font-size: 11px; + flex: 0 0 auto; padding: 8px 12px; font-size: 11px; line-height: 1.45; background: var(--vscode-inputValidation-warningBackground, rgba(228,103,107,.15)); border-bottom: 1px solid var(--vscode-inputValidation-warningBorder, #e4676b); } -::-webkit-scrollbar { width: 10px; height: 10px; } -::-webkit-scrollbar-thumb { background: var(--vscode-scrollbarSlider-background); border-radius: 5px; } -::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground); } `; const SCRIPT = String.raw` @@ -427,10 +453,12 @@ function renderToolbar() { ico.innerHTML = svg[mode]; count.hidden = true; - if (mode === "publish") { label.textContent = "Publish branch"; cell.title = "This branch is not on the remote yet"; } - else if (mode === "pull") { label.textContent = "Pull origin"; cell.title = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↓ " + r.behind; } - else if (mode === "push") { label.textContent = "Push origin"; cell.title = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↑ " + r.ahead; } - else { label.textContent = "Fetch origin"; cell.title = relFetched(r.lastFetched); } + const sub = $("syncSub"); + if (mode === "publish") { label.textContent = "Publish branch"; sub.textContent = "This branch is not on GitHub yet"; } + else if (mode === "pull") { label.textContent = "Pull origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↓ " + r.behind; } + else if (mode === "push") { label.textContent = "Push origin"; sub.textContent = relFetched(r.lastFetched); count.hidden = false; count.textContent = "↑ " + r.ahead; } + else { label.textContent = "Fetch origin"; sub.textContent = relFetched(r.lastFetched); } + cell.title = sub.textContent; cell.dataset.mode = mode; } function relFetched(d) { From f193ef536965292c66a52e997570fb476f65c0c6 Mon Sep 17 00:00:00 2001 From: KMS <243513302+AlyNotMe@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:29:17 +0200 Subject: [PATCH 16/33] feat(timeline): undo, amend, co-authors, conflict resolution & stash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New backend services (SOLID: one responsibility each, wired via the router): - WorkingTreeService.amend / undoLastCommit — amend rewrites HEAD (warns when it is already on a remote); undo is `reset --soft HEAD~1`. - ConflictService — markResolved / continue / abort, driving whichever of merge / cherry-pick / revert / rebase is detected in the git dir (detection shared with the read model via git-operation-state.ts). - StashService — push (with untracked) / apply / pop / drop. - RepositoryDataService now reports operation, conflicted paths, canUndo and the stash list in the snapshot; the controller broadcasts updateOperation + updateStashes on every refresh. Webview: - Commit box: "Amend last commit" checkbox (prefills the last message) and an "Add co-authors" field that appends Co-Authored-By trailers. - History tab: an "Undo " bar when undo is safe. - Conflict bar with Abort / Continue (Continue enabled once no file is left conflicted); conflicted files show "Mark resolved". - Branch dropdown: "Stash all changes" and the list of existing stashes with pop / delete. Co-Authored-By: Claude Sonnet 5 --- .../interfaces/repository-snapshot.ts | 20 ++ src/webviews/timeline/messages.ts | 19 ++ .../timeline/services/conflict.service.ts | 90 ++++++++ .../timeline/services/git-operation-state.ts | 36 ++++ .../services/repository-data.service.ts | 48 +++++ .../timeline/services/stash.service.ts | 61 ++++++ .../timeline/services/webview-html.service.ts | 200 +++++++++++++++--- .../timeline/services/working-tree.service.ts | 57 +++++ src/webviews/timeline/timeline-controller.ts | 23 ++ 9 files changed, 529 insertions(+), 25 deletions(-) create mode 100644 src/webviews/timeline/services/conflict.service.ts create mode 100644 src/webviews/timeline/services/git-operation-state.ts create mode 100644 src/webviews/timeline/services/stash.service.ts diff --git a/src/webviews/timeline/interfaces/repository-snapshot.ts b/src/webviews/timeline/interfaces/repository-snapshot.ts index 906f7eb..14d2b13 100644 --- a/src/webviews/timeline/interfaces/repository-snapshot.ts +++ b/src/webviews/timeline/interfaces/repository-snapshot.ts @@ -21,6 +21,20 @@ export interface RepositorySummary { remote?: string; } +/** + * @description A multi-step git operation left mid-flight by a conflict. + */ +export type InProgressOperation = "merge" | "cherry-pick" | "revert" | "rebase"; + +/** + * @description One entry of `git stash list`. + */ +export interface StashEntry { + index: number; + message: string; + branch: string | null; +} + /** * @description Everything the timeline needs for one render pass, assembled by * the repository read model in a single call. @@ -35,6 +49,12 @@ export interface RepositorySnapshot { repository: RepositorySummary; remoteStatus: RemoteStatus; tags: Record; + /** Non-null when a merge/cherry-pick/revert/rebase is stopped in conflict. */ + operation: InProgressOperation | null; + conflicted: string[]; + /** True when HEAD has a parent and no operation is in progress (undo is safe). */ + canUndo: boolean; + stashes: StashEntry[]; } /** Shared page size for commit history queries. */ diff --git a/src/webviews/timeline/messages.ts b/src/webviews/timeline/messages.ts index 8ab947a..d4bf9c6 100644 --- a/src/webviews/timeline/messages.ts +++ b/src/webviews/timeline/messages.ts @@ -4,8 +4,10 @@ import { CommitDetail, } from "./interfaces/timeline-view-provider.interface"; import { + InProgressOperation, RemoteStatus, RepositorySummary, + StashEntry, } from "./interfaces/repository-snapshot"; /* ------------------------------------------------------------------ * @@ -24,7 +26,16 @@ export type InboundMessage = | { command: "unstageFiles"; files: string[] } | { command: "commit"; message: string } | { command: "commitFiles"; message: string; files: string[] } + | { command: "amendCommit"; message: string; files: string[] } + | { command: "undoLastCommit" } | { command: "discardFiles"; files: string[] } + | { command: "markResolved"; files: string[] } + | { command: "abortOperation" } + | { command: "continueOperation" } + | { command: "getStashes" } + | { command: "stashPush"; message: string } + | { command: "stashApply"; index: number; drop: boolean } + | { command: "stashDrop"; index: number } | { command: "getWorkingDiff"; filePath: string } | { command: "fetch" } | { command: "pull" } @@ -95,6 +106,14 @@ export type OutboundMessage = } | { command: "commitSucceeded" } | { command: "mergeConflict"; operation: string; files: string[] } + | { + command: "updateOperation"; + operation: InProgressOperation | null; + conflicted: string[]; + canUndo: boolean; + lastCommitSummary: string | null; + } + | { command: "updateStashes"; stashes: StashEntry[] } | { command: "workingDiff"; payload: { path: string; diff: string } } | { command: "fileDiff"; payload: { path: string; diff: string } } | { command: "commitDetail"; payload: CommitDetail } diff --git a/src/webviews/timeline/services/conflict.service.ts b/src/webviews/timeline/services/conflict.service.ts new file mode 100644 index 0000000..d3c5aba --- /dev/null +++ b/src/webviews/timeline/services/conflict.service.ts @@ -0,0 +1,90 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { InProgressOperation } from "../interfaces/repository-snapshot"; +import { Notifier, RepositoryContext, Refresher } from "../ports"; +import { detectInProgressOperation } from "./git-operation-state"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +const CONTINUE: Record = { + merge: ["merge", "--continue"], + "cherry-pick": ["cherry-pick", "--continue"], + revert: ["revert", "--continue"], + rebase: ["rebase", "--continue"], +}; + +const ABORT: Record = { + merge: ["merge", "--abort"], + "cherry-pick": ["cherry-pick", "--abort"], + revert: ["revert", "--abort"], + rebase: ["rebase", "--abort"], +}; + +/** + * @description Drives a merge/cherry-pick/revert/rebase that stopped in + * conflict: mark files resolved, continue, or abort. The operation in progress + * is detected from the repository, not passed by the webview. + */ +export class ConflictService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + /** `git add`s the given paths, marking them resolved. */ + markResolved(files: string[]): Promise { + return this.withRepo(async (repo) => { + if (files.length === 0) { + return; + } + await this.git.plain(repo.localPath).add(files); + await this.refresher.refresh(); + }); + } + + /** Finishes the operation once every conflict is resolved. */ + continue(): Promise { + return this.withRepo(async (repo) => { + const git = this.git.plain(repo.localPath); + const op = await this.currentOperation(repo.localPath); + if (!op) { + this.notifier.warn("No operation in progress"); + return; + } + const status = await git.status(); + if (status.conflicted.length > 0) { + this.notifier.warn( + `${status.conflicted.length} file(s) still in conflict`, + ); + return; + } + await git + .raw([...CONTINUE[op], "--no-edit"]) + .catch(() => git.raw(CONTINUE[op])); + this.notifier.info(`${op} completed`); + await this.refresher.refresh(); + }); + } + + /** Rolls the operation back to its pre-conflict state. */ + abort(): Promise { + return this.withRepo(async (repo) => { + const op = await this.currentOperation(repo.localPath); + if (!op) { + this.notifier.warn("No operation in progress"); + return; + } + await this.git.plain(repo.localPath).raw(ABORT[op]); + this.notifier.info(`${op} aborted`); + await this.refresher.refresh(); + }); + } + + private currentOperation( + repoPath: string, + ): Promise { + return detectInProgressOperation(this.git.plain(repoPath)); + } +} diff --git a/src/webviews/timeline/services/git-operation-state.ts b/src/webviews/timeline/services/git-operation-state.ts new file mode 100644 index 0000000..ec38b60 --- /dev/null +++ b/src/webviews/timeline/services/git-operation-state.ts @@ -0,0 +1,36 @@ +import * as fs from "fs"; +import * as path from "path"; +import { SimpleGit } from "simple-git"; +import { InProgressOperation } from "../interfaces/repository-snapshot"; + +/** + * @description Detects a multi-step git operation left mid-flight (typically by + * a conflict) by probing the marker files in the git directory. + * + * @param git - A client bound to the repository + * @returns The operation in progress, or `null` when the tree is idle + */ +export async function detectInProgressOperation( + git: SimpleGit, +): Promise { + const dir = ( + await git.raw(["rev-parse", "--absolute-git-dir"]).catch(() => "") + ).trim(); + if (!dir) { + return null; + } + const has = (p: string): boolean => fs.existsSync(path.join(dir, p)); + if (has("rebase-merge") || has("rebase-apply")) { + return "rebase"; + } + if (has("CHERRY_PICK_HEAD")) { + return "cherry-pick"; + } + if (has("REVERT_HEAD")) { + return "revert"; + } + if (has("MERGE_HEAD")) { + return "merge"; + } + return null; +} diff --git a/src/webviews/timeline/services/repository-data.service.ts b/src/webviews/timeline/services/repository-data.service.ts index a3e800c..482b405 100644 --- a/src/webviews/timeline/services/repository-data.service.ts +++ b/src/webviews/timeline/services/repository-data.service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import { GitClientFactory } from "../../../core/git/git-authenticator"; import { RepositoryContext } from "../ports"; +import { detectInProgressOperation } from "./git-operation-state"; import { ChangeEntry, CommitEntry, @@ -8,8 +9,10 @@ import { } from "../interfaces/timeline-view-provider.interface"; import { COMMITS_PAGE_SIZE, + InProgressOperation, RemoteStatus, RepositorySnapshot, + StashEntry, } from "../interfaces/repository-snapshot"; /** @@ -62,6 +65,8 @@ export class RepositoryDataService { ); const branchList = this.listBranches(branch.all); + const operation = await this.detectOperation(git); + const canUndo = operation === null && (await this.hasParentCommit(git)); return { changes, @@ -77,6 +82,10 @@ export class RepositoryDataService { }, remoteStatus, tags, + operation, + conflicted: status.conflicted, + canUndo, + stashes: await this.getStashes(git), }; } @@ -228,6 +237,45 @@ export class RepositoryDataService { /* ---------------- internals ---------------- */ + private detectOperation( + git: ReturnType, + ): Promise { + return detectInProgressOperation(git); + } + + private async hasParentCommit( + git: ReturnType, + ): Promise { + try { + await git.raw(["rev-parse", "--verify", "-q", "HEAD^"]); + return true; + } catch { + return false; + } + } + + private async getStashes( + git: ReturnType, + ): Promise { + try { + const raw = await git.raw(["stash", "list", "--format=%gd%x09%gs"]); + return raw + .split("\n") + .filter((l) => l.trim()) + .map((line, index) => { + const [, subject = ""] = line.split("\t"); + const onBranch = subject.match(/^(?:WIP on|On) ([^:]+):/); + return { + index, + message: subject.replace(/^(?:WIP on|On) [^:]+:\s*/, "") || subject, + branch: onBranch ? onBranch[1] : null, + }; + }); + } catch { + return []; + } + } + private toCommitEntry( commit: { hash?: string; diff --git a/src/webviews/timeline/services/stash.service.ts b/src/webviews/timeline/services/stash.service.ts new file mode 100644 index 0000000..051c313 --- /dev/null +++ b/src/webviews/timeline/services/stash.service.ts @@ -0,0 +1,61 @@ +import { GitClientFactory } from "../../../core/git/git-authenticator"; +import { Notifier, RepositoryContext, Refresher } from "../ports"; +import { RepositoryScopedService } from "./repository-scoped.service"; + +/** + * @description Stash management: save the working tree, re-apply an entry + * (optionally dropping it), or discard one. + */ +export class StashService extends RepositoryScopedService { + constructor( + repos: RepositoryContext, + notifier: Notifier, + private readonly git: GitClientFactory, + private readonly refresher: Refresher, + ) { + super(repos, notifier); + } + + push(message: string): Promise { + return this.withRepo(async (repo) => { + const args = ["stash", "push", "--include-untracked"]; + if (message.trim()) { + args.push("-m", message.trim()); + } + const out = await this.git.plain(repo.localPath).raw(args); + if (/No local changes/i.test(out)) { + this.notifier.info("Nothing to stash"); + return; + } + this.notifier.info("Changes stashed"); + await this.refresher.refresh(); + }); + } + + apply(index: number, drop: boolean): Promise { + return this.withRepo(async (repo) => { + await this.git + .plain(repo.localPath) + .raw(["stash", drop ? "pop" : "apply", `stash@{${index}}`]); + this.notifier.info(drop ? "Stash popped" : "Stash applied"); + await this.refresher.refresh(); + }); + } + + drop(index: number): Promise { + return this.withRepo(async (repo) => { + const ok = await this.notifier.confirm( + `Delete stash@{${index}}? This cannot be undone.`, + "Delete", + ); + if (!ok) { + return; + } + await this.git + .plain(repo.localPath) + .raw(["stash", "drop", `stash@{${index}}`]); + this.notifier.info("Stash deleted"); + await this.refresher.refresh(); + }); + } +} diff --git a/src/webviews/timeline/services/webview-html.service.ts b/src/webviews/timeline/services/webview-html.service.ts index 10818c8..d176321 100644 --- a/src/webviews/timeline/services/webview-html.service.ts +++ b/src/webviews/timeline/services/webview-html.service.ts @@ -85,7 +85,13 @@ ${STYLES}
- +
@@ -105,11 +111,21 @@ ${STYLES}
+ +
+ + +