diff --git a/.node-scripts/validate-changed-package-versions.js b/.node-scripts/validate-changed-package-versions.js index 5239e716..605b121e 100644 --- a/.node-scripts/validate-changed-package-versions.js +++ b/.node-scripts/validate-changed-package-versions.js @@ -114,7 +114,8 @@ function getLatestReleasedVersion(changedPackage) { function isPackageThatHasNotPublished(changedPackage) { return [ - "packages/ENGINE-TEMPLATE" + "packages/ENGINE-TEMPLATE", + "packages/code-analyzer-apexguru-engine" // remove ths exception once PR merges and is published ].includes(changedPackage.replace("\\","/")); } diff --git a/package-lock.json b/package-lock.json index 471df464..0c10ebff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1415,6 +1415,166 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsforce/jsforce-node": { + "version": "3.10.14", + "resolved": "https://registry.npmjs.org/@jsforce/jsforce-node/-/jsforce-node-3.10.14.tgz", + "integrity": "sha512-p8Ug1SypcAT7Q0zZA0+7fyBmgUpB/aXkde4Bxmu0S/O4p28CVwgYvKyFd9vswmHIhFabd/QqUCrlYuVhYdr2Ew==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4", + "base64url": "^3.0.1", + "csv-parse": "^5.5.2", + "csv-stringify": "^6.6.0", + "faye": "^1.4.0", + "form-data": "^4.0.4", + "https-proxy-agent": "^5.0.0", + "multistream": "^3.1.0", + "node-fetch": "^2.6.1", + "xml2js": "^0.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jsforce/jsforce-node/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@jsforce/jsforce-node/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@lwc/eslint-plugin-lwc": { "version": "2.2.0", "license": "MIT", @@ -1526,6 +1686,12 @@ "node": ">= 8" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "license": "MIT", @@ -1581,6 +1747,10 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@salesforce/code-analyzer-apexguru-engine": { + "resolved": "packages/code-analyzer-apexguru-engine", + "link": true + }, "node_modules/@salesforce/code-analyzer-core": { "resolved": "packages/code-analyzer-core", "link": true @@ -1617,6 +1787,76 @@ "resolved": "packages/code-analyzer-sfge-engine", "link": true }, + "node_modules/@salesforce/core": { + "version": "8.28.1", + "resolved": "https://registry.npmjs.org/@salesforce/core/-/core-8.28.1.tgz", + "integrity": "sha512-k9lPsULo+lOEZvpm1J1nJOFwKp5O5IfNqya7pw627QdKGcsWZm6v9caVHKUX9IjyB+S3dasNqaZT5O7l76C4oQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@jsforce/jsforce-node": "^3.10.13", + "@salesforce/kit": "^3.2.4", + "@salesforce/ts-types": "^2.0.12", + "ajv": "^8.18.0", + "change-case": "^4.1.2", + "fast-levenshtein": "^3.0.0", + "faye": "^1.4.1", + "form-data": "^4.0.4", + "js2xmlparser": "^4.0.1", + "jsonwebtoken": "9.0.3", + "jszip": "3.10.1", + "memfs": "4.38.1", + "pino": "^9.7.0", + "pino-abstract-transport": "^1.2.0", + "pino-pretty": "^11.3.0", + "proper-lockfile": "^4.1.2", + "semver": "^7.7.3", + "ts-retry-promise": "^0.8.1", + "zod": "^4.1.12" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@salesforce/core/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@salesforce/core/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/@salesforce/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@salesforce/core/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@salesforce/engine-template": { "resolved": "packages/ENGINE-TEMPLATE", "link": true @@ -1628,11 +1868,41 @@ "eslint": "^7 || ^8" } }, + "node_modules/@salesforce/kit": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.6.tgz", + "integrity": "sha512-O8S4LWerHa9Zosqh+IoQjgLtpxMOfObRxaRnUdRV4MLtFUi+bQxQiyFvve6eEaBaMP1b1xVDQpvSvQ+PXEDGFQ==", + "license": "Apache-2.0", + "dependencies": { + "@salesforce/ts-types": "^2.0.12" + } + }, + "node_modules/@salesforce/ts-types": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz", + "integrity": "sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.34.41", "devOptional": true, "license": "MIT" }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "devOptional": true, @@ -2280,6 +2550,18 @@ "win32" ] }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.15.0", "license": "MIT", @@ -2520,6 +2802,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, "node_modules/ast-types": { "version": "0.13.4", "license": "MIT", @@ -2548,6 +2836,21 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "license": "MIT", @@ -2668,6 +2971,35 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.8.31", "license": "Apache-2.0", @@ -2754,6 +3086,36 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "devOptional": true, @@ -2807,6 +3169,16 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "5.3.1", "devOptional": true, @@ -2833,6 +3205,17 @@ ], "license": "CC-BY-4.0" }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, "node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -2847,6 +3230,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/char-regex": { "version": "1.0.2", "devOptional": true, @@ -2953,6 +3356,24 @@ "version": "1.1.4", "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "10.0.1", "license": "MIT", @@ -2964,13 +3385,23 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", - "dev": true, "license": "MIT" }, "node_modules/cross-env": { @@ -3001,6 +3432,24 @@ "node": ">= 8" } }, + "node_modules/csprng": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/csprng/-/csprng-0.1.2.tgz", + "integrity": "sha512-D3WAbvvgUVIqSxUfdvLeGjuotsB32bvfVPd+AaaTWMtyUeC9zgCnw5xs94no89yFLVsafvY9dMZEhTwsY/ZecA==", + "license": "MIT", + "dependencies": { + "sequin": "*" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/csv-parse": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz", + "integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==", + "license": "MIT" + }, "node_modules/csv-stringify": { "version": "6.7.0", "license": "MIT" @@ -3061,6 +3510,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.3", "license": "MIT", @@ -3143,6 +3601,15 @@ "node": ">= 14" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "devOptional": true, @@ -3161,11 +3628,21 @@ "node": ">=6.0.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -3186,6 +3663,15 @@ "devOptional": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.259", "license": "ISC" @@ -3205,6 +3691,15 @@ "version": "9.2.2", "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/error-ex": { "version": "1.3.4", "devOptional": true, @@ -3937,6 +4432,24 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "devOptional": true, @@ -3988,6 +4501,12 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" @@ -4024,6 +4543,28 @@ "version": "2.0.6", "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", @@ -4055,6 +4596,15 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.19.1", "license": "ISC", @@ -4062,6 +4612,35 @@ "reusify": "^1.0.4" } }, + "node_modules/faye": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/faye/-/faye-1.4.1.tgz", + "integrity": "sha512-Cg/khikhqlvumHO3efwx2tps2ZgQRjUMrO24G0quz7MMzRYYaEjU224YFXOeuPIvanRegIchVxj6pmHK1W0ikA==", + "license": "Apache-2.0", + "dependencies": { + "asap": "*", + "csprng": "*", + "faye-websocket": ">=0.9.1", + "safe-buffer": "*", + "tough-cookie": "*", + "tunnel-agent": "*" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "devOptional": true, @@ -4197,6 +4776,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "11.3.2", "dev": true, @@ -4387,6 +4982,22 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/globals": { "version": "17.4.0", "license": "MIT", @@ -4423,7 +5034,6 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "devOptional": true, "license": "ISC" }, "node_modules/graphemer": { @@ -4523,6 +5133,22 @@ "node": ">= 0.4" } }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/hermes-estree": { "version": "0.25.1", "license": "MIT" @@ -4539,6 +5165,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "license": "MIT", @@ -4583,6 +5215,35 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "7.0.5", "license": "MIT", @@ -4590,6 +5251,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "license": "MIT", @@ -5682,6 +6349,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -5696,6 +6372,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, "node_modules/jsesc": { "version": "3.1.0", "license": "MIT", @@ -5744,6 +6429,28 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "license": "MIT", @@ -5757,6 +6464,39 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "license": "MIT", @@ -5797,6 +6537,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "devOptional": true, @@ -5815,6 +6564,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "dev": true, @@ -5824,6 +6609,12 @@ "version": "4.6.2", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", @@ -5834,6 +6625,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "license": "ISC", @@ -5879,6 +6679,27 @@ "version": "2.23.0", "license": "CC0-1.0" }, + "node_modules/memfs": { + "version": "4.38.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.38.1.tgz", + "integrity": "sha512-exfrOkkU3m0EpbQ0iQJP93HUbkprnIBU7IUnobSNAzHkBUzsklLwENGLEm8ZwJmMuLoFEfv1pYQ54wSpkay4kQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "devOptional": true, @@ -5909,6 +6730,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "devOptional": true, @@ -5949,6 +6791,30 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/multistream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-3.1.0.tgz", + "integrity": "sha512-zBgD3kn8izQAN/TaL1PCMv15vYpf+Vcrsfub06njuYVYlzUldzpopTlrEZ53pZVEbfn3Shtv7vRFoOv6LOV87Q==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^3.4.0" + } + }, + "node_modules/multistream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/napi-postinstall": { "version": "0.3.4", "devOptional": true, @@ -5979,6 +6845,36 @@ "node": ">= 0.4.0" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "devOptional": true, @@ -6117,6 +7013,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "license": "ISC", @@ -6235,6 +7140,22 @@ "devOptional": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "license": "MIT", @@ -6262,6 +7183,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "license": "MIT", @@ -6336,17 +7277,188 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.7", - "devOptional": true, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", "license": "MIT", - "engines": { - "node": ">= 6" + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "devOptional": true, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/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/pino-abstract-transport/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/pino-abstract-transport/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/pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/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/pino-pretty/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/pino-pretty/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/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pino/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "devOptional": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -6441,9 +7553,33 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", - "dev": true, + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT" }, "node_modules/prop-types": { @@ -6459,6 +7595,23 @@ "version": "16.13.1", "license": "MIT" }, + "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/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/proxy-agent": { "version": "6.5.0", "license": "MIT", @@ -6487,6 +7640,16 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "license": "MIT", @@ -6527,6 +7690,12 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "devOptional": true, @@ -6534,7 +7703,6 @@ }, "node_modules/readable-stream": { "version": "2.3.8", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -6548,9 +7716,17 @@ }, "node_modules/readable-stream/node_modules/isarray": { "version": "1.0.0", - "dev": true, "license": "MIT" }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "license": "MIT", @@ -6597,6 +7773,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "license": "MIT", @@ -6660,6 +7845,15 @@ "node": ">= 18.0.0" } }, + "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/reusify": { "version": "1.1.0", "license": "MIT", @@ -6823,7 +8017,6 @@ }, "node_modules/safe-buffer": { "version": "5.1.2", - "dev": true, "license": "MIT" }, "node_modules/safe-push-apply": { @@ -6855,6 +8048,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.7.4", "license": "ISC", @@ -6865,6 +8082,26 @@ "node": ">=10" } }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/sequin": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sequin/-/sequin-0.1.1.tgz", + "integrity": "sha512-hJWMZRwP75ocoBM+1/YaCsvS0j5MTPeBHJkS2/wruehl9xwtX30HlDF1Gt6UZ8HHHY8SJa2/IL+jo+JJCd59rA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "license": "MIT", @@ -6905,6 +8142,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "license": "MIT", @@ -7013,6 +8256,16 @@ "npm": ">= 3.0.0" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/socks": { "version": "2.8.7", "license": "MIT", @@ -7037,6 +8290,15 @@ "node": ">= 14" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "devOptional": true, @@ -7061,6 +8323,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "devOptional": true, @@ -7098,7 +8369,6 @@ }, "node_modules/string_decoder": { "version": "1.1.1", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -7419,6 +8689,31 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "license": "MIT", @@ -7458,6 +8753,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldts": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.27" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "devOptional": true, @@ -7473,6 +8786,40 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "license": "MIT", @@ -7545,6 +8892,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ts-retry-promise": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/ts-retry-promise/-/ts-retry-promise-0.8.1.tgz", + "integrity": "sha512-+AHPUmAhr5bSRRK5CurE9kNH8gZlEHnCgusZ0zy2bjfatUBDX0h6vGQjiT0YrGwSDwRZmU+bapeX6mj55FOPvg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "license": "MIT", @@ -7576,6 +8932,18 @@ "version": "2.8.1", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "license": "MIT", @@ -7816,6 +9184,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/uri-js": { "version": "4.4.1", "license": "BSD-2-Clause", @@ -7825,7 +9211,6 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -7867,6 +9252,45 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", @@ -8072,6 +9496,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "license": "MIT", @@ -8079,6 +9525,12 @@ "node": ">=8.0" } }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "license": "Apache-2.0" + }, "node_modules/y18n": { "version": "5.0.8", "devOptional": true, @@ -8161,6 +9613,258 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "packages/code-analyzer-apexguru-engine": { + "name": "@salesforce/code-analyzer-apexguru-engine", + "version": "0.36.0", + "license": "BSD-3-Clause", + "dependencies": { + "@salesforce/code-analyzer-engine-api": "0.36.0", + "@salesforce/core": "^8.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@types/jest": "^30.0.0", + "@types/node": "^20.0.0", + "eslint": "^9.39.2", + "jest": "^30.3.0", + "rimraf": "^6.1.3", + "ts-jest": "^29.4.6", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "packages/code-analyzer-apexguru-engine/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "packages/code-analyzer-core": { "name": "@salesforce/code-analyzer-core", "version": "0.45.0", @@ -8682,7 +10386,7 @@ }, "packages/code-analyzer-eslint-engine": { "name": "@salesforce/code-analyzer-eslint-engine", - "version": "0.41.0", + "version": "0.42.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@babel/preset-react": "^7.28.5", diff --git a/packages/code-analyzer-apexguru-engine/eslint.config.mjs b/packages/code-analyzer-apexguru-engine/eslint.config.mjs new file mode 100644 index 00000000..44661709 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/eslint.config.mjs @@ -0,0 +1,16 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + }] + } + } +); diff --git a/packages/code-analyzer-apexguru-engine/package.json b/packages/code-analyzer-apexguru-engine/package.json new file mode 100644 index 00000000..abf7d742 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/package.json @@ -0,0 +1,65 @@ +{ + "name": "@salesforce/code-analyzer-apexguru-engine", + "description": "ApexGuru Engine Package for the Salesforce Code Analyzer", + "version": "0.36.0-SNAPSHOT", + "author": "The Salesforce Code Analyzer Team", + "license": "BSD-3-Clause", + "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", + "repository": { + "type": "git", + "url": "git+https://github.com/forcedotcom/code-analyzer-core.git", + "directory": "packages/code-analyzer-apexguru-engine" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "dependencies": { + "@salesforce/code-analyzer-engine-api": "0.36.0", + "@salesforce/core": "^8.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@types/jest": "^30.0.0", + "@types/node": "^20.0.0", + "eslint": "^9.39.2", + "jest": "^30.3.0", + "rimraf": "^6.1.3", + "ts-jest": "^29.4.6", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "files": [ + "dist", + "LICENSE", + "package.json" + ], + "scripts": { + "build": "tsc --build tsconfig.build.json --verbose", + "test": "tsc --build tsconfig.json && jest --coverage", + "lint": "eslint src/**/*.ts", + "package": "npm pack", + "all": "npm run build && npm run lint && npm run test && npm run package", + "clean": "tsc --build tsconfig.build.json --clean", + "postclean": "rimraf dist && rimraf coverage && rimraf ./*.tgz", + "scrub": "npm run clean && rimraf node_modules", + "showcoverage": "open ./coverage/lcov-report/index.html" + }, + "jest": { + "testTimeout": 60000, + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": [ + "**/*.test.ts" + ], + "testPathIgnorePatterns": [ + "/node_modules/", + "/dist/" + ], + "collectCoverageFrom": [ + "src/**/*.ts", + "!src/index.ts" + ] + } +} diff --git a/packages/code-analyzer-apexguru-engine/src/apexguru-rules.ts b/packages/code-analyzer-apexguru-engine/src/apexguru-rules.ts new file mode 100644 index 00000000..43391edb --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/apexguru-rules.ts @@ -0,0 +1,175 @@ + + +import { RuleDescription, SeverityLevel, COMMON_TAGS } from '@salesforce/code-analyzer-engine-api'; + +/** + * Known ApexGuru rules with descriptions and metadata. + * + * This list should be updated when Salesforce adds new ApexGuru rules. + * Violations for rules NOT in this list will be mapped to the fallback "apexguru-other" rule. + */ +export const APEXGURU_RULES: RuleDescription[] = [ + // ================================================================================================================= + // PERFORMANCE RULES - HIGH SEVERITY (CRITICAL - RECOMMENDED) + // ================================================================================================================= + + { + name: 'SoqlInALoop', + severityLevel: SeverityLevel.High, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'SOQL query inside a loop causes performance issues and can hit governor limits', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_in_loop.htm&type=5'] + }, + + { + name: 'DmlInALoop', + severityLevel: SeverityLevel.High, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'DML statement inside a loop causes performance issues and can hit governor limits', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_dml_in_loop.htm&type=5'] + }, + + // ================================================================================================================= + // PERFORMANCE RULES - MODERATE SEVERITY (CRITICAL - RECOMMENDED) + // ================================================================================================================= + + { + name: 'SoqlWithoutAWhereClauseOrLimitStatement', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'SOQL query without WHERE clause or LIMIT statement can cause performance issues and heap size exceptions', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_without_where_clause_or_limit_statement.htm&type=5'] + }, + + { + name: 'SoqlWithWildcardFilter', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'SOQL query using LIKE with leading wildcard is inefficient and cannot use indexes', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_with_wildcard_filter.htm&type=5'] + }, + + { + name: 'SchemaGetGlobalDescribeNotEfficient', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Using Schema.getGlobalDescribe() causes unnecessary overhead and decreases performance', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_schema_getglobaldescribe_not_efficient.htm&type=5'] + }, + + // ================================================================================================================= + // PERFORMANCE RULES - MODERATE SEVERITY (PERFORMANCE ONLY - NOT RECOMMENDED) + // ================================================================================================================= + + { + name: 'Soql Aggregation', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Manual aggregation in Apex instead of using SOQL aggregate functions causes performance issues', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_aggregating_in_apex.htm&type=5'] + }, + + { + name: 'SoqlWithApexFilter', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Filtering records in Apex instead of using SOQL WHERE clause causes performance issues', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_with_apex_filter.htm&type=5'] + }, + + { + name: 'CopyingListOrSetElementsUsingAForLoop', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Copying list or set elements using a for loop is inefficient - use addAll() instead', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_copying_elements_with_for_loop.htm&type=5'] + }, + + { + name: 'Redundant Soql', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Multiple identical SOQL queries cause unnecessary database round trips', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_redundant_soql.htm&type=5'] + }, + + { + name: 'SoqlWithNegativeExpressions', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'SOQL queries using negative expressions (NOT IN, !=) don\'t use indexes and cause full table scans', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_with_negative_expressions.htm&type=5'] + }, + + { + name: 'SObjectMapInAForLoop', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.CATEGORIES.PERFORMANCE, COMMON_TAGS.LANGUAGES.APEX], + description: 'Building Map using .put() in a for loop is inefficient - use map constructor or putAll()', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_sobject_map_in_for_loop.htm&type=5'] + }, + + // ================================================================================================================= + // BEST PRACTICES - LOW SEVERITY (RECOMMENDED) + // ================================================================================================================= + + { + name: 'UsingTheTestMethodKeyword', + severityLevel: SeverityLevel.Low, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.APEX], + description: 'The testMethod keyword is deprecated - use @isTest annotation instead', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_test_case_antipattern_using_testmethod.htm&type=5'] + }, + + // ================================================================================================================= + // BEST PRACTICES - LOW SEVERITY (NOT RECOMMENDED) + // ================================================================================================================= + + { + name: 'SortingInApex', + severityLevel: SeverityLevel.Low, + tags: [COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.APEX], + description: 'Sorting records in Apex wastes CPU time and can exceed governor limits - use ORDER BY in SOQL', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_sorting_in_apex.htm&type=5'] + }, + + { + name: 'BusyLoopDelay', + severityLevel: SeverityLevel.Low, + tags: [COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.APEX], + description: 'Using empty loops to delay execution wastes CPU time - use System.enqueueJob with delay parameter', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_busy_loop_delay.htm&type=5'] + }, + + { + name: 'SoqlWithUnusedFields', + severityLevel: SeverityLevel.Low, + tags: [COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.APEX], + description: 'SOQL query selecting unused fields increases resource consumption unnecessarily', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru_antipattern_soql_with_unused_fields.htm&type=5'] + }, + + // ================================================================================================================= + // FALLBACK RULE + // ================================================================================================================= + + { + name: 'apexguru-other', + severityLevel: SeverityLevel.Moderate, + tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.APEX], + description: 'Other ApexGuru rules - covers new rules added by Salesforce that are not yet explicitly declared', + resourceUrls: ['https://help.salesforce.com/s/articleView?id=xcloud.apexguru.htm'] + } +]; + +/** + * Helper to check if a rule name is known + */ +export function isKnownRule(ruleName: string): boolean { + return APEXGURU_RULES.some(rule => rule.name === ruleName); +} + +/** + * Fallback rule name for unknown violations + */ +export const FALLBACK_RULE_NAME = 'apexguru-other'; diff --git a/packages/code-analyzer-apexguru-engine/src/config.ts b/packages/code-analyzer-apexguru-engine/src/config.ts new file mode 100644 index 00000000..2fa7b989 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/config.ts @@ -0,0 +1,138 @@ +import { ConfigDescription, ConfigValueExtractor } from '@salesforce/code-analyzer-engine-api'; + +/** + * Configuration for ApexGuru Engine + * Currently minimal - authentication is handled via SF CLI + */ +export type ApexGuruEngineConfig = { + /** + * Maximum time to wait for ApexGuru API response (in milliseconds) + * Default: 120000 (2 minutes) + */ + api_timeout_ms: number; + + /** + * Initial retry delay for polling (in milliseconds) + * Default: 2000 (2 seconds) + */ + api_initial_retry_ms: number; + + /** + * Maximum retry delay for polling (in milliseconds) + * Exponential backoff will not exceed this value + * Default: 60000 (60 seconds) + */ + api_max_retry_ms: number; + + /** + * Backoff multiplier for exponential backoff polling + * Each retry delay is multiplied by this value (e.g., 2x = 2s, 4s, 8s, 16s...) + * Default: 2 + */ + api_backoff_multiplier: number; +}; + +/** + * Default configuration values + */ +export const DEFAULT_APEXGURU_ENGINE_CONFIG: ApexGuruEngineConfig = { + api_timeout_ms: 120000, // 2 minutes + api_initial_retry_ms: 2000, // 2 seconds + api_max_retry_ms: 60000, // 60 seconds + api_backoff_multiplier: 2 // 2x exponential backoff +}; + +/** + * Configuration schema description for ApexGuru Engine + */ +export const APEXGURU_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = { + overview: 'Configuration for ApexGuru Engine. Authentication is handled via Salesforce CLI (sf org login web).', + fieldDescriptions: { + api_timeout_ms: { + descriptionText: 'Maximum time to wait for ApexGuru API response (in milliseconds). Default: 120000 (2 minutes)', + valueType: 'number', + defaultValue: 120000 + }, + api_initial_retry_ms: { + descriptionText: 'Initial retry delay for polling ApexGuru API (in milliseconds). Default: 2000 (2 seconds)', + valueType: 'number', + defaultValue: 2000 + }, + api_max_retry_ms: { + descriptionText: 'Maximum retry delay for polling (in milliseconds). Exponential backoff will not exceed this value. Default: 60000 (60 seconds)', + valueType: 'number', + defaultValue: 60000 + }, + api_backoff_multiplier: { + descriptionText: 'Backoff multiplier for exponential backoff polling. Each retry delay is multiplied by this value. Default: 2', + valueType: 'number', + defaultValue: 2 + } + } +}; + +/** + * Validates and normalizes ApexGuru engine configuration + */ +export async function validateAndNormalizeConfig( + configValueExtractor: ConfigValueExtractor +): Promise { + // Validate only expected keys are present + configValueExtractor.validateContainsOnlySpecifiedKeys([ + 'api_timeout_ms', + 'api_initial_retry_ms', + 'api_max_retry_ms', + 'api_backoff_multiplier' + ]); + + // Extract and validate timeout + const apiTimeoutMs: number = configValueExtractor.extractNumber( + 'api_timeout_ms', + DEFAULT_APEXGURU_ENGINE_CONFIG.api_timeout_ms + ) ?? DEFAULT_APEXGURU_ENGINE_CONFIG.api_timeout_ms; + + if (apiTimeoutMs <= 0) { + throw new Error('api_timeout_ms must be greater than 0'); + } + + // Extract and validate initial retry delay + const apiInitialRetryMs: number = configValueExtractor.extractNumber( + 'api_initial_retry_ms', + DEFAULT_APEXGURU_ENGINE_CONFIG.api_initial_retry_ms + ) ?? DEFAULT_APEXGURU_ENGINE_CONFIG.api_initial_retry_ms; + + if (apiInitialRetryMs <= 0) { + throw new Error('api_initial_retry_ms must be greater than 0'); + } + + // Extract and validate max retry delay + const apiMaxRetryMs: number = configValueExtractor.extractNumber( + 'api_max_retry_ms', + DEFAULT_APEXGURU_ENGINE_CONFIG.api_max_retry_ms + ) ?? DEFAULT_APEXGURU_ENGINE_CONFIG.api_max_retry_ms; + + if (apiMaxRetryMs <= 0) { + throw new Error('api_max_retry_ms must be greater than 0'); + } + + if (apiMaxRetryMs < apiInitialRetryMs) { + throw new Error('api_max_retry_ms must be greater than or equal to api_initial_retry_ms'); + } + + // Extract and validate backoff multiplier + const apiBackoffMultiplier: number = configValueExtractor.extractNumber( + 'api_backoff_multiplier', + DEFAULT_APEXGURU_ENGINE_CONFIG.api_backoff_multiplier + ) ?? DEFAULT_APEXGURU_ENGINE_CONFIG.api_backoff_multiplier; + + if (apiBackoffMultiplier < 1) { + throw new Error('api_backoff_multiplier must be greater than or equal to 1'); + } + + return { + api_timeout_ms: apiTimeoutMs, + api_initial_retry_ms: apiInitialRetryMs, + api_max_retry_ms: apiMaxRetryMs, + api_backoff_multiplier: apiBackoffMultiplier + }; +} diff --git a/packages/code-analyzer-apexguru-engine/src/constants.ts b/packages/code-analyzer-apexguru-engine/src/constants.ts new file mode 100644 index 00000000..8a58d264 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/constants.ts @@ -0,0 +1,17 @@ + + +/** + * Engine name + */ +export const ENGINE_NAME = 'apexguru'; + +/** + * File extensions that ApexGuru can analyze + * ApexGuru supports Apex class files (.cls) and trigger files (.trigger) + */ +export const APEXGURU_FILE_EXTENSIONS = ['.cls', '.trigger']; + +/** + * Display name for supported language + */ +export const SUPPORTED_LANGUAGE = 'Apex'; diff --git a/packages/code-analyzer-apexguru-engine/src/engine.ts b/packages/code-analyzer-apexguru-engine/src/engine.ts new file mode 100644 index 00000000..66fca426 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/engine.ts @@ -0,0 +1,294 @@ + + +import { + Engine, + EngineEventEmitter, + DescribeOptions, + RunOptions, + RuleDescription, + EngineRunResults, + Violation, + CodeLocation, + Fix, + Suggestion, + LogLevel +} from '@salesforce/code-analyzer-engine-api'; +import { ApexGuruService } from './services/ApexGuruService'; +import { ApexGuruViolation, ApexGuruLocation, ApexGuruFix, 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'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; + +/** + * ApexGuru Engine implementation + * Analyzes Apex classes using Salesforce ApexGuru APIs + */ +export class ApexGuruEngine extends EngineEventEmitter implements Engine { + private readonly apexGuruService: ApexGuruService; + private readonly config: ApexGuruEngineConfig; + + constructor(config: ApexGuruEngineConfig = DEFAULT_APEXGURU_ENGINE_CONFIG) { + super(); + this.config = config; + this.apexGuruService = new ApexGuruService( + this.emitLogEvent.bind(this), + config.api_timeout_ms, + config.api_initial_retry_ms, + config.api_max_retry_ms, + config.api_backoff_multiplier + ); + } + + getName(): string { + return ENGINE_NAME; + } + + async getEngineVersion(): Promise { + const pathToPackageJson: string = path.join(__dirname, '..', 'package.json'); + const packageJson: {version: string} = JSON.parse(await fs.readFile(pathToPackageJson, 'utf-8')); + return packageJson.version; + } + + async describeRules(describeOptions: DescribeOptions): Promise { + this.emitDescribeRulesProgressEvent(0); + + // Check if targeted files contain any Apex files + if (describeOptions.workspace) { + const targetedFiles = await describeOptions.workspace.getTargetedFiles(); + const hasApexFiles = targetedFiles.some(file => this.isApexFile(path.basename(file))); + + if (!hasApexFiles) { + this.emitLogEvent(LogLevel.Debug, 'No Apex files in target set. Returning no ApexGuru rules.'); + this.emitDescribeRulesProgressEvent(100); + return []; + } + } + + // ApexGuru is dynamic - new rules can be added by Salesforce at any time. + // We declare known rules explicitly (in apexguru-rules.ts), plus a fallback rule. + // Unknown violations from the API will be mapped to "apexguru-other". + this.emitDescribeRulesProgressEvent(100); + + return APEXGURU_RULES; + } + + async runRules(ruleNames: string[], runOptions: RunOptions): Promise { + // Short-circuit if no rules selected - avoid unnecessary auth/network calls + if (ruleNames.length === 0) { + return { violations: [] }; + } + + // Note: ApexGuru API analyzes code 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. + + // Create a Set for faster rule name lookup + const selectedRulesSet = new Set(ruleNames); + + // Extract targetOrg from environment + const targetOrg = this.getTargetOrgFromEnvironment(); + + // Initialize authentication + try { + await this.apexGuruService.initialize(targetOrg); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Failed to authenticate: ${message}\n` + + 'Please authenticate with: sf org login web' + ); + } + + // 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 targeted files from workspace and filter for 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 + return { violations: [] }; + } + + try { + // Analyze each file + const allViolations: Violation[] = []; + 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 apexGuruViolations: ApexGuruViolation[] = await this.apexGuruService.analyzeApexClass( + fileContent, + filePath + ); + + 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 + } + } + + return { violations: allViolations }; + } finally { + // Always cleanup resources to allow process to exit + this.apexGuruService.cleanup(); + } + } + + /** + * Check if file is an Apex file based on extension + */ + private isApexFile(fileName: string): boolean { + return APEXGURU_FILE_EXTENSIONS.some(ext => fileName.toLowerCase().endsWith(ext)); + } + + /** + * 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. + */ + private getTargetOrgFromEnvironment(): string | undefined { + // Check environment variable + if (process.env.SF_TARGET_ORG) { + return process.env.SF_TARGET_ORG; + } + + // Return undefined to use default org from SF CLI + return undefined; + } + +} + +/** + * Convert ApexGuru violation to Code Analyzer violation format + * + * Note: Violations do not include severity/tags in Code Analyzer's data model. + * Severity and tags are defined in RuleDescription (from describeRules()). + * + * For unknown rules (not in apexguru-rules.ts), violations are mapped to the + * fallback rule "apexguru-other" to ensure Core validation passes. + */ +function toViolation( + av: ApexGuruViolation, + filePath: string, + includeFixes: boolean, + includeSuggestions: boolean +): Violation { + // Map unknown rules to fallback to ensure Core validation passes + const ruleName = isKnownRule(av.rule) ? av.rule : FALLBACK_RULE_NAME; + + const violation: Violation = { + ruleName, + message: av.message, + codeLocations: av.locations.map(loc => normalizeLocation(loc, filePath)), + primaryLocationIndex: av.primaryLocationIndex, + 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)); + } + + 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 + */ +function toSuggestion(apexGuruSuggestion: ApexGuruSuggestion, filePath: string): Suggestion { + return { + location: normalizeLocation(apexGuruSuggestion.location, filePath), + message: apexGuruSuggestion.message // Keep "// explanation\ncode" as-is + }; +} + +/** + * Normalize location by filling in required fields + * + * ApexGuru API only provides: + * - startLine (required) + * - comment (optional) + * + * We fill in: + * - file (required by Code Analyzer, not in ApexGuru response) + * - startColumn = 1 (required by Code Analyzer, reasonable default) + * - endLine/endColumn are left undefined (optional fields) + */ +function normalizeLocation(location: ApexGuruLocation, filePath: string): CodeLocation { + const startLine = location.startLine ?? 1; + const startColumn = location.startColumn ?? 1; // Default to column 1 if not provided + + return { + file: filePath, + startLine, + startColumn, + endLine: location.endLine, // undefined if not provided (optional) + endColumn: location.endColumn, // undefined if not provided (optional) + comment: location.comment + }; +} diff --git a/packages/code-analyzer-apexguru-engine/src/index.ts b/packages/code-analyzer-apexguru-engine/src/index.ts new file mode 100644 index 00000000..01ed313f --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/index.ts @@ -0,0 +1,21 @@ + + +import { EnginePlugin } from '@salesforce/code-analyzer-engine-api'; +import { ApexGuruEnginePlugin } from './plugin'; + +/** + * Factory function to create the ApexGuru engine plugin + * This is the entry point for dynamic loading by Code Analyzer + */ +function createEnginePlugin(): EnginePlugin { + return new ApexGuruEnginePlugin(); +} + +// Export plugin factory and plugin class +export { createEnginePlugin, ApexGuruEnginePlugin }; + +// Export engine and supporting classes for direct usage +export { ApexGuruEngine } from './engine'; +export { ApexGuruService } from './services/ApexGuruService'; +export { ApexGuruAuthService } from './services/ApexGuruAuthService'; +export * from './types'; diff --git a/packages/code-analyzer-apexguru-engine/src/plugin.ts b/packages/code-analyzer-apexguru-engine/src/plugin.ts new file mode 100644 index 00000000..617beebb --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/plugin.ts @@ -0,0 +1,65 @@ + + +import { + ConfigDescription, + ConfigObject, + ConfigValueExtractor, + Engine, + EnginePluginV1 +} from '@salesforce/code-analyzer-engine-api'; +import { ApexGuruEngine } from './engine'; +import { ENGINE_NAME } from './constants'; +import { + APEXGURU_ENGINE_CONFIG_DESCRIPTION, + ApexGuruEngineConfig, + validateAndNormalizeConfig +} from './config'; + +/** + * Engine Plugin for ApexGuru + * Provides factory methods to create and configure ApexGuru engine instances + */ +export class ApexGuruEnginePlugin extends EnginePluginV1 { + /** + * Returns the name of the engine this plugin provides + */ + getAvailableEngineNames(): string[] { + return [ENGINE_NAME]; + } + + /** + * Describes the configuration schema for ApexGuru engine + */ + describeEngineConfig(engineName: string): ConfigDescription { + if (engineName !== ENGINE_NAME) { + throw new Error(`Unsupported engine name: ${engineName}`); + } + + return APEXGURU_ENGINE_CONFIG_DESCRIPTION; + } + + /** + * Creates and validates engine configuration + */ + async createEngineConfig( + engineName: string, + configValueExtractor: ConfigValueExtractor + ): Promise { + if (engineName !== ENGINE_NAME) { + throw new Error(`Unsupported engine name: ${engineName}`); + } + + return await validateAndNormalizeConfig(configValueExtractor) as ConfigObject; + } + + /** + * Creates an instance of the ApexGuru engine + */ + async createEngine(engineName: string, engineConfig: ConfigObject): Promise { + if (engineName !== ENGINE_NAME) { + throw new Error(`Unsupported engine name: ${engineName}`); + } + + return new ApexGuruEngine(engineConfig as ApexGuruEngineConfig); + } +} diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts new file mode 100644 index 00000000..9b81022d --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts @@ -0,0 +1,89 @@ + + +import { AuthInfo, Connection } from '@salesforce/core'; +import { LogLevel } from '@salesforce/code-analyzer-engine-api'; +import { AuthConfig } from '../types'; + +/** + * TEMPORARY: Hardcoded credentials for testing + * TODO: Implement SF CLI, env vars, and OAuth in future PR + * NEVER commit real credentials - use 'YOUR_ACCESS_TOKEN_HERE' as placeholder + */ +const HARDCODED_ACCESS_TOKEN = 'YOUR_ACCESS_TOKEN_HERE'; // Get from: sf org display --verbose +const HARDCODED_INSTANCE_URL = 'https://yourorg.my.salesforce.com'; // e.g., https://yourorg.my.salesforce.com + +/** + * Handles authentication to Salesforce orgs for ApexGuru API access + * TODO: Currently uses hardcoded credentials only. Implement proper auth in future PR. + */ +export class ApexGuruAuthService { + private connection?: Connection; + private readonly emitLogEvent: (logLevel: LogLevel, message: string) => void; + + constructor(emitLogEvent: (logLevel: LogLevel, message: string) => void = () => {}) { + this.emitLogEvent = emitLogEvent; + } + + /** + * Initialize connection to Salesforce org + * TODO: Implement SF CLI, env vars, and OAuth in future PR + * @param _config - Auth configuration (currently unused, for future implementation) + */ + async initialize(_config: AuthConfig): Promise { + // Use hardcoded credentials (temporary implementation) + this.emitLogEvent(LogLevel.Warn, '⚠️ Using HARDCODED authentication credentials (for testing)'); + + // Validate that credentials were actually set + if (!HARDCODED_ACCESS_TOKEN || HARDCODED_ACCESS_TOKEN.includes('YOUR_ACCESS_TOKEN')) { + throw new Error( + 'Hardcoded credentials not set! Edit ApexGuruAuthService.ts and set:\n' + + ' - HARDCODED_ACCESS_TOKEN (get from: sf org display --verbose)\n' + + ' - HARDCODED_INSTANCE_URL (e.g., https://yourorg.my.salesforce.com)' + ); + } + + this.connection = await Connection.create({ + authInfo: await AuthInfo.create({ + accessTokenOptions: { + accessToken: HARDCODED_ACCESS_TOKEN, + instanceUrl: HARDCODED_INSTANCE_URL + } + }) + }); + } + + /** + * Get the connection object (has access token and instance URL built-in) + */ + getConnection(): Connection { + if (!this.connection) { + throw new Error('Auth service not initialized. Call initialize() first.'); + } + return this.connection; + } + + /** + * Get access token directly (for debugging or direct API calls) + */ + getAccessToken(): string { + const token = this.getConnection().accessToken; + if (!token) { + throw new Error('Access token not available'); + } + return token; + } + + /** + * Get instance URL (for debugging or direct API calls) + */ + getInstanceUrl(): string { + return this.getConnection().instanceUrl; + } + + /** + * Get API version from connection + */ + getApiVersion(): string { + return this.getConnection().version || '64.0'; + } +} diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts new file mode 100644 index 00000000..0260a67f --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -0,0 +1,279 @@ + + +import { Connection } from '@salesforce/core'; +import { LogLevel } from '@salesforce/code-analyzer-engine-api'; +import { ApexGuruAuthService } from './ApexGuruAuthService'; +import { + ApexGuruInitialResponse, + ApexGuruQueryResponse, + ApexGuruResponseStatus, + ApexGuruViolation +} from '../types'; +import * as http from 'node:http'; +import * as https from 'node:https'; + +/** + * Service for interacting with ApexGuru APIs + */ +export class ApexGuruService { + private readonly authService: ApexGuruAuthService; + private readonly emitLogEvent: (logLevel: LogLevel, message: string) => void; + private readonly maxTimeoutMs: number; + private readonly initialRetryMs: number; + private readonly maxRetryMs: number; + private readonly backoffMultiplier: number; + private progressCallback?: (progress: number) => void; + private isCancelled = false; + + constructor( + emitLogEvent: (logLevel: LogLevel, message: string) => void, + maxTimeoutMs: number, + initialRetryMs: number, + maxRetryMs: number, + backoffMultiplier: number + ) { + this.authService = new ApexGuruAuthService(emitLogEvent); + this.emitLogEvent = emitLogEvent; + this.maxTimeoutMs = maxTimeoutMs; + this.initialRetryMs = initialRetryMs; + this.maxRetryMs = maxRetryMs; + this.backoffMultiplier = backoffMultiplier; + } + + /** + * Initialize authentication + */ + async initialize(targetOrg?: string): Promise { + await this.authService.initialize({ targetOrg }); + } + + /** + * Set progress callback for polling updates + */ + setProgressCallback(callback: (progress: number) => void): void { + this.progressCallback = callback; + } + + /** + * 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(): 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 + } + } + + /** + * Validate ApexGuru access + * Throws error with specific context if validation fails + */ + async validate(): Promise { + let timeoutId: NodeJS.Timeout; + const validatePromise = this.performValidate(); + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error(`Validate request timed out after ${this.maxTimeoutMs}ms`)), this.maxTimeoutMs); + }); + + try { + await Promise.race([validatePromise, timeoutPromise]); + } finally { + clearTimeout(timeoutId!); + } + } + + /** + * Internal validate implementation (without timeout wrapper) + */ + 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; + } + + 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.' + ); + } + + /** + * 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 { + 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); + }); + + try { + return await Promise.race([analysisPromise, timeoutPromise]); + } finally { + clearTimeout(timeoutId!); + } + } + + /** + * Internal analysis implementation (without timeout wrapper) + * Performs submit + poll + */ + private async performAnalysis(classContent: string): Promise { + // Step 1: Submit request + const requestId = await this.submitAnalysis(classContent); + + // Step 2: Poll for results + const violations = await this.pollForResults(requestId); + + return violations; + } + + /** + * Submit Apex class for analysis + */ + 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`; + + const base64Content = Buffer.from(classContent, 'utf-8').toString('base64'); + const requestBody = { classContent: base64Content }; + + try { + const response: ApexGuruInitialResponse = await connection.request({ + method: 'POST', + url, + body: JSON.stringify(requestBody), + headers: { 'Content-Type': 'application/json' } + }); + + // Normalize status to lowercase + if (response.status) { + response.status = response.status.toLowerCase(); + } + + 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'; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to submit analysis request: ${message}`); + } + } + + /** + * Poll for analysis results with exponential backoff + * Note: Timeout is handled by analyzeApexClass wrapper, not here + */ + private async pollForResults(requestId: string): Promise { + 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'); + } + + if (attempts > 0) { + await this.sleep(delay); + } + + attempts++; + + // Emit asymptotic progress (approaches 95% but never quite reaches it) + // Formula: 95 * (1 - e^(-attempts/4)) + if (this.progressCallback) { + const asymptoticProgress = 95 * (1 - Math.exp(-attempts / 4)); + 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) { + return this.parseReport(response.report); + } + + // Check for failures + if (response.status === ApexGuruResponseStatus.FAILED) { + throw new Error(`Analysis failed: ${response.message || 'Unknown error'}`); + } + + 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); + } + } + + /** + * Parse Base64-encoded report + */ + 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'); + } + + return violations; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to parse ApexGuru report: ${message}`); + } + } + + /** + * Sleep utility for polling + */ + 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 new file mode 100644 index 00000000..3c0f90da --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/src/types/index.ts @@ -0,0 +1,101 @@ + + +/** + * Configuration for ApexGuru authentication + */ +export type AuthConfig = { + /** SF org alias or username (e.g., 'myorg', 'user@example.com') */ + targetOrg?: string; + + /** Direct access token (for CI/CD environments) */ + accessToken?: string; + + /** Direct instance URL (for CI/CD environments) */ + instanceUrl?: string; +}; + +/** + * ApexGuru API response statuses + */ +export enum ApexGuruResponseStatus { + NEW = "new", + PROCESSING = "processing", + SUCCESS = "success", + FAILED = "failed", + ERROR = "error" +} + +/** + * Base ApexGuru API response + */ +export type ApexGuruResponse = { + status: string; + message?: string; +}; + +/** + * Response from initial POST /apexguru/request + */ +export type ApexGuruInitialResponse = ApexGuruResponse & { + requestId?: string; +}; + +/** + * Response from GET /apexguru/request/{id} + */ +export type ApexGuruQueryResponse = ApexGuruResponse & { + report?: string; // Base64 encoded JSON array of violations +}; + +/** + * ApexGuru violation structure (matches API response) + */ +export type ApexGuruViolation = { + rule: string; + message: string; + locations: ApexGuruLocation[]; + primaryLocationIndex: number; + resources: string[]; + severity: number; + suggestions?: ApexGuruSuggestion[]; + fixes?: ApexGuruFix[]; + metadata?: { + original_code: string; + class_name: string; + category: string; + }; +}; + +/** + * Location in ApexGuru response (no file field) + */ +export type ApexGuruLocation = { + startLine: number; + startColumn?: number; + endLine?: number; + endColumn?: number; + comment?: string; +}; + +/** + * Suggestion in ApexGuru response + */ +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 +}; diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts new file mode 100644 index 00000000..40ae30e6 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts @@ -0,0 +1,58 @@ +import { ApexGuruAuthService } from '../src/services/ApexGuruAuthService'; + +// Mock @salesforce/core +jest.mock('@salesforce/core'); + +describe('ApexGuruAuthService', () => { + let authService: ApexGuruAuthService; + let mockEmitLogEvent: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + mockEmitLogEvent = jest.fn(); + authService = new ApexGuruAuthService(mockEmitLogEvent); + }); + + describe('initialize', () => { + it('should throw error when hardcoded credentials not set', async () => { + // Placeholder credentials should trigger error + await expect(authService.initialize({})) + .rejects.toThrow('Hardcoded credentials not set'); + }); + + // TODO: Add tests for proper auth methods when implemented + // - SF CLI integration + // - Environment variables + // - OAuth flow + }); + + describe('getConnection', () => { + it('should throw error if not initialized', () => { + expect(() => authService.getConnection()) + .toThrow('Auth service not initialized'); + }); + }); + + describe('getAccessToken', () => { + it('should throw error if not initialized', () => { + expect(() => authService.getAccessToken()) + .toThrow('Auth service not initialized'); + }); + }); + + describe('getInstanceUrl', () => { + it('should throw error if not initialized', () => { + expect(() => authService.getInstanceUrl()) + .toThrow('Auth service not initialized'); + }); + }); + + describe('getApiVersion', () => { + it('should throw error if not initialized', () => { + expect(() => authService.getApiVersion()) + .toThrow('Auth service not initialized'); + }); + + // TODO: Add test for getApiVersion with mock connection when proper auth is implemented + }); +}); diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts new file mode 100644 index 00000000..3d556f35 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruEngine.test.ts @@ -0,0 +1,429 @@ +import { ApexGuruEngine } from '../src/engine'; +import { ApexGuruService } from '../src/services/ApexGuruService'; +import { RunOptions, Workspace } from '@salesforce/code-analyzer-engine-api'; +import * as fs from 'node:fs/promises'; + +// Mock dependencies +jest.mock('../src/services/ApexGuruService'); +jest.mock('node:fs/promises'); + +describe('ApexGuruEngine', () => { + let engine: ApexGuruEngine; + let mockApexGuruService: jest.Mocked; + let mockWorkspace: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + mockApexGuruService = { + initialize: jest.fn(), + validate: jest.fn(), + analyzeApexClass: jest.fn(), + cleanup: jest.fn(), + setProgressCallback: jest.fn() + } as any; + + (ApexGuruService as jest.Mock).mockImplementation(() => mockApexGuruService); + + mockWorkspace = { + getTargetedFiles: jest.fn(), + getAllFilesAndFolders: jest.fn(), + getWorkspaceId: jest.fn().mockReturnValue('test-workspace'), + targetOrg: undefined + } as any; + + engine = new ApexGuruEngine(); + }); + + describe('getName', () => { + it('should return engine name', () => { + expect(engine.getName()).toBe('apexguru'); + }); + }); + + describe('getEngineVersion', () => { + it('should return version from package.json', async () => { + (fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({ version: '1.2.3' })); + + const version = await engine.getEngineVersion(); + + expect(version).toBe('1.2.3'); + }); + }); + + describe('describeRules', () => { + it('should return all ApexGuru rules', async () => { + const rules = await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working' + }); + + expect(rules.length).toBeGreaterThan(0); + expect(rules.find(r => r.name === 'SoqlInALoop')).toBeDefined(); + expect(rules.find(r => r.name === 'DmlInALoop')).toBeDefined(); + }); + + it('should emit progress events', async () => { + const progressSpy = jest.spyOn(engine as any, 'emitDescribeRulesProgressEvent'); + + await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working' + }); + + expect(progressSpy).toHaveBeenCalledWith(0); + expect(progressSpy).toHaveBeenCalledWith(100); + }); + + it('should return empty array when workspace has no Apex files', async () => { + mockWorkspace.getTargetedFiles = jest.fn().mockResolvedValue([ + '/project/js/app.js', + '/project/js/utils.js', + '/project/css/styles.css' + ]); + + const rules = await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working', + workspace: mockWorkspace + }); + + expect(rules).toEqual([]); + expect(mockWorkspace.getTargetedFiles).toHaveBeenCalled(); + }); + + it('should return all rules when workspace has Apex files', async () => { + mockWorkspace.getTargetedFiles = jest.fn().mockResolvedValue([ + '/project/classes/Account.cls', + '/project/js/app.js' + ]); + + const rules = await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working', + workspace: mockWorkspace + }); + + expect(rules.length).toBeGreaterThan(0); + expect(rules.find(r => r.name === 'SoqlInALoop')).toBeDefined(); + }); + + it('should return all rules when workspace has trigger files', async () => { + mockWorkspace.getTargetedFiles = jest.fn().mockResolvedValue([ + '/project/triggers/AccountTrigger.trigger', + '/project/js/app.js' + ]); + + const rules = await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working', + workspace: mockWorkspace + }); + + expect(rules.length).toBeGreaterThan(0); + expect(rules.find(r => r.name === 'DmlInALoop')).toBeDefined(); + }); + + it('should return all rules when no workspace provided', async () => { + const rules = await engine.describeRules({ + logFolder: '/tmp/logs', + workingFolder: '/tmp/working' + // No workspace + }); + + expect(rules.length).toBeGreaterThan(0); + expect(rules.find(r => r.name === 'SoqlInALoop')).toBeDefined(); + }); + }); + + describe('runRules', () => { + let mockRunOptions: RunOptions; + + beforeEach(() => { + mockRunOptions = { + workspace: mockWorkspace, + logFolder: '/tmp/logs', + workingFolder: '/tmp/working', + includeSuggestions: false + }; + mockApexGuruService.initialize.mockResolvedValue(); + mockApexGuruService.validate.mockResolvedValue(); + }); + + it('should return empty results immediately when no rules selected', async () => { + const results = await engine.runRules([], mockRunOptions); + + expect(results.violations).toEqual([]); + expect(mockApexGuruService.initialize).not.toHaveBeenCalled(); + expect(mockApexGuruService.validate).not.toHaveBeenCalled(); + expect(mockWorkspace.getTargetedFiles).not.toHaveBeenCalled(); + }); + + it('should authenticate and validate', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.initialize).toHaveBeenCalledWith(undefined); + expect(mockApexGuruService.validate).toHaveBeenCalled(); + }); + + it('should throw error if authentication fails', async () => { + mockApexGuruService.initialize.mockRejectedValue(new Error('Auth failed')); + + await expect(engine.runRules(['SoqlInALoop'], mockRunOptions)) + .rejects.toThrow('Failed to authenticate'); + }); + + it('should throw error if validation fails', async () => { + mockApexGuruService.validate.mockRejectedValue(new Error('ApexGuru is not available for this org')); + + await expect(engine.runRules(['SoqlInALoop'], mockRunOptions)) + .rejects.toThrow('Failed to validate ApexGuru access'); + }); + + it('should return empty results if no Apex files found', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue([ + '/test/Test.js', + '/test/Test.java' + ]); + + const results = await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(results.violations).toEqual([]); + expect(mockApexGuruService.cleanup).toHaveBeenCalled(); + }); + + it('should analyze Apex class files', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue([ + '/test/Test.cls', + '/test/Controller.cls' + ]); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.analyzeApexClass).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledWith('/test/Test.cls', 'utf-8'); + expect(fs.readFile).toHaveBeenCalledWith('/test/Controller.cls', 'utf-8'); + }); + + it('should filter violations by selected rules', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + + // ApexGuru API returns 3 violations but only 2 match selected rules + mockApexGuruService.analyzeApexClass.mockResolvedValue([ + { + rule: 'SoqlInALoop', + message: 'SOQL in loop', + locations: [{ startLine: 10 }], + primaryLocationIndex: 0, + severity: 1, + resources: [] + }, + { + rule: 'DmlInALoop', + message: 'DML in loop', + locations: [{ startLine: 20 }], + primaryLocationIndex: 0, + severity: 1, + resources: [] + }, + { + rule: 'SoqlWithWildcardFilter', + message: 'Wildcard filter', + locations: [{ startLine: 30 }], + primaryLocationIndex: 0, + severity: 2, + resources: [] + } + ]); + + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + const results = await engine.runRules( + ['SoqlInALoop', 'DmlInALoop'], + mockRunOptions + ); + + expect(results.violations).toHaveLength(2); + expect(results.violations.find(v => v.ruleName === 'SoqlInALoop')).toBeDefined(); + expect(results.violations.find(v => v.ruleName === 'DmlInALoop')).toBeDefined(); + expect(results.violations.find(v => v.ruleName === 'SoqlWithWildcardFilter')).toBeUndefined(); + }); + + it('should include suggestions when includeSuggestions is true', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([ + { + rule: 'SoqlInALoop', + message: 'SOQL in loop', + locations: [{ startLine: 10 }], + primaryLocationIndex: 0, + severity: 1, + resources: [], + suggestions: [ + { + location: { startLine: 10 }, + message: '// Move query outside loop\nList accounts = [SELECT Id FROM Account];' + } + ] + } + ]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + const results = await engine.runRules(['SoqlInALoop'], { + ...mockRunOptions, + includeSuggestions: true + }); + + expect(results.violations).toHaveLength(1); + expect(results.violations[0].suggestions).toBeDefined(); + expect(results.violations[0].suggestions?.length).toBe(1); + }); + + it('should emit progress events', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + const progressSpy = jest.spyOn(engine as any, 'emitRunRulesProgressEvent'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(progressSpy).toHaveBeenCalled(); + expect(progressSpy.mock.calls.length).toBeGreaterThan(0); + }); + + it('should set progress callback on ApexGuru service', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.setProgressCallback).toHaveBeenCalled(); + }); + + it('should continue analyzing after single file failure', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue([ + '/test/Test1.cls', + '/test/Test2.cls', + '/test/Test3.cls' + ]); + + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + // Second file fails + mockApexGuruService.analyzeApexClass + .mockResolvedValueOnce([]) + .mockRejectedValueOnce(new Error('Analysis failed')) + .mockResolvedValueOnce([]); + + + const results = await engine.runRules(['SoqlInALoop'], mockRunOptions); + + // Should still return results from files 1 and 3 + expect(mockApexGuruService.analyzeApexClass).toHaveBeenCalledTimes(3); + expect(results.violations).toBeDefined(); + }); + + it('should always cleanup resources', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.cleanup).toHaveBeenCalled(); + }); + + it('should cleanup even when error occurs within try block', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + // Make analyzeApexClass throw an error + mockApexGuruService.analyzeApexClass.mockRejectedValue(new Error('Fatal API error')); + + // Even though analysis fails, cleanup should still be called + const result = await engine.runRules(['SoqlInALoop'], mockRunOptions); + + // The engine catches individual file errors and continues, so result is returned + expect(result.violations).toEqual([]); + expect(mockApexGuruService.cleanup).toHaveBeenCalled(); + }); + + it('should handle .trigger files', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue([ + '/test/AccountTrigger.trigger' + ]); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('trigger AccountTrigger on Account {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.analyzeApexClass).toHaveBeenCalled(); + }); + + it('should extract targetOrg from environment', async () => { + process.env.SF_TARGET_ORG = 'my-org'; + + mockWorkspace.getTargetedFiles.mockResolvedValue(['/test/Test.cls']); + mockApexGuruService.analyzeApexClass.mockResolvedValue([]); + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + await engine.runRules(['SoqlInALoop'], mockRunOptions); + + expect(mockApexGuruService.initialize).toHaveBeenCalledWith('my-org'); + + delete process.env.SF_TARGET_ORG; + }); + + it('should aggregate violations from multiple files', async () => { + mockWorkspace.getTargetedFiles.mockResolvedValue([ + '/test/Test1.cls', + '/test/Test2.cls' + ]); + + // First file returns 1 violation, second file returns 2 violations + mockApexGuruService.analyzeApexClass + .mockResolvedValueOnce([ + { + rule: 'SoqlInALoop', + message: 'Violation 1', + locations: [{ startLine: 10 }], + primaryLocationIndex: 0, + severity: 1, + resources: [] + } + ]) + .mockResolvedValueOnce([ + { + rule: 'SoqlInALoop', + message: 'Violation 2', + locations: [{ startLine: 20 }], + primaryLocationIndex: 0, + severity: 1, + resources: [] + }, + { + rule: 'DmlInALoop', + message: 'Violation 3', + locations: [{ startLine: 30 }], + primaryLocationIndex: 0, + severity: 1, + resources: [] + } + ]); + + (fs.readFile as jest.Mock).mockResolvedValue('public class Test {}'); + + const results = await engine.runRules(['SoqlInALoop', 'DmlInALoop'], mockRunOptions); + + expect(results.violations).toHaveLength(3); + }); + }); +}); diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts new file mode 100644 index 00000000..13a19feb --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts @@ -0,0 +1,342 @@ +import { ApexGuruService } from '../src/services/ApexGuruService'; +import { ApexGuruAuthService } from '../src/services/ApexGuruAuthService'; +import { Connection } from '@salesforce/core'; +import { ApexGuruResponseStatus } from '../src/types'; + +// Mock dependencies +jest.mock('../src/services/ApexGuruAuthService'); + +describe('ApexGuruService', () => { + let apexGuruService: ApexGuruService; + let mockEmitLogEvent: jest.Mock; + let mockConnection: Partial; + let mockAuthService: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + mockEmitLogEvent = jest.fn(); + + mockConnection = { + instanceUrl: 'https://test.salesforce.com', + accessToken: 'test-token', + version: '64.0', + request: jest.fn() + }; + + mockAuthService = { + initialize: jest.fn(), + getConnection: jest.fn().mockReturnValue(mockConnection), + getAccessToken: jest.fn().mockReturnValue('test-token'), + getInstanceUrl: jest.fn().mockReturnValue('https://test.salesforce.com'), + getApiVersion: jest.fn().mockReturnValue('64.0') + } as any; + + (ApexGuruAuthService as jest.Mock).mockImplementation(() => mockAuthService); + + apexGuruService = new ApexGuruService( + mockEmitLogEvent, + 120000, // maxTimeoutMs + 2000, // initialRetryMs + 60000, // maxRetryMs + 2 // backoffMultiplier + ); + }); + + describe('initialize', () => { + it('should initialize auth service with target org', async () => { + await apexGuruService.initialize('myorg'); + + expect(mockAuthService.initialize).toHaveBeenCalledWith({ targetOrg: 'myorg' }); + }); + + it('should initialize auth service without target org', async () => { + await apexGuruService.initialize(); + + expect(mockAuthService.initialize).toHaveBeenCalledWith({ targetOrg: undefined }); + }); + }); + + describe('validate', () => { + it('should succeed when validation returns success status', async () => { + (mockConnection.request as jest.Mock).mockResolvedValue({ + status: ApexGuruResponseStatus.SUCCESS + }); + + await expect(apexGuruService.validate()).resolves.toBeUndefined(); + + expect(mockConnection.request).toHaveBeenCalledWith({ + method: 'GET', + url: '/services/data/v64.0/apexguru/validate' + }); + }); + + it('should succeed for uppercase SUCCESS status', async () => { + (mockConnection.request as jest.Mock).mockResolvedValue({ + status: 'SUCCESS' + }); + + await expect(apexGuruService.validate()).resolves.toBeUndefined(); + }); + + it('should throw error when validation fails', async () => { + (mockConnection.request as jest.Mock).mockResolvedValue({ + status: ApexGuruResponseStatus.FAILED + }); + + await expect(apexGuruService.validate()) + .rejects.toThrow('ApexGuru is not available for this org'); + }); + + it('should throw error on network failure', async () => { + (mockConnection.request as jest.Mock).mockRejectedValue(new Error('Network error')); + + await expect(apexGuruService.validate()) + .rejects.toThrow('Network error'); + }); + + it('should throw timeout error when validation takes too long', async () => { + jest.useFakeTimers(); + + (mockConnection.request as jest.Mock).mockImplementation(() => + new Promise(resolve => setTimeout(() => resolve({ status: ApexGuruResponseStatus.SUCCESS }), 200000)) + ); + + const validatePromise = apexGuruService.validate(); + + jest.advanceTimersByTime(120000); + + await expect(validatePromise).rejects.toThrow('Validate request timed out after 120000ms'); + + jest.useRealTimers(); + }); + }); + + describe('analyzeApexClass', () => { + const testClassContent = 'public class Test { }'; + const testFilePath = '/test/Test.cls'; + + it('should successfully analyze and return violations', async () => { + const mockRequestId = 'req-123'; + const mockViolations = [{ + rule: 'SoqlInALoop', + message: 'SOQL in loop', + locations: [{ startLine: 5 }], + primaryLocationIndex: 0, + resources: [], + severity: 3 + }]; + + // Mock submit response + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: mockRequestId + }); + + // Mock poll response with success + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') + }); + + const violations = await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + expect(violations).toEqual(mockViolations); + expect(mockConnection.request).toHaveBeenCalledTimes(2); + }); + + it('should submit base64 encoded content', async () => { + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify([])).toString('base64') + }); + + await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + const submitCall = (mockConnection.request as jest.Mock).mock.calls[0][0]; + expect(submitCall.method).toBe('POST'); + expect(submitCall.url).toBe('/services/data/v64.0/apexguru/request'); + + const body = JSON.parse(submitCall.body); + expect(body.classContent).toBe(Buffer.from(testClassContent).toString('base64')); + }); + + it('should poll multiple times until success', async () => { + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + // First poll returns "new", second returns success + (mockConnection.request as jest.Mock) + .mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW }) + .mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify([])).toString('base64') + }); + + await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + expect(mockConnection.request).toHaveBeenCalledTimes(3); // 1 submit + 2 polls + }, 15000); + + it('should handle immediate success response', async () => { + const mockViolations = [{ rule: 'Test', message: 'test', locations: [{ startLine: 1 }], primaryLocationIndex: 0, resources: [], severity: 1 }]; + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') + }); + + const violations = await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + expect(violations).toEqual(mockViolations); + }); + + it('should throw error when analysis fails', async () => { + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.FAILED, + message: 'Analysis failed' + }); + + await expect(apexGuruService.analyzeApexClass(testClassContent, testFilePath)) + .rejects.toThrow('ApexGuru analysis failed: Analysis failed'); + }); + + it('should throw error on poll failure', async () => { + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.FAILED, + message: 'Processing failed' + }); + + await expect(apexGuruService.analyzeApexClass(testClassContent, testFilePath)) + .rejects.toThrow('Analysis failed: Processing failed'); + }); + + it('should throw error on poll error status', async () => { + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.ERROR, + message: 'Internal error' + }); + + await expect(apexGuruService.analyzeApexClass(testClassContent, testFilePath)) + .rejects.toThrow('Analysis error: Internal error'); + }); + + // Timeout test removed - difficult to test with Promise.race pattern + // Timeout behavior is tested in integration/e2e tests + + it('should invoke progress callback during polling', async () => { + const progressCallback = jest.fn(); + apexGuruService.setProgressCallback(progressCallback); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock) + .mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW }) + .mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify([])).toString('base64') + }); + + await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + expect(progressCallback).toHaveBeenCalled(); + expect(progressCallback.mock.calls.length).toBeGreaterThan(0); + }, 15000); + + it('should parse report correctly', async () => { + const mockViolations = [ + { + rule: 'SoqlInALoop', + message: 'SOQL in loop', + locations: [{ startLine: 5 }], + primaryLocationIndex: 0, + resources: ['https://example.com'], + severity: 3 + }, + { + rule: 'DmlInALoop', + message: 'DML in loop', + locations: [{ startLine: 10 }], + primaryLocationIndex: 0, + resources: [], + severity: 3 + } + ]; + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.SUCCESS, + report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') + }); + + const violations = await apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + expect(violations).toHaveLength(2); + expect(violations[0].rule).toBe('SoqlInALoop'); + expect(violations[1].rule).toBe('DmlInALoop'); + }); + + it('should stop polling when timeout occurs', async () => { + jest.useFakeTimers(); + + // Mock submit response + (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + status: ApexGuruResponseStatus.NEW, + requestId: 'req-123' + }); + + // Mock never-ending polling (keeps returning "processing") + (mockConnection.request as jest.Mock).mockImplementation(() => + new Promise(resolve => { + setTimeout(() => resolve({ status: ApexGuruResponseStatus.NEW }), 100); + }) + ); + + const analyzePromise = apexGuruService.analyzeApexClass(testClassContent, testFilePath); + + // Fast-forward past the timeout + jest.advanceTimersByTime(120000); + + await expect(analyzePromise).rejects.toThrow('Analysis timed out'); + + // Verify flag remains true so background polling can detect and abort + expect((apexGuruService as any).isCancelled).toBe(true); + + jest.useRealTimers(); + }); + }); + + describe('cleanup', () => { + it('should not throw error', () => { + expect(() => apexGuruService.cleanup()).not.toThrow(); + }); + }); +}); diff --git a/packages/code-analyzer-apexguru-engine/tsconfig.build.json b/packages/code-analyzer-apexguru-engine/tsconfig.build.json new file mode 100644 index 00000000..69d7a287 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": [ + "./src" + ], + "references": [ + { + "path": "../code-analyzer-engine-api/tsconfig.build.json" + } + ] +} diff --git a/packages/code-analyzer-apexguru-engine/tsconfig.json b/packages/code-analyzer-apexguru-engine/tsconfig.json new file mode 100644 index 00000000..8077e9a0 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true + }, + "include": [ + "./src", + "./test" + ] +}