diff --git a/CHANGELOG.md b/CHANGELOG.md index dcafc01e..e9540bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,17 @@ * JSONRPC docs for provider response has invalid format ([#61](https://github.com/rdkcentral/firebolt-openrpc/issues/61)) ([34ddeaf](https://github.com/rdkcentral/firebolt-openrpc/commit/34ddeaf7345074a894f901464d366b6eb4488e51)) * multiple provider doc headers for same module are broken ([#56](https://github.com/rdkcentral/firebolt-openrpc/issues/56)) ([e63928f](https://github.com/rdkcentral/firebolt-openrpc/commit/e63928f5160c9d4227f3207c8a7c90495245ff7a)) -## 1.5.0 +# 1.6.0 + +- Convert all unit tests to TypeScript +- Docs generation Clean-up (schemas, etc.) +- Temporal Set methods, e.g. DeveloperTools.find() +- Provider APIs +- Support for static declarations template +- Bugfix: remove unit test global object +- Bugfix: include external schemas in declarations + +# 1.5.0 - Much more beautiful validation error messages - Moved Transport Layer API out of `src/templates` and into `src/js` diff --git a/package-lock.json b/package-lock.json index 10b66733..d238788a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "mkdirp": "^0.5.6", "node-fetch": "^3.2.10", "nopt": "^5.0.0", + "text-decoding": "^1.0.0", "util": "^0.12.4" }, "bin": { @@ -561,15 +562,15 @@ } }, "node_modules/@commitlint/cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.1.2.tgz", - "integrity": "sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.2.0.tgz", + "integrity": "sha512-kd1zykcrjIKyDRftWW1E1TJqkgzeosEkv1BiYPCdzkb/g/3BrfgwZUHR1vg+HO3qKUb/0dN+jNXArhGGAHpmaQ==", "dev": true, "dependencies": { "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.1.0", - "@commitlint/load": "^17.1.2", - "@commitlint/read": "^17.1.0", + "@commitlint/lint": "^17.2.0", + "@commitlint/load": "^17.2.0", + "@commitlint/read": "^17.2.0", "@commitlint/types": "^17.0.0", "execa": "^5.0.0", "lodash": "^4.17.19", @@ -626,9 +627,9 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.1.0.tgz", - "integrity": "sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.2.0.tgz", + "integrity": "sha512-g5hQqRa80f++SYS233dbDSg16YdyounMTAhVcmqtInNeY/GF3aA4st9SVtJxpeGrGmueMrU4L+BBb+6Vs5wrcg==", "dev": true, "dependencies": { "conventional-changelog-conventionalcommits": "^5.0.0" @@ -747,9 +748,9 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.1.0.tgz", - "integrity": "sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.2.0.tgz", + "integrity": "sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==", "dev": true, "dependencies": { "@commitlint/types": "^17.0.0", @@ -775,14 +776,14 @@ } }, "node_modules/@commitlint/lint": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.1.0.tgz", - "integrity": "sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.2.0.tgz", + "integrity": "sha512-N2oLn4Dj672wKH5qJ4LGO+73UkYXGHO+NTVUusGw83SjEv7GjpqPGKU6KALW2kFQ/GsDefSvOjpSi3CzWHQBDg==", "dev": true, "dependencies": { - "@commitlint/is-ignored": "^17.1.0", - "@commitlint/parse": "^17.0.0", - "@commitlint/rules": "^17.0.0", + "@commitlint/is-ignored": "^17.2.0", + "@commitlint/parse": "^17.2.0", + "@commitlint/rules": "^17.2.0", "@commitlint/types": "^17.0.0" }, "engines": { @@ -790,9 +791,9 @@ } }, "node_modules/@commitlint/load": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.1.2.tgz", - "integrity": "sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.2.0.tgz", + "integrity": "sha512-HDD57qSqNrk399R4TIjw31AWBG8dBjNj1MrDKZKmC/wvimtnIFlqzcu1+sxfXIOHj/+M6tcMWDtvknGUd7SU+g==", "dev": true, "dependencies": { "@commitlint/config-validator": "^17.1.0", @@ -813,9 +814,9 @@ } }, "node_modules/@commitlint/load/node_modules/@types/node": { - "version": "14.18.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.32.tgz", - "integrity": "sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==", + "version": "14.18.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", + "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", "dev": true }, "node_modules/@commitlint/load/node_modules/ansi-styles": { @@ -880,18 +881,18 @@ } }, "node_modules/@commitlint/message": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.0.0.tgz", - "integrity": "sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.2.0.tgz", + "integrity": "sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==", "dev": true, "engines": { "node": ">=v14" } }, "node_modules/@commitlint/parse": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.0.0.tgz", - "integrity": "sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.2.0.tgz", + "integrity": "sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==", "dev": true, "dependencies": { "@commitlint/types": "^17.0.0", @@ -903,9 +904,9 @@ } }, "node_modules/@commitlint/read": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.1.0.tgz", - "integrity": "sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.2.0.tgz", + "integrity": "sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==", "dev": true, "dependencies": { "@commitlint/top-level": "^17.0.0", @@ -936,13 +937,13 @@ } }, "node_modules/@commitlint/rules": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.0.0.tgz", - "integrity": "sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.2.0.tgz", + "integrity": "sha512-1YynwD4Eh7HXZNpqG8mtUlL2pSX2jBy61EejYJv4ooZPcg50Ak7LPOyD3a9UZnsE76AXWFBz+yo9Hv4MIpAa0Q==", "dev": true, "dependencies": { "@commitlint/ensure": "^17.0.0", - "@commitlint/message": "^17.0.0", + "@commitlint/message": "^17.2.0", "@commitlint/to-lines": "^17.0.0", "@commitlint/types": "^17.0.0", "execa": "^5.0.0" @@ -3169,9 +3170,9 @@ } }, "node_modules/cosmiconfig-typescript-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.1.1.tgz", - "integrity": "sha512-9DHpa379Gp0o0Zefii35fcmuuin6q92FnLDffzdZ0l9tVd3nEobG3O+MZ06+kuBvFTSVScvNb/oHA13Nd4iipg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.2.0.tgz", + "integrity": "sha512-NkANeMnaHrlaSSlpKGyvn2R4rqUDeE/9E5YHx+b4nwo0R8dZyAqcih8/gxpCZvqWP9Vf6xuLpMSzSgdVEIM78g==", "dev": true, "engines": { "node": ">=12", @@ -3309,9 +3310,9 @@ } }, "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "dependencies": { "decamelize": "^1.1.0", @@ -3319,6 +3320,9 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decamelize-keys/node_modules/map-obj": { @@ -6925,9 +6929,9 @@ } }, "node_modules/marked": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", - "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.1.tgz", + "integrity": "sha512-VK1/jNtwqDLvPktNpL0Fdg3qoeUZhmRsuiIjPEy/lHwXW4ouLoZfO4XoWd4ClDt+hupV1VLpkZhEovjU0W/kqA==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -11245,6 +11249,11 @@ "node": ">=8" } }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -11468,9 +11477,9 @@ } }, "node_modules/uglify-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.3.tgz", - "integrity": "sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, "optional": true, "bin": { @@ -12294,15 +12303,15 @@ "optional": true }, "@commitlint/cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.1.2.tgz", - "integrity": "sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.2.0.tgz", + "integrity": "sha512-kd1zykcrjIKyDRftWW1E1TJqkgzeosEkv1BiYPCdzkb/g/3BrfgwZUHR1vg+HO3qKUb/0dN+jNXArhGGAHpmaQ==", "dev": true, "requires": { "@commitlint/format": "^17.0.0", - "@commitlint/lint": "^17.1.0", - "@commitlint/load": "^17.1.2", - "@commitlint/read": "^17.1.0", + "@commitlint/lint": "^17.2.0", + "@commitlint/load": "^17.2.0", + "@commitlint/read": "^17.2.0", "@commitlint/types": "^17.0.0", "execa": "^5.0.0", "lodash": "^4.17.19", @@ -12346,9 +12355,9 @@ } }, "@commitlint/config-conventional": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.1.0.tgz", - "integrity": "sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.2.0.tgz", + "integrity": "sha512-g5hQqRa80f++SYS233dbDSg16YdyounMTAhVcmqtInNeY/GF3aA4st9SVtJxpeGrGmueMrU4L+BBb+6Vs5wrcg==", "dev": true, "requires": { "conventional-changelog-conventionalcommits": "^5.0.0" @@ -12436,9 +12445,9 @@ } }, "@commitlint/is-ignored": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.1.0.tgz", - "integrity": "sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.2.0.tgz", + "integrity": "sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==", "dev": true, "requires": { "@commitlint/types": "^17.0.0", @@ -12457,21 +12466,21 @@ } }, "@commitlint/lint": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.1.0.tgz", - "integrity": "sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.2.0.tgz", + "integrity": "sha512-N2oLn4Dj672wKH5qJ4LGO+73UkYXGHO+NTVUusGw83SjEv7GjpqPGKU6KALW2kFQ/GsDefSvOjpSi3CzWHQBDg==", "dev": true, "requires": { - "@commitlint/is-ignored": "^17.1.0", - "@commitlint/parse": "^17.0.0", - "@commitlint/rules": "^17.0.0", + "@commitlint/is-ignored": "^17.2.0", + "@commitlint/parse": "^17.2.0", + "@commitlint/rules": "^17.2.0", "@commitlint/types": "^17.0.0" } }, "@commitlint/load": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.1.2.tgz", - "integrity": "sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.2.0.tgz", + "integrity": "sha512-HDD57qSqNrk399R4TIjw31AWBG8dBjNj1MrDKZKmC/wvimtnIFlqzcu1+sxfXIOHj/+M6tcMWDtvknGUd7SU+g==", "dev": true, "requires": { "@commitlint/config-validator": "^17.1.0", @@ -12489,9 +12498,9 @@ }, "dependencies": { "@types/node": { - "version": "14.18.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.32.tgz", - "integrity": "sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==", + "version": "14.18.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", + "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", "dev": true }, "ansi-styles": { @@ -12540,15 +12549,15 @@ } }, "@commitlint/message": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.0.0.tgz", - "integrity": "sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.2.0.tgz", + "integrity": "sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==", "dev": true }, "@commitlint/parse": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.0.0.tgz", - "integrity": "sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.2.0.tgz", + "integrity": "sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==", "dev": true, "requires": { "@commitlint/types": "^17.0.0", @@ -12557,9 +12566,9 @@ } }, "@commitlint/read": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.1.0.tgz", - "integrity": "sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.2.0.tgz", + "integrity": "sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==", "dev": true, "requires": { "@commitlint/top-level": "^17.0.0", @@ -12584,13 +12593,13 @@ } }, "@commitlint/rules": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.0.0.tgz", - "integrity": "sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.2.0.tgz", + "integrity": "sha512-1YynwD4Eh7HXZNpqG8mtUlL2pSX2jBy61EejYJv4ooZPcg50Ak7LPOyD3a9UZnsE76AXWFBz+yo9Hv4MIpAa0Q==", "dev": true, "requires": { "@commitlint/ensure": "^17.0.0", - "@commitlint/message": "^17.0.0", + "@commitlint/message": "^17.2.0", "@commitlint/to-lines": "^17.0.0", "@commitlint/types": "^17.0.0", "execa": "^5.0.0" @@ -14339,9 +14348,9 @@ } }, "cosmiconfig-typescript-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.1.1.tgz", - "integrity": "sha512-9DHpa379Gp0o0Zefii35fcmuuin6q92FnLDffzdZ0l9tVd3nEobG3O+MZ06+kuBvFTSVScvNb/oHA13Nd4iipg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.2.0.tgz", + "integrity": "sha512-NkANeMnaHrlaSSlpKGyvn2R4rqUDeE/9E5YHx+b4nwo0R8dZyAqcih8/gxpCZvqWP9Vf6xuLpMSzSgdVEIM78g==", "dev": true, "requires": {} }, @@ -14440,9 +14449,9 @@ "dev": true }, "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dev": true, "requires": { "decamelize": "^1.1.0", @@ -17119,9 +17128,9 @@ "dev": true }, "marked": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", - "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.1.tgz", + "integrity": "sha512-VK1/jNtwqDLvPktNpL0Fdg3qoeUZhmRsuiIjPEy/lHwXW4ouLoZfO4XoWd4ClDt+hupV1VLpkZhEovjU0W/kqA==", "dev": true }, "marked-terminal": { @@ -20203,6 +20212,11 @@ "minimatch": "^3.0.4" } }, + "text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, "text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -20362,9 +20376,9 @@ "dev": true }, "uglify-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.3.tgz", - "integrity": "sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, "optional": true }, diff --git a/package.json b/package.json index cb14a534..19048793 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build": "npm run validate && npm run build:docs && npm run build:sdk", "validate": "node ./src/cli.mjs --task validate --source ./src --shared-schemas test/sharedSchemas && npm run build:openrpc && node ./src/cli.mjs --task validate --source ./build/sdk-open-rpc.json --shared-schemas test/sharedSchemas", "build:openrpc": "node ./src/cli.mjs --task openrpc --source ./src --template ./src/template/openrpc/template.json --output ./build/sdk-open-rpc.json --shared-schemas test/sharedSchemas", - "build:sdk": "node ./src/cli.mjs --task sdk --source ./src/ --template ./src/local/template/js --output ./build/sdk/javascript/src --shared-schemas test/sharedSchemas", + "build:sdk": "node ./src/cli.mjs --task sdk --source ./src/ --template ./src/local/template/js --output ./build/sdk/javascript/src --shared-schemas test/sharedSchemas --main sdk.mjs", "build:d": "node ./src/cli.mjs --task declarations --source ./src/ --output ./dist/lib/sdk.d.ts --shared-schemas src/schemas", "build:docs": "node ./src/cli.mjs --task docs --source ./src/ --template ./src/template/markdown --output ./build/docs/markdown --shared-schemas test/sharedSchemas --as-path", "build:wiki": "node ./src/cli.mjs --task docs --source ./src/ --template ./src/template/markdown --output ./build/docs/wiki --shared-schemas test/sharedSchemas", @@ -57,6 +57,7 @@ "mkdirp": "^0.5.6", "node-fetch": "^3.2.10", "nopt": "^5.0.0", + "text-decoding": "^1.0.0", "util": "^0.12.4" } } diff --git a/src/cli.mjs b/src/cli.mjs index 745966d7..944dcad8 100755 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -15,7 +15,8 @@ const knownOpts = { 'output': [path], 'shared-schemas': [path], 'as-path': Boolean, - 'static-modules': String + 'static-modules': String, + 'main': String } const shortHands = { 't': '--task', diff --git a/src/js/http/Server.js b/src/js/http/Server.js new file mode 100644 index 00000000..07991be8 --- /dev/null +++ b/src/js/http/Server.js @@ -0,0 +1,94 @@ +import http from 'http' +import dns from 'dns' + +const lookup = dns.lookup + +const config = (await import(process.env.npm_package_json, { assert: {type: "json" }})).default +const name = 'firebolt-' + config.name.split('/').pop().split('-').shift() +const rpcPath = process.env.npm_config_local_prefix + '/dist/' + name + '-open-rpc.json' +const rpc = (await import(rpcPath, {assert: { type: "json" }})).default +const endpoint = "http://localhost/" +const domain = new URL(endpoint).hostname +const port = parseInt(new URL(endpoint).port) || 80 +const httpTag = m => m.tags && m.tags.find(t => t.name === 'http') +const httpPath = m => httpTag(m) && httpTag(m)['x-http-path'] + +console.log(`endpoint: ${endpoint}`) +console.log(`domain: ${domain}`) +console.log(`port: ${port}`) + +dns.lookup = function (...args) { + if (args[0] === domain) { + console.log(`spoofing domain ${domain} to localhost`) + return args.pop()(null, '127.0.0.1', 4) + } + else { + return lookup(...args) + } +} + +async function setup() { + return new Promise((resolve, reject) => { + + const process = async (request, response) => { + const requestPath = request.url.split(endpoint).pop() + const method = rpc.methods.find(m => { + const tag = httpTag(m) + let path = tag && httpPath(m) + + if (path && path.endsWith('/')) { + path = path.substr(0, path.length-1) + } + + return path && requestPath.startsWith(path.split("$")[0]) + }) + + let body + + if (request.method === 'POST') { + const buffers = []; + + for await (const chunk of request) { + buffers.push(chunk); + } + + body = Buffer.concat(buffers).toString(); + } + + response.setHeader("Content-Type", "application/json") + response.writeHead(200) + + if (method) { + response.write(JSON.stringify(method.examples[0].result.value)) + } + else if (request.url.endsWith('/account/authenticate')) { + response.write(JSON.stringify({ + oat: "OAT", + bearerToken: "BEARER", + d: "D" + })) + } + else { + response.write('{}') + } + response.end() + } + + const server = http.createServer(process); + + global.__firebolt__shutdown__ = () => { + server.close() + } + + server.listen(port, () => { + console.log("Firebolt Server Started") + resolve() + }); + }) +} + + +export default async function(globalConfig, projectConfig) { + await setup() + return +} \ No newline at end of file diff --git a/src/js/http/Teardown.js b/src/js/http/Teardown.js new file mode 100644 index 00000000..99afd0ee --- /dev/null +++ b/src/js/http/Teardown.js @@ -0,0 +1,4 @@ +export default async function(globalConfig, projectConfig) { + await __firebolt__shutdown__() + return +} \ No newline at end of file diff --git a/src/js/shared/Configuration/index.mjs b/src/js/shared/Configuration/index.mjs new file mode 100644 index 00000000..02d0548f --- /dev/null +++ b/src/js/shared/Configuration/index.mjs @@ -0,0 +1,25 @@ +const config = {} + +function set(name, value) { + // if the only argument is an object, copy all of the values in + if (name && !value && typeof name === 'object') { + Object.assign(config, name) + } + else { + config[name] = value + } +} + +function get(name) { + if (!name) { + return JSON.parse(JSON.stringify(config)) + } + else { + return config[name] + } +} + +export default { + get, + set +} \ No newline at end of file diff --git a/src/js/shared/Events/index.mjs b/src/js/shared/Events/index.mjs index bfb5010f..51138203 100644 --- a/src/js/shared/Events/index.mjs +++ b/src/js/shared/Events/index.mjs @@ -280,4 +280,4 @@ export default { broadcast(event, value) { emit(Object.entries(keys).find( ([k, v]) => v === 'app.'+event)[0], value) }, -} +} \ No newline at end of file diff --git a/src/js/shared/Extensions/index.mjs b/src/js/shared/Extensions/index.mjs new file mode 100644 index 00000000..d4b96cd6 --- /dev/null +++ b/src/js/shared/Extensions/index.mjs @@ -0,0 +1,55 @@ +import Transport from '../Transport/index.mjs' + +window.__firebolt = window.__firebolt || {} +const initializers = {} +const apis = {} +const queue = {} +const initialized = [] +const pending = [] +const frozen = false + +window.__firebolt.registerExtensionSDK = (id, initializer) => { + initializers[id] = initializer + + if (queue[id]) { + initialize(id, queue[id]) + delete queue[id] + } +} + +// Method for handing off platform tokens to extension SDKs +registerAPI('authorize', (...args) => Transport.send('capabilities', 'authorize', {...args} )) + +function initialize(id, config) { + if (!frozen) { + Object.freeze(apis) + } + Object.freeze(config) + if (initializers[id]) { + const getEndPoint = Transport.send('capabilities', 'endpoint', { id }) + const init = initializers[id] + delete initializers[id] + pending.push(id) + getEndPoint.then((endpoint) => { + init(endpoint, config, apis) + initialized.push(id) + delete pending[id] + }) + } + else { + queue[id] = config + } +} + +function active() { + return Object.freeze(initialized.contact()) +} + +export function registerAPI(name, method) { + apis[name] = method +} + +export default { + initialize, + active +} \ No newline at end of file diff --git a/src/js/shared/Http/index.mjs b/src/js/shared/Http/index.mjs new file mode 100644 index 00000000..9806ad95 --- /dev/null +++ b/src/js/shared/Http/index.mjs @@ -0,0 +1,100 @@ +let baseUri + +let authApiResolver +let authApiReady = new Promise( resolve => authApiResolver = resolve ) + + +let proxyProvider = (resource, options) => Promise.resolve({ resource, options }) + +const join = (...args) => args.map(a => (''+a).split('/')).flat().filter(a => a!=='').join('/').replace(':/', '://') + +function setEndpoint(endpoint) { + baseUri = endpoint +} + +function onAuthorize(callback) { + if (authApiResolver) { + authApiResolver(callback) + authApiResolver = null + } +} + +function getAuthorization(grants) { + return authApiReady.then(authGetter => authGetter(grants)) +} + + +// this gets called syncrhonously by the SDK itself, if needed +export function proxy(callback) { + proxyProvider = callback +} + +function send(module, method, params, http = {}) { + + let path = http.path || `/${module}/${method}` + const headers = http.headers || {} + const body = params && JSON.stringify(params) || null + let query = http.parameters || '' + + Object.entries(params).forEach(([name, value]) => { + const re = new RegExp('\\${param.' + name + '}', 'g') + path = path.replace(re, value) + query = query.replace(re, value) + Object.keys(headers).forEach(header => { + headers[header] = headers[header].replace(re, value) + }) + }) + + return new Promise((resolve, reject) => { + const requested = http.uses.map(capability => ({ role: 'use', capability })) + .concat(http.manages.map(capability => ({ role: 'manage', capability }))) + .concat(http.provides.map(capability => ({ role: 'provide', capability }))) + + getAuthorization(requested).then((token) => { + // TODO: this could be a bit more robust... + Object.assign(headers, { + 'Authorization': token + }) + + const resource = join(baseUri, path) + (query ? '?' + query : '') + const options = { + method: http.method || 'GET', + headers: headers + } + + if (method === 'POST' && body) { + options.body = body + } + + proxyProvider(resource, options).then( ({ resource, options }) => { + console.log(`Fetch ${method} ${resource}`) + options.body && console.log(options.body) + + fetch(resource, options).then(response => { + if (response.ok) { + response.json().then(json => { + resolve(json) + }, error => { + reject(error) + }) + } + else { + reject(new Error(response.status + ': ' + response.statusText)) + } + }).catch(error => { + reject(error) + }) + }).catch(error => { + reject(error) + }) + }).catch(error => { + reject(error) + }) + }) +} + +export default { + send, + setEndpoint, + onAuthorize +} \ No newline at end of file diff --git a/src/js/shared/Prop/MockProps.mjs b/src/js/shared/Prop/MockProps.mjs index 16d2e470..e3e152b8 100644 --- a/src/js/shared/Prop/MockProps.mjs +++ b/src/js/shared/Prop/MockProps.mjs @@ -2,9 +2,9 @@ import Mock from "../Transport/MockTransport.mjs" const mocks = {} -function mock(module, method, args, def) { +function mock(module, method, params, def) { const fullMethod = `${module}.${method}` - if ((args == null) || args.length === 0 || (Object.values(args[0]).length === 0)) { + if ((params == null) || (Object.values(params).length === 0)) { // get const rv = mocks[fullMethod] && (mocks[fullMethod].value != null) ? mocks[fullMethod].value : def return rv @@ -17,11 +17,11 @@ function mock(module, method, args, def) { } } mocks[fullMethod] = mockMethod - mockMethod.value = args[0].value + mockMethod.value = params.value Mock.event(module, method + 'Changed', { - value: args[0].value + value: params[0].value }) - return {} + return null } } diff --git a/src/js/shared/TemporalSet/index.mjs b/src/js/shared/TemporalSet/index.mjs index 75243aa8..01e19471 100644 --- a/src/js/shared/TemporalSet/index.mjs +++ b/src/js/shared/TemporalSet/index.mjs @@ -53,7 +53,7 @@ function start(module, method, addName, removeName, params, add, remove, transfo module: module, method: method, params: params, - transforms: transforms + options: { transforms } } ] @@ -63,7 +63,7 @@ function start(module, method, addName, removeName, params, add, remove, transfo params: { listen: true }, - transforms: transforms + options: { transforms } }) if (remove) { @@ -73,7 +73,7 @@ function start(module, method, addName, removeName, params, add, remove, transfo params: { listen: true }, - transforms: transforms + options: { transforms } }) } diff --git a/src/js/shared/Transport/MockTransport.mjs b/src/js/shared/Transport/MockTransport.mjs index 36acb0fd..2e8fd549 100644 --- a/src/js/shared/Transport/MockTransport.mjs +++ b/src/js/shared/Transport/MockTransport.mjs @@ -79,6 +79,7 @@ function handle(json) { }, id: json.id }))) + throw error } setTimeout(() => callback(JSON.stringify({ diff --git a/src/js/shared/Transport/index.mjs b/src/js/shared/Transport/index.mjs index 84c5d0fd..c4e6c7f5 100644 --- a/src/js/shared/Transport/index.mjs +++ b/src/js/shared/Transport/index.mjs @@ -92,26 +92,26 @@ export default class Transport { this._queue.flush(tl) } - static send (module, method, params, transforms) { + static send (module, method, params, options) { /** Transport singleton across all SDKs to keep single id map */ - return Transport.get()._send(module, method, params, transforms) + return Transport.get()._send(module, method, params, options) } - static listen(module, method, params, transforms) { - return Transport.get()._sendAndGetId(module, method, params, transforms) + static listen(module, method, params, options) { + return Transport.get()._sendAndGetId(module, method, params, options) } - _send (module, method, params, transforms) { + _send (module, method, params, options) { if (Array.isArray(module) && !method && !params) { return this._batch(module) } else { - return this._sendAndGetId(module, method, params, transforms).promise + return this._sendAndGetId(module, method, params, options).promise } } - _sendAndGetId (module, method, params, transforms) { - const {promise, json, id } = this._processRequest(module, method, params, transforms) + _sendAndGetId (module, method, params, options) { + const {promise, json, id } = this._processRequest(module, method, params, options) const msg = JSON.stringify(json) if (Settings.getLogLevel() === 'DEBUG') { console.debug('Sending message to transport: ' + msg) @@ -125,8 +125,8 @@ export default class Transport { const results = [] const json = [] - requests.forEach( ({module, method, params, transforms}) => { - const result = this._processRequest(module, method, params, transforms) + requests.forEach( ({module, method, params, options}) => { + const result = this._processRequest(module, method, params, options) results.push({ promise: result.promise, id: result.id @@ -143,9 +143,9 @@ export default class Transport { return results } - _processRequest (module, method, params, transforms) { + _processRequest (module, method, params, options) { - const p = this._addPromiseToQueue(module, method, params, transforms) + const p = this._addPromiseToQueue(module, method, params, options) const json = this._createRequestJSON(module, method, params) const result = { @@ -163,13 +163,13 @@ export default class Transport { return { jsonrpc: '2.0', method: module + '.' + method, params: params, id: this._id } } - _addPromiseToQueue (module, method, params, transforms) { + _addPromiseToQueue (module, method, params, options) { return new Promise((resolve, reject) => { this._promises[this._id] = {} this._promises[this._id].promise = this this._promises[this._id].resolve = resolve this._promises[this._id].reject = reject - this._promises[this._id].transforms = transforms + this._promises[this._id].options = options const deprecated = this._deprecated[module.toLowerCase() + '.' + method.toLowerCase()] if (deprecated) { @@ -223,12 +223,12 @@ export default class Transport { // Do any module-specific transforms on the result let result = json.result - if (p.transforms) { + if (p.options && p.options.transforms) { if (Array.isArray(json.result)) { - result = result.map(x => Results.transform(x, p.transforms)) + result = result.map(x => Results.transform(x, p.options.transforms)) } else { - result = Results.transform(result, p.transforms) + result = Results.transform(result, p.options.transforms) } } diff --git a/src/modules/simple.json b/src/modules/simple.json index a7c52223..b1f8659a 100644 --- a/src/modules/simple.json +++ b/src/modules/simple.json @@ -6,6 +6,37 @@ "version": "0.0.0" }, "methods": [ + { + "name": "prop", + "summary": "A prop.", + "tags": [ + { + "name": "capabilities", + "x-uses": [ "xrn:firebolt:capability:test:test" ] + } + ], + "description": "A method for testing basic method generation.", + "params": [ + ], + "result": { + "name": "result", + "summary": "A result for testing basic method generation.", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default Example", + "params": [ + ], + "result": { + "name": "Default Result", + "value": "the prop" + } + } + ] + }, { "name": "method", "summary": "A method.", diff --git a/src/schemas/firebolt-openrpc.json b/src/schemas/firebolt-openrpc.json index db953347..c3712738 100644 --- a/src/schemas/firebolt-openrpc.json +++ b/src/schemas/firebolt-openrpc.json @@ -39,6 +39,55 @@ "tags" ], "properties": { + "tags": { + "type": "array", + "contains": { + "anyOf": [ + { + "type": "object", + "required": ["x-uses"], + "properties": { + "x-uses": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^xrn:firebolt:capability:" + } + } + } + }, + { + "type": "object", + "required": ["x-manages"], + "properties": { + "x-uses": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^xrn:firebolt:capability:" + } + } + } + }, + { + "type": "object", + "required": ["x-provides"], + "properties": { + "x-uses": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^xrn:firebolt:capability:" + } + } + } + } + ] + } + }, "examples": { "type": "array", "minItems": 1 @@ -291,6 +340,46 @@ "properties": { "tags": { "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/CapabilitiesTag" + }, + { + "$ref": "#/definitions/EventTag" + }, + { + "$ref": "#/definitions/PropertyTag" + }, + { + "$ref": "#/definitions/PolymorphicPullTag" + }, + { + "$ref": "#/definitions/TemporalSetTag" + }, + { + "$ref": "#/definitions/PolymorphicReducerTag" + }, + { + "$ref": "#/definitions/SynchronousTag" + }, + { + "$ref": "#/definitions/CallsMetricsTag" + }, + { + "$ref": "#/definitions/ExcludeTag" + }, + { + "$ref": "#/definitions/RpcOnlyTag" + }, + { + "$ref": "#/definitions/DeprecatedTag" + }, + { + "$ref": "#/definitions/HttpTag" + } + ] + }, "minItems": 1 } } @@ -309,6 +398,9 @@ { "$ref": "#/definitions/EventMethod" }, + { + "$ref": "#/definitions/HttpMethod" + }, { "$ref": "#/definitions/RpcOnlyMethod" }, @@ -415,6 +507,40 @@ } } }, + "HttpMethod": { + "type": "object", + "properties": { + "tags": { + "allOf": [ + { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/HttpTag" + }, + { + "$ref": "#/definitions/CapabilitiesTag" + }, + { + "$ref": "#/definitions/CallsMetricsTag" + }, + { + "$ref": "#/definitions/PolymorphicReducerTag" + } + ] + } + }, + { + "type": "array", + "contains": { + "$ref": "#/definitions/HttpTag" + } + } + ] + } + } + }, "CapabilityMethod": { "type": "object", "properties": { @@ -622,7 +748,33 @@ } } ] - }, + }, + "HttpTag": { + "allOf": [ + { + "$ref": "#/definitions/Tag" + }, + { + "type": "object", + "properties": { + "name": { + "const": "http" + } + }, + "propertyNames": { + "type": "string", + "enum": [ + "name", + "x-http-path", + "x-http-method", + "x-http-parameters", + "x-http-headers", + "x-http-result" + ] + } + } + ] + }, "DeprecatedTag": { "allOf": [ { diff --git a/src/template/js/configuration/default.js b/src/template/js/configuration/default.js new file mode 100644 index 00000000..e69de29b diff --git a/src/template/js/configuration/http.js b/src/template/js/configuration/http.js new file mode 100644 index 00000000..0fb95400 --- /dev/null +++ b/src/template/js/configuration/http.js @@ -0,0 +1,9 @@ +function _initialize(endpoint, config, apis) { + Configuration.set(config) + Http.setEndpoint(endpoint || config.endpoint) + Http.onAuthorize(apis.authorize) +} + +export const initialize = window.__firebolt ? () => { throw new Error('Use Extensions.initialize() from \'@firebolt-js/sdk\' to initialize ${sdk.name}.') } : _initialize + +window.__firebolt && window.__firebolt.registerExtensionSDK('${sdk.id}', _initialize) \ No newline at end of file diff --git a/src/template/js/methods/calls-metrics.js b/src/template/js/methods/calls-metrics.js index ca50996e..80cc8760 100644 --- a/src/template/js/methods/calls-metrics.js +++ b/src/template/js/methods/calls-metrics.js @@ -2,9 +2,7 @@ import { ${method.name} as log${method.Name} } from '../Metrics/index.mjs' function ${method.name}(${method.params}) { - const transforms = ${method.transforms} - - const p = Transport.send('${info.title}', '${method.name}', { ${method.params} }, transforms) + const p = Transport.send('${info.title}', '${method.name}', { ${method.params} }, ${transport.options}) p.then(_ => { setTimeout(_ => { diff --git a/src/template/js/methods/default.js b/src/template/js/methods/default.js index 6aefb893..ef07ba2d 100644 --- a/src/template/js/methods/default.js +++ b/src/template/js/methods/default.js @@ -1,7 +1,4 @@ function ${method.name}(${method.params}) { - - const transforms = ${method.transforms} - - return Transport.send('${info.title}', '${method.name}', { ${method.params} }, transforms) + return Transport.send('${info.title}', '${method.name}', { ${method.params} }, ${transport.options}) } \ No newline at end of file diff --git a/src/template/js/methods/polymorphic-pull.js b/src/template/js/methods/polymorphic-pull.js index 8cbfa308..2c831567 100644 --- a/src/template/js/methods/polymorphic-pull.js +++ b/src/template/js/methods/polymorphic-pull.js @@ -25,11 +25,13 @@ function ${method.name} (data) { }).catch(error => { const msg = typeof error === 'string' ? error : error.message || 'Unknown Error' console.error(`App '${method.name}' callback failed: ${msg}`) + error.stack && console.error(error.stack) }) } catch (error) { const msg = typeof error === 'string' ? error : error.message || 'Unknown Error' console.error(`App '${method.name}' callback failed: ${msg}`) + error.stack && console.error(error.stack) } }) } diff --git a/src/template/js/methods/polymorphic-reducer.js b/src/template/js/methods/polymorphic-reducer.js index 51c9907c..b18e6a05 100644 --- a/src/template/js/methods/polymorphic-reducer.js +++ b/src/template/js/methods/polymorphic-reducer.js @@ -1,11 +1,9 @@ function ${method.name}(${method.params}) { - const transforms = ${method.transforms} - if (arguments.length === 1 && Array.isArray(arguments[0])) { - return Transport.send('${info.title}', '${method.name}', arguments[0], transforms) + return Transport.send('${info.title}', '${method.name}', arguments[0], ${transport.options}) } else { - return Transport.send('${info.title}', '${method.name}', { ${method.params} }, transforms) + return Transport.send('${info.title}', '${method.name}', { ${method.params} }, ${transport.options}) } } \ No newline at end of file diff --git a/src/template/js/methods/temporal-set.js b/src/template/js/methods/temporal-set.js index ae6ea076..1240e90a 100644 --- a/src/template/js/methods/temporal-set.js +++ b/src/template/js/methods/temporal-set.js @@ -8,7 +8,7 @@ function ${method.name}(...args) { add = args.pop() } - const transforms = ${method.transforms} + const { transforms } = ${transport.options} return TemporalSet.start('${info.title}', '${method.name}', '${method.temporalset.add}', '${method.temporalset.remove}', arguments, add, remove, transforms) } \ No newline at end of file diff --git a/test/Browser.js b/test/Browser.js deleted file mode 100644 index 1b77a9ad..00000000 --- a/test/Browser.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2021 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/* - * This file polyfills various Browser APIs so that they work in nodejs - * SDKs that leverage firebolt-openrpc should reference it via the - * project's jest.config.json file via the setupFiles option, e.g.: - * - * setupFiles: [ - * "./node_modules/@firebolt-js/openrpc/test/Browser.js" - * ] - * - */ - -const config = (await import(process.env.npm_package_json)).default - -const nodeVersion = () => { - const nodeVersionString = (config.devDependencies || {}).node || "" - let nodeVersionMajor = nodeVersionString.match(/^[~^@]?([0-9]+)/) - if (nodeVersionMajor && nodeVersionMajor.length) { - return nodeVersionMajor[1] - } - else { - return 0 - } -} - -// add fetch polyfill to node prior to 18 -if (nodeVersion() < 18) { - const fetch = await import('node-fetch') - global.fetch = fetch -} - -global.window = {} -global.window.location = { - set href(ref) { - console.log(`window.location.href set to '${ref}'.`) - } -} diff --git a/test/suite/props.test.js b/test/suite/props.test.js new file mode 100644 index 00000000..6b11256e --- /dev/null +++ b/test/suite/props.test.js @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Simple } from '../../build/sdk/javascript/src/sdk.mjs' +import Setup from '../Setup' +import { expect } from '@jest/globals'; + + +test('Property get', () => { + return Simple.prop().then(p => { + expect(p).toBe('the prop') + }) +}) + +test('Property set', () => { + return Simple.prop('new value').then(_ => { + expect(true).toBe(true) + }) +}) + +// test('Property get new value', () => { +// return Simple.prop().then(p => { +// expect(p).toBe('new value') +// }) +// }) diff --git a/util/sdk/index.mjs b/util/sdk/index.mjs index dddaa2de..fd79611b 100755 --- a/util/sdk/index.mjs +++ b/util/sdk/index.mjs @@ -19,7 +19,7 @@ */ import h from 'highland' -import { fsWriteFile, fsCopy, localModules, combineStreamObjects, schemaFetcher, loadFilesIntoObject, clearDirectory, fsMkDirP, logSuccess, logHeader, loadVersion, trimPath } from '../shared/helpers.mjs' +import { fsWriteFile, fsCopy, localModules, combineStreamObjects, schemaFetcher, loadFilesIntoObject, clearDirectory, fsMkDirP, logSuccess, logHeader, trimPath, treeShakeDirectory, loadJson } from '../shared/helpers.mjs' import { insertMacros, insertAggregateMacrosOnly, generateMacros, generateAggregateMacros } from './macros/index.mjs' import path from 'path' @@ -42,7 +42,8 @@ const run = ({ template: templateFolderArg, output: outputFolderArg, 'shared-schemas': sharedSchemasFolderArg, - 'static-modules': staticModulesArg + 'static-modules': staticModulesArg, + 'main': mainEntryPoint }) => { // Important file/directory locations const packageJsonFile = path.join(srcFolderArg, '..', 'package.json') @@ -53,7 +54,7 @@ const run = ({ const sdkTemplateFolder = path.join(templateFolderArg) const sharedSdkTemplateFolder = path.join(__dirname, '..', '..', 'src', 'template', 'js', 'sdk') const staticSdkCodeFolder = path.join(__dirname, '..', '..', 'src', 'js', 'shared') - + const allModules = localModules(modulesFolder, markdownFolder) const combinedSchemas = combineStreamObjects(schemaFetcher(sharedSchemasFolder), schemaFetcher(schemasFolder)) // Used to const localTemplates = loadFilesIntoObject(sdkTemplateFolder, ['.js', '.mjs'] , '/template/js/sdk/') @@ -79,9 +80,9 @@ const run = ({ return dirPart === moduleTitle && filePart === file // <-- / }) - const macroOrchestrator = schemas => modules => (localTemplates = [], sharedTemplates = [], globalDefaults = [], methodTemplates = []) => version => { + const macroOrchestrator = schemas => modules => (localTemplates = [], sharedTemplates = [], globalDefaults = [], codeTemplates = []) => packageJson => { - const macrosAlmost = generateMacros(Object.fromEntries(methodTemplates)) // <-- method expects object + const macrosAlmost = generateMacros(Object.fromEntries(codeTemplates)) // <-- method expects object const combinedTemplates = Object.entries( Object.assign( @@ -111,17 +112,17 @@ const run = ({ return h([indexTemplate, defaultsTemplate, otherModuleFiles]) .merge() .map(([file, contents]) => { - const macrofied = insertMacros(contents, macros, module, version) // <-- macro replacement + const macrofied = insertMacros(contents, macros, module, packageJson) // <-- macro replacement return [file, macrofied] }) }) - const aggregateMacros = generateAggregateMacros(Object.assign(modules, staticModules)) + const aggregateMacros = generateAggregateMacros(Object.fromEntries(codeTemplates), Object.assign(modules, staticModules), packageJson) return h(combinedTemplates) .concat(macrofiedModules) // <-- concat guarantees macrofied content wins over plain template content .map(([file, contents]) => { - return [file, insertAggregateMacrosOnly(contents, aggregateMacros)] // <-- aggregate macro replacement happens for all + return [file, insertAggregateMacrosOnly(contents, aggregateMacros, packageJson)] // <-- aggregate macro replacement happens for all }) .map(([k, v]) => [path.join(outputFolderArg, k), v]) // <-- concat output folder arg with template path .tap(([file, _]) => { @@ -144,15 +145,21 @@ const run = ({ .map(fnWithSchemas) .flatMap(fnWithModules => allSharedTemplates // Loads all 4 kinds of templates into an object. .flatMap(shared => { - const methodTemplates = Object.entries(shared).filter(([k, _]) => path.dirname(k).startsWith('methods')) + const methodTemplates = Object.entries(shared).filter(([k, _]) => !path.dirname(k).startsWith('sdk')) const sharedSdkTemplates = Object.entries(shared).filter(([k, _]) => path.dirname(k).startsWith('sdk')).map(([k, v]) => [k.slice(4), v]) const globalDefaults = Object.entries(shared).filter(([k, _]) => path.dirname(k) === '.') return localTemplates .map(local => fnWithModules(Object.entries(local), sharedSdkTemplates, globalDefaults, methodTemplates)) - .flatMap(fnWithTemplates => loadVersion(packageJsonFile) - .tap(v => logHeader(`Generating ${v.readable} --${v.original}--`)) - .flatMap(fnWithTemplates) // <-- This is calling macroOrchestrator with the last of its arguments, version - ) + .flatMap(fnWithTemplates => loadJson(packageJsonFile) + .tap(p => logHeader(`Generating ${p.description} --${p.version}--`)) + .flatMap(fnWithTemplates) // <-- This is calling macroOrchestrator with the last of its arguments, the parsed package.json + .collect() + .flatMap(_ => loadJson(packageJsonFile)) + .flatMap(json => { + const entry = mainEntryPoint || path.basename(json.main) + return treeShakeDirectory(outputFolderArg, entry) + }) + ) }) ) ) diff --git a/util/sdk/macros/index.mjs b/util/sdk/macros/index.mjs index 62d65f63..0d178675 100644 --- a/util/sdk/macros/index.mjs +++ b/util/sdk/macros/index.mjs @@ -24,15 +24,15 @@ import getPath from 'crocks/Maybe/getPath.js' import pointfree from 'crocks/pointfree/index.js' const { chain, filter, option, map, reduce, concat } = pointfree import logic from 'crocks/logic/index.js' -const { and, not, or } = logic +const { and, or, not } = logic import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' -import isNil from 'crocks/core/isNil.js' const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates -import { isExcludedMethod, isRPCOnlyMethod, isProviderInterfaceMethod, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext } from '../../shared/modules.mjs' -import { getTemplateForMethod } from '../../shared/template.mjs' +import { isExcludedMethod, isRPCOnlyMethod, isProviderInterfaceMethod, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, getUsedCapabilitiesFromMethod, getProvidedCapabilitiesFromMethod, getManagedCapabilitiesFromMethod, isEventMethodWithContext } from '../../shared/modules.mjs' +import { getTemplateForConfig, getTemplateForMethod } from '../../shared/template.mjs' import { getMethodSignatureParams } from '../../shared/javascript.mjs' +import { parseVersion } from '../../shared/helpers.mjs' import isEmpty from 'crocks/core/isEmpty.js' import { localizeDependencies } from '../../shared/json-schema.mjs' @@ -98,6 +98,13 @@ const isEventMethod = compose( getPath(['tags']) ) +const isHttpMethod = compose( + option(false), + map(_ => true), + chain(find(propEq('name', 'http'))), + getPath(['tags']) +) + const isSynchronousMethod = compose( option(false), map(_ => true), @@ -124,13 +131,6 @@ const isPropertyMethod = (m) => { return hasTag(m, 'property') || hasTag(m, 'property:immutable') || hasTag(m, 'property:readonly') } -// Pick methods that call RCP out of the methods array -const rpcMethodsOrEmptyArray = compose( - option([]), - map(filter(not(isSynchronousMethod))), - getMethods -) - // Pick events out of the methods array const eventsOrEmptyArray = compose( option([]), @@ -184,6 +184,20 @@ const providersOrEmptyArray = compose( getMethods ) +// Pick methods that call RCP out of the methods array +const rpcMethodsOrEmptyArray = compose( + option([]), + map(filter(not(or(or(isHttpMethod, isSynchronousMethod), isExcludedMethod)))), + getMethods +) + +// Pick methods that call http out of the methods array +const httpMethodsOrEmptyArray = compose( + option([]), + map(filter(isHttpMethod)), + getMethods +) + // Pick deprecated methods out of the methods array const deprecatedOrEmptyArray = compose( option([]), @@ -205,13 +219,27 @@ const makeEventName = x => x.name[2].toLowerCase() + x.name.substr(3) // onFooBa const makeProviderMethod = x => x.name["onRequest".length].toLowerCase() + x.name.substr("onRequest".length + 1) // onRequestChallenge becomes challenge //import { default as platform } from '../Platform/defaults' -const generateAggregateMacros = (modules = {}) => Object.values(modules) - .reduce((acc, module) => { - acc.exports += `export { default as ${getModuleName(module)} } from './${getModuleName(module)}/index.mjs'\n` - acc.mockImports += `import { default as ${getModuleName(module).toLowerCase()} } from './${getModuleName(module)}/defaults.mjs'\n` - acc.mockObjects += ` ${getModuleName(module).toLowerCase()}: ${getModuleName(module).toLowerCase()},\n` +const generateAggregateMacros = (templates, modules = {}, packageJson) => Object.values(modules) + .reduce((acc, module, i, arr) => { + acc.imports += `${generateAggregateImports(module)}\n` + + const name = getModuleName(module) + + if (arr.length === 1) { + acc.exports += `import _${name} from './${name}/index.mjs'\n` + acc.exports += `export const ${name} = _${name}\n` + acc.exports += `export default _${name}\n` + } + else { + acc.exports += `export { default as ${name} } from './${name}/index.mjs'\n` + } + + acc.mockImports += `import { default as ${name.toLowerCase()} } from './${name}/defaults.mjs'\n` + acc.mockObjects += ` ${name.toLowerCase()}: ${name.toLowerCase()},\n` + acc.configuration += generateConfig(templates, module, packageJson) + acc.httpEndpoint = acc.httpEndpoint || module.info['x-http-endpoint'] return acc - }, {exports: '', mockImports: '', mockObjects: ''}) + }, {imports: '', exports: '', mockImports: '', mockObjects: '', configuration: '', httpEndpoint: ''}) const generateMacros = templates => obj => { const imports = generateImports(obj) @@ -235,14 +263,21 @@ const generateMacros = templates => obj => { return macros } -const insertAggregateMacrosOnly = (fContents = '', aggregateMacros = {}) => { +const insertAggregateMacrosOnly = (fContents = '', aggregateMacros = {}, packageJson) => { + fContents = fContents.replace(/[ \t]*\/\* \$\{IMPORTS\} \*\/[ \t]*\n/, aggregateMacros.imports) + fContents = fContents.replace(/[ \t]*\/\* \$\{ID\} \*\/[ \t]*\n/, packageJson.$id) + fContents = fContents.replace(/[ \t]*\/\* \$\{CONFIGURATION\} \*\/[ \t]*\n/, aggregateMacros.configuration) fContents = fContents.replace(/[ \t]*\/\* \$\{EXPORTS\} \*\/[ \t]*\n/, aggregateMacros.exports) fContents = fContents.replace(/[ \t]*\/\* \$\{MOCK_IMPORTS\} \*\/[ \t]*\n/, aggregateMacros.mockImports) fContents = fContents.replace(/[ \t]*\/\* \$\{MOCK_OBJECTS\} \*\/[ \t]*\n/, aggregateMacros.mockObjects) + fContents = fContents.replace(/\$\{http\.endpoint\}/g, aggregateMacros.httpEndpoint) + fContents = fContents.replace(/\$\{sdk.id\}/g, packageJson.$id) + fContents = fContents.replace(/\$\{sdk.name\}/g, packageJson.name) return fContents } -const insertMacros = (fContents = '', macros = {}, module = {}, version = {}) => { +const insertMacros = (fContents = '', macros = {}, module = {}, packageJson = {}) => { + const version = parseVersion(packageJson) fContents = fContents.replace(/[ \t]*\/\* \$\{METHODS\} \*\/[ \t]*\n/, macros.methods) fContents = fContents.replace(/[ \t]*\/\* \$\{METHOD_LIST\} \*\/[ \t]*\n/, macros.methodList) fContents = fContents.replace(/[ \t]*\/\* \$\{ENUMS\} \*\/[ \t]*\n/, macros.enums) @@ -255,6 +290,7 @@ const insertMacros = (fContents = '', macros = {}, module = {}, version = {}) => fContents = fContents.replace(/\$\{major\}/g, version.major) fContents = fContents.replace(/\$\{minor\}/g, version.minor) fContents = fContents.replace(/\$\{patch\}/g, version.patch) + fContents = fContents.replace(/\$\{http.endpoint\}/g, module.info['x-http-endpoint'] || '') const exampleMatches = [...fContents.matchAll(/0 \/\* \$\{EXAMPLE\:(.*?)\} \*\//g)] const findCorrespondingMethodExample = methods => match => compose( @@ -328,7 +364,7 @@ function generateDefaults(json = {}) { const def = JSON.stringify(val.examples[0].result.value, null, ' ') if (isPropertyMethod(val)) { acc += ` - ${val.name}: function () { return MockProps.mock('${moduleName}', '${val.name}', arguments, ${def}) }` + ${val.name}: function (params) { return MockProps.mock('${moduleName}', '${val.name}', params, ${def}) }` } else { acc += ` ${val.name}: ${def}` @@ -357,6 +393,11 @@ const generateImports = json => { imports += `import Transport from '../Transport/index.mjs'\n` } + if (httpMethodsOrEmptyArray(json).length) { + imports += `import Transport from '../Http/index.mjs'\n` + imports += `import Configuration from '../Configuration/index.mjs'\n` + } + if (eventsOrEmptyArray(json).length) { imports += `import Events from '../Events/index.mjs'\n` imports += `import { registerEvents } from \'../Events/index.mjs\'\n` @@ -386,8 +427,24 @@ const generateImports = json => { return imports } -const generateInitialization = json => generateEventInitialization(json) + '\n' + generateProviderInitialization(json) + '\n' + generateDeprecatedInitialization(json) +const generateInitialization = json => [generateEventInitialization(json), generateProviderInitialization(json), generateDeprecatedInitialization(json)].join('\n') +const generateConfig = (templates, json, pkg) => [generateHttpConfig(templates, json, pkg)].join('\n') + +const generateHttpConfig = (templates, json, pkg) => { + if (httpMethodsOrEmptyArray(json).length) { + return getTemplateForConfig(httpMethodsOrEmptyArray(json)[0], '.js', templates) + } +} + +const generateAggregateImports = json => { + if (httpMethodsOrEmptyArray(json).length) { + const config = `import Http from './Http/index.mjs' +import Configuration from './Configuration/index.mjs' +` + return config + } +} const generateEventInitialization = json => compose( reduce((acc, method, i, arr) => { @@ -497,8 +554,18 @@ function generateMethods(json = {}, templates = {}, onlyEvents = false) { method.context = methodObj.params.filter(p => p.name !== 'listen').map(p => p.name) } + const transportOptions = {} + if (isHttpMethod(methodObj)) { + const tag = methodObj.tags.find(t => t.name === 'http') + Object.keys(tag).filter(k => k.startsWith("x-http")).map(k => transportOptions[k.substring(7)] = tag[k]) + } + + transportOptions.uses = getUsedCapabilitiesFromMethod(methodObj) + transportOptions.manages = getManagedCapabilitiesFromMethod(methodObj) + transportOptions.provides = getProvidedCapabilitiesFromMethod(methodObj) + if (hasMethodAttributes(methodObj)) { - method.transforms = { + transportOptions.transforms = { methods: getMethodAttributes(methodObj) } } @@ -519,7 +586,7 @@ function generateMethods(json = {}, templates = {}, onlyEvents = false) { .replace(/\$\{method\.params\.count}/g, methodObj.params ? methodObj.params.length : 0) .replace(/\$\{method\.temporalset\.add\}/g, temporalAddName) .replace(/\$\{method\.temporalset\.remove\}/g, temporalRemoveName) - .replace(/\$\{method\.transforms}/g, JSON.stringify(method.transforms)) + .replace(/\$\{transport\.options\}/g, JSON.stringify(transportOptions)) acc = acc.concat(javascript) diff --git a/util/shared/helpers.mjs b/util/shared/helpers.mjs index da7006b6..88e44fe1 100644 --- a/util/shared/helpers.mjs +++ b/util/shared/helpers.mjs @@ -33,6 +33,7 @@ const { writeFile, readFile, // NOTE: This explicit casing is _required_ copyFile, // NOTE: This explicit casing is _required_ + rm, rmdir, stat } = fs @@ -46,12 +47,13 @@ const fsCopyFile = h.wrapCallback(copyFile) const fsCopy = h.wrapCallback(copy) const fsMkDirP = h.wrapCallback((path, cb) => mkdir(path, { recursive: true }, cb)) const fsRemoveDirectory = h.wrapCallback(rmdir) +const fsRemoveFile = h.wrapCallback(rm) const fsWriteFile = h.wrapCallback(writeFile) const fsReadFile = h.wrapCallback(readFile) const bufferToString = buf => buf.toString() const copyDirectory = dir => h.wrapCallback(copy) -const clearDirectory = dir => fsRemoveDirectory(dir, {recursive: true}) +const clearDirectory = dir => fsRemoveDirectory(dir, { recursive: true }) const isFile = dir => fsStat(dir).map(statObj => statObj.isFile()) const logSuccess = message => console.log(`\x1b[32m ✓ \x1b[0m\x1b[2m ${message}\x1b[0m`) @@ -87,6 +89,77 @@ const recursiveFileDirectoryList = dirOrFile => { }) } +const removeFileAndParentIfEmpty = file => fsRemoveFile(file).flatMap(_ => h(removeParentDirIfEmpty(file))) + +const removeParentDirIfEmpty = file => { + return new Promise((resolve, reject) => { + const dir = path.dirname(file) + fs.stat(dir, (err, stat) => { + if (stat && !stat.isFile()) { + fs.readdir(dir, (err, files) => { + if (files.length === 0) { + rmdir(dir, { recursive: true }, err => { + if (err) { + reject(err) + } + else { + resolve() + } + }) + } + else { + resolve() + } + }) + } + else { + resolve() + } + }) + }) +} + + +const treeShakeDirectory = (baseUrl, entryPoint) => { + return h(new Promise((resolve, reject) => { + treeShakenFileList(baseUrl, entryPoint).then(keep => { + recursiveFileDirectoryList(baseUrl) + .filter(file => file.endsWith('.js') || file.endsWith('.mjs')) + .filter(file => !keep.includes(file)) + .tap(file => logInfo(`Tree-shaking: ${trimPath(file)} from build.`)) + .flatMap(removeFileAndParentIfEmpty) + .collect() + .tap(_ => resolve()) + .done(_ => { }) + }) + })) +} + +const importedFiles = code => Array.from(new Set([...code.matchAll(/(import|export).*?from\s+['"](.*?)['"]/g)].map(arr => arr[2]))) + +const treeShakenFileList = (baseUrl, entryPoint, list = []) => { + const file = path.join(baseUrl, entryPoint) + baseUrl = path.dirname(file) + entryPoint = path.basename(file) + + list.push(file) + + return new Promise((resolve, reject) => { + readFile(path.join(baseUrl, entryPoint), (err, data) => { + if (data) { + const imports = importedFiles(bufferToString(data)) + const newImports = imports.filter(i => !list.includes(i)) + list.push(...newImports) + return Promise.all(newImports.map(i => treeShakenFileList(baseUrl, i, list).then(moreImports => list.push(...moreImports)))) + .then(_ => { resolve(Array.from(new Set(list)).sort()) }) + } + else { + reject(err) + } + }) + }) +} + // A through stream that expects a stream of filepaths, reads the contents // of any .suffix files found, and converts them to an array tuple that // has the filepath and the contents of the file. @@ -102,14 +175,14 @@ const readUrl = url => h((push) => { res.on('end', () => push(null, h.nil)) }) }) -.collect() -.map(Buffer.concat) - + .collect() + .map(Buffer.concat) + const loadUrlContent = url => { return readUrl(url) .map(buf => [url, bufferToString(buf)]) } - + const jsonErrorHandler = filepath => (err, push) => { console.error(`\n\u{1F494} Error: ${filepath}\n`) @@ -122,32 +195,36 @@ const jsonErrorHandler = filepath => (err, push) => { } } +const parseVersion = json => { + const x = json.version.split('-').map(x => x.split('.')) + const v = { + major: x[0][0], + minor: x[0][1], + patch: x[0][2] + } + + // grab description + v.readable = json.description + + if (x.length === 2) { + const tag = x[1][0][0].toUpperCase() + x[1][0].substr(1) + const build = x[1][1] + v.readable += ` [${tag} ${build}]` + } + + v.original = json.version + + return v +} + // example: // '1.0.0-beta.1' -const loadVersion = path => fsReadFile(path) +const loadVersion = path => loadJson(path) + .map(parseVersion) + +const loadJson = path => fsReadFile(path) .map(bufferToString) .map(JSON.parse) - .map(json => { - const x = json.version.split('-').map(x => x.split('.')) - const v = { - major: x[0][0], - minor: x[0][1], - patch: x[0][2] - } - - // grab description - v.readable = json.description - - if (x.length === 2) { - const tag = x[1][0][0].toUpperCase() + x[1][0].substr(1) - const build = x[1][1] - v.readable += ` [${tag} ${build}]` - } - - v.original = json.version - - return v - }) const getFilename = (json, asPath) => (json.info ? json.info.title : (asPath ? json.title : json.title + 'Schema')) const getDirectory = (json, asPath) => asPath ? json.info ? '' : 'schemas' : '' @@ -173,13 +250,13 @@ const fileCollectionReducer = (truncateBefore = '') => (acc = {}, payload = '') return acc } -const hasPublicInterfaces = json => json.methods && json.methods.filter(m => m.tags && m.tags.find(t=>t['x-provides'])).length > 0 -const hasPublicMethods = json => hasPublicInterfaces(json) || (json.methods && json.methods.filter(m => !m.tags || !m.tags.map(t=>t.name).includes('rpc-only')).length > 0) +const hasPublicInterfaces = json => json.methods && json.methods.filter(m => m.tags && m.tags.find(t => t['x-provides'])).length > 0 +const hasPublicMethods = json => hasPublicInterfaces(json) || (json.methods && json.methods.filter(m => !m.tags || !m.tags.map(t => t.name).includes('rpc-only')).length > 0) const alphabeticalSorter = (a, b) => a.info.title > b.info.title ? 1 : b.info.title > a.info.title ? -1 : 0 const combineStreamObjects = (...xs) => h([...xs]).flatten().collect().map(xs => Object.assign({}, ...xs)) const schemaMapper = ([_filepath, parsed]) => { if (parsed && parsed.$id) { - return [parsed.$id, parsed] + return [parsed.$id, parsed] } } @@ -207,13 +284,13 @@ const externalMarkdownDescriptions = markdownFolder => recursiveFileDirectoryLis .reduce({}, fileCollectionReducer('/src/')) const schemaFetcher = uri => loadSchema(uri) - .flatMap(([file, contents]) => h.of(contents) - .map(JSON.parse) - .tap(removeIgnoredAdditionalItems) - .tap(schema => replaceUri('https://raw.githubusercontent.com/json-schema-tools/meta-schema/1.5.9/src/schema.json', 'https://meta.json-schema.tools/', schema)) - .errors(jsonErrorHandler(file)) - .map(parsed => schemaMapper([file, parsed]))) - .reduce({}, fileCollectionReducer()) + .flatMap(([file, contents]) => h.of(contents) + .map(JSON.parse) + .tap(removeIgnoredAdditionalItems) + .tap(schema => replaceUri('https://raw.githubusercontent.com/json-schema-tools/meta-schema/1.5.9/src/schema.json', 'https://meta.json-schema.tools/', schema)) + .errors(jsonErrorHandler(file)) + .map(parsed => schemaMapper([file, parsed]))) + .reduce({}, fileCollectionReducer()) const loadSchema = uri => { if (uri.startsWith('http') || uri.startsWith('https')) { @@ -221,8 +298,8 @@ const loadSchema = uri => { } else { return recursiveFileDirectoryList(uri) - .flatFilter(isFile) - .through(loadFileContent('.json')) + .flatFilter(isFile) + .through(loadFileContent('.json')) } } @@ -259,9 +336,9 @@ const localModules = (modulesFolder = '', markdownFolder = '', disableTransforms } }) .reduce({}, fileCollectionReducer('/modules/')) - } +} - const trimPath = file => path.relative(process.cwd(), file) +const trimPath = file => path.relative(process.cwd(), file) export { loadFilesIntoObject, @@ -271,7 +348,11 @@ export { jsonErrorHandler, bufferToString, clearDirectory, + treeShakenFileList, + treeShakeDirectory, + loadJson, loadVersion, + parseVersion, fsMkDirP, fsCopy, fsCopyFile, diff --git a/util/shared/modules.mjs b/util/shared/modules.mjs index 6495c268..94b4e6db 100644 --- a/util/shared/modules.mjs +++ b/util/shared/modules.mjs @@ -70,11 +70,24 @@ const isProviderInterfaceMethod = compose( ) ) ), - getPath(['tags']) + getPath(['tags']), ) ) ) + +const getUsedCapabilitiesFromMethod = (json) => { + return (json && json.tags && json.tags.find(t => t.name === "capabilities") || {})['x-uses'] || [] +} + +const getManagedCapabilitiesFromMethod = (json) => { + return (json && json.tags && json.tags.find(t => t.name === "capabilities") || {})['x-manages'] || [] +} + +const getProvidedCapabilitiesFromMethod = (json) => { + return (json && json.tags && json.tags.find(t => t.name === "capabilities") || {})['x-provides'] || [] +} + const getProvidedCapabilities = (json) => { return Array.from(new Set([...getMethods(json).filter(isProviderInterfaceMethod).map(method => method.tags.find(tag => tag['x-provides'])['x-provides'])])) } @@ -473,7 +486,7 @@ const createSetterFromProperty = property => { const createFocusFromProvider = provider => { if (!provider.name.startsWith('onRequest')) { - throw "Methods with the `x-provider` tag extension MUST start with 'onRequest'." + throw "Can only create a focus callback for methods that start with 'onRequest'." } const ready = JSON.parse(JSON.stringify(provider)) @@ -508,7 +521,7 @@ const createFocusFromProvider = provider => { const createResponseFromProvider = (provider, type, json) => { if (!provider.name.startsWith('onRequest')) { - throw "Methods with the `x-provider` tag extension MUST start with 'onRequest'." + throw "Can only create a response callback for methods that start with 'onRequest'." } const response = JSON.parse(JSON.stringify(provider)) @@ -647,6 +660,19 @@ const createResponseFromProvider = (provider, type, json) => { return response } +const generateHttpExtensions = json => { + const https = json.methods.filter( m => m.tags && m.tags.find( t => t.name == 'http')) || [] + https.forEach(method => { + Object.keys(json.info).forEach(key => { + if (key.startsWith('x-http')) { + method.tags.find(t => t.name === 'http')[key] = method[key] || json.info[key] + } + }) + }) + + return json +} + const generatePropertyEvents = json => { const properties = json.methods.filter( m => m.tags && m.tags.find( t => t.name == 'property')) || [] const readonlies = json.methods.filter( m => m.tags && m.tags.find( t => t.name == 'property:readonly')) || [] @@ -685,7 +711,7 @@ const generateTemporalSetMethods = json => { const generateProviderMethods = json => { - const providers = json.methods.filter( m => m.name.startsWith('onRequest') && m.tags && m.tags.find( t => t.name == 'capabilities' && t['x-provides'])) || [] + const providers = json.methods.filter(isProviderInterfaceMethod) || [] providers.forEach(provider => { if (! isRPCOnlyMethod(provider)) { @@ -700,8 +726,10 @@ const generateProviderMethods = json => { }) providers.forEach(provider => { - json.methods.push(createResponseFromProvider(provider, 'Response', json)) - json.methods.push(createResponseFromProvider(provider, 'Error', json)) + if (provider.name.startsWith("onRequest")) { + json.methods.push(createResponseFromProvider(provider, 'Response', json)) + json.methods.push(createResponseFromProvider(provider, 'Error', json)) + } }) return json @@ -787,6 +815,7 @@ const fireboltize = (json) => { json = generateTemporalSetMethods(json) json = generateEventListenerParameters(json) json = generateEventListenResponse(json) + json = generateHttpExtensions(json) return json } @@ -815,8 +844,10 @@ export { getPublicEvents, getSchemas, getParamsFromMethod, - fireboltize, + getUsedCapabilitiesFromMethod, + getManagedCapabilitiesFromMethod, + getProvidedCapabilitiesFromMethod, getPayloadFromEvent, - getPathFromModule, - providerHasNoParameters + providerHasNoParameters, + fireboltize } \ No newline at end of file diff --git a/util/shared/template.mjs b/util/shared/template.mjs index 826ad644..0c8bec48 100644 --- a/util/shared/template.mjs +++ b/util/shared/template.mjs @@ -21,6 +21,12 @@ const getTemplateForMethod = (method, suffix, templates) => { return templates[`methods/${template}${suffix}`] } +const getTemplateForConfig = (method, suffix, templates) => { + const template = method.tags && method.tags.map(t=>t.name).find(t => Object.keys(templates).includes('configuration/' + t + suffix)) || 'default' + return templates[`configuration/${template}${suffix}`] +} + export { getTemplateForMethod, + getTemplateForConfig }