diff --git a/.gitignore b/.gitignore index d38be344d..1ac740f9b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ config.json *.tgz cache .claude +/src/generated/ diff --git a/README.md b/README.md index d07754d62..593045a4e 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,33 @@ interface { } ``` +### Introspection + +The server supports introspection commands that allow clients to discover the API at runtime. These commands work both before and after `initialize`, so clients can explore the API at any point — including before negotiating a schema version. + +#### Get command schema + +Returns the full [JSON Schema](https://json-schema.org/) describing all incoming message types. The schema uses `$ref` pointers and a shared `definitions` dictionary for compact representation. + +```ts +interface { + messageId: string; + command: "introspect.commands"; +} +``` + +Returns a JSON Schema (Draft 7) object describing all valid incoming messages. + +**Exploring the schema** + +The returned schema can be explored with any JSON Schema-compatible tool: + +- **[jq](https://jqlang.org/)** — Query the schema from the command line (e.g. `jq '.definitions.IncomingMessageNode'`) +- **[JSON Schema Viewer](https://json-schema.app/)** — Paste the schema into the online viewer for interactive exploration +- **[JSON Crack](https://jsoncrack.com/)** — Visualize the schema as an interactive graph +- **VS Code** — Extensions like [JSON Schema Viewer](https://marketplace.visualstudio.com/items?itemName=jock.svg) provide in-editor navigation +- **[jsonschema](https://python-jsonschema.readthedocs.io/) (Python)** / **[ajv](https://ajv.js.org/) (JS)** — Validate messages programmatically against the schema + ### Driver level commands #### Get the config of the driver diff --git a/package-lock.json b/package-lock.json index 9da8a51c7..5a101535e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "lint-staged": "^16.2.6", "prettier": "^3.6.2", "semver": "^7.5.4", + "ts-json-schema-generator": "^2.4.0", "tsx": "^4.19.2", "typescript": "^5.3.3", "typescript-eslint": "^8.46.1", @@ -891,6 +892,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -2869,6 +2880,36 @@ "dev": true, "license": "MIT" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2919,6 +2960,31 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3109,6 +3175,22 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3488,6 +3570,16 @@ "node": ">= 12.0.0" } }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/mdns-server": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/mdns-server/-/mdns-server-1.0.11.tgz", @@ -3545,6 +3637,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -3586,6 +3688,16 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nrf-intel-hex": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/nrf-intel-hex/-/nrf-intel-hex-1.4.0.tgz", @@ -3717,6 +3829,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3765,6 +3884,23 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4314,6 +4450,39 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-json-schema-generator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-2.4.0.tgz", + "integrity": "sha512-HbmNsgs58CfdJq0gpteRTxPXG26zumezOs+SB9tgky6MpqiFgQwieCn2MW70+sxpHouZ/w9LW0V6L4ZQO4y1Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "commander": "^13.1.0", + "glob": "^11.0.1", + "json5": "^2.2.3", + "normalize-path": "^3.0.0", + "safe-stable-stringify": "^2.5.0", + "tslib": "^2.8.1", + "typescript": "^5.8.2" + }, + "bin": { + "ts-json-schema-generator": "bin/ts-json-schema-generator.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ts-json-schema-generator/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5584,6 +5753,12 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, + "@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -6950,6 +7125,24 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "dev": true }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6978,6 +7171,20 @@ "resolve-pkg-maps": "^1.0.0" } }, + "glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "requires": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -7108,6 +7315,15 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "requires": { + "@isaacs/cliui": "^9.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7376,6 +7592,12 @@ "triple-beam": "^1.3.0" } }, + "lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true + }, "mdns-server": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/mdns-server/-/mdns-server-1.0.11.tgz", @@ -7415,6 +7637,12 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, + "minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true + }, "moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -7444,6 +7672,12 @@ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "nrf-intel-hex": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/nrf-intel-hex/-/nrf-intel-hex-1.4.0.tgz", @@ -7528,6 +7762,12 @@ "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", "dev": true }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7561,6 +7801,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7925,6 +8175,30 @@ "dev": true, "requires": {} }, + "ts-json-schema-generator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-2.4.0.tgz", + "integrity": "sha512-HbmNsgs58CfdJq0gpteRTxPXG26zumezOs+SB9tgky6MpqiFgQwieCn2MW70+sxpHouZ/w9LW0V6L4ZQO4y1Ug==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15", + "commander": "^13.1.0", + "glob": "^11.0.1", + "json5": "^2.2.3", + "normalize-path": "^3.0.0", + "safe-stable-stringify": "^2.5.0", + "tslib": "^2.8.1", + "typescript": "^5.8.2" + }, + "dependencies": { + "commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true + } + } + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index e8f2938a0..95c279722 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "scripts": { "lint": "eslint", "lint:fix": "eslint --fix && prettier -w .", - "test": "prettier --check src && tsc --noEmit && npm run lint && tsx src/test/integration.ts", + "test": "npm run generate:schema && prettier --check src && tsc --noEmit && npm run lint && tsx src/test/integration.ts", + "generate:schema": "node scripts/generate-schema.cjs src/lib/incoming_message.ts IncomingMessage src/generated/incoming_message_schema.json && node scripts/wrap-json-module.cjs src/generated/incoming_message_schema.json src/generated/incoming_message_schema.ts", + "prebuild": "npm run generate:schema", "build": "tsc -p .", "postbuild": "esm2cjs --in dist-esm --out dist-cjs -l error -t node20", "prepare": "npm run build", @@ -66,6 +68,7 @@ "lint-staged": "^16.2.6", "prettier": "^3.6.2", "semver": "^7.5.4", + "ts-json-schema-generator": "^2.4.0", "tsx": "^4.19.2", "typescript": "^5.3.3", "typescript-eslint": "^8.46.1", diff --git a/scripts/generate-schema.cjs b/scripts/generate-schema.cjs new file mode 100644 index 000000000..8c152666b --- /dev/null +++ b/scripts/generate-schema.cjs @@ -0,0 +1,33 @@ +// Usage: node scripts/generate-schema.cjs [...flags] +// Generates a JSON schema from a TypeScript type. +// Extra flags are passed through to ts-json-schema-generator. +const { execFileSync } = require("child_process"); +const { mkdirSync } = require("fs"); +const { dirname, join } = require("path"); + +const [sourcePath, typeName, output, ...extraFlags] = process.argv.slice(2); +if (!sourcePath || !typeName || !output) { + console.error( + "Usage: node scripts/generate-schema.cjs [...flags]", + ); + process.exit(1); +} + +mkdirSync(dirname(output), { recursive: true }); + +execFileSync( + join(__dirname, "../node_modules/.bin/ts-json-schema-generator"), + [ + "--path", + sourcePath, + "--type", + typeName, + "--tsconfig", + "tsconfig.json", + "--no-type-check", + "--out", + output, + ...extraFlags, + ], + { stdio: "inherit" }, +); diff --git a/scripts/wrap-json-module.cjs b/scripts/wrap-json-module.cjs new file mode 100644 index 000000000..9baae2fdf --- /dev/null +++ b/scripts/wrap-json-module.cjs @@ -0,0 +1,14 @@ +// Usage: node scripts/wrap-json-module.cjs +// Wraps a JSON file as a TypeScript module with a default export. +const { readFileSync, writeFileSync } = require("fs"); + +const [input, output] = process.argv.slice(2); +if (!input || !output) { + console.error( + "Usage: node scripts/wrap-json-module.cjs ", + ); + process.exit(1); +} + +const json = readFileSync(input, "utf-8").trimEnd(); +writeFileSync(output, `export default ${json};\n`); diff --git a/src/lib/command.ts b/src/lib/command.ts index 16008a29e..03ccd6071 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -3,9 +3,11 @@ export { ConfigManagerCommand } from "./config_manager/command.js"; export { ControllerCommand } from "./controller/command.js"; export { DriverCommand } from "./driver/command.js"; export { EndpointCommand } from "./endpoint/command.js"; +export { IntrospectCommand } from "./introspect/command.js"; export { MulticastGroupCommand } from "./multicast_group/command.js"; export { NodeCommand } from "./node/command.js"; export { UtilsCommand } from "./utils/command.js"; +export { ZnifferCommand } from "./zniffer/command.js"; export enum ServerCommand { startListening = "start_listening", diff --git a/src/lib/controller/incoming_message.ts b/src/lib/controller/incoming_message.ts index e64975263..c9c21a620 100644 --- a/src/lib/controller/incoming_message.ts +++ b/src/lib/controller/incoming_message.ts @@ -285,7 +285,7 @@ export interface IncomingCommandControllerFirmwareUpdateOTW extends IncomingComm fileFormat?: FirmwareFileFormat; } -export interface IncomingCommandIsFirmwareUpdateInProgress extends IncomingCommandControllerBase { +export interface IncomingCommandControllerIsFirmwareUpdateInProgress extends IncomingCommandControllerBase { command: ControllerCommand.isFirmwareUpdateInProgress; } @@ -354,7 +354,7 @@ export type IncomingMessageController = | IncomingCommandControllerBeginOTAFirmwareUpdate | IncomingCommandControllerFirmwareUpdateOTA | IncomingCommandControllerFirmwareUpdateOTW - | IncomingCommandIsFirmwareUpdateInProgress + | IncomingCommandControllerIsFirmwareUpdateInProgress | IncomingCommandControllerSetMaxLongRangePowerlevel | IncomingCommandControllerGetMaxLongRangePowerlevel | IncomingCommandControllerSetLongRangeChannel diff --git a/src/lib/driver/incoming_message.ts b/src/lib/driver/incoming_message.ts index 02edeb563..defa13abb 100644 --- a/src/lib/driver/incoming_message.ts +++ b/src/lib/driver/incoming_message.ts @@ -9,87 +9,87 @@ import { } from "zwave-js"; import { LogContexts } from "../logging.js"; -interface IncomingCommandGetConfig extends IncomingCommandBase { +interface IncomingCommandDriverGetConfig extends IncomingCommandBase { command: DriverCommand.getConfig; } -interface IncomingCommandUpdateLogConfig extends IncomingCommandBase { +interface IncomingCommandDriverUpdateLogConfig extends IncomingCommandBase { command: DriverCommand.updateLogConfig; config: Partial; } -interface IncomingCommandGetLogConfig extends IncomingCommandBase { +interface IncomingCommandDriverGetLogConfig extends IncomingCommandBase { command: DriverCommand.getLogConfig; } -interface IncomingCommandEnableStatistics extends IncomingCommandBase { +interface IncomingCommandDriverEnableStatistics extends IncomingCommandBase { command: DriverCommand.enableStatistics; applicationName: string; applicationVersion: string; } -interface IncomingCommandDisableStatistics extends IncomingCommandBase { +interface IncomingCommandDriverDisableStatistics extends IncomingCommandBase { command: DriverCommand.disableStatistics; } -interface IncomingCommandIsStatisticsEnabled extends IncomingCommandBase { +interface IncomingCommandDriverIsStatisticsEnabled extends IncomingCommandBase { command: DriverCommand.isStatisticsEnabled; } -interface IncomingCommandStartListeningLogs extends IncomingCommandBase { +interface IncomingCommandDriverStartListeningLogs extends IncomingCommandBase { command: DriverCommand.startListeningLogs; filter?: Partial; } -interface IncomingCommandStopListeningLogs extends IncomingCommandBase { +interface IncomingCommandDriverStopListeningLogs extends IncomingCommandBase { command: DriverCommand.stopListeningLogs; } -interface IncomingCommandCheckForConfigUpdates extends IncomingCommandBase { +interface IncomingCommandDriverCheckForConfigUpdates extends IncomingCommandBase { command: DriverCommand.checkForConfigUpdates; } -interface IncomingCommandInstallConfigUpdate extends IncomingCommandBase { +interface IncomingCommandDriverInstallConfigUpdate extends IncomingCommandBase { command: DriverCommand.installConfigUpdate; } -interface IncomingCommandSetPreferredScales extends IncomingCommandBase { +interface IncomingCommandDriverSetPreferredScales extends IncomingCommandBase { command: DriverCommand.setPreferredScales; scales: ZWaveOptions["preferences"]["scales"]; } -interface IncomingCommandEnableErrorReporting extends IncomingCommandBase { +interface IncomingCommandDriverEnableErrorReporting extends IncomingCommandBase { command: DriverCommand.enableErrorReporting; } -interface IncomingCommandSoftReset extends IncomingCommandBase { +interface IncomingCommandDriverSoftReset extends IncomingCommandBase { command: DriverCommand.softReset; } -interface IncomingCommandTrySoftReset extends IncomingCommandBase { +interface IncomingCommandDriverTrySoftReset extends IncomingCommandBase { command: DriverCommand.trySoftReset; } -interface IncomingCommandHardReset extends IncomingCommandBase { +interface IncomingCommandDriverHardReset extends IncomingCommandBase { command: DriverCommand.hardReset; } -interface IncomingCommandShutdown extends IncomingCommandBase { +interface IncomingCommandDriverShutdown extends IncomingCommandBase { command: DriverCommand.shutdown; } -interface IncomingCommandUpdateOptions extends IncomingCommandBase { +interface IncomingCommandDriverUpdateOptions extends IncomingCommandBase { command: DriverCommand.updateOptions; options: EditableZWaveOptions; } -interface IncomingCommandSendTestFrame extends IncomingCommandBase { +interface IncomingCommandDriverSendTestFrame extends IncomingCommandBase { command: DriverCommand.sendTestFrame; nodeId: number; powerlevel: Powerlevel; } -export type IncomingCommandFirmwareUpdateOTW = IncomingCommandBase & { +export type IncomingCommandDriverFirmwareUpdateOTW = IncomingCommandBase & { command: DriverCommand.firmwareUpdateOTW; } & ( | { @@ -105,31 +105,31 @@ export type IncomingCommandFirmwareUpdateOTW = IncomingCommandBase & { } ); -export interface IncomingCommandIsOTWFirmwareUpdateInProgress extends IncomingCommandBase { +export interface IncomingCommandDriverIsOTWFirmwareUpdateInProgress extends IncomingCommandBase { command: DriverCommand.isOTWFirmwareUpdateInProgress; } -interface IncomingCommandSoftResetAndRestart extends IncomingCommandBase { +interface IncomingCommandDriverSoftResetAndRestart extends IncomingCommandBase { command: DriverCommand.softResetAndRestart; } -interface IncomingCommandEnterBootloader extends IncomingCommandBase { +interface IncomingCommandDriverEnterBootloader extends IncomingCommandBase { command: DriverCommand.enterBootloader; } -interface IncomingCommandLeaveBootloader extends IncomingCommandBase { +interface IncomingCommandDriverLeaveBootloader extends IncomingCommandBase { command: DriverCommand.leaveBootloader; } // CC version queries -interface IncomingCommandGetSupportedCCVersion extends IncomingCommandBase { +interface IncomingCommandDriverGetSupportedCCVersion extends IncomingCommandBase { command: DriverCommand.getSupportedCCVersion; cc: CommandClasses; nodeId: number; endpointIndex?: number; } -interface IncomingCommandGetSafeCCVersion extends IncomingCommandBase { +interface IncomingCommandDriverGetSafeCCVersion extends IncomingCommandBase { command: DriverCommand.getSafeCCVersion; cc: CommandClasses; nodeId: number; @@ -137,47 +137,47 @@ interface IncomingCommandGetSafeCCVersion extends IncomingCommandBase { } // User agent -interface IncomingCommandUpdateUserAgent extends IncomingCommandBase { +interface IncomingCommandDriverUpdateUserAgent extends IncomingCommandBase { command: DriverCommand.updateUserAgent; components: Record; } // RSSI monitoring -interface IncomingCommandEnableFrequentRSSIMonitoring extends IncomingCommandBase { +interface IncomingCommandDriverEnableFrequentRSSIMonitoring extends IncomingCommandBase { command: DriverCommand.enableFrequentRSSIMonitoring; durationMs: number; } -interface IncomingCommandDisableFrequentRSSIMonitoring extends IncomingCommandBase { +interface IncomingCommandDriverDisableFrequentRSSIMonitoring extends IncomingCommandBase { command: DriverCommand.disableFrequentRSSIMonitoring; } export type IncomingMessageDriver = - | IncomingCommandGetConfig - | IncomingCommandUpdateLogConfig - | IncomingCommandGetLogConfig - | IncomingCommandDisableStatistics - | IncomingCommandEnableStatistics - | IncomingCommandIsStatisticsEnabled - | IncomingCommandStartListeningLogs - | IncomingCommandStopListeningLogs - | IncomingCommandCheckForConfigUpdates - | IncomingCommandInstallConfigUpdate - | IncomingCommandSetPreferredScales - | IncomingCommandEnableErrorReporting - | IncomingCommandSoftReset - | IncomingCommandTrySoftReset - | IncomingCommandHardReset - | IncomingCommandShutdown - | IncomingCommandUpdateOptions - | IncomingCommandSendTestFrame - | IncomingCommandFirmwareUpdateOTW - | IncomingCommandIsOTWFirmwareUpdateInProgress - | IncomingCommandSoftResetAndRestart - | IncomingCommandEnterBootloader - | IncomingCommandLeaveBootloader - | IncomingCommandGetSupportedCCVersion - | IncomingCommandGetSafeCCVersion - | IncomingCommandUpdateUserAgent - | IncomingCommandEnableFrequentRSSIMonitoring - | IncomingCommandDisableFrequentRSSIMonitoring; + | IncomingCommandDriverGetConfig + | IncomingCommandDriverUpdateLogConfig + | IncomingCommandDriverGetLogConfig + | IncomingCommandDriverDisableStatistics + | IncomingCommandDriverEnableStatistics + | IncomingCommandDriverIsStatisticsEnabled + | IncomingCommandDriverStartListeningLogs + | IncomingCommandDriverStopListeningLogs + | IncomingCommandDriverCheckForConfigUpdates + | IncomingCommandDriverInstallConfigUpdate + | IncomingCommandDriverSetPreferredScales + | IncomingCommandDriverEnableErrorReporting + | IncomingCommandDriverSoftReset + | IncomingCommandDriverTrySoftReset + | IncomingCommandDriverHardReset + | IncomingCommandDriverShutdown + | IncomingCommandDriverUpdateOptions + | IncomingCommandDriverSendTestFrame + | IncomingCommandDriverFirmwareUpdateOTW + | IncomingCommandDriverIsOTWFirmwareUpdateInProgress + | IncomingCommandDriverSoftResetAndRestart + | IncomingCommandDriverEnterBootloader + | IncomingCommandDriverLeaveBootloader + | IncomingCommandDriverGetSupportedCCVersion + | IncomingCommandDriverGetSafeCCVersion + | IncomingCommandDriverUpdateUserAgent + | IncomingCommandDriverEnableFrequentRSSIMonitoring + | IncomingCommandDriverDisableFrequentRSSIMonitoring; diff --git a/src/lib/incoming_message.ts b/src/lib/incoming_message.ts index d492f4b6a..ca2e4adb1 100644 --- a/src/lib/incoming_message.ts +++ b/src/lib/incoming_message.ts @@ -10,6 +10,7 @@ import { IncomingMessageEndpoint } from "./endpoint/incoming_message.js"; import { IncomingMessageUtils } from "./utils/incoming_message.js"; import { IncomingMessageConfigManager } from "./config_manager/incoming_message.js"; import { LogContexts } from "./logging.js"; +import { IncomingMessageIntrospect } from "./introspect/incoming_message.js"; import { IncomingMessageZniffer } from "./zniffer/incoming_message.js"; interface IncomingCommandStartListening extends IncomingCommandBase { @@ -59,6 +60,7 @@ export type IncomingMessage = | IncomingMessageUtils | IncomingMessageZniffer | IncomingMessageConfigManager + | IncomingMessageIntrospect | IncomingCommandInitialize | IncomingCommandStartListeningLogs | IncomingCommandStopListeningLogs; diff --git a/src/lib/instance.ts b/src/lib/instance.ts index 51aecb33e..dfa764ab2 100644 --- a/src/lib/instance.ts +++ b/src/lib/instance.ts @@ -4,6 +4,7 @@ export enum Instance { controller = "controller", driver = "driver", endpoint = "endpoint", + introspect = "introspect", multicast_group = "multicast_group", node = "node", utils = "utils", diff --git a/src/lib/introspect/command.ts b/src/lib/introspect/command.ts new file mode 100644 index 000000000..32ca90b99 --- /dev/null +++ b/src/lib/introspect/command.ts @@ -0,0 +1,3 @@ +export enum IntrospectCommand { + commands = "introspect.commands", +} diff --git a/src/lib/introspect/incoming_message.ts b/src/lib/introspect/incoming_message.ts new file mode 100644 index 000000000..a83bbc805 --- /dev/null +++ b/src/lib/introspect/incoming_message.ts @@ -0,0 +1,11 @@ +import { IncomingCommandBase } from "../incoming_message_base.js"; +import { IntrospectCommand } from "./command.js"; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IncomingCommandIntrospectBase extends IncomingCommandBase {} + +export interface IncomingCommandIntrospectCommands extends IncomingCommandIntrospectBase { + command: IntrospectCommand.commands; +} + +export type IncomingMessageIntrospect = IncomingCommandIntrospectCommands; diff --git a/src/lib/introspect/message_handler.ts b/src/lib/introspect/message_handler.ts new file mode 100644 index 000000000..5f5b2ee4e --- /dev/null +++ b/src/lib/introspect/message_handler.ts @@ -0,0 +1,21 @@ +import { UnknownCommandError } from "../error.js"; +import incomingMessageSchema from "../../generated/incoming_message_schema.js"; +import { MessageHandler } from "../message_handler.js"; +import { IntrospectCommand } from "./command.js"; +import { IncomingMessageIntrospect } from "./incoming_message.js"; +import { IntrospectResultTypes } from "./outgoing_message.js"; + +export class IntrospectMessageHandler implements MessageHandler { + async handle( + message: IncomingMessageIntrospect, + ): Promise { + const { command } = message; + + switch (message.command) { + case IntrospectCommand.commands: + return incomingMessageSchema; + default: + throw new UnknownCommandError(command); + } + } +} diff --git a/src/lib/introspect/outgoing_message.ts b/src/lib/introspect/outgoing_message.ts new file mode 100644 index 000000000..096c02301 --- /dev/null +++ b/src/lib/introspect/outgoing_message.ts @@ -0,0 +1,5 @@ +import { IntrospectCommand } from "./command.js"; + +export interface IntrospectResultTypes { + [IntrospectCommand.commands]: Record; +} diff --git a/src/lib/multicast_group/incoming_message.ts b/src/lib/multicast_group/incoming_message.ts index 269c85044..8bcb47e95 100644 --- a/src/lib/multicast_group/incoming_message.ts +++ b/src/lib/multicast_group/incoming_message.ts @@ -44,7 +44,7 @@ export interface IncomingCommandMulticastGroupSupportsCCAPI extends IncomingComm commandClass: CommandClasses; } -export interface IncomingCommandBroadcastNodeGetDefinedValueIDs extends IncomingCommandMulticastGroupBase { +export interface IncomingCommandMulticastGroupGetDefinedValueIDs extends IncomingCommandMulticastGroupBase { command: MulticastGroupCommand.getDefinedValueIDs; } @@ -55,4 +55,4 @@ export type IncomingMessageMulticastGroup = | IncomingCommandMulticastGroupGetCCVersion | IncomingCommandMulticastGroupInvokeCCAPI | IncomingCommandMulticastGroupSupportsCCAPI - | IncomingCommandBroadcastNodeGetDefinedValueIDs; + | IncomingCommandMulticastGroupGetDefinedValueIDs; diff --git a/src/lib/node/incoming_message.ts b/src/lib/node/incoming_message.ts index 025f5c311..d00a523e2 100644 --- a/src/lib/node/incoming_message.ts +++ b/src/lib/node/incoming_message.ts @@ -63,11 +63,11 @@ export interface IncomingCommandNodeAbortFirmwareUpdate extends IncomingCommandN command: NodeCommand.abortFirmwareUpdate; } -export interface IncomingCommandGetFirmwareUpdateCapabilities extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetFirmwareUpdateCapabilities extends IncomingCommandNodeBase { command: NodeCommand.getFirmwareUpdateCapabilities; } -export interface IncomingCommandGetFirmwareUpdateCapabilitiesCached extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetFirmwareUpdateCapabilitiesCached extends IncomingCommandNodeBase { command: NodeCommand.getFirmwareUpdateCapabilitiesCached; } @@ -104,79 +104,79 @@ export interface IncomingCommandNodePing extends IncomingCommandNodeBase { command: NodeCommand.ping; } -export interface IncomingCommandHasSecurityClass extends IncomingCommandNodeBase { +export interface IncomingCommandNodeHasSecurityClass extends IncomingCommandNodeBase { command: NodeCommand.hasSecurityClass; securityClass: SecurityClass; } -export interface IncomingCommandGetHighestSecurityClass extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetHighestSecurityClass extends IncomingCommandNodeBase { command: NodeCommand.getHighestSecurityClass; } -export interface IncomingCommandTestPowerlevel extends IncomingCommandNodeBase { +export interface IncomingCommandNodeTestPowerlevel extends IncomingCommandNodeBase { command: NodeCommand.testPowerlevel; testNodeId: number; powerlevel: Powerlevel; testFrameCount: number; } -export interface IncomingCommandCheckLifelineHealth extends IncomingCommandNodeBase { +export interface IncomingCommandNodeCheckLifelineHealth extends IncomingCommandNodeBase { command: NodeCommand.checkLifelineHealth; rounds?: number; } -export interface IncomingCommandCheckRouteHealth extends IncomingCommandNodeBase { +export interface IncomingCommandNodeCheckRouteHealth extends IncomingCommandNodeBase { command: NodeCommand.checkRouteHealth; targetNodeId: number; rounds?: number; } -export interface IncomingCommandGetValue extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetValue extends IncomingCommandNodeBase { command: NodeCommand.getValue; valueId: ValueID; } -export interface IncomingCommandGetEndpointCount extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetEndpointCount extends IncomingCommandNodeBase { command: NodeCommand.getEndpointCount; } -export interface IncomingCommandInterviewCC extends IncomingCommandNodeBase { +export interface IncomingCommandNodeInterviewCC extends IncomingCommandNodeBase { command: NodeCommand.interviewCC; commandClass: CommandClasses; } -export interface IncomingCommandGetState extends IncomingCommandNodeBase { +export interface IncomingCommandNodeGetState extends IncomingCommandNodeBase { command: NodeCommand.getState; } -export interface IncomingCommandSetName extends IncomingCommandNodeBase { +export interface IncomingCommandNodeSetName extends IncomingCommandNodeBase { command: NodeCommand.setName; name: string; updateCC?: boolean; } -export interface IncomingCommandSetLocation extends IncomingCommandNodeBase { +export interface IncomingCommandNodeSetLocation extends IncomingCommandNodeBase { command: NodeCommand.setLocation; location: string; updateCC?: boolean; } -export interface IncomingCommandSetKeepAwake extends IncomingCommandNodeBase { +export interface IncomingCommandNodeSetKeepAwake extends IncomingCommandNodeBase { command: NodeCommand.setKeepAwake; keepAwake: boolean; } -export interface IncomingCommandIsFirmwareUpdateInProgress extends IncomingCommandNodeBase { +export interface IncomingCommandNodeIsFirmwareUpdateInProgress extends IncomingCommandNodeBase { command: | NodeCommand.isFirmwareUpdateInProgress | NodeCommand.getFirmwareUpdateProgress; } -export interface IncomingCommandWaitForWakeup extends IncomingCommandNodeBase { +export interface IncomingCommandNodeWaitForWakeup extends IncomingCommandNodeBase { command: NodeCommand.waitForWakeup; } -export interface IncomingCommandInterview extends IncomingCommandNodeBase { +export interface IncomingCommandNodeInterview extends IncomingCommandNodeBase { command: NodeCommand.interview; } @@ -244,29 +244,29 @@ export type IncomingMessageNode = | IncomingCommandNodeBeginFirmwareUpdate | IncomingCommandNodeUpdateFirmware | IncomingCommandNodeAbortFirmwareUpdate - | IncomingCommandGetFirmwareUpdateCapabilities - | IncomingCommandGetFirmwareUpdateCapabilitiesCached + | IncomingCommandNodeGetFirmwareUpdateCapabilities + | IncomingCommandNodeGetFirmwareUpdateCapabilitiesCached | IncomingCommandNodePollValue | IncomingCommandNodeSetRawConfigParameterValue | IncomingCommandNodeGetRawConfigParameterValue | IncomingCommandNodeRefreshValues | IncomingCommandNodeRefreshCCValues | IncomingCommandNodePing - | IncomingCommandHasSecurityClass - | IncomingCommandGetHighestSecurityClass - | IncomingCommandTestPowerlevel - | IncomingCommandCheckLifelineHealth - | IncomingCommandCheckRouteHealth - | IncomingCommandGetValue - | IncomingCommandGetEndpointCount - | IncomingCommandInterviewCC - | IncomingCommandGetState - | IncomingCommandSetName - | IncomingCommandSetLocation - | IncomingCommandSetKeepAwake - | IncomingCommandIsFirmwareUpdateInProgress - | IncomingCommandWaitForWakeup - | IncomingCommandInterview + | IncomingCommandNodeHasSecurityClass + | IncomingCommandNodeGetHighestSecurityClass + | IncomingCommandNodeTestPowerlevel + | IncomingCommandNodeCheckLifelineHealth + | IncomingCommandNodeCheckRouteHealth + | IncomingCommandNodeGetValue + | IncomingCommandNodeGetEndpointCount + | IncomingCommandNodeInterviewCC + | IncomingCommandNodeGetState + | IncomingCommandNodeSetName + | IncomingCommandNodeSetLocation + | IncomingCommandNodeSetKeepAwake + | IncomingCommandNodeIsFirmwareUpdateInProgress + | IncomingCommandNodeWaitForWakeup + | IncomingCommandNodeInterview | IncomingCommandNodeGetValueTimestamp | IncomingCommandNodeManuallyIdleNotificationValueMethod1 | IncomingCommandNodeManuallyIdleNotificationValueMethod2 diff --git a/src/lib/outgoing_message.ts b/src/lib/outgoing_message.ts index 13c24748d..37358bc28 100644 --- a/src/lib/outgoing_message.ts +++ b/src/lib/outgoing_message.ts @@ -10,6 +10,7 @@ import { MulticastGroupResultTypes } from "./multicast_group/outgoing_message.js import { EndpointResultTypes } from "./endpoint/outgoing_message.js"; import { UtilsResultTypes } from "./utils/outgoing_message.js"; import { ConfigManagerResultTypes } from "./config_manager/outgoing_message.js"; +import { IntrospectResultTypes } from "./introspect/outgoing_message.js"; import { ZnifferResultTypes } from "./zniffer/outgoing_message.js"; // https://github.com/microsoft/TypeScript/issues/1897#issuecomment-822032151 @@ -77,6 +78,7 @@ export type ResultTypes = ServerResultTypes & BroadcastNodeResultTypes & EndpointResultTypes & UtilsResultTypes & + IntrospectResultTypes & ZnifferResultTypes & ConfigManagerResultTypes; diff --git a/src/lib/server.ts b/src/lib/server.ts index 1645a36d5..204d94f39 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -38,6 +38,7 @@ import { import { Instance } from "./instance.js"; import { ServerCommand } from "./command.js"; import { DriverMessageHandler } from "./driver/message_handler.js"; +import { IntrospectMessageHandler } from "./introspect/message_handler.js"; import { LogContexts, LoggingEventForwarder } from "./logging.js"; import { BroadcastNodeMessageHandler } from "./broadcast_node/message_handler.js"; import { MulticastGroupMessageHandler } from "./multicast_group/message_handler.js"; @@ -113,6 +114,7 @@ export class Client { this, ), [Instance.endpoint]: new EndpointMessageHandler(this.driver, this), + [Instance.introspect]: new IntrospectMessageHandler(), [Instance.utils]: new UtilsMessageHandler(), [Instance.zniffer]: new ZnifferMessageHandler(driver, clientsController), }; @@ -200,6 +202,7 @@ export class Client { return this.sendResultSuccess( msg.messageId, await this.instanceHandlers[instance].handle(msg), + instance === Instance.introspect, ); } diff --git a/src/test/integration.ts b/src/test/integration.ts index 878e1537c..ce304a171 100644 --- a/src/test/integration.ts +++ b/src/test/integration.ts @@ -56,6 +56,24 @@ const runTest = async () => { type: "version", }); + // Test introspect.commands works before initialize + socket.send( + JSON.stringify({ + command: "introspect.commands", + messageId: "introspect", + }), + ); + + const introspectResult = (await nextMessage()) as any; + assert.strictEqual(introspectResult.type, "result"); + assert.strictEqual(introspectResult.success, true); + assert.strictEqual(introspectResult.messageId, "introspect"); + assert.ok(introspectResult.result.definitions, "schema has definitions"); + assert.ok( + introspectResult.result.anyOf || introspectResult.result.$ref, + "schema has anyOf or $ref at root", + ); + socket.send( JSON.stringify({ command: "initialize",