openapi-typesafe 1.0.0-rc.5 → 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 (148) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/adapters/redocly-loader/query-document-names.cjs +50 -0
  3. package/dist/adapters/redocly-loader/query-document-names.cjs.map +1 -0
  4. package/dist/adapters/redocly-loader/query-document-names.mjs +48 -0
  5. package/dist/adapters/redocly-loader/query-document-names.mjs.map +1 -0
  6. package/dist/adapters/redocly-loader/query-named-components.cjs +29 -37
  7. package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -1
  8. package/dist/adapters/redocly-loader/query-named-components.mjs +29 -37
  9. package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -1
  10. package/dist/adapters/redocly-loader/redocly-config-source.cjs +3 -0
  11. package/dist/adapters/redocly-loader/redocly-config-source.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/redocly-config-source.mjs +4 -1
  13. package/dist/adapters/redocly-loader/redocly-config-source.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +16 -7
  15. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  16. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +17 -8
  17. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  18. package/dist/adapters/redocly-loader/ref-access.cjs +9 -1
  19. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  20. package/dist/adapters/redocly-loader/ref-access.mjs +10 -2
  21. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  22. package/dist/adapters/redocly-loader/remote-fetch.cjs +16 -1
  23. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  24. package/dist/adapters/redocly-loader/remote-fetch.mjs +16 -2
  25. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  26. package/dist/api/cwd-option.cjs +58 -0
  27. package/dist/api/cwd-option.cjs.map +1 -0
  28. package/dist/api/cwd-option.mjs +52 -0
  29. package/dist/api/cwd-option.mjs.map +1 -0
  30. package/dist/api/default-ports.cjs +15 -7
  31. package/dist/api/default-ports.cjs.map +1 -1
  32. package/dist/api/default-ports.mjs +14 -8
  33. package/dist/api/default-ports.mjs.map +1 -1
  34. package/dist/api/document-run.cjs +2 -16
  35. package/dist/api/document-run.cjs.map +1 -1
  36. package/dist/api/document-run.mjs +2 -12
  37. package/dist/api/document-run.mjs.map +1 -1
  38. package/dist/api/legacy-utils.cjs +1 -1
  39. package/dist/api/legacy-utils.cjs.map +1 -1
  40. package/dist/api/legacy-utils.mjs +1 -1
  41. package/dist/api/legacy-utils.mjs.map +1 -1
  42. package/dist/api/openapi-ts.cjs.map +1 -1
  43. package/dist/api/openapi-ts.mjs.map +1 -1
  44. package/dist/api/option-errors.cjs +24 -0
  45. package/dist/api/option-errors.cjs.map +1 -0
  46. package/dist/api/option-errors.mjs +21 -0
  47. package/dist/api/option-errors.mjs.map +1 -0
  48. package/dist/api/ref-limit-options.cjs +19 -24
  49. package/dist/api/ref-limit-options.cjs.map +1 -1
  50. package/dist/api/ref-limit-options.mjs +15 -20
  51. package/dist/api/ref-limit-options.mjs.map +1 -1
  52. package/dist/application/transform/document/path-template-refs.cjs +12 -2
  53. package/dist/application/transform/document/path-template-refs.cjs.map +1 -1
  54. package/dist/application/transform/document/path-template-refs.mjs +12 -2
  55. package/dist/application/transform/document/path-template-refs.mjs.map +1 -1
  56. package/dist/application/transform/ref-pointer/generated-shape.cjs +28 -10
  57. package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
  58. package/dist/application/transform/ref-pointer/generated-shape.mjs +29 -11
  59. package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -1
  60. package/dist/application/transform/ref-pointer/original-index.cjs +1 -1
  61. package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
  62. package/dist/application/transform/ref-pointer/original-index.mjs +1 -1
  63. package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -1
  64. package/dist/application/transform/ref-pointer/property-shape.cjs +5 -0
  65. package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -1
  66. package/dist/application/transform/ref-pointer/property-shape.mjs +5 -1
  67. package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -1
  68. package/dist/application/transform/ref-pointer/typed-walk.cjs +11 -1
  69. package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
  70. package/dist/application/transform/ref-pointer/typed-walk.mjs +12 -2
  71. package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -1
  72. package/dist/cli/api-settings.cjs +3 -2
  73. package/dist/cli/api-settings.cjs.map +1 -1
  74. package/dist/cli/api-settings.mjs +2 -1
  75. package/dist/cli/api-settings.mjs.map +1 -1
  76. package/dist/cli/api-writes.cjs +7 -1
  77. package/dist/cli/api-writes.cjs.map +1 -1
  78. package/dist/cli/api-writes.mjs +7 -1
  79. package/dist/cli/api-writes.mjs.map +1 -1
  80. package/dist/cli/cli-arguments.cjs +25 -107
  81. package/dist/cli/cli-arguments.cjs.map +1 -1
  82. package/dist/cli/cli-arguments.mjs +18 -100
  83. package/dist/cli/cli-arguments.mjs.map +1 -1
  84. package/dist/cli/cli-errors.cjs +1 -0
  85. package/dist/cli/cli-errors.cjs.map +1 -1
  86. package/dist/cli/cli-errors.mjs +1 -0
  87. package/dist/cli/cli-errors.mjs.map +1 -1
  88. package/dist/cli/cli-flag-names.cjs +77 -0
  89. package/dist/cli/cli-flag-names.cjs.map +1 -0
  90. package/dist/cli/cli-flag-names.mjs +64 -0
  91. package/dist/cli/cli-flag-names.mjs.map +1 -0
  92. package/dist/cli/cli-output.cjs +7 -2
  93. package/dist/cli/cli-output.cjs.map +1 -1
  94. package/dist/cli/cli-output.mjs +7 -2
  95. package/dist/cli/cli-output.mjs.map +1 -1
  96. package/dist/cli/main.cjs +1 -1
  97. package/dist/cli/main.cjs.map +1 -1
  98. package/dist/cli/main.mjs +1 -1
  99. package/dist/cli/main.mjs.map +1 -1
  100. package/dist/cli/redocly-apis.cjs +14 -4
  101. package/dist/cli/redocly-apis.cjs.map +1 -1
  102. package/dist/cli/redocly-apis.mjs +14 -4
  103. package/dist/cli/redocly-apis.mjs.map +1 -1
  104. package/dist/cli/written-flags.cjs +83 -0
  105. package/dist/cli/written-flags.cjs.map +1 -0
  106. package/dist/cli/written-flags.mjs +77 -0
  107. package/dist/cli/written-flags.mjs.map +1 -0
  108. package/dist/domain/errors.cjs +1 -0
  109. package/dist/domain/errors.cjs.map +1 -1
  110. package/dist/domain/errors.mjs +1 -0
  111. package/dist/domain/errors.mjs.map +1 -1
  112. package/dist/domain/location-text.cjs +7 -1
  113. package/dist/domain/location-text.cjs.map +1 -1
  114. package/dist/domain/location-text.mjs +7 -1
  115. package/dist/domain/location-text.mjs.map +1 -1
  116. package/dist/index.d.cts +3 -0
  117. package/dist/index.d.mts +3 -0
  118. package/dist/index.d.ts +3 -0
  119. package/package.json +1 -1
  120. package/src/adapters/redocly-loader/query-document-names.ts +79 -0
  121. package/src/adapters/redocly-loader/query-named-components.ts +48 -49
  122. package/src/adapters/redocly-loader/redocly-config-source.ts +11 -1
  123. package/src/adapters/redocly-loader/redocly-schema-loader.ts +20 -6
  124. package/src/adapters/redocly-loader/ref-access.ts +16 -2
  125. package/src/adapters/redocly-loader/remote-fetch.ts +26 -0
  126. package/src/api/cwd-option.ts +76 -0
  127. package/src/api/default-ports.ts +33 -10
  128. package/src/api/document-run.ts +2 -17
  129. package/src/api/legacy-utils.ts +1 -1
  130. package/src/api/openapi-ts.ts +14 -1
  131. package/src/api/option-errors.ts +22 -0
  132. package/src/api/ref-limit-options.ts +17 -24
  133. package/src/application/ports/redocly-config-source.ts +5 -0
  134. package/src/application/transform/document/path-template-refs.ts +28 -7
  135. package/src/application/transform/ref-pointer/generated-shape.ts +57 -14
  136. package/src/application/transform/ref-pointer/original-index.ts +6 -3
  137. package/src/application/transform/ref-pointer/property-shape.ts +9 -0
  138. package/src/application/transform/ref-pointer/typed-walk.ts +26 -4
  139. package/src/cli/api-writes.ts +11 -3
  140. package/src/cli/cli-arguments.ts +42 -153
  141. package/src/cli/cli-errors.ts +1 -0
  142. package/src/cli/cli-flag-names.ts +99 -0
  143. package/src/cli/cli-output.ts +20 -6
  144. package/src/cli/main.ts +2 -1
  145. package/src/cli/redocly-apis.ts +21 -5
  146. package/src/cli/written-flags.ts +131 -0
  147. package/src/domain/errors.ts +1 -0
  148. package/src/domain/location-text.ts +17 -2
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { findConfig, IGNORE_FILE, loadConfig, createConfig } from '@redocly/openapi-core';
3
+ import { findConfig, getMergedConfig, IGNORE_FILE, loadConfig, createConfig } from '@redocly/openapi-core';
4
4
  import { OpenAPITypesafeError, ERROR_CODES } from '../../domain/errors.mjs';
5
5
 
6
6
  function reasonOf(error) {
@@ -45,6 +45,9 @@ class RedoclyConfigFiles {
45
45
  );
46
46
  return loadedConfig(create, void 0);
47
47
  }
48
+ forApi(config, apiName) {
49
+ return getMergedConfig(config, apiName);
50
+ }
48
51
  }
49
52
  function createRedoclyConfigSource(cwd) {
50
53
  return new RedoclyConfigFiles(cwd);
@@ -1 +1 @@
1
- {"version":3,"file":"redocly-config-source.mjs","sources":["../../../src/adapters/redocly-loader/redocly-config-source.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createConfig, findConfig, IGNORE_FILE, loadConfig } from \"@redocly/openapi-core\";\nimport type {\n RedoclyConfig,\n RedoclyConfigSource,\n} from \"../../application/ports/redocly-config-source.js\";\nimport { ERROR_CODES, OpenAPITypesafeError } from \"../../domain/errors.js\";\n\n/**\n * The first line of a failure, with the error's name: a YAML error goes on with a snippet of the\n * file, and the name tells a YAML error from a file system one.\n */\nfunction reasonOf(error: unknown): string {\n return String(error).replace(/\\n[\\s\\S]*$/, \"\");\n}\n\n/**\n * The config, or `CLI_CONFIG_INVALID`. Redocly reads the config file and the lint-ignore file\n * next to it (or in the working directory); either can be malformed, and its error does not\n * always name the file, so the message names both.\n */\nasync function loadedConfig(\n load: () => Promise<RedoclyConfig>,\n configPath: string | undefined,\n): Promise<RedoclyConfig> {\n try {\n return await load();\n } catch (error) {\n const config =\n configPath === undefined ? \"the default Redocly config\" : `the Redocly config ${configPath}`;\n const where = configPath === undefined ? \"in the working directory\" : \"next to it\";\n throw new OpenAPITypesafeError({\n code: ERROR_CODES.CLI_CONFIG_INVALID,\n message: `Cannot load ${config} or the ${IGNORE_FILE} ${where}: ${reasonOf(error)}`,\n details: { configPath },\n cause: error,\n });\n }\n}\n\n/** The Redocly config port on `@redocly/openapi-core`, resolving paths from `cwd`. */\nclass RedoclyConfigFiles implements RedoclyConfigSource {\n readonly #cwd: string;\n\n constructor(cwd: string) {\n this.#cwd = cwd;\n }\n\n find(explicitPath: string | undefined): string | undefined {\n if (!explicitPath) {\n return findConfig(this.#cwd);\n }\n const resolved = path.resolve(this.#cwd, explicitPath);\n if (!fs.existsSync(resolved)) {\n return undefined;\n }\n return fs.statSync(resolved).isDirectory() ? findConfig(resolved) : resolved;\n }\n\n load(configPath: string): Promise<RedoclyConfig> {\n return loadedConfig(() => loadConfig({ configPath }), configPath);\n }\n\n createDefault(): Promise<RedoclyConfig> {\n const create = () =>\n createConfig(\n { rules: { \"operation-operationId-unique\": { severity: \"warn\" } } },\n { extends: [\"minimal\"] },\n );\n return loadedConfig(create, undefined);\n }\n}\n\n/** Find and load `redocly.yaml` relative to the directory `cwd`. */\nexport function createRedoclyConfigSource(cwd: string): RedoclyConfigSource {\n return new RedoclyConfigFiles(cwd);\n}\n"],"names":[],"mappings":";;;;;AAaA,SAAS,SAAS,KAAA,EAAwB;AACxC,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,cAAc,EAAE,CAAA;AAC/C;AAOA,eAAe,YAAA,CACb,MACA,UAAA,EACwB;AACxB,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,IAAA,EAAK;AAAA,EACpB,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,MAAA,GACJ,UAAA,KAAe,MAAA,GAAY,4BAAA,GAA+B,sBAAsB,UAAU,CAAA,CAAA;AAC5F,IAAA,MAAM,KAAA,GAAQ,UAAA,KAAe,MAAA,GAAY,0BAAA,GAA6B,YAAA;AACtE,IAAA,MAAM,IAAI,oBAAA,CAAqB;AAAA,MAC7B,MAAM,WAAA,CAAY,kBAAA;AAAA,MAClB,OAAA,EAAS,CAAA,YAAA,EAAe,MAAM,CAAA,QAAA,EAAW,WAAW,IAAI,KAAK,CAAA,EAAA,EAAK,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAAA,MACjF,OAAA,EAAS,EAAE,UAAA,EAAW;AAAA,MACtB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACF;AAGA,MAAM,kBAAA,CAAkD;AAAA,EAC7C,IAAA;AAAA,EAET,YAAY,GAAA,EAAa;AACvB,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA,EAEA,KAAK,YAAA,EAAsD;AACzD,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,OAAO,UAAA,CAAW,KAAK,IAAI,CAAA;AAAA,IAC7B;AACA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,MAAM,YAAY,CAAA;AACrD,IAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,EAAA,CAAG,SAAS,QAAQ,CAAA,CAAE,aAAY,GAAI,UAAA,CAAW,QAAQ,CAAA,GAAI,QAAA;AAAA,EACtE;AAAA,EAEA,KAAK,UAAA,EAA4C;AAC/C,IAAA,OAAO,aAAa,MAAM,UAAA,CAAW,EAAE,UAAA,EAAY,GAAG,UAAU,CAAA;AAAA,EAClE;AAAA,EAEA,aAAA,GAAwC;AACtC,IAAA,MAAM,SAAS,MACb,YAAA;AAAA,MACE,EAAE,OAAO,EAAE,8BAAA,EAAgC,EAAE,QAAA,EAAU,MAAA,IAAS,EAAE;AAAA,MAClE,EAAE,OAAA,EAAS,CAAC,SAAS,CAAA;AAAE,KACzB;AACF,IAAA,OAAO,YAAA,CAAa,QAAQ,MAAS,CAAA;AAAA,EACvC;AACF;AAGO,SAAS,0BAA0B,GAAA,EAAkC;AAC1E,EAAA,OAAO,IAAI,mBAAmB,GAAG,CAAA;AACnC;;"}
1
+ {"version":3,"file":"redocly-config-source.mjs","sources":["../../../src/adapters/redocly-loader/redocly-config-source.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n createConfig,\n findConfig,\n getMergedConfig,\n IGNORE_FILE,\n loadConfig,\n} from \"@redocly/openapi-core\";\nimport type {\n RedoclyConfig,\n RedoclyConfigSource,\n} from \"../../application/ports/redocly-config-source.js\";\nimport { ERROR_CODES, OpenAPITypesafeError } from \"../../domain/errors.js\";\n\n/**\n * The first line of a failure, with the error's name: a YAML error goes on with a snippet of the\n * file, and the name tells a YAML error from a file system one.\n */\nfunction reasonOf(error: unknown): string {\n return String(error).replace(/\\n[\\s\\S]*$/, \"\");\n}\n\n/**\n * The config, or `CLI_CONFIG_INVALID`. Redocly reads the config file and the lint-ignore file\n * next to it (or in the working directory); either can be malformed, and its error does not\n * always name the file, so the message names both.\n */\nasync function loadedConfig(\n load: () => Promise<RedoclyConfig>,\n configPath: string | undefined,\n): Promise<RedoclyConfig> {\n try {\n return await load();\n } catch (error) {\n const config =\n configPath === undefined ? \"the default Redocly config\" : `the Redocly config ${configPath}`;\n const where = configPath === undefined ? \"in the working directory\" : \"next to it\";\n throw new OpenAPITypesafeError({\n code: ERROR_CODES.CLI_CONFIG_INVALID,\n message: `Cannot load ${config} or the ${IGNORE_FILE} ${where}: ${reasonOf(error)}`,\n details: { configPath },\n cause: error,\n });\n }\n}\n\n/** The Redocly config port on `@redocly/openapi-core`, resolving paths from `cwd`. */\nclass RedoclyConfigFiles implements RedoclyConfigSource {\n readonly #cwd: string;\n\n constructor(cwd: string) {\n this.#cwd = cwd;\n }\n\n find(explicitPath: string | undefined): string | undefined {\n if (!explicitPath) {\n return findConfig(this.#cwd);\n }\n const resolved = path.resolve(this.#cwd, explicitPath);\n if (!fs.existsSync(resolved)) {\n return undefined;\n }\n return fs.statSync(resolved).isDirectory() ? findConfig(resolved) : resolved;\n }\n\n load(configPath: string): Promise<RedoclyConfig> {\n return loadedConfig(() => loadConfig({ configPath }), configPath);\n }\n\n createDefault(): Promise<RedoclyConfig> {\n const create = () =>\n createConfig(\n { rules: { \"operation-operationId-unique\": { severity: \"warn\" } } },\n { extends: [\"minimal\"] },\n );\n return loadedConfig(create, undefined);\n }\n\n forApi(config: RedoclyConfig, apiName: string): RedoclyConfig {\n return getMergedConfig(config, apiName);\n }\n}\n\n/** Find and load `redocly.yaml` relative to the directory `cwd`. */\nexport function createRedoclyConfigSource(cwd: string): RedoclyConfigSource {\n return new RedoclyConfigFiles(cwd);\n}\n"],"names":[],"mappings":";;;;;AAmBA,SAAS,SAAS,KAAA,EAAwB;AACxC,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,cAAc,EAAE,CAAA;AAC/C;AAOA,eAAe,YAAA,CACb,MACA,UAAA,EACwB;AACxB,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,IAAA,EAAK;AAAA,EACpB,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,MAAA,GACJ,UAAA,KAAe,MAAA,GAAY,4BAAA,GAA+B,sBAAsB,UAAU,CAAA,CAAA;AAC5F,IAAA,MAAM,KAAA,GAAQ,UAAA,KAAe,MAAA,GAAY,0BAAA,GAA6B,YAAA;AACtE,IAAA,MAAM,IAAI,oBAAA,CAAqB;AAAA,MAC7B,MAAM,WAAA,CAAY,kBAAA;AAAA,MAClB,OAAA,EAAS,CAAA,YAAA,EAAe,MAAM,CAAA,QAAA,EAAW,WAAW,IAAI,KAAK,CAAA,EAAA,EAAK,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAAA,MACjF,OAAA,EAAS,EAAE,UAAA,EAAW;AAAA,MACtB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACF;AAGA,MAAM,kBAAA,CAAkD;AAAA,EAC7C,IAAA;AAAA,EAET,YAAY,GAAA,EAAa;AACvB,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA,EAEA,KAAK,YAAA,EAAsD;AACzD,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,OAAO,UAAA,CAAW,KAAK,IAAI,CAAA;AAAA,IAC7B;AACA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,MAAM,YAAY,CAAA;AACrD,IAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC5B,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,EAAA,CAAG,SAAS,QAAQ,CAAA,CAAE,aAAY,GAAI,UAAA,CAAW,QAAQ,CAAA,GAAI,QAAA;AAAA,EACtE;AAAA,EAEA,KAAK,UAAA,EAA4C;AAC/C,IAAA,OAAO,aAAa,MAAM,UAAA,CAAW,EAAE,UAAA,EAAY,GAAG,UAAU,CAAA;AAAA,EAClE;AAAA,EAEA,aAAA,GAAwC;AACtC,IAAA,MAAM,SAAS,MACb,YAAA;AAAA,MACE,EAAE,OAAO,EAAE,8BAAA,EAAgC,EAAE,QAAA,EAAU,MAAA,IAAS,EAAE;AAAA,MAClE,EAAE,OAAA,EAAS,CAAC,SAAS,CAAA;AAAE,KACzB;AACF,IAAA,OAAO,YAAA,CAAa,QAAQ,MAAS,CAAA;AAAA,EACvC;AAAA,EAEA,MAAA,CAAO,QAAuB,OAAA,EAAgC;AAC5D,IAAA,OAAO,eAAA,CAAgB,QAAQ,OAAO,CAAA;AAAA,EACxC;AACF;AAGO,SAAS,0BAA0B,GAAA,EAAkC;AAC1E,EAAA,OAAO,IAAI,mBAAmB,GAAG,CAAA;AACnC;;"}
@@ -104,17 +104,20 @@ class RedoclySchemaLoader {
104
104
  }
105
105
  #resolver(request) {
106
106
  const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;
107
+ const redirects = new remoteFetch.RedirectedUrls();
107
108
  const config = remoteFetch.resolveConfigWithAccess(redoc.resolve, {
108
109
  timeoutMs: remoteRefTimeoutMs,
109
110
  access: request.refAccess,
110
111
  rootUrl: request.source instanceof URL ? request.source.href : void 0,
111
112
  maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
112
- bodyLimits: remoteDocumentLimits
113
+ bodyLimits: remoteDocumentLimits,
114
+ redirects
113
115
  });
114
116
  return new refAccess.RefAccessResolver(config, {
115
117
  access: request.refAccess,
116
118
  maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
117
- maxRemoteNodes: remoteDocumentLimits.maxNodes
119
+ maxRemoteNodes: remoteDocumentLimits.maxNodes,
120
+ redirects
118
121
  });
119
122
  }
120
123
  async #lintAndBundle(document, resolver, shouldLint) {
@@ -147,8 +150,8 @@ class RedoclySchemaLoader {
147
150
  });
148
151
  bundledMappingCopies.removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);
149
152
  bundledRefRepair.repairBundledRefs(bundled.bundle.parsed);
150
- const documentRefs = resolver.getFiles();
151
- const renaming = queryNamedComponents.renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documentRefs);
153
+ const documents = await loadedDocuments.loadedDocuments(document, resolver);
154
+ const renaming = queryNamedComponents.renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);
152
155
  this.#report(bundled.problems, document.source.absoluteRef, renaming);
153
156
  this.#options.logger.debug("Bundled schema", "bundle", this.#options.now() - startedAt);
154
157
  return bundled.bundle.parsed;
@@ -156,11 +159,13 @@ class RedoclySchemaLoader {
156
159
  /**
157
160
  * Report every problem; then throw if any of them is an error. A problem that the Redocly
158
161
  * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out
159
- * (upstream #2189).
162
+ * (upstream #2189). A name collision that the renamed components no longer have is dropped.
160
163
  */
161
164
  #report(problems, rootRef, renaming = UNRENAMED) {
162
165
  const errorMessages = [];
163
- for (const problem of problems) {
166
+ for (const problem of problems.filter(
167
+ (found) => !renaming.isResolvedCollision(found.message)
168
+ )) {
164
169
  if (problem.ignored === true) {
165
170
  this.#options.logger.debug(
166
171
  `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,
@@ -184,7 +189,11 @@ class RedoclySchemaLoader {
184
189
  function lintWarningText(message, ruleId) {
185
190
  return warnings.warningText(`${message}, rule ${ruleId}`, warnings.WARNING_CODES.LINT_PROBLEM);
186
191
  }
187
- const UNRENAMED = { pointer: (pointer) => pointer, text: (text) => text };
192
+ const UNRENAMED = {
193
+ pointer: (pointer) => pointer,
194
+ text: (text) => text,
195
+ isResolvedCollision: () => false
196
+ };
188
197
  function locationText(location, renaming, rootRef) {
189
198
  const pointer = renaming.pointer(location.pointer);
190
199
  const documentRef = location.source.absoluteRef;
@@ -1 +1 @@
1
- {"version":3,"file":"redocly-schema-loader.cjs","sources":["../../../src/adapters/redocly-loader/redocly-schema-loader.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n bundle,\n type Document,\n type LocationObject,\n lintDocument,\n type NormalizedProblem,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport type { Logger } from \"../../application/ports/logger.js\";\nimport type {\n LoadedSchema,\n SchemaLoader,\n SchemaLoadRequest,\n} from \"../../application/ports/schema-loader.js\";\nimport { withStackOverflowReportedAsync } from \"../../application/stack-overflow.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { WARNING_CODES, warningText } from \"../../domain/warnings.js\";\nimport type { OpenAPI3 } from \"../../types.js\";\nimport { removeBundledMappingCopies, schemaKeysOf } from \"./bundled-mapping-copies.js\";\nimport { repairBundledRefs } from \"./bundled-ref-repair.js\";\nimport { loadedDocuments, localFilesRead } from \"./loaded-documents.js\";\nimport { findMalformedRefs, malformedRefsError } from \"./malformed-refs.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport {\n type ComponentRenaming,\n componentKeysOf,\n renameQueryNamedComponents,\n} from \"./query-named-components.js\";\nimport { RefAccessResolver } from \"./ref-access.js\";\nimport { checkRefsResolve } from \"./ref-resolution-check.js\";\nimport { REF_CYCLE_MESSAGE } from \"./ref-resolution-steps.js\";\nimport { type RemoteDocumentLimits, resolveConfigWithAccess } from \"./remote-fetch.js\";\nimport {\n loadFailedError,\n refCycleError,\n schemaInvalidError,\n versionUnsupportedError,\n} from \"./schema-load-errors.js\";\nimport { parseSchema } from \"./schema-source.js\";\nimport { findUnsupportedKeys, unsupportedKeysError } from \"./unsupported-keys.js\";\n\n/**\n * The SchemaLoader port on Redocly 1.34 (ADR-005): parse any inherited source type, then lint and\n * bundle with the given Redocly config. Problems go to the logger; errors fail the load.\n */\nexport interface RedoclySchemaLoaderOptions {\n readonly redoc: RedoclyConfig;\n readonly logger: Logger;\n /** A monotonic time in milliseconds, for the debug durations. */\n readonly now: () => number;\n /** Time limit of one remote fetch, unless the Redocly config brings its own `customFetch`. */\n readonly remoteRefTimeoutMs: number;\n readonly remoteDocumentLimits: RemoteDocumentLimits;\n}\n\nconst UNSUPPORTED_FORMAT_MESSAGE = \"Unsupported schema format, expected `openapi: 3.x`\";\n\n/**\n * Accept OpenAPI 3.x only; Swagger 2 and other formats get their own message. An empty file or a\n * `null` root parses to nothing, so it is checked before any field is read.\n */\nfunction assertOpenAPI3(document: unknown): void {\n if (typeof document !== \"object\" || document === null) {\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, {});\n }\n const parsed = document as Record<string, unknown>;\n const openapiVersion = Number.parseFloat(parsed.openapi as string);\n const isOpenAPI3 =\n !parsed.swagger &&\n Boolean(parsed.openapi) &&\n !Number.isNaN(openapiVersion) &&\n openapiVersion >= 3 &&\n openapiVersion < 4;\n if (isOpenAPI3) {\n return;\n }\n if (parsed.swagger) {\n throw versionUnsupportedError(\n \"Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.\",\n parsed,\n );\n }\n if (parsed.openapi || openapiVersion < 3 || openapiVersion >= 4) {\n throw versionUnsupportedError(`Unsupported OpenAPI version: ${parsed.openapi}`, parsed);\n }\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, parsed);\n}\n\n/**\n * The name an inline source (a string, object, buffer or stream) gets inside the working\n * directory. Redocly resolves relative `$ref`s against the directory of the document, so the\n * document must be named as a file in the working directory, not as the directory itself.\n */\nconst INLINE_SOURCE_NAME = \"inline-source\";\n\n/** The base for relative `$ref`s: the source file or URL itself, else a file in the cwd. */\nfunction sourceAbsoluteRef(source: unknown, cwd: URL): string {\n const document = source instanceof URL ? source : new URL(INLINE_SOURCE_NAME, cwd);\n return document.protocol === \"file:\" ? sourceFilePath(document) : document.href;\n}\n\n/**\n * The path of a `file:` source. A URL that names no path on this platform (an encoded slash, a\n * host on POSIX, no drive letter on Windows) cannot be read, so it fails as a read would.\n */\nfunction sourceFilePath(source: URL): string {\n try {\n return fileURLToPath(source);\n } catch (error) {\n // Node.js reports these as TypeErrors (ERR_INVALID_FILE_URL_PATH, ERR_INVALID_FILE_URL_HOST).\n if (!(error instanceof TypeError)) {\n throw error;\n }\n throw loadFailedError(source.href, error);\n }\n}\n\n/** Report the document problems Redocly throws as typed errors; anything else passes through. */\nasync function rethrowDocumentError(\n error: unknown,\n root: Document,\n resolver: BaseResolver,\n): Promise<never> {\n if (error instanceof Error && error.message === REF_CYCLE_MESSAGE) {\n throw refCycleError(error);\n }\n if (error instanceof URIError) {\n throw malformedRefsError(findMalformedRefs(await loadedDocuments(root, resolver)), error);\n }\n if (error instanceof TypeError) {\n const keys = findUnsupportedKeys(await loadedDocuments(root, resolver));\n throw keys.length === 0 ? error : unsupportedKeysError(keys, error);\n }\n throw error;\n}\n\nclass RedoclySchemaLoader implements SchemaLoader {\n readonly #options: RedoclySchemaLoaderOptions;\n\n constructor(options: RedoclySchemaLoaderOptions) {\n this.#options = options;\n }\n\n async load(request: SchemaLoadRequest): Promise<LoadedSchema> {\n const resolver = this.#resolver(request);\n // Parsing is guarded too: `JSON.stringify` of a deep object source overflows the stack on\n // Node.js 22 and 24, before linting starts.\n const bundled = await withStackOverflowReportedAsync(\"load\", () =>\n this.#parseAndBundle(request, resolver),\n ).catch((error: unknown) => {\n throw resolver.firstRefusal ?? error;\n });\n // A refused `$ref` is an unresolved-`$ref` problem, which a Redocly config can turn off.\n if (resolver.firstRefusal !== undefined) {\n throw resolver.firstRefusal;\n }\n return bundled;\n }\n\n async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<LoadedSchema> {\n const { logger, now } = this.#options;\n const configLoadedAt = now();\n logger.debug(\"Loaded Redoc config\", \"redoc\", now() - configLoadedAt);\n const parseStartedAt = now();\n const absoluteRef = sourceAbsoluteRef(request.source, request.cwd);\n const document = await parseSchema(request.source, { absoluteRef, resolver });\n logger.debug(\"Parsed schema\", \"redoc\", now() - parseStartedAt);\n assertOpenAPI3(document.parsed);\n pointMappingNamesAtSchemas(document.parsed);\n const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);\n const isRootAFile = request.source instanceof URL && request.source.protocol === \"file:\";\n const localFiles = await localFilesRead(document, resolver, isRootAFile);\n return { document: bundled, localFiles };\n }\n\n #resolver(request: SchemaLoadRequest): RefAccessResolver {\n const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;\n const config = resolveConfigWithAccess(redoc.resolve, {\n timeoutMs: remoteRefTimeoutMs,\n access: request.refAccess,\n rootUrl: request.source instanceof URL ? request.source.href : undefined,\n maxParallelFetches: remoteDocumentLimits.maxParallelFetches,\n bodyLimits: remoteDocumentLimits,\n });\n return new RefAccessResolver(config, {\n access: request.refAccess,\n maxRemoteDocuments: remoteDocumentLimits.maxDocuments,\n maxRemoteNodes: remoteDocumentLimits.maxNodes,\n });\n }\n\n async #lintAndBundle(\n document: Document,\n resolver: BaseResolver,\n shouldLint: boolean,\n ): Promise<OpenAPI3> {\n const rethrow = (error: unknown) => rethrowDocumentError(error, document, resolver);\n // Redocly leaves unhandled rejections behind when a `$ref` fails in two documents (ADR-031).\n await checkRefsResolve(document, resolver, this.#options.redoc.styleguide).catch(rethrow);\n if (shouldLint) {\n await this.#lint(document, resolver).catch(rethrow);\n }\n return this.#bundle(document, resolver).catch(rethrow);\n }\n\n async #lint(document: Document, resolver: BaseResolver): Promise<void> {\n const startedAt = this.#options.now();\n const problems = await lintDocument({\n document,\n config: this.#options.redoc.styleguide,\n externalRefResolver: resolver,\n });\n this.#report(problems, document.source.absoluteRef);\n this.#options.logger.debug(\"Linted schema\", \"lint\", this.#options.now() - startedAt);\n }\n\n async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {\n const startedAt = this.#options.now();\n const schemaKeys = schemaKeysOf(document.parsed);\n const componentKeys = componentKeysOf(document.parsed);\n // The lint resolver: documents it already loaded are not fetched again.\n const bundled = await bundle({\n config: this.#options.redoc,\n dereference: false,\n doc: document,\n externalRefResolver: resolver,\n });\n removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);\n repairBundledRefs(bundled.bundle.parsed);\n const documentRefs = resolver.getFiles();\n const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documentRefs);\n // The problems name the components the bundle made, so they are shown by their new names.\n this.#report(bundled.problems, document.source.absoluteRef, renaming);\n this.#options.logger.debug(\"Bundled schema\", \"bundle\", this.#options.now() - startedAt);\n return bundled.bundle.parsed;\n }\n\n /**\n * Report every problem; then throw if any of them is an error. A problem that the Redocly\n * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out\n * (upstream #2189).\n */\n #report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {\n const errorMessages: string[] = [];\n for (const problem of problems) {\n if (problem.ignored === true) {\n this.#options.logger.debug(\n `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,\n \"lint\",\n );\n continue;\n }\n const message = problemMessage(problem, renaming, rootRef);\n if (problem.severity === \"error\") {\n errorMessages.push(message);\n this.#options.logger.error(message);\n } else {\n this.#options.logger.warn(lintWarningText(message, problem.ruleId));\n }\n }\n if (errorMessages.length > 0) {\n throw schemaInvalidError(errorMessages);\n }\n }\n}\n\n/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */\nfunction lintWarningText(message: string, ruleId: string): string {\n return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);\n}\n\n/** Problems found before the bundle named any component. */\nconst UNRENAMED: ComponentRenaming = { pointer: (pointer) => pointer, text: (text) => text };\n\n/** A problem location that names a JSON Pointer, not a line and column. */\ntype PointerLocation = Extract<LocationObject, { pointer: string }>;\n\n/**\n * Where a problem is: its pointer, and the document when it is not the root (URL secrets left\n * out).\n */\nfunction locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {\n const pointer = renaming.pointer(location.pointer);\n const documentRef = location.source.absoluteRef;\n return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;\n}\n\nfunction problemMessage(\n problem: NormalizedProblem,\n renaming: ComponentRenaming,\n rootRef: string,\n): string {\n const location = problem.location?.[0];\n const message = renaming.text(problem.message);\n return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;\n}\n\n/** A schema loader with one Redocly config and logger, for one or more loads. */\nexport function createRedoclySchemaLoader(options: RedoclySchemaLoaderOptions): SchemaLoader {\n return new RedoclySchemaLoader(options);\n}\n"],"names":["versionUnsupportedError","fileURLToPath","loadFailedError","REF_CYCLE_MESSAGE","refCycleError","malformedRefsError","findMalformedRefs","loadedDocuments","findUnsupportedKeys","unsupportedKeysError","withStackOverflowReportedAsync","parseSchema","pointMappingNamesAtSchemas","localFilesRead","resolveConfigWithAccess","RefAccessResolver","checkRefsResolve","lintDocument","schemaKeysOf","componentKeysOf","bundle","removeBundledMappingCopies","repairBundledRefs","renameQueryNamedComponents","schemaInvalidError","warningText","WARNING_CODES","redactUrlSecrets"],"mappings":"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"}
1
+ {"version":3,"file":"redocly-schema-loader.cjs","sources":["../../../src/adapters/redocly-loader/redocly-schema-loader.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n bundle,\n type Document,\n type LocationObject,\n lintDocument,\n type NormalizedProblem,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport type { Logger } from \"../../application/ports/logger.js\";\nimport type {\n LoadedSchema,\n SchemaLoader,\n SchemaLoadRequest,\n} from \"../../application/ports/schema-loader.js\";\nimport { withStackOverflowReportedAsync } from \"../../application/stack-overflow.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { WARNING_CODES, warningText } from \"../../domain/warnings.js\";\nimport type { OpenAPI3 } from \"../../types.js\";\nimport { removeBundledMappingCopies, schemaKeysOf } from \"./bundled-mapping-copies.js\";\nimport { repairBundledRefs } from \"./bundled-ref-repair.js\";\nimport { loadedDocuments, localFilesRead } from \"./loaded-documents.js\";\nimport { findMalformedRefs, malformedRefsError } from \"./malformed-refs.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport {\n type ComponentRenaming,\n componentKeysOf,\n renameQueryNamedComponents,\n} from \"./query-named-components.js\";\nimport { RefAccessResolver } from \"./ref-access.js\";\nimport { checkRefsResolve } from \"./ref-resolution-check.js\";\nimport { REF_CYCLE_MESSAGE } from \"./ref-resolution-steps.js\";\nimport {\n RedirectedUrls,\n type RemoteDocumentLimits,\n resolveConfigWithAccess,\n} from \"./remote-fetch.js\";\nimport {\n loadFailedError,\n refCycleError,\n schemaInvalidError,\n versionUnsupportedError,\n} from \"./schema-load-errors.js\";\nimport { parseSchema } from \"./schema-source.js\";\nimport { findUnsupportedKeys, unsupportedKeysError } from \"./unsupported-keys.js\";\n\n/**\n * The SchemaLoader port on Redocly 1.34 (ADR-005): parse any inherited source type, then lint and\n * bundle with the given Redocly config. Problems go to the logger; errors fail the load.\n */\nexport interface RedoclySchemaLoaderOptions {\n readonly redoc: RedoclyConfig;\n readonly logger: Logger;\n /** A monotonic time in milliseconds, for the debug durations. */\n readonly now: () => number;\n /** Time limit of one remote fetch, unless the Redocly config brings its own `customFetch`. */\n readonly remoteRefTimeoutMs: number;\n readonly remoteDocumentLimits: RemoteDocumentLimits;\n}\n\nconst UNSUPPORTED_FORMAT_MESSAGE = \"Unsupported schema format, expected `openapi: 3.x`\";\n\n/**\n * Accept OpenAPI 3.x only; Swagger 2 and other formats get their own message. An empty file or a\n * `null` root parses to nothing, so it is checked before any field is read.\n */\nfunction assertOpenAPI3(document: unknown): void {\n if (typeof document !== \"object\" || document === null) {\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, {});\n }\n const parsed = document as Record<string, unknown>;\n const openapiVersion = Number.parseFloat(parsed.openapi as string);\n const isOpenAPI3 =\n !parsed.swagger &&\n Boolean(parsed.openapi) &&\n !Number.isNaN(openapiVersion) &&\n openapiVersion >= 3 &&\n openapiVersion < 4;\n if (isOpenAPI3) {\n return;\n }\n if (parsed.swagger) {\n throw versionUnsupportedError(\n \"Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.\",\n parsed,\n );\n }\n if (parsed.openapi || openapiVersion < 3 || openapiVersion >= 4) {\n throw versionUnsupportedError(`Unsupported OpenAPI version: ${parsed.openapi}`, parsed);\n }\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, parsed);\n}\n\n/**\n * The name an inline source (a string, object, buffer or stream) gets inside the working\n * directory. Redocly resolves relative `$ref`s against the directory of the document, so the\n * document must be named as a file in the working directory, not as the directory itself.\n */\nconst INLINE_SOURCE_NAME = \"inline-source\";\n\n/** The base for relative `$ref`s: the source file or URL itself, else a file in the cwd. */\nfunction sourceAbsoluteRef(source: unknown, cwd: URL): string {\n const document = source instanceof URL ? source : new URL(INLINE_SOURCE_NAME, cwd);\n return document.protocol === \"file:\" ? sourceFilePath(document) : document.href;\n}\n\n/**\n * The path of a `file:` source. A URL that names no path on this platform (an encoded slash, a\n * host on POSIX, no drive letter on Windows) cannot be read, so it fails as a read would.\n */\nfunction sourceFilePath(source: URL): string {\n try {\n return fileURLToPath(source);\n } catch (error) {\n // Node.js reports these as TypeErrors (ERR_INVALID_FILE_URL_PATH, ERR_INVALID_FILE_URL_HOST).\n if (!(error instanceof TypeError)) {\n throw error;\n }\n throw loadFailedError(source.href, error);\n }\n}\n\n/** Report the document problems Redocly throws as typed errors; anything else passes through. */\nasync function rethrowDocumentError(\n error: unknown,\n root: Document,\n resolver: BaseResolver,\n): Promise<never> {\n if (error instanceof Error && error.message === REF_CYCLE_MESSAGE) {\n throw refCycleError(error);\n }\n if (error instanceof URIError) {\n throw malformedRefsError(findMalformedRefs(await loadedDocuments(root, resolver)), error);\n }\n if (error instanceof TypeError) {\n const keys = findUnsupportedKeys(await loadedDocuments(root, resolver));\n throw keys.length === 0 ? error : unsupportedKeysError(keys, error);\n }\n throw error;\n}\n\nclass RedoclySchemaLoader implements SchemaLoader {\n readonly #options: RedoclySchemaLoaderOptions;\n\n constructor(options: RedoclySchemaLoaderOptions) {\n this.#options = options;\n }\n\n async load(request: SchemaLoadRequest): Promise<LoadedSchema> {\n const resolver = this.#resolver(request);\n // Parsing is guarded too: `JSON.stringify` of a deep object source overflows the stack on\n // Node.js 22 and 24, before linting starts.\n const bundled = await withStackOverflowReportedAsync(\"load\", () =>\n this.#parseAndBundle(request, resolver),\n ).catch((error: unknown) => {\n throw resolver.firstRefusal ?? error;\n });\n // A refused `$ref` is an unresolved-`$ref` problem, which a Redocly config can turn off.\n if (resolver.firstRefusal !== undefined) {\n throw resolver.firstRefusal;\n }\n return bundled;\n }\n\n async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<LoadedSchema> {\n const { logger, now } = this.#options;\n const configLoadedAt = now();\n logger.debug(\"Loaded Redoc config\", \"redoc\", now() - configLoadedAt);\n const parseStartedAt = now();\n const absoluteRef = sourceAbsoluteRef(request.source, request.cwd);\n const document = await parseSchema(request.source, { absoluteRef, resolver });\n logger.debug(\"Parsed schema\", \"redoc\", now() - parseStartedAt);\n assertOpenAPI3(document.parsed);\n pointMappingNamesAtSchemas(document.parsed);\n const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);\n const isRootAFile = request.source instanceof URL && request.source.protocol === \"file:\";\n const localFiles = await localFilesRead(document, resolver, isRootAFile);\n return { document: bundled, localFiles };\n }\n\n #resolver(request: SchemaLoadRequest): RefAccessResolver {\n const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;\n // One record per load, as the resolver and its cache are.\n const redirects = new RedirectedUrls();\n const config = resolveConfigWithAccess(redoc.resolve, {\n timeoutMs: remoteRefTimeoutMs,\n access: request.refAccess,\n rootUrl: request.source instanceof URL ? request.source.href : undefined,\n maxParallelFetches: remoteDocumentLimits.maxParallelFetches,\n bodyLimits: remoteDocumentLimits,\n redirects,\n });\n return new RefAccessResolver(config, {\n access: request.refAccess,\n maxRemoteDocuments: remoteDocumentLimits.maxDocuments,\n maxRemoteNodes: remoteDocumentLimits.maxNodes,\n redirects,\n });\n }\n\n async #lintAndBundle(\n document: Document,\n resolver: BaseResolver,\n shouldLint: boolean,\n ): Promise<OpenAPI3> {\n const rethrow = (error: unknown) => rethrowDocumentError(error, document, resolver);\n // Redocly leaves unhandled rejections behind when a `$ref` fails in two documents (ADR-031).\n await checkRefsResolve(document, resolver, this.#options.redoc.styleguide).catch(rethrow);\n if (shouldLint) {\n await this.#lint(document, resolver).catch(rethrow);\n }\n return this.#bundle(document, resolver).catch(rethrow);\n }\n\n async #lint(document: Document, resolver: BaseResolver): Promise<void> {\n const startedAt = this.#options.now();\n const problems = await lintDocument({\n document,\n config: this.#options.redoc.styleguide,\n externalRefResolver: resolver,\n });\n this.#report(problems, document.source.absoluteRef);\n this.#options.logger.debug(\"Linted schema\", \"lint\", this.#options.now() - startedAt);\n }\n\n async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {\n const startedAt = this.#options.now();\n const schemaKeys = schemaKeysOf(document.parsed);\n const componentKeys = componentKeysOf(document.parsed);\n // The lint resolver: documents it already loaded are not fetched again.\n const bundled = await bundle({\n config: this.#options.redoc,\n dereference: false,\n doc: document,\n externalRefResolver: resolver,\n });\n removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);\n repairBundledRefs(bundled.bundle.parsed);\n const documents = await loadedDocuments(document, resolver);\n const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);\n // The problems name the components the bundle made, so they are shown by their new names.\n this.#report(bundled.problems, document.source.absoluteRef, renaming);\n this.#options.logger.debug(\"Bundled schema\", \"bundle\", this.#options.now() - startedAt);\n return bundled.bundle.parsed;\n }\n\n /**\n * Report every problem; then throw if any of them is an error. A problem that the Redocly\n * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out\n * (upstream #2189). A name collision that the renamed components no longer have is dropped.\n */\n #report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {\n const errorMessages: string[] = [];\n for (const problem of problems.filter(\n (found) => !renaming.isResolvedCollision(found.message),\n )) {\n if (problem.ignored === true) {\n this.#options.logger.debug(\n `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,\n \"lint\",\n );\n continue;\n }\n const message = problemMessage(problem, renaming, rootRef);\n if (problem.severity === \"error\") {\n errorMessages.push(message);\n this.#options.logger.error(message);\n } else {\n this.#options.logger.warn(lintWarningText(message, problem.ruleId));\n }\n }\n if (errorMessages.length > 0) {\n throw schemaInvalidError(errorMessages);\n }\n }\n}\n\n/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */\nfunction lintWarningText(message: string, ruleId: string): string {\n return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);\n}\n\n/** Problems found before the bundle named any component. */\nconst UNRENAMED: ComponentRenaming = {\n pointer: (pointer) => pointer,\n text: (text) => text,\n isResolvedCollision: () => false,\n};\n\n/** A problem location that names a JSON Pointer, not a line and column. */\ntype PointerLocation = Extract<LocationObject, { pointer: string }>;\n\n/**\n * Where a problem is: its pointer, and the document when it is not the root (URL secrets left\n * out).\n */\nfunction locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {\n const pointer = renaming.pointer(location.pointer);\n const documentRef = location.source.absoluteRef;\n return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;\n}\n\nfunction problemMessage(\n problem: NormalizedProblem,\n renaming: ComponentRenaming,\n rootRef: string,\n): string {\n const location = problem.location?.[0];\n const message = renaming.text(problem.message);\n return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;\n}\n\n/** A schema loader with one Redocly config and logger, for one or more loads. */\nexport function createRedoclySchemaLoader(options: RedoclySchemaLoaderOptions): SchemaLoader {\n return new RedoclySchemaLoader(options);\n}\n"],"names":["versionUnsupportedError","fileURLToPath","loadFailedError","REF_CYCLE_MESSAGE","refCycleError","malformedRefsError","findMalformedRefs","loadedDocuments","findUnsupportedKeys","unsupportedKeysError","withStackOverflowReportedAsync","parseSchema","pointMappingNamesAtSchemas","localFilesRead","RedirectedUrls","resolveConfigWithAccess","RefAccessResolver","checkRefsResolve","lintDocument","schemaKeysOf","componentKeysOf","bundle","removeBundledMappingCopies","repairBundledRefs","renameQueryNamedComponents","schemaInvalidError","warningText","WARNING_CODES","redactUrlSecrets"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { componentKeysOf, renameQueryNamedComponents } from './query-named-compo
12
12
  import { RefAccessResolver } from './ref-access.mjs';
13
13
  import { checkRefsResolve } from './ref-resolution-check.mjs';
14
14
  import { REF_CYCLE_MESSAGE } from './ref-resolution-steps.mjs';
15
- import { resolveConfigWithAccess } from './remote-fetch.mjs';
15
+ import { resolveConfigWithAccess, RedirectedUrls } from './remote-fetch.mjs';
16
16
  import { schemaInvalidError, versionUnsupportedError, refCycleError, loadFailedError } from './schema-load-errors.mjs';
17
17
  import { parseSchema } from './schema-source.mjs';
18
18
  import { findUnsupportedKeys, unsupportedKeysError } from './unsupported-keys.mjs';
@@ -102,17 +102,20 @@ class RedoclySchemaLoader {
102
102
  }
103
103
  #resolver(request) {
104
104
  const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;
105
+ const redirects = new RedirectedUrls();
105
106
  const config = resolveConfigWithAccess(redoc.resolve, {
106
107
  timeoutMs: remoteRefTimeoutMs,
107
108
  access: request.refAccess,
108
109
  rootUrl: request.source instanceof URL ? request.source.href : void 0,
109
110
  maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
110
- bodyLimits: remoteDocumentLimits
111
+ bodyLimits: remoteDocumentLimits,
112
+ redirects
111
113
  });
112
114
  return new RefAccessResolver(config, {
113
115
  access: request.refAccess,
114
116
  maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
115
- maxRemoteNodes: remoteDocumentLimits.maxNodes
117
+ maxRemoteNodes: remoteDocumentLimits.maxNodes,
118
+ redirects
116
119
  });
117
120
  }
118
121
  async #lintAndBundle(document, resolver, shouldLint) {
@@ -145,8 +148,8 @@ class RedoclySchemaLoader {
145
148
  });
146
149
  removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);
147
150
  repairBundledRefs(bundled.bundle.parsed);
148
- const documentRefs = resolver.getFiles();
149
- const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documentRefs);
151
+ const documents = await loadedDocuments(document, resolver);
152
+ const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);
150
153
  this.#report(bundled.problems, document.source.absoluteRef, renaming);
151
154
  this.#options.logger.debug("Bundled schema", "bundle", this.#options.now() - startedAt);
152
155
  return bundled.bundle.parsed;
@@ -154,11 +157,13 @@ class RedoclySchemaLoader {
154
157
  /**
155
158
  * Report every problem; then throw if any of them is an error. A problem that the Redocly
156
159
  * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out
157
- * (upstream #2189).
160
+ * (upstream #2189). A name collision that the renamed components no longer have is dropped.
158
161
  */
159
162
  #report(problems, rootRef, renaming = UNRENAMED) {
160
163
  const errorMessages = [];
161
- for (const problem of problems) {
164
+ for (const problem of problems.filter(
165
+ (found) => !renaming.isResolvedCollision(found.message)
166
+ )) {
162
167
  if (problem.ignored === true) {
163
168
  this.#options.logger.debug(
164
169
  `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,
@@ -182,7 +187,11 @@ class RedoclySchemaLoader {
182
187
  function lintWarningText(message, ruleId) {
183
188
  return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);
184
189
  }
185
- const UNRENAMED = { pointer: (pointer) => pointer, text: (text) => text };
190
+ const UNRENAMED = {
191
+ pointer: (pointer) => pointer,
192
+ text: (text) => text,
193
+ isResolvedCollision: () => false
194
+ };
186
195
  function locationText(location, renaming, rootRef) {
187
196
  const pointer = renaming.pointer(location.pointer);
188
197
  const documentRef = location.source.absoluteRef;
@@ -1 +1 @@
1
- {"version":3,"file":"redocly-schema-loader.mjs","sources":["../../../src/adapters/redocly-loader/redocly-schema-loader.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n bundle,\n type Document,\n type LocationObject,\n lintDocument,\n type NormalizedProblem,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport type { Logger } from \"../../application/ports/logger.js\";\nimport type {\n LoadedSchema,\n SchemaLoader,\n SchemaLoadRequest,\n} from \"../../application/ports/schema-loader.js\";\nimport { withStackOverflowReportedAsync } from \"../../application/stack-overflow.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { WARNING_CODES, warningText } from \"../../domain/warnings.js\";\nimport type { OpenAPI3 } from \"../../types.js\";\nimport { removeBundledMappingCopies, schemaKeysOf } from \"./bundled-mapping-copies.js\";\nimport { repairBundledRefs } from \"./bundled-ref-repair.js\";\nimport { loadedDocuments, localFilesRead } from \"./loaded-documents.js\";\nimport { findMalformedRefs, malformedRefsError } from \"./malformed-refs.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport {\n type ComponentRenaming,\n componentKeysOf,\n renameQueryNamedComponents,\n} from \"./query-named-components.js\";\nimport { RefAccessResolver } from \"./ref-access.js\";\nimport { checkRefsResolve } from \"./ref-resolution-check.js\";\nimport { REF_CYCLE_MESSAGE } from \"./ref-resolution-steps.js\";\nimport { type RemoteDocumentLimits, resolveConfigWithAccess } from \"./remote-fetch.js\";\nimport {\n loadFailedError,\n refCycleError,\n schemaInvalidError,\n versionUnsupportedError,\n} from \"./schema-load-errors.js\";\nimport { parseSchema } from \"./schema-source.js\";\nimport { findUnsupportedKeys, unsupportedKeysError } from \"./unsupported-keys.js\";\n\n/**\n * The SchemaLoader port on Redocly 1.34 (ADR-005): parse any inherited source type, then lint and\n * bundle with the given Redocly config. Problems go to the logger; errors fail the load.\n */\nexport interface RedoclySchemaLoaderOptions {\n readonly redoc: RedoclyConfig;\n readonly logger: Logger;\n /** A monotonic time in milliseconds, for the debug durations. */\n readonly now: () => number;\n /** Time limit of one remote fetch, unless the Redocly config brings its own `customFetch`. */\n readonly remoteRefTimeoutMs: number;\n readonly remoteDocumentLimits: RemoteDocumentLimits;\n}\n\nconst UNSUPPORTED_FORMAT_MESSAGE = \"Unsupported schema format, expected `openapi: 3.x`\";\n\n/**\n * Accept OpenAPI 3.x only; Swagger 2 and other formats get their own message. An empty file or a\n * `null` root parses to nothing, so it is checked before any field is read.\n */\nfunction assertOpenAPI3(document: unknown): void {\n if (typeof document !== \"object\" || document === null) {\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, {});\n }\n const parsed = document as Record<string, unknown>;\n const openapiVersion = Number.parseFloat(parsed.openapi as string);\n const isOpenAPI3 =\n !parsed.swagger &&\n Boolean(parsed.openapi) &&\n !Number.isNaN(openapiVersion) &&\n openapiVersion >= 3 &&\n openapiVersion < 4;\n if (isOpenAPI3) {\n return;\n }\n if (parsed.swagger) {\n throw versionUnsupportedError(\n \"Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.\",\n parsed,\n );\n }\n if (parsed.openapi || openapiVersion < 3 || openapiVersion >= 4) {\n throw versionUnsupportedError(`Unsupported OpenAPI version: ${parsed.openapi}`, parsed);\n }\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, parsed);\n}\n\n/**\n * The name an inline source (a string, object, buffer or stream) gets inside the working\n * directory. Redocly resolves relative `$ref`s against the directory of the document, so the\n * document must be named as a file in the working directory, not as the directory itself.\n */\nconst INLINE_SOURCE_NAME = \"inline-source\";\n\n/** The base for relative `$ref`s: the source file or URL itself, else a file in the cwd. */\nfunction sourceAbsoluteRef(source: unknown, cwd: URL): string {\n const document = source instanceof URL ? source : new URL(INLINE_SOURCE_NAME, cwd);\n return document.protocol === \"file:\" ? sourceFilePath(document) : document.href;\n}\n\n/**\n * The path of a `file:` source. A URL that names no path on this platform (an encoded slash, a\n * host on POSIX, no drive letter on Windows) cannot be read, so it fails as a read would.\n */\nfunction sourceFilePath(source: URL): string {\n try {\n return fileURLToPath(source);\n } catch (error) {\n // Node.js reports these as TypeErrors (ERR_INVALID_FILE_URL_PATH, ERR_INVALID_FILE_URL_HOST).\n if (!(error instanceof TypeError)) {\n throw error;\n }\n throw loadFailedError(source.href, error);\n }\n}\n\n/** Report the document problems Redocly throws as typed errors; anything else passes through. */\nasync function rethrowDocumentError(\n error: unknown,\n root: Document,\n resolver: BaseResolver,\n): Promise<never> {\n if (error instanceof Error && error.message === REF_CYCLE_MESSAGE) {\n throw refCycleError(error);\n }\n if (error instanceof URIError) {\n throw malformedRefsError(findMalformedRefs(await loadedDocuments(root, resolver)), error);\n }\n if (error instanceof TypeError) {\n const keys = findUnsupportedKeys(await loadedDocuments(root, resolver));\n throw keys.length === 0 ? error : unsupportedKeysError(keys, error);\n }\n throw error;\n}\n\nclass RedoclySchemaLoader implements SchemaLoader {\n readonly #options: RedoclySchemaLoaderOptions;\n\n constructor(options: RedoclySchemaLoaderOptions) {\n this.#options = options;\n }\n\n async load(request: SchemaLoadRequest): Promise<LoadedSchema> {\n const resolver = this.#resolver(request);\n // Parsing is guarded too: `JSON.stringify` of a deep object source overflows the stack on\n // Node.js 22 and 24, before linting starts.\n const bundled = await withStackOverflowReportedAsync(\"load\", () =>\n this.#parseAndBundle(request, resolver),\n ).catch((error: unknown) => {\n throw resolver.firstRefusal ?? error;\n });\n // A refused `$ref` is an unresolved-`$ref` problem, which a Redocly config can turn off.\n if (resolver.firstRefusal !== undefined) {\n throw resolver.firstRefusal;\n }\n return bundled;\n }\n\n async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<LoadedSchema> {\n const { logger, now } = this.#options;\n const configLoadedAt = now();\n logger.debug(\"Loaded Redoc config\", \"redoc\", now() - configLoadedAt);\n const parseStartedAt = now();\n const absoluteRef = sourceAbsoluteRef(request.source, request.cwd);\n const document = await parseSchema(request.source, { absoluteRef, resolver });\n logger.debug(\"Parsed schema\", \"redoc\", now() - parseStartedAt);\n assertOpenAPI3(document.parsed);\n pointMappingNamesAtSchemas(document.parsed);\n const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);\n const isRootAFile = request.source instanceof URL && request.source.protocol === \"file:\";\n const localFiles = await localFilesRead(document, resolver, isRootAFile);\n return { document: bundled, localFiles };\n }\n\n #resolver(request: SchemaLoadRequest): RefAccessResolver {\n const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;\n const config = resolveConfigWithAccess(redoc.resolve, {\n timeoutMs: remoteRefTimeoutMs,\n access: request.refAccess,\n rootUrl: request.source instanceof URL ? request.source.href : undefined,\n maxParallelFetches: remoteDocumentLimits.maxParallelFetches,\n bodyLimits: remoteDocumentLimits,\n });\n return new RefAccessResolver(config, {\n access: request.refAccess,\n maxRemoteDocuments: remoteDocumentLimits.maxDocuments,\n maxRemoteNodes: remoteDocumentLimits.maxNodes,\n });\n }\n\n async #lintAndBundle(\n document: Document,\n resolver: BaseResolver,\n shouldLint: boolean,\n ): Promise<OpenAPI3> {\n const rethrow = (error: unknown) => rethrowDocumentError(error, document, resolver);\n // Redocly leaves unhandled rejections behind when a `$ref` fails in two documents (ADR-031).\n await checkRefsResolve(document, resolver, this.#options.redoc.styleguide).catch(rethrow);\n if (shouldLint) {\n await this.#lint(document, resolver).catch(rethrow);\n }\n return this.#bundle(document, resolver).catch(rethrow);\n }\n\n async #lint(document: Document, resolver: BaseResolver): Promise<void> {\n const startedAt = this.#options.now();\n const problems = await lintDocument({\n document,\n config: this.#options.redoc.styleguide,\n externalRefResolver: resolver,\n });\n this.#report(problems, document.source.absoluteRef);\n this.#options.logger.debug(\"Linted schema\", \"lint\", this.#options.now() - startedAt);\n }\n\n async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {\n const startedAt = this.#options.now();\n const schemaKeys = schemaKeysOf(document.parsed);\n const componentKeys = componentKeysOf(document.parsed);\n // The lint resolver: documents it already loaded are not fetched again.\n const bundled = await bundle({\n config: this.#options.redoc,\n dereference: false,\n doc: document,\n externalRefResolver: resolver,\n });\n removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);\n repairBundledRefs(bundled.bundle.parsed);\n const documentRefs = resolver.getFiles();\n const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documentRefs);\n // The problems name the components the bundle made, so they are shown by their new names.\n this.#report(bundled.problems, document.source.absoluteRef, renaming);\n this.#options.logger.debug(\"Bundled schema\", \"bundle\", this.#options.now() - startedAt);\n return bundled.bundle.parsed;\n }\n\n /**\n * Report every problem; then throw if any of them is an error. A problem that the Redocly\n * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out\n * (upstream #2189).\n */\n #report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {\n const errorMessages: string[] = [];\n for (const problem of problems) {\n if (problem.ignored === true) {\n this.#options.logger.debug(\n `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,\n \"lint\",\n );\n continue;\n }\n const message = problemMessage(problem, renaming, rootRef);\n if (problem.severity === \"error\") {\n errorMessages.push(message);\n this.#options.logger.error(message);\n } else {\n this.#options.logger.warn(lintWarningText(message, problem.ruleId));\n }\n }\n if (errorMessages.length > 0) {\n throw schemaInvalidError(errorMessages);\n }\n }\n}\n\n/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */\nfunction lintWarningText(message: string, ruleId: string): string {\n return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);\n}\n\n/** Problems found before the bundle named any component. */\nconst UNRENAMED: ComponentRenaming = { pointer: (pointer) => pointer, text: (text) => text };\n\n/** A problem location that names a JSON Pointer, not a line and column. */\ntype PointerLocation = Extract<LocationObject, { pointer: string }>;\n\n/**\n * Where a problem is: its pointer, and the document when it is not the root (URL secrets left\n * out).\n */\nfunction locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {\n const pointer = renaming.pointer(location.pointer);\n const documentRef = location.source.absoluteRef;\n return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;\n}\n\nfunction problemMessage(\n problem: NormalizedProblem,\n renaming: ComponentRenaming,\n rootRef: string,\n): string {\n const location = problem.location?.[0];\n const message = renaming.text(problem.message);\n return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;\n}\n\n/** A schema loader with one Redocly config and logger, for one or more loads. */\nexport function createRedoclySchemaLoader(options: RedoclySchemaLoaderOptions): SchemaLoader {\n return new RedoclySchemaLoader(options);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redocly-schema-loader.mjs","sources":["../../../src/adapters/redocly-loader/redocly-schema-loader.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n bundle,\n type Document,\n type LocationObject,\n lintDocument,\n type NormalizedProblem,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport type { Logger } from \"../../application/ports/logger.js\";\nimport type {\n LoadedSchema,\n SchemaLoader,\n SchemaLoadRequest,\n} from \"../../application/ports/schema-loader.js\";\nimport { withStackOverflowReportedAsync } from \"../../application/stack-overflow.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { WARNING_CODES, warningText } from \"../../domain/warnings.js\";\nimport type { OpenAPI3 } from \"../../types.js\";\nimport { removeBundledMappingCopies, schemaKeysOf } from \"./bundled-mapping-copies.js\";\nimport { repairBundledRefs } from \"./bundled-ref-repair.js\";\nimport { loadedDocuments, localFilesRead } from \"./loaded-documents.js\";\nimport { findMalformedRefs, malformedRefsError } from \"./malformed-refs.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport {\n type ComponentRenaming,\n componentKeysOf,\n renameQueryNamedComponents,\n} from \"./query-named-components.js\";\nimport { RefAccessResolver } from \"./ref-access.js\";\nimport { checkRefsResolve } from \"./ref-resolution-check.js\";\nimport { REF_CYCLE_MESSAGE } from \"./ref-resolution-steps.js\";\nimport {\n RedirectedUrls,\n type RemoteDocumentLimits,\n resolveConfigWithAccess,\n} from \"./remote-fetch.js\";\nimport {\n loadFailedError,\n refCycleError,\n schemaInvalidError,\n versionUnsupportedError,\n} from \"./schema-load-errors.js\";\nimport { parseSchema } from \"./schema-source.js\";\nimport { findUnsupportedKeys, unsupportedKeysError } from \"./unsupported-keys.js\";\n\n/**\n * The SchemaLoader port on Redocly 1.34 (ADR-005): parse any inherited source type, then lint and\n * bundle with the given Redocly config. Problems go to the logger; errors fail the load.\n */\nexport interface RedoclySchemaLoaderOptions {\n readonly redoc: RedoclyConfig;\n readonly logger: Logger;\n /** A monotonic time in milliseconds, for the debug durations. */\n readonly now: () => number;\n /** Time limit of one remote fetch, unless the Redocly config brings its own `customFetch`. */\n readonly remoteRefTimeoutMs: number;\n readonly remoteDocumentLimits: RemoteDocumentLimits;\n}\n\nconst UNSUPPORTED_FORMAT_MESSAGE = \"Unsupported schema format, expected `openapi: 3.x`\";\n\n/**\n * Accept OpenAPI 3.x only; Swagger 2 and other formats get their own message. An empty file or a\n * `null` root parses to nothing, so it is checked before any field is read.\n */\nfunction assertOpenAPI3(document: unknown): void {\n if (typeof document !== \"object\" || document === null) {\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, {});\n }\n const parsed = document as Record<string, unknown>;\n const openapiVersion = Number.parseFloat(parsed.openapi as string);\n const isOpenAPI3 =\n !parsed.swagger &&\n Boolean(parsed.openapi) &&\n !Number.isNaN(openapiVersion) &&\n openapiVersion >= 3 &&\n openapiVersion < 4;\n if (isOpenAPI3) {\n return;\n }\n if (parsed.swagger) {\n throw versionUnsupportedError(\n \"Unsupported Swagger version: 2.x. Use OpenAPI 3.x instead.\",\n parsed,\n );\n }\n if (parsed.openapi || openapiVersion < 3 || openapiVersion >= 4) {\n throw versionUnsupportedError(`Unsupported OpenAPI version: ${parsed.openapi}`, parsed);\n }\n throw versionUnsupportedError(UNSUPPORTED_FORMAT_MESSAGE, parsed);\n}\n\n/**\n * The name an inline source (a string, object, buffer or stream) gets inside the working\n * directory. Redocly resolves relative `$ref`s against the directory of the document, so the\n * document must be named as a file in the working directory, not as the directory itself.\n */\nconst INLINE_SOURCE_NAME = \"inline-source\";\n\n/** The base for relative `$ref`s: the source file or URL itself, else a file in the cwd. */\nfunction sourceAbsoluteRef(source: unknown, cwd: URL): string {\n const document = source instanceof URL ? source : new URL(INLINE_SOURCE_NAME, cwd);\n return document.protocol === \"file:\" ? sourceFilePath(document) : document.href;\n}\n\n/**\n * The path of a `file:` source. A URL that names no path on this platform (an encoded slash, a\n * host on POSIX, no drive letter on Windows) cannot be read, so it fails as a read would.\n */\nfunction sourceFilePath(source: URL): string {\n try {\n return fileURLToPath(source);\n } catch (error) {\n // Node.js reports these as TypeErrors (ERR_INVALID_FILE_URL_PATH, ERR_INVALID_FILE_URL_HOST).\n if (!(error instanceof TypeError)) {\n throw error;\n }\n throw loadFailedError(source.href, error);\n }\n}\n\n/** Report the document problems Redocly throws as typed errors; anything else passes through. */\nasync function rethrowDocumentError(\n error: unknown,\n root: Document,\n resolver: BaseResolver,\n): Promise<never> {\n if (error instanceof Error && error.message === REF_CYCLE_MESSAGE) {\n throw refCycleError(error);\n }\n if (error instanceof URIError) {\n throw malformedRefsError(findMalformedRefs(await loadedDocuments(root, resolver)), error);\n }\n if (error instanceof TypeError) {\n const keys = findUnsupportedKeys(await loadedDocuments(root, resolver));\n throw keys.length === 0 ? error : unsupportedKeysError(keys, error);\n }\n throw error;\n}\n\nclass RedoclySchemaLoader implements SchemaLoader {\n readonly #options: RedoclySchemaLoaderOptions;\n\n constructor(options: RedoclySchemaLoaderOptions) {\n this.#options = options;\n }\n\n async load(request: SchemaLoadRequest): Promise<LoadedSchema> {\n const resolver = this.#resolver(request);\n // Parsing is guarded too: `JSON.stringify` of a deep object source overflows the stack on\n // Node.js 22 and 24, before linting starts.\n const bundled = await withStackOverflowReportedAsync(\"load\", () =>\n this.#parseAndBundle(request, resolver),\n ).catch((error: unknown) => {\n throw resolver.firstRefusal ?? error;\n });\n // A refused `$ref` is an unresolved-`$ref` problem, which a Redocly config can turn off.\n if (resolver.firstRefusal !== undefined) {\n throw resolver.firstRefusal;\n }\n return bundled;\n }\n\n async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<LoadedSchema> {\n const { logger, now } = this.#options;\n const configLoadedAt = now();\n logger.debug(\"Loaded Redoc config\", \"redoc\", now() - configLoadedAt);\n const parseStartedAt = now();\n const absoluteRef = sourceAbsoluteRef(request.source, request.cwd);\n const document = await parseSchema(request.source, { absoluteRef, resolver });\n logger.debug(\"Parsed schema\", \"redoc\", now() - parseStartedAt);\n assertOpenAPI3(document.parsed);\n pointMappingNamesAtSchemas(document.parsed);\n const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);\n const isRootAFile = request.source instanceof URL && request.source.protocol === \"file:\";\n const localFiles = await localFilesRead(document, resolver, isRootAFile);\n return { document: bundled, localFiles };\n }\n\n #resolver(request: SchemaLoadRequest): RefAccessResolver {\n const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;\n // One record per load, as the resolver and its cache are.\n const redirects = new RedirectedUrls();\n const config = resolveConfigWithAccess(redoc.resolve, {\n timeoutMs: remoteRefTimeoutMs,\n access: request.refAccess,\n rootUrl: request.source instanceof URL ? request.source.href : undefined,\n maxParallelFetches: remoteDocumentLimits.maxParallelFetches,\n bodyLimits: remoteDocumentLimits,\n redirects,\n });\n return new RefAccessResolver(config, {\n access: request.refAccess,\n maxRemoteDocuments: remoteDocumentLimits.maxDocuments,\n maxRemoteNodes: remoteDocumentLimits.maxNodes,\n redirects,\n });\n }\n\n async #lintAndBundle(\n document: Document,\n resolver: BaseResolver,\n shouldLint: boolean,\n ): Promise<OpenAPI3> {\n const rethrow = (error: unknown) => rethrowDocumentError(error, document, resolver);\n // Redocly leaves unhandled rejections behind when a `$ref` fails in two documents (ADR-031).\n await checkRefsResolve(document, resolver, this.#options.redoc.styleguide).catch(rethrow);\n if (shouldLint) {\n await this.#lint(document, resolver).catch(rethrow);\n }\n return this.#bundle(document, resolver).catch(rethrow);\n }\n\n async #lint(document: Document, resolver: BaseResolver): Promise<void> {\n const startedAt = this.#options.now();\n const problems = await lintDocument({\n document,\n config: this.#options.redoc.styleguide,\n externalRefResolver: resolver,\n });\n this.#report(problems, document.source.absoluteRef);\n this.#options.logger.debug(\"Linted schema\", \"lint\", this.#options.now() - startedAt);\n }\n\n async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {\n const startedAt = this.#options.now();\n const schemaKeys = schemaKeysOf(document.parsed);\n const componentKeys = componentKeysOf(document.parsed);\n // The lint resolver: documents it already loaded are not fetched again.\n const bundled = await bundle({\n config: this.#options.redoc,\n dereference: false,\n doc: document,\n externalRefResolver: resolver,\n });\n removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);\n repairBundledRefs(bundled.bundle.parsed);\n const documents = await loadedDocuments(document, resolver);\n const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);\n // The problems name the components the bundle made, so they are shown by their new names.\n this.#report(bundled.problems, document.source.absoluteRef, renaming);\n this.#options.logger.debug(\"Bundled schema\", \"bundle\", this.#options.now() - startedAt);\n return bundled.bundle.parsed;\n }\n\n /**\n * Report every problem; then throw if any of them is an error. A problem that the Redocly\n * config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out\n * (upstream #2189). A name collision that the renamed components no longer have is dropped.\n */\n #report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {\n const errorMessages: string[] = [];\n for (const problem of problems.filter(\n (found) => !renaming.isResolvedCollision(found.message),\n )) {\n if (problem.ignored === true) {\n this.#options.logger.debug(\n `Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,\n \"lint\",\n );\n continue;\n }\n const message = problemMessage(problem, renaming, rootRef);\n if (problem.severity === \"error\") {\n errorMessages.push(message);\n this.#options.logger.error(message);\n } else {\n this.#options.logger.warn(lintWarningText(message, problem.ruleId));\n }\n }\n if (errorMessages.length > 0) {\n throw schemaInvalidError(errorMessages);\n }\n }\n}\n\n/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */\nfunction lintWarningText(message: string, ruleId: string): string {\n return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);\n}\n\n/** Problems found before the bundle named any component. */\nconst UNRENAMED: ComponentRenaming = {\n pointer: (pointer) => pointer,\n text: (text) => text,\n isResolvedCollision: () => false,\n};\n\n/** A problem location that names a JSON Pointer, not a line and column. */\ntype PointerLocation = Extract<LocationObject, { pointer: string }>;\n\n/**\n * Where a problem is: its pointer, and the document when it is not the root (URL secrets left\n * out).\n */\nfunction locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {\n const pointer = renaming.pointer(location.pointer);\n const documentRef = location.source.absoluteRef;\n return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;\n}\n\nfunction problemMessage(\n problem: NormalizedProblem,\n renaming: ComponentRenaming,\n rootRef: string,\n): string {\n const location = problem.location?.[0];\n const message = renaming.text(problem.message);\n return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;\n}\n\n/** A schema loader with one Redocly config and logger, for one or more loads. */\nexport function createRedoclySchemaLoader(options: RedoclySchemaLoaderOptions): SchemaLoader {\n return new RedoclySchemaLoader(options);\n}\n"],"names":[],"mappings":"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"}
@@ -143,7 +143,7 @@ class RefAccessResolver extends openapiCore.BaseResolver {
143
143
  throw refusal;
144
144
  }
145
145
  try {
146
- return await super.loadExternalRef(absoluteRef);
146
+ return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));
147
147
  } catch (error) {
148
148
  const cause = error.originalError;
149
149
  if (isRefusal(cause)) {
@@ -152,6 +152,14 @@ class RefAccessResolver extends openapiCore.BaseResolver {
152
152
  throw error;
153
153
  }
154
154
  }
155
+ /**
156
+ * A redirected document is named by the URL it came from, so its relative `$ref`s, its
157
+ * component names and its problems use that URL. Redocly still finds it by the requested one.
158
+ */
159
+ #atFinalUrl(source) {
160
+ const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);
161
+ return finalUrl === source.absoluteRef ? source : new openapiCore.Source(finalUrl, source.body, source.mimeType);
162
+ }
155
163
  #accessRefusal(absoluteRef) {
156
164
  return absoluteRef === this.#rootRef ? Promise.resolve(void 0) : refusalOf(absoluteRef, this.#limits.access);
157
165
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ref-access.cjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n type Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport { isParsedSource, RemoteNodeLimit } from \"./remote-node-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`\n * against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;\n * Redocly then reads either from disk, and on Windows a drive letter is an absolute path.\n */\nfunction localFromRemoteRefusal(\n base: string,\n absoluteRef: string,\n): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {\n return undefined;\n }\n const shownBase = redactUrlSecrets(base);\n return refForbiddenError(absoluteRef, { reason: \"local-ref-from-remote\", base: shownBase });\n}\n\n/** The scheme of an `http(s)` URL, in any case. */\nconst REMOTE_SCHEME_RE = /^https?:(?=\\/\\/)/i;\n\n/** The limits one resolver checks; each load gets its own resolver. */\nexport interface RefAccessLimits {\n readonly access: RefAccess;\n /** Remote documents one load may fetch, the root included (threat model T2). */\n readonly maxRemoteDocuments: number;\n /** Nodes the remote documents of one load may parse into together (threat model T2). */\n readonly maxRemoteNodes: number;\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #limits: RefAccessLimits;\n readonly #nodeLimit: RemoteNodeLimit;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */\n #remoteDocumentCount = 0;\n\n constructor(config: ResolveConfig, limits: RefAccessLimits) {\n super(config);\n this.#limits = limits;\n this.#nodeLimit = new RemoteNodeLimit(limits.maxRemoteNodes);\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n /**\n * A scheme is the same in any case (RFC 3986, section 3.1), but Redocly takes only `http://` and\n * `https://` as URLs: it read `HTTP://host/x.yaml` as a file path, and its \"not found\" message\n * showed the user info and the query of the URL, which no longer looked like one. The scheme\n * is written in lowercase first, so the `$ref` loads as the URL it is, within the same limits.\n */\n override resolveExternalRef(base: string | null, ref: string): string {\n const normalized = ref.replace(REMOTE_SCHEME_RE, (scheme) => scheme.toLowerCase());\n return super.resolveExternalRef(base, normalized);\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n const refusal =\n base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n return Promise.reject(refusal);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /**\n * Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a\n * `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas\n * before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).\n */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = this.#parsed(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n pointMappingNamesAtSchemas(document.parsed);\n return document;\n }\n\n /**\n * A remote document is parsed within the node limit; a local one as Redocly parses it. Whether\n * a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the\n * extension of a URL with its query.\n */\n #parsed(source: Source, isRoot: boolean): Document {\n if (!isRemoteRef(source.absoluteRef)) {\n return super.parseDocument(source, isRoot);\n }\n if (!isParsedSource(source, isRoot)) {\n return { source, parsed: source.body };\n }\n try {\n return this.#nodeLimit.parse(source);\n } catch (error) {\n if (isRefusal(error)) {\n this.#refusals.push(error);\n }\n throw new YamlParseError(error as Error, source);\n }\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return await super.loadExternalRef(absoluteRef);\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n\n #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {\n return absoluteRef === this.#rootRef\n ? Promise.resolve(undefined)\n : refusalOf(absoluteRef, this.#limits.access);\n }\n\n /**\n * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three\n * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).\n */\n #countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(absoluteRef)) {\n return undefined;\n }\n this.#remoteDocumentCount += 1;\n const { maxRemoteDocuments } = this.#limits;\n return this.#remoteDocumentCount > maxRemoteDocuments\n ? refForbiddenError(absoluteRef, {\n reason: \"too-many-remote-documents\",\n maxDocuments: maxRemoteDocuments,\n })\n : undefined;\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":["path","realpath","fileURLToPath","refForbiddenError","isHostAllowed","redactUrlSecrets","BaseResolver","RemoteNodeLimit","aliasExpansionFailure","YamlParseError","pointMappingNamesAtSchemas","isParsedSource","ERROR_CODES"],"mappings":"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"}
1
+ {"version":3,"file":"ref-access.cjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport type { RedirectedUrls } from \"./remote-fetch.js\";\nimport { isParsedSource, RemoteNodeLimit } from \"./remote-node-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`\n * against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;\n * Redocly then reads either from disk, and on Windows a drive letter is an absolute path.\n */\nfunction localFromRemoteRefusal(\n base: string,\n absoluteRef: string,\n): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {\n return undefined;\n }\n const shownBase = redactUrlSecrets(base);\n return refForbiddenError(absoluteRef, { reason: \"local-ref-from-remote\", base: shownBase });\n}\n\n/** The scheme of an `http(s)` URL, in any case. */\nconst REMOTE_SCHEME_RE = /^https?:(?=\\/\\/)/i;\n\n/** The limits one resolver checks; each load gets its own resolver. */\nexport interface RefAccessLimits {\n readonly access: RefAccess;\n /** Remote documents one load may fetch, the root included (threat model T2). */\n readonly maxRemoteDocuments: number;\n /** Nodes the remote documents of one load may parse into together (threat model T2). */\n readonly maxRemoteNodes: number;\n /** The final URLs of redirected documents, which their relative `$ref`s resolve against. */\n readonly redirects: RedirectedUrls;\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #limits: RefAccessLimits;\n readonly #nodeLimit: RemoteNodeLimit;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */\n #remoteDocumentCount = 0;\n\n constructor(config: ResolveConfig, limits: RefAccessLimits) {\n super(config);\n this.#limits = limits;\n this.#nodeLimit = new RemoteNodeLimit(limits.maxRemoteNodes);\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n /**\n * A scheme is the same in any case (RFC 3986, section 3.1), but Redocly takes only `http://` and\n * `https://` as URLs: it read `HTTP://host/x.yaml` as a file path, and its \"not found\" message\n * showed the user info and the query of the URL, which no longer looked like one. The scheme\n * is written in lowercase first, so the `$ref` loads as the URL it is, within the same limits.\n */\n override resolveExternalRef(base: string | null, ref: string): string {\n const normalized = ref.replace(REMOTE_SCHEME_RE, (scheme) => scheme.toLowerCase());\n return super.resolveExternalRef(base, normalized);\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n const refusal =\n base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n return Promise.reject(refusal);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /**\n * Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a\n * `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas\n * before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).\n */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = this.#parsed(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n pointMappingNamesAtSchemas(document.parsed);\n return document;\n }\n\n /**\n * A remote document is parsed within the node limit; a local one as Redocly parses it. Whether\n * a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the\n * extension of a URL with its query.\n */\n #parsed(source: Source, isRoot: boolean): Document {\n if (!isRemoteRef(source.absoluteRef)) {\n return super.parseDocument(source, isRoot);\n }\n if (!isParsedSource(source, isRoot)) {\n return { source, parsed: source.body };\n }\n try {\n return this.#nodeLimit.parse(source);\n } catch (error) {\n if (isRefusal(error)) {\n this.#refusals.push(error);\n }\n throw new YamlParseError(error as Error, source);\n }\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n\n /**\n * A redirected document is named by the URL it came from, so its relative `$ref`s, its\n * component names and its problems use that URL. Redocly still finds it by the requested one.\n */\n #atFinalUrl(source: Source): Source {\n const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);\n return finalUrl === source.absoluteRef\n ? source\n : new Source(finalUrl, source.body, source.mimeType);\n }\n\n #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {\n return absoluteRef === this.#rootRef\n ? Promise.resolve(undefined)\n : refusalOf(absoluteRef, this.#limits.access);\n }\n\n /**\n * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three\n * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).\n */\n #countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(absoluteRef)) {\n return undefined;\n }\n this.#remoteDocumentCount += 1;\n const { maxRemoteDocuments } = this.#limits;\n return this.#remoteDocumentCount > maxRemoteDocuments\n ? refForbiddenError(absoluteRef, {\n reason: \"too-many-remote-documents\",\n maxDocuments: maxRemoteDocuments,\n })\n : undefined;\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":["path","realpath","fileURLToPath","refForbiddenError","isHostAllowed","redactUrlSecrets","BaseResolver","RemoteNodeLimit","aliasExpansionFailure","YamlParseError","pointMappingNamesAtSchemas","isParsedSource","Source","ERROR_CODES"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { realpath } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { BaseResolver, YamlParseError } from '@redocly/openapi-core';
4
+ import { BaseResolver, YamlParseError, Source } from '@redocly/openapi-core';
5
5
  import { ERROR_CODES } from '../../domain/errors.mjs';
6
6
  import { redactUrlSecrets } from '../../domain/url-secrets.mjs';
7
7
  import { aliasExpansionFailure } from './alias-limit.mjs';
@@ -137,7 +137,7 @@ class RefAccessResolver extends BaseResolver {
137
137
  throw refusal;
138
138
  }
139
139
  try {
140
- return await super.loadExternalRef(absoluteRef);
140
+ return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));
141
141
  } catch (error) {
142
142
  const cause = error.originalError;
143
143
  if (isRefusal(cause)) {
@@ -146,6 +146,14 @@ class RefAccessResolver extends BaseResolver {
146
146
  throw error;
147
147
  }
148
148
  }
149
+ /**
150
+ * A redirected document is named by the URL it came from, so its relative `$ref`s, its
151
+ * component names and its problems use that URL. Redocly still finds it by the requested one.
152
+ */
153
+ #atFinalUrl(source) {
154
+ const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);
155
+ return finalUrl === source.absoluteRef ? source : new Source(finalUrl, source.body, source.mimeType);
156
+ }
149
157
  #accessRefusal(absoluteRef) {
150
158
  return absoluteRef === this.#rootRef ? Promise.resolve(void 0) : refusalOf(absoluteRef, this.#limits.access);
151
159
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ref-access.mjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n type Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport { isParsedSource, RemoteNodeLimit } from \"./remote-node-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`\n * against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;\n * Redocly then reads either from disk, and on Windows a drive letter is an absolute path.\n */\nfunction localFromRemoteRefusal(\n base: string,\n absoluteRef: string,\n): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {\n return undefined;\n }\n const shownBase = redactUrlSecrets(base);\n return refForbiddenError(absoluteRef, { reason: \"local-ref-from-remote\", base: shownBase });\n}\n\n/** The scheme of an `http(s)` URL, in any case. */\nconst REMOTE_SCHEME_RE = /^https?:(?=\\/\\/)/i;\n\n/** The limits one resolver checks; each load gets its own resolver. */\nexport interface RefAccessLimits {\n readonly access: RefAccess;\n /** Remote documents one load may fetch, the root included (threat model T2). */\n readonly maxRemoteDocuments: number;\n /** Nodes the remote documents of one load may parse into together (threat model T2). */\n readonly maxRemoteNodes: number;\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #limits: RefAccessLimits;\n readonly #nodeLimit: RemoteNodeLimit;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */\n #remoteDocumentCount = 0;\n\n constructor(config: ResolveConfig, limits: RefAccessLimits) {\n super(config);\n this.#limits = limits;\n this.#nodeLimit = new RemoteNodeLimit(limits.maxRemoteNodes);\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n /**\n * A scheme is the same in any case (RFC 3986, section 3.1), but Redocly takes only `http://` and\n * `https://` as URLs: it read `HTTP://host/x.yaml` as a file path, and its \"not found\" message\n * showed the user info and the query of the URL, which no longer looked like one. The scheme\n * is written in lowercase first, so the `$ref` loads as the URL it is, within the same limits.\n */\n override resolveExternalRef(base: string | null, ref: string): string {\n const normalized = ref.replace(REMOTE_SCHEME_RE, (scheme) => scheme.toLowerCase());\n return super.resolveExternalRef(base, normalized);\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n const refusal =\n base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n return Promise.reject(refusal);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /**\n * Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a\n * `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas\n * before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).\n */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = this.#parsed(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n pointMappingNamesAtSchemas(document.parsed);\n return document;\n }\n\n /**\n * A remote document is parsed within the node limit; a local one as Redocly parses it. Whether\n * a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the\n * extension of a URL with its query.\n */\n #parsed(source: Source, isRoot: boolean): Document {\n if (!isRemoteRef(source.absoluteRef)) {\n return super.parseDocument(source, isRoot);\n }\n if (!isParsedSource(source, isRoot)) {\n return { source, parsed: source.body };\n }\n try {\n return this.#nodeLimit.parse(source);\n } catch (error) {\n if (isRefusal(error)) {\n this.#refusals.push(error);\n }\n throw new YamlParseError(error as Error, source);\n }\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return await super.loadExternalRef(absoluteRef);\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n\n #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {\n return absoluteRef === this.#rootRef\n ? Promise.resolve(undefined)\n : refusalOf(absoluteRef, this.#limits.access);\n }\n\n /**\n * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three\n * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).\n */\n #countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(absoluteRef)) {\n return undefined;\n }\n this.#remoteDocumentCount += 1;\n const { maxRemoteDocuments } = this.#limits;\n return this.#remoteDocumentCount > maxRemoteDocuments\n ? refForbiddenError(absoluteRef, {\n reason: \"too-many-remote-documents\",\n maxDocuments: maxRemoteDocuments,\n })\n : undefined;\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ref-access.mjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { pointMappingNamesAtSchemas } from \"./mapping-schema-names.js\";\nimport type { RedirectedUrls } from \"./remote-fetch.js\";\nimport { isParsedSource, RemoteNodeLimit } from \"./remote-node-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`\n * against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;\n * Redocly then reads either from disk, and on Windows a drive letter is an absolute path.\n */\nfunction localFromRemoteRefusal(\n base: string,\n absoluteRef: string,\n): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {\n return undefined;\n }\n const shownBase = redactUrlSecrets(base);\n return refForbiddenError(absoluteRef, { reason: \"local-ref-from-remote\", base: shownBase });\n}\n\n/** The scheme of an `http(s)` URL, in any case. */\nconst REMOTE_SCHEME_RE = /^https?:(?=\\/\\/)/i;\n\n/** The limits one resolver checks; each load gets its own resolver. */\nexport interface RefAccessLimits {\n readonly access: RefAccess;\n /** Remote documents one load may fetch, the root included (threat model T2). */\n readonly maxRemoteDocuments: number;\n /** Nodes the remote documents of one load may parse into together (threat model T2). */\n readonly maxRemoteNodes: number;\n /** The final URLs of redirected documents, which their relative `$ref`s resolve against. */\n readonly redirects: RedirectedUrls;\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #limits: RefAccessLimits;\n readonly #nodeLimit: RemoteNodeLimit;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */\n #remoteDocumentCount = 0;\n\n constructor(config: ResolveConfig, limits: RefAccessLimits) {\n super(config);\n this.#limits = limits;\n this.#nodeLimit = new RemoteNodeLimit(limits.maxRemoteNodes);\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n /**\n * A scheme is the same in any case (RFC 3986, section 3.1), but Redocly takes only `http://` and\n * `https://` as URLs: it read `HTTP://host/x.yaml` as a file path, and its \"not found\" message\n * showed the user info and the query of the URL, which no longer looked like one. The scheme\n * is written in lowercase first, so the `$ref` loads as the URL it is, within the same limits.\n */\n override resolveExternalRef(base: string | null, ref: string): string {\n const normalized = ref.replace(REMOTE_SCHEME_RE, (scheme) => scheme.toLowerCase());\n return super.resolveExternalRef(base, normalized);\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n const refusal =\n base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n return Promise.reject(refusal);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /**\n * Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a\n * `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas\n * before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).\n */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = this.#parsed(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n pointMappingNamesAtSchemas(document.parsed);\n return document;\n }\n\n /**\n * A remote document is parsed within the node limit; a local one as Redocly parses it. Whether\n * a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the\n * extension of a URL with its query.\n */\n #parsed(source: Source, isRoot: boolean): Document {\n if (!isRemoteRef(source.absoluteRef)) {\n return super.parseDocument(source, isRoot);\n }\n if (!isParsedSource(source, isRoot)) {\n return { source, parsed: source.body };\n }\n try {\n return this.#nodeLimit.parse(source);\n } catch (error) {\n if (isRefusal(error)) {\n this.#refusals.push(error);\n }\n throw new YamlParseError(error as Error, source);\n }\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n\n /**\n * A redirected document is named by the URL it came from, so its relative `$ref`s, its\n * component names and its problems use that URL. Redocly still finds it by the requested one.\n */\n #atFinalUrl(source: Source): Source {\n const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);\n return finalUrl === source.absoluteRef\n ? source\n : new Source(finalUrl, source.body, source.mimeType);\n }\n\n #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {\n return absoluteRef === this.#rootRef\n ? Promise.resolve(undefined)\n : refusalOf(absoluteRef, this.#limits.access);\n }\n\n /**\n * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three\n * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).\n */\n #countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(absoluteRef)) {\n return undefined;\n }\n this.#remoteDocumentCount += 1;\n const { maxRemoteDocuments } = this.#limits;\n return this.#remoteDocumentCount > maxRemoteDocuments\n ? refForbiddenError(absoluteRef, {\n reason: \"too-many-remote-documents\",\n maxDocuments: maxRemoteDocuments,\n })\n : undefined;\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":[],"mappings":"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"}