mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-14 14:45:17 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2766f3fdc | |||
| e24206ebf0 | |||
| 3410785a3a | |||
| b4a12d606e | |||
| a0545545d7 | |||
| 1f11650ca1 | |||
| da55facf2c | |||
| dc2dbc5710 | |||
| bdd6303bcc | |||
| 66d0fc0683 | |||
| 05e1d8cd96 | |||
| 9f0de74caa | |||
| a820739bd2 | |||
| 304042017c | |||
| 82cd9f2979 | |||
| a3f1fea27b | |||
| bb5c6a42e7 | |||
| 445baaaa61 | |||
| 7419ee7dba | |||
| aa6be381a4 | |||
| 61575838f3 | |||
| 359bb979f8 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Remove unused search api method from gitbook/api/lib
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Update OpenAPI code examples to support multiple content-type
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/openapi-parser': major
|
||||
---
|
||||
|
||||
Use Scalar parser to upgrade OpenAPI spec v2
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Hide x-gitbook-\* symbols in OpenAPI blocks
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Fix circularRef in schema + examples OpenAPI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Update design for hint block: use semantic colors (info, warning, danger, success) and add alternative styling for hints with headings
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add support for new OpenAPI ref
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Fix ID not set when there is no operation summary
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@gitbook/openapi-parser': patch
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Render GitBook blocks in OpenAPI operation description
|
||||
@@ -22,3 +22,6 @@ yarn-error.log*
|
||||
|
||||
# Env files
|
||||
.env.local
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
"wrangler": "3.82.0",
|
||||
},
|
||||
},
|
||||
"packages/colors": {
|
||||
"name": "@gitbook/colors",
|
||||
"version": "0.2.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
},
|
||||
},
|
||||
"packages/emoji-codepoints": {
|
||||
"name": "@gitbook/emoji-codepoints",
|
||||
"version": "0.2.0",
|
||||
@@ -31,10 +38,11 @@
|
||||
},
|
||||
"packages/gitbook": {
|
||||
"name": "gitbook",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.2",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.93.0",
|
||||
"@gitbook/api": "^0.95.0",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -61,6 +69,8 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.15",
|
||||
"micromark": "^4.0.1",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"next": "14.2.23",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
@@ -71,17 +81,11 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"rison": "^0.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "^1.27.2",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.4",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
},
|
||||
@@ -151,12 +155,10 @@
|
||||
},
|
||||
"packages/openapi-parser": {
|
||||
"name": "@gitbook/openapi-parser",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.4",
|
||||
"@scalar/openapi-types": "^0.1.6",
|
||||
"swagger2openapi": "^7.0.8",
|
||||
"yaml": "1.10.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
@@ -207,13 +209,14 @@
|
||||
},
|
||||
"packages/react-openapi": {
|
||||
"name": "@gitbook/react-openapi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@scalar/api-client-react": "1.0.87",
|
||||
"@scalar/oas-utils": "^0.2.101",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
"react-aria": "^3.37.0",
|
||||
"react-aria-components": "^1.6.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
@@ -564,8 +567,6 @@
|
||||
|
||||
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
||||
|
||||
"@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="],
|
||||
|
||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.6.8", "", { "dependencies": { "@floating-ui/utils": "^0.2.8" } }, "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA=="],
|
||||
@@ -598,6 +599,8 @@
|
||||
|
||||
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
|
||||
|
||||
"@gitbook/colors": ["@gitbook/colors@workspace:packages/colors"],
|
||||
|
||||
"@gitbook/emoji-codepoints": ["@gitbook/emoji-codepoints@workspace:packages/emoji-codepoints"],
|
||||
|
||||
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.8", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.6.0" } }, "sha512-i4PgiuGyUb52Muhc52kK3aMJIMfMkA2RbPW30tre8a6M8T6mWTfYo6gafSgjNvF1vH29zcuB8oBYnF0gO4XcHA=="],
|
||||
@@ -1676,8 +1679,6 @@
|
||||
|
||||
"call-bind": ["call-bind@1.0.7", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.1" } }, "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w=="],
|
||||
|
||||
"call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
@@ -1718,8 +1719,6 @@
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
@@ -1916,8 +1915,6 @@
|
||||
|
||||
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
|
||||
|
||||
"es6-promise": ["es6-promise@3.3.1", "", {}, "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg=="],
|
||||
|
||||
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
|
||||
|
||||
"es6-weak-map": ["es6-weak-map@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.46", "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA=="],
|
||||
@@ -2046,8 +2043,6 @@
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
|
||||
|
||||
"fast-text-encoding": ["fast-text-encoding@1.0.6", "", {}, "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.0.2", "", {}, "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row=="],
|
||||
@@ -2116,8 +2111,6 @@
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.2.4", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="],
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
@@ -2210,7 +2203,7 @@
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.2.0", "unist-util-position": "^5.0.0" } }, "sha512-IGrgWLuip4O2nq5CugXy4GI2V8kx4sFVy5Hd4vF7AR2gxS0N9s7nEAVUyeMtZKZvzrxVsHt73XdTsno1tClIkQ=="],
|
||||
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
|
||||
|
||||
@@ -2240,8 +2233,6 @@
|
||||
|
||||
"http-errors": ["http-errors@1.4.0", "", { "dependencies": { "inherits": "2.0.1", "statuses": ">= 1.2.1 < 2" } }, "sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw=="],
|
||||
|
||||
"http2-client": ["http2-client@1.3.5", "", {}, "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"human-id": ["human-id@1.0.2", "", {}, "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw=="],
|
||||
@@ -2332,7 +2323,7 @@
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
"is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||
|
||||
"is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
|
||||
|
||||
@@ -2404,6 +2395,8 @@
|
||||
|
||||
"json-stringify-deterministic": ["json-stringify-deterministic@1.0.12", "", {}, "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g=="],
|
||||
|
||||
"json-xml-parse": ["json-xml-parse@1.3.0", "", {}, "sha512-MVosauc/3W2wL4dd4yaJzH5oXw+HOUfptn0+d4+bFghMiJFop7MaqIwFXJNLiRnNYJNQ6L4o7B+53n5wcvoLFw=="],
|
||||
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||
@@ -2542,7 +2535,7 @@
|
||||
|
||||
"microdiff": ["microdiff@1.4.0", "", {}, "sha512-OBKBOa1VBznvLPb/3ljeJaENVe0fO0lnWl77lR4vhPlQD71UpjEoRV5P0KdQkcjbFlBu1Oy2mEUBMU3wxcBAGg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
|
||||
"micromark": ["micromark@4.0.1", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA=="],
|
||||
|
||||
@@ -2654,16 +2647,12 @@
|
||||
|
||||
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-fetch-h2": ["node-fetch-h2@2.3.0", "", { "dependencies": { "http2-client": "^1.2.5" } }, "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg=="],
|
||||
"node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
|
||||
|
||||
"node-forge": ["node-forge@1.3.1", "", {}, "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="],
|
||||
|
||||
"node-gyp-build": ["node-gyp-build@4.8.2", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw=="],
|
||||
|
||||
"node-readfiles": ["node-readfiles@0.2.0", "", { "dependencies": { "es6-promise": "^3.2.1" } }, "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.18", "", {}, "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="],
|
||||
|
||||
"nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
|
||||
@@ -2680,16 +2669,6 @@
|
||||
|
||||
"nuqs": ["nuqs@2.2.3", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "@remix-run/react": ">=2", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router-dom": ">=6" }, "optionalPeers": ["@remix-run/react", "next", "react-router-dom"] }, "sha512-nMCcUW06KSqEXA0xp+LiRqDpIE59BVYbjZLe0HUisJAlswfihHYSsAjYTzV0lcE1thfh8uh+LqUHGdQ8qq8rfA=="],
|
||||
|
||||
"oas-kit-common": ["oas-kit-common@1.0.8", "", { "dependencies": { "fast-safe-stringify": "^2.0.7" } }, "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ=="],
|
||||
|
||||
"oas-linter": ["oas-linter@3.2.2", "", { "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", "yaml": "^1.10.0" } }, "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ=="],
|
||||
|
||||
"oas-resolver": ["oas-resolver@2.5.6", "", { "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", "reftools": "^1.1.9", "yaml": "^1.10.0", "yargs": "^17.0.1" }, "bin": { "resolve": "resolve.js" } }, "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ=="],
|
||||
|
||||
"oas-schema-walker": ["oas-schema-walker@1.1.5", "", {}, "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ=="],
|
||||
|
||||
"oas-validator": ["oas-validator@5.0.8", "", { "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", "oas-linter": "^3.2.2", "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", "reftools": "^1.1.9", "should": "^13.2.1", "yaml": "^1.10.0" } }, "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
|
||||
@@ -2922,8 +2901,6 @@
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.1", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" } }, "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg=="],
|
||||
|
||||
"reftools": ["reftools@1.1.9", "", {}, "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w=="],
|
||||
|
||||
"regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
|
||||
|
||||
"regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
@@ -2960,8 +2937,6 @@
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"require-in-the-middle": ["require-in-the-middle@7.4.0", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ=="],
|
||||
@@ -3042,18 +3017,6 @@
|
||||
|
||||
"shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="],
|
||||
|
||||
"should": ["should@13.2.3", "", { "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", "should-type": "^1.4.0", "should-type-adaptors": "^1.0.1", "should-util": "^1.0.0" } }, "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ=="],
|
||||
|
||||
"should-equal": ["should-equal@2.0.0", "", { "dependencies": { "should-type": "^1.4.0" } }, "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA=="],
|
||||
|
||||
"should-format": ["should-format@3.0.3", "", { "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" } }, "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q=="],
|
||||
|
||||
"should-type": ["should-type@1.4.0", "", {}, "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ=="],
|
||||
|
||||
"should-type-adaptors": ["should-type-adaptors@1.1.0", "", { "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" } }, "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA=="],
|
||||
|
||||
"should-util": ["should-util@1.0.1", "", {}, "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g=="],
|
||||
|
||||
"side-channel": ["side-channel@1.0.6", "", { "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" } }, "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
@@ -3148,8 +3111,6 @@
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"swagger2openapi": ["swagger2openapi@7.0.8", "", { "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", "node-fetch-h2": "^2.3.0", "node-readfiles": "^0.2.0", "oas-kit-common": "^1.0.8", "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", "oas-validator": "^5.0.8", "reftools": "^1.1.9", "yaml": "^1.10.0", "yargs": "^17.0.1" }, "bin": { "swagger2openapi": "swagger2openapi.js", "oas-validate": "oas-validate.js", "boast": "boast.js" } }, "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g=="],
|
||||
|
||||
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@2.5.5", "", {}, "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA=="],
|
||||
@@ -3376,7 +3337,7 @@
|
||||
|
||||
"wrangler": ["wrangler@3.82.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/workers-shared": "0.6.0", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "date-fns": "^4.1.0", "esbuild": "0.17.19", "itty-time": "^1.0.6", "miniflare": "3.20241018.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.3.0", "resolve": "^1.22.8", "resolve.exports": "^2.0.2", "selfsigned": "^2.0.1", "source-map": "^0.6.1", "unenv": "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf", "workerd": "1.20241018.1", "xxhash-wasm": "^1.0.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20241018.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-W4gyIkxOfqbFU6QrUvK+Ay2rwH+DcyXmIuT/1/9EG9K6jngmmon8ifVYGRTftRb2Vu7hDNLovbGfjeAXm+CEXA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
@@ -3398,15 +3359,11 @@
|
||||
|
||||
"y-codemirror.next": ["y-codemirror.next@0.3.5", "", { "dependencies": { "lib0": "^0.2.42" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "yjs": "^13.5.6" } }, "sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
||||
"yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
"yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||
|
||||
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
|
||||
|
||||
@@ -4138,8 +4095,6 @@
|
||||
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
|
||||
"@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
@@ -4170,6 +4125,8 @@
|
||||
|
||||
"@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
|
||||
|
||||
"@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"@mapbox/node-pre-gyp/tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||
|
||||
"@next/eslint-plugin-next/glob": ["glob@10.3.10", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g=="],
|
||||
@@ -4262,22 +4219,16 @@
|
||||
|
||||
"@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="],
|
||||
|
||||
"@scalar/api-client/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"@scalar/components/@scalar/themes": ["@scalar/themes@0.9.48", "", { "dependencies": { "@scalar/types": "0.0.19" } }, "sha512-lsehl9KnlKgWG8BOIIaX3iIuNMLjRFZ0ctb1KsQBhOSsoTrq3c9EUJdYJiiTWzm52zdhF+RkIx7uHaX/pvgctw=="],
|
||||
|
||||
"@scalar/components/nanoid": ["nanoid@5.0.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ=="],
|
||||
|
||||
"@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.8.9", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-vTXrkl/hX3CG2dMe8mYutjsjfFLZOXGFJGqz6k8lUk8avFP/5eGEsd/eZe1UIcwtDwcjgJIyQ4p3pIC7jBvfuw=="],
|
||||
|
||||
"@scalar/import/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"@scalar/oas-utils/nanoid": ["nanoid@5.0.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ=="],
|
||||
|
||||
"@scalar/oas-utils/yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
|
||||
|
||||
"@scalar/openapi-parser/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"@scalar/use-toasts/nanoid": ["nanoid@5.0.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
@@ -4286,14 +4237,14 @@
|
||||
|
||||
"@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"@sentry/cli/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"@sentry/nextjs/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
|
||||
"@sentry/node/@opentelemetry/core": ["@opentelemetry/core@1.30.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA=="],
|
||||
|
||||
"@sentry/webpack-plugin/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="],
|
||||
|
||||
"@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@smithy/chunked-blob-reader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -4484,8 +4435,6 @@
|
||||
|
||||
"@vercel/fun/ms": ["ms@2.1.1", "", {}, "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="],
|
||||
|
||||
"@vercel/fun/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
|
||||
|
||||
"@vercel/fun/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="],
|
||||
|
||||
"@vercel/gatsby-plugin-vercel-builder/esbuild": ["esbuild@0.14.47", "", { "optionalDependencies": { "esbuild-android-64": "0.14.47", "esbuild-android-arm64": "0.14.47", "esbuild-darwin-64": "0.14.47", "esbuild-darwin-arm64": "0.14.47", "esbuild-freebsd-64": "0.14.47", "esbuild-freebsd-arm64": "0.14.47", "esbuild-linux-32": "0.14.47", "esbuild-linux-64": "0.14.47", "esbuild-linux-arm": "0.14.47", "esbuild-linux-arm64": "0.14.47", "esbuild-linux-mips64le": "0.14.47", "esbuild-linux-ppc64le": "0.14.47", "esbuild-linux-riscv64": "0.14.47", "esbuild-linux-s390x": "0.14.47", "esbuild-netbsd-64": "0.14.47", "esbuild-openbsd-64": "0.14.47", "esbuild-sunos-64": "0.14.47", "esbuild-windows-32": "0.14.47", "esbuild-windows-64": "0.14.47", "esbuild-windows-arm64": "0.14.47" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA=="],
|
||||
@@ -4644,6 +4593,10 @@
|
||||
|
||||
"gaxios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"gitbook/@gitbook/api": ["@gitbook/api@0.95.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-9KAbt27Ile6cqAch7QEbiJHALQHojYlhsPzilgdQ5wpHgLwsrd7Smd58A3/8bWBKq4KV0vP4rh3oYhIw+LlWFw=="],
|
||||
|
||||
"gitbook-v2/next": ["next@15.2.0-canary.45", "", { "dependencies": { "@next/env": "15.2.0-canary.45", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.0-canary.45", "@next/swc-darwin-x64": "15.2.0-canary.45", "@next/swc-linux-arm64-gnu": "15.2.0-canary.45", "@next/swc-linux-arm64-musl": "15.2.0-canary.45", "@next/swc-linux-x64-gnu": "15.2.0-canary.45", "@next/swc-linux-x64-musl": "15.2.0-canary.45", "@next/swc-win32-arm64-msvc": "15.2.0-canary.45", "@next/swc-win32-x64-msvc": "15.2.0-canary.45", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-UsneTQn9tntbiAaXpvoXhhsTBb58Q2XIs2Dfka+qWA8motBz0ZvW297YHLxhdur4xN0IJvknnZKl5Bs7wAGlOg=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="],
|
||||
@@ -4682,6 +4635,8 @@
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"mdast-util-from-markdown/micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
|
||||
|
||||
"mdast-util-gfm/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
|
||||
"mdast-util-gfm-footnote/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
@@ -4694,18 +4649,12 @@
|
||||
|
||||
"meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"meow/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||
|
||||
"micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
|
||||
|
||||
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
|
||||
|
||||
"micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"minimist-options/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="],
|
||||
|
||||
"minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||
|
||||
"minizlib/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
|
||||
|
||||
"next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
|
||||
@@ -4730,8 +4679,6 @@
|
||||
|
||||
"postcss-load-config/lilconfig": ["lilconfig@3.1.2", "", {}, "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow=="],
|
||||
|
||||
"postcss-load-config/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"psi/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
|
||||
"pump/end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
|
||||
@@ -4760,6 +4707,8 @@
|
||||
|
||||
"read-yaml-file/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
|
||||
|
||||
"rehype-stringify/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
|
||||
|
||||
"remark-stringify/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
|
||||
|
||||
"require-in-the-middle/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
@@ -4814,6 +4763,8 @@
|
||||
|
||||
"type-is/mime-types": ["mime-types@3.0.0", "", { "dependencies": { "mime-db": "^1.53.0" } }, "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w=="],
|
||||
|
||||
"unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"update-notifier/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
@@ -4828,6 +4779,12 @@
|
||||
|
||||
"wrangler/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
@@ -5304,8 +5261,6 @@
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@mapbox/node-pre-gyp/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
@@ -5614,6 +5569,8 @@
|
||||
|
||||
"gtoken/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
|
||||
|
||||
"mdast-util-from-markdown/micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"raw-body/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
|
||||
|
||||
"raw-body/http-errors/inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
@@ -5660,6 +5617,8 @@
|
||||
|
||||
"wrangler/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@aws-sdk/client-dynamodb/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-sdk/client-dynamodb/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.734.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.734.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.734.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.734.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.734.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.1", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.2", "@smithy/middleware-retry": "^4.0.3", "@smithy/middleware-serde": "^4.0.1", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.2", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.2", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.3", "@smithy/util-defaults-mode-node": "^4.0.3", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-oerepp0mut9VlgTwnG5Ds/lb0C0b2/rQ+hL/rF6q+HGKPfGsCuPvFx1GtwGKCXd49ase88/jVgrhcA9OQbz3kg=="],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist/
|
||||
@@ -0,0 +1,7 @@
|
||||
# @gitbook/colors
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 445baaa: Initial release
|
||||
@@ -0,0 +1,3 @@
|
||||
# `@gitbook/colors`
|
||||
|
||||
A set of default colors and transformation functions used throughout the GitBook Open and app.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@gitbook/colors",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "0.2.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "tsc -w"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Default primary color throughout the GitBook ecosystem.
|
||||
*/
|
||||
export const DEFAULT_PRIMARY_COLOR = '#346DDB';
|
||||
|
||||
/**
|
||||
* The darkest color that exists in GitBook, used as the relative minimum of every generated color scale.
|
||||
*/
|
||||
export const DARK_BASE = '#1D1D1D';
|
||||
|
||||
/**
|
||||
* The lightest color that exists in GitBook, used as the relative maximum of every generated color scale.
|
||||
*/
|
||||
export const LIGHT_BASE = '#FFFFFF';
|
||||
|
||||
/**
|
||||
* Used as the basis of all UI elements that are not colored by the primary color. Neutral gray by default, overridden by site customization.
|
||||
*/
|
||||
export const DEFAULT_TINT_COLOR = '#787878';
|
||||
|
||||
/**
|
||||
* Used for informational messages and neutral alerts.
|
||||
*/
|
||||
export const DEFAULT_HINT_INFO_COLOR = '#787878';
|
||||
|
||||
/**
|
||||
* Used for showing important information or non-critical warnings.
|
||||
*/
|
||||
export const DEFAULT_HINT_WARNING_COLOR = '#FE9A00';
|
||||
|
||||
/**
|
||||
* Used for destructive actions or raising attention to critical information.
|
||||
*/
|
||||
export const DEFAULT_HINT_DANGER_COLOR = '#FB2C36';
|
||||
|
||||
/**
|
||||
* Used for showing positive actions or achievements.
|
||||
*/
|
||||
export const DEFAULT_HINT_SUCCESS_COLOR = '#00C950';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './colors';
|
||||
export * from './transformations';
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DARK_BASE, LIGHT_BASE, DEFAULT_TINT_COLOR } from './colors';
|
||||
|
||||
type ColorShades = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6,9 +8,6 @@ type RGBColor = [number, number, number];
|
||||
type OKLABColor = { L: number; A: number; B: number };
|
||||
type OKLCHColor = { L: number; C: number; H: number };
|
||||
|
||||
export const DARK_BASE = '#1d1d1d';
|
||||
export const LIGHT_BASE = '#ffffff';
|
||||
export const DEFAULT_TINT_COLOR = '#787878';
|
||||
const D65 = [95.047, 100.0, 108.883]; // Reference white (D65)
|
||||
|
||||
export enum ColorCategory {
|
||||
@@ -226,7 +225,7 @@ export function colorScale(
|
||||
/**
|
||||
* Convert a hex color to an RGB color set.
|
||||
*/
|
||||
function hexToRgbArray(hex: string): RGBColor {
|
||||
export function hexToRgbArray(hex: string): RGBColor {
|
||||
const originalHex = hex;
|
||||
|
||||
let value = hex.replace('#', '');
|
||||
@@ -252,7 +251,7 @@ function hexToRgbArray(hex: string): RGBColor {
|
||||
/**
|
||||
* Convert a RGB color set to a hex color.
|
||||
*/
|
||||
function rgbArrayToHex(rgb: RGBColor): string {
|
||||
export function rgbArrayToHex(rgb: RGBColor): string {
|
||||
return `#${rgb
|
||||
.map((channel) => {
|
||||
const component = channel.toString(16);
|
||||
@@ -262,7 +261,7 @@ function rgbArrayToHex(rgb: RGBColor): string {
|
||||
.join('')}`;
|
||||
}
|
||||
|
||||
function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
export function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
const rgb = end.map((channel, index) => {
|
||||
return Math.round(channel + percentage * (start[index] - channel));
|
||||
});
|
||||
@@ -271,21 +270,21 @@ function getColor(percentage: number, start: RGBColor, end: RGBColor) {
|
||||
}
|
||||
|
||||
// Utility constants and helper functions
|
||||
function rgbToLinear(rgb: RGBColor): [number, number, number] {
|
||||
export function rgbToLinear(rgb: RGBColor): [number, number, number] {
|
||||
return rgb.map((v) => {
|
||||
const scaled = v / 255;
|
||||
return scaled <= 0.04045 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
|
||||
}) as [number, number, number];
|
||||
}
|
||||
|
||||
function linearToRgb(linear: [number, number, number]): RGBColor {
|
||||
export function linearToRgb(linear: [number, number, number]): RGBColor {
|
||||
return linear.map((v) => {
|
||||
const scaled = v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
|
||||
return Math.round(Math.max(0, Math.min(1, scaled)) * 255);
|
||||
}) as RGBColor;
|
||||
}
|
||||
|
||||
function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
export function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
const [r, g, b] = rgbToLinear(rgb);
|
||||
|
||||
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||
@@ -303,7 +302,7 @@ function rgbToOklab(rgb: RGBColor): OKLABColor {
|
||||
};
|
||||
}
|
||||
|
||||
function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
export function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
const { L, A, B } = oklab;
|
||||
|
||||
const lRoot = L + 0.3963377774 * A + 0.2158037573 * B;
|
||||
@@ -321,14 +320,14 @@ function oklabToRgb(oklab: OKLABColor): RGBColor {
|
||||
return linearToRgb([r, g, b]);
|
||||
}
|
||||
|
||||
function oklabToOklch(oklab: OKLABColor): OKLCHColor {
|
||||
export function oklabToOklch(oklab: OKLABColor): OKLCHColor {
|
||||
const { L, A, B } = oklab;
|
||||
const C = Math.sqrt(A ** 2 + B ** 2);
|
||||
const H = (Math.atan2(B, A) * 180) / Math.PI;
|
||||
return { L, C, H: H < 0 ? H + 360 : H };
|
||||
}
|
||||
|
||||
function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
export function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
const { L, C, H } = oklch;
|
||||
const rad = (H * Math.PI) / 180;
|
||||
return {
|
||||
@@ -338,15 +337,15 @@ function oklchToOklab(oklch: OKLCHColor): OKLABColor {
|
||||
};
|
||||
}
|
||||
|
||||
function rgbToOklch(rgb: RGBColor): OKLCHColor {
|
||||
export function rgbToOklch(rgb: RGBColor): OKLCHColor {
|
||||
return oklabToOklch(rgbToOklab(rgb));
|
||||
}
|
||||
|
||||
function oklchToRgb(oklch: OKLCHColor): RGBColor {
|
||||
export function oklchToRgb(oklch: OKLCHColor): RGBColor {
|
||||
return oklabToRgb(oklchToOklab(oklch));
|
||||
}
|
||||
|
||||
function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
export function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
const [r, g, b] = rgbToLinear(rgb);
|
||||
return [
|
||||
(r * 0.4124564 + g * 0.3575761 + b * 0.1804375) * 100,
|
||||
@@ -355,7 +354,11 @@ function rgbToXyz(rgb: RGBColor): [number, number, number] {
|
||||
];
|
||||
}
|
||||
|
||||
function xyzToLab65(xyz: [number, number, number]): { L: number; A: number; B: number } {
|
||||
export function xyzToLab65(xyz: [number, number, number]): {
|
||||
L: number;
|
||||
A: number;
|
||||
B: number;
|
||||
} {
|
||||
const [x, y, z] = xyz.map((v, i) => {
|
||||
const scaled = v / D65[i];
|
||||
return scaled > 0.008856 ? Math.cbrt(scaled) : 7.787 * scaled + 16 / 116;
|
||||
@@ -368,7 +371,7 @@ function xyzToLab65(xyz: [number, number, number]): { L: number; A: number; B: n
|
||||
};
|
||||
}
|
||||
|
||||
function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
export function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
return xyzToLab65(rgbToXyz(rgb));
|
||||
}
|
||||
|
||||
@@ -376,7 +379,7 @@ function rgbTolab65(rgb: RGBColor): { L: number; A: number; B: number } {
|
||||
Delta Phi Star perceptual lightness contrast by Andrew Somers:
|
||||
https://github.com/Myndex/deltaphistar
|
||||
*/
|
||||
const PHI = 0.5 + Math.sqrt(1.25);
|
||||
export const PHI = 0.5 + Math.sqrt(1.25);
|
||||
|
||||
export function dpsContrast(a: RGBColor, b: RGBColor) {
|
||||
const dps = Math.abs(rgbTolab65(a).L ** PHI - rgbTolab65(b).L ** PHI);
|
||||
@@ -387,7 +390,10 @@ export function dpsContrast(a: RGBColor, b: RGBColor) {
|
||||
export function colorContrast(background: string, foreground: string[] = [LIGHT_BASE, DARK_BASE]) {
|
||||
const bg = hexToRgbArray(background);
|
||||
|
||||
const best: { color?: RGBColor; contrast: number } = { color: undefined, contrast: 0 };
|
||||
const best: { color?: RGBColor; contrast: number } = {
|
||||
color: undefined,
|
||||
contrast: 0,
|
||||
};
|
||||
for (const color of foreground) {
|
||||
const c = hexToRgbArray(color);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"incremental": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,5 +1,22 @@
|
||||
# gitbook
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 359bb97: Fix opening external links when the GitBook page is embedded in an iframe
|
||||
- 6157583: Improve Markdown parsing
|
||||
- 82cd9f2: Add support for anchor links in OpenAPI blocks
|
||||
- Updated dependencies [445baaa]
|
||||
- Updated dependencies [bb5c6a4]
|
||||
- Updated dependencies [a3f1fea]
|
||||
- Updated dependencies [6157583]
|
||||
- Updated dependencies [7419ee7]
|
||||
- Updated dependencies [82cd9f2]
|
||||
- @gitbook/colors@0.2.0
|
||||
- @gitbook/react-openapi@1.0.2
|
||||
- @gitbook/openapi-parser@1.0.1
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
CustomizationThemeMode,
|
||||
SiteCustomizationSettings,
|
||||
} from '@gitbook/api';
|
||||
import { test, expect, Page, BrowserContext } from '@playwright/test';
|
||||
import { BrowserContext, expect, Page, test } from '@playwright/test';
|
||||
import deepMerge from 'deepmerge';
|
||||
import rison from 'rison';
|
||||
import { DeepPartial } from 'ts-essentials';
|
||||
@@ -222,6 +222,10 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
||||
styling: {
|
||||
theme: newTheme,
|
||||
primaryColor: { light: '#346DDB', dark: '#346DDB' },
|
||||
infoColor: { light: '#787878', dark: '#787878' },
|
||||
warningColor: { light: '#FE9A00', dark: '#FE9A00' },
|
||||
dangerColor: { light: '#FB2C36', dark: '#FB2C36' },
|
||||
successColor: { light: '#00C950', dark: '#00C950' },
|
||||
corners: CustomizationCorners.Rounded,
|
||||
font: CustomizationFont.Inter,
|
||||
background: CustomizationBackground.Plain,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent -f ../../.env.local next dev",
|
||||
@@ -17,8 +17,9 @@
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gitbook/api": "^0.93.0",
|
||||
"@gitbook/api": "^0.95.0",
|
||||
"@gitbook/cache-do": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -45,6 +46,8 @@
|
||||
"mathjax": "^3.2.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.15",
|
||||
"micromark": "^4.0.1",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"next": "14.2.23",
|
||||
"next-themes": "^0.2.1",
|
||||
"nuqs": "^2.2.3",
|
||||
@@ -55,17 +58,11 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"rison": "^0.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "^1.27.2",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.4",
|
||||
"url-join": "^5.0.0",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
import { CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import { colorContrast } from '@gitbook/colors';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { NextRequest } from 'next/server';
|
||||
import React from 'react';
|
||||
|
||||
import { googleFontsMap } from '@/fonts';
|
||||
import { colorContrast } from '@/lib/colors';
|
||||
import { getAbsoluteHref } from '@/lib/links';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
import { getContentTitle } from '@/lib/utils';
|
||||
|
||||
@@ -244,8 +244,8 @@ async function PDFPageDocument(props: {
|
||||
{document ? (
|
||||
<DocumentView
|
||||
document={document}
|
||||
style={'mt-6 space-y-6'}
|
||||
blockStyle={['max-w-full']}
|
||||
style="mt-6 space-y-6"
|
||||
blockStyle="max-w-full"
|
||||
context={{
|
||||
mode: 'print',
|
||||
content: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SiteInsightsAd } from '@gitbook/api';
|
||||
import { hexToRgba } from '@gitbook/colors';
|
||||
import * as React from 'react';
|
||||
|
||||
import { hexToRgba } from '@/lib/colors';
|
||||
import { getResizedImageURL } from '@/lib/images';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DocumentBlockHeading } from '@gitbook/api';
|
||||
import type { DocumentBlockHeading } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from './Block';
|
||||
import type { BlockProps } from './Block';
|
||||
import { Inlines } from './Inlines';
|
||||
import { getBlockTextStyle } from './spacing';
|
||||
|
||||
@@ -24,6 +24,7 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
'hash',
|
||||
'grid',
|
||||
'grid-area-1-1',
|
||||
'relative',
|
||||
@@ -41,20 +42,14 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
||||
<a
|
||||
href={`#${id}`}
|
||||
aria-label="Direct link to heading"
|
||||
className={tcls(
|
||||
'inline-flex',
|
||||
'h-full',
|
||||
'items-start',
|
||||
'dark:shadow-none',
|
||||
'dark:ring-0',
|
||||
textStyle.lineHeight,
|
||||
)}
|
||||
className={tcls('inline-flex', 'h-full', 'items-start', textStyle.lineHeight)}
|
||||
>
|
||||
<Icon
|
||||
icon="hashtag"
|
||||
className={tcls(
|
||||
'w-3.5',
|
||||
'h-[1lh]',
|
||||
'h-[1em]',
|
||||
'mt-0.5',
|
||||
'transition-colors',
|
||||
'text-transparent',
|
||||
'group-hover:text-tint-subtle',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DocumentBlockHint } from '@gitbook/api';
|
||||
import { Icon, IconName } from '@gitbook/icons';
|
||||
import type { DocumentBlockHeading, DocumentBlockHint } from '@gitbook/api';
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from './Block';
|
||||
import { Block, type BlockProps } from './Block';
|
||||
import { Blocks } from './Blocks';
|
||||
import { getBlockTextStyle } from './spacing';
|
||||
|
||||
@@ -13,41 +13,74 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
const hintStyle = HINT_STYLES[block.data.style] ?? HINT_STYLES.info;
|
||||
const firstLine = getBlockTextStyle(block.nodes[0]);
|
||||
|
||||
const firstNode = block.nodes[0];
|
||||
const hasHeading = ['heading-1', 'heading-2', 'heading-3'].includes(block.nodes[0].type);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'hint',
|
||||
'p-4',
|
||||
'transition-colors',
|
||||
'rounded-md',
|
||||
hasHeading ? 'rounded-l' : null,
|
||||
'straight-corners:rounded-none',
|
||||
hintStyle.style,
|
||||
'overflow-hidden',
|
||||
hasHeading ? ['border-l-2', hintStyle.containerWithHeader] : hintStyle.container,
|
||||
|
||||
'text-sm',
|
||||
|
||||
'grid',
|
||||
'grid-cols-[auto_1fr]',
|
||||
hasHeading ? 'grid-rows-[auto_auto]' : '',
|
||||
|
||||
style,
|
||||
)}
|
||||
>
|
||||
<div className={tcls('flex', 'flex-row')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'py-3',
|
||||
'pl-3',
|
||||
'-mt-px', // Bump icon up 1px for optical alignment with heading
|
||||
hasHeading ? hintStyle.header : null,
|
||||
hintStyle.iconColor,
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={hintStyle.icon}
|
||||
className={tcls(
|
||||
'size-5',
|
||||
'mr-4',
|
||||
'mt-0.5',
|
||||
firstLine.lineHeight,
|
||||
hintStyle.iconColor,
|
||||
)}
|
||||
/>
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
nodes={block.nodes}
|
||||
blockStyle={tcls(
|
||||
hintStyle.bodyColor,
|
||||
// render hash icon on the other side of the heading
|
||||
'flip-heading-hash',
|
||||
)}
|
||||
style={['flex-1', 'space-y-4', '[&_.hint]:border', '[&_pre]:border']}
|
||||
className={tcls('size-[1.2em]', 'mt-0.5', firstLine.lineHeight)}
|
||||
/>
|
||||
</div>
|
||||
{hasHeading ? (
|
||||
<Block
|
||||
style={tcls(
|
||||
'text-[1em] *:mt-0 p-3 flip-heading-hash',
|
||||
hasHeading ? hintStyle.header : null,
|
||||
)}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
{...contextProps}
|
||||
block={firstNode}
|
||||
/>
|
||||
) : null}
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
nodes={hasHeading ? block.nodes.slice(1) : block.nodes}
|
||||
blockStyle={tcls(
|
||||
hintStyle.body,
|
||||
// render hash icon on the other side of the heading
|
||||
'flip-heading-hash',
|
||||
)}
|
||||
style={[
|
||||
'p-3',
|
||||
'empty:p-0',
|
||||
'-row-end-1',
|
||||
'-col-end-1',
|
||||
'space-y-4',
|
||||
'[&_.hint]:border',
|
||||
'[&_pre]:border',
|
||||
'[&_pre]:border-neutral',
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -55,77 +88,69 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
const HINT_STYLES: {
|
||||
[style in DocumentBlockHint['data']['style']]: {
|
||||
icon: IconName;
|
||||
iconColor: ClassValue;
|
||||
bodyColor: ClassValue;
|
||||
style: ClassValue;
|
||||
iconColor?: ClassValue;
|
||||
body?: ClassValue;
|
||||
header?: ClassValue;
|
||||
container?: ClassValue;
|
||||
containerWithHeader?: ClassValue;
|
||||
};
|
||||
} = {
|
||||
info: {
|
||||
icon: 'circle-info',
|
||||
iconColor: ['text-primary'],
|
||||
bodyColor: ['[&_a]:text-primary', '[&_a:hover]:text-primary-strong'],
|
||||
style: [
|
||||
'bg-tint',
|
||||
'print-mode:!bg-tint',
|
||||
'theme-muted:bg-tint-base',
|
||||
'theme-bold-tint:bg-tint-base',
|
||||
'theme-gradient:bg-tint-12/1',
|
||||
'border-tint',
|
||||
'[&_.can-override-bg]:bg-tint-active',
|
||||
'[&_.can-override-text]:text-tint-strong',
|
||||
iconColor: 'text-info-subtle contrast-more:text-info',
|
||||
header: 'bg-info-active',
|
||||
body: [
|
||||
'text-neutral-strong',
|
||||
'[&_.can-override-bg]:bg-neutral-active',
|
||||
'[&_.can-override-text]:text-neutral-strong',
|
||||
],
|
||||
container:
|
||||
'bg-neutral theme-muted:bg-neutral-base theme-bold-tint:bg-neutral-base theme-gradient:bg-neutral-12/1 border-neutral',
|
||||
containerWithHeader: 'border-info-solid bg-neutral-subtle theme-gradient:bg-neutral-12/1',
|
||||
},
|
||||
warning: {
|
||||
icon: 'circle-exclamation',
|
||||
iconColor: ['text-amber-500', 'dark:text-orange-400'], // Darker shades of orange-* mismatch with lighter shades, so in light mode we use amber text on top of orange bg.
|
||||
bodyColor: [
|
||||
'text-orange-950',
|
||||
'dark:text-orange-50',
|
||||
'[&_a]:text-orange-800',
|
||||
'[&_a:hover]:text-orange-900',
|
||||
'dark:[&_a]:text-orange-400',
|
||||
'dark:[&_a:hover]:text-orange-300',
|
||||
'[&_.can-override-bg]:bg-orange-500/3',
|
||||
'[&_.can-override-text]:text-orange-800',
|
||||
'dark:[&_.can-override-text]:text-orange-400',
|
||||
'decoration-orange-800/6',
|
||||
'dark:decoration-orange-400/6',
|
||||
iconColor: 'text-warning-subtle contrast-more:text-warning',
|
||||
header: 'bg-warning-active',
|
||||
body: [
|
||||
'text-neutral-strong',
|
||||
'[&_a]:text-warning',
|
||||
'[&_a:hover]:text-warning-strong',
|
||||
'[&_.can-override-bg]:bg-warning-active',
|
||||
'[&_.can-override-text]:text-warning-strong',
|
||||
'decoration-warning/6',
|
||||
],
|
||||
style: ['bg-orange-500/2', 'border-orange-500/4'],
|
||||
container: 'bg-warning border-warning',
|
||||
containerWithHeader: 'border-warning-solid bg-warning-subtle',
|
||||
},
|
||||
danger: {
|
||||
icon: 'triangle-exclamation',
|
||||
iconColor: ['text-red-500', 'dark:text-red-400'],
|
||||
bodyColor: [
|
||||
'text-red-950',
|
||||
'dark:text-red-50',
|
||||
'[&_a]:text-red-800',
|
||||
'[&_a:hover]:text-red-900',
|
||||
'dark:[&_a]:text-red-400',
|
||||
'dark:[&_a:hover]:text-red-300',
|
||||
'[&_.can-override-bg]:bg-red-500/3',
|
||||
'[&_.can-override-text]:text-red-400',
|
||||
'decoration-red-800/6',
|
||||
'dark:decoration-red-400/6',
|
||||
iconColor: 'text-danger-subtle contrast-more:text-danger',
|
||||
header: 'bg-danger-active',
|
||||
body: [
|
||||
'text-neutral-strong',
|
||||
'[&_a]:text-danger',
|
||||
'[&_a:hover]:text-danger-strong',
|
||||
'[&_.can-override-bg]:bg-danger-active',
|
||||
'[&_.can-override-text]:text-danger-strong',
|
||||
'decoration-danger/6',
|
||||
],
|
||||
style: ['bg-red-500/2', 'border-red-500/4'],
|
||||
container: 'bg-danger border-danger',
|
||||
containerWithHeader: 'border-danger-solid bg-danger-subtle',
|
||||
},
|
||||
success: {
|
||||
icon: 'circle-check',
|
||||
iconColor: ['text-green-500', 'dark:text-green-400'],
|
||||
bodyColor: [
|
||||
'text-green-950',
|
||||
'dark:text-green-50',
|
||||
'[&_a]:text-green-800',
|
||||
'[&_a:hover]:text-green-900',
|
||||
'dark:[&_a]:text-green-400',
|
||||
'dark:[&_a:hover]:text-green-300',
|
||||
'[&_.can-override-bg]:bg-green-500/3',
|
||||
'[&_.can-override-text]:text-green-800',
|
||||
'dark:[&_.can-override-text]:text-green-400',
|
||||
'decoration-green-800/6',
|
||||
'dark:decoration-green-400/6',
|
||||
iconColor: 'text-success-subtle contrast-more:text-success',
|
||||
header: 'bg-success-active',
|
||||
body: [
|
||||
'text-neutral-strong',
|
||||
'[&_a]:text-success',
|
||||
'[&_a:hover]:text-success-strong',
|
||||
'[&_.can-override-bg]:bg-success-active',
|
||||
'[&_.can-override-text]:text-success-strong',
|
||||
'decoration-success/6',
|
||||
],
|
||||
style: ['bg-green-500/2', 'border-green-500/4'],
|
||||
container: 'bg-success border-success',
|
||||
containerWithHeader: 'border-success-solid bg-success-subtle',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { DocumentBlockOpenAPI } from '@gitbook/api';
|
||||
import { DocumentBlockOpenAPI, JSONDocument } from '@gitbook/api';
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { OpenAPIOperation } from '@gitbook/react-openapi';
|
||||
import React from 'react';
|
||||
|
||||
import { LoadingPane } from '@/components/primitives';
|
||||
import { fetchOpenAPIBlock } from '@/lib/openapi';
|
||||
import { resolveOpenAPIBlock } from '@/lib/openapi/fetch';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from '../Block';
|
||||
import { PlainCodeBlock } from '../CodeBlock';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
import { Heading } from '../Heading';
|
||||
|
||||
import './style.css';
|
||||
import './scalar.css';
|
||||
@@ -27,13 +28,17 @@ export async function OpenAPI(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
|
||||
async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
const { block, context } = props;
|
||||
const { data, specUrl, error } = await fetchOpenAPIBlock(block, context.resolveContentRef);
|
||||
|
||||
const { data, specUrl, error } = await resolveOpenAPIBlock({
|
||||
block,
|
||||
context: { resolveContentRef: context.resolveContentRef },
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={tcls('hidden')}>
|
||||
<div className="hidden">
|
||||
<p>
|
||||
Error with {error.rootURL}: {error.message}
|
||||
Error with {specUrl}: {error.message}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -53,7 +58,40 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
chevronRight: <Icon icon="chevron-right" />,
|
||||
plus: <Icon icon="plus" />,
|
||||
},
|
||||
CodeBlock: PlainCodeBlock,
|
||||
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
||||
renderDocument: (documentProps) => (
|
||||
<DocumentView
|
||||
document={documentProps.document as JSONDocument}
|
||||
context={props.context}
|
||||
style="space-y-6"
|
||||
blockStyle="max-w-full"
|
||||
/>
|
||||
),
|
||||
renderHeading: (headingProps) => (
|
||||
<Heading
|
||||
document={props.document}
|
||||
ancestorBlocks={props.ancestorBlocks}
|
||||
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||
context={props.context}
|
||||
style={headingProps.deprecated ? 'line-through' : undefined}
|
||||
block={{
|
||||
object: 'block',
|
||||
key: `${block.key}-heading`,
|
||||
meta: block.meta,
|
||||
data: {},
|
||||
type: 'heading-2',
|
||||
nodes: [
|
||||
{
|
||||
key: `${block.key}-heading-text`,
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{ text: headingProps.title, object: 'leaf', marks: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
),
|
||||
defaultInteractiveOpened: context.mode === 'print',
|
||||
id: block.meta?.id,
|
||||
blockKey: block.key,
|
||||
@@ -62,32 +100,3 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockOpenAPI>) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIFallback() {
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
aria-busy
|
||||
className={'openapi-block ' + tcls('flex', 'flex-1', 'flex-col', 'gap-3')}
|
||||
>
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[47px]', '[max-width:calc(48rem-1px)]']}
|
||||
/>
|
||||
<LoadingPane
|
||||
tile={12}
|
||||
style={['rounded-md', 'h-[35px]', '[max-width:calc(48rem-1px)]']}
|
||||
/>
|
||||
<div className={tcls('flex', 'gap-[25px]')}>
|
||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[2.5/1]', 'w-full']} />
|
||||
</div>
|
||||
<div className={tcls('flex', 'flex-1', 'flex-col', 'gap-3')}>
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
||||
<LoadingPane tile={24} style={['rounded-md', 'aspect-[4/1]', 'w-full']} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* Layout Components */
|
||||
.openapi-operation {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-height: 600px;
|
||||
@apply flex-1 flex flex-col gap-4 mb-14;
|
||||
}
|
||||
|
||||
@@ -18,20 +16,12 @@
|
||||
@apply flex flex-col items-start justify-start gap-2;
|
||||
}
|
||||
|
||||
.openapi-summary-title {
|
||||
@apply font-semibold text-xl;
|
||||
}
|
||||
|
||||
.openapi-summary-title[data-deprecated='true'] {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
.openapi-deprecated {
|
||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded-full text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||
@apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
|
||||
}
|
||||
|
||||
.openapi-deprecated-sunset-date {
|
||||
@apply font-semibold font-mono;
|
||||
@apply font-semibold font-mono truncate;
|
||||
}
|
||||
|
||||
.openapi-description.openapi-markdown {
|
||||
@@ -191,7 +181,7 @@
|
||||
.openapi-schema-name {
|
||||
/* To make double click on the property name select only the name,
|
||||
we disable selection on the parent and re-enable it on the children. */
|
||||
@apply select-none flex gap-2.5 items-baseline text-sm;
|
||||
@apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap;
|
||||
}
|
||||
|
||||
.openapi-schema-name .openapi-deprecated {
|
||||
@@ -463,7 +453,7 @@
|
||||
}
|
||||
|
||||
.openapi-section-header-content {
|
||||
@apply flex-1 text-base gap-1.5 flex w-full font-medium text-tint-strong;
|
||||
@apply flex-1 overflow-hidden text-base gap-1.5 flex w-full font-medium text-tint-strong;
|
||||
}
|
||||
|
||||
.openapi-section-header-controls {
|
||||
@@ -500,11 +490,13 @@
|
||||
|
||||
/* Tabs */
|
||||
.openapi-tabs-list {
|
||||
@apply flex flex-row gap-3 py-1.5 px-2.5 border-b border-tint-subtle w-full;
|
||||
@apply flex flex-row gap-1.5 py-1.5 px-2.5 border-b border-tint-subtle w-full overflow-x-scroll;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.openapi-tabs-tab {
|
||||
@apply hover:bg-primary-hover font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded;
|
||||
@apply hover:bg-primary-hover whitespace-nowrap font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded;
|
||||
}
|
||||
|
||||
.openapi-tabs-tab[aria-selected='true'] {
|
||||
@@ -579,11 +571,15 @@
|
||||
|
||||
/* Disclosure */
|
||||
.openapi-disclosure-trigger {
|
||||
@apply transition-all duration-300 hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
|
||||
@apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
|
||||
}
|
||||
|
||||
.openapi-disclosure-trigger span {
|
||||
@apply truncate;
|
||||
}
|
||||
|
||||
.openapi-disclosure svg {
|
||||
@apply size-3 transition-transform duration-300;
|
||||
@apply size-3 shrink-0 transition-transform duration-300;
|
||||
}
|
||||
|
||||
.openapi-disclosure-trigger[aria-expanded='true'] svg {
|
||||
|
||||
@@ -15,31 +15,29 @@ import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
export function Text(props: { text: DocumentText }) {
|
||||
const { text } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort(
|
||||
(a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0),
|
||||
)
|
||||
.reduce<React.ReactNode>((children, mark, index) => {
|
||||
const Mark = MARK_STYLES[mark.type];
|
||||
return text.leaves.map((leaf, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{leaf.marks
|
||||
// Sort to have code marks at the end, so that they don't interfere with other marks
|
||||
.sort((a, b) => (a.type === 'code' ? 1 : 0) - (b.type === 'code' ? 1 : 0))
|
||||
.reduce<React.ReactNode>((children, mark, index) => {
|
||||
const Mark = MARK_STYLES[mark.type];
|
||||
|
||||
if (!Mark) {
|
||||
return children;
|
||||
}
|
||||
if (!Mark) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return <Mark mark={mark}>{children}</Mark>;
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Mark key="mark" mark={mark}>
|
||||
{children}
|
||||
</Mark>
|
||||
);
|
||||
}, leaf.text)}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const MARK_STYLES = {
|
||||
|
||||
@@ -9,18 +9,22 @@ import {
|
||||
type CustomizationTint,
|
||||
type SiteCustomizationSettings,
|
||||
} from '@gitbook/api';
|
||||
import { IconsProvider, IconStyle } from '@gitbook/icons';
|
||||
|
||||
import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getStaticFileURL } from '@/lib/assets';
|
||||
import {
|
||||
colorContrast,
|
||||
colorScale,
|
||||
type ColorScaleOptions,
|
||||
DEFAULT_TINT_COLOR,
|
||||
DEFAULT_HINT_INFO_COLOR,
|
||||
DEFAULT_HINT_SUCCESS_COLOR,
|
||||
DEFAULT_HINT_DANGER_COLOR,
|
||||
DEFAULT_HINT_WARNING_COLOR,
|
||||
hexToRgb,
|
||||
} from '@/lib/colors';
|
||||
} from '@gitbook/colors';
|
||||
import { IconsProvider, IconStyle } from '@gitbook/icons';
|
||||
|
||||
import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { getStaticFileURL } from '@/lib/assets';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { ClientContexts } from './ClientContexts';
|
||||
@@ -42,6 +46,7 @@ export async function CustomizationRootLayout(props: {
|
||||
const tintColor = getTintColor(customization);
|
||||
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
|
||||
const sidebarStyles = getSidebarStyles(customization);
|
||||
const { infoColor, successColor, warningColor, dangerColor } = getSemanticColors(customization);
|
||||
|
||||
return (
|
||||
<html
|
||||
@@ -85,6 +90,11 @@ export async function CustomizationRootLayout(props: {
|
||||
)
|
||||
};
|
||||
--header-link: ${hexToRgb(customization.header.linkColor?.light ?? colorContrast(tintColor?.light ?? customization.styling.primaryColor.light))};
|
||||
|
||||
${generateColorVariable('info', infoColor.light)}
|
||||
${generateColorVariable('warning', warningColor.light)}
|
||||
${generateColorVariable('danger', dangerColor.light)}
|
||||
${generateColorVariable('success', successColor.light)}
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -94,6 +104,11 @@ export async function CustomizationRootLayout(props: {
|
||||
|
||||
--header-background: ${hexToRgb(customization.header.backgroundColor?.dark ?? tintColor?.dark ?? customization.styling.primaryColor.dark)};
|
||||
--header-link: ${hexToRgb(customization.header.linkColor?.dark ?? colorContrast(tintColor?.dark ?? customization.styling.primaryColor.dark))};
|
||||
|
||||
${generateColorVariable('info', infoColor.dark, { darkMode: true })}
|
||||
${generateColorVariable('warning', warningColor.dark, { darkMode: true })}
|
||||
${generateColorVariable('danger', dangerColor.dark, { darkMode: true })}
|
||||
${generateColorVariable('success', successColor.dark, { darkMode: true })}
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
@@ -198,6 +213,45 @@ function getSidebarStyles(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the semnatic color customization settings.
|
||||
* If it is a space customization, it will return the default styles.
|
||||
*/
|
||||
function getSemanticColors(
|
||||
customization: CustomizationSettings | SiteCustomizationSettings,
|
||||
): Pick<
|
||||
SiteCustomizationSettings['styling'],
|
||||
'infoColor' | 'successColor' | 'warningColor' | 'dangerColor'
|
||||
> {
|
||||
if ('infoColor' in customization.styling) {
|
||||
return {
|
||||
infoColor: customization.styling.infoColor,
|
||||
successColor: customization.styling.successColor,
|
||||
warningColor: customization.styling.warningColor,
|
||||
dangerColor: customization.styling.dangerColor,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
infoColor: {
|
||||
light: DEFAULT_HINT_INFO_COLOR,
|
||||
dark: DEFAULT_HINT_INFO_COLOR,
|
||||
},
|
||||
successColor: {
|
||||
light: DEFAULT_HINT_SUCCESS_COLOR,
|
||||
dark: DEFAULT_HINT_SUCCESS_COLOR,
|
||||
},
|
||||
warningColor: {
|
||||
light: DEFAULT_HINT_WARNING_COLOR,
|
||||
dark: DEFAULT_HINT_WARNING_COLOR,
|
||||
},
|
||||
dangerColor: {
|
||||
light: DEFAULT_HINT_DANGER_COLOR,
|
||||
dark: DEFAULT_HINT_DANGER_COLOR,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type ColorInput = string;
|
||||
function generateColorVariable(
|
||||
name: string,
|
||||
|
||||
@@ -47,6 +47,13 @@ export const Link = React.forwardRef(function Link(
|
||||
});
|
||||
}
|
||||
|
||||
// When the page is embedded in an iframe, for security reasons other urls cannot be opened.
|
||||
// In this case, we open the link in a new tab.
|
||||
if (isExternal && window.self !== window.top) {
|
||||
event.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
|
||||
domProps.onClick?.(event);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`parseMarkdown should parse a simple table 1`] = `
|
||||
"<h2>Table</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th align="left">b</th>
|
||||
<th align="right">c</th>
|
||||
<th align="center">d</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>"
|
||||
`;
|
||||
|
||||
exports[`parseMarkdown should parse a complex table 1`] = `
|
||||
"<p>Returns information for all non-fungible tokens for an account.</p>
|
||||
<h2>Ordering</h2>
|
||||
<p>When considering NFTs, their order is governed by a combination of their numerical <strong>token.Id</strong> and <strong>serialnumber</strong> values, with <strong>token.id</strong> being the parent column.
|
||||
A serialnumbers value governs its order within the given token.id</p>
|
||||
<p>In that regard, if a user acquired a set of NFTs in the order (2-2, 2-4 1-5, 1-1, 1-3, 3-3, 3-4), the following layouts illustrate the ordering expectations for ownership listing</p>
|
||||
<ol>
|
||||
<li><strong>All NFTs in ASC order</strong>: 1-1, 1-3, 1-5, 2-2, 2-4, 3-3, 3-4</li>
|
||||
<li><strong>All NFTs in DESC order</strong>: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3, 1-1</li>
|
||||
<li><strong>NFTs above 1-1 in ASC order</strong>: 1-3, 1-5, 2-2, 2-4, 3-3, 3-4</li>
|
||||
<li><strong>NFTs below 3-3 in ASC order</strong>: 1-1, 1-3, 1-5, 2-2, 2-4</li>
|
||||
<li><strong>NFTs between 1-3 and 3-3 inclusive in DESC order</strong>: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3</li>
|
||||
</ol>
|
||||
<p>Note: The default order for this API is currently DESC</p>
|
||||
<h2>Filtering</h2>
|
||||
<p>When filtering there are some restrictions enforced to ensure correctness and scalability.</p>
|
||||
<p><strong>The table below defines the restrictions and support for the NFT ownership endpoint</strong></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Query Param</th>
|
||||
<th>Comparison Operator</th>
|
||||
<th>Support</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>token.id</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=lte:X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only.</td>
|
||||
<td>?token.id=gte:X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serialnumber</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of a <strong>token.id</strong> query</td>
|
||||
<td>?serialnumber=Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of an <strong>lte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
|
||||
<td>?token.id=lte:X&serialnumber=lt:Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td>Single occurrence only. Requires the presence of an <strong>gte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
|
||||
<td>?token.id=gte:X&serialnumber=gt:Y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>spender.id</td>
|
||||
<td>eq</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=Z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ne</td>
|
||||
<td>N</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>lt(e)</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=lt:Z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gt(e)</td>
|
||||
<td>Y</td>
|
||||
<td></td>
|
||||
<td>?spender.id=gt:Z</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Note: When searching across a range for individual NFTs a <strong>serialnumber</strong> with an additional <strong>token.id</strong> query filter must be provided.
|
||||
Both filters must be a single occurrence of <strong>gt(e)</strong> or <strong>lt(e)</strong> which provide a lower and or upper boundary for search.</p>"
|
||||
`;
|
||||
@@ -208,6 +208,42 @@ export const getUserById = cache({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the latest version of an OpenAPI spec by its slug.
|
||||
*/
|
||||
export const getLatestOpenAPISpecVersionContent = cache({
|
||||
name: 'api.getLatestOpenApiSpecVersionContent',
|
||||
tag: (organization, openAPISpec) =>
|
||||
getAPICacheTag({
|
||||
tag: 'openapi',
|
||||
organization,
|
||||
openAPISpec,
|
||||
}),
|
||||
get: async (organizationId: string, slug: string, options: CacheFunctionOptions) => {
|
||||
try {
|
||||
const apiCtx = await api();
|
||||
const response = await apiCtx.client.orgs.getLatestOpenApiSpecVersionContent(
|
||||
organizationId,
|
||||
slug,
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
return cacheResponse(response, { revalidateBefore: 60 * 60 });
|
||||
} catch (error) {
|
||||
if (checkHasErrorCode(error, 404)) {
|
||||
return {
|
||||
revalidateBefore: 5,
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve a URL to the content to render.
|
||||
*/
|
||||
@@ -1066,55 +1102,6 @@ export async function getSpaceContentData(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Search content in a space.
|
||||
*/
|
||||
export const searchSpaceContent = cache({
|
||||
name: 'api.searchSpaceContent',
|
||||
tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
|
||||
getKeySuffix: getAPIContextId,
|
||||
get: async (
|
||||
spaceId: string,
|
||||
/** The revision ID is used as a cache bust key, to avoid revalidating lot of cache entries by tags */
|
||||
revisionId: string,
|
||||
query: string,
|
||||
options: CacheFunctionOptions,
|
||||
) => {
|
||||
const apiCtx = await api();
|
||||
const response = await apiCtx.client.spaces.searchSpaceContent(
|
||||
spaceId,
|
||||
{ query },
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
return cacheResponse(response);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Search content accross all spaces in a parent (site or collection).
|
||||
*/
|
||||
export const searchParentContent = cache({
|
||||
name: 'api.searchParentContent',
|
||||
tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
|
||||
getKeySuffix: getAPIContextId,
|
||||
get: async (parentId: string, query: string, options: CacheFunctionOptions) => {
|
||||
const apiCtx = await api();
|
||||
const response = await apiCtx.client.search.searchContent(
|
||||
{ query },
|
||||
{
|
||||
...noCacheFetchOptions,
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
return cacheResponse(response, {
|
||||
ttl: 60 * 60,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Search content in a Site or specific SiteSpaces.
|
||||
*/
|
||||
@@ -1275,6 +1262,12 @@ export function getAPICacheTag(
|
||||
| {
|
||||
tag: 'site';
|
||||
site: string;
|
||||
}
|
||||
// All data related to an OpenAPI spec
|
||||
| {
|
||||
tag: 'openapi';
|
||||
organization: string;
|
||||
openAPISpec: string;
|
||||
},
|
||||
): string {
|
||||
switch (spec.tag) {
|
||||
@@ -1298,6 +1291,8 @@ export function getAPICacheTag(
|
||||
return `site:${spec.site}`;
|
||||
case 'integration':
|
||||
return `integration:${spec.integration}`;
|
||||
case 'openapi':
|
||||
return `organization:${spec.organization}:openapi:${spec.openAPISpec}`;
|
||||
default:
|
||||
assertNever(spec);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { JSONDocument, ContentRef } from '@gitbook/api';
|
||||
|
||||
import { getNodeText } from './document';
|
||||
import { fetchOpenAPIBlock } from './openapi';
|
||||
import { resolveOpenAPIBlock } from './openapi/fetch';
|
||||
import { ResolvedContentRef } from './references';
|
||||
|
||||
export interface DocumentSection {
|
||||
@@ -38,7 +38,10 @@ export async function getDocumentSections(
|
||||
}
|
||||
|
||||
if (block.type === 'swagger' && block.meta?.id) {
|
||||
const { data: operation } = await fetchOpenAPIBlock(block, resolveContentRef);
|
||||
const { data: operation } = await resolveOpenAPIBlock({
|
||||
block,
|
||||
context: { resolveContentRef },
|
||||
});
|
||||
if (operation) {
|
||||
sections.push({
|
||||
id: block.meta.id,
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('parseMarkdown', () => {
|
||||
| a | b | c | d |
|
||||
| - | :- | -: | :-: |`);
|
||||
|
||||
expect(result).toContain('<table>');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should parse a complex table', async () => {
|
||||
@@ -52,6 +52,6 @@ When filtering there are some restrictions enforced to ensure correctness and sc
|
||||
Note: When searching across a range for individual NFTs a **serialnumber** with an additional **token.id** query filter must be provided.
|
||||
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.`);
|
||||
|
||||
expect(result).toContain('<table>');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import { unified } from 'unified';
|
||||
import { micromark } from 'micromark';
|
||||
import { gfmHtml, gfm } from 'micromark-extension-gfm';
|
||||
|
||||
/**
|
||||
* Parse markdown and output HTML.
|
||||
*/
|
||||
export async function parseMarkdown(markdown: string): Promise<string> {
|
||||
const promise = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkRehype)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeStringify)
|
||||
.process(markdown)
|
||||
.then((file) => file.toString());
|
||||
|
||||
return promise;
|
||||
export function parseMarkdown(input: string): string {
|
||||
return micromark(input, {
|
||||
extensions: [gfm()],
|
||||
htmlExtensions: [gfmHtml()],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { parseOpenAPI } from '@gitbook/openapi-parser';
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { enrichFilesystem } from './enrich';
|
||||
|
||||
const spec = await Bun.file(new URL('./fixtures/multiline-spec.yaml', import.meta.url)).text();
|
||||
|
||||
describe('#enrichFilesystem', () => {
|
||||
it('supports multiline descriptions', async () => {
|
||||
const filesystem = await parseOpenAPI({
|
||||
value: spec,
|
||||
rootURL: null,
|
||||
});
|
||||
const enriched = await enrichFilesystem(filesystem);
|
||||
expect(enriched[0].specification.paths['/pet'].put['x-gitbook-description-html']).toBe(
|
||||
'<p>Social platform</p>',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { traverse, Filesystem } from '@gitbook/openapi-parser';
|
||||
|
||||
import { parseMarkdown } from '../markdown';
|
||||
|
||||
/**
|
||||
* Enrich a filesystem with HTML descriptions.
|
||||
*/
|
||||
export async function enrichFilesystem(filesystem: Filesystem) {
|
||||
const parseMarkdownWithCache = createMarkdownParser();
|
||||
return traverse(filesystem, async (node, path) => {
|
||||
if (
|
||||
path !== undefined &&
|
||||
'description' in node &&
|
||||
typeof node.description === 'string' &&
|
||||
node.description
|
||||
) {
|
||||
const key = path[path.length - 1];
|
||||
// Avoid parsing descriptions in examples.
|
||||
if (key !== 'example') {
|
||||
const description = node.description.trim();
|
||||
if (description) {
|
||||
node['x-gitbook-description-html'] = parseMarkdownWithCache(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a markdown parser that caches the results of parsing.
|
||||
*/
|
||||
const createMarkdownParser = () => (input: string) => {
|
||||
const cache: Record<string, string> = {};
|
||||
const existing = cache[input];
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const result = parseMarkdown(input);
|
||||
cache[input] = result;
|
||||
return result;
|
||||
};
|
||||
@@ -1,29 +1,54 @@
|
||||
import { ContentRef, DocumentBlockOpenAPI } from '@gitbook/api';
|
||||
import { parseOpenAPI, OpenAPIParseError, traverse } from '@gitbook/openapi-parser';
|
||||
import { parseOpenAPI, OpenAPIParseError } from '@gitbook/openapi-parser';
|
||||
import { type OpenAPIOperationData, resolveOpenAPIOperation } from '@gitbook/react-openapi';
|
||||
|
||||
import { cache, noCacheFetchOptions, CacheFunctionOptions } from '@/lib/cache';
|
||||
|
||||
import { parseMarkdown } from './markdown';
|
||||
import { ResolvedContentRef } from './references';
|
||||
import { enrichFilesystem } from './enrich';
|
||||
import { ResolvedContentRef } from '../references';
|
||||
|
||||
const weakmap = new WeakMap<DocumentBlockOpenAPI, ResolveOpenAPIBlockResult>();
|
||||
|
||||
/**
|
||||
* Fetch an OpenAPI specification for an operation.
|
||||
* Cache the result of resolving an OpenAPI block.
|
||||
* It is important because the resolve is called in sections and in the block itself.
|
||||
*/
|
||||
export async function fetchOpenAPIBlock(
|
||||
block: DocumentBlockOpenAPI,
|
||||
resolveContentRef: (ref: ContentRef) => Promise<ResolvedContentRef | null>,
|
||||
): Promise<
|
||||
| { data: OpenAPIOperationData | null; specUrl: string | null; error?: undefined }
|
||||
export function resolveOpenAPIBlock(args: ResolveOpenAPIBlockArgs): ResolveOpenAPIBlockResult {
|
||||
if (weakmap.has(args.block)) {
|
||||
return weakmap.get(args.block)!;
|
||||
}
|
||||
|
||||
const result = baseResolveOpenAPIBlock(args);
|
||||
weakmap.set(args.block, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
type ResolveOpenAPIBlockArgs = {
|
||||
block: DocumentBlockOpenAPI;
|
||||
context: { resolveContentRef: (ref: ContentRef) => Promise<ResolvedContentRef | null> };
|
||||
};
|
||||
type ResolveOpenAPIBlockResult = Promise<
|
||||
| { error?: undefined; data: OpenAPIOperationData | null; specUrl: string | null }
|
||||
| { error: OpenAPIParseError; data?: undefined; specUrl?: undefined }
|
||||
> {
|
||||
const resolved = block.data.ref ? await resolveContentRef(block.data.ref) : null;
|
||||
if (!resolved || !block.data.path || !block.data.method) {
|
||||
>;
|
||||
/**
|
||||
* Resolve OpenAPI block.
|
||||
*/
|
||||
async function baseResolveOpenAPIBlock(args: ResolveOpenAPIBlockArgs): ResolveOpenAPIBlockResult {
|
||||
const { context, block } = args;
|
||||
if (!block.data.path || !block.data.method) {
|
||||
return { data: null, specUrl: null };
|
||||
}
|
||||
|
||||
const resolved = block.data.ref ? await context.resolveContentRef(block.data.ref) : null;
|
||||
|
||||
if (!resolved) {
|
||||
return { data: null, specUrl: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const filesystem = await fetchFilesystem(resolved.href);
|
||||
const filesystem = resolved.openAPIFilesystem ?? (await fetchFilesystem(resolved.href));
|
||||
|
||||
const data = await resolveOpenAPIOperation(filesystem, {
|
||||
path: block.data.path,
|
||||
method: block.data.method,
|
||||
@@ -40,7 +65,7 @@ export async function fetchOpenAPIBlock(
|
||||
}
|
||||
|
||||
const fetchFilesystem = cache({
|
||||
name: 'openapi.fetch.v5',
|
||||
name: 'openapi.fetch.v6',
|
||||
get: async (url: string, options: CacheFunctionOptions) => {
|
||||
// Wrap the raw string to prevent invalid URLs from being passed to fetch.
|
||||
// This can happen if the URL has whitespace, which is currently handled differently by Cloudflare's implementation of fetch:
|
||||
@@ -58,38 +83,13 @@ const fetchFilesystem = cache({
|
||||
|
||||
const text = await response.text();
|
||||
const filesystem = await parseOpenAPI({ value: text, rootURL: url });
|
||||
const parseMarkdownWithCache = createMarkdownParser();
|
||||
const transformedFs = await traverse(filesystem, async (node, path) => {
|
||||
if ('description' in node && typeof node.description === 'string' && node.description) {
|
||||
const lastKey = path && path[path.length - 1];
|
||||
// Avoid parsing descriptions in examples.
|
||||
if (lastKey === 'example') {
|
||||
return node;
|
||||
}
|
||||
node['x-description-html'] = await parseMarkdownWithCache(node.description);
|
||||
}
|
||||
return node;
|
||||
});
|
||||
const richFilesystem = await enrichFilesystem(filesystem);
|
||||
return {
|
||||
// Cache for 4 hours
|
||||
ttl: 24 * 60 * 60,
|
||||
// Revalidate every 2 hours
|
||||
revalidateBefore: 22 * 60 * 60,
|
||||
data: transformedFs,
|
||||
data: richFilesystem,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a markdown parser that caches the results of parsing.
|
||||
*/
|
||||
const createMarkdownParser = () => async (input: string) => {
|
||||
const cache = new Map<string, Promise<string>>();
|
||||
if (cache.has(input)) {
|
||||
return cache.get(input) as Promise<string>;
|
||||
}
|
||||
|
||||
const promise = parseMarkdown(input);
|
||||
cache.set(input, promise);
|
||||
return promise;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
openapi: 3.0.2
|
||||
info:
|
||||
title: OpenAPI spec
|
||||
version: 0.0.0
|
||||
servers:
|
||||
- url: '/api/v3'
|
||||
paths:
|
||||
'/pet':
|
||||
put:
|
||||
summary: Update an existing pet
|
||||
description: |
|
||||
Social platform
|
||||
operationId: updatePet
|
||||
requestBody:
|
||||
description: Update an existent pet in the store
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
example: 10
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
example: 10
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SiteSpace,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import type { Filesystem } from '@gitbook/openapi-parser';
|
||||
import assertNever from 'assert-never';
|
||||
import React from 'react';
|
||||
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
SpaceContentPointer,
|
||||
getCollection,
|
||||
getDocument,
|
||||
getLatestOpenAPISpecVersionContent,
|
||||
getPageDocument,
|
||||
getPublishedContentSite,
|
||||
getReusableContent,
|
||||
@@ -50,6 +52,8 @@ export interface ResolvedContentRef {
|
||||
file?: RevisionFile;
|
||||
/** Resolved reusable content, if the ref points to reusable content on a revision. */
|
||||
reusableContent?: RevisionReusableContent;
|
||||
/** Resolve OpenAPI spec filesystem. */
|
||||
openAPIFilesystem?: Filesystem;
|
||||
}
|
||||
|
||||
export interface ContentRefContext extends PageHrefContext {
|
||||
@@ -272,6 +276,27 @@ export async function resolveContentRef(
|
||||
};
|
||||
}
|
||||
|
||||
case 'openapi': {
|
||||
if (!siteContext) {
|
||||
return null;
|
||||
}
|
||||
const { organizationId } = siteContext;
|
||||
const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent(
|
||||
organizationId,
|
||||
contentRef.spec,
|
||||
);
|
||||
|
||||
if (!openAPISpecVersionContent) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
href: openAPISpecVersionContent.url,
|
||||
text: contentRef.spec,
|
||||
active: false,
|
||||
openAPIFilesystem: openAPISpecVersionContent.filesystem as Filesystem,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
assertNever(contentRef);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import typography from '@tailwindcss/typography';
|
||||
import type { Config } from 'tailwindcss';
|
||||
import plugin from 'tailwindcss/plugin';
|
||||
|
||||
import { ColorCategory, hexToRgb, scale, shadesOfColor } from './src/lib/colors';
|
||||
import { ColorCategory, hexToRgb, scale, shadesOfColor } from '@gitbook/colors';
|
||||
|
||||
export const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
export const opacities = [0, 4, 8, 12, 16, 24, 40, 64, 72, 88, 96, 100];
|
||||
|
||||
export const semanticColors = ['info', 'warning', 'danger', 'success'];
|
||||
|
||||
/**
|
||||
* Generate a Tailwind color shades from a variable.
|
||||
*/
|
||||
@@ -100,6 +102,18 @@ const config: Config = {
|
||||
|
||||
'header-background': 'rgb(var(--header-background))',
|
||||
'header-link': 'rgb(var(--header-link))',
|
||||
|
||||
// Add each semantic color
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [color, generateVarShades(color)]),
|
||||
),
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
`contrast-${color}`,
|
||||
generateVarShades(`contrast-${color}`),
|
||||
]),
|
||||
),
|
||||
|
||||
yellow: generateShades('#f4e28d'),
|
||||
teal: generateShades('#3f89a1'),
|
||||
pomegranate: generateShades('#f25b3a'),
|
||||
@@ -127,6 +141,17 @@ const config: Config = {
|
||||
ColorCategory.components,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.components,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
gradientColorStops: {
|
||||
primary: generateVarShades('primary', [
|
||||
@@ -144,26 +169,71 @@ const config: Config = {
|
||||
ColorCategory.components,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.components,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
borderColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.borders]),
|
||||
tint: generateVarShades('tint', [ColorCategory.borders]),
|
||||
neutral: generateVarShades('neutral', [ColorCategory.borders]),
|
||||
primary: generateVarShades('primary', [
|
||||
ColorCategory.borders,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
tint: generateVarShades('tint', [ColorCategory.borders, ColorCategory.accents]),
|
||||
neutral: generateVarShades('neutral', [
|
||||
ColorCategory.borders,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [ColorCategory.borders, ColorCategory.accents]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
ringColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.borders]),
|
||||
tint: generateVarShades('tint', [ColorCategory.borders]),
|
||||
neutral: generateVarShades('neutral', [ColorCategory.borders]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [ColorCategory.borders]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
outlineColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.borders]),
|
||||
tint: generateVarShades('tint', [ColorCategory.borders]),
|
||||
neutral: generateVarShades('neutral', [ColorCategory.borders]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [ColorCategory.borders]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
boxShadowColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.borders]),
|
||||
tint: generateVarShades('tint', [ColorCategory.borders]),
|
||||
neutral: generateVarShades('neutral', [ColorCategory.borders]),
|
||||
// Semantic colors
|
||||
...Object.fromEntries(
|
||||
semanticColors.map((color) => [
|
||||
color,
|
||||
generateVarShades(color, [ColorCategory.borders]),
|
||||
]),
|
||||
),
|
||||
},
|
||||
textColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.text]),
|
||||
@@ -181,6 +251,18 @@ const config: Config = {
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
...Object.fromEntries(
|
||||
semanticColors.flatMap((color) => [
|
||||
[color, generateVarShades(color, [ColorCategory.text])],
|
||||
[
|
||||
`contrast-${color}`,
|
||||
generateVarShades(`contrast-${color}`, [
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
],
|
||||
]),
|
||||
),
|
||||
},
|
||||
textDecorationColor: {
|
||||
primary: generateVarShades('primary', [ColorCategory.text]),
|
||||
@@ -198,6 +280,18 @@ const config: Config = {
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
...Object.fromEntries(
|
||||
semanticColors.flatMap((color) => [
|
||||
[color, generateVarShades(color, [ColorCategory.text])],
|
||||
[
|
||||
`contrast-${color}`,
|
||||
generateVarShades(`contrast-${color}`, [
|
||||
ColorCategory.backgrounds,
|
||||
ColorCategory.accents,
|
||||
]),
|
||||
],
|
||||
]),
|
||||
),
|
||||
},
|
||||
animation: {
|
||||
present: 'present .5s ease-out both',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @gitbook/openapi-parser
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6157583: Improve Markdown parsing
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -9,13 +9,11 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@scalar/openapi-parser": "^0.10.4",
|
||||
"@scalar/openapi-types": "^0.1.6",
|
||||
"swagger2openapi": "^7.0.8",
|
||||
"yaml": "1.10.2"
|
||||
"@scalar/openapi-types": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/strictest": "^2.0.5",
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface OpenAPICustomSpecProperties {
|
||||
/**
|
||||
* Description in HTML format.
|
||||
*/
|
||||
'x-description-html'?: string;
|
||||
'x-gitbook-description-html'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,12 @@ export interface OpenAPICustomOperationProperties {
|
||||
/**
|
||||
* Description in HTML format.
|
||||
*/
|
||||
'x-description-html'?: string;
|
||||
'x-gitbook-description-html'?: string;
|
||||
|
||||
/**
|
||||
* Description in Document format.
|
||||
*/
|
||||
'x-gitbook-description-document'?: object;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,6 @@ describe('#convertOpenAPIV2ToOpenAPIV3', () => {
|
||||
});
|
||||
// Ensure the structure returned is not recursive (not dereferenced).
|
||||
JSON.stringify(schema);
|
||||
expect(schema[0]!.specification.openapi).toBe('3.0.0');
|
||||
expect(schema[0]!.specification.openapi).toBe('3.1.1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import YAML from 'yaml';
|
||||
import swagger2openapi, { type ConvertOutputOptions } from 'swagger2openapi';
|
||||
|
||||
import { OpenAPIParseError } from './error';
|
||||
import { parseOpenAPIV3 } from './v3';
|
||||
import type { AnyApiDefinitionFormat } from '@scalar/openapi-parser';
|
||||
import type { Filesystem, OpenAPIV3xDocument } from './types';
|
||||
import { upgrade } from '@scalar/openapi-parser';
|
||||
|
||||
/**
|
||||
* Convert a Swagger 2.0 schema to an OpenAPI 3.0 schema.
|
||||
@@ -20,57 +18,22 @@ export async function convertOpenAPIV2ToOpenAPIV3(input: {
|
||||
rootURL: string | null;
|
||||
}): Promise<Filesystem<OpenAPIV3xDocument>> {
|
||||
const { value, rootURL } = input;
|
||||
// In this case we want the raw value to be able to convert it.
|
||||
const schema = typeof value === 'string' ? rawParseOpenAPI({ value, rootURL }) : value;
|
||||
try {
|
||||
// @ts-expect-error Types are incompatible between the two libraries
|
||||
const convertResult = (await swagger2openapi.convertObj(schema, {
|
||||
resolve: false,
|
||||
resolveInternal: false,
|
||||
laxDefaults: true,
|
||||
laxurls: true,
|
||||
lint: false,
|
||||
prevalidate: false,
|
||||
anchors: true,
|
||||
patch: true,
|
||||
})) as ConvertOutputOptions;
|
||||
|
||||
return parseOpenAPIV3({ rootURL, value: convertResult.openapi });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'S2OError') {
|
||||
throw new OpenAPIParseError('Failed to convert Swagger 2.0 to OpenAPI 3.0', {
|
||||
code: 'v2-conversion',
|
||||
rootURL,
|
||||
cause: error,
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the config file from a raw string.
|
||||
* Useful to get the raw object from a file.
|
||||
*/
|
||||
function rawParseOpenAPI(input: { value: string; rootURL: string | null }): unknown {
|
||||
const { value, rootURL } = input;
|
||||
|
||||
// Try with JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (jsonError) {
|
||||
const upgradeResult = (() => {
|
||||
try {
|
||||
// Try with YAML
|
||||
return YAML.parse(value);
|
||||
} catch (yamlError) {
|
||||
if (yamlError instanceof Error && yamlError.name.startsWith('YAML')) {
|
||||
throw new OpenAPIParseError('Failed to parse YAML: ' + yamlError.message, {
|
||||
code: 'yaml-parse',
|
||||
return upgrade(value);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new OpenAPIParseError('Failed to convert Swagger 2.0 to OpenAPI 3.1.1', {
|
||||
code: 'v2-conversion',
|
||||
rootURL,
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
throw yamlError;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return parseOpenAPIV3({ ...input, rootURL, value: upgradeResult.specification });
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,17 @@
|
||||
# @gitbook/react-openapi
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bb5c6a4: Support multiple response media types and examples
|
||||
- a3f1fea: Fix display of OpenAPI header description
|
||||
- 6157583: Improve Markdown parsing
|
||||
- 7419ee7: Show additional fields in OpenAPI block
|
||||
- 82cd9f2: Add support for anchor links in OpenAPI blocks
|
||||
- Updated dependencies [6157583]
|
||||
- @gitbook/openapi-parser@1.0.1
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
@@ -16,6 +16,7 @@
|
||||
"@scalar/oas-utils": "^0.2.101",
|
||||
"clsx": "^2.1.1",
|
||||
"flatted": "^3.2.9",
|
||||
"json-xml-parse": "^1.3.0",
|
||||
"react-aria-components": "^1.6.0",
|
||||
"react-aria": "^3.37.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
|
||||
@@ -57,9 +57,7 @@ export function OpenAPICodeSample(props: {
|
||||
data.path +
|
||||
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
||||
method: data.method,
|
||||
body: requestBodyContent
|
||||
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
|
||||
: undefined,
|
||||
body: requestBodyContent ? generateMediaTypeExample(requestBodyContent[1]) : undefined,
|
||||
headers: {
|
||||
...getSecurityHeaders(data.securities),
|
||||
...headersObject,
|
||||
@@ -74,7 +72,10 @@ export function OpenAPICodeSample(props: {
|
||||
const autoCodeSamples = codeSampleGenerators.map((generator) => ({
|
||||
key: `default-${generator.id}`,
|
||||
label: generator.label,
|
||||
body: <context.CodeBlock code={generator.generate(input)} syntax={generator.syntax} />,
|
||||
body: context.renderCodeBlock({
|
||||
code: generator.generate(input),
|
||||
syntax: generator.syntax,
|
||||
}),
|
||||
}));
|
||||
|
||||
// Use custom samples if defined
|
||||
@@ -97,7 +98,10 @@ export function OpenAPICodeSample(props: {
|
||||
.map((sample) => ({
|
||||
key: `redocly-${sample.lang}`,
|
||||
label: sample.label,
|
||||
body: <context.CodeBlock code={sample.source} syntax={sample.lang} />,
|
||||
body: context.renderCodeBlock({
|
||||
code: sample.source,
|
||||
syntax: sample.lang,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { OpenAPISpec } from './OpenAPISpec';
|
||||
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||
import { OpenAPIPath } from './OpenAPIPath';
|
||||
import { resolveDescription } from './utils';
|
||||
import { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
|
||||
/**
|
||||
* Display an interactive OpenAPI operation.
|
||||
@@ -25,14 +26,15 @@ export function OpenAPIOperation(props: {
|
||||
blockKey: context.blockKey,
|
||||
};
|
||||
|
||||
const description = resolveDescription(operation)?.trim();
|
||||
|
||||
return (
|
||||
<div className={clsx('openapi-operation', className)}>
|
||||
<div className="openapi-summary" id={context.id}>
|
||||
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
||||
{operation.summary}
|
||||
</h2>
|
||||
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
|
||||
{operation.summary
|
||||
? context.renderHeading({
|
||||
deprecated: operation.deprecated ?? false,
|
||||
title: operation.summary,
|
||||
})
|
||||
: null}
|
||||
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
||||
</div>
|
||||
<div className="openapi-columns">
|
||||
@@ -46,11 +48,7 @@ export function OpenAPIOperation(props: {
|
||||
{`.`}
|
||||
</div>
|
||||
) : null}
|
||||
{description ? (
|
||||
<div className="openapi-intro">
|
||||
<Markdown className="openapi-description" source={description} />
|
||||
</div>
|
||||
) : null}
|
||||
<OpenAPIOperationDescription operation={operation} context={context} />
|
||||
<OpenAPIPath data={data} context={context} />
|
||||
<OpenAPISpec data={data} context={clientContext} />
|
||||
</div>
|
||||
@@ -64,3 +62,30 @@ export function OpenAPIOperation(props: {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIOperationDescription(props: {
|
||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||
context: OpenAPIContextProps;
|
||||
}) {
|
||||
const { operation } = props;
|
||||
if (operation['x-gitbook-description-document']) {
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
{props.context.renderDocument({
|
||||
document: operation['x-gitbook-description-document'],
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const description = resolveDescription(operation);
|
||||
if (!description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="openapi-intro">
|
||||
<Markdown className="openapi-description" source={description} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||
import { resolveDescription } from './utils';
|
||||
import { parameterToProperty, resolveDescription } from './utils';
|
||||
import type { OpenAPIClientContext } from './types';
|
||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
|
||||
@@ -27,13 +27,11 @@ export function OpenAPIResponse(props: {
|
||||
return (
|
||||
<div className="openapi-response-body">
|
||||
{headers.length > 0 ? (
|
||||
<OpenAPIDisclosure context={context} label={'Headers'}>
|
||||
<OpenAPIDisclosure context={context} label="Headers">
|
||||
<OpenAPISchemaProperties
|
||||
properties={headers.map(([name, header]) => ({
|
||||
propertyName: name,
|
||||
schema: header.schema ?? {},
|
||||
required: header.required,
|
||||
}))}
|
||||
properties={headers.map(([name, header]) => {
|
||||
return parameterToProperty({ name, ...header });
|
||||
})}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
@@ -41,11 +39,7 @@ export function OpenAPIResponse(props: {
|
||||
<div className="openapi-responsebody">
|
||||
<OpenAPISchemaProperties
|
||||
id={`response-${context.blockKey}`}
|
||||
properties={[
|
||||
{
|
||||
schema: mediaType.schema ?? {},
|
||||
},
|
||||
]}
|
||||
properties={mediaType.schema ? [{ schema: mediaType.schema }] : []}
|
||||
context={context}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import { generateSchemaExample } from './generateSchemaExample';
|
||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||
import { InteractiveSection } from './InteractiveSection';
|
||||
import { json2xml } from './json2xml';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
|
||||
/**
|
||||
* Display an example of the response content.
|
||||
@@ -38,84 +39,51 @@ export function OpenAPIResponseExample(props: {
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
|
||||
const examples = responses
|
||||
.map(([key, value]) => {
|
||||
const responseObject = value;
|
||||
const mediaTypeObject = (() => {
|
||||
if (!responseObject.content) {
|
||||
return null;
|
||||
}
|
||||
const key = Object.keys(responseObject.content)[0];
|
||||
return (
|
||||
responseObject.content['application/json'] ??
|
||||
(key ? responseObject.content[key] : null)
|
||||
);
|
||||
})();
|
||||
const tabs = responses
|
||||
.map(([key, responseObject]) => {
|
||||
const description = resolveDescription(responseObject);
|
||||
|
||||
if (!mediaTypeObject) {
|
||||
if (checkIsReference(responseObject)) {
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description: resolveDescription(responseObject),
|
||||
body: <OpenAPIEmptyResponseExample />,
|
||||
description,
|
||||
body: (
|
||||
<OpenAPIExample
|
||||
example={getExampleFromReference(responseObject)}
|
||||
context={context}
|
||||
syntax="json"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const example = handleUnresolvedReference(
|
||||
(() => {
|
||||
const { examples, example } = mediaTypeObject;
|
||||
if (examples) {
|
||||
const key = Object.keys(examples)[0];
|
||||
if (key) {
|
||||
// @TODO handle multiple examples
|
||||
const firstExample = examples[key];
|
||||
if (firstExample) {
|
||||
return firstExample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (example) {
|
||||
return { value: example };
|
||||
}
|
||||
|
||||
const schema = mediaTypeObject.schema;
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value: generateSchemaExample(schema) };
|
||||
})(),
|
||||
);
|
||||
if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description,
|
||||
body: <OpenAPIEmptyResponseExample />,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
description: resolveDescription(responseObject),
|
||||
body: example?.value ? (
|
||||
<context.CodeBlock
|
||||
code={
|
||||
typeof example.value === 'string'
|
||||
? example.value
|
||||
: stringifyOpenAPI(example.value, null, 2)
|
||||
}
|
||||
syntax="json"
|
||||
/>
|
||||
) : (
|
||||
<OpenAPIEmptyResponseExample />
|
||||
),
|
||||
body: <OpenAPIResponse context={context} content={responseObject.content} />,
|
||||
};
|
||||
})
|
||||
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
||||
Boolean(val),
|
||||
);
|
||||
|
||||
if (examples.length === 0) {
|
||||
if (tabs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={examples}>
|
||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
||||
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
@@ -123,6 +91,212 @@ export function OpenAPIResponseExample(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIResponse(props: {
|
||||
context: OpenAPIContextProps;
|
||||
content: {
|
||||
[media: string]: OpenAPIV3.MediaTypeObject;
|
||||
};
|
||||
}) {
|
||||
const { context, content } = props;
|
||||
|
||||
const entries = Object.entries(content);
|
||||
const firstEntry = entries[0];
|
||||
|
||||
if (!firstEntry) {
|
||||
throw new Error('One media type is required');
|
||||
}
|
||||
|
||||
if (entries.length === 1) {
|
||||
const [mediaType, mediaTypeObject] = firstEntry;
|
||||
return (
|
||||
<OpenAPIResponseMediaType
|
||||
context={context}
|
||||
mediaType={mediaType}
|
||||
mediaTypeObject={mediaTypeObject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = entries.map((entry) => {
|
||||
const [mediaType, mediaTypeObject] = entry;
|
||||
return {
|
||||
key: mediaType,
|
||||
label: mediaType,
|
||||
body: (
|
||||
<OpenAPIResponseMediaType
|
||||
context={context}
|
||||
mediaType={mediaType}
|
||||
mediaTypeObject={mediaTypeObject}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}>
|
||||
<InteractiveSection
|
||||
header={<OpenAPITabsList />}
|
||||
className="openapi-response-media-types"
|
||||
>
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
</OpenAPITabs>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenAPIResponseMediaType(props: {
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
mediaType: string;
|
||||
context: OpenAPIContextProps;
|
||||
}) {
|
||||
const { mediaTypeObject, mediaType } = props;
|
||||
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
||||
const syntax = getSyntaxFromMediaType(mediaType);
|
||||
const firstExample = examples[0];
|
||||
|
||||
if (!firstExample) {
|
||||
return <OpenAPIEmptyResponseExample />;
|
||||
}
|
||||
|
||||
if (examples.length === 1) {
|
||||
return (
|
||||
<OpenAPIExample
|
||||
example={firstExample.example}
|
||||
context={props.context}
|
||||
syntax={syntax}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = examples.map((example) => {
|
||||
return {
|
||||
key: example.key,
|
||||
label: example.example.summary || example.key,
|
||||
body: (
|
||||
<OpenAPIExample
|
||||
example={firstExample.example}
|
||||
context={props.context}
|
||||
syntax={syntax}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
|
||||
<InteractiveSection
|
||||
header={<OpenAPITabsList />}
|
||||
className="openapi-response-media-type-examples"
|
||||
>
|
||||
<OpenAPITabsPanels />
|
||||
</InteractiveSection>
|
||||
</OpenAPITabs>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an example.
|
||||
*/
|
||||
function OpenAPIExample(props: {
|
||||
example: OpenAPIV3.ExampleObject;
|
||||
context: OpenAPIContextProps;
|
||||
syntax: string;
|
||||
}) {
|
||||
const { example, context, syntax } = props;
|
||||
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
||||
|
||||
if (code === null) {
|
||||
return <OpenAPIEmptyResponseExample />;
|
||||
}
|
||||
|
||||
return context.renderCodeBlock({ code, syntax });
|
||||
}
|
||||
|
||||
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
||||
const { example, xml } = args;
|
||||
|
||||
if (!example.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof example.value === 'string') {
|
||||
return example.value;
|
||||
}
|
||||
|
||||
if (xml) {
|
||||
return json2xml(example.value);
|
||||
}
|
||||
|
||||
return stringifyOpenAPI(example.value, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the syntax from a media type.
|
||||
*/
|
||||
function getSyntaxFromMediaType(mediaType: string): string {
|
||||
if (mediaType.includes('json')) {
|
||||
return 'json';
|
||||
}
|
||||
|
||||
if (mediaType === 'application/xml') {
|
||||
return 'xml';
|
||||
}
|
||||
|
||||
return 'text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples from a media type object.
|
||||
*/
|
||||
function getExamplesFromMediaTypeObject(args: {
|
||||
mediaType: string;
|
||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
||||
const { mediaTypeObject, mediaType } = args;
|
||||
if (mediaTypeObject.examples) {
|
||||
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
||||
return {
|
||||
key,
|
||||
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (mediaTypeObject.example) {
|
||||
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
||||
}
|
||||
|
||||
if (mediaTypeObject.schema) {
|
||||
if (mediaType === 'application/xml') {
|
||||
// @TODO normally we should use the name of the schema but we don't have it
|
||||
// fix it when we got the reference name
|
||||
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: {
|
||||
value: {
|
||||
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
||||
xml: mediaType === 'application/xml',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'default',
|
||||
example: { value: generateSchemaExample(mediaTypeObject.schema) },
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty response example.
|
||||
*/
|
||||
function OpenAPIEmptyResponseExample() {
|
||||
return (
|
||||
<pre className="openapi-response-example-empty">
|
||||
@@ -131,15 +305,9 @@ function OpenAPIEmptyResponseExample() {
|
||||
);
|
||||
}
|
||||
|
||||
function handleUnresolvedReference(
|
||||
input: OpenAPIV3.ExampleObject | null,
|
||||
): OpenAPIV3.ExampleObject | null {
|
||||
const isReference = checkIsReference(input?.value);
|
||||
|
||||
if (isReference) {
|
||||
// If we find a reference that wasn't resolved or needed to be resolved externally, render out the URL
|
||||
return { value: input.value.$ref };
|
||||
}
|
||||
|
||||
return input;
|
||||
/**
|
||||
* Generate an example from a reference object.
|
||||
*/
|
||||
function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
||||
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
||||
|
||||
export interface OpenAPISchemaPropertyEntry {
|
||||
propertyName?: string;
|
||||
required?: boolean;
|
||||
propertyName?: string | undefined;
|
||||
required?: boolean | undefined;
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
}
|
||||
|
||||
@@ -47,23 +47,6 @@ export function OpenAPISchemaProperty(
|
||||
? null
|
||||
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
||||
|
||||
if ((properties && !!properties.length) || schema.type === 'object') {
|
||||
return (
|
||||
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
||||
<OpenAPISchemaPresentation {...props} />
|
||||
{properties && properties.length > 0 ? (
|
||||
<OpenAPIDisclosure context={context}>
|
||||
<OpenAPISchemaProperties
|
||||
properties={properties}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
) : null}
|
||||
</InteractiveSection>
|
||||
);
|
||||
}
|
||||
|
||||
if (alternatives?.[0]?.length) {
|
||||
return (
|
||||
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
||||
@@ -76,6 +59,29 @@ export function OpenAPISchemaProperty(
|
||||
context={context}
|
||||
/>
|
||||
))}
|
||||
{parentCircularRef ? (
|
||||
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
||||
) : null}
|
||||
</InteractiveSection>
|
||||
);
|
||||
}
|
||||
|
||||
if ((properties && properties.length > 0) || schema.type === 'object') {
|
||||
return (
|
||||
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
||||
<OpenAPISchemaPresentation {...props} />
|
||||
{properties && properties.length > 0 ? (
|
||||
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
|
||||
<OpenAPISchemaProperties
|
||||
properties={properties}
|
||||
circularRefs={circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
) : null}
|
||||
{parentCircularRef ? (
|
||||
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
||||
) : null}
|
||||
</InteractiveSection>
|
||||
);
|
||||
}
|
||||
@@ -166,16 +172,24 @@ function OpenAPISchemaAlternative(props: {
|
||||
const { schema, circularRefs, context } = props;
|
||||
const id = useId();
|
||||
const subProperties = getSchemaProperties(schema);
|
||||
const description = resolveDescription(schema);
|
||||
|
||||
return (
|
||||
<OpenAPIDisclosure context={context}>
|
||||
<OpenAPISchemaProperties
|
||||
id={id}
|
||||
properties={subProperties ?? [{ schema }]}
|
||||
circularRefs={subProperties ? new Map(circularRefs).set(schema, id) : circularRefs}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
<>
|
||||
{description ? (
|
||||
<Markdown source={description} className="openapi-schema-description" />
|
||||
) : null}
|
||||
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
|
||||
<OpenAPISchemaProperties
|
||||
id={id}
|
||||
properties={subProperties ?? [{ schema }]}
|
||||
circularRefs={
|
||||
subProperties ? new Map(circularRefs).set(schema, id) : circularRefs
|
||||
}
|
||||
context={context}
|
||||
/>
|
||||
</OpenAPIDisclosure>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,7 +233,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
||||
|
||||
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
|
||||
return (
|
||||
typeof schema.example === 'string' ||
|
||||
(typeof schema.example === 'string' && !!schema.example) ||
|
||||
typeof schema.example === 'number' ||
|
||||
typeof schema.example === 'boolean' ||
|
||||
(Array.isArray(schema.example) && schema.example.length > 0) ||
|
||||
@@ -234,10 +248,10 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
||||
return (
|
||||
<div className="openapi-schema-presentation">
|
||||
<OpenAPISchemaName
|
||||
schema={schema}
|
||||
type={getSchemaTitle(schema)}
|
||||
propertyName={propertyName}
|
||||
required={required}
|
||||
deprecated={schema.deprecated}
|
||||
/>
|
||||
{schema['x-deprecated-sunset'] ? (
|
||||
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
|
||||
@@ -252,12 +266,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
||||
) : null}
|
||||
{shouldDisplayExample(schema) ? (
|
||||
<div className="openapi-schema-example">
|
||||
Example:{' '}
|
||||
<code>
|
||||
{typeof schema.example === 'string'
|
||||
? schema.example
|
||||
: stringifyOpenAPI(schema.example)}
|
||||
</code>
|
||||
Example: <code>{formatExample(schema.example)}</code>
|
||||
</div>
|
||||
) : null}
|
||||
{schema.pattern ? (
|
||||
@@ -276,17 +285,6 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
||||
* Get the sub-properties of a schema.
|
||||
*/
|
||||
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
|
||||
if (schema.allOf) {
|
||||
return schema.allOf.reduce((acc, subSchema) => {
|
||||
const properties = getSchemaProperties(subSchema) ?? [
|
||||
{
|
||||
schema: subSchema,
|
||||
},
|
||||
];
|
||||
return [...acc, ...properties];
|
||||
}, [] as OpenAPISchemaPropertyEntry[]);
|
||||
}
|
||||
|
||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||
if (schema.type === 'array' && !!schema.items) {
|
||||
const items = schema.items;
|
||||
@@ -295,6 +293,11 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
||||
return itemProperties;
|
||||
}
|
||||
|
||||
// If the items are a primitive type, we don't need to display them
|
||||
if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
propertyName: 'items',
|
||||
@@ -351,8 +354,7 @@ export function getSchemaAlternatives(
|
||||
}
|
||||
|
||||
if (schema.allOf) {
|
||||
// allOf is managed in `getSchemaProperties`
|
||||
return null;
|
||||
return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator];
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -378,11 +380,6 @@ export function getSchemaTitle(
|
||||
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
|
||||
discriminator?: OpenAPIV3.DiscriminatorObject,
|
||||
): string {
|
||||
if (schema.title) {
|
||||
// If the schema has a title, use it
|
||||
return schema.title;
|
||||
}
|
||||
|
||||
// Try using the discriminator
|
||||
if (discriminator?.propertyName && schema.properties) {
|
||||
const discriminatorProperty = schema.properties[discriminator.propertyName];
|
||||
@@ -397,7 +394,7 @@ export function getSchemaTitle(
|
||||
let type = 'any';
|
||||
|
||||
if (schema.enum) {
|
||||
type = 'enum';
|
||||
type = `${schema.type} · enum`;
|
||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||
} else if (schema.type === 'array' && !!schema.items) {
|
||||
type = `${getSchemaTitle(schema.items)}[]`;
|
||||
@@ -407,7 +404,7 @@ export function getSchemaTitle(
|
||||
type = schema.type ?? 'object';
|
||||
|
||||
if (schema.format) {
|
||||
type += ` ${schema.format}`;
|
||||
type += ` · ${schema.format}`;
|
||||
}
|
||||
} else if ('anyOf' in schema) {
|
||||
type = 'any of';
|
||||
@@ -419,9 +416,35 @@ export function getSchemaTitle(
|
||||
type = 'not';
|
||||
}
|
||||
|
||||
if (schema.nullable) {
|
||||
type = `nullable ${type}`;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined {
|
||||
if (schema.type === 'array' && !!schema.items) {
|
||||
if (schema.items.oneOf) {
|
||||
return 'available items';
|
||||
}
|
||||
|
||||
// Fallback to "child attributes" for enums and objects
|
||||
if (schema.items.enum || schema.items.type === 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
|
||||
}
|
||||
|
||||
return schema.title;
|
||||
}
|
||||
|
||||
function formatExample(example: any): string {
|
||||
if (typeof example === 'string') {
|
||||
return example
|
||||
.replace(/\n/g, ' ') // Replace newlines with spaces
|
||||
.replace(/\s+/g, ' ') // Collapse multiple spaces/newlines into a single space
|
||||
.replace(/([\{\}:,])\s+/g, '$1 ') // Ensure a space after {, }, :, and ,
|
||||
.replace(/\s+([\{\}:,])/g, ' $1') // Ensure a space before {, }, :, and ,
|
||||
.trim();
|
||||
}
|
||||
|
||||
return stringifyOpenAPI(example);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
|
||||
interface OpenAPISchemaNameProps {
|
||||
schema?: OpenAPIV3.SchemaObject;
|
||||
propertyName?: string | JSX.Element;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
deprecated?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10,18 +12,48 @@ interface OpenAPISchemaNameProps {
|
||||
* It includes the property name, type, required and deprecated status.
|
||||
*/
|
||||
export function OpenAPISchemaName(props: OpenAPISchemaNameProps): JSX.Element {
|
||||
const { type, propertyName, required, deprecated } = props;
|
||||
const { schema, type, propertyName, required } = props;
|
||||
|
||||
const additionalItems = schema && getAdditionalItems(schema);
|
||||
|
||||
return (
|
||||
<div className="openapi-schema-name">
|
||||
{propertyName ? (
|
||||
<span data-deprecated={deprecated} className="openapi-schema-propertyname">
|
||||
<span data-deprecated={schema?.deprecated} className="openapi-schema-propertyname">
|
||||
{propertyName}
|
||||
</span>
|
||||
) : null}
|
||||
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
||||
<span>
|
||||
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
||||
{additionalItems ? (
|
||||
<span className="openapi-schema-type">{additionalItems}</span>
|
||||
) : null}
|
||||
</span>
|
||||
{required ? <span className="openapi-schema-required">required</span> : null}
|
||||
{deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
||||
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
|
||||
let additionalItems = '';
|
||||
|
||||
if (schema.minimum || schema.minLength) {
|
||||
additionalItems += ` · min: ${schema.minimum || schema.minLength}`;
|
||||
}
|
||||
|
||||
if (schema.maximum || schema.maxLength) {
|
||||
additionalItems += ` · max: ${schema.maximum || schema.maxLength}`;
|
||||
}
|
||||
|
||||
// If the schema has a default value, we display it
|
||||
if (typeof schema.default !== 'undefined') {
|
||||
additionalItems += ` · default: ${schema.default}`;
|
||||
}
|
||||
|
||||
if (schema.nullable) {
|
||||
additionalItems = ` | nullable`;
|
||||
}
|
||||
|
||||
return additionalItems;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OpenAPIResponses } from './OpenAPIResponses';
|
||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||
import { OpenAPISecurities } from './OpenAPISecurities';
|
||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
||||
import { resolveDescription } from './utils';
|
||||
import { parameterToProperty } from './utils';
|
||||
|
||||
/**
|
||||
* Client component to render the spec for the request and response.
|
||||
@@ -38,22 +38,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
||||
header={group.label}
|
||||
>
|
||||
<OpenAPISchemaProperties
|
||||
properties={group.parameters.map((parameter) => {
|
||||
const description = resolveDescription(parameter);
|
||||
return {
|
||||
propertyName: parameter.name,
|
||||
schema: {
|
||||
// Description of the parameter is defined at the parameter level
|
||||
// we use display it if the schema doesn't override it
|
||||
description: description,
|
||||
example: parameter.example,
|
||||
// Deprecated can be defined at the parameter level
|
||||
deprecated: parameter.deprecated,
|
||||
...(parameter.schema ?? {}),
|
||||
},
|
||||
required: parameter.required,
|
||||
};
|
||||
})}
|
||||
properties={group.parameters.map(parameterToProperty)}
|
||||
context={context}
|
||||
/>
|
||||
</InteractiveSection>
|
||||
|
||||
@@ -74,11 +74,17 @@ export function OpenAPITabs(
|
||||
const tabFromState = syncedTabs.get(stateKey);
|
||||
|
||||
if (!items.some((item) => item.key === tabFromState?.key)) {
|
||||
return;
|
||||
return setSelectedTab(defaultTab);
|
||||
}
|
||||
|
||||
if (tabFromState && tabFromState?.key !== selectedTab?.key) {
|
||||
setSelectedTab(tabFromState);
|
||||
const tabFromItems = items.find((item) => item.key === tabFromState.key);
|
||||
|
||||
if (!tabFromItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedTab(tabFromItems);
|
||||
}
|
||||
}
|
||||
}, [isVisible, stateKey, syncedTabs, selectedTabKey]);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getUrlFromServerState indents correctly 1`] = `
|
||||
"<?xml version="1.0"?>
|
||||
<id>10</id>
|
||||
<name>doggie</name>
|
||||
<category>
|
||||
<id>1</id>
|
||||
<name>Dogs</name>
|
||||
</category>
|
||||
<photoUrls>string</photoUrls>
|
||||
<tags>
|
||||
<id>0</id>
|
||||
<name>string</name>
|
||||
</tags>
|
||||
<status>available</status>
|
||||
"
|
||||
`;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { it, expect } from 'bun:test';
|
||||
import { parseHostAndPath } from './code-samples';
|
||||
import { it, expect, describe } from 'bun:test';
|
||||
import { codeSampleGenerators, CodeSampleInput, parseHostAndPath } from './code-samples';
|
||||
|
||||
it('should parse host and path on url strings properly', () => {
|
||||
const testUrls = [
|
||||
@@ -49,3 +49,595 @@ it('should parse host and path on url strings properly', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('curL code sample generator', () => {
|
||||
const generator = codeSampleGenerators.find((g) => g.id === 'curl');
|
||||
|
||||
expect(generator).toBeDefined();
|
||||
|
||||
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/x-www-form-urlencoded' \\\n --data 'key=value'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/json body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"key\": \"value\"\n }'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/xml body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
body: '<key>value</key>',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/xml' \\\n --data-binary $'<key>value</key>'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/graphql body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: '{ key }',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/json' \\\n --data '\"{ key }\"'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/csv body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
},
|
||||
body: 'key,value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: text/csv' \\\n --data-binary $'key,value'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/pdf body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
body: 'file',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: application/pdf' \\\n --data-binary '@file'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/plain body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: 'value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: text/plain' \\\n --data 'value'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multipart/form-data body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
"curl -L \\\n --url 'https://example.com/path' \\\n --header 'Content-Type: multipart/form-data' \\\n --form 'key=value'",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('javascript code sample generator', () => {
|
||||
const generator = codeSampleGenerators.find((g) => g.id === 'javascript');
|
||||
|
||||
expect(generator).toBeDefined();
|
||||
|
||||
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const params = new URLSearchParams();\n\nparams.append("key", "value");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/x-www-form-urlencoded"\n },\n body: params.toString()\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/json body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify({\n "key": "value"\n })\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/xml body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
body: '<key>value</key>',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const xml = `\n <key>value</key>`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/xml"\n },\n body: xml\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/graphql body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: '{ key }',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const query = `\n { key }`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/graphql"\n },\n body: JSON.stringify(query)\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/csv body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
},
|
||||
body: 'key,value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const csv = `\n key,value`;\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "text/csv"\n },\n body: csv\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/pdf body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
body: 'file',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const formData = new FormData();\n\nformData.append("file", "file");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "application/pdf"\n },\n body: formData\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/plain body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: 'value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "text/plain"\n },\n body: "value"\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multipart/form-data body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'const formData = new FormData();\n\nformData.append("key", "value");\n\nconst response = await fetch(\'https://example.com/path\', {\n method: \'GET\',\n headers: {\n "Content-Type": "multipart/form-data"\n },\n body: formData\n});\n\nconst data = await response.json();',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('python code sample generator', () => {
|
||||
const generator = codeSampleGenerators.find((g) => g.id === 'python');
|
||||
|
||||
expect(generator).toBeDefined();
|
||||
|
||||
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/x-www-form-urlencoded"},\n data={"key":"value"}\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/json body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data={"key":"value"}\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/xml body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
body: '<key>value</key>',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/xml"},\n data="<key>value</key>"\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/graphql body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: '{ key }',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/graphql"},\n data="{ key }"\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/csv body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
},
|
||||
body: 'key,value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"text/csv"},\n data="key,value"\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/pdf body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
body: 'file',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nfiles = {\n "file": "file",\n}\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/pdf"},\n files=files\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/plain body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: 'value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"text/plain"},\n data="value"\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multipart/form-data body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'import requests\n\nfiles = {\n "key": "value",\n}\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"multipart/form-data"},\n files=files\n)\n\ndata = response.json()',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('http code sample generator', () => {
|
||||
const generator = codeSampleGenerators.find((g) => g.id === 'http');
|
||||
|
||||
expect(generator).toBeDefined();
|
||||
|
||||
it('should format application/x-www-form-urlencoded body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 15\nAccept: */*\n\n"key=value"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/json body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/json\nContent-Length: 15\nAccept: */*\n\n{\n "key": "value"\n}',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/xml body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/xml',
|
||||
},
|
||||
body: '<key>value</key>',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/xml\nContent-Length: 18\nAccept: */*\n\n"<key>value</key>"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/graphql body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: '{ key }',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/graphql\nContent-Length: 9\nAccept: */*\n\n"{ key }"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/csv body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
},
|
||||
body: 'key,value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: text/csv\nContent-Length: 11\nAccept: */*\n\n"key,value"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format application/pdf body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
body: 'file',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: application/pdf\nContent-Length: 6\nAccept: */*\n\n"file"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format text/plain body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: 'value',
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: text/plain\nContent-Length: 7\nAccept: */*\n\n"value"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multipart/form-data body properly', () => {
|
||||
const input: CodeSampleInput = {
|
||||
method: 'GET',
|
||||
url: 'https://example.com/path',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: {
|
||||
key: 'value',
|
||||
},
|
||||
};
|
||||
|
||||
const output = generator!.generate(input);
|
||||
|
||||
expect(output).toBe(
|
||||
'GET /path HTTP/1.1\nHost: example.com\nContent-Type: multipart/form-data\nContent-Length: 15\nAccept: */*\n\n{\n "key": "value"\n}',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
import {
|
||||
isFormData,
|
||||
isPDF,
|
||||
isFormUrlEncoded,
|
||||
isText,
|
||||
isXML,
|
||||
isCSV,
|
||||
isGraphQL,
|
||||
isPlainObject,
|
||||
} from './contentTypeChecks';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
|
||||
export interface CodeSampleInput {
|
||||
@@ -30,14 +40,27 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
||||
|
||||
lines.push(`--url '${url}'`);
|
||||
|
||||
if (headers) {
|
||||
if (body) {
|
||||
const bodyContent = BodyGenerators.getCurlBody(body, headers);
|
||||
|
||||
if (bodyContent) {
|
||||
body = bodyContent.body;
|
||||
headers = bodyContent.headers;
|
||||
}
|
||||
}
|
||||
|
||||
if (headers && Object.keys(headers).length > 0) {
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
lines.push(`--header '${key}: ${value}'`);
|
||||
});
|
||||
}
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
lines.push(`--data '${stringifyOpenAPI(body)}'`);
|
||||
if (body) {
|
||||
if (Array.isArray(body)) {
|
||||
lines.push(...body);
|
||||
} else {
|
||||
lines.push(body);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.map((line, index) => (index > 0 ? indent(line, 2) : line)).join(separator);
|
||||
@@ -50,18 +73,29 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
||||
generate: ({ method, url, headers, body }) => {
|
||||
let code = '';
|
||||
|
||||
if (body) {
|
||||
const lines = BodyGenerators.getJavaScriptBody(body, headers);
|
||||
|
||||
if (lines) {
|
||||
// add the generated code to the top
|
||||
code += lines.code;
|
||||
body = lines.body;
|
||||
headers = lines.headers;
|
||||
}
|
||||
}
|
||||
|
||||
code += `const response = await fetch('${url}', {
|
||||
method: '${method.toUpperCase()}',\n`;
|
||||
|
||||
if (headers) {
|
||||
if (headers && Object.keys(headers).length > 0) {
|
||||
code += indent(`headers: ${stringifyOpenAPI(headers, null, 2)},\n`, 4);
|
||||
}
|
||||
|
||||
if (body) {
|
||||
code += indent(`body: JSON.stringify(${stringifyOpenAPI(body, null, 2)}),\n`, 4);
|
||||
code += indent(`body: ${body}\n`, 4);
|
||||
}
|
||||
|
||||
code += `});\n`;
|
||||
code += `});\n\n`;
|
||||
code += `const data = await response.json();`;
|
||||
|
||||
return code;
|
||||
@@ -73,15 +107,34 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
||||
syntax: 'python',
|
||||
generate: ({ method, url, headers, body }) => {
|
||||
let code = 'import requests\n\n';
|
||||
|
||||
if (body) {
|
||||
const lines = BodyGenerators.getPythonBody(body, headers);
|
||||
|
||||
// add the generated code to the top
|
||||
if (lines) {
|
||||
code += lines.code;
|
||||
body = lines.body;
|
||||
headers = lines.headers;
|
||||
}
|
||||
}
|
||||
|
||||
code += `response = requests.${method.toLowerCase()}(\n`;
|
||||
code += indent(`"${url}",\n`, 4);
|
||||
if (headers) {
|
||||
|
||||
if (headers && Object.keys(headers).length > 0) {
|
||||
code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4);
|
||||
}
|
||||
|
||||
if (body) {
|
||||
code += indent(`json=${stringifyOpenAPI(body)}\n`, 4);
|
||||
if (body === 'files') {
|
||||
code += indent(`files=${body}\n`, 4);
|
||||
} else {
|
||||
code += indent(`data=${stringifyOpenAPI(body)}\n`, 4);
|
||||
}
|
||||
}
|
||||
code += ')\n';
|
||||
|
||||
code += ')\n\n';
|
||||
code += `data = response.json()`;
|
||||
return code;
|
||||
},
|
||||
@@ -99,6 +152,12 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
||||
// handle unicode chars with a text encoder
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
const bodyString = BodyGenerators.getHTTPBody(body, headers);
|
||||
|
||||
if (bodyString) {
|
||||
body = bodyString;
|
||||
}
|
||||
|
||||
headers = {
|
||||
...headers,
|
||||
'Content-Length': encoder.encode(bodyContent).length.toString(),
|
||||
@@ -117,7 +176,7 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
|
||||
.join('\n') + '\n'
|
||||
: '';
|
||||
|
||||
const bodyString = body ? `\n${stringifyOpenAPI(body, null, 2)}` : '';
|
||||
const bodyString = body ? `\n${body}` : '';
|
||||
|
||||
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
|
||||
Host: ${host}
|
||||
@@ -157,3 +216,165 @@ export function parseHostAndPath(url: string) {
|
||||
return { host, path };
|
||||
}
|
||||
}
|
||||
|
||||
// Body Generators
|
||||
const BodyGenerators = {
|
||||
getCurlBody(body: any, headers?: Record<string, string>) {
|
||||
if (!body || !headers) return undefined;
|
||||
|
||||
// Copy headers to avoid mutating the original object
|
||||
const headersCopy = { ...headers };
|
||||
const contentType: string = headersCopy['Content-Type'] || '';
|
||||
|
||||
if (isFormData(contentType)) {
|
||||
body = isPlainObject(body)
|
||||
? Object.entries(body).map(([key, value]) => `--form '${key}=${String(value)}'`)
|
||||
: `--form 'file=@${body}'`;
|
||||
} else if (isFormUrlEncoded(contentType)) {
|
||||
body = isPlainObject(body)
|
||||
? `--data '${Object.entries(body)
|
||||
.map(([key, value]) => `${key}=${String(value)}`)
|
||||
.join('&')}'`
|
||||
: String(body);
|
||||
} else if (isText(contentType)) {
|
||||
body = `--data '${String(body).replace(/"/g, '')}'`;
|
||||
} else if (isXML(contentType) || isCSV(contentType)) {
|
||||
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '')}'`;
|
||||
} else if (isGraphQL(contentType)) {
|
||||
body = `--data '${stringifyOpenAPI(body)}'`;
|
||||
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
||||
headersCopy['Content-Type'] = 'application/json';
|
||||
} else if (isPDF(contentType)) {
|
||||
// We use --data-binary to avoid cURL converting newlines to \r\n
|
||||
body = `--data-binary '@${String(body)}'`;
|
||||
} else {
|
||||
body = `--data '${stringifyOpenAPI(body, null, 2)}'`;
|
||||
}
|
||||
|
||||
return {
|
||||
body,
|
||||
headers: headersCopy,
|
||||
};
|
||||
},
|
||||
getJavaScriptBody: (body: any, headers?: Record<string, string>) => {
|
||||
if (!body || !headers) return;
|
||||
|
||||
let code = '';
|
||||
|
||||
// Copy headers to avoid mutating the original object
|
||||
const headersCopy = { ...headers };
|
||||
const contentType: string = headersCopy['Content-Type'] || '';
|
||||
|
||||
// Use FormData for file uploads
|
||||
if (isFormData(contentType)) {
|
||||
code += 'const formData = new FormData();\n\n';
|
||||
if (isPlainObject(body)) {
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
code += `formData.append("${key}", "${String(value)}");\n`;
|
||||
});
|
||||
} else if (typeof body === 'string') {
|
||||
code += `formData.append("file", "${body}");\n`;
|
||||
}
|
||||
code += '\n';
|
||||
body = 'formData';
|
||||
} else if (isFormUrlEncoded(contentType)) {
|
||||
// Use URLSearchParams for form-urlencoded data
|
||||
code += 'const params = new URLSearchParams();\n\n';
|
||||
if (isPlainObject(body)) {
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
code += `params.append("${key}", "${String(value)}");\n`;
|
||||
});
|
||||
}
|
||||
code += '\n';
|
||||
body = 'params.toString()';
|
||||
} else if (isGraphQL(contentType)) {
|
||||
if (isPlainObject(body)) {
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
code += `const ${key} = \`\n${indent(String(value), 4)}\`;\n\n`;
|
||||
});
|
||||
body = `JSON.stringify({ ${Object.keys(body).join(', ')} })`;
|
||||
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
||||
headersCopy['Content-Type'] = 'application/json';
|
||||
} else {
|
||||
code += `const query = \`\n${indent(String(body), 4)}\`;\n\n`;
|
||||
body = 'JSON.stringify(query)';
|
||||
}
|
||||
} else if (isCSV(contentType)) {
|
||||
code += 'const csv = `\n';
|
||||
code += indent(String(body), 4);
|
||||
code += '`;\n\n';
|
||||
body = 'csv';
|
||||
} else if (isPDF(contentType)) {
|
||||
// Use FormData to upload PDF files
|
||||
code += 'const formData = new FormData();\n\n';
|
||||
code += `formData.append("file", "${body}");\n\n`;
|
||||
body = 'formData';
|
||||
} else if (isXML(contentType)) {
|
||||
code += 'const xml = `\n';
|
||||
code += indent(String(body), 4);
|
||||
code += '`;\n\n';
|
||||
body = 'xml';
|
||||
} else if (isText(contentType)) {
|
||||
body = stringifyOpenAPI(body, null, 2);
|
||||
} else {
|
||||
body = `JSON.stringify(${stringifyOpenAPI(body, null, 2)})`;
|
||||
}
|
||||
|
||||
return { body, code, headers: headersCopy };
|
||||
},
|
||||
getPythonBody: (body: any, headers?: Record<string, string>) => {
|
||||
if (!body || !headers) return;
|
||||
let code = '';
|
||||
const contentType: string = headers['Content-Type'] || '';
|
||||
|
||||
if (isFormData(contentType)) {
|
||||
code += 'files = {\n';
|
||||
if (isPlainObject(body)) {
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
code += indent(`"${key}": "${String(value)}",`, 4) + '\n';
|
||||
});
|
||||
}
|
||||
code += '}\n\n';
|
||||
body = 'files';
|
||||
}
|
||||
|
||||
if (isPDF(contentType)) {
|
||||
code += 'files = {\n';
|
||||
code += indent(`"file": "${body}",`, 4) + '\n';
|
||||
code += '}\n\n';
|
||||
body = 'files';
|
||||
}
|
||||
|
||||
return { body, code, headers };
|
||||
},
|
||||
getHTTPBody: (body: any, headers?: Record<string, string>) => {
|
||||
if (!body || !headers) return undefined;
|
||||
|
||||
const contentType: string = headers['Content-Type'] || '';
|
||||
|
||||
const typeHandlers = {
|
||||
pdf: () => `${stringifyOpenAPI(body, null, 2)}`,
|
||||
formUrlEncoded: () => {
|
||||
const encoded = isPlainObject(body)
|
||||
? Object.entries(body)
|
||||
.map(([key, value]) => `${key}=${String(value)}`)
|
||||
.join('&')
|
||||
: String(body);
|
||||
return `"${encoded}"`;
|
||||
},
|
||||
text: () => `"${String(body)}"`,
|
||||
xmlOrCsv: () => `"${stringifyOpenAPI(body).replace(/"/g, '')}"`,
|
||||
default: () => `${stringifyOpenAPI(body, null, 2)}`,
|
||||
};
|
||||
|
||||
if (isPDF(contentType)) return typeHandlers.pdf();
|
||||
if (isFormUrlEncoded(contentType)) return typeHandlers.formUrlEncoded();
|
||||
if (isText(contentType)) return typeHandlers.text();
|
||||
if (isXML(contentType) || isCSV(contentType)) {
|
||||
return typeHandlers.xmlOrCsv();
|
||||
}
|
||||
|
||||
return typeHandlers.default();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export function isJSON(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('application/json') || false;
|
||||
}
|
||||
|
||||
export function isXML(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('application/xml') || false;
|
||||
}
|
||||
|
||||
export function isGraphQL(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('application/graphql') || false;
|
||||
}
|
||||
|
||||
export function isCSV(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('text/csv') || false;
|
||||
}
|
||||
|
||||
export function isPDF(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('application/pdf') || false;
|
||||
}
|
||||
|
||||
export function isText(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('text/plain') || false;
|
||||
}
|
||||
|
||||
export function isFormUrlEncoded(contentType?: string): boolean {
|
||||
return contentType?.toLowerCase().includes('application/x-www-form-urlencoded') || false;
|
||||
}
|
||||
|
||||
export function isFormData(contentType?: string): boolean {
|
||||
return !!contentType && contentType.toLowerCase().includes('multipart/form-data');
|
||||
}
|
||||
|
||||
export function isPlainObject(value: unknown): boolean {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -3,32 +3,40 @@ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
||||
|
||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||
|
||||
type ScalarGetExampleFromSchemaOptions = NonNullable<Parameters<typeof getExampleFromSchema>[1]>;
|
||||
type GenerateSchemaExampleOptions = Pick<
|
||||
ScalarGetExampleFromSchemaOptions,
|
||||
'xml' | 'omitEmptyAndOptionalProperties' | 'mode'
|
||||
>;
|
||||
|
||||
/**
|
||||
* Generate a JSON example from a schema
|
||||
*/
|
||||
export function generateSchemaExample(
|
||||
schema: OpenAPIV3.SchemaObject,
|
||||
options: {
|
||||
onlyRequired?: boolean;
|
||||
} = {},
|
||||
options?: GenerateSchemaExampleOptions,
|
||||
): JSONValue | undefined {
|
||||
return getExampleFromSchema(schema, {
|
||||
emptyString: 'text',
|
||||
omitEmptyAndOptionalProperties: options.onlyRequired,
|
||||
variables: {
|
||||
'date-time': new Date().toISOString(),
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
email: 'name@gmail.com',
|
||||
hostname: 'example.com',
|
||||
ipv4: '0.0.0.0',
|
||||
ipv6: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
||||
uri: 'https://example.com',
|
||||
uuid: '123e4567-e89b-12d3-a456-426614174000',
|
||||
binary: 'binary',
|
||||
byte: 'Ynl0ZXM=',
|
||||
password: 'password',
|
||||
return getExampleFromSchema(
|
||||
schema,
|
||||
{
|
||||
emptyString: 'text',
|
||||
variables: {
|
||||
'date-time': new Date().toISOString(),
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
email: 'name@gmail.com',
|
||||
hostname: 'example.com',
|
||||
ipv4: '0.0.0.0',
|
||||
ipv6: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
||||
uri: 'https://example.com',
|
||||
uuid: '123e4567-e89b-12d3-a456-426614174000',
|
||||
binary: 'binary',
|
||||
byte: 'Ynl0ZXM=',
|
||||
password: 'password',
|
||||
},
|
||||
...options,
|
||||
},
|
||||
});
|
||||
3, // Max depth for circular references
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,9 +44,7 @@ export function generateSchemaExample(
|
||||
*/
|
||||
export function generateMediaTypeExample(
|
||||
mediaType: OpenAPIV3.MediaTypeObject,
|
||||
options: {
|
||||
onlyRequired?: boolean;
|
||||
} = {},
|
||||
options?: GenerateSchemaExampleOptions,
|
||||
): JSONValue | undefined {
|
||||
if (mediaType.example) {
|
||||
return mediaType.example;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { json2xml } from './json2xml';
|
||||
|
||||
describe('getUrlFromServerState', () => {
|
||||
it('transforms JSON to xml', () => {
|
||||
const xml = json2xml({
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
expect(xml).toBe('<?xml version="1.0"?>\n<foo>bar</foo>\n');
|
||||
});
|
||||
|
||||
it('wraps array items', () => {
|
||||
const xml = json2xml({
|
||||
urls: {
|
||||
url: ['https://example.com', 'https://example.com'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(xml).toBe(
|
||||
'<?xml version="1.0"?>\n<urls>\n\t<url>https://example.com</url>\n\t<url>https://example.com</url>\n</urls>\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('indents correctly', () => {
|
||||
const xml = json2xml({
|
||||
id: 10,
|
||||
name: 'doggie',
|
||||
category: {
|
||||
id: 1,
|
||||
name: 'Dogs',
|
||||
},
|
||||
photoUrls: ['string'],
|
||||
tags: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'string',
|
||||
},
|
||||
],
|
||||
status: 'available',
|
||||
});
|
||||
|
||||
expect(xml).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { jsXml } from 'json-xml-parse';
|
||||
|
||||
/**
|
||||
* This function converts an object to XML.
|
||||
*/
|
||||
export function json2xml(data: Record<string, any>) {
|
||||
return jsXml.toXmlString(data, { beautify: true });
|
||||
}
|
||||
@@ -9,7 +9,7 @@ async function fetchFilesystem(url: string) {
|
||||
const filesystem = await parseOpenAPI({ value: text, rootURL: url });
|
||||
const transformedFs = await traverse(filesystem, async (node) => {
|
||||
if ('description' in node && typeof node.description === 'string' && node.description) {
|
||||
node['x-description-html'] = node.description;
|
||||
node['x-gitbook-description-html'] = node.description;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/**
|
||||
* Stringify an OpenAPI object. Same API as JSON.stringify.
|
||||
*/
|
||||
export function stringifyOpenAPI(body: unknown, transformer?: null, indent?: number): string {
|
||||
return JSON.stringify(body, transformer, indent);
|
||||
export function stringifyOpenAPI(body: unknown, _?: null, indent?: number): string {
|
||||
return JSON.stringify(
|
||||
body,
|
||||
(key, value) => {
|
||||
// Ignore internal keys
|
||||
if (key.startsWith('x-gitbook-')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
indent,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,18 @@ import type {
|
||||
} from '@gitbook/openapi-parser';
|
||||
|
||||
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
||||
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
||||
/**
|
||||
* Render a code block.
|
||||
*/
|
||||
renderCodeBlock: (props: { code: string; syntax: string }) => React.ReactNode;
|
||||
/**
|
||||
* Render the heading of the operation.
|
||||
*/
|
||||
renderHeading: (props: { deprecated: boolean; title: string }) => React.ReactNode;
|
||||
/**
|
||||
* Render the document of the operation.
|
||||
*/
|
||||
renderDocument: (props: { document: object }) => React.ReactNode;
|
||||
|
||||
/** Spec url for the Scalar Api Client */
|
||||
specUrl: string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||
|
||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||
export function checkIsReference(
|
||||
input: unknown,
|
||||
): input is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
|
||||
return typeof input === 'object' && !!input && '$ref' in input;
|
||||
}
|
||||
|
||||
@@ -11,10 +13,88 @@ export function createStateKey(key: string, scope?: string) {
|
||||
/**
|
||||
* Resolve the description of an object.
|
||||
*/
|
||||
export function resolveDescription(object: AnyObject) {
|
||||
return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
|
||||
? object['x-description-html']
|
||||
export function resolveDescription(object: OpenAPIV3.SchemaObject | AnyObject) {
|
||||
if ('items' in object && object.items) {
|
||||
return resolveDescription(object.items);
|
||||
}
|
||||
|
||||
return 'x-gitbook-description-html' in object &&
|
||||
typeof object['x-gitbook-description-html'] === 'string'
|
||||
? object['x-gitbook-description-html'].trim()
|
||||
: typeof object.description === 'string'
|
||||
? object.description
|
||||
? object.description.trim()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract descriptions from an object.
|
||||
*/
|
||||
export function extractDescriptions(object: AnyObject) {
|
||||
return {
|
||||
description: object.description,
|
||||
['x-gitbook-description-html']:
|
||||
'x-gitbook-description-html' in object
|
||||
? object['x-gitbook-description-html']
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the first example from an object.
|
||||
*/
|
||||
export function resolveFirstExample(object: AnyObject) {
|
||||
if ('examples' in object && typeof object.examples === 'object' && object.examples) {
|
||||
const keys = Object.keys(object.examples);
|
||||
const firstKey = keys[0];
|
||||
if (firstKey && object.examples[firstKey]) {
|
||||
return object.examples[firstKey];
|
||||
}
|
||||
}
|
||||
if ('example' in object && object.example !== undefined) {
|
||||
return object.example;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the schema of a parameter.
|
||||
* Extract the description, example and deprecated from parameter.
|
||||
*/
|
||||
export function resolveParameterSchema(
|
||||
parameter: OpenAPIV3.ParameterBaseObject,
|
||||
): OpenAPIV3.SchemaObject {
|
||||
const schema = checkIsReference(parameter.schema) ? undefined : parameter.schema;
|
||||
return {
|
||||
// Description of the parameter is defined at the parameter level
|
||||
// we use display it if the schema doesn't override it
|
||||
...extractDescriptions(parameter),
|
||||
example: resolveFirstExample(parameter),
|
||||
// Deprecated can be defined at the parameter level
|
||||
deprecated: parameter.deprecated,
|
||||
...schema,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a parameter object to a property object.
|
||||
*/
|
||||
export function parameterToProperty(
|
||||
parameter: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject,
|
||||
): {
|
||||
propertyName: string | undefined;
|
||||
schema: OpenAPIV3.SchemaObject;
|
||||
required: boolean | undefined;
|
||||
} {
|
||||
if (checkIsReference(parameter)) {
|
||||
return {
|
||||
propertyName: parameter.$ref ?? 'Unknown ref',
|
||||
schema: {},
|
||||
required: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
propertyName: parameter.name,
|
||||
schema: resolveParameterSchema(parameter),
|
||||
required: parameter.required,
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user