openapi-typesafe 1.0.0-rc.4 → 1.0.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/CHANGELOG.md +214 -0
  2. package/dist/adapters/node-file-system/node-file-system.cjs +7 -0
  3. package/dist/adapters/node-file-system/node-file-system.cjs.map +1 -1
  4. package/dist/adapters/node-file-system/node-file-system.mjs +7 -0
  5. package/dist/adapters/node-file-system/node-file-system.mjs.map +1 -1
  6. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs +19 -0
  7. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs.map +1 -0
  8. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs +17 -0
  9. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs.map +1 -0
  10. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs +5 -32
  11. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs +6 -33
  13. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/component-schemas.cjs +17 -0
  15. package/dist/adapters/redocly-loader/component-schemas.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/component-schemas.mjs +14 -0
  17. package/dist/adapters/redocly-loader/component-schemas.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/mapping-schema-names.cjs +22 -0
  19. package/dist/adapters/redocly-loader/mapping-schema-names.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/mapping-schema-names.mjs +20 -0
  21. package/dist/adapters/redocly-loader/mapping-schema-names.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/query-document-names.cjs +50 -0
  23. package/dist/adapters/redocly-loader/query-document-names.cjs.map +1 -0
  24. package/dist/adapters/redocly-loader/query-document-names.mjs +48 -0
  25. package/dist/adapters/redocly-loader/query-document-names.mjs.map +1 -0
  26. package/dist/adapters/redocly-loader/query-named-components.cjs +129 -0
  27. package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -0
  28. package/dist/adapters/redocly-loader/query-named-components.mjs +126 -0
  29. package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -0
  30. package/dist/adapters/redocly-loader/redocly-config-source.cjs +3 -0
  31. package/dist/adapters/redocly-loader/redocly-config-source.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/redocly-config-source.mjs +4 -1
  33. package/dist/adapters/redocly-loader/redocly-config-source.mjs.map +1 -1
  34. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +39 -18
  35. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  36. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +40 -19
  37. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  38. package/dist/adapters/redocly-loader/ref-access.cjs +25 -4
  39. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  40. package/dist/adapters/redocly-loader/ref-access.mjs +26 -5
  41. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  42. package/dist/adapters/redocly-loader/ref-holders.cjs +52 -10
  43. package/dist/adapters/redocly-loader/ref-holders.cjs.map +1 -1
  44. package/dist/adapters/redocly-loader/ref-holders.mjs +52 -11
  45. package/dist/adapters/redocly-loader/ref-holders.mjs.map +1 -1
  46. package/dist/adapters/redocly-loader/remote-fetch.cjs +20 -3
  47. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  48. package/dist/adapters/redocly-loader/remote-fetch.mjs +20 -4
  49. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  50. package/dist/adapters/redocly-loader/remote-node-limit.cjs +17 -3
  51. package/dist/adapters/redocly-loader/remote-node-limit.cjs.map +1 -1
  52. package/dist/adapters/redocly-loader/remote-node-limit.mjs +17 -3
  53. package/dist/adapters/redocly-loader/remote-node-limit.mjs.map +1 -1
  54. package/dist/adapters/redocly-loader/schema-source.cjs +7 -2
  55. package/dist/adapters/redocly-loader/schema-source.cjs.map +1 -1
  56. package/dist/adapters/redocly-loader/schema-source.mjs +8 -3
  57. package/dist/adapters/redocly-loader/schema-source.mjs.map +1 -1
  58. package/dist/adapters/terminal-logger/terminal-logger.cjs +7 -3
  59. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  60. package/dist/adapters/terminal-logger/terminal-logger.mjs +7 -3
  61. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  62. package/dist/api/cwd-option.cjs +58 -0
  63. package/dist/api/cwd-option.cjs.map +1 -0
  64. package/dist/api/cwd-option.mjs +52 -0
  65. package/dist/api/cwd-option.mjs.map +1 -0
  66. package/dist/api/default-ports.cjs +15 -7
  67. package/dist/api/default-ports.cjs.map +1 -1
  68. package/dist/api/default-ports.mjs +14 -8
  69. package/dist/api/default-ports.mjs.map +1 -1
  70. package/dist/api/document-refs.cjs +16 -1
  71. package/dist/api/document-refs.cjs.map +1 -1
  72. package/dist/api/document-refs.mjs +16 -1
  73. package/dist/api/document-refs.mjs.map +1 -1
  74. package/dist/api/document-run.cjs +6 -26
  75. package/dist/api/document-run.cjs.map +1 -1
  76. package/dist/api/document-run.mjs +5 -21
  77. package/dist/api/document-run.mjs.map +1 -1
  78. package/dist/api/legacy-utils.cjs +1 -1
  79. package/dist/api/legacy-utils.cjs.map +1 -1
  80. package/dist/api/legacy-utils.mjs +1 -1
  81. package/dist/api/legacy-utils.mjs.map +1 -1
  82. package/dist/api/openapi-ts.cjs +1 -2
  83. package/dist/api/openapi-ts.cjs.map +1 -1
  84. package/dist/api/openapi-ts.mjs +2 -3
  85. package/dist/api/openapi-ts.mjs.map +1 -1
  86. package/dist/api/option-errors.cjs +24 -0
  87. package/dist/api/option-errors.cjs.map +1 -0
  88. package/dist/api/option-errors.mjs +21 -0
  89. package/dist/api/option-errors.mjs.map +1 -0
  90. package/dist/api/ref-limit-options.cjs +58 -0
  91. package/dist/api/ref-limit-options.cjs.map +1 -0
  92. package/dist/api/ref-limit-options.mjs +52 -0
  93. package/dist/api/ref-limit-options.mjs.map +1 -0
  94. package/dist/api/transform-schema.cjs +12 -5
  95. package/dist/api/transform-schema.cjs.map +1 -1
  96. package/dist/api/transform-schema.mjs +12 -5
  97. package/dist/api/transform-schema.mjs.map +1 -1
  98. package/dist/application/transform/declared-value-types.cjs +36 -0
  99. package/dist/application/transform/declared-value-types.cjs.map +1 -0
  100. package/dist/application/transform/declared-value-types.mjs +34 -0
  101. package/dist/application/transform/declared-value-types.mjs.map +1 -0
  102. package/dist/application/transform/document/document.cjs +3 -1
  103. package/dist/application/transform/document/document.cjs.map +1 -1
  104. package/dist/application/transform/document/document.mjs +3 -1
  105. package/dist/application/transform/document/document.mjs.map +1 -1
  106. package/dist/application/transform/document/exploded-query-values.cjs +63 -0
  107. package/dist/application/transform/document/exploded-query-values.cjs.map +1 -0
  108. package/dist/application/transform/document/exploded-query-values.mjs +59 -0
  109. package/dist/application/transform/document/exploded-query-values.mjs.map +1 -0
  110. package/dist/application/transform/document/header.cjs +11 -3
  111. package/dist/application/transform/document/header.cjs.map +1 -1
  112. package/dist/application/transform/document/header.mjs +10 -2
  113. package/dist/application/transform/document/header.mjs.map +1 -1
  114. package/dist/application/transform/document/parameter.cjs +1 -1
  115. package/dist/application/transform/document/parameter.cjs.map +1 -1
  116. package/dist/application/transform/document/parameter.mjs +1 -1
  117. package/dist/application/transform/document/parameter.mjs.map +1 -1
  118. package/dist/application/transform/document/parameters.cjs +23 -5
  119. package/dist/application/transform/document/parameters.cjs.map +1 -1
  120. package/dist/application/transform/document/parameters.mjs +23 -5
  121. package/dist/application/transform/document/parameters.mjs.map +1 -1
  122. package/dist/application/transform/document/path-item.cjs +4 -3
  123. package/dist/application/transform/document/path-item.cjs.map +1 -1
  124. package/dist/application/transform/document/path-item.mjs +4 -4
  125. package/dist/application/transform/document/path-item.mjs.map +1 -1
  126. package/dist/application/transform/document/path-template-key.cjs +84 -0
  127. package/dist/application/transform/document/path-template-key.cjs.map +1 -0
  128. package/dist/application/transform/document/path-template-key.mjs +79 -0
  129. package/dist/application/transform/document/path-template-key.mjs.map +1 -0
  130. package/dist/application/transform/document/path-template-refs.cjs +80 -0
  131. package/dist/application/transform/document/path-template-refs.cjs.map +1 -0
  132. package/dist/application/transform/document/path-template-refs.mjs +77 -0
  133. package/dist/application/transform/document/path-template-refs.mjs.map +1 -0
  134. package/dist/application/transform/document/paths-enum.cjs +7 -6
  135. package/dist/application/transform/document/paths-enum.cjs.map +1 -1
  136. package/dist/application/transform/document/paths-enum.mjs +7 -6
  137. package/dist/application/transform/document/paths-enum.mjs.map +1 -1
  138. package/dist/application/transform/document/paths.cjs +38 -66
  139. package/dist/application/transform/document/paths.cjs.map +1 -1
  140. package/dist/application/transform/document/paths.mjs +36 -66
  141. package/dist/application/transform/document/paths.mjs.map +1 -1
  142. package/dist/application/transform/document/webhooks.cjs +10 -3
  143. package/dist/application/transform/document/webhooks.cjs.map +1 -1
  144. package/dist/application/transform/document/webhooks.mjs +9 -2
  145. package/dist/application/transform/document/webhooks.mjs.map +1 -1
  146. package/dist/application/transform/footer/document-footer.cjs +6 -3
  147. package/dist/application/transform/footer/document-footer.cjs.map +1 -1
  148. package/dist/application/transform/footer/document-footer.mjs +6 -3
  149. package/dist/application/transform/footer/document-footer.mjs.map +1 -1
  150. package/dist/application/transform/helper-types.cjs +3 -3
  151. package/dist/application/transform/helper-types.cjs.map +1 -1
  152. package/dist/application/transform/helper-types.mjs +3 -3
  153. package/dist/application/transform/helper-types.mjs.map +1 -1
  154. package/dist/application/transform/ref-pointer/generated-location.cjs +4 -4
  155. package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -1
  156. package/dist/application/transform/ref-pointer/generated-location.mjs +1 -1
  157. package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -1
  158. package/dist/application/transform/ref-pointer/generated-shape.cjs +63 -64
  159. package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
  160. package/dist/application/transform/ref-pointer/generated-shape.mjs +57 -53
  161. package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -1
  162. package/dist/application/transform/ref-pointer/inline-cycles.cjs +2 -2
  163. package/dist/application/transform/ref-pointer/inline-cycles.cjs.map +1 -1
  164. package/dist/application/transform/ref-pointer/inline-cycles.mjs +1 -1
  165. package/dist/application/transform/ref-pointer/inline-cycles.mjs.map +1 -1
  166. package/dist/application/transform/ref-pointer/located-node.cjs +23 -0
  167. package/dist/application/transform/ref-pointer/located-node.cjs.map +1 -0
  168. package/dist/application/transform/ref-pointer/located-node.mjs +18 -0
  169. package/dist/application/transform/ref-pointer/located-node.mjs.map +1 -0
  170. package/dist/application/transform/ref-pointer/original-index.cjs +69 -14
  171. package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
  172. package/dist/application/transform/ref-pointer/original-index.mjs +71 -16
  173. package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -1
  174. package/dist/application/transform/ref-pointer/pointer-type.cjs +2 -2
  175. package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
  176. package/dist/application/transform/ref-pointer/pointer-type.mjs +1 -1
  177. package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
  178. package/dist/application/transform/ref-pointer/pointer-walk.cjs +54 -13
  179. package/dist/application/transform/ref-pointer/pointer-walk.cjs.map +1 -1
  180. package/dist/application/transform/ref-pointer/pointer-walk.mjs +46 -7
  181. package/dist/application/transform/ref-pointer/pointer-walk.mjs.map +1 -1
  182. package/dist/application/transform/ref-pointer/property-shape.cjs +57 -0
  183. package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -0
  184. package/dist/application/transform/ref-pointer/property-shape.mjs +49 -0
  185. package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -0
  186. package/dist/application/transform/ref-pointer/typed-walk.cjs +90 -22
  187. package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
  188. package/dist/application/transform/ref-pointer/typed-walk.mjs +87 -19
  189. package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -1
  190. package/dist/application/transform/schema/access-flags.cjs.map +1 -1
  191. package/dist/application/transform/schema/access-flags.mjs.map +1 -1
  192. package/dist/application/transform/schema/index-signature-type.cjs +21 -0
  193. package/dist/application/transform/schema/index-signature-type.cjs.map +1 -0
  194. package/dist/application/transform/schema/index-signature-type.mjs +19 -0
  195. package/dist/application/transform/schema/index-signature-type.mjs.map +1 -0
  196. package/dist/application/transform/schema/input-location.cjs +23 -0
  197. package/dist/application/transform/schema/input-location.cjs.map +1 -0
  198. package/dist/application/transform/schema/input-location.mjs +21 -0
  199. package/dist/application/transform/schema/input-location.mjs.map +1 -0
  200. package/dist/application/transform/schema/object-type.cjs +15 -5
  201. package/dist/application/transform/schema/object-type.cjs.map +1 -1
  202. package/dist/application/transform/schema/object-type.mjs +15 -5
  203. package/dist/application/transform/schema/object-type.mjs.map +1 -1
  204. package/dist/application/transform/schema/schema-transform.cjs +2 -1
  205. package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
  206. package/dist/application/transform/schema/schema-transform.mjs +2 -1
  207. package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
  208. package/dist/application/transform/schema/unevaluated-properties.cjs +2 -30
  209. package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
  210. package/dist/application/transform/schema/unevaluated-properties.mjs +3 -31
  211. package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
  212. package/dist/cli/api-logger.cjs +13 -0
  213. package/dist/cli/api-logger.cjs.map +1 -0
  214. package/dist/cli/api-logger.mjs +11 -0
  215. package/dist/cli/api-logger.mjs.map +1 -0
  216. package/dist/cli/api-settings.cjs +10 -17
  217. package/dist/cli/api-settings.cjs.map +1 -1
  218. package/dist/cli/api-settings.mjs +10 -17
  219. package/dist/cli/api-settings.mjs.map +1 -1
  220. package/dist/cli/api-writes.cjs +7 -1
  221. package/dist/cli/api-writes.cjs.map +1 -1
  222. package/dist/cli/api-writes.mjs +7 -1
  223. package/dist/cli/api-writes.mjs.map +1 -1
  224. package/dist/cli/cli-arguments.cjs +32 -74
  225. package/dist/cli/cli-arguments.cjs.map +1 -1
  226. package/dist/cli/cli-arguments.mjs +28 -70
  227. package/dist/cli/cli-arguments.mjs.map +1 -1
  228. package/dist/cli/cli-errors.cjs +18 -0
  229. package/dist/cli/cli-errors.cjs.map +1 -1
  230. package/dist/cli/cli-errors.mjs +18 -1
  231. package/dist/cli/cli-errors.mjs.map +1 -1
  232. package/dist/cli/cli-flag-names.cjs +77 -0
  233. package/dist/cli/cli-flag-names.cjs.map +1 -0
  234. package/dist/cli/cli-flag-names.mjs +64 -0
  235. package/dist/cli/cli-flag-names.mjs.map +1 -0
  236. package/dist/cli/cli-output.cjs +13 -6
  237. package/dist/cli/cli-output.cjs.map +1 -1
  238. package/dist/cli/cli-output.mjs +14 -7
  239. package/dist/cli/cli-output.mjs.map +1 -1
  240. package/dist/cli/main.cjs +11 -5
  241. package/dist/cli/main.cjs.map +1 -1
  242. package/dist/cli/main.mjs +11 -5
  243. package/dist/cli/main.mjs.map +1 -1
  244. package/dist/cli/redocly-apis.cjs +33 -12
  245. package/dist/cli/redocly-apis.cjs.map +1 -1
  246. package/dist/cli/redocly-apis.mjs +35 -14
  247. package/dist/cli/redocly-apis.mjs.map +1 -1
  248. package/dist/cli/ref-limits.cjs +15 -1
  249. package/dist/cli/ref-limits.cjs.map +1 -1
  250. package/dist/cli/ref-limits.mjs +15 -2
  251. package/dist/cli/ref-limits.mjs.map +1 -1
  252. package/dist/cli/run-cli.cjs +24 -7
  253. package/dist/cli/run-cli.cjs.map +1 -1
  254. package/dist/cli/run-cli.mjs +25 -8
  255. package/dist/cli/run-cli.mjs.map +1 -1
  256. package/dist/cli/written-flags.cjs +83 -0
  257. package/dist/cli/written-flags.cjs.map +1 -0
  258. package/dist/cli/written-flags.mjs +77 -0
  259. package/dist/cli/written-flags.mjs.map +1 -0
  260. package/dist/domain/cause-secrets.cjs +31 -16
  261. package/dist/domain/cause-secrets.cjs.map +1 -1
  262. package/dist/domain/cause-secrets.mjs +31 -16
  263. package/dist/domain/cause-secrets.mjs.map +1 -1
  264. package/dist/domain/errors.cjs +3 -0
  265. package/dist/domain/errors.cjs.map +1 -1
  266. package/dist/domain/errors.mjs +3 -0
  267. package/dist/domain/errors.mjs.map +1 -1
  268. package/dist/domain/location-text.cjs +15 -0
  269. package/dist/domain/location-text.cjs.map +1 -0
  270. package/dist/domain/location-text.mjs +13 -0
  271. package/dist/domain/location-text.mjs.map +1 -0
  272. package/dist/domain/openapi/document-places.cjs +55 -0
  273. package/dist/domain/openapi/document-places.cjs.map +1 -0
  274. package/dist/domain/openapi/document-places.mjs +53 -0
  275. package/dist/domain/openapi/document-places.mjs.map +1 -0
  276. package/dist/domain/output-fixes.cjs +8 -2
  277. package/dist/domain/output-fixes.cjs.map +1 -1
  278. package/dist/domain/output-fixes.mjs +8 -2
  279. package/dist/domain/output-fixes.mjs.map +1 -1
  280. package/dist/domain/terminal-text.cjs +31 -0
  281. package/dist/domain/terminal-text.cjs.map +1 -0
  282. package/dist/domain/terminal-text.mjs +29 -0
  283. package/dist/domain/terminal-text.mjs.map +1 -0
  284. package/dist/domain/warnings.cjs +1 -0
  285. package/dist/domain/warnings.cjs.map +1 -1
  286. package/dist/domain/warnings.mjs +1 -0
  287. package/dist/domain/warnings.mjs.map +1 -1
  288. package/dist/index.d.cts +29 -18
  289. package/dist/index.d.mts +29 -18
  290. package/dist/index.d.ts +29 -18
  291. package/package.json +1 -1
  292. package/src/adapters/node-file-system/node-file-system.ts +14 -1
  293. package/src/adapters/pipe-safe-writer/pipe-safe-writer.ts +29 -0
  294. package/src/adapters/redocly-loader/bundled-mapping-copies.ts +7 -42
  295. package/src/adapters/redocly-loader/component-schemas.ts +19 -0
  296. package/src/adapters/redocly-loader/mapping-schema-names.ts +25 -0
  297. package/src/adapters/redocly-loader/query-document-names.ts +79 -0
  298. package/src/adapters/redocly-loader/query-named-components.ts +200 -0
  299. package/src/adapters/redocly-loader/redocly-config-source.ts +11 -1
  300. package/src/adapters/redocly-loader/redocly-schema-loader.ts +63 -17
  301. package/src/adapters/redocly-loader/ref-access.ts +32 -5
  302. package/src/adapters/redocly-loader/ref-holders.ts +75 -14
  303. package/src/adapters/redocly-loader/remote-fetch.ts +37 -2
  304. package/src/adapters/redocly-loader/remote-node-limit.ts +44 -4
  305. package/src/adapters/redocly-loader/schema-source.ts +15 -3
  306. package/src/adapters/terminal-logger/terminal-logger.ts +10 -4
  307. package/src/api/cwd-option.ts +76 -0
  308. package/src/api/default-ports.ts +33 -10
  309. package/src/api/document-refs.ts +29 -1
  310. package/src/api/document-run.ts +7 -32
  311. package/src/api/legacy-utils.ts +1 -1
  312. package/src/api/openapi-ts.ts +20 -4
  313. package/src/api/option-errors.ts +22 -0
  314. package/src/api/ref-limit-options.ts +69 -0
  315. package/src/api/transform-schema.ts +20 -5
  316. package/src/application/ports/file-system.ts +8 -0
  317. package/src/application/ports/redocly-config-source.ts +5 -0
  318. package/src/application/transform/declared-value-types.ts +42 -0
  319. package/src/application/transform/document/document.ts +4 -2
  320. package/src/application/transform/document/exploded-query-values.ts +88 -0
  321. package/src/application/transform/document/header.ts +25 -5
  322. package/src/application/transform/document/parameter.ts +4 -4
  323. package/src/application/transform/document/parameters.ts +45 -10
  324. package/src/application/transform/document/path-item.ts +3 -3
  325. package/src/application/transform/document/path-template-key.ts +152 -0
  326. package/src/application/transform/document/path-template-refs.ts +127 -0
  327. package/src/application/transform/document/paths-enum.ts +23 -9
  328. package/src/application/transform/document/paths.ts +53 -123
  329. package/src/application/transform/document/webhooks.ts +18 -5
  330. package/src/application/transform/footer/document-footer.ts +18 -8
  331. package/src/application/transform/helper-types.ts +4 -4
  332. package/src/application/transform/ref-pointer/generated-location.ts +1 -1
  333. package/src/application/transform/ref-pointer/generated-shape.ts +115 -83
  334. package/src/application/transform/ref-pointer/inline-cycles.ts +1 -1
  335. package/src/application/transform/ref-pointer/located-node.ts +38 -0
  336. package/src/application/transform/ref-pointer/original-index.ts +106 -20
  337. package/src/application/transform/ref-pointer/pointer-type.ts +1 -1
  338. package/src/application/transform/ref-pointer/pointer-walk.ts +82 -8
  339. package/src/application/transform/ref-pointer/property-shape.ts +106 -0
  340. package/src/application/transform/ref-pointer/typed-walk.ts +143 -29
  341. package/src/application/transform/schema/access-flags.ts +5 -2
  342. package/src/application/transform/schema/index-signature-type.ts +31 -0
  343. package/src/application/transform/schema/input-location.ts +35 -0
  344. package/src/application/transform/schema/object-type.ts +18 -8
  345. package/src/application/transform/schema/schema-transform.ts +2 -1
  346. package/src/application/transform/schema/unevaluated-properties.ts +4 -44
  347. package/src/cli/api-logger.ts +17 -0
  348. package/src/cli/api-settings.ts +12 -20
  349. package/src/cli/api-writes.ts +11 -3
  350. package/src/cli/cli-arguments.ts +57 -92
  351. package/src/cli/cli-dependencies.ts +7 -2
  352. package/src/cli/cli-errors.ts +23 -0
  353. package/src/cli/cli-flag-names.ts +99 -0
  354. package/src/cli/cli-output.ts +31 -16
  355. package/src/cli/main.ts +12 -5
  356. package/src/cli/redocly-apis.ts +56 -20
  357. package/src/cli/ref-limits.ts +21 -2
  358. package/src/cli/run-cli.ts +39 -7
  359. package/src/cli/written-flags.ts +131 -0
  360. package/src/domain/cause-secrets.ts +59 -25
  361. package/src/domain/errors.ts +3 -0
  362. package/src/domain/location-text.ts +27 -0
  363. package/src/domain/openapi/document-places.ts +78 -0
  364. package/src/domain/output-fixes.ts +9 -2
  365. package/src/domain/terminal-text.ts +44 -0
  366. package/src/domain/warnings.ts +1 -0
@@ -0,0 +1,200 @@
1
+ import {
2
+ appendPointer,
3
+ canonicalRef,
4
+ parseRefPointer,
5
+ ROOT_POINTER,
6
+ } from "../../domain/openapi/json-pointer.js";
7
+ import { type DocumentNames, queryDocumentNames } from "./query-document-names.js";
8
+ import { findMappings, findRefHolders } from "./ref-holders.js";
9
+ import type { LoadedDocument } from "./unsupported-keys.js";
10
+
11
+ /**
12
+ * Redocly 1.x bundles an external `$ref` into a component named after the last segment of the
13
+ * document's URL, without its extension, when the pointer gives no free name (a `$ref` to a whole
14
+ * document, or two targets with the same pointer). The query stays in that name, and a query holds
15
+ * API keys and signatures, so `https://host/schemas/pet?token=…` became the component
16
+ * `pet?token=…` in the generated file and in messages (threat model T4b). This renames such a
17
+ * component after the URL without its query: `pet`, with a `-2` suffix when that name is taken.
18
+ * Each component is matched to its document by its node, since two URLs can give one name.
19
+ */
20
+
21
+ type Components = Record<string, Record<string, unknown>>;
22
+
23
+ /** The keys of each `components` group before bundling: the bundle edits the document in place. */
24
+ export type ComponentKeys = ReadonlyMap<string, ReadonlySet<string>>;
25
+
26
+ /** Old and new name of each renamed component, per `components` group. */
27
+ type Renames = Map<string, Map<string, string>>;
28
+
29
+ const COMPONENTS_KEY = "components";
30
+ /** What Redocly puts between the base name and the rest of a component name. */
31
+ const NAME_SUFFIX_STARTS = ["_", "-"];
32
+ const FIRST_SERIAL = 2;
33
+ /**
34
+ * The serial Redocly adds when a name is taken (`pet-2`). Two URLs that differ only in the query
35
+ * took one name; without the query they may not, so the serial is chosen again (`freeName`).
36
+ */
37
+ const REDOCLY_SERIAL_RE = /-\d+$/;
38
+
39
+ function isContainer(value: unknown): value is Record<string, unknown> {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+
43
+ function componentsOf(document: unknown): Components {
44
+ const components = isContainer(document) ? document[COMPONENTS_KEY] : undefined;
45
+ if (!isContainer(components)) {
46
+ return {};
47
+ }
48
+ const groups: Components = {};
49
+ for (const [group, entries] of Object.entries(components)) {
50
+ if (isContainer(entries)) {
51
+ groups[group] = entries;
52
+ }
53
+ }
54
+ return groups;
55
+ }
56
+
57
+ /** The keys of every `components` group of a document. */
58
+ export function componentKeysOf(document: unknown): ComponentKeys {
59
+ const keys = new Map<string, ReadonlySet<string>>();
60
+ for (const [group, entries] of Object.entries(componentsOf(document))) {
61
+ keys.set(group, new Set(Object.keys(entries)));
62
+ }
63
+ return keys;
64
+ }
65
+
66
+ /** The name a component of a document with a query gets; undefined for any other component. */
67
+ function queryFreeName(key: string, names: DocumentNames | undefined): string | undefined {
68
+ if (names === undefined) {
69
+ return undefined;
70
+ }
71
+ const { named, clean } = names;
72
+ const hasBaseName =
73
+ key === named || NAME_SUFFIX_STARTS.some((start) => key.startsWith(`${named}${start}`));
74
+ return hasBaseName
75
+ ? `${clean}${key.slice(named.length).replace(REDOCLY_SERIAL_RE, "")}`
76
+ : undefined;
77
+ }
78
+
79
+ function freeName(name: string, taken: ReadonlySet<string>): string {
80
+ let free = name;
81
+ for (let serial = FIRST_SERIAL; taken.has(free); serial += 1) {
82
+ free = `${name}-${serial}`;
83
+ }
84
+ return free;
85
+ }
86
+
87
+ /** Rename the bundled components of one group in place, keeping their order. */
88
+ function renameGroup(
89
+ entries: Record<string, unknown>,
90
+ keysBefore: ReadonlySet<string>,
91
+ documentNames: ReadonlyMap<object, DocumentNames>,
92
+ ): Map<string, string> {
93
+ const renames = new Map<string, string>();
94
+ const taken = new Set(Object.keys(entries));
95
+ for (const key of Object.keys(entries)) {
96
+ const node = entries[key];
97
+ const names = typeof node === "object" && node !== null ? documentNames.get(node) : undefined;
98
+ const name = keysBefore.has(key) ? undefined : queryFreeName(key, names);
99
+ if (name !== undefined) {
100
+ taken.delete(key);
101
+ const free = freeName(name, taken);
102
+ taken.add(free);
103
+ renames.set(key, free);
104
+ }
105
+ }
106
+ for (const [key, value] of Object.entries(entries)) {
107
+ delete entries[key];
108
+ entries[renames.get(key) ?? key] = value;
109
+ }
110
+ return renames;
111
+ }
112
+
113
+ /** Rewrites pointers and messages that name a renamed component. */
114
+ export interface ComponentRenaming {
115
+ /** The pointer with a renamed component's new name; any other text as it is. */
116
+ readonly pointer: (pointer: string) => string;
117
+ /** The text with each renamed component's old name replaced by its new name. */
118
+ readonly text: (text: string) => string;
119
+ /**
120
+ * Whether the problem is Redocly's report of two components with one name that the new names
121
+ * tell apart: it renamed `new-api` to `new-api-2`, and the documents are now `pet` and `owner`.
122
+ */
123
+ readonly isResolvedCollision: (message: string) => boolean;
124
+ }
125
+
126
+ /** The warning Redocly 1.34 gives when it adds a serial to a component name. */
127
+ const COLLISION_RE =
128
+ /^Two schemas are referenced with the same name but different content\. Renamed (.+) to (.+)\.$/;
129
+ const SERIAL_RE = /^-\d+$/;
130
+
131
+ function resolvesCollision(message: string, renames: Renames): boolean {
132
+ const [, first, second] = COLLISION_RE.exec(message) ?? [];
133
+ if (first === undefined || second === undefined) {
134
+ return false;
135
+ }
136
+ const renamed = new Map([...renames.values()].flatMap((group) => [...group]));
137
+ const [newFirst, newSecond] = [renamed.get(first) ?? first, renamed.get(second) ?? second];
138
+ return !(newSecond.startsWith(newFirst) && SERIAL_RE.test(newSecond.slice(newFirst.length)));
139
+ }
140
+
141
+ function renamedPointer(pointer: string, renames: Renames): string {
142
+ if (!pointer.startsWith(`${ROOT_POINTER}/`)) {
143
+ return pointer;
144
+ }
145
+ const [components, group = "", name = "", ...rest] = parseRefPointer(canonicalRef(pointer));
146
+ const renamed = components === COMPONENTS_KEY ? renames.get(group)?.get(name) : undefined;
147
+ return renamed === undefined
148
+ ? pointer
149
+ : appendPointer(ROOT_POINTER, [COMPONENTS_KEY, group, renamed, ...rest]);
150
+ }
151
+
152
+ function renamedText(text: string, renames: Renames): string {
153
+ const pairs = [...renames.values()].flatMap((group) => [...group]);
154
+ // The longest old name first, so a name that starts another one does not split it.
155
+ pairs.sort(([a], [b]) => b.length - a.length);
156
+ return pairs.reduce((shown, [old, renamed]) => shown.replaceAll(old, renamed), text);
157
+ }
158
+
159
+ function rewriteRefs(document: unknown, renaming: ComponentRenaming): void {
160
+ for (const { node } of findRefHolders(document)) {
161
+ node.$ref = renaming.pointer(node.$ref);
162
+ }
163
+ for (const mapping of findMappings(document)) {
164
+ for (const [name, value] of Object.entries(mapping)) {
165
+ if (typeof value === "string") {
166
+ mapping[name] = renaming.pointer(value);
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Rename, in place, the components the bundle named after a URL with a query, and every local
174
+ * `$ref` and `mapping` value that names them. `documents` are the documents the load read.
175
+ */
176
+ export function renameQueryNamedComponents(
177
+ bundled: unknown,
178
+ keysBefore: ComponentKeys,
179
+ documents: readonly LoadedDocument[],
180
+ ): ComponentRenaming {
181
+ const documentNames = queryDocumentNames(documents);
182
+ const renames: Renames = new Map();
183
+ if (documentNames.size > 0) {
184
+ for (const [group, entries] of Object.entries(componentsOf(bundled))) {
185
+ const groupRenames = renameGroup(entries, keysBefore.get(group) ?? new Set(), documentNames);
186
+ if (groupRenames.size > 0) {
187
+ renames.set(group, groupRenames);
188
+ }
189
+ }
190
+ }
191
+ const renaming: ComponentRenaming = {
192
+ pointer: (pointer) => renamedPointer(pointer, renames),
193
+ text: (text) => renamedText(text, renames),
194
+ isResolvedCollision: (message) => resolvesCollision(message, renames),
195
+ };
196
+ if (renames.size > 0) {
197
+ rewriteRefs(bundled, renaming);
198
+ }
199
+ return renaming;
200
+ }
@@ -1,6 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { createConfig, findConfig, IGNORE_FILE, loadConfig } from "@redocly/openapi-core";
3
+ import {
4
+ createConfig,
5
+ findConfig,
6
+ getMergedConfig,
7
+ IGNORE_FILE,
8
+ loadConfig,
9
+ } from "@redocly/openapi-core";
4
10
  import type {
5
11
  RedoclyConfig,
6
12
  RedoclyConfigSource,
@@ -70,6 +76,10 @@ class RedoclyConfigFiles implements RedoclyConfigSource {
70
76
  );
71
77
  return loadedConfig(create, undefined);
72
78
  }
79
+
80
+ forApi(config: RedoclyConfig, apiName: string): RedoclyConfig {
81
+ return getMergedConfig(config, apiName);
82
+ }
73
83
  }
74
84
 
75
85
  /** Find and load `redocly.yaml` relative to the directory `cwd`. */
@@ -3,6 +3,7 @@ import {
3
3
  type BaseResolver,
4
4
  bundle,
5
5
  type Document,
6
+ type LocationObject,
6
7
  lintDocument,
7
8
  type NormalizedProblem,
8
9
  type Config as RedoclyConfig,
@@ -14,16 +15,27 @@ import type {
14
15
  SchemaLoadRequest,
15
16
  } from "../../application/ports/schema-loader.js";
16
17
  import { withStackOverflowReportedAsync } from "../../application/stack-overflow.js";
18
+ import { redactUrlSecrets } from "../../domain/url-secrets.js";
17
19
  import { WARNING_CODES, warningText } from "../../domain/warnings.js";
18
20
  import type { OpenAPI3 } from "../../types.js";
19
21
  import { removeBundledMappingCopies, schemaKeysOf } from "./bundled-mapping-copies.js";
20
22
  import { repairBundledRefs } from "./bundled-ref-repair.js";
21
23
  import { loadedDocuments, localFilesRead } from "./loaded-documents.js";
22
24
  import { findMalformedRefs, malformedRefsError } from "./malformed-refs.js";
25
+ import { pointMappingNamesAtSchemas } from "./mapping-schema-names.js";
26
+ import {
27
+ type ComponentRenaming,
28
+ componentKeysOf,
29
+ renameQueryNamedComponents,
30
+ } from "./query-named-components.js";
23
31
  import { RefAccessResolver } from "./ref-access.js";
24
32
  import { checkRefsResolve } from "./ref-resolution-check.js";
25
33
  import { REF_CYCLE_MESSAGE } from "./ref-resolution-steps.js";
26
- import { type RemoteDocumentLimits, resolveConfigWithAccess } from "./remote-fetch.js";
34
+ import {
35
+ RedirectedUrls,
36
+ type RemoteDocumentLimits,
37
+ resolveConfigWithAccess,
38
+ } from "./remote-fetch.js";
27
39
  import {
28
40
  loadFailedError,
29
41
  refCycleError,
@@ -160,6 +172,7 @@ class RedoclySchemaLoader implements SchemaLoader {
160
172
  const document = await parseSchema(request.source, { absoluteRef, resolver });
161
173
  logger.debug("Parsed schema", "redoc", now() - parseStartedAt);
162
174
  assertOpenAPI3(document.parsed);
175
+ pointMappingNamesAtSchemas(document.parsed);
163
176
  const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);
164
177
  const isRootAFile = request.source instanceof URL && request.source.protocol === "file:";
165
178
  const localFiles = await localFilesRead(document, resolver, isRootAFile);
@@ -168,16 +181,21 @@ class RedoclySchemaLoader implements SchemaLoader {
168
181
 
169
182
  #resolver(request: SchemaLoadRequest): RefAccessResolver {
170
183
  const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;
184
+ // One record per load, as the resolver and its cache are.
185
+ const redirects = new RedirectedUrls();
171
186
  const config = resolveConfigWithAccess(redoc.resolve, {
172
187
  timeoutMs: remoteRefTimeoutMs,
173
188
  access: request.refAccess,
189
+ rootUrl: request.source instanceof URL ? request.source.href : undefined,
174
190
  maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
175
191
  bodyLimits: remoteDocumentLimits,
192
+ redirects,
176
193
  });
177
194
  return new RefAccessResolver(config, {
178
195
  access: request.refAccess,
179
196
  maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
180
197
  maxRemoteNodes: remoteDocumentLimits.maxNodes,
198
+ redirects,
181
199
  });
182
200
  }
183
201
 
@@ -202,13 +220,14 @@ class RedoclySchemaLoader implements SchemaLoader {
202
220
  config: this.#options.redoc.styleguide,
203
221
  externalRefResolver: resolver,
204
222
  });
205
- this.#report(problems);
223
+ this.#report(problems, document.source.absoluteRef);
206
224
  this.#options.logger.debug("Linted schema", "lint", this.#options.now() - startedAt);
207
225
  }
208
226
 
209
227
  async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {
210
228
  const startedAt = this.#options.now();
211
229
  const schemaKeys = schemaKeysOf(document.parsed);
230
+ const componentKeys = componentKeysOf(document.parsed);
212
231
  // The lint resolver: documents it already loaded are not fetched again.
213
232
  const bundled = await bundle({
214
233
  config: this.#options.redoc,
@@ -216,9 +235,12 @@ class RedoclySchemaLoader implements SchemaLoader {
216
235
  doc: document,
217
236
  externalRefResolver: resolver,
218
237
  });
219
- this.#report(bundled.problems);
220
238
  removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);
221
239
  repairBundledRefs(bundled.bundle.parsed);
240
+ const documents = await loadedDocuments(document, resolver);
241
+ const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);
242
+ // The problems name the components the bundle made, so they are shown by their new names.
243
+ this.#report(bundled.problems, document.source.absoluteRef, renaming);
222
244
  this.#options.logger.debug("Bundled schema", "bundle", this.#options.now() - startedAt);
223
245
  return bundled.bundle.parsed;
224
246
  }
@@ -226,24 +248,26 @@ class RedoclySchemaLoader implements SchemaLoader {
226
248
  /**
227
249
  * Report every problem; then throw if any of them is an error. A problem that the Redocly
228
250
  * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out
229
- * (upstream #2189).
251
+ * (upstream #2189). A name collision that the renamed components no longer have is dropped.
230
252
  */
231
- #report(problems: readonly NormalizedProblem[]): void {
253
+ #report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {
232
254
  const errorMessages: string[] = [];
233
- for (const problem of problems) {
255
+ for (const problem of problems.filter(
256
+ (found) => !renaming.isResolvedCollision(found.message),
257
+ )) {
234
258
  if (problem.ignored === true) {
235
259
  this.#options.logger.debug(
236
- `Ignored by the lint ignore file: ${problemMessage(problem)}`,
260
+ `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,
237
261
  "lint",
238
262
  );
239
263
  continue;
240
264
  }
241
- const message = problemMessage(problem);
265
+ const message = problemMessage(problem, renaming, rootRef);
242
266
  if (problem.severity === "error") {
243
267
  errorMessages.push(message);
244
268
  this.#options.logger.error(message);
245
269
  } else {
246
- this.#options.logger.warn(lintWarningText(problem));
270
+ this.#options.logger.warn(lintWarningText(message, problem.ruleId));
247
271
  }
248
272
  }
249
273
  if (errorMessages.length > 0) {
@@ -253,16 +277,38 @@ class RedoclySchemaLoader implements SchemaLoader {
253
277
  }
254
278
 
255
279
  /** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */
256
- function lintWarningText(problem: NormalizedProblem): string {
257
- return warningText(
258
- `${problemMessage(problem)}, rule ${problem.ruleId}`,
259
- WARNING_CODES.LINT_PROBLEM,
260
- );
280
+ function lintWarningText(message: string, ruleId: string): string {
281
+ return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);
282
+ }
283
+
284
+ /** Problems found before the bundle named any component. */
285
+ const UNRENAMED: ComponentRenaming = {
286
+ pointer: (pointer) => pointer,
287
+ text: (text) => text,
288
+ isResolvedCollision: () => false,
289
+ };
290
+
291
+ /** A problem location that names a JSON Pointer, not a line and column. */
292
+ type PointerLocation = Extract<LocationObject, { pointer: string }>;
293
+
294
+ /**
295
+ * Where a problem is: its pointer, and the document when it is not the root (URL secrets left
296
+ * out).
297
+ */
298
+ function locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {
299
+ const pointer = renaming.pointer(location.pointer);
300
+ const documentRef = location.source.absoluteRef;
301
+ return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;
261
302
  }
262
303
 
263
- function problemMessage(problem: NormalizedProblem): string {
264
- const location = problem.location?.[0]?.pointer;
265
- return location ? `${problem.message} at ${location}` : problem.message;
304
+ function problemMessage(
305
+ problem: NormalizedProblem,
306
+ renaming: ComponentRenaming,
307
+ rootRef: string,
308
+ ): string {
309
+ const location = problem.location?.[0];
310
+ const message = renaming.text(problem.message);
311
+ return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;
266
312
  }
267
313
 
268
314
  /** A schema loader with one Redocly config and logger, for one or more loads. */
@@ -5,7 +5,7 @@ import {
5
5
  BaseResolver,
6
6
  type Document,
7
7
  type Config as RedoclyConfig,
8
- type Source,
8
+ Source,
9
9
  YamlParseError,
10
10
  } from "@redocly/openapi-core";
11
11
  import type { RefAccess } from "../../application/ports/schema-loader.js";
@@ -13,6 +13,8 @@ import { ERROR_CODES, type OpenAPITypesafeError } from "../../domain/errors.js";
13
13
  import { redactUrlSecrets } from "../../domain/url-secrets.js";
14
14
  import { aliasExpansionFailure } from "./alias-limit.js";
15
15
  import { isHostAllowed } from "./allowed-hosts.js";
16
+ import { pointMappingNamesAtSchemas } from "./mapping-schema-names.js";
17
+ import type { RedirectedUrls } from "./remote-fetch.js";
16
18
  import { isParsedSource, RemoteNodeLimit } from "./remote-node-limit.js";
17
19
  import { refForbiddenError } from "./schema-load-errors.js";
18
20
 
@@ -107,6 +109,8 @@ export interface RefAccessLimits {
107
109
  readonly maxRemoteDocuments: number;
108
110
  /** Nodes the remote documents of one load may parse into together (threat model T2). */
109
111
  readonly maxRemoteNodes: number;
112
+ /** The final URLs of redirected documents, which their relative `$ref`s resolve against. */
113
+ readonly redirects: RedirectedUrls;
110
114
  }
111
115
 
112
116
  /**
@@ -162,21 +166,33 @@ export class RefAccessResolver extends BaseResolver {
162
166
  return super.resolveDocument(base, ref, isRoot);
163
167
  }
164
168
 
165
- /** Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). */
169
+ /**
170
+ * Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a
171
+ * `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas
172
+ * before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).
173
+ */
166
174
  override parseDocument(source: Source, isRoot = false): Document {
167
175
  const document = this.#parsed(source, isRoot);
168
176
  const failure = aliasExpansionFailure(document.parsed);
169
177
  if (failure !== undefined) {
170
178
  throw new YamlParseError(failure, source);
171
179
  }
180
+ pointMappingNamesAtSchemas(document.parsed);
172
181
  return document;
173
182
  }
174
183
 
175
- /** A remote document is parsed within the node limit; any other one as Redocly parses it. */
184
+ /**
185
+ * A remote document is parsed within the node limit; a local one as Redocly parses it. Whether
186
+ * a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the
187
+ * extension of a URL with its query.
188
+ */
176
189
  #parsed(source: Source, isRoot: boolean): Document {
177
- if (!isRemoteRef(source.absoluteRef) || !isParsedSource(source, isRoot)) {
190
+ if (!isRemoteRef(source.absoluteRef)) {
178
191
  return super.parseDocument(source, isRoot);
179
192
  }
193
+ if (!isParsedSource(source, isRoot)) {
194
+ return { source, parsed: source.body };
195
+ }
180
196
  try {
181
197
  return this.#nodeLimit.parse(source);
182
198
  } catch (error) {
@@ -194,7 +210,7 @@ export class RefAccessResolver extends BaseResolver {
194
210
  throw refusal;
195
211
  }
196
212
  try {
197
- return await super.loadExternalRef(absoluteRef);
213
+ return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));
198
214
  } catch (error) {
199
215
  // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.
200
216
  const cause = (error as { originalError?: unknown }).originalError;
@@ -205,6 +221,17 @@ export class RefAccessResolver extends BaseResolver {
205
221
  }
206
222
  }
207
223
 
224
+ /**
225
+ * A redirected document is named by the URL it came from, so its relative `$ref`s, its
226
+ * component names and its problems use that URL. Redocly still finds it by the requested one.
227
+ */
228
+ #atFinalUrl(source: Source): Source {
229
+ const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);
230
+ return finalUrl === source.absoluteRef
231
+ ? source
232
+ : new Source(finalUrl, source.body, source.mimeType);
233
+ }
234
+
208
235
  #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {
209
236
  return absoluteRef === this.#rootRef
210
237
  ? Promise.resolve(undefined)
@@ -6,38 +6,99 @@ export interface RefHolder {
6
6
  readonly pointer: string;
7
7
  }
8
8
 
9
+ /**
10
+ * Keywords whose value is data, not OpenAPI: a `$ref` or a `discriminator` in an example, a
11
+ * default, a `const` or an `enum` value, or an extension is part of the data and stays as written.
12
+ */
13
+ const DATA_KEYWORDS = new Set(["example", "examples", "default", "const", "enum"]);
14
+ const EXTENSION_PREFIX = "x-";
15
+
16
+ /**
17
+ * Keywords whose value maps names to objects: a property, a schema or a response may be named
18
+ * `default` or `example`, and is OpenAPI all the same.
19
+ */
20
+ const NAME_MAPS = new Set([
21
+ "properties",
22
+ "patternProperties",
23
+ "dependentSchemas",
24
+ "$defs",
25
+ "definitions",
26
+ "schemas",
27
+ "responses",
28
+ "parameters",
29
+ "requestBodies",
30
+ "headers",
31
+ "securitySchemes",
32
+ "links",
33
+ "callbacks",
34
+ "pathItems",
35
+ "mediaTypes",
36
+ "content",
37
+ "encoding",
38
+ "paths",
39
+ "webhooks",
40
+ ]);
41
+
9
42
  interface PendingNode {
10
43
  readonly value: object;
11
44
  readonly pointer: string;
45
+ /** Whether the keys of `value` are names (of properties, schemas, responses), not keywords. */
46
+ readonly hasNameKeys: boolean;
12
47
  }
13
48
 
14
- function hasStringRef(value: object): value is { $ref: string } {
15
- return !Array.isArray(value) && typeof (value as { $ref?: unknown }).$ref === "string";
49
+ function isDataKey(key: string): boolean {
50
+ return DATA_KEYWORDS.has(key) || key.startsWith(EXTENSION_PREFIX);
16
51
  }
17
52
 
18
53
  /**
19
- * Every object with a string `$ref` in a parsed document, in no particular order. The walk uses
20
- * its own stack, so a deeply nested document does not run out of call stack, and a node shared by
21
- * YAML aliases (or a cycle of them) is visited once.
54
+ * Every object of a parsed document that is OpenAPI, not data (`DATA_KEYWORDS`), with its JSON
55
+ * Pointer, in no particular order. The walk uses its own stack, so a deeply nested document does
56
+ * not run out of call stack, and a node shared by YAML aliases (or a cycle of them) is visited
57
+ * once.
22
58
  */
23
- export function findRefHolders(document: unknown): RefHolder[] {
24
- const found: RefHolder[] = [];
59
+ function openApiNodes(document: unknown): PendingNode[] {
60
+ const found: PendingNode[] = [];
25
61
  const visited = new WeakSet<object>();
26
62
  const pending: PendingNode[] = [];
27
- const enqueue = (value: unknown, pointer: string): void => {
63
+ const enqueue = (value: unknown, pointer: string, hasNameKeys: boolean): void => {
28
64
  if (typeof value === "object" && value !== null && !visited.has(value)) {
29
65
  visited.add(value);
30
- pending.push({ value, pointer });
66
+ pending.push({ value, pointer, hasNameKeys });
31
67
  }
32
68
  };
33
- enqueue(document, ROOT_POINTER);
69
+ enqueue(document, ROOT_POINTER, false);
34
70
  for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
35
- if (hasStringRef(next.value)) {
36
- found.push({ node: next.value, pointer: next.pointer });
37
- }
71
+ found.push(next);
72
+ // One push per child: spreading an array of millions of children overflows the stack.
38
73
  for (const [key, child] of Object.entries(next.value)) {
39
- enqueue(child, childPointer(next.pointer, key));
74
+ if (next.hasNameKeys || !isDataKey(key)) {
75
+ enqueue(child, childPointer(next.pointer, key), !next.hasNameKeys && NAME_MAPS.has(key));
76
+ }
40
77
  }
41
78
  }
42
79
  return found;
43
80
  }
81
+
82
+ function hasStringRef(value: object): value is { $ref: string } {
83
+ return !Array.isArray(value) && typeof (value as { $ref?: unknown }).$ref === "string";
84
+ }
85
+
86
+ /** Every object with a string `$ref` in a parsed document, outside data, in no particular order. */
87
+ export function findRefHolders(document: unknown): RefHolder[] {
88
+ return openApiNodes(document).flatMap(({ value, pointer, hasNameKeys }) =>
89
+ !hasNameKeys && hasStringRef(value) ? [{ node: value, pointer }] : [],
90
+ );
91
+ }
92
+
93
+ function isMapping(value: unknown): value is Record<string, unknown> {
94
+ return typeof value === "object" && value !== null && !Array.isArray(value);
95
+ }
96
+
97
+ /** Every `discriminator.mapping` object of a parsed document, outside data. */
98
+ export function findMappings(document: unknown): Record<string, unknown>[] {
99
+ return openApiNodes(document).flatMap(({ value, hasNameKeys }) => {
100
+ const { discriminator } = value as { discriminator?: unknown };
101
+ const mapping = !hasNameKeys && isMapping(discriminator) ? discriminator.mapping : undefined;
102
+ return isMapping(mapping) ? [mapping] : [];
103
+ });
104
+ }