mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(patch): cherry-pick 81ccd80 to release/v0.28.0-preview.5-pr-18406 to patch version v0.28.0-preview.5 and create version 0.28.0-preview.6 (#18651)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com> Co-authored-by: galz10 <galzahavi@google.com>
This commit is contained in:
Generated
+81
-1
@@ -26,9 +26,11 @@
|
|||||||
"@types/minimatch": "^5.1.2",
|
"@types/minimatch": "^5.1.2",
|
||||||
"@types/mock-fs": "^4.13.4",
|
"@types/mock-fs": "^4.13.4",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
|
"@types/proper-lockfile": "^4.1.4",
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
"@types/shell-quote": "^1.7.5",
|
"@types/shell-quote": "^1.7.5",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
"@vitest/coverage-v8": "^3.1.1",
|
"@vitest/coverage-v8": "^3.1.1",
|
||||||
"@vitest/eslint-plugin": "^1.3.4",
|
"@vitest/eslint-plugin": "^1.3.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
@@ -2251,6 +2253,7 @@
|
|||||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^6.0.0",
|
"@octokit/auth-token": "^6.0.0",
|
||||||
"@octokit/graphql": "^9.0.2",
|
"@octokit/graphql": "^9.0.2",
|
||||||
@@ -2431,6 +2434,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
@@ -2464,6 +2468,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
|
||||||
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
|
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
},
|
},
|
||||||
@@ -2832,6 +2837,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
|
||||||
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
|
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.0.1",
|
"@opentelemetry/core": "2.0.1",
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
@@ -2865,6 +2871,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
|
||||||
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
|
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.0.1",
|
"@opentelemetry/core": "2.0.1",
|
||||||
"@opentelemetry/resources": "2.0.1"
|
"@opentelemetry/resources": "2.0.1"
|
||||||
@@ -2917,6 +2924,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
|
||||||
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
|
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.0.1",
|
"@opentelemetry/core": "2.0.1",
|
||||||
"@opentelemetry/resources": "2.0.1",
|
"@opentelemetry/resources": "2.0.1",
|
||||||
@@ -4102,6 +4110,16 @@
|
|||||||
"kleur": "^3.0.3"
|
"kleur": "^3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/proper-lockfile": {
|
||||||
|
"version": "4.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/proper-lockfile/-/proper-lockfile-4.1.4.tgz",
|
||||||
|
"integrity": "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/retry": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/qs": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||||
@@ -4122,6 +4140,7 @@
|
|||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -4196,6 +4215,13 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/retry": {
|
||||||
|
"version": "0.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz",
|
||||||
|
"integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/sarif": {
|
"node_modules/@types/sarif": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz",
|
||||||
@@ -4326,6 +4352,16 @@
|
|||||||
"boxen": "^7.1.1"
|
"boxen": "^7.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
@@ -4399,6 +4435,7 @@
|
|||||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.35.0",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.35.0",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
@@ -5391,6 +5428,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -8400,6 +8438,7 @@
|
|||||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -8940,6 +8979,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -10541,6 +10581,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.8.tgz",
|
||||||
"integrity": "sha512-v0thcXIKl9hqF/1w4HqA6MKxIcMoWSP3YtEZIAA+eeJngXpN5lGnMkb6rllB7FnOdwyEyYaFTcu1ZVr4/JZpWQ==",
|
"integrity": "sha512-v0thcXIKl9hqF/1w4HqA6MKxIcMoWSP3YtEZIAA+eeJngXpN5lGnMkb6rllB7FnOdwyEyYaFTcu1ZVr4/JZpWQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||||
"ansi-escapes": "^7.0.0",
|
"ansi-escapes": "^7.0.0",
|
||||||
@@ -14040,6 +14081,32 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proper-lockfile": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"retry": "^0.12.0",
|
||||||
|
"signal-exit": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proper-lockfile/node_modules/retry": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proper-lockfile/node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/proto-list": {
|
"node_modules/proto-list": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
@@ -14299,6 +14366,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -14309,6 +14377,7 @@
|
|||||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shell-quote": "^1.6.1",
|
"shell-quote": "^1.6.1",
|
||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
@@ -16545,6 +16614,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16768,7 +16838,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.20.3",
|
"version": "4.20.3",
|
||||||
@@ -16776,6 +16847,7 @@
|
|||||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.25.0",
|
"esbuild": "~0.25.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
@@ -16948,6 +17020,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -17155,6 +17228,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -17268,6 +17342,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -17280,6 +17355,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/expect": "3.2.4",
|
"@vitest/expect": "3.2.4",
|
||||||
@@ -17984,6 +18060,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -18083,6 +18160,7 @@
|
|||||||
"mnemonist": "^0.40.3",
|
"mnemonist": "^0.40.3",
|
||||||
"open": "^10.1.2",
|
"open": "^10.1.2",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"read-package-up": "^11.0.0",
|
"read-package-up": "^11.0.0",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
@@ -18184,6 +18262,7 @@
|
|||||||
"mnemonist": "^0.40.3",
|
"mnemonist": "^0.40.3",
|
||||||
"open": "^10.1.2",
|
"open": "^10.1.2",
|
||||||
"picomatch": "^4.0.1",
|
"picomatch": "^4.0.1",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"read-package-up": "^11.0.0",
|
"read-package-up": "^11.0.0",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
"simple-git": "^3.28.0",
|
"simple-git": "^3.28.0",
|
||||||
@@ -18278,6 +18357,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -86,9 +86,11 @@
|
|||||||
"@types/minimatch": "^5.1.2",
|
"@types/minimatch": "^5.1.2",
|
||||||
"@types/mock-fs": "^4.13.4",
|
"@types/mock-fs": "^4.13.4",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
|
"@types/proper-lockfile": "^4.1.4",
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
"@types/shell-quote": "^1.7.5",
|
"@types/shell-quote": "^1.7.5",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
"@vitest/coverage-v8": "^3.1.1",
|
"@vitest/coverage-v8": "^3.1.1",
|
||||||
"@vitest/eslint-plugin": "^1.3.4",
|
"@vitest/eslint-plugin": "^1.3.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"mnemonist": "^0.40.3",
|
"mnemonist": "^0.40.3",
|
||||||
"open": "^10.1.2",
|
"open": "^10.1.2",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"read-package-up": "^11.0.0",
|
"read-package-up": "^11.0.0",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
|
|||||||
@@ -186,7 +186,10 @@ export class ExtensionManager extends ExtensionLoader {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const trustedFolders = loadTrustedFolders();
|
const trustedFolders = loadTrustedFolders();
|
||||||
trustedFolders.setValue(this.workspaceDir, TrustLevel.TRUST_FOLDER);
|
await trustedFolders.setValue(
|
||||||
|
this.workspaceDir,
|
||||||
|
TrustLevel.TRUST_FOLDER,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not install extension because the current workspace at ${this.workspaceDir} is not trusted.`,
|
`Could not install extension because the current workspace at ${this.workspaceDir} is not trusted.`,
|
||||||
|
|||||||
@@ -5,23 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import * as path from 'node:path';
|
|
||||||
import * as os from 'node:os';
|
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import { getMissingSettings } from './extensionSettings.js';
|
import { getMissingSettings } from './extensionSettings.js';
|
||||||
import type { ExtensionConfig } from '../extension.js';
|
import type { ExtensionConfig } from '../extension.js';
|
||||||
import { ExtensionStorage } from './storage.js';
|
|
||||||
import {
|
import {
|
||||||
KeychainTokenStorage,
|
|
||||||
debugLogger,
|
debugLogger,
|
||||||
type ExtensionInstallMetadata,
|
type ExtensionInstallMetadata,
|
||||||
type GeminiCLIExtension,
|
type GeminiCLIExtension,
|
||||||
coreEvents,
|
coreEvents,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import { EXTENSION_SETTINGS_FILENAME } from './variables.js';
|
|
||||||
import { ExtensionManager } from '../extension-manager.js';
|
import { ExtensionManager } from '../extension-manager.js';
|
||||||
import { createTestMergedSettings } from '../settings.js';
|
import { createTestMergedSettings } from '../settings.js';
|
||||||
|
|
||||||
|
// --- Mocks ---
|
||||||
|
|
||||||
vi.mock('node:fs', async (importOriginal) => {
|
vi.mock('node:fs', async (importOriginal) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const actual = await importOriginal<any>();
|
const actual = await importOriginal<any>();
|
||||||
@@ -29,11 +26,23 @@ vi.mock('node:fs', async (importOriginal) => {
|
|||||||
...actual,
|
...actual,
|
||||||
default: {
|
default: {
|
||||||
...actual.default,
|
...actual.default,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
existsSync: vi.fn(),
|
||||||
existsSync: vi.fn((...args: any[]) => actual.existsSync(...args)),
|
statSync: vi.fn(),
|
||||||
|
lstatSync: vi.fn(),
|
||||||
|
realpathSync: vi.fn((p) => p),
|
||||||
|
},
|
||||||
|
existsSync: vi.fn(),
|
||||||
|
statSync: vi.fn(),
|
||||||
|
lstatSync: vi.fn(),
|
||||||
|
realpathSync: vi.fn((p) => p),
|
||||||
|
promises: {
|
||||||
|
...actual.promises,
|
||||||
|
mkdir: vi.fn(),
|
||||||
|
writeFile: vi.fn(),
|
||||||
|
rm: vi.fn(),
|
||||||
|
cp: vi.fn(),
|
||||||
|
readFile: vi.fn(),
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
existsSync: vi.fn((...args: any[]) => actual.existsSync(...args)),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,183 +58,93 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|||||||
log: vi.fn(),
|
log: vi.fn(),
|
||||||
},
|
},
|
||||||
coreEvents: {
|
coreEvents: {
|
||||||
emitFeedback: vi.fn(), // Mock emitFeedback
|
emitFeedback: vi.fn(),
|
||||||
on: vi.fn(),
|
on: vi.fn(),
|
||||||
off: vi.fn(),
|
off: vi.fn(),
|
||||||
|
emitConsoleLog: vi.fn(),
|
||||||
},
|
},
|
||||||
|
loadSkillsFromDir: vi.fn().mockResolvedValue([]),
|
||||||
|
loadAgentsFromDirectory: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ agents: [], errors: [] }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock os.homedir because ExtensionStorage uses it
|
vi.mock('./consent.js', () => ({
|
||||||
|
maybeRequestConsentOrFail: vi.fn().mockResolvedValue(undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./extensionSettings.js', async (importOriginal) => {
|
||||||
|
const actual =
|
||||||
|
await importOriginal<typeof import('./extensionSettings.js')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
getEnvContents: vi.fn().mockResolvedValue({}),
|
||||||
|
getMissingSettings: vi.fn(), // We will mock this implementation per test
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../trustedFolders.js', () => ({
|
||||||
|
isWorkspaceTrusted: vi.fn().mockReturnValue({ isTrusted: true }), // Default to trusted to simplify flow
|
||||||
|
loadTrustedFolders: vi.fn().mockReturnValue({
|
||||||
|
setValue: vi.fn().mockResolvedValue(undefined),
|
||||||
|
}),
|
||||||
|
TrustLevel: { TRUST_FOLDER: 'TRUST_FOLDER' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock ExtensionStorage to avoid real FS paths
|
||||||
|
vi.mock('./storage.js', () => ({
|
||||||
|
ExtensionStorage: class {
|
||||||
|
constructor(public name: string) {}
|
||||||
|
getExtensionDir() {
|
||||||
|
return `/mock/extensions/${this.name}`;
|
||||||
|
}
|
||||||
|
static getUserExtensionsDir() {
|
||||||
|
return '/mock/extensions';
|
||||||
|
}
|
||||||
|
static createTmpDir() {
|
||||||
|
return Promise.resolve('/mock/tmp');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('os', async (importOriginal) => {
|
vi.mock('os', async (importOriginal) => {
|
||||||
const mockedOs = await importOriginal<typeof os>();
|
const mockedOs = await importOriginal<typeof import('node:os')>();
|
||||||
return {
|
return {
|
||||||
...mockedOs,
|
...mockedOs,
|
||||||
homedir: vi.fn(),
|
homedir: vi.fn().mockReturnValue('/mock/home'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extensionUpdates', () => {
|
describe('extensionUpdates', () => {
|
||||||
let tempHomeDir: string;
|
|
||||||
let tempWorkspaceDir: string;
|
let tempWorkspaceDir: string;
|
||||||
let extensionDir: string;
|
|
||||||
let mockKeychainData: Record<string, Record<string, string>>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
mockKeychainData = {};
|
// Default fs mocks
|
||||||
|
vi.mocked(fs.promises.mkdir).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(fs.promises.writeFile).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(fs.promises.rm).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(fs.promises.cp).mockResolvedValue(undefined);
|
||||||
|
|
||||||
// Mock Keychain
|
// Allow directories to exist by default to satisfy Config/WorkspaceContext checks
|
||||||
vi.mocked(KeychainTokenStorage).mockImplementation(
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||||
(serviceName: string) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if (!mockKeychainData[serviceName]) {
|
vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any);
|
||||||
mockKeychainData[serviceName] = {};
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}
|
vi.mocked(fs.lstatSync).mockReturnValue({ isDirectory: () => true } as any);
|
||||||
const keychainData = mockKeychainData[serviceName];
|
vi.mocked(fs.realpathSync).mockImplementation((p) => p as string);
|
||||||
return {
|
|
||||||
getSecret: vi
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(
|
|
||||||
async (key: string) => keychainData[key] || null,
|
|
||||||
),
|
|
||||||
setSecret: vi
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(async (key: string, value: string) => {
|
|
||||||
keychainData[key] = value;
|
|
||||||
}),
|
|
||||||
deleteSecret: vi.fn().mockImplementation(async (key: string) => {
|
|
||||||
delete keychainData[key];
|
|
||||||
}),
|
|
||||||
listSecrets: vi
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(async () => Object.keys(keychainData)),
|
|
||||||
isAvailable: vi.fn().mockResolvedValue(true),
|
|
||||||
} as unknown as KeychainTokenStorage;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup Temp Dirs
|
tempWorkspaceDir = '/mock/workspace';
|
||||||
tempHomeDir = fs.mkdtempSync(
|
|
||||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
|
||||||
);
|
|
||||||
tempWorkspaceDir = fs.mkdtempSync(
|
|
||||||
path.join(os.tmpdir(), 'gemini-cli-test-workspace-'),
|
|
||||||
);
|
|
||||||
extensionDir = path.join(tempHomeDir, '.gemini', 'extensions', 'test-ext');
|
|
||||||
|
|
||||||
// Mock ExtensionStorage to rely on our temp extension dir
|
|
||||||
vi.spyOn(ExtensionStorage.prototype, 'getExtensionDir').mockReturnValue(
|
|
||||||
extensionDir,
|
|
||||||
);
|
|
||||||
// Mock getEnvFilePath is checking extensionDir/variables.env? No, it used ExtensionStorage logic.
|
|
||||||
// getEnvFilePath in extensionSettings.ts:
|
|
||||||
// if workspace, process.cwd()/.env (we need to mock process.cwd or move tempWorkspaceDir there)
|
|
||||||
// if user, ExtensionStorage(name).getEnvFilePath() -> joins extensionDir + '.env'
|
|
||||||
|
|
||||||
fs.mkdirSync(extensionDir, { recursive: true });
|
|
||||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
|
||||||
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
||||||
fs.rmSync(tempWorkspaceDir, { recursive: true, force: true });
|
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMissingSettings', () => {
|
|
||||||
it('should return empty list if all settings are present', async () => {
|
|
||||||
const config: ExtensionConfig = {
|
|
||||||
name: 'test-ext',
|
|
||||||
version: '1.0.0',
|
|
||||||
settings: [
|
|
||||||
{ name: 's1', description: 'd1', envVar: 'VAR1' },
|
|
||||||
{ name: 's2', description: 'd2', envVar: 'VAR2', sensitive: true },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const extensionId = '12345';
|
|
||||||
|
|
||||||
// Setup User Env
|
|
||||||
const userEnvPath = path.join(extensionDir, EXTENSION_SETTINGS_FILENAME);
|
|
||||||
fs.writeFileSync(userEnvPath, 'VAR1=val1');
|
|
||||||
|
|
||||||
// Setup Keychain
|
|
||||||
const userKeychain = new KeychainTokenStorage(
|
|
||||||
`Gemini CLI Extensions test-ext ${extensionId}`,
|
|
||||||
);
|
|
||||||
await userKeychain.setSecret('VAR2', 'val2');
|
|
||||||
|
|
||||||
const missing = await getMissingSettings(
|
|
||||||
config,
|
|
||||||
extensionId,
|
|
||||||
tempWorkspaceDir,
|
|
||||||
);
|
|
||||||
expect(missing).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should identify missing non-sensitive settings', async () => {
|
|
||||||
const config: ExtensionConfig = {
|
|
||||||
name: 'test-ext',
|
|
||||||
version: '1.0.0',
|
|
||||||
settings: [{ name: 's1', description: 'd1', envVar: 'VAR1' }],
|
|
||||||
};
|
|
||||||
const extensionId = '12345';
|
|
||||||
|
|
||||||
const missing = await getMissingSettings(
|
|
||||||
config,
|
|
||||||
extensionId,
|
|
||||||
tempWorkspaceDir,
|
|
||||||
);
|
|
||||||
expect(missing).toHaveLength(1);
|
|
||||||
expect(missing[0].name).toBe('s1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should identify missing sensitive settings', async () => {
|
|
||||||
const config: ExtensionConfig = {
|
|
||||||
name: 'test-ext',
|
|
||||||
version: '1.0.0',
|
|
||||||
settings: [
|
|
||||||
{ name: 's2', description: 'd2', envVar: 'VAR2', sensitive: true },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const extensionId = '12345';
|
|
||||||
|
|
||||||
const missing = await getMissingSettings(
|
|
||||||
config,
|
|
||||||
extensionId,
|
|
||||||
tempWorkspaceDir,
|
|
||||||
);
|
|
||||||
expect(missing).toHaveLength(1);
|
|
||||||
expect(missing[0].name).toBe('s2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should respect settings present in workspace', async () => {
|
|
||||||
const config: ExtensionConfig = {
|
|
||||||
name: 'test-ext',
|
|
||||||
version: '1.0.0',
|
|
||||||
settings: [{ name: 's1', description: 'd1', envVar: 'VAR1' }],
|
|
||||||
};
|
|
||||||
const extensionId = '12345';
|
|
||||||
|
|
||||||
// Setup Workspace Env
|
|
||||||
const workspaceEnvPath = path.join(
|
|
||||||
tempWorkspaceDir,
|
|
||||||
EXTENSION_SETTINGS_FILENAME,
|
|
||||||
);
|
|
||||||
fs.writeFileSync(workspaceEnvPath, 'VAR1=val1');
|
|
||||||
|
|
||||||
const missing = await getMissingSettings(
|
|
||||||
config,
|
|
||||||
extensionId,
|
|
||||||
tempWorkspaceDir,
|
|
||||||
);
|
|
||||||
expect(missing).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ExtensionManager integration', () => {
|
describe('ExtensionManager integration', () => {
|
||||||
it('should warn about missing settings after update', async () => {
|
it('should warn about missing settings after update', async () => {
|
||||||
// Mock ExtensionManager methods to avoid FS/Network usage
|
// 1. Setup Data
|
||||||
const newConfig: ExtensionConfig = {
|
const newConfig: ExtensionConfig = {
|
||||||
name: 'test-ext',
|
name: 'test-ext',
|
||||||
version: '1.1.0',
|
version: '1.1.0',
|
||||||
@@ -239,31 +158,30 @@ describe('extensionUpdates', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const installMetadata: ExtensionInstallMetadata = {
|
const installMetadata: ExtensionInstallMetadata = {
|
||||||
source: extensionDir,
|
source: '/mock/source',
|
||||||
type: 'local',
|
type: 'local',
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 2. Setup Manager
|
||||||
const manager = new ExtensionManager({
|
const manager = new ExtensionManager({
|
||||||
workspaceDir: tempWorkspaceDir,
|
workspaceDir: tempWorkspaceDir,
|
||||||
|
|
||||||
settings: createTestMergedSettings({
|
settings: createTestMergedSettings({
|
||||||
telemetry: { enabled: false },
|
telemetry: { enabled: false },
|
||||||
experimental: { extensionConfig: true },
|
experimental: { extensionConfig: true },
|
||||||
}),
|
}),
|
||||||
requestConsent: vi.fn().mockResolvedValue(true),
|
requestConsent: vi.fn().mockResolvedValue(true),
|
||||||
requestSetting: null, // Simulate non-interactive
|
requestSetting: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock methods called by installOrUpdateExtension
|
// 3. Mock Internal Manager Methods
|
||||||
vi.spyOn(manager, 'loadExtensionConfig').mockResolvedValue(newConfig);
|
vi.spyOn(manager, 'loadExtensionConfig').mockResolvedValue(newConfig);
|
||||||
vi.spyOn(manager, 'getExtensions').mockReturnValue([
|
vi.spyOn(manager, 'getExtensions').mockReturnValue([
|
||||||
{
|
{
|
||||||
name: 'test-ext',
|
name: 'test-ext',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
installMetadata,
|
installMetadata,
|
||||||
path: extensionDir,
|
path: '/mock/extensions/test-ext',
|
||||||
// Mocks for other required props
|
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
mcpServers: {},
|
mcpServers: {},
|
||||||
hooks: undefined,
|
hooks: undefined,
|
||||||
@@ -275,23 +193,28 @@ describe('extensionUpdates', () => {
|
|||||||
} as unknown as GeminiCLIExtension,
|
} as unknown as GeminiCLIExtension,
|
||||||
]);
|
]);
|
||||||
vi.spyOn(manager, 'uninstallExtension').mockResolvedValue(undefined);
|
vi.spyOn(manager, 'uninstallExtension').mockResolvedValue(undefined);
|
||||||
|
// Mock loadExtension to return something so the method doesn't crash at the end
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
vi.spyOn(manager as any, 'loadExtension').mockResolvedValue(
|
vi.spyOn(manager as any, 'loadExtension').mockResolvedValue({
|
||||||
{} as unknown as GeminiCLIExtension,
|
name: 'test-ext',
|
||||||
);
|
version: '1.1.0',
|
||||||
vi.spyOn(manager, 'enableExtension').mockResolvedValue(undefined);
|
} as GeminiCLIExtension);
|
||||||
|
|
||||||
// Mock fs.promises for the operations inside installOrUpdateExtension
|
// 4. Mock External Helpers
|
||||||
vi.spyOn(fs.promises, 'mkdir').mockResolvedValue(undefined);
|
// This is the key fix: we explicitly mock `getMissingSettings` to return
|
||||||
vi.spyOn(fs.promises, 'writeFile').mockResolvedValue(undefined);
|
// the result we expect, avoiding any real FS or logic execution during the update.
|
||||||
vi.spyOn(fs.promises, 'rm').mockResolvedValue(undefined);
|
vi.mocked(getMissingSettings).mockResolvedValue([
|
||||||
vi.mocked(fs.existsSync).mockReturnValue(false); // No hooks
|
{
|
||||||
try {
|
name: 's1',
|
||||||
await manager.installOrUpdateExtension(installMetadata, previousConfig);
|
description: 'd1',
|
||||||
} catch (_) {
|
envVar: 'VAR1',
|
||||||
// Ignore errors from copyExtension or others, we just want to verify the warning
|
},
|
||||||
}
|
]);
|
||||||
|
|
||||||
|
// 5. Execute
|
||||||
|
await manager.installOrUpdateExtension(installMetadata, previousConfig);
|
||||||
|
|
||||||
|
// 6. Assert
|
||||||
expect(debugLogger.warn).toHaveBeenCalledWith(
|
expect(debugLogger.warn).toHaveBeenCalledWith(
|
||||||
expect.stringContaining(
|
expect.stringContaining(
|
||||||
'Extension "test-ext" has missing settings: s1',
|
'Extension "test-ext" has missing settings: s1',
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
|
import * as crypto from 'node:crypto';
|
||||||
|
import { lock } from 'proper-lockfile';
|
||||||
import {
|
import {
|
||||||
FatalConfigError,
|
FatalConfigError,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
@@ -13,10 +15,13 @@ import {
|
|||||||
ideContextStore,
|
ideContextStore,
|
||||||
GEMINI_DIR,
|
GEMINI_DIR,
|
||||||
homedir,
|
homedir,
|
||||||
|
coreEvents,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import type { Settings } from './settings.js';
|
import type { Settings } from './settings.js';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
|
|
||||||
|
const { promises: fsPromises } = fs;
|
||||||
|
|
||||||
export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json';
|
export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json';
|
||||||
|
|
||||||
export function getUserSettingsDir(): string {
|
export function getUserSettingsDir(): string {
|
||||||
@@ -67,6 +72,13 @@ export interface TrustResult {
|
|||||||
|
|
||||||
const realPathCache = new Map<string, string>();
|
const realPathCache = new Map<string, string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the trusted folders JSON content, stripping comments.
|
||||||
|
*/
|
||||||
|
function parseTrustedFoldersJson(content: string): unknown {
|
||||||
|
return JSON.parse(stripJsonComments(content));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FOR TESTING PURPOSES ONLY.
|
* FOR TESTING PURPOSES ONLY.
|
||||||
* Clears the real path cache.
|
* Clears the real path cache.
|
||||||
@@ -150,19 +162,67 @@ export class LoadedTrustedFolders {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(path: string, trustLevel: TrustLevel): void {
|
async setValue(folderPath: string, trustLevel: TrustLevel): Promise<void> {
|
||||||
const originalTrustLevel = this.user.config[path];
|
if (this.errors.length > 0) {
|
||||||
this.user.config[path] = trustLevel;
|
const errorMessages = this.errors.map(
|
||||||
|
(error) => `Error in ${error.path}: ${error.message}`,
|
||||||
|
);
|
||||||
|
throw new FatalConfigError(
|
||||||
|
`Cannot update trusted folders because the configuration file is invalid:\n${errorMessages.join('\n')}\nPlease fix the file manually before trying to update it.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirPath = path.dirname(this.user.path);
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
await fsPromises.mkdir(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockfile requires the file to exist
|
||||||
|
if (!fs.existsSync(this.user.path)) {
|
||||||
|
await fsPromises.writeFile(this.user.path, JSON.stringify({}, null, 2), {
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await lock(this.user.path, {
|
||||||
|
retries: {
|
||||||
|
retries: 10,
|
||||||
|
minTimeout: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveTrustedFolders(this.user);
|
// Re-read the file to handle concurrent updates
|
||||||
} catch (e) {
|
const content = await fsPromises.readFile(this.user.path, 'utf-8');
|
||||||
// Revert the in-memory change if the save failed.
|
let config: Record<string, TrustLevel>;
|
||||||
if (originalTrustLevel === undefined) {
|
try {
|
||||||
delete this.user.config[path];
|
config = parseTrustedFoldersJson(content) as Record<string, TrustLevel>;
|
||||||
} else {
|
} catch (error) {
|
||||||
this.user.config[path] = originalTrustLevel;
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
`Failed to parse trusted folders file at ${this.user.path}. The file may be corrupted.`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
config = {};
|
||||||
}
|
}
|
||||||
throw e;
|
|
||||||
|
const originalTrustLevel = config[folderPath];
|
||||||
|
config[folderPath] = trustLevel;
|
||||||
|
this.user.config[folderPath] = trustLevel;
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveTrustedFolders({ ...this.user, config });
|
||||||
|
} catch (e) {
|
||||||
|
// Revert the in-memory change if the save failed.
|
||||||
|
if (originalTrustLevel === undefined) {
|
||||||
|
delete this.user.config[folderPath];
|
||||||
|
} else {
|
||||||
|
this.user.config[folderPath] = originalTrustLevel;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,10 +250,7 @@ export function loadTrustedFolders(): LoadedTrustedFolders {
|
|||||||
try {
|
try {
|
||||||
if (fs.existsSync(userPath)) {
|
if (fs.existsSync(userPath)) {
|
||||||
const content = fs.readFileSync(userPath, 'utf-8');
|
const content = fs.readFileSync(userPath, 'utf-8');
|
||||||
const parsed = JSON.parse(stripJsonComments(content)) as Record<
|
const parsed = parseTrustedFoldersJson(content) as Record<string, string>;
|
||||||
string,
|
|
||||||
string
|
|
||||||
>;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof parsed !== 'object' ||
|
typeof parsed !== 'object' ||
|
||||||
@@ -241,11 +298,26 @@ export function saveTrustedFolders(
|
|||||||
fs.mkdirSync(dirPath, { recursive: true });
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
const content = JSON.stringify(trustedFoldersFile.config, null, 2);
|
||||||
trustedFoldersFile.path,
|
const tempPath = `${trustedFoldersFile.path}.tmp.${crypto.randomUUID()}`;
|
||||||
JSON.stringify(trustedFoldersFile.config, null, 2),
|
|
||||||
{ encoding: 'utf-8', mode: 0o600 },
|
try {
|
||||||
);
|
fs.writeFileSync(tempPath, content, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
fs.renameSync(tempPath, trustedFoldersFile.path);
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up temp file if it was created but rename failed
|
||||||
|
if (fs.existsSync(tempPath)) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(tempPath);
|
||||||
|
} catch {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Is folder trust feature enabled per the current applied settings */
|
/** Is folder trust feature enabled per the current applied settings */
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe('ConsentPrompt', () => {
|
|||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onConfirm with true when "Yes" is selected', () => {
|
it('calls onConfirm with true when "Yes" is selected', async () => {
|
||||||
const prompt = 'Are you sure?';
|
const prompt = 'Are you sure?';
|
||||||
const { unmount } = render(
|
const { unmount } = render(
|
||||||
<ConsentPrompt
|
<ConsentPrompt
|
||||||
@@ -78,7 +78,7 @@ describe('ConsentPrompt', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
|
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
|
||||||
act(() => {
|
await act(async () => {
|
||||||
onSelect(true);
|
onSelect(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ describe('ConsentPrompt', () => {
|
|||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onConfirm with false when "No" is selected', () => {
|
it('calls onConfirm with false when "No" is selected', async () => {
|
||||||
const prompt = 'Are you sure?';
|
const prompt = 'Are you sure?';
|
||||||
const { unmount } = render(
|
const { unmount } = render(
|
||||||
<ConsentPrompt
|
<ConsentPrompt
|
||||||
@@ -97,7 +97,7 @@ describe('ConsentPrompt', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
|
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
|
||||||
act(() => {
|
await act(async () => {
|
||||||
onSelect(false);
|
onSelect(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -46,22 +46,26 @@ describe('LogoutConfirmationDialog', () => {
|
|||||||
expect(mockCall.isFocused).toBe(true);
|
expect(mockCall.isFocused).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onSelect with LOGIN when Login is selected', () => {
|
it('should call onSelect with LOGIN when Login is selected', async () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
renderWithProviders(<LogoutConfirmationDialog onSelect={onSelect} />);
|
renderWithProviders(<LogoutConfirmationDialog onSelect={onSelect} />);
|
||||||
|
|
||||||
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
|
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
|
||||||
mockCall.onSelect(LogoutChoice.LOGIN);
|
await act(async () => {
|
||||||
|
mockCall.onSelect(LogoutChoice.LOGIN);
|
||||||
|
});
|
||||||
|
|
||||||
expect(onSelect).toHaveBeenCalledWith(LogoutChoice.LOGIN);
|
expect(onSelect).toHaveBeenCalledWith(LogoutChoice.LOGIN);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onSelect with EXIT when Exit is selected', () => {
|
it('should call onSelect with EXIT when Exit is selected', async () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
renderWithProviders(<LogoutConfirmationDialog onSelect={onSelect} />);
|
renderWithProviders(<LogoutConfirmationDialog onSelect={onSelect} />);
|
||||||
|
|
||||||
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
|
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
|
||||||
mockCall.onSelect(LogoutChoice.EXIT);
|
await act(async () => {
|
||||||
|
mockCall.onSelect(LogoutChoice.EXIT);
|
||||||
|
});
|
||||||
|
|
||||||
expect(onSelect).toHaveBeenCalledWith(LogoutChoice.EXIT);
|
expect(onSelect).toHaveBeenCalledWith(LogoutChoice.EXIT);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -125,7 +125,10 @@ export const MultiFolderTrustDialog: React.FC<MultiFolderTrustDialogProps> = ({
|
|||||||
try {
|
try {
|
||||||
const expandedPath = path.resolve(expandHomeDir(dir));
|
const expandedPath = path.resolve(expandHomeDir(dir));
|
||||||
if (choice === MultiFolderTrustChoice.YES_AND_REMEMBER) {
|
if (choice === MultiFolderTrustChoice.YES_AND_REMEMBER) {
|
||||||
trustedFolders.setValue(expandedPath, TrustLevel.TRUST_FOLDER);
|
await trustedFolders.setValue(
|
||||||
|
expandedPath,
|
||||||
|
TrustLevel.TRUST_FOLDER,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
workspaceContext.addDirectory(expandedPath);
|
workspaceContext.addDirectory(expandedPath);
|
||||||
added.push(dir);
|
added.push(dir);
|
||||||
|
|||||||
@@ -69,13 +69,14 @@ export function PermissionsModifyTrustDialog({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (needsRestart && key.name === 'r') {
|
if (needsRestart && key.name === 'r') {
|
||||||
const success = commitTrustLevelChange();
|
void (async () => {
|
||||||
if (success) {
|
const success = await commitTrustLevelChange();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
if (success) {
|
||||||
relaunchApp();
|
void relaunchApp();
|
||||||
} else {
|
} else {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -149,7 +149,9 @@ describe('useFolderTrust', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.TRUST_FOLDER,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -173,7 +175,9 @@ describe('useFolderTrust', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_PARENT);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.TRUST_PARENT,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -197,7 +201,9 @@ describe('useFolderTrust', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.DO_NOT_TRUST);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.DO_NOT_TRUST,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -221,7 +227,7 @@ describe('useFolderTrust', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(
|
await result.current.handleFolderTrustSelect(
|
||||||
'invalid_choice' as FolderTrustChoice,
|
'invalid_choice' as FolderTrustChoice,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -253,7 +259,9 @@ describe('useFolderTrust', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.TRUST_FOLDER,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -272,7 +280,9 @@ describe('useFolderTrust', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.TRUST_FOLDER,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -294,8 +304,10 @@ describe('useFolderTrust', () => {
|
|||||||
useFolderTrust(mockSettings, onTrustChange, addItem),
|
useFolderTrust(mockSettings, onTrustChange, addItem),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
await result.current.handleFolderTrustSelect(
|
||||||
|
FolderTrustChoice.TRUST_FOLDER,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const useFolderTrust = (
|
|||||||
}, [folderTrust, onTrustChange, settings.merged, addItem]);
|
}, [folderTrust, onTrustChange, settings.merged, addItem]);
|
||||||
|
|
||||||
const handleFolderTrustSelect = useCallback(
|
const handleFolderTrustSelect = useCallback(
|
||||||
(choice: FolderTrustChoice) => {
|
async (choice: FolderTrustChoice) => {
|
||||||
const trustLevelMap: Record<FolderTrustChoice, TrustLevel> = {
|
const trustLevelMap: Record<FolderTrustChoice, TrustLevel> = {
|
||||||
[FolderTrustChoice.TRUST_FOLDER]: TrustLevel.TRUST_FOLDER,
|
[FolderTrustChoice.TRUST_FOLDER]: TrustLevel.TRUST_FOLDER,
|
||||||
[FolderTrustChoice.TRUST_PARENT]: TrustLevel.TRUST_PARENT,
|
[FolderTrustChoice.TRUST_PARENT]: TrustLevel.TRUST_PARENT,
|
||||||
@@ -62,7 +62,7 @@ export const useFolderTrust = (
|
|||||||
const trustedFolders = loadTrustedFolders();
|
const trustedFolders = loadTrustedFolders();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustedFolders.setValue(cwd, trustLevel);
|
await trustedFolders.setValue(cwd, trustLevel);
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
coreEvents.emitFeedback(
|
coreEvents.emitFeedback(
|
||||||
'error',
|
'error',
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
expect(result.current.isInheritedTrustFromParent).toBe(false);
|
expect(result.current.isInheritedTrustFromParent).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set needsRestart but not save when trust changes', () => {
|
it('should set needsRestart but not save when trust changes', async () => {
|
||||||
const mockSetValue = vi.fn();
|
const mockSetValue = vi.fn();
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
@@ -157,15 +157,15 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.needsRestart).toBe(true);
|
expect(result.current.needsRestart).toBe(true);
|
||||||
expect(mockSetValue).not.toHaveBeenCalled();
|
expect(mockSetValue).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save immediately if trust does not change', () => {
|
it('should save immediately if trust does not change', async () => {
|
||||||
const mockSetValue = vi.fn();
|
const mockSetValue = vi.fn();
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
@@ -181,8 +181,8 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_PARENT);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_PARENT);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.needsRestart).toBe(false);
|
expect(result.current.needsRestart).toBe(false);
|
||||||
@@ -193,7 +193,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
expect(mockOnExit).toHaveBeenCalled();
|
expect(mockOnExit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should commit the pending trust level change', () => {
|
it('should commit the pending trust level change', async () => {
|
||||||
const mockSetValue = vi.fn();
|
const mockSetValue = vi.fn();
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
@@ -208,14 +208,14 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.needsRestart).toBe(true);
|
expect(result.current.needsRestart).toBe(true);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.commitTrustLevelChange();
|
await result.current.commitTrustLevelChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSetValue).toHaveBeenCalledWith(
|
expect(mockSetValue).toHaveBeenCalledWith(
|
||||||
@@ -224,7 +224,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add warning when setting DO_NOT_TRUST but still trusted by parent', () => {
|
it('should add warning when setting DO_NOT_TRUST but still trusted by parent', async () => {
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
setValue: vi.fn(),
|
setValue: vi.fn(),
|
||||||
@@ -238,8 +238,8 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
await result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockAddItem).toHaveBeenCalledWith(
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
@@ -251,7 +251,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add warning when setting DO_NOT_TRUST but still trusted by IDE', () => {
|
it('should add warning when setting DO_NOT_TRUST but still trusted by IDE', async () => {
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
setValue: vi.fn(),
|
setValue: vi.fn(),
|
||||||
@@ -265,8 +265,8 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
await result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockAddItem).toHaveBeenCalledWith(
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
@@ -299,7 +299,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
expect(result.current.isInheritedTrustFromIde).toBe(false);
|
expect(result.current.isInheritedTrustFromIde).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save immediately without needing a restart', () => {
|
it('should save immediately without needing a restart', async () => {
|
||||||
const mockSetValue = vi.fn();
|
const mockSetValue = vi.fn();
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
@@ -314,8 +314,8 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.needsRestart).toBe(false);
|
expect(result.current.needsRestart).toBe(false);
|
||||||
@@ -326,7 +326,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
expect(mockOnExit).toHaveBeenCalled();
|
expect(mockOnExit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add a warning when setting DO_NOT_TRUST', () => {
|
it('should not add a warning when setting DO_NOT_TRUST', async () => {
|
||||||
mockedLoadTrustedFolders.mockReturnValue({
|
mockedLoadTrustedFolders.mockReturnValue({
|
||||||
user: { config: {} },
|
user: { config: {} },
|
||||||
setValue: vi.fn(),
|
setValue: vi.fn(),
|
||||||
@@ -340,15 +340,15 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
await result.current.updateTrustLevel(TrustLevel.DO_NOT_TRUST);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockAddItem).not.toHaveBeenCalled();
|
expect(mockAddItem).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit feedback when setValue throws in updateTrustLevel', () => {
|
it('should emit feedback when setValue throws in updateTrustLevel', async () => {
|
||||||
const mockSetValue = vi.fn().mockImplementation(() => {
|
const mockSetValue = vi.fn().mockImplementation(() => {
|
||||||
throw new Error('test error');
|
throw new Error('test error');
|
||||||
});
|
});
|
||||||
@@ -368,8 +368,8 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_PARENT);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_PARENT);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(emitFeedbackSpy).toHaveBeenCalledWith(
|
expect(emitFeedbackSpy).toHaveBeenCalledWith(
|
||||||
@@ -379,7 +379,7 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
expect(mockOnExit).toHaveBeenCalled();
|
expect(mockOnExit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit feedback when setValue throws in commitTrustLevelChange', () => {
|
it('should emit feedback when setValue throws in commitTrustLevelChange', async () => {
|
||||||
const mockSetValue = vi.fn().mockImplementation(() => {
|
const mockSetValue = vi.fn().mockImplementation(() => {
|
||||||
throw new Error('test error');
|
throw new Error('test error');
|
||||||
});
|
});
|
||||||
@@ -398,12 +398,12 @@ describe('usePermissionsModifyTrust', () => {
|
|||||||
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
await result.current.updateTrustLevel(TrustLevel.TRUST_FOLDER);
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
const success = result.current.commitTrustLevelChange();
|
const success = await result.current.commitTrustLevelChange();
|
||||||
expect(success).toBe(false);
|
expect(success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -92,12 +92,12 @@ export const usePermissionsModifyTrust = (
|
|||||||
settings.merged.security.folderTrust.enabled ?? true;
|
settings.merged.security.folderTrust.enabled ?? true;
|
||||||
|
|
||||||
const updateTrustLevel = useCallback(
|
const updateTrustLevel = useCallback(
|
||||||
(trustLevel: TrustLevel) => {
|
async (trustLevel: TrustLevel) => {
|
||||||
// If we are not editing the current workspace, the logic is simple:
|
// If we are not editing the current workspace, the logic is simple:
|
||||||
// just save the setting and exit. No restart or warnings are needed.
|
// just save the setting and exit. No restart or warnings are needed.
|
||||||
if (!isCurrentWorkspace) {
|
if (!isCurrentWorkspace) {
|
||||||
const folders = loadTrustedFolders();
|
const folders = loadTrustedFolders();
|
||||||
folders.setValue(cwd, trustLevel);
|
await folders.setValue(cwd, trustLevel);
|
||||||
onExit();
|
onExit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ export const usePermissionsModifyTrust = (
|
|||||||
} else {
|
} else {
|
||||||
const folders = loadTrustedFolders();
|
const folders = loadTrustedFolders();
|
||||||
try {
|
try {
|
||||||
folders.setValue(cwd, trustLevel);
|
await folders.setValue(cwd, trustLevel);
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
coreEvents.emitFeedback(
|
coreEvents.emitFeedback(
|
||||||
'error',
|
'error',
|
||||||
@@ -153,11 +153,11 @@ export const usePermissionsModifyTrust = (
|
|||||||
[cwd, settings.merged, onExit, addItem, isCurrentWorkspace],
|
[cwd, settings.merged, onExit, addItem, isCurrentWorkspace],
|
||||||
);
|
);
|
||||||
|
|
||||||
const commitTrustLevelChange = useCallback(() => {
|
const commitTrustLevelChange = useCallback(async () => {
|
||||||
if (pendingTrustLevel) {
|
if (pendingTrustLevel) {
|
||||||
const folders = loadTrustedFolders();
|
const folders = loadTrustedFolders();
|
||||||
try {
|
try {
|
||||||
folders.setValue(cwd, pendingTrustLevel);
|
await folders.setValue(cwd, pendingTrustLevel);
|
||||||
return true;
|
return true;
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
coreEvents.emitFeedback(
|
coreEvents.emitFeedback(
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"mnemonist": "^0.40.3",
|
"mnemonist": "^0.40.3",
|
||||||
"open": "^10.1.2",
|
"open": "^10.1.2",
|
||||||
"picomatch": "^4.0.1",
|
"picomatch": "^4.0.1",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"read-package-up": "^11.0.0",
|
"read-package-up": "^11.0.0",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
"simple-git": "^3.28.0",
|
"simple-git": "^3.28.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user