diff --git a/package-lock.json b/package-lock.json index d4af30aa..6145c212 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,8 @@ "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", "html-minifier-terser": "^7.2.0", + "json-schema-to-typescript": "^15.0.4", + "jsonc-parser": "^3.3.1", "reading-time": "^1.5.0", "recma-jsx": "^1.0.0", "rehype-recma": "^1.0.0", @@ -95,6 +97,23 @@ "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", "license": "MIT" }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@clack/core": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.2.tgz", @@ -471,6 +490,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@msgpack/msgpack": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", @@ -622,22 +647,22 @@ } }, "node_modules/@orama/orama": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.6.tgz", - "integrity": "sha512-qtSrqCqRU93SjEBedz987tvWao1YQSELjBhGkHYGVP7Dg0lBWP6d+uZEIt5gxTAYio/YWWlhivmRABvRfPLmnQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.7.tgz", + "integrity": "sha512-6yB0117ZjsgNevZw3LP+bkrZa9mU/POPVaXgzMPOBbBc35w2P3R+1vMMhEfC06kYCpd5bf0jodBaTkYQW5TVeQ==", "license": "Apache-2.0", "engines": { - "node": ">= 16.0.0" + "node": ">= 20.0.0" } }, "node_modules/@orama/plugin-data-persistence": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@orama/plugin-data-persistence/-/plugin-data-persistence-3.1.6.tgz", - "integrity": "sha512-6he5bwPZUzC7D7WSaLg3kdIRjk/DsedpnJQDPuiDBL+TYBD9heBVZx19C9aypys7vojFj+3t0brHhccoM0vReA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@orama/plugin-data-persistence/-/plugin-data-persistence-3.1.7.tgz", + "integrity": "sha512-Q/hPX6Na3l4plIAYb57PfkgWQ3pInOFd3Yg2rus4dSURcsAc8EZJTRzYIf7o7ywRn5Zt/lXNaJH3HQfql1AenA==", "license": "Apache-2.0", "dependencies": { "@msgpack/msgpack": "^2.7.2", - "@orama/orama": "3.1.6", + "@orama/orama": "3.1.7", "dpack": "^0.6.22" } }, @@ -766,7 +791,12 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", "license": "MIT" }, "node_modules/@types/mdast": { @@ -785,9 +815,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", - "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", + "version": "22.15.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.26.tgz", + "integrity": "sha512-lgISkNrqdQ5DAzjBhnDNGKDuXDNo7/1V4FhNzsKREhWLZTOELQAptuAnJMzHtUl1qyEBBy9lNBKQ9WjyiSloTw==", "dev": true, "license": "MIT", "dependencies": { @@ -972,9 +1002,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.5.tgz", - "integrity": "sha512-efMrMFYcAY+Bg3TjHS9TIxyLW7DCkbmWyaePXA/FTuNNgzUgM9ffBoeA+4g90DjHMUuGyIcM4+96w1RoxNP3Tw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.8.tgz", + "integrity": "sha512-rsRK8T7yxraNRDmpFLZCWqpea6OlXPNRRCjWMx24O1V86KFol7u2gj9zJCv6zB1oJjtnzWceuqdnCgOipFcJPA==", "cpu": [ "arm64" ], @@ -986,9 +1016,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.5.tgz", - "integrity": "sha512-K5Usy9LwmeLohtZGOC0IxhybYluGMrtBP/l73jVNKvuk240KmblE6lphSbydrocvEZEVfTfLmba8UeoSUfnh4A==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.8.tgz", + "integrity": "sha512-16yEMWa+Olqkk8Kl6Bu0ltT5OgEedkSAsxcz1B3yEctrDYp3EMBu/5PPAGhWVGnwhtf3hNe3y15gfYBAjOv5tQ==", "cpu": [ "x64" ], @@ -1000,9 +1030,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.5.tgz", - "integrity": "sha512-4vur1vMwq/hOkruiR24shuatm56jZo098x8ETchIewX8RbSwyTqHjnnJZ1WTLX2Vkg9hgy4RQqFpLnrL6Xp/hQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.8.tgz", + "integrity": "sha512-ST4uqF6FmdZQgv+Q73FU1uHzppeT4mhX3IIEmHlLObrv5Ep50olWRz0iQ4PWovadjHMTAmpuJAGaAuCZYb7UAQ==", "cpu": [ "x64" ], @@ -1014,9 +1044,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.5.tgz", - "integrity": "sha512-/hD8IHDjlTUb1/ePHavsaHYRF8lMDh+14TXHmxC8cwqrBVoHIzGZV66z2VjBDpDUtmAutptOhfKBpRLv0O0ywA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.8.tgz", + "integrity": "sha512-Z/A/4Rm2VWku2g25C3tVb986fY6unx5jaaCFpx1pbAj0OKkyuJ5wcQLHvNbIcJ9qhiYwXfrkB7JNlxrAbg7YFg==", "cpu": [ "arm" ], @@ -1028,9 +1058,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.5.tgz", - "integrity": "sha512-UPrkyN5ziuT+uRATrwabvl8JZNMt1T/fN96bZVnK3E34lQLbku99biFEUHZgXh0knJzoSoAKWfyMyyrcv4Dqfg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.8.tgz", + "integrity": "sha512-HN0p7o38qKmDo3bZUiQa6gP7Qhf0sKgJZtRfSHi6JL2Gi4NaUVF0EO1sQ1RHbeQ4VvfjUGMh3QE5dxEh06BgQQ==", "cpu": [ "arm" ], @@ -1042,9 +1072,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.5.tgz", - "integrity": "sha512-btpXWiZystUjfNviOWjf7gwjak0h1dSrzjDGn4b8OkSIMw3Gp4yYtOMZRXxUtaaZRdnOQHqRh9+39PyK6LXQbQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.8.tgz", + "integrity": "sha512-HsoVqDBt9G69AN0KWeDNJW+7i8KFlwxrbbnJffgTGpiZd6Jw+Q95sqkXp8y458KhKduKLmXfVZGnKBTNxAgPjw==", "cpu": [ "arm64" ], @@ -1056,9 +1086,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.5.tgz", - "integrity": "sha512-fzTDlm/RWRgHomLSabeV+/iKkAld+kUQaBJ2h0OveaV6+ZmZqEbdG9WDCe8U3/dax49mlPwZIvEnMZujzTPWCg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.8.tgz", + "integrity": "sha512-VfR2yTDUbUvn+e/Aw22CC9fQg9zdShHAfwWctNBdOk7w9CHWl2OtYlcMvjzMAns8QxoHQoqn3/CEnZ4Ts7hfrA==", "cpu": [ "arm64" ], @@ -1070,9 +1100,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.5.tgz", - "integrity": "sha512-i+9usBSko2DyFvB7iimhfDtIk9tWhg4sKh7kZC8JGfGMdhYWZ8a40VvgE/Xj8iDsX6ngVRsIsgsNCU9jPx86zw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.8.tgz", + "integrity": "sha512-xUauVQNz4uDgs4UJJiUAwMe3N0PA0wvtImh7V0IFu++UKZJhssXbKHBRR4ecUJpUHCX2bc4Wc8sGsB6P+7BANg==", "cpu": [ "ppc64" ], @@ -1084,9 +1114,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.5.tgz", - "integrity": "sha512-gpdNeCckfTMOWyZ+AjB0KpgHE2aCCoGtKDSocKwU9RkfWpeVvpcokey5l1A68WXCDE33sonekbe8Wm4+E0z7VQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.8.tgz", + "integrity": "sha512-GqyIB+CuSHGhhc8ph5RrurtNetYJjb6SctSHafqmdGcRuGi6uyTMR8l18hMEhZFsXdFMc/MpInPLvmNV22xn+A==", "cpu": [ "riscv64" ], @@ -1098,9 +1128,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.5.tgz", - "integrity": "sha512-avni2nC47b0ZBCXL3lg6I3z9lyP1kKVYZXIyIsA/pcTra+Uuq0RgeWeEBc8IJ6DjGrpft7gWyyekrYK58VomGQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.8.tgz", + "integrity": "sha512-eEU3rWIFRv60xaAbtsgwHNWRZGD7cqkpCvNtio/f1TjEE3HfKLzPNB24fA9X/8ZXQrGldE65b7UKK3PmO4eWIQ==", "cpu": [ "riscv64" ], @@ -1112,9 +1142,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.5.tgz", - "integrity": "sha512-GLv1+kVnVluyG8KRIl176jIoExlhgl3ASZz+VGyQpv5EwD5FqOtZHFzsRJA3xXNQlnHj3iMO4SA/HX4dc6iOvA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.8.tgz", + "integrity": "sha512-GVLI0f4I4TlLqEUoOFvTWedLsJEdvsD0+sxhdvQ5s+N+m2DSynTs8h9jxR0qQbKlpHWpc2Ortz3z48NHRT4l+w==", "cpu": [ "s390x" ], @@ -1126,9 +1156,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.5.tgz", - "integrity": "sha512-frsoBmP2ww2axFqZvIexnDF5UuO0exCZjrchM7uvPbNzZCaU+B43r6Y3ywEFsXXH6MbZNpw10Ntuwb9N0orfcg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.8.tgz", + "integrity": "sha512-GX1pZ/4ncUreB0Rlp1l7bhKAZ8ZmvDIgXdeb5V2iK0eRRF332+6gRfR/r5LK88xfbtOpsmRHU6mQ4N8ZnwvGEA==", "cpu": [ "x64" ], @@ -1140,9 +1170,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.5.tgz", - "integrity": "sha512-kdI20RI0k+XcA+vuW6KB/EJbzUvRfo8PsKy2DFlX1fhTVsEXaf21nkU9C3NdTwlTkl9YvvLGNTKoJDH7yn7K8w==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.8.tgz", + "integrity": "sha512-n1N84MnsvDupzVuYqJGj+2pb9s8BI1A5RgXHvtVFHedGZVBCFjDpQVRlmsFMt6xZiKwDPaqsM16O/1isCUGt7w==", "cpu": [ "x64" ], @@ -1154,9 +1184,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.5.tgz", - "integrity": "sha512-6F+PAhfsokXDtLihQzomvVK0rYzSP/qkgJg4+R4RaCmE3pwFspLeyUi1Wd11hwP4FQQn5/5Yw9jraUMQpMPWCg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.8.tgz", + "integrity": "sha512-x94WnaU5g+pCPDVedfnXzoG6lCOF2xFGebNwhtbJCWfceE94Zj8aysSxdxotlrZrxnz5D3ijtyFUYtpz04n39Q==", "cpu": [ "wasm32" ], @@ -1171,9 +1201,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.5.tgz", - "integrity": "sha512-rZ1SRHK95gOqy7hQBcG2sxKMoKFRFAl8f+cGYayA3RRNidkY86uNsXZiWDGgIuelYXSudvAd9RElDib/Lkx7pQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.8.tgz", + "integrity": "sha512-vst2u8EJZ5L6jhJ6iLis3w9rg16aYqRxQuBAMYQRVrPMI43693hLP7DuqyOBRKgsQXy9/jgh204k0ViHkqQgdg==", "cpu": [ "arm64" ], @@ -1185,9 +1215,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.5.tgz", - "integrity": "sha512-49JiW5JickDuC/VqSBlbZTqwX8sJBGBfodU/v4+vM8Eig63JOAK7bOtG8M8kxXRrkJIGhumba4cTf4QcWbMRcg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.8.tgz", + "integrity": "sha512-yb3LZOLMFqnA+/ShlE1E5bpYPGDsA590VHHJPB+efnyowT776GJXBoh82em6O9WmYBUq57YblGTcMYAFBm72HA==", "cpu": [ "ia32" ], @@ -1199,9 +1229,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.5.tgz", - "integrity": "sha512-69JcsNlbafX/FsafXswKb5M+jPXC9IRcNVz5SqEKH9+PA5jmJ6+fFyjFX1pipBRADGn+EuPhCeDcQl+CAxP+2g==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.8.tgz", + "integrity": "sha512-hHKFx+opG5BA3/owMXon8ypwSotBGTdblG6oda/iOu9+OEYnk0cxD2uIcGyGT8jCK578kV+xMrNxqbn8Zjlpgw==", "cpu": [ "x64" ], @@ -1306,7 +1336,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/astring": { @@ -2738,7 +2767,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2760,7 +2788,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2839,7 +2866,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2865,6 +2891,29 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2879,6 +2928,12 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2916,9 +2971,9 @@ } }, "node_modules/lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", "dev": true, "license": "MIT", "dependencies": { @@ -3040,6 +3095,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.iteratee": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.iteratee/-/lodash.iteratee-4.7.0.tgz", @@ -4106,6 +4167,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4446,7 +4516,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -5197,6 +5266,48 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5287,9 +5398,9 @@ } }, "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" @@ -5494,9 +5605,9 @@ } }, "node_modules/unrs-resolver": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.5.tgz", - "integrity": "sha512-DnuJxogme0dCRIdH+yIwpaNLWfff9DqcpfDh4J8qca17rOnu6e3AfNzB8mnUzjv7EgayXQkwnt1A2vT8BM9ZHA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.8.tgz", + "integrity": "sha512-2zsXwyOXmCX9nGz4vhtZRYhe30V78heAv+KDc21A/KMdovGHbZcixeD5JHEF0DrFXzdytwuzYclcPbvp8A3Jlw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5504,26 +5615,26 @@ "napi-postinstall": "^0.2.2" }, "funding": { - "url": "https://github.com/sponsors/JounQin" + "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.5", - "@unrs/resolver-binding-darwin-x64": "1.7.5", - "@unrs/resolver-binding-freebsd-x64": "1.7.5", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.5", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.5", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.5", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.5", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.5", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.5", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.5", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.5", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.5", - "@unrs/resolver-binding-linux-x64-musl": "1.7.5", - "@unrs/resolver-binding-wasm32-wasi": "1.7.5", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.5", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.5", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.5" + "@unrs/resolver-binding-darwin-arm64": "1.7.8", + "@unrs/resolver-binding-darwin-x64": "1.7.8", + "@unrs/resolver-binding-freebsd-x64": "1.7.8", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.8", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.8", + "@unrs/resolver-binding-linux-arm64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-arm64-musl": "1.7.8", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-riscv64-musl": "1.7.8", + "@unrs/resolver-binding-linux-s390x-gnu": "1.7.8", + "@unrs/resolver-binding-linux-x64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-x64-musl": "1.7.8", + "@unrs/resolver-binding-wasm32-wasi": "1.7.8", + "@unrs/resolver-binding-win32-arm64-msvc": "1.7.8", + "@unrs/resolver-binding-win32-ia32-msvc": "1.7.8", + "@unrs/resolver-binding-win32-x64-msvc": "1.7.8" } }, "node_modules/uri-js": { @@ -5686,15 +5797,15 @@ } }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yocto-queue": { diff --git a/package.json b/package.json index 623aba33..7e014ef0 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ "reading-time": "^1.5.0", "recma-jsx": "^1.0.0", "rehype-recma": "^1.0.0", + "json-schema-to-typescript": "^15.0.4", + "jsonc-parser": "^3.3.1", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", diff --git a/src/generators/index.mjs b/src/generators/index.mjs index 7e3f3a73..6df5c398 100644 --- a/src/generators/index.mjs +++ b/src/generators/index.mjs @@ -12,6 +12,8 @@ import legacyJsonAll from './legacy-json-all/index.mjs'; import llmsTxt from './llms-txt/index.mjs'; import manPage from './man-page/index.mjs'; import oramaDb from './orama-db/index.mjs'; +import json from './json/index.mjs'; +import jsonAll from './json-all/index.mjs'; export const publicGenerators = { 'json-simple': jsonSimple, @@ -25,6 +27,8 @@ export const publicGenerators = { 'orama-db': oramaDb, 'llms-txt': llmsTxt, 'jsx-ast': jsxAst, + json, + 'json-all': jsonAll, }; export const allGenerators = { diff --git a/src/generators/json-all/index.mjs b/src/generators/json-all/index.mjs new file mode 100644 index 00000000..dd203ad7 --- /dev/null +++ b/src/generators/json-all/index.mjs @@ -0,0 +1,78 @@ +// @ts-check +'use strict'; + +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { DOC_NODE_VERSION } from '../../constants.mjs'; +import { generateJsonSchema } from './util/generateJsonSchema.mjs'; + +// TODO add test w/ https://www.npmjs.com/package/jsonschema + +/** + * TODO docs + * + * @typedef {Array} Input + * + * @type {GeneratorMetadata} + */ +export default { + name: 'json-all', + + // This should be kept in sync with the JSON schema version for this + // generator AND the `json` generator + version: '2.0.0', + + description: 'TODO', + + dependsOn: 'json', + + /** + * Generates a JSON file. + * + * @param {Input} input + * @param {Partial} param1 + * @returns {Promise} + */ + async generate(input, { output }) { + const generatedValue = { + $schema: `https://nodejs.org/docs/${DOC_NODE_VERSION}/api/node-doc-all-schema.jsonc`, + modules: [], + text: [], + }; + + const propertiesToIgnore = ['$schema', 'source']; + + input.forEach(section => { + const copiedSection = {}; + + Object.keys(section).forEach(key => { + if (!propertiesToIgnore.includes(key)) { + copiedSection[key] = section[key]; + } + }); + + switch (section.type) { + case 'module': + generatedValue.modules.push(copiedSection); + break; + case 'text': + generatedValue.text.push(copiedSection); + break; + default: + throw new TypeError(`unsupported root section type ${section.type}`); + } + }); + + if (output) { + const schema = generateJsonSchema(); + + // Write the parsed JSON schema to the output directory + await writeFile( + join(output, 'node-doc-schema.json'), + JSON.stringify(schema) + ); + } + + return generatedValue; + }, +}; diff --git a/src/generators/json-all/util/generateJsonSchema.mjs b/src/generators/json-all/util/generateJsonSchema.mjs new file mode 100644 index 00000000..a7d35837 --- /dev/null +++ b/src/generators/json-all/util/generateJsonSchema.mjs @@ -0,0 +1,26 @@ +// @ts-check +'use strict'; + +import { DOC_NODE_VERSION } from '../../../constants.mjs'; + +const JSON_SCHEMA_URL = `https://nodejs.org/docs/${DOC_NODE_VERSION}/api/node-doc-schema.json`; + +export const generateJsonSchema = () => ({ + $schema: 'http://json-schema.org/draft-07/schema#', + // This should be kept in sync with the generator version for this generator + // AND the `json` generator and schema + $id: 'nodejs-api-doc-all@v2.0.0', // This should be kept in sync with the generator version. + title: 'Node.js API Documentation Schema (All)', + readOnly: true, + + properties: { + modules: { + type: 'array', + items: { $ref: `${JSON_SCHEMA_URL}/#/definitions/Module` }, + }, + text: { + type: 'array', + items: { $ref: `${JSON_SCHEMA_URL}/#/definitions/Text` }, + }, + }, +}); diff --git a/src/generators/json/constants.mjs b/src/generators/json/constants.mjs new file mode 100644 index 00000000..ad9a93a7 --- /dev/null +++ b/src/generators/json/constants.mjs @@ -0,0 +1 @@ +'use strict'; diff --git a/src/generators/json/generated.d.ts b/src/generators/json/generated.d.ts new file mode 100644 index 00000000..412e7250 --- /dev/null +++ b/src/generators/json/generated.d.ts @@ -0,0 +1,180 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type NodeJsAPIDocumentationSchema = DocumentRoot & (Module | Text); +/** + * A JavaScript module. + */ +export type Module = SectionBase & { + type: 'module'; + /** + * https://jsdoc.app/tags-see + */ + '@see': string; + /** + * https://jsdoc.app/tags-module + */ + '@module': string; + /** + * Classes exported from this module. + */ + classes?: Class[]; + /** + * Methods exported from this module. + */ + methods?: Method[]; + /** + * APIs that are available globally. + */ + globals?: (Class | Method)[]; + properties?: Property[]; + [k: string]: unknown; +}; +export type Text = SectionBase; +/** + * Node.js version number + */ +export type NodeCoreVersion = string; +export type Class = SectionBase & { + type: 'class'; + '@constructor': MethodSignature[]; + methods: Method[]; + staticMethods: Method[]; + properties: Property[]; + [k: string]: unknown; +}; +/** + * A JavaScript function. + */ +export type Method = SectionBase & { + type: 'method'; + signatures: MethodSignature[]; + [k: string]: unknown; +}; +/** + * A property on a JavaScript object or class. + */ +export type Property = SectionBase & { + type: 'property'; + /** + * JavaScript type of the property. + */ + '@type'?: string | [string, ...string[]]; + /** + * Is this property modifiable by user code? + */ + mutable?: boolean; + [k: string]: unknown; +}; + +/** + * Common properties found at the root of each document. + */ +export interface DocumentRoot { + /** + * The path to the Markdown source used to generate this document. It is relative to the Node.js repository root. + */ + source: string; + [k: string]: unknown; +} +/** + * Common properties found in each section of a document. + */ +export interface SectionBase { + /** + * Type of the section + */ + type: 'module' | 'class' | 'method' | 'property' | 'text'; + /** + * https://jsdoc.app/tags-name + */ + '@name': string; + /** + * Description of the section. + */ + description?: string; + /** + * Sections that just hold further text on this section. + */ + text?: Text[]; + /** + * https://jsdoc.app/tags-example + */ + '@example'?: string | string[]; + /** + * https://jsdoc.app/tags-deprecated + */ + '@deprecated'?: NodeCoreVersion[]; + stability?: Stability; + /** + * The changes this API has underwent. + */ + changes?: Change[]; + /** + * https://jsdoc.app/tags-since + */ + '@since'?: NodeCoreVersion[]; + /** + * todo what does this describe lol + */ + napiVersion?: number[]; + /** + * Versions that this was removed in. + */ + removedIn?: NodeCoreVersion[]; + [k: string]: unknown; +} +/** + * Describes the stability of an object. + */ +export interface Stability { + /** + * The stability value. + */ + value: number; + /** + * Textual representation of the stability. + */ + text: string; + [k: string]: unknown; +} +export interface Change { + version: NodeCoreVersion[]; + /** + * URL to the PR that introduced this change. + */ + prUrl?: string; + /** + * Description of the change. + */ + description: string; + [k: string]: unknown; +} +export interface MethodSignature { + parameters?: MethodParameter[]; + /** + * The method signature's return type. + */ + '@returns'?: string | [string, ...string[]]; + [k: string]: unknown; +} +export interface MethodParameter { + /** + * Name of the parameter. + */ + '@name': string; + /** + * Type of the parameter + */ + '@type': string | [string, ...string[]]; + description?: string; + /** + * The parameter's default value + */ + '@default'?: string; + [k: string]: unknown; +} diff --git a/src/generators/json/index.mjs b/src/generators/json/index.mjs new file mode 100644 index 00000000..fb2fe75f --- /dev/null +++ b/src/generators/json/index.mjs @@ -0,0 +1,108 @@ +// @ts-check +'use strict'; + +import { writeFile, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { parse as jsoncParse } from 'jsonc-parser'; +import { groupNodesByModule } from '../../utils/generators.mjs'; +import { createSectionBuilder } from './utils/createSection.mjs'; + +// TODO add test w/ https://www.npmjs.com/package/jsonschema + +/** + * This generator is responsible for generating the JSON representation of the + * docs. + * + * This is a top-level generator, intaking the raw AST tree of the api docs. + * It generates JSON files to the specified output directory given by the + * config. + * + * @typedef {Array} Input + * + * @type {GeneratorMetadata>} + */ +export default { + name: 'json', + + // This should be kept in sync with the JSON schema version for this + // generator AND the `json-all` generator + version: '2.0.0', + + description: + 'This generator is responsible for generating the JSON representation of the docs.', + + dependsOn: 'ast', + + /** + * Generates a JSON file. + * + * @param {Input} input + * @param {Partial} param1 + * @returns {Promise>} + */ + async generate(input, { output }) { + const groupedModules = groupNodesByModule(input); + + const buildSection = createSectionBuilder(); + + /** + * @param {ApiDocMetadataEntry} head + * @returns {import('./generated.d.ts').NodeJsAPIDocumentationSchema} + */ + const processModuleNodes = head => { + const nodes = groupedModules.get(head.api); + if (!nodes) { + throw new TypeError(`no grouped nodes found for ${head.api}`); + } + + const section = buildSection(head, nodes); + + return section; + }; + + /** + * @type {Array} + */ + const generatedValues = []; + + // Gets the first nodes of each module, which is considered the "head" + const headNodes = input.filter(node => node.heading.depth === 1); + + headNodes.forEach(async node => { + // Get the json for the node's section + const section = processModuleNodes(node); + + generatedValues.push(section); + + // Write it to the output file + if (output) { + await writeFile( + join(output, `${node.api}.json`), + JSON.stringify(section, null, 2) + ); + } + }); + + if (output) { + // Current directory path relative to the `index.mjs` file + const baseDir = import.meta.dirname; + + // Read the contents of the JSON schema + // const schemaString = await readFile( + // join(baseDir, 'schema.jsonc'), + // 'utf8' + // ); + + // // Parse the JSON schema into an object + // const schema = await jsoncParse(schemaString); + + // Write the parsed JSON schema to the output directory + // await writeFile( + // join(output, 'node-doc-schema.json'), + // JSON.stringify(schema) + // ); + } + + return generatedValues; + }, +}; diff --git a/src/generators/json/schema.jsonc b/src/generators/json/schema.jsonc new file mode 100644 index 00000000..7b1aa9d2 --- /dev/null +++ b/src/generators/json/schema.jsonc @@ -0,0 +1,381 @@ +{ + /** + * NOTE: if you modify this, please: + * - Bump the version in the $id property + * - Bump the version of the `json` and `json-all` generator. + * - Run `tools/generate-json-types.mjs` and ensure there aren't type errors + */ + + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "nodejs-api-doc@v2.0.0", // This should be kept in sync with the generator version + "title": "Node.js API Documentation Schema", + "readOnly": true, + + "allOf": [ + { "$ref": "#/definitions/DocumentRoot" }, + { + "oneOf": [ + // Top of a document can either be a module (for api declarations) or + // text (for general docs on things) + { "$ref": "#/definitions/Module" }, + { "$ref": "#/definitions/Text" }, + ], + }, + ], + + "definitions": { + "DocumentRoot": { + "type": "object", + "description": "Common properties found at the root of each document.", + "properties": { + "source": { + "type": "string", + "description": "The path to the Markdown source used to generate this document. It is relative to the Node.js repository root.", + "examples": ["doc/api/net.md"], + }, + }, + "required": ["source"], + }, + + "SectionBase": { + "type": "object", + "description": "Common properties found in each section of a document.", + "properties": { + "type": { + "type": "string", + "enum": ["module", "class", "method", "property", "text"], + "description": "Type of the section", + }, + "@name": { + "type": "string", + "description": "https://jsdoc.app/tags-name", + "examples": ["Buffer", "Addons"], + }, + "description": { + "type": "string", + "description": "Description of the section.", + }, + "text": { + "type": "array", + "description": "Sections that just hold further text on this section.", + "items": { "$ref": "#/definitions/Text" }, + }, + "@example": { + "description": "https://jsdoc.app/tags-example", + "oneOf": [ + { "type": "string" }, + { + "type": "array", + "items": { + "type": "string", + }, + }, + ], + }, + "@deprecated": { + "type": "array", + "description": "https://jsdoc.app/tags-deprecated", + "items": { "$ref": "#/definitions/NodeCoreVersion" }, + }, + "stability": { "$ref": "#/definitions/Stability" }, + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/Change", + }, + "description": "The changes this API has underwent.", + }, + "@since": { + "type": "array", + "description": "https://jsdoc.app/tags-since", + "items": { "$ref": "#/definitions/NodeCoreVersion" }, + }, + "napiVersion": { + "type": "array", + "description": "todo what does this describe lol", + "items": { "type": "number" }, + }, + "removedIn": { + "type": "array", + "description": "Versions that this was removed in.", + "items": { "$ref": "#/definitions/NodeCoreVersion" }, + }, + }, + "required": ["type", "@name"], + }, + + "Module": { + "description": "A JavaScript module.", + "allOf": [ + { "$ref": "#/definitions/SectionBase" }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["module"], + }, + "@see": { + "type": "string", + "description": "https://jsdoc.app/tags-see", + }, + "@module": { + "type": "string", + "description": "https://jsdoc.app/tags-module", + "examples": ["node:buffer"], + }, + "classes": { + "type": "array", + "description": "Classes exported from this module.", + "items": { "$ref": "#/definitions/Class" }, + }, + "methods": { + "type": "array", + "description": "Methods exported from this module.", + "items": { "$ref": "#/definitions/Method" }, + }, + "globals": { + "type": "array", + "description": "APIs that are available globally.", + "items": { + "oneOf": [ + { "$ref": "#/definitions/Class" }, + { "$ref": "#/definitions/Method" }, + ], + }, + }, + "properties": { + "type": "array", + "items": { "$ref": "#/definitions/Property" }, + }, + }, + "required": ["type", "@module", "@see"], + }, + ], + }, + + "Class": { + "allOf": [ + { "$ref": "#/definitions/SectionBase" }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["class"], + }, + "@constructor": { + "type": "array", + "items": { "$ref": "#/definitions/MethodSignature" }, + }, + "methods": { + "type": "array", + "items": { "$ref": "#/definitions/Method" }, + }, + "staticMethods": { + "type": "array", + "items": { "$ref": "#/definitions/Method" }, + }, + "properties": { + "type": "array", + "items": { "$ref": "#/definitions/Property" }, + }, + }, + "required": [ + "type", + "@constructor", + "methods", + "staticMethods", + "properties", + ], + }, + ], + }, + + "Method": { + "description": "A JavaScript function.", + "allOf": [ + { "$ref": "#/definitions/SectionBase" }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["method"], + }, + "signatures": { + "type": "array", + "items": { "$ref": "#/definitions/MethodSignature" }, + }, + }, + "required": ["type", "signatures"], + }, + ], + }, + + "MethodSignature": { + "type": "object", + "properties": { + "parameters": { + "type": "array", + "items": { "$ref": "#/definitions/MethodParameter" }, + }, + // "@returns": { + // "description": "The method signature's return type.", + // "$ref": "https://json-schema.org/draft-07/schema#", + // }, + "@returns": { + "description": "The method signature's return type.", + "oneOf": [ + { + "type": "string", + "examples": ["string"], + }, + { + "type": "array", + "items": { "type": "string" }, + "examples": [["string", "Promise"]], + "minItems": 1, + }, + ], + }, + }, + "required": [], + }, + + "MethodParameter": { + "type": "object", + "properties": { + "@name": { + "type": "string", + "description": "Name of the parameter.", + }, + "@type": { + "description": "Type of the parameter", + "oneOf": [ + { + "type": "string", + "examples": ["string"], + }, + { + "type": "array", + "items": { + "type": "string", + }, + "minItems": 1, + }, + ], + }, + "description": { + "type": "string", + }, + "@default": { + "type": "string", + "description": "The parameter's default value", + "examples": ["foo"], + }, + }, + "required": ["@name", "@type"], + }, + + "Global": { + "type": "object", + "properties": {}, + "required": [], + }, + + "Property": { + "description": "A property on a JavaScript object or class.", + "allOf": [ + { "$ref": "#/definitions/SectionBase" }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["property"], + }, + // "@type": { + // "description": "JavaScript type of the property.", + // "$ref": "https://json-schema.org/draft-07/schema#", + // }, + "@type": { + "description": "JavaScript type of the property.", + "oneOf": [ + { + "type": "string", + "examples": ["string"], + }, + { + "type": "array", + "items": { + "type": "string", + }, + "minItems": 1, + }, + ], + }, + "mutable": { + "type": "boolean", + "description": "Is this property modifiable by user code?", + "default": false, + }, + }, + "required": ["type"], + }, + ], + }, + + "Text": { + "allOf": [{ "$ref": "#/definitions/SectionBase" }], + }, + + "NodeCoreVersion": { + "type": "string", + "description": "Node.js version number", + // Taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + // and slightly modified to support the `v` in front + "pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["v24.0.0"], + }, + + "Change": { + "type": "object", + "properties": { + "version": { + "type": "array", + "items": { + "$ref": "#/definitions/NodeCoreVersion", + }, + }, + "prUrl": { + "type": "string", + "description": "URL to the PR that introduced this change.", + }, + "description": { + "type": "string", + "description": "Description of the change.", + }, + }, + "required": ["version", "description"], + }, + + "Stability": { + "type": "object", + "description": "Describes the stability of an object.", + "properties": { + "value": { + "type": "number", + "description": "The stability value.", + "minimum": 0, + "maximum": 3, + }, + "text": { + "type": "string", + "description": "Textual representation of the stability.", + }, + }, + "required": ["value", "text"], + }, + }, +} diff --git a/src/generators/json/tmp/addons.json b/src/generators/json/tmp/addons.json new file mode 100644 index 00000000..22f8f8cc --- /dev/null +++ b/src/generators/json/tmp/addons.json @@ -0,0 +1,4 @@ +{ + "$schema": "./parsed-schema.json", + "source": "doc/api/addons.md" +} diff --git a/src/generators/json/tmp/buffer.json b/src/generators/json/tmp/buffer.json new file mode 100644 index 00000000..4c268268 --- /dev/null +++ b/src/generators/json/tmp/buffer.json @@ -0,0 +1,119 @@ +{ + "$schema": "./parsed-schema.json", + "source": "doc/api/buffer.md", + "type": "module", + "@name": "Buffer", + "description": "`Buffer` objects are used to represent a fixed-length sequence of bytes. Many Node.js APIs support `Buffer`s.\nThe `Buffer` class is a subclass of JavaScript's {Uint8Array} class and extends it with methods that cover additional use cases. Node.js APIs accept plain {Uint8Array}s wherever `Buffer`s are supported as well.\n\nWhile the `Buffer` class is available within the global scope, it is still\nrecommended to explicitly reference it via an import or require statement.", + "@see": "https://nodejs.org/api/buffer.html", + "@module": "node:buffer", + "@since": ["v24.0.0"], + "classes": [ + { + "type": "class", + "@name": "SlowBuffer", + "description": "bla bla bla" + } + ], + "methods": [ + { + "type": "method", + "@name": "isAscii", + "signatures": [ + { + "parameters": [ + { + "@name": "", + "@default": "foo", + "@type": "string", + "description": "" + } + ], + "@returns": "boolean" + } + ] + } + ], + "properties": [ + { + "type": "property", + "@type": "number", + "@name": "INSPECT_MAX_BYTES", + "mutable": true + } + ], + "globals": [ + { + "type": "class", + "@name": "Buffer", + "description": "The `Buffer` class is a global type for dealing with binary data directly. It can be constructed in a variety of ways.", + "staticMethods": [ + { + "type": "method", + "@name": "alloc", + "description": "Allocates a new Buffer of size bytes. If fill is undefined, the Buffer will be zero-filled.\nIf `size` is larger than `buffer.constats.MAX_LENGTH` OR SMALLER THAN 0, `ERR_OUT_OF_RANGE` is thrown.\nIf `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`.\nIf both fill and encoding are specified, the allocated Buffer will be initialized by calling buf.fill(fill, encoding).\nCalling Buffer.alloc() can be measurably slower than the alternative Buffer.allocUnsafe() but ensures that the newly created Buffer instance contents will never contain sensitive data from previous allocations, including data that might not have been allocated for Buffers.\nA TypeError will be thrown if size is not a number.", + "@example": [ + "import { Buffer } from 'node:buffer'\n\nconst buf = Buffer.alloc(5);\n\nconsole.log(buf);\n// Prints: ", + "import { Buffer } from 'node:buffer;\n\nconst buf = Buffer.alloc(5, 'a');\n\nconsole.log(buf);\n// Prints: " + ], + "changes": [ + { + "version": ["v20.0.0"], + "description": "blablabla", + "prUrl": "asdf1234" + }, + { + "version": ["v15.0.0"], + "description": "blablabla", + "prUrl": "asdf1234" + } + ], + "signatures": [ + { + "parameters": [ + { + "@name": "size", + "@type": "number" + }, + { + "@name": "fill", + "@type": ["string", "Buffer", "Uint8Array", "number"], + "@default": "0" + }, + { + "@name": "encoding", + "@type": "string", + "@default": "utf8" + } + ], + "@returns": "Buffer" + } + ] + } + ] + }, + { + "type": "method", + "@name": "atob", + "@example": "import { atob } from 'node:buffer';\n\natob('asdf');", + "@since": ["v15.13.0", "v14.17.0"], + "@deprecated": ["v24.0.0"], + "description": "Global alias for {@link Buffer.atob}.", + "stability": { + "value": 3, + "text": "Legacy. Use `Buffer.from(data, 'base64')` instead." + }, + "signatures": [ + { + "parameters": [ + { + "@name": "data", + "@type": "any", + "description": "An ASCII (Latin1) string." + } + ], + "@returns": "string" + } + ] + } + ] +} diff --git a/src/generators/json/tmp/parsed-schema.json b/src/generators/json/tmp/parsed-schema.json new file mode 100644 index 00000000..465df8ee --- /dev/null +++ b/src/generators/json/tmp/parsed-schema.json @@ -0,0 +1,370 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "nodejs-api-doc@v0.0.0", + "title": "Node.js API Documentation Schema", + "readOnly": true, + "allOf": [ + { + "$ref": "#/definitions/DocumentRoot" + }, + { + "oneOf": [ + { + "$ref": "#/definitions/Module" + } + ] + } + ], + "definitions": { + "DocumentRoot": { + "type": "object", + "description": "Common properties found at the root of each document.", + "properties": { + "source": { + "type": "string", + "description": "The path to the Markdown source used to generate this document. It is relative to the Node.js repository root.", + "examples": ["doc/api/net.md"] + } + }, + "required": ["source"] + }, + "SectionBase": { + "type": "object", + "description": "Common properties found in each section of a document.", + "properties": { + "@name": { + "type": "string", + "description": "https://jsdoc.app/tags-name", + "examples": ["Buffer", "Addons"] + }, + "description": { + "type": "string", + "description": "Description of the section." + }, + "@example": { + "description": "https://jsdoc.app/tags-example", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "@deprecated": { + "type": "array", + "description": "https://jsdoc.app/tags-deprecated", + "items": { + "$ref": "#/definitions/NodeCoreVersion" + } + }, + "stability": { + "$ref": "#/definitions/Stability" + }, + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/Change" + }, + "description": "The changes this API has underwent." + }, + "@since": { + "type": "array", + "description": "https://jsdoc.app/tags-since", + "items": { + "$ref": "#/definitions/NodeCoreVersion" + } + }, + "napiVersion": { + "type": "string", + "description": "todo what does this describe lol", + "items": { + "type": "string" + } + }, + "removedIn": { + "type": "array", + "description": "Versions that this was removed in.", + "items": { + "$ref": "#/definitions/NodeCoreVersion" + } + } + }, + "required": ["@name"] + }, + "Module": { + "allOf": [ + { + "$ref": "#/definitions/SectionBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["module"] + }, + "@module": { + "type": "string", + "description": "https://jsdoc.app/tags-module", + "examples": ["node:buffer"] + }, + "classes": { + "type": "array", + "description": "Classes exported from this module.", + "items": { + "$ref": "#/definitions/Class" + } + }, + "methods": { + "type": "array", + "description": "Methods exported from this module.", + "items": { + "$ref": "#/definitions/Method" + } + }, + "globals": { + "type": "array", + "description": "APIs that are available globally.", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Class" + }, + { + "$ref": "#/definitions/Method" + } + ] + } + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/definitions/Property" + } + } + }, + "required": ["type", "@module", "@see"] + } + ] + }, + "Class": { + "allOf": [ + { + "$ref": "#/definitions/SectionBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["class"] + }, + "@constructor": { + "type": "array", + "items": { + "$ref": "#/definitions/MethodSignature" + } + }, + "methods": { + "type": "array", + "items": { + "$ref": "#/definitions/Method" + } + }, + "staticMethods": { + "type": "array", + "items": { + "$ref": "#/definitions/Method" + } + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/definitions/Property" + } + } + }, + "required": ["type"] + } + ] + }, + "Method": { + "description": "A JavaScript function.", + "allOf": [ + { + "$ref": "#/definitions/SectionBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["method"] + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/MethodSignature" + } + } + }, + "required": ["type", "signatures"] + } + ] + }, + "MethodSignature": { + "type": "object", + "properties": { + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/MethodParameter" + } + }, + "@returns": { + "description": "The method signature's return type.", + "oneOf": [ + { + "type": "string", + "examples": ["string"] + }, + { + "type": "array", + "items": { + "type": "string" + }, + "examples": [["string", "Promise"]], + "minItems": 1 + } + ] + } + }, + "required": [] + }, + "MethodParameter": { + "type": "object", + "properties": { + "@name": { + "type": "string", + "description": "Name of the parameter." + }, + "@type": { + "description": "Type of the parameter", + "oneOf": [ + { + "type": "string", + "examples": ["string"] + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + ] + }, + "description": { + "type": "string" + }, + "@default": { + "type": "string", + "description": "The parameter's default value", + "examples": ["foo"] + } + }, + "required": ["@name", "@type"] + }, + "Global": { + "type": "object", + "properties": {}, + "required": [] + }, + "Property": { + "description": "A property on a JavaScript object or class.", + "allOf": [ + { + "$ref": "#/definitions/SectionBase" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["property"] + }, + "@type": { + "description": "JavaScript type of the property.", + "oneOf": [ + { + "type": "string", + "examples": ["string"] + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + ] + }, + "mutable": { + "type": "boolean", + "description": "Is this property modifiable by user code?", + "default": false + } + }, + "required": ["type", "@type"] + } + ] + }, + "NodeCoreVersion": { + "type": "string", + "description": "Node.js version number", + "examples": ["v24.0.0"] + }, + "Change": { + "type": "object", + "properties": { + "version": { + "type": "array", + "items": { + "$ref": "#/definitions/NodeCoreVersion" + } + }, + "prUrl": { + "type": "string", + "description": "URL to the PR that introduced this change." + }, + "description": { + "type": "string", + "description": "Description of the change." + } + }, + "required": ["version", "prUrl", "description"] + }, + "Stability": { + "type": "object", + "description": "Describes the stability of an object.", + "properties": { + "value": { + "type": "number", + "description": "The stability value.", + "minimum": 0, + "maximum": 3 + }, + "text": { + "type": "string", + "description": "Textual representation of the stability." + } + }, + "required": ["value", "text"] + } + } +} diff --git a/src/generators/json/types.d.ts b/src/generators/json/types.d.ts new file mode 100644 index 00000000..1d947525 --- /dev/null +++ b/src/generators/json/types.d.ts @@ -0,0 +1,20 @@ +import { + Class, + Method, + Module, + Property, + SectionBase, + Text, +} from './generated.d.ts'; + +export type Section = SectionBase & + (Module | Class | Method | Property | Text) & + GeneratorMetadata; + +/** + * This is metadata that's only relevant to the generator and should be removed + * before the file is output. + */ +export type GeneratorMetadata = { + parent?: Section; +}; diff --git a/src/generators/json/utils/createClassSection.mjs b/src/generators/json/utils/createClassSection.mjs new file mode 100644 index 00000000..7fdb795e --- /dev/null +++ b/src/generators/json/utils/createClassSection.mjs @@ -0,0 +1,36 @@ +// @ts-check +'use strict'; + +import { findParentSection } from './findParentSection.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + */ + +export const createClassSectionBuilder = () => { + /** + * Adds the properties expected in a class section to an object. + * @param {import('../generated.d.ts').Class} section The class section + */ + return section => { + section['@constructor'] = []; + + section.methods = []; + + section.staticMethods = []; + + section.properties = []; + + const parent = findParentSection(section, 'module'); + + if (parent) { + if (!Array.isArray(parent.classes)) { + throw new TypeError( + `expected parent.classes to be an array, got ${typeof parent.classes}` + ); + } + + parent.classes.push(section); + } + }; +}; diff --git a/src/generators/json/utils/createMethodSection.mjs b/src/generators/json/utils/createMethodSection.mjs new file mode 100644 index 00000000..75e917b7 --- /dev/null +++ b/src/generators/json/utils/createMethodSection.mjs @@ -0,0 +1,188 @@ +// @ts-check +'use strict'; + +import { findParentSection } from './findParentSection.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + * + * @typedef {{ type: 'parameter' } | { type: 'returnValue' }} ParameterListNode + */ + +export const createMethodSectionBuilder = () => { + /** + * Handles each node in a parameter list + * @param {import('mdast').ListItem} param0 + * @returns {import('../generated.d.ts').MethodParameter} + */ + const parseParameterListNode = ({ children }) => { + /** + * `paragraph` will be a parameter's type declaration (ex/ "`asd` {string} Description of asd") + * or the method's return value (ex/ "Returns: {integer}") + * + * `list` only occurs when the parameter is an object that has documented properties + */ + const [paragraph, list] = children; + + // Safety checks + if (paragraph.type !== 'paragraph') { + throw new TypeError( + `expected first node in parameter list node to be a paragraph` + ); + } + + if (list && list.type !== 'list') { + throw new TypeError( + `expected second node in parameter list item to be a list` + ); + } + + const [parameterNameNode] = paragraph.children; + + switch (parameterNameNode.type) { + case 'inlineCode': { + // node is something like: "`asd` {string} Description of asd" + const [ + , + blankSpaceNode, + parameterTypeNode, + ...parameterDescriptionNodes + ] = paragraph.children; + + // if (blankSpaceNode.type !== 'text' || blankSpaceNode.value !== ' ') { + // console.log('asdasd', blankSpaceNode, blankSpaceNode.type, `"${blankSpaceNode.value}"`); + // throw new TypeError( + // `expected blank space between parameter name and type` + // ); + // } + + if (parameterTypeNode.type !== 'link') { + // console.log('asd', JSON.stringify(paragraph.children, null, 2)); + throw new TypeError(`expected parameter type to be a link`); + } + + // console.log(parameterTypeNode); + + break; + } + case 'text': { + // node is something like: "Returns: {integer}"" + break; + } + } + + // console.log(JSON.stringify(paragraph.children, null, 2)) + // console.log('---------------------'); + + // if (children[0].type !== 'paragraph') { + // console.log('0', children[0].type) + // } + + // if (children.length == 2 && children[1].type !== 'list') { + // console.log(1, children[1].type) + // } + + // if (children.length > 2) { + // console.log('>2', children) + // } + + // switch (children.length) { + // case 1: + // if (children[0].type !== 'paragraph') { + // console.log('0', children[0].type) + // } + // break; + // case 2: + // } + + // children is going to have + + // const [] + + // children.forEach(child => { + + // }); + + // console.log(JSON.stringify(children, null, 2)); + + // console.log('-----------'); + + return { + '@name': 'asd', + }; + }; + + /** + * TODO docs + * @param {HierarchizedEntry} entry The AST entry + * @returns {Record | undefined} + */ + const parseParameters = entry => { + const [, ...nodes] = entry.content.children; + + // The first list that exists in a doc entry should be the method's + // parameter list. + const listNode = nodes.find(node => node.type === 'list'); + + if (!listNode) { + // Method doesn't take in any parameters + return undefined; + } + + /** + * @type {Record} + */ + const parameters = {}; + + listNode.children.forEach(listItem => { + const parameter = parseParameterListNode(listItem); + + parameter[parameter['@name']] = parameter; + }); + + return parameters; + }; + + /** + * TODO docs + * @param {HierarchizedEntry} entry The AST entry + * @param {import('../generated.d.ts').Method} section The method section + */ + const parseSignatures = (entry, section) => { + section.signatures = []; + + // Parse all the parameters and store them in a name:section map + const parameters = parseParameters(entry, section); + + // Parse the value of entry.heading.data.text to get the order of parameters and which are optional + // console.log(entry.heading.data.text); + }; + + /** + * Adds the properties expected in a method section to an object. + * @param {HierarchizedEntry} entry The AST entry + * @param {import('../generated.d.ts').Method} section The method section + */ + return (entry, section) => { + parseSignatures(entry, section); + + const parent = findParentSection(section, ['class', 'module']); + + // Add this section to the parent if it exists + if (parent) { + // Put static methods in `staticMethods` property and non-static methods + // in the `methods` property + const property = entry.heading.data.text.startsWith('Static method:') + ? 'staticMethods' + : 'methods'; + + if (!Array.isArray(parent[property])) { + throw new TypeError( + `expected parent[${property}] to be an array, got type ${typeof parent[property]} instead (parent type=${parent.type})` + ); + } + + parent[property].push(section); + } + }; +}; diff --git a/src/generators/json/utils/createModuleSection.mjs b/src/generators/json/utils/createModuleSection.mjs new file mode 100644 index 00000000..bfb9dae6 --- /dev/null +++ b/src/generators/json/utils/createModuleSection.mjs @@ -0,0 +1,30 @@ +// @ts-check +'use strict'; + +import { DOC_NODE_VERSION } from '../../../constants.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + */ + +export const createModuleSectionBuilder = () => { + /** + * Adds the properties expected in a module section to an object. + * @param {HierarchizedEntry} entry The AST entry + * @param {import('../generated.d.ts').Module} section The module section + */ + return (entry, section) => { + section['@see'] = + `https://nodejs.org/dist/${DOC_NODE_VERSION}/doc/api/${entry.api}.html`; + + section['@module'] = `node:${entry.api}`; + + section.classes = []; + + section.methods = []; + + section.globals = []; + + section.properties = []; + }; +}; diff --git a/src/generators/json/utils/createPropertySection.mjs b/src/generators/json/utils/createPropertySection.mjs new file mode 100644 index 00000000..56ec5ebc --- /dev/null +++ b/src/generators/json/utils/createPropertySection.mjs @@ -0,0 +1,108 @@ +// @ts-check +'use strict'; + +import { findParentSection } from './findParentSection.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + */ + +export const createPropertySectionBuilder = () => { + /** + * TODO docs + * @param {HierarchizedEntry} entry The AST entry + * @param {import('../generated.d.ts').Property} section The method section + */ + const parseType = (entry, section) => { + const [, ...nodes] = entry.content.children; + + // The first list that exists in the entry should be its type info + const listNode = nodes.find(node => node.type === 'list'); + + if (!listNode) { + // No type information, default to `any` + return; + } + + const firstListElement = listNode.children[0].children[0]; + + if (firstListElement.type !== 'paragraph') { + throw new TypeError( + `expected first node in property type list node to be a paragraph, got ${firstListElement.type}` + ); + } + + // Should look something like these in the Markdown source: + // {integer} **Default:** 8192 + // {integer} bla bla bla + // {boolean} + // Text: {Function} bla bla bla + let typeNode = firstListElement.children[0]; + + /** + * @param {import('mdast').Link} node + */ + const parseTypeFromLink = node => { + const { type, value } = node.children[0]; + + if (type !== 'inlineCode') { + throw new TypeError( + `unexpected link node child type ${type} for property ${section['@name']}` + ); + } + + let formattedValue = value; + if (formattedValue.startsWith('<')) { + formattedValue = formattedValue.substring(1, formattedValue.length - 1); + } + + // TODO if this is a native type, make sure it's correct + // (bigint -> BigInt, integer -> number/BigInt or whatever) + section['@type'] = formattedValue; + }; + + switch (typeNode.type) { + case 'link': { + parseTypeFromLink(typeNode); + + break; + } + case 'text': { + if (typeNode.value !== 'Type: ') { + break; + } + + typeNode = firstListElement.children[1]; + + if (typeNode.type === 'link') { + parseTypeFromLink(typeNode); + } + + break; + } + default: { + // Not something that we can get a type from + break; + } + } + }; + + /** + * Adds the properties expected in a method section to an object. + * @param {HierarchizedEntry} entry The AST entry + * @param {import('../generated.d.ts').Property} section The method section + */ + return (entry, section) => { + // console.log(JSON.stringify(entry, null, 2)); + + // TODO how to tell if it's mutable? + parseType(entry, section); + + const parent = findParentSection(section, ['class', 'module']); + + // Add this section to the parent if it exists + if (parent) { + parent.properties.push(section); + } + }; +}; diff --git a/src/generators/json/utils/createSection.mjs b/src/generators/json/utils/createSection.mjs new file mode 100644 index 00000000..904f3314 --- /dev/null +++ b/src/generators/json/utils/createSection.mjs @@ -0,0 +1,130 @@ +// @ts-check +'use strict'; + +import { buildHierarchy } from '../../legacy-json/utils/buildHierarchy.mjs'; +import { createSectionBaseBuilder } from './createSectionBase.mjs'; +import { createModuleSectionBuilder } from './createModuleSection.mjs'; +import { createClassSectionBuilder } from './createClassSection.mjs'; +import { createMethodSectionBuilder } from './createMethodSection.mjs'; +import { createPropertySectionBuilder } from './createPropertySection.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + */ + +/** + * + */ +export const createSectionBuilder = () => { + const createSectionBase = createSectionBaseBuilder(); + const createModuleSection = createModuleSectionBuilder(); + const createClassSection = createClassSectionBuilder(); + const createMethodSection = createMethodSectionBuilder(); + const createPropertySection = createPropertySectionBuilder(); + + /** + * Creates the properties that exist in the root of a document + * @param {ApiDocMetadataEntry} head The head metadata entry + * @returns {import('../generated.d.ts').DocumentRoot} + */ + const createDocumentRoot = head => { + return { + source: head.api_doc_source, + }; + }; + + /** + * Processes children of a given entry and updates the section. + * @param {HierarchizedEntry} entry - The current entry. + * @param {import('../types.d.ts').Section | undefined} parent + */ + const handleChildren = ({ hierarchyChildren }, parent) => + hierarchyChildren?.forEach(child => createSection(child, parent)); + + /** + * @param {HierarchizedEntry} entry + * @param {import('../types.d.ts').Section | undefined} parent + * @returns {import('../types.d.ts').Section} + */ + const createSection = (entry, parent) => { + /** + * @type {import('../types.d.ts').Section} + */ + const section = createSectionBase(entry, parent?.type); + + // Temporarily add the parent section to the section so we have access to + // it and can easily traverse through them when we need to + section.parent = parent; + + switch (section.type) { + case 'module': + createModuleSection(entry, section); + break; + case 'class': + createClassSection(section); + break; + case 'method': + // createMethodSection(entry, section); + break; + case 'property': + createPropertySection(entry, section); + break; + case 'text': + if (parent) { + parent.text ??= []; + + parent.text.push(section); + } + + break; + default: + throw new TypeError(`unhandled section type ${section.type}`); + } + + handleChildren(entry, section); + + // Remove the parent property we added to the section earlier + delete section.parent; + + // if (parent) { + // if (!parent.tmp) { + // parent.tmp = []; + // } + // parent.tmp.push(section); + // } + // console.debug(section); + + return section; + }; + + /** + * Builds the module section from head metadata and entries. + * @param {ApiDocMetadataEntry} head The head metadata entry + * @param {Array} entries The list of metadata entries + * @returns {import('../generated.d.ts').NodeJsAPIDocumentationSchema} + */ + return (head, entries) => { + const entryHierarchy = buildHierarchy(entries); + + if (entryHierarchy.length != 1) { + throw new TypeError(`${head.api_doc_source} has multiple root elements`); + } + + const documentRoot = createDocumentRoot(head); + + const section = createSection(entryHierarchy[0], undefined); + + if (section.type !== 'module' && section.type !== 'text') { + throw new TypeError( + `expected root section to be a module or text, got ${section.type} (${head.api_doc_source})` + ); + } + + return { + // $schema: `https://nodejs.org/doc/${DOC_NODE_VERSION}/api/node-doc-schema.json` + $schema: './node-doc-schema.json', + ...documentRoot, + ...section, + }; + }; +}; diff --git a/src/generators/json/utils/createSectionBase.mjs b/src/generators/json/utils/createSectionBase.mjs new file mode 100644 index 00000000..94141b27 --- /dev/null +++ b/src/generators/json/utils/createSectionBase.mjs @@ -0,0 +1,197 @@ +// @ts-check +'use strict'; + +import { enforceArray } from '../../../utils/array.mjs'; + +/** + * @typedef {import('../../legacy-json/types.d.ts').HierarchizedEntry} HierarchizedEntry + */ + +/** + * Mapping of {@link HeadingMetadataEntry['type']} to types defined in the + * JSON schema. + */ +const ENTRY_TO_SECTION_TYPE = /** @type {const} */ ({ + module: 'module', + class: 'class', + ctor: 'method', + method: 'method', + classMethod: 'method', + property: 'property', + misc: 'text', + text: 'text', +}); + +export const createSectionBaseBuilder = () => { + /** + * @param {import('mdast').RootContent} headingNode + * @param {number} depth + * @returns {typeof ENTRY_TO_SECTION_TYPE[string]} + */ + const determineType = (headingNode, depth) => { + const fallback = depth === 1 ? 'module' : 'text'; + + return ENTRY_TO_SECTION_TYPE[headingNode?.data.type ?? fallback]; + }; + + /** + * Adds a description to the section base. + * @param {import('../generated.d.ts').SectionBase} section + * @param {Array} nodes + */ + const addDescriptionAndExamples = (section, nodes) => { + nodes.forEach(node => { + /** + * @type {string | undefined} + */ + let content; + + switch (node.type) { + case 'paragraph': { + addDescriptionAndExamples(section, node.children); + break; + } + case 'emphasis': { + addDescriptionAndExamples(section, node.children); + break; + } + case 'inlineCode': { + content = `\`${node.value}\``; + break; + } + case 'text': { + content = node.value; + break; + } + case 'link': { + if (node.label) { + // Standard link to some resource + content = `[${node.label}](${node.url})`; + } else { + // Missing the label, let's see if it's a reference to a global + const childNode = node.children[0]; + + if (childNode && childNode.type === 'inlineCode') { + content = `[${childNode.value}](${node.url})`; + } else { + // TODO + // console.error('not', childNode); + } + } + + break; + } + case 'code': { + // TODO this is kinda ugly + if (Array.isArray(section['@example'])) { + section['@example'] = [...section['@example'], node.value]; + } else if (section['@example']) { + section['@example'] = [section['@example'], node.value]; + } else { + section['@example'] = node.value; + } + + break; + } + default: { + // No content to add to description + break; + } + } + + if (content) { + // Create the description property if it doesn't already exist + section.description ??= ''; + + // Add this nodes' content to the description + section.description += content; + } + }); + }; + + /** + * Adds the deprecated property to the section if needed. + * @param {import('../generated.d.ts').SectionBase} section + * @param {HierarchizedEntry} entry + */ + const addDeprecatedStatus = (section, entry) => { + if (!entry.deprecated_in) { + return; + } + + section['@deprecated'] = enforceArray(entry.deprecated_in); + }; + + /** + * Adds the stability property to the section. + * @param {import('../generated.d.ts').SectionBase} section + * @param {HierarchizedEntry} entry + */ + const addStabilityStatus = (section, entry) => { + const stability = entry.stability.children.map(node => node.data)?.[0]; + + if (!stability) { + return; + } + + section.stability = { + value: stability.index, + text: stability.description, + }; + }; + + /** + * Adds the properties relating to versioning to the section. + * @param {import('../generated.d.ts').SectionBase} section + * @param {HierarchizedEntry} entry + */ + const addVersionProperties = (section, entry) => { + if (entry.changes.length > 0) { + section.changes = entry.changes.map(change => ({ + description: change.description, + prUrl: change['pr-url'], + version: enforceArray(change.version), + })); + } + + if (entry.added_in) { + section['@since'] = enforceArray(entry.added_in); + } + + if (entry.n_api_version) { + section.napiVersion = enforceArray(entry.n_api_version); + } + + if (entry.removed_in) { + section.removedIn = enforceArray(entry.removed_in); + } + }; + + /** + * Returns an object containing the properties that can be found in every + * section type that we have. + * + * @param {HierarchizedEntry} entry The AST entry + * @returns {import('../generated.d.ts').SectionBase} + */ + return entry => { + const [headingNode, ...nodes] = entry.content.children; + + const type = determineType(headingNode, entry.heading.depth); + + /** + * @type {import('../generated.d.ts').SectionBase} + */ + const base = { + type, + '@name': headingNode.data.name, + }; + + addDescriptionAndExamples(base, nodes); + addDeprecatedStatus(base, entry); + addStabilityStatus(base, entry); + addVersionProperties(base, entry); + + return base; + }; +}; diff --git a/src/generators/json/utils/findParentSection.mjs b/src/generators/json/utils/findParentSection.mjs new file mode 100644 index 00000000..d5f5fc09 --- /dev/null +++ b/src/generators/json/utils/findParentSection.mjs @@ -0,0 +1,25 @@ +'use strict'; + +import { enforceArray } from '../../../utils/array.mjs'; + +/** + * Finds the closest parent section with the specified type(s). + * @param {import('../types.d.ts').Section} section + * @param {import('../generated.d.ts').SectionBase['type'] | Array} type + * @returns {import('../types.d.ts').Section | undefined} + */ +export function findParentSection(section, type) { + type = enforceArray(type); + + let parent = section.parent; + + while (parent) { + if (type.includes(parent.type)) { + return parent; + } + + parent = parent.parent; + } + + return undefined; +} diff --git a/src/generators/legacy-json/types.d.ts b/src/generators/legacy-json/types.d.ts index 347ab62d..6334dfd4 100644 --- a/src/generators/legacy-json/types.d.ts +++ b/src/generators/legacy-json/types.d.ts @@ -8,7 +8,7 @@ export interface HierarchizedEntry extends ApiDocMetadataEntry { /** * List of child entries that are part of this entry's hierarchy. */ - hierarchyChildren: ApiDocMetadataEntry[]; + hierarchyChildren: HierarchizedEntry[]; } /** diff --git a/tmp.out b/tmp.out new file mode 100644 index 00000000..c15807e5 --- /dev/null +++ b/tmp.out @@ -0,0 +1,3 @@ +null +null +null diff --git a/tools/generate-json-types.mjs b/tools/generate-json-types.mjs new file mode 100755 index 00000000..d910876b --- /dev/null +++ b/tools/generate-json-types.mjs @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +/** + * Generates the typedefs for the JSON generator from the JSON schema + * + * To use, just run this. + */ + +import { join } from 'node:path'; +import { readFile, writeFile } from 'node:fs/promises'; +import { parse } from 'jsonc-parser'; +import { compile } from 'json-schema-to-typescript'; + +const JSON_GENERATOR_PATH = join( + import.meta.dirname, + '..', + 'src', + 'generators', + 'json' +); +const SCHEMA_PATH = join(JSON_GENERATOR_PATH, 'schema.jsonc'); +const TYPES_PATH = join(JSON_GENERATOR_PATH, 'generated.d.ts'); + +// Read the contents of the JSON schema +const schemaString = await readFile(SCHEMA_PATH, 'utf8'); + +// Parse the JSON schema into an object +const schema = await parse(schemaString); + +// Compile the the JSON schema into TypeScript typedefs +const typeDefs = await compile(schema, 'ApiDocSchema'); + +// Write the types to the expected output path +await writeFile(TYPES_PATH, typeDefs);