From 948457978043f17a4485f1437114942e5ca78270 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Mon, 15 Jun 2026 12:24:55 +0530 Subject: [PATCH 1/3] apex guru changes --- package-lock.json | 639 ++++++++++++++++-- .../package.json | 7 +- .../src/engine.ts | 140 ++-- .../src/services/ApexGuruService.ts | 306 ++++----- .../src/types/index.ts | 68 +- 5 files changed, 804 insertions(+), 356 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97dc9106..a5c00684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -980,7 +980,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -998,7 +997,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -1011,7 +1009,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -2032,6 +2029,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/archiver": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.4.tgz", + "integrity": "sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2142,6 +2149,27 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sarif": { "version": "2.1.7", "dev": true, @@ -2837,6 +2865,132 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver-utils/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==", + "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/archiver-utils/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/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==", + "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/archiver/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" @@ -3011,6 +3165,12 @@ "meriyah": "^6.0.3" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "license": "MIT", @@ -3060,6 +3220,20 @@ "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/babel-jest": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", @@ -3159,6 +3333,97 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", + "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", + "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3300,6 +3565,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "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", @@ -3589,6 +3863,67 @@ "node": ">=14" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/compress-commons/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==", + "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/compress-commons/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -3612,6 +3947,76 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/crc32-stream/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==", + "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/crc32-stream/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/cross-env": { "version": "10.1.0", "dev": true, @@ -3876,7 +4281,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "devOptional": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -4677,6 +5081,15 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4746,6 +5159,12 @@ "version": "3.1.3", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -5006,7 +5425,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "devOptional": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -5205,7 +5623,6 @@ "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", - "devOptional": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -5729,7 +6146,6 @@ "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==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5865,7 +6281,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -6057,7 +6472,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -6857,6 +7271,18 @@ "node": ">=0.10" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6907,6 +7333,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7132,7 +7564,6 @@ }, "node_modules/minipass": { "version": "7.1.2", - "devOptional": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -7252,7 +7683,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7496,7 +7926,6 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "devOptional": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { @@ -7608,7 +8037,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -7625,7 +8053,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "devOptional": true, "license": "ISC" }, "node_modules/picocolors": { @@ -8109,6 +8536,27 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -8630,7 +9078,6 @@ }, "node_modules/signal-exit": { "version": "4.1.0", - "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -8768,6 +9215,17 @@ "node": ">= 0.4" } }, + "node_modules/streamx": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz", + "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "license": "MIT", @@ -8793,7 +9251,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "devOptional": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -8812,7 +9269,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8827,14 +9283,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -8847,7 +9301,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -8969,7 +9422,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9054,6 +9506,27 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "devOptional": true, @@ -9106,6 +9579,15 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "license": "MIT" @@ -9847,7 +10329,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -9866,7 +10347,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -9884,14 +10364,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/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==", - "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9906,7 +10384,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -9919,7 +10396,6 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -9932,7 +10408,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -10068,6 +10543,65 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/zip-stream/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==", + "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/zip-stream/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/zod": { "version": "3.25.76", "license": "MIT", @@ -10087,16 +10621,21 @@ }, "packages/code-analyzer-apexguru-engine": { "name": "@salesforce/code-analyzer-apexguru-engine", - "version": "0.38.0", + "version": "0.39.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", - "@salesforce/core": "^8.28.3" + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", + "@salesforce/core": "^8.28.3", + "archiver": "^7.0.0", + "form-data": "^4.0.0", + "node-fetch": "^2.7.0" }, "devDependencies": { "@eslint/js": "^9.39.4", + "@types/archiver": "^6.0.0", "@types/jest": "^30.0.0", "@types/node": "^20.0.0", + "@types/node-fetch": "^2.6.0", "eslint": "^9.39.4", "jest": "^30.4.2", "rimraf": "^6.1.3", @@ -10339,10 +10878,10 @@ }, "packages/code-analyzer-core": { "name": "@salesforce/code-analyzer-core", - "version": "0.48.0", + "version": "0.49.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.7.0", "isbinaryfile": "^5.0.7", @@ -10591,7 +11130,7 @@ }, "packages/code-analyzer-engine-api": { "name": "@salesforce/code-analyzer-engine-api", - "version": "0.38.0", + "version": "0.39.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@types/node": "^20.0.0", @@ -10908,7 +11447,7 @@ }, "packages/code-analyzer-eslint-engine": { "name": "@salesforce/code-analyzer-eslint-engine", - "version": "0.43.0", + "version": "0.44.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@babel/preset-react": "^7.28.5", @@ -10916,8 +11455,8 @@ "@lwc/eslint-plugin-lwc": "^3.5.0", "@lwc/eslint-plugin-lwc-platform": "^6.3.0", "@salesforce-ux/eslint-plugin-slds": "^1.2.1", - "@salesforce/code-analyzer-engine-api": "0.38.0", - "@salesforce/code-analyzer-eslint8-engine": "0.15.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", + "@salesforce/code-analyzer-eslint8-engine": "0.16.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "^4.1.2", "@salesforce/eslint-plugin-lightning": "^2.0.0", "@types/node": "^20.0.0", @@ -11374,7 +11913,7 @@ }, "packages/code-analyzer-eslint8-engine": { "name": "@salesforce/code-analyzer-eslint8-engine", - "version": "0.15.0", + "version": "0.16.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@babel/core": "7.27.4", @@ -11382,7 +11921,7 @@ "@eslint/js": "8.57.1", "@lwc/eslint-plugin-lwc": "2.2.0", "@lwc/eslint-plugin-lwc-platform": "5.2.0", - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", @@ -11912,10 +12451,10 @@ }, "packages/code-analyzer-flow-engine": { "name": "@salesforce/code-analyzer-flow-engine", - "version": "0.37.0", + "version": "0.38.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "@types/semver": "^7.7.1", "semver": "^7.7.4" @@ -12157,10 +12696,10 @@ }, "packages/code-analyzer-pmd-engine": { "name": "@salesforce/code-analyzer-pmd-engine", - "version": "0.41.0", + "version": "0.42.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "@types/semver": "^7.7.1", "semver": "^7.7.4" @@ -12402,10 +12941,10 @@ }, "packages/code-analyzer-regex-engine": { "name": "@salesforce/code-analyzer-regex-engine", - "version": "0.36.0", + "version": "0.37.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.7", "p-limit": "^3.1.0" @@ -12647,10 +13186,10 @@ }, "packages/code-analyzer-retirejs-engine": { "name": "@salesforce/code-analyzer-retirejs-engine", - "version": "0.35.0", + "version": "0.36.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.7", "node-stream-zip": "^1.15.0", @@ -12893,10 +13432,10 @@ }, "packages/code-analyzer-sfge-engine": { "name": "@salesforce/code-analyzer-sfge-engine", - "version": "0.21.0", + "version": "0.22.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.4" }, @@ -13141,7 +13680,7 @@ "version": "0.1.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.38.0", + "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", "@types/node": "^20.0.0" }, "devDependencies": { diff --git a/packages/code-analyzer-apexguru-engine/package.json b/packages/code-analyzer-apexguru-engine/package.json index 6fafbfe2..e98a47e4 100644 --- a/packages/code-analyzer-apexguru-engine/package.json +++ b/packages/code-analyzer-apexguru-engine/package.json @@ -14,12 +14,17 @@ "types": "dist/index.d.ts", "dependencies": { "@salesforce/code-analyzer-engine-api": "0.39.0-SNAPSHOT", - "@salesforce/core": "^8.28.3" + "@salesforce/core": "^8.28.3", + "archiver": "^7.0.0", + "form-data": "^4.0.0", + "node-fetch": "^2.7.0" }, "devDependencies": { "@eslint/js": "^9.39.4", + "@types/archiver": "^6.0.0", "@types/jest": "^30.0.0", "@types/node": "^20.0.0", + "@types/node-fetch": "^2.6.0", "eslint": "^9.39.4", "jest": "^30.4.2", "rimraf": "^6.1.3", diff --git a/packages/code-analyzer-apexguru-engine/src/engine.ts b/packages/code-analyzer-apexguru-engine/src/engine.ts index 1dd255c6..f7e72e39 100644 --- a/packages/code-analyzer-apexguru-engine/src/engine.ts +++ b/packages/code-analyzer-apexguru-engine/src/engine.ts @@ -9,12 +9,11 @@ import { EngineRunResults, Violation, CodeLocation, - Fix, Suggestion, LogLevel } from '@salesforce/code-analyzer-engine-api'; import { ApexGuruService } from './services/ApexGuruService'; -import { ApexGuruViolation, ApexGuruLocation, ApexGuruFix, ApexGuruSuggestion, ApexGuruScanMetadata } from './types'; +import { ApexGuruViolation, ApexGuruLocation, ApexGuruSuggestion, ApexGuruScanMetadata } from './types'; import { ApexGuruEngineConfig, DEFAULT_APEXGURU_ENGINE_CONFIG } from './config'; import { ENGINE_NAME, APEXGURU_FILE_EXTENSIONS } from './constants'; import { APEXGURU_RULES, isKnownRule, FALLBACK_RULE_NAME } from './apexguru-rules'; @@ -80,7 +79,7 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { return { violations: [] }; } - // Note: ApexGuru API analyzes code and returns ALL detected violations. + // Note: SFAP ApexGuru API analyzes entire workspace and returns ALL detected violations. // Individual rules cannot be enabled/disabled via the API. // We filter violations to match the selected rules after analysis completes. @@ -101,92 +100,57 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { ); } - // Validate ApexGuru access - try { - await this.apexGuruService.validate(); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`Failed to validate ApexGuru access: ${message}`); - } + // Get workspace root path + const workspaceRoot = runOptions.workspace.getWorkspaceRoot(); - // Get targeted files from workspace and filter for Apex files + // Get targeted files to verify we have Apex files const targetedFiles = await runOptions.workspace.getTargetedFiles(); const apexFiles = targetedFiles.filter(file => this.isApexFile(path.basename(file))); if (apexFiles.length === 0) { this.emitLogEvent(LogLevel.Warn, 'No Apex class files found to analyze'); - this.apexGuruService.cleanup(); // Cleanup even on early return + this.apexGuruService.cleanup(); return { violations: [] }; } + // Workspace root is null when files come from different drives/roots — ApexGuru requires a single zip + if (!workspaceRoot) { + this.apexGuruService.cleanup(); + throw new Error('ApexGuru requires a common workspace root, but the targeted files do not share one.'); + } + try { - // Analyze each file - const allViolations: Violation[] = []; - const scanMetadataByFile: { [filePath: string]: ApexGuruScanMetadata } = {}; - let filesProcessed = 0; - - for (let i = 0; i < apexFiles.length; i++) { - const filePath = apexFiles[i]; - - try { - // Emit progress at start of file - const baseProgress = (filesProcessed / apexFiles.length) * 100; - this.emitRunRulesProgressEvent(baseProgress); - - // Set up progress callback for polling - // Each file gets a slice of the total progress (0-95% of that slice during polling) - const progressSlicePerFile = 100 / apexFiles.length; - this.apexGuruService.setProgressCallback((pollingProgress: number) => { - // Map polling progress (0-95) to this file's slice - const fileProgress = baseProgress + (pollingProgress / 100) * progressSlicePerFile; - this.emitRunRulesProgressEvent(fileProgress); - }); - - const fileContent = await fs.readFile(filePath, 'utf-8'); - const { violations: apexGuruViolations, scanMetadata } = await this.apexGuruService.analyzeApexClass( - fileContent, - filePath - ); - - if (scanMetadata) { - scanMetadataByFile[filePath] = scanMetadata; - } - - const violations = apexGuruViolations.map(av => - toViolation(av, filePath, runOptions.includeFixes ?? false, runOptions.includeSuggestions ?? false) - ); - - // Filter violations to only include selected rules - const filteredViolations = violations.filter(v => selectedRulesSet.has(v.ruleName)); - allViolations.push(...filteredViolations); - - if (violations.length !== filteredViolations.length) { - this.emitLogEvent( - LogLevel.Fine, - `Filtered ${violations.length - filteredViolations.length} violation(s) for unselected rules` - ); - } - - filesProcessed++; - const endProgress = (filesProcessed / apexFiles.length) * 100; - this.emitRunRulesProgressEvent(endProgress); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - this.emitLogEvent( - LogLevel.Warn, - `Failed to analyze ${path.basename(filePath)}: ${message}` - ); - // Continue with other files - } + // Set up progress callback for polling + this.apexGuruService.setProgressCallback((pollingProgress: number) => { + this.emitRunRulesProgressEvent(pollingProgress); + }); + + // Scan entire workspace (creates zip -> submits -> polls -> decodes) + const { violations: apexGuruViolations, scanMetadata } = await this.apexGuruService.scanWorkspace(workspaceRoot); + + // Convert all ApexGuru violations to Code Analyzer format + const allViolations = apexGuruViolations.map(av => { + // SFAP response includes file path in location.file + const filePath = av.locations[0]?.file ?? 'unknown'; + return toViolation(av, filePath, runOptions.includeSuggestions ?? false); + }); + + // Filter violations to only include selected rules + const filteredViolations = allViolations.filter(v => selectedRulesSet.has(v.ruleName)); + + if (allViolations.length !== filteredViolations.length) { + this.emitLogEvent( + LogLevel.Fine, + `Filtered ${allViolations.length - filteredViolations.length} violation(s) for unselected rules` + ); } - const insights: Record | undefined = Object.keys(scanMetadataByFile).length > 0 - ? scanMetadataByFile - : undefined; + // Return insights as scan metadata (workspace-level) + const insights: Record | undefined = scanMetadata ? { scan: scanMetadata } : undefined; - return { violations: allViolations, insights }; + return { violations: filteredViolations, insights }; } finally { - // Always cleanup resources to allow process to exit + // Always cleanup resources this.apexGuruService.cleanup(); } } @@ -223,7 +187,6 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { function toViolation( av: ApexGuruViolation, filePath: string, - includeFixes: boolean, includeSuggestions: boolean ): Violation { // Map unknown rules to fallback to ensure Core validation passes @@ -237,11 +200,6 @@ function toViolation( resourceUrls: av.resources }; - // Add fixes if requested and available - if (includeFixes && av.fixes?.length) { - violation.fixes = av.fixes.map(fix => toFix(fix, filePath)); - } - // Add suggestions if requested and available if (includeSuggestions && av.suggestions?.length) { violation.suggestions = av.suggestions.map(suggestion => toSuggestion(suggestion, filePath)); @@ -250,17 +208,6 @@ function toViolation( return violation; } -/** - * Convert ApexGuru fix to Code Analyzer Fix format - * Note: ApexGuru API does not currently return fixes, only suggestions - */ -function toFix(apexGuruFix: ApexGuruFix, filePath: string): Fix { - return { - location: normalizeLocation(apexGuruFix.location, filePath), - fixedCode: apexGuruFix.fixedCode - }; -} - /** * Convert ApexGuru suggestion to Code Analyzer Suggestion format * Note: suggestion.message contains "// explanation\ncode" - we keep it as-is @@ -275,13 +222,14 @@ function toSuggestion(apexGuruSuggestion: ApexGuruSuggestion, filePath: string): /** * Normalize location by filling in required fields * - * ApexGuru API only provides: + * SFAP ApexGuru API provides: + * - file (from SFAP response, workspace-relative path) * - startLine (required) * - comment (optional) * * We fill in: - * - file (required by Code Analyzer, not in ApexGuru response) - * - startColumn = 1 (required by Code Analyzer, reasonable default) + * - startColumn = 1 (required by Code Analyzer, reasonable default if not provided) + * - Use file from location if provided, else use filePath parameter * - endLine/endColumn are left undefined (optional fields) */ function normalizeLocation(location: ApexGuruLocation, filePath: string): CodeLocation { @@ -289,7 +237,7 @@ function normalizeLocation(location: ApexGuruLocation, filePath: string): CodeLo const startColumn = location.startColumn ?? 1; // Default to column 1 if not provided return { - file: filePath, + file: location.file ?? filePath, // SFAP includes file path in response startLine, startColumn, endLine: location.endLine, // undefined if not provided (optional) diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts index 2667b115..0b0f531f 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -1,20 +1,22 @@ -import { Connection } from '@salesforce/core'; import { LogLevel } from '@salesforce/code-analyzer-engine-api'; import { ApexGuruAuthService } from './ApexGuruAuthService'; import { - ApexGuruInitialResponse, - ApexGuruQueryResponse, + ApexGuruSubmitResponse, + ApexGuruPollResponse, ApexGuruResponseStatus, ApexGuruScanMetadata, ApexGuruViolation } from '../types'; -import * as http from 'node:http'; -import * as https from 'node:https'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import archiver from 'archiver'; +import FormData from 'form-data'; +import fetch from 'node-fetch'; /** - * Service for interacting with ApexGuru APIs + * Service for interacting with SFAP ApexGuru workspace scan APIs */ export class ApexGuruService { private readonly authService: ApexGuruAuthService; @@ -23,6 +25,7 @@ export class ApexGuruService { private readonly initialRetryMs: number; private readonly maxRetryMs: number; private readonly backoffMultiplier: number; + private readonly sfapBaseUrl = 'https://dev.api.salesforce.com/platform/scale/v1-beta.1'; private progressCallback?: (progress: number) => void; private isCancelled = false; @@ -49,16 +52,7 @@ export class ApexGuruService { await this.authService.initialize({ targetOrg }); // Mint Org JWT for SFAP API access - const orgJwt = await this.authService.mintOrgJwt(); - - try { - const jwtParts = orgJwt.split('.'); - if (jwtParts.length === 3) { - //const payload = JSON.parse(Buffer.from(jwtParts[1], 'base64').toString()); - } - } catch (error) { - this.emitLogEvent(LogLevel.Warn, `Could not decode JWT payload: ${error}`); - } + await this.authService.mintOrgJwt(); } /** @@ -69,222 +63,196 @@ export class ApexGuruService { } /** - * Cleanup resources - force close all HTTP connections - * This is critical to allow the Node.js process to exit, especially when timeouts occur - * and underlying HTTP requests are still pending + * Cleanup resources */ cleanup(): void { - try { - // TODO: This destroys process-wide HTTP agents, which could interfere with - // concurrent HTTP work in the Code Analyzer process. We should investigate - // using custom agents specific to ApexGuru's Connection and destroy only - // those agents instead of the global ones. For now, this approach works - // because Node.js automatically recreates destroyed agents when needed. - // To be addressed in a future PR. - http.globalAgent.destroy(); - https.globalAgent.destroy(); - } catch { - // Ignore cleanup errors - best effort - } + // No global HTTP agent cleanup needed for fetch-based implementation } /** - * Validate ApexGuru access - * Throws error with specific context if validation fails + * Scan workspace Apex files and return violations with insights */ - async validate(): Promise { + async scanWorkspace(workspacePath: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { + this.isCancelled = false; let timeoutId: NodeJS.Timeout; - const validatePromise = this.performValidate(); + const scanPromise = this.performScan(workspacePath); const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => reject(new Error(`Validate request timed out after ${this.maxTimeoutMs}ms`)), this.maxTimeoutMs); + timeoutId = setTimeout(() => { + this.isCancelled = true; + reject(new Error(`Workspace scan timed out after ${this.maxTimeoutMs}ms`)); + }, this.maxTimeoutMs); }); try { - await Promise.race([validatePromise, timeoutPromise]); + return await Promise.race([scanPromise, timeoutPromise]); } finally { clearTimeout(timeoutId!); } } /** - * Internal validate implementation (without timeout wrapper) + * Perform the full scan workflow: create zip -> submit -> poll -> decode */ - private async performValidate(): Promise { - const connection: Connection = this.authService.getConnection(); - const apiVersion = this.authService.getApiVersion(); - const url = `/services/data/v${apiVersion}/apexguru/validate`; - - const response = await connection.request({ - method: 'GET', - url - }) as { status?: string }; - - if (response.status && response.status.toLowerCase() === ApexGuruResponseStatus.SUCCESS) { - return; - } + private async performScan(workspacePath: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { + // Step 1: Create zip of workspace + const zipBuffer = await this.createWorkspaceZip(workspacePath); - throw new Error( - `ApexGuru is not available for this org (status: ${response.status ?? 'unknown'}).\n` + - 'Please check that ApexGuru is enabled and you have the required permissions.' - ); - } + // Step 2: Submit scan + const { scanId } = await this.submitScan(zipBuffer); - /** - * Submit Apex class for analysis and wait for results - * Wraps submit + poll together with a single timeout (api_timeout_ms) - */ - async analyzeApexClass(classContent: string, filePath: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { - this.isCancelled = false; - let timeoutId: NodeJS.Timeout; - const analysisPromise = this.performAnalysis(classContent); - const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => { - this.isCancelled = true; - reject(new Error(`Analysis timed out after ${this.maxTimeoutMs}ms for file: ${filePath}`)); - }, this.maxTimeoutMs); - }); + // Step 3: Poll for results + const pollResponse = await this.pollForResults(scanId); - try { - return await Promise.race([analysisPromise, timeoutPromise]); - } finally { - clearTimeout(timeoutId!); - } + // Step 4: Decode and return + return this.decodeResults(pollResponse); } /** - * Internal analysis implementation (without timeout wrapper) - * Performs submit + poll + * Create a zip file of workspace Apex files */ - private async performAnalysis(classContent: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { - // Step 1: Submit request - const requestId = await this.submitAnalysis(classContent); + private async createWorkspaceZip(workspacePath: string): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + const archive = archiver('zip', { zlib: { level: 9 } }); + + archive.on('data', (chunk: Buffer) => chunks.push(chunk)); + archive.on('end', () => resolve(Buffer.concat(chunks))); + archive.on('error', reject); + + // Find force-app directory + const forceAppPath = path.join(workspacePath, 'force-app'); + if (!fs.existsSync(forceAppPath)) { + reject(new Error(`force-app directory not found at ${forceAppPath}`)); + return; + } + + // Add all .cls and .trigger files, exclude hidden/temp files + archive.glob('**/*.{cls,trigger}', { + cwd: forceAppPath, + ignore: ['**/__MACOSX/**', '**/.*', '**/.sfdx/**', '**/.DS_Store'] + }, { prefix: 'force-app' }); - // Step 2: Poll for results - return await this.pollForResults(requestId); + archive.finalize(); + }); } /** - * Submit Apex class for analysis + * Submit workspace zip to SFAP API */ - private async submitAnalysis(classContent: string): Promise { - const connection: Connection = this.authService.getConnection(); - const apiVersion = this.authService.getApiVersion(); - const url = `/services/data/v${apiVersion}/apexguru/request`; + private async submitScan(zipBuffer: Buffer): Promise { + const orgJwt = await this.authService.mintOrgJwt(); + const url = `${this.sfapBaseUrl}/apex-guru/scan`; - const base64Content = Buffer.from(classContent, 'utf-8').toString('base64'); - const requestBody = { classContent: base64Content }; + const form = new FormData(); + form.append('file', zipBuffer, { filename: 'project.zip', contentType: 'application/zip' }); + form.append('analysisModeHint', 'full'); try { - const response: ApexGuruInitialResponse = await connection.request({ + const response = await fetch(url, { method: 'POST', - url, - body: JSON.stringify(requestBody), - headers: { 'Content-Type': 'application/json' } + headers: { + 'Authorization': `Bearer ${orgJwt}`, + ...form.getHeaders() + }, + body: form }); - // Normalize status to lowercase - if (response.status) { - response.status = response.status.toLowerCase(); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`SFAP API returned ${response.status}: ${errorText}`); } - if (response.status === ApexGuruResponseStatus.FAILED) { - throw new Error(`ApexGuru analysis failed: ${response.message || 'Unknown error'}`); - } - - if (response.status !== ApexGuruResponseStatus.NEW && response.status !== ApexGuruResponseStatus.SUCCESS) { - throw new Error(`Unexpected response status: ${response.status}`); - } - - return response.requestId || 'pending'; + return await response.json() as ApexGuruSubmitResponse; } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`Failed to submit analysis request: ${message}`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to submit scan: ${errorMessage}`); } } /** - * Poll for analysis results with exponential backoff - * Note: Timeout is handled by analyzeApexClass wrapper, not here + * Poll SFAP API for scan results with exponential backoff */ - private async pollForResults(requestId: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { - const connection: Connection = this.authService.getConnection(); - const apiVersion = this.authService.getApiVersion(); - const url = requestId === 'pending' - ? `/services/data/v${apiVersion}/apexguru/request` - : `/services/data/v${apiVersion}/apexguru/request/${requestId}`; - - let delay = this.initialRetryMs; - let attempts = 0; - - while (true) { - if (this.isCancelled) { - throw new Error('Analysis cancelled due to timeout'); - } + private async pollForResults(scanId: string): Promise { + const orgJwt = await this.authService.mintOrgJwt(); + const url = `${this.sfapBaseUrl}/apex-guru/scan/${scanId}`; - if (attempts > 0) { - await this.sleep(delay); - } + let retryMs = this.initialRetryMs; + const startTime = Date.now(); - attempts++; + while (!this.isCancelled) { + const elapsedMs = Date.now() - startTime; - // Emit asymptotic progress (approaches 95% but never quite reaches it) - // Formula: 95 * (1 - e^(-attempts/4)) + // Asymptotic progress: approaches 100% but never reaches it if (this.progressCallback) { - const asymptoticProgress = 95 * (1 - Math.exp(-attempts / 4)); + const asymptoticProgress = Math.min(95, 100 * (1 - Math.exp(-elapsedMs / (this.maxTimeoutMs * 0.5)))); this.progressCallback(asymptoticProgress); } - const response: ApexGuruQueryResponse = await connection.request({ - method: 'GET', - url - }); - - // Normalize status - if (response.status) { - response.status = response.status.toLowerCase(); - } - - // Check if analysis is complete - if (response.status === ApexGuruResponseStatus.SUCCESS && response.report) { - const violations = this.parseReport(response.report); - return { violations, scanMetadata: response.scanMetadata }; - } - - // Check for failures - if (response.status === ApexGuruResponseStatus.FAILED) { - throw new Error(`Analysis failed: ${response.message || 'Unknown error'}`); + try { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${orgJwt}` + } + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`SFAP API returned ${response.status}: ${errorText}`); + } + + const pollResponse: ApexGuruPollResponse = await response.json() as ApexGuruPollResponse; + + // Check status + if (pollResponse.status === ApexGuruResponseStatus.SUCCEEDED) { + if (this.progressCallback) { + this.progressCallback(100); + } + return pollResponse; + } + + if (pollResponse.status === ApexGuruResponseStatus.FAILED) { + throw new Error(`Scan failed: ${pollResponse.message ?? 'Unknown error'}`); + } + + // Still processing (QUEUED or RUNNING), wait and retry + await this.sleep(retryMs); + retryMs = Math.min(retryMs * this.backoffMultiplier, this.maxRetryMs); + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Polling failed: ${errorMessage}`); } - - if (response.status === ApexGuruResponseStatus.ERROR) { - throw new Error(`Analysis error: ${response.message || 'Unknown error'}`); - } - - // Still processing, continue polling with exponential backoff - delay = Math.min(delay * this.backoffMultiplier, this.maxRetryMs); } + + throw new Error('Scan was cancelled'); } /** - * Parse Base64-encoded report + * Decode base64 report and parse violations */ - private parseReport(reportBase64: string): ApexGuruViolation[] { - try { - const reportJson = Buffer.from(reportBase64, 'base64').toString('utf-8'); - const violations: ApexGuruViolation[] = JSON.parse(reportJson); - - if (!Array.isArray(violations)) { - throw new Error('Report is not an array of violations'); - } + private decodeResults(pollResponse: ApexGuruPollResponse): {violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata} { + if (!pollResponse.report) { + return { violations: [], scanMetadata: pollResponse.scanMetadata ?? undefined }; + } - return violations; + try { + // Decode base64 report + const decodedReport = Buffer.from(pollResponse.report, 'base64').toString('utf-8'); + const violations: ApexGuruViolation[] = JSON.parse(decodedReport); + + return { + violations, + scanMetadata: pollResponse.scanMetadata ?? undefined + }; } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`Failed to parse ApexGuru report: ${message}`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to decode report: ${errorMessage}`); } } /** - * Sleep utility for polling + * Sleep utility */ private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/packages/code-analyzer-apexguru-engine/src/types/index.ts b/packages/code-analyzer-apexguru-engine/src/types/index.ts index 36382c47..86273a52 100644 --- a/packages/code-analyzer-apexguru-engine/src/types/index.ts +++ b/packages/code-analyzer-apexguru-engine/src/types/index.ts @@ -15,41 +15,44 @@ export type AuthConfig = { }; /** - * ApexGuru API response statuses + * SFAP ApexGuru API response statuses */ export enum ApexGuruResponseStatus { - NEW = "new", - PROCESSING = "processing", - SUCCESS = "success", - FAILED = "failed", - ERROR = "error" + QUEUED = "QUEUED", + RUNNING = "RUNNING", + SUCCEEDED = "SUCCEEDED", + FAILED = "FAILED" } /** - * Base ApexGuru API response + * Response from POST https://dev.api.salesforce.com/platform/scale/v1-beta.1/apex-guru/scan */ -export type ApexGuruResponse = { +export type ApexGuruSubmitResponse = { + scanId: string; status: string; - message?: string; + analysisMode: string; + createdMs: number; }; /** - * Response from initial POST /apexguru/request + * Response from GET https://dev.api.salesforce.com/platform/scale/v1-beta.1/apex-guru/scan/{scanId} */ -export type ApexGuruInitialResponse = ApexGuruResponse & { - requestId?: string; -}; - -/** - * Response from GET /apexguru/request/{id} - */ -export type ApexGuruQueryResponse = ApexGuruResponse & { - report?: string; // Base64 encoded JSON array of violations - scanMetadata?: ApexGuruScanMetadata; +export type ApexGuruPollResponse = { + scanId: string; + status: string; + analysisMode: string; + createdMs: number; + updatedMs: number; + processingStartMs: number | null; + processingEndMs: number | null; + scanMetadata: ApexGuruScanMetadata | null; + report: string | null; // Base64 encoded JSON array of violations + reportS3Key: string | null; + message: string | null; }; /** - * ApexGuru violation structure (matches API response) + * ApexGuru violation structure from decoded report (matches SFAP API response) */ export type ApexGuruViolation = { rule: string; @@ -59,16 +62,15 @@ export type ApexGuruViolation = { resources: string[]; severity: number; suggestions?: ApexGuruSuggestion[]; - fixes?: ApexGuruFix[]; metadata?: { original_code: string; class_name: string; - category: string; + file: string; }; }; /** - * Location in ApexGuru response (no file field) + * Location in ApexGuru response (includes file field from SFAP) */ export type ApexGuruLocation = { startLine: number; @@ -76,6 +78,7 @@ export type ApexGuruLocation = { endLine?: number; endColumn?: number; comment?: string; + file?: string; // File path from SFAP response }; /** @@ -83,22 +86,7 @@ export type ApexGuruLocation = { */ export type ApexGuruSuggestion = { location: ApexGuruLocation; - message: string; // Contains "// explanation\ncode" -}; - -/** - * Fix in ApexGuru response - */ -export type ApexGuruFix = { - location: ApexGuruLocation; - fixedCode: string; -}; - -/** - * Request body for POST /apexguru/request - */ -export type ApexGuruRequestBody = { - classContent: string; // Base64 encoded Apex class + message: string; // Code suggestion }; export type OrgJwtResponse = { From 2d9b2d2a10979e53fa98d2c4a09e8e07bc20ce19 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Tue, 16 Jun 2026 15:10:43 +0530 Subject: [PATCH 2/3] zip strategy --- .../src/engine.ts | 9 +++-- .../src/services/ApexGuruService.ts | 38 ++++++++++--------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/code-analyzer-apexguru-engine/src/engine.ts b/packages/code-analyzer-apexguru-engine/src/engine.ts index f7e72e39..f4d345c1 100644 --- a/packages/code-analyzer-apexguru-engine/src/engine.ts +++ b/packages/code-analyzer-apexguru-engine/src/engine.ts @@ -13,7 +13,7 @@ import { LogLevel } from '@salesforce/code-analyzer-engine-api'; import { ApexGuruService } from './services/ApexGuruService'; -import { ApexGuruViolation, ApexGuruLocation, ApexGuruSuggestion, ApexGuruScanMetadata } from './types'; +import { ApexGuruViolation, ApexGuruLocation, ApexGuruSuggestion } from './types'; import { ApexGuruEngineConfig, DEFAULT_APEXGURU_ENGINE_CONFIG } from './config'; import { ENGINE_NAME, APEXGURU_FILE_EXTENSIONS } from './constants'; import { APEXGURU_RULES, isKnownRule, FALLBACK_RULE_NAME } from './apexguru-rules'; @@ -119,14 +119,17 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { throw new Error('ApexGuru requires a common workspace root, but the targeted files do not share one.'); } + // If the user passed --target, zip only those paths; otherwise zip the whole workspace. + const pathsToZip = runOptions.workspace.getRawTargets() ?? [workspaceRoot]; + try { // Set up progress callback for polling this.apexGuruService.setProgressCallback((pollingProgress: number) => { this.emitRunRulesProgressEvent(pollingProgress); }); - // Scan entire workspace (creates zip -> submits -> polls -> decodes) - const { violations: apexGuruViolations, scanMetadata } = await this.apexGuruService.scanWorkspace(workspaceRoot); + // Scan (creates zip -> submits -> polls -> decodes) + const { violations: apexGuruViolations, scanMetadata } = await this.apexGuruService.scanWorkspace(workspaceRoot, pathsToZip); // Convert all ApexGuru violations to Code Analyzer format const allViolations = apexGuruViolations.map(av => { diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts index 0b0f531f..a40590b5 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -70,12 +70,14 @@ export class ApexGuruService { } /** - * Scan workspace Apex files and return violations with insights + * Zip the given paths and scan them. workspaceRoot is used to compute zip-entry names so + * the archive preserves project structure (e.g. force-app/main/default/classes/Foo.cls). + * Each path in pathsToZip may be a file or a folder; contents are not inspected or filtered. */ - async scanWorkspace(workspacePath: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { + async scanWorkspace(workspaceRoot: string, pathsToZip: string[]): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { this.isCancelled = false; let timeoutId: NodeJS.Timeout; - const scanPromise = this.performScan(workspacePath); + const scanPromise = this.performScan(workspaceRoot, pathsToZip); const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { this.isCancelled = true; @@ -93,9 +95,9 @@ export class ApexGuruService { /** * Perform the full scan workflow: create zip -> submit -> poll -> decode */ - private async performScan(workspacePath: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { + private async performScan(workspaceRoot: string, pathsToZip: string[]): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { // Step 1: Create zip of workspace - const zipBuffer = await this.createWorkspaceZip(workspacePath); + const zipBuffer = await this.createWorkspaceZip(workspaceRoot, pathsToZip); // Step 2: Submit scan const { scanId } = await this.submitScan(zipBuffer); @@ -108,9 +110,10 @@ export class ApexGuruService { } /** - * Create a zip file of workspace Apex files + * Zip the given paths as-is. Each path may be a file or a folder. + * Entry names are computed relative to workspaceRoot so the archive mirrors the project layout. */ - private async createWorkspaceZip(workspacePath: string): Promise { + private async createWorkspaceZip(workspaceRoot: string, pathsToZip: string[]): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; const archive = archiver('zip', { zlib: { level: 9 } }); @@ -119,19 +122,18 @@ export class ApexGuruService { archive.on('end', () => resolve(Buffer.concat(chunks))); archive.on('error', reject); - // Find force-app directory - const forceAppPath = path.join(workspacePath, 'force-app'); - if (!fs.existsSync(forceAppPath)) { - reject(new Error(`force-app directory not found at ${forceAppPath}`)); - return; + for (const absPath of pathsToZip) { + const stat = fs.statSync(absPath); + const entryName = absPath === workspaceRoot + ? '' + : path.relative(workspaceRoot, absPath); + if (stat.isDirectory()) { + archive.directory(absPath, entryName || false); + } else { + archive.file(absPath, { name: entryName || path.basename(absPath) }); + } } - // Add all .cls and .trigger files, exclude hidden/temp files - archive.glob('**/*.{cls,trigger}', { - cwd: forceAppPath, - ignore: ['**/__MACOSX/**', '**/.*', '**/.sfdx/**', '**/.DS_Store'] - }, { prefix: 'force-app' }); - archive.finalize(); }); } From 5ca92cb2ce7c1a426a952acdda74d97fcbc9d689 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Tue, 16 Jun 2026 16:46:11 +0530 Subject: [PATCH 3/3] auth changes --- .../code-analyzer-apexguru-engine/src/config.ts | 14 ++++++++++---- .../code-analyzer-apexguru-engine/src/engine.ts | 15 +++++++-------- .../test/ApexGuruEngine.test.ts | 10 +++------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/code-analyzer-apexguru-engine/src/config.ts b/packages/code-analyzer-apexguru-engine/src/config.ts index 4606a07c..511b8a18 100644 --- a/packages/code-analyzer-apexguru-engine/src/config.ts +++ b/packages/code-analyzer-apexguru-engine/src/config.ts @@ -54,6 +54,10 @@ export const DEFAULT_APEXGURU_ENGINE_CONFIG: ApexGuruEngineConfig = { export const APEXGURU_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = { overview: 'Configuration for ApexGuru Engine. Authentication is handled via Salesforce CLI (sf org login web). Use --target-org flag to specify the org.', fieldDescriptions: { + target_org: { + descriptionText: 'Target Salesforce org alias or username. If not specified, uses the default SF CLI org. This value is typically set by the CLI --target-org flag.', + valueType: 'string' + }, api_timeout_ms: { descriptionText: 'Maximum time to wait for ApexGuru API response (in milliseconds). Default: 120000 (2 minutes)', valueType: 'number', @@ -85,16 +89,18 @@ export async function validateAndNormalizeConfig( ): Promise { // Validate only expected keys are present configValueExtractor.validateContainsOnlySpecifiedKeys([ + 'target_org', 'api_timeout_ms', 'api_initial_retry_ms', 'api_max_retry_ms', 'api_backoff_multiplier' ]); - // Extract target org from CLI flag only - // - If user passes --target-org: use that org - // - If user doesn't pass --target-org: undefined (auth service uses default SF CLI org) - const targetOrg: string | undefined = process.env.CODE_ANALYZER_TARGET_ORG; + // Extract target org from config (passed by CLI via --target-org flag) + // - If user passes --target-org: CLI sets this in engine config as the alias/username string + // - If not provided: undefined (auth service will fall back to default SF CLI org) + // Core resolves credentials internally via @salesforce/core (Org.create → getConnection) + const targetOrg: string | undefined = configValueExtractor.extractString('target_org'); // Extract and validate timeout const apiTimeoutMs: number = configValueExtractor.extractNumber( diff --git a/packages/code-analyzer-apexguru-engine/src/engine.ts b/packages/code-analyzer-apexguru-engine/src/engine.ts index f4d345c1..5f88741a 100644 --- a/packages/code-analyzer-apexguru-engine/src/engine.ts +++ b/packages/code-analyzer-apexguru-engine/src/engine.ts @@ -86,8 +86,8 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { // Create a Set for faster rule name lookup const selectedRulesSet = new Set(ruleNames); - // Extract targetOrg from environment - const targetOrg = this.getTargetOrgFromEnvironment(); + // Get target org alias/username from config (passed by CLI --target-org flag) + const targetOrg = this.getTargetOrg(); // Initialize authentication try { @@ -166,13 +166,12 @@ export class ApexGuruEngine extends EngineEventEmitter implements Engine { } /** - * Extract target org from environment - * Note: Workspace does not currently expose org configuration through the Engine API. - * Target org can be set via SF_TARGET_ORG environment variable. + * Get the target org alias/username from engine config. + * The CLI passes this through as a plain string (alias or username). + * Core resolves credentials internally via @salesforce/core. + * If undefined, ApexGuruAuthService will fall back to the default SF CLI org. */ - private getTargetOrgFromEnvironment(): string | undefined { - // Return target_org from config (set via CLI --target-org flag or config file) - // If undefined, ApexGuruAuthService will use default SF CLI org + private getTargetOrg(): string | undefined { return this.config.target_org; } diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts index 95641319..1d3cb5ad 100644 --- a/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts @@ -368,11 +368,9 @@ describe('ApexGuruEngine', () => { expect(mockApexGuruService.analyzeApexClass).toHaveBeenCalled(); }); - it('should extract targetOrg from environment', async () => { - // Set environment variable BEFORE creating the engine - process.env.CODE_ANALYZER_TARGET_ORG = 'my-org'; - - // Create new engine with config that includes target_org from environment + it('should pass target_org from config to auth service', async () => { + // target_org is passed through config (set by CLI --target-org flag) + // Core resolves credentials internally via @salesforce/core const engineWithConfig = new ApexGuruEngine({ target_org: 'my-org', api_timeout_ms: 120000, api_initial_retry_ms: 2000, api_max_retry_ms: 60000, api_backoff_multiplier: 2 }); mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); @@ -382,8 +380,6 @@ describe('ApexGuruEngine', () => { await engineWithConfig.runRules(['SoqlInALoop'], mockRunOptions); expect(mockApexGuruService.initialize).toHaveBeenCalledWith('my-org'); - - delete process.env.CODE_ANALYZER_TARGET_ORG; }); it('should aggregate violations from multiple files', async () => {