openapi-typesafe 1.0.0-rc.4 → 1.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +214 -0
- package/dist/adapters/node-file-system/node-file-system.cjs +7 -0
- package/dist/adapters/node-file-system/node-file-system.cjs.map +1 -1
- package/dist/adapters/node-file-system/node-file-system.mjs +7 -0
- package/dist/adapters/node-file-system/node-file-system.mjs.map +1 -1
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs +19 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs.map +1 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs +17 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs.map +1 -0
- package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs +5 -32
- package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs.map +1 -1
- package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs +6 -33
- package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs.map +1 -1
- package/dist/adapters/redocly-loader/component-schemas.cjs +17 -0
- package/dist/adapters/redocly-loader/component-schemas.cjs.map +1 -0
- package/dist/adapters/redocly-loader/component-schemas.mjs +14 -0
- package/dist/adapters/redocly-loader/component-schemas.mjs.map +1 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.cjs +22 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.cjs.map +1 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.mjs +20 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.mjs.map +1 -0
- package/dist/adapters/redocly-loader/query-document-names.cjs +50 -0
- package/dist/adapters/redocly-loader/query-document-names.cjs.map +1 -0
- package/dist/adapters/redocly-loader/query-document-names.mjs +48 -0
- package/dist/adapters/redocly-loader/query-document-names.mjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs +129 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs +126 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-config-source.cjs +3 -0
- package/dist/adapters/redocly-loader/redocly-config-source.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-config-source.mjs +4 -1
- package/dist/adapters/redocly-loader/redocly-config-source.mjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +39 -18
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +40 -19
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +25 -4
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +26 -5
- package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-holders.cjs +52 -10
- package/dist/adapters/redocly-loader/ref-holders.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-holders.mjs +52 -11
- package/dist/adapters/redocly-loader/ref-holders.mjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.cjs +20 -3
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +20 -4
- package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-node-limit.cjs +17 -3
- package/dist/adapters/redocly-loader/remote-node-limit.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-node-limit.mjs +17 -3
- package/dist/adapters/redocly-loader/remote-node-limit.mjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-source.cjs +7 -2
- package/dist/adapters/redocly-loader/schema-source.cjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-source.mjs +8 -3
- package/dist/adapters/redocly-loader/schema-source.mjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.cjs +7 -3
- package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.mjs +7 -3
- package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
- package/dist/api/cwd-option.cjs +58 -0
- package/dist/api/cwd-option.cjs.map +1 -0
- package/dist/api/cwd-option.mjs +52 -0
- package/dist/api/cwd-option.mjs.map +1 -0
- package/dist/api/default-ports.cjs +15 -7
- package/dist/api/default-ports.cjs.map +1 -1
- package/dist/api/default-ports.mjs +14 -8
- package/dist/api/default-ports.mjs.map +1 -1
- package/dist/api/document-refs.cjs +16 -1
- package/dist/api/document-refs.cjs.map +1 -1
- package/dist/api/document-refs.mjs +16 -1
- package/dist/api/document-refs.mjs.map +1 -1
- package/dist/api/document-run.cjs +6 -26
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +5 -21
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/legacy-utils.cjs +1 -1
- package/dist/api/legacy-utils.cjs.map +1 -1
- package/dist/api/legacy-utils.mjs +1 -1
- package/dist/api/legacy-utils.mjs.map +1 -1
- package/dist/api/openapi-ts.cjs +1 -2
- package/dist/api/openapi-ts.cjs.map +1 -1
- package/dist/api/openapi-ts.mjs +2 -3
- package/dist/api/openapi-ts.mjs.map +1 -1
- package/dist/api/option-errors.cjs +24 -0
- package/dist/api/option-errors.cjs.map +1 -0
- package/dist/api/option-errors.mjs +21 -0
- package/dist/api/option-errors.mjs.map +1 -0
- package/dist/api/ref-limit-options.cjs +58 -0
- package/dist/api/ref-limit-options.cjs.map +1 -0
- package/dist/api/ref-limit-options.mjs +52 -0
- package/dist/api/ref-limit-options.mjs.map +1 -0
- package/dist/api/transform-schema.cjs +12 -5
- package/dist/api/transform-schema.cjs.map +1 -1
- package/dist/api/transform-schema.mjs +12 -5
- package/dist/api/transform-schema.mjs.map +1 -1
- package/dist/application/transform/declared-value-types.cjs +36 -0
- package/dist/application/transform/declared-value-types.cjs.map +1 -0
- package/dist/application/transform/declared-value-types.mjs +34 -0
- package/dist/application/transform/declared-value-types.mjs.map +1 -0
- package/dist/application/transform/document/document.cjs +3 -1
- package/dist/application/transform/document/document.cjs.map +1 -1
- package/dist/application/transform/document/document.mjs +3 -1
- package/dist/application/transform/document/document.mjs.map +1 -1
- package/dist/application/transform/document/exploded-query-values.cjs +63 -0
- package/dist/application/transform/document/exploded-query-values.cjs.map +1 -0
- package/dist/application/transform/document/exploded-query-values.mjs +59 -0
- package/dist/application/transform/document/exploded-query-values.mjs.map +1 -0
- package/dist/application/transform/document/header.cjs +11 -3
- package/dist/application/transform/document/header.cjs.map +1 -1
- package/dist/application/transform/document/header.mjs +10 -2
- package/dist/application/transform/document/header.mjs.map +1 -1
- package/dist/application/transform/document/parameter.cjs +1 -1
- package/dist/application/transform/document/parameter.cjs.map +1 -1
- package/dist/application/transform/document/parameter.mjs +1 -1
- package/dist/application/transform/document/parameter.mjs.map +1 -1
- package/dist/application/transform/document/parameters.cjs +23 -5
- package/dist/application/transform/document/parameters.cjs.map +1 -1
- package/dist/application/transform/document/parameters.mjs +23 -5
- package/dist/application/transform/document/parameters.mjs.map +1 -1
- package/dist/application/transform/document/path-item.cjs +4 -3
- package/dist/application/transform/document/path-item.cjs.map +1 -1
- package/dist/application/transform/document/path-item.mjs +4 -4
- package/dist/application/transform/document/path-item.mjs.map +1 -1
- package/dist/application/transform/document/path-template-key.cjs +84 -0
- package/dist/application/transform/document/path-template-key.cjs.map +1 -0
- package/dist/application/transform/document/path-template-key.mjs +79 -0
- package/dist/application/transform/document/path-template-key.mjs.map +1 -0
- package/dist/application/transform/document/path-template-refs.cjs +80 -0
- package/dist/application/transform/document/path-template-refs.cjs.map +1 -0
- package/dist/application/transform/document/path-template-refs.mjs +77 -0
- package/dist/application/transform/document/path-template-refs.mjs.map +1 -0
- package/dist/application/transform/document/paths-enum.cjs +7 -6
- package/dist/application/transform/document/paths-enum.cjs.map +1 -1
- package/dist/application/transform/document/paths-enum.mjs +7 -6
- package/dist/application/transform/document/paths-enum.mjs.map +1 -1
- package/dist/application/transform/document/paths.cjs +38 -66
- package/dist/application/transform/document/paths.cjs.map +1 -1
- package/dist/application/transform/document/paths.mjs +36 -66
- package/dist/application/transform/document/paths.mjs.map +1 -1
- package/dist/application/transform/document/webhooks.cjs +10 -3
- package/dist/application/transform/document/webhooks.cjs.map +1 -1
- package/dist/application/transform/document/webhooks.mjs +9 -2
- package/dist/application/transform/document/webhooks.mjs.map +1 -1
- package/dist/application/transform/footer/document-footer.cjs +6 -3
- package/dist/application/transform/footer/document-footer.cjs.map +1 -1
- package/dist/application/transform/footer/document-footer.mjs +6 -3
- package/dist/application/transform/footer/document-footer.mjs.map +1 -1
- package/dist/application/transform/helper-types.cjs +3 -3
- package/dist/application/transform/helper-types.cjs.map +1 -1
- package/dist/application/transform/helper-types.mjs +3 -3
- package/dist/application/transform/helper-types.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-location.cjs +4 -4
- package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-location.mjs +1 -1
- package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-shape.cjs +63 -64
- package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-shape.mjs +57 -53
- package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.cjs +2 -2
- package/dist/application/transform/ref-pointer/inline-cycles.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.mjs +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/located-node.cjs +23 -0
- package/dist/application/transform/ref-pointer/located-node.cjs.map +1 -0
- package/dist/application/transform/ref-pointer/located-node.mjs +18 -0
- package/dist/application/transform/ref-pointer/located-node.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/original-index.cjs +69 -14
- package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/original-index.mjs +71 -16
- package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.cjs +2 -2
- package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-walk.cjs +54 -13
- package/dist/application/transform/ref-pointer/pointer-walk.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-walk.mjs +46 -7
- package/dist/application/transform/ref-pointer/pointer-walk.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/property-shape.cjs +57 -0
- package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs +49 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/typed-walk.cjs +90 -22
- package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/typed-walk.mjs +87 -19
- package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -1
- package/dist/application/transform/schema/access-flags.cjs.map +1 -1
- package/dist/application/transform/schema/access-flags.mjs.map +1 -1
- package/dist/application/transform/schema/index-signature-type.cjs +21 -0
- package/dist/application/transform/schema/index-signature-type.cjs.map +1 -0
- package/dist/application/transform/schema/index-signature-type.mjs +19 -0
- package/dist/application/transform/schema/index-signature-type.mjs.map +1 -0
- package/dist/application/transform/schema/input-location.cjs +23 -0
- package/dist/application/transform/schema/input-location.cjs.map +1 -0
- package/dist/application/transform/schema/input-location.mjs +21 -0
- package/dist/application/transform/schema/input-location.mjs.map +1 -0
- package/dist/application/transform/schema/object-type.cjs +15 -5
- package/dist/application/transform/schema/object-type.cjs.map +1 -1
- package/dist/application/transform/schema/object-type.mjs +15 -5
- package/dist/application/transform/schema/object-type.mjs.map +1 -1
- package/dist/application/transform/schema/schema-transform.cjs +2 -1
- package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
- package/dist/application/transform/schema/schema-transform.mjs +2 -1
- package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs +2 -30
- package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.mjs +3 -31
- package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
- package/dist/cli/api-logger.cjs +13 -0
- package/dist/cli/api-logger.cjs.map +1 -0
- package/dist/cli/api-logger.mjs +11 -0
- package/dist/cli/api-logger.mjs.map +1 -0
- package/dist/cli/api-settings.cjs +10 -17
- package/dist/cli/api-settings.cjs.map +1 -1
- package/dist/cli/api-settings.mjs +10 -17
- package/dist/cli/api-settings.mjs.map +1 -1
- package/dist/cli/api-writes.cjs +7 -1
- package/dist/cli/api-writes.cjs.map +1 -1
- package/dist/cli/api-writes.mjs +7 -1
- package/dist/cli/api-writes.mjs.map +1 -1
- package/dist/cli/cli-arguments.cjs +32 -74
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +28 -70
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/cli-errors.cjs +18 -0
- package/dist/cli/cli-errors.cjs.map +1 -1
- package/dist/cli/cli-errors.mjs +18 -1
- package/dist/cli/cli-errors.mjs.map +1 -1
- package/dist/cli/cli-flag-names.cjs +77 -0
- package/dist/cli/cli-flag-names.cjs.map +1 -0
- package/dist/cli/cli-flag-names.mjs +64 -0
- package/dist/cli/cli-flag-names.mjs.map +1 -0
- package/dist/cli/cli-output.cjs +13 -6
- package/dist/cli/cli-output.cjs.map +1 -1
- package/dist/cli/cli-output.mjs +14 -7
- package/dist/cli/cli-output.mjs.map +1 -1
- package/dist/cli/main.cjs +11 -5
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.mjs +11 -5
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +33 -12
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +35 -14
- package/dist/cli/redocly-apis.mjs.map +1 -1
- package/dist/cli/ref-limits.cjs +15 -1
- package/dist/cli/ref-limits.cjs.map +1 -1
- package/dist/cli/ref-limits.mjs +15 -2
- package/dist/cli/ref-limits.mjs.map +1 -1
- package/dist/cli/run-cli.cjs +24 -7
- package/dist/cli/run-cli.cjs.map +1 -1
- package/dist/cli/run-cli.mjs +25 -8
- package/dist/cli/run-cli.mjs.map +1 -1
- package/dist/cli/written-flags.cjs +83 -0
- package/dist/cli/written-flags.cjs.map +1 -0
- package/dist/cli/written-flags.mjs +77 -0
- package/dist/cli/written-flags.mjs.map +1 -0
- package/dist/domain/cause-secrets.cjs +31 -16
- package/dist/domain/cause-secrets.cjs.map +1 -1
- package/dist/domain/cause-secrets.mjs +31 -16
- package/dist/domain/cause-secrets.mjs.map +1 -1
- package/dist/domain/errors.cjs +3 -0
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +3 -0
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/location-text.cjs +15 -0
- package/dist/domain/location-text.cjs.map +1 -0
- package/dist/domain/location-text.mjs +13 -0
- package/dist/domain/location-text.mjs.map +1 -0
- package/dist/domain/openapi/document-places.cjs +55 -0
- package/dist/domain/openapi/document-places.cjs.map +1 -0
- package/dist/domain/openapi/document-places.mjs +53 -0
- package/dist/domain/openapi/document-places.mjs.map +1 -0
- package/dist/domain/output-fixes.cjs +8 -2
- package/dist/domain/output-fixes.cjs.map +1 -1
- package/dist/domain/output-fixes.mjs +8 -2
- package/dist/domain/output-fixes.mjs.map +1 -1
- package/dist/domain/terminal-text.cjs +31 -0
- package/dist/domain/terminal-text.cjs.map +1 -0
- package/dist/domain/terminal-text.mjs +29 -0
- package/dist/domain/terminal-text.mjs.map +1 -0
- package/dist/domain/warnings.cjs +1 -0
- package/dist/domain/warnings.cjs.map +1 -1
- package/dist/domain/warnings.mjs +1 -0
- package/dist/domain/warnings.mjs.map +1 -1
- package/dist/index.d.cts +29 -18
- package/dist/index.d.mts +29 -18
- package/dist/index.d.ts +29 -18
- package/package.json +1 -1
- package/src/adapters/node-file-system/node-file-system.ts +14 -1
- package/src/adapters/pipe-safe-writer/pipe-safe-writer.ts +29 -0
- package/src/adapters/redocly-loader/bundled-mapping-copies.ts +7 -42
- package/src/adapters/redocly-loader/component-schemas.ts +19 -0
- package/src/adapters/redocly-loader/mapping-schema-names.ts +25 -0
- package/src/adapters/redocly-loader/query-document-names.ts +79 -0
- package/src/adapters/redocly-loader/query-named-components.ts +200 -0
- package/src/adapters/redocly-loader/redocly-config-source.ts +11 -1
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +63 -17
- package/src/adapters/redocly-loader/ref-access.ts +32 -5
- package/src/adapters/redocly-loader/ref-holders.ts +75 -14
- package/src/adapters/redocly-loader/remote-fetch.ts +37 -2
- package/src/adapters/redocly-loader/remote-node-limit.ts +44 -4
- package/src/adapters/redocly-loader/schema-source.ts +15 -3
- package/src/adapters/terminal-logger/terminal-logger.ts +10 -4
- package/src/api/cwd-option.ts +76 -0
- package/src/api/default-ports.ts +33 -10
- package/src/api/document-refs.ts +29 -1
- package/src/api/document-run.ts +7 -32
- package/src/api/legacy-utils.ts +1 -1
- package/src/api/openapi-ts.ts +20 -4
- package/src/api/option-errors.ts +22 -0
- package/src/api/ref-limit-options.ts +69 -0
- package/src/api/transform-schema.ts +20 -5
- package/src/application/ports/file-system.ts +8 -0
- package/src/application/ports/redocly-config-source.ts +5 -0
- package/src/application/transform/declared-value-types.ts +42 -0
- package/src/application/transform/document/document.ts +4 -2
- package/src/application/transform/document/exploded-query-values.ts +88 -0
- package/src/application/transform/document/header.ts +25 -5
- package/src/application/transform/document/parameter.ts +4 -4
- package/src/application/transform/document/parameters.ts +45 -10
- package/src/application/transform/document/path-item.ts +3 -3
- package/src/application/transform/document/path-template-key.ts +152 -0
- package/src/application/transform/document/path-template-refs.ts +127 -0
- package/src/application/transform/document/paths-enum.ts +23 -9
- package/src/application/transform/document/paths.ts +53 -123
- package/src/application/transform/document/webhooks.ts +18 -5
- package/src/application/transform/footer/document-footer.ts +18 -8
- package/src/application/transform/helper-types.ts +4 -4
- package/src/application/transform/ref-pointer/generated-location.ts +1 -1
- package/src/application/transform/ref-pointer/generated-shape.ts +115 -83
- package/src/application/transform/ref-pointer/inline-cycles.ts +1 -1
- package/src/application/transform/ref-pointer/located-node.ts +38 -0
- package/src/application/transform/ref-pointer/original-index.ts +106 -20
- package/src/application/transform/ref-pointer/pointer-type.ts +1 -1
- package/src/application/transform/ref-pointer/pointer-walk.ts +82 -8
- package/src/application/transform/ref-pointer/property-shape.ts +106 -0
- package/src/application/transform/ref-pointer/typed-walk.ts +143 -29
- package/src/application/transform/schema/access-flags.ts +5 -2
- package/src/application/transform/schema/index-signature-type.ts +31 -0
- package/src/application/transform/schema/input-location.ts +35 -0
- package/src/application/transform/schema/object-type.ts +18 -8
- package/src/application/transform/schema/schema-transform.ts +2 -1
- package/src/application/transform/schema/unevaluated-properties.ts +4 -44
- package/src/cli/api-logger.ts +17 -0
- package/src/cli/api-settings.ts +12 -20
- package/src/cli/api-writes.ts +11 -3
- package/src/cli/cli-arguments.ts +57 -92
- package/src/cli/cli-dependencies.ts +7 -2
- package/src/cli/cli-errors.ts +23 -0
- package/src/cli/cli-flag-names.ts +99 -0
- package/src/cli/cli-output.ts +31 -16
- package/src/cli/main.ts +12 -5
- package/src/cli/redocly-apis.ts +56 -20
- package/src/cli/ref-limits.ts +21 -2
- package/src/cli/run-cli.ts +39 -7
- package/src/cli/written-flags.ts +131 -0
- package/src/domain/cause-secrets.ts +59 -25
- package/src/domain/errors.ts +3 -0
- package/src/domain/location-text.ts +27 -0
- package/src/domain/openapi/document-places.ts +78 -0
- package/src/domain/output-fixes.ts +9 -2
- package/src/domain/terminal-text.ts +44 -0
- package/src/domain/warnings.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-fetch.mjs","sources":["../../../src/adapters/redocly-loader/remote-fetch.ts"],"sourcesContent":["import type { Config as RedoclyConfig } from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { type ConfiguredHeader, headersFor } from \"./configured-headers.js\";\nimport { RemoteBodyLimit, type RemoteBodyLimits } from \"./remote-body-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * How the loader fetches remote documents: a time limit, size limits, the host limit and the\n * configured headers checked on every redirect.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** The fetch Redocly calls for a remote document (`resolve.http.customFetch`). */\nexport type RemoteFetch = (input: string, init?: RequestInit) => Promise<Response>;\n\n/**\n * Redirects followed for one remote document. The loader follows them itself, so that each hop is\n * checked; 20 is the limit of the Fetch standard, which fetch applied before.\n */\nexport const MAX_REDIRECTS = 20;\n\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\n\n/** Statuses whose responses have no body; `new Response()` refuses a body for them. */\nconst NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);\n\n/**\n * A response whose body is already read within the size limits, so that reading it again cannot\n * wait, fail or take more memory.\n */\nasync function bufferedResponse(\n response: Response,\n url: string,\n bodyLimit: RemoteBodyLimit,\n): Promise<Response> {\n const body = await bodyLimit.read(response, url);\n const { status, statusText, headers } = response;\n return new Response(NULL_BODY_STATUSES.has(status) ? null : body, {\n status,\n statusText,\n headers,\n });\n}\n\n/**\n * Codes of a connection that closed before any response came. The server can close a kept-alive\n * connection while the process is busy (parsing a large document, generating another API) and\n * has not read the close yet; the next request on that connection then fails with one of these.\n */\nconst CLOSED_CONNECTION_CODES: ReadonlySet<string> = new Set([\n \"ECONNRESET\",\n \"EPIPE\",\n \"UND_ERR_SOCKET\",\n]);\n\n/** Methods that may be sent again without a second effect (RFC 9110 §9.2.2). */\nconst IDEMPOTENT_METHODS: ReadonlySet<string> = new Set([\"GET\", \"HEAD\"]);\nconst DEFAULT_METHOD = \"GET\";\n\n/** Whether fetch failed because its connection closed before any response came. */\nfunction isClosedConnection(error: unknown): boolean {\n const cause = error instanceof TypeError ? error.cause : undefined;\n const code = cause instanceof Error && \"code\" in cause ? cause.code : undefined;\n return typeof code === \"string\" && CLOSED_CONNECTION_CODES.has(code);\n}\n\n/**\n * Fetch, and send the request once more when its connection closed before any response came, as\n * RFC 9112 §9.3.1 allows for an idempotent request. Fetch drops a connection that failed, so the\n * second attempt goes out on another one; a second failure is the server's answer and is thrown.\n */\nasync function fetchOnOpenConnection(input: string, init: RequestInit): Promise<Response> {\n try {\n return await fetch(input, init);\n } catch (error) {\n const method = (init.method ?? DEFAULT_METHOD).toUpperCase();\n if (!(IDEMPOTENT_METHODS.has(method) && isClosedConnection(error))) {\n throw error;\n }\n return fetch(input, init);\n }\n}\n\n/**\n * Fetch with a time limit for the whole response, body included: Node's own fetch waits five\n * minutes for a server that never answers, and a server that sends its body one byte at a time\n * never finishes. The body is read here, inside the limit; when Redocly read it, the abort\n * surfaced as a `DOMException` whose read-only `message` Redocly tried to change, which gave\n * \"Cannot set property message of which has only a getter\" instead of the time limit.\n */\nfunction fetchWithTimeout(timeoutMs: number, bodyLimit: RemoteBodyLimit): RemoteFetch {\n return async (input, init = {}) => {\n try {\n // One signal for both attempts, so that a request sent again stays within the same limit.\n const signal = AbortSignal.timeout(timeoutMs);\n const response = await fetchOnOpenConnection(input, { ...init, signal });\n return await bufferedResponse(response, input, bodyLimit);\n } catch (cause) {\n const isTimeout = cause instanceof Error && cause.name === \"TimeoutError\";\n throw isTimeout ? new Error(`Timed out after ${timeoutMs} ms loading ${input}`) : cause;\n }\n };\n}\n\n/** A configured `customFetch` with its bodies read within the size limits. */\nfunction fetchWithBodyLimit(fetchImpl: RemoteFetch, bodyLimit: RemoteBodyLimit): RemoteFetch {\n return async (input, init) => bufferedResponse(await fetchImpl(input, init), input, bodyLimit);\n}\n\n/** What every hop of a remote fetch is checked against. */\nexport interface RedirectPolicy {\n /** `resolve.http.headers` of the Redocly config; chosen again for the URL of every hop. */\n readonly headers: readonly ConfiguredHeader[];\n /** `--remote-ref-hosts`; undefined allows every host. */\n readonly hosts: readonly string[] | undefined;\n}\n\n/**\n * Follow redirects one by one. Fetch drops only `Authorization`, `Cookie` and\n * `Proxy-Authorization` on a redirect to another origin and keeps every other header (an\n * `X-Api-Key`), so the configured headers are chosen for each hop's own URL instead, and a hop to\n * a host that is not listed is refused before it is fetched. The first URL was checked by the\n * resolver (or is the root document). Redocly passes only the configured headers in `init`, and\n * it gets an empty list of them (`resolveConfigWithAccess`), so `init.headers` is replaced.\n */\nexport function fetchWithCheckedRedirects(\n fetchImpl: RemoteFetch,\n policy: RedirectPolicy,\n): RemoteFetch {\n return async (input, init = {}) => {\n let url = new URL(input);\n for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {\n if (hop > 0 && policy.hosts !== undefined && !isHostAllowed(url, policy.hosts)) {\n throw refForbiddenError(url.href, {\n reason: \"host-not-allowed\",\n allowedHosts: [...policy.hosts],\n });\n }\n const headers = await headersFor(url, policy.headers);\n const response = await fetchImpl(url.href, { ...init, headers, redirect: \"manual\" });\n const location = response.headers.get(\"location\");\n if (!REDIRECT_STATUSES.has(response.status) || location === null) {\n return response;\n }\n url = new URL(location, url);\n }\n throw new Error(`More than ${MAX_REDIRECTS} redirects loading ${input}`);\n };\n}\n\n/** Bounds on the remote documents of one load (threat model T2, ADR-033). */\nexport interface RemoteDocumentLimits extends RemoteBodyLimits {\n /** Remote documents one load may fetch, the root included; one more is refused. */\n readonly maxDocuments: number;\n /** Remote documents fetched at the same time; the others wait for a free slot. */\n readonly maxParallelFetches: number;\n /** Nodes (keys and values) the remote documents of one load may parse into together. */\n readonly maxNodes: number;\n}\n\n/**\n * At most `limit` calls of the fetch run at once; the others wait in order. A finished call hands\n * its slot to the next waiting one.\n */\nexport function fetchWithConcurrencyLimit(fetchImpl: RemoteFetch, limit: number): RemoteFetch {\n let running = 0;\n const waiting: (() => void)[] = [];\n return async (input, init) => {\n if (running < limit) {\n running += 1;\n } else {\n await new Promise<void>((resolve) => waiting.push(resolve));\n }\n try {\n return await fetchImpl(input, init);\n } finally {\n const next = waiting.shift();\n if (next === undefined) {\n running -= 1;\n } else {\n next();\n }\n }\n };\n}\n\nexport interface RemoteFetchOptions {\n /** Time limit of one fetch and its body, unless the Redocly config has its own `customFetch`. */\n readonly timeoutMs: number;\n readonly access: RefAccess;\n readonly maxParallelFetches: number;\n readonly bodyLimits: RemoteBodyLimits;\n}\n\n/**\n * The resolve config with the fetch the loader uses. A `customFetch` of the Redocly config is\n * kept and wrapped, so that each redirect is checked, each body is read within the size limits\n * and only a few documents load at once. The\n * configured headers move into that wrapper: Redocly would match them against the raw URL and\n * send them on through redirects.\n */\nexport function resolveConfigWithAccess(\n resolve: ResolveConfig,\n options: RemoteFetchOptions,\n): ResolveConfig {\n // Redocly types `customFetch` as `Function`; it is called as fetch is.\n const configured = resolve.http.customFetch as RemoteFetch | undefined;\n // One limit per load, so that the total covers every document this resolver fetches.\n const bodyLimit = new RemoteBodyLimit(options.bodyLimits);\n const base =\n configured === undefined\n ? fetchWithTimeout(options.timeoutMs, bodyLimit)\n : fetchWithBodyLimit(configured, bodyLimit);\n const checked = fetchWithCheckedRedirects(base, {\n headers: resolve.http.headers,\n hosts: options.access.remoteHosts,\n });\n // The limit is outside the redirects, so all hops of one document share one slot.\n const customFetch = fetchWithConcurrencyLimit(checked, options.maxParallelFetches);\n return { ...resolve, http: { ...resolve.http, headers: [], customFetch } };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remote-fetch.mjs","sources":["../../../src/adapters/redocly-loader/remote-fetch.ts"],"sourcesContent":["import type { Config as RedoclyConfig } from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { type ConfiguredHeader, headersFor } from \"./configured-headers.js\";\nimport { RemoteBodyLimit, type RemoteBodyLimits } from \"./remote-body-limit.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * How the loader fetches remote documents: a time limit, size limits, the host limit and the\n * configured headers checked on every redirect.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** The fetch Redocly calls for a remote document (`resolve.http.customFetch`). */\nexport type RemoteFetch = (input: string, init?: RequestInit) => Promise<Response>;\n\n/**\n * Redirects followed for one remote document. The loader follows them itself, so that each hop is\n * checked; 20 is the limit of the Fetch standard, which fetch applied before.\n */\nexport const MAX_REDIRECTS = 20;\n\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\n\n/** Statuses whose responses have no body; `new Response()` refuses a body for them. */\nconst NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);\n\n/**\n * A response whose body is already read within the size limits, so that reading it again cannot\n * wait, fail or take more memory.\n */\nasync function bufferedResponse(\n response: Response,\n url: string,\n bodyLimit: RemoteBodyLimit,\n): Promise<Response> {\n const body = await bodyLimit.read(response, url);\n const { status, statusText, headers } = response;\n return new Response(NULL_BODY_STATUSES.has(status) ? null : body, {\n status,\n statusText,\n headers,\n });\n}\n\n/**\n * Codes of a connection that closed before any response came. The server can close a kept-alive\n * connection while the process is busy (parsing a large document, generating another API) and\n * has not read the close yet; the next request on that connection then fails with one of these.\n */\nconst CLOSED_CONNECTION_CODES: ReadonlySet<string> = new Set([\n \"ECONNRESET\",\n \"EPIPE\",\n \"UND_ERR_SOCKET\",\n]);\n\n/** Methods that may be sent again without a second effect (RFC 9110 §9.2.2). */\nconst IDEMPOTENT_METHODS: ReadonlySet<string> = new Set([\"GET\", \"HEAD\"]);\nconst DEFAULT_METHOD = \"GET\";\n\n/** Whether fetch failed because its connection closed before any response came. */\nfunction isClosedConnection(error: unknown): boolean {\n const cause = error instanceof TypeError ? error.cause : undefined;\n const code = cause instanceof Error && \"code\" in cause ? cause.code : undefined;\n return typeof code === \"string\" && CLOSED_CONNECTION_CODES.has(code);\n}\n\n/**\n * Fetch, and send the request once more when its connection closed before any response came, as\n * RFC 9112 §9.3.1 allows for an idempotent request. Fetch drops a connection that failed, so the\n * second attempt goes out on another one; a second failure is the server's answer and is thrown.\n */\nasync function fetchOnOpenConnection(input: string, init: RequestInit): Promise<Response> {\n try {\n return await fetch(input, init);\n } catch (error) {\n const method = (init.method ?? DEFAULT_METHOD).toUpperCase();\n if (!(IDEMPOTENT_METHODS.has(method) && isClosedConnection(error))) {\n throw error;\n }\n return fetch(input, init);\n }\n}\n\n/**\n * Fetch with a time limit for the whole response, body included: Node's own fetch waits five\n * minutes for a server that never answers, and a server that sends its body one byte at a time\n * never finishes. The body is read here, inside the limit; when Redocly read it, the abort\n * surfaced as a `DOMException` whose read-only `message` Redocly tried to change, which gave\n * \"Cannot set property message of which has only a getter\" instead of the time limit.\n */\nfunction fetchWithTimeout(timeoutMs: number, bodyLimit: RemoteBodyLimit): RemoteFetch {\n return async (input, init = {}) => {\n try {\n // One signal for both attempts, so that a request sent again stays within the same limit.\n const signal = AbortSignal.timeout(timeoutMs);\n const response = await fetchOnOpenConnection(input, { ...init, signal });\n return await bufferedResponse(response, input, bodyLimit);\n } catch (cause) {\n const isTimeout = cause instanceof Error && cause.name === \"TimeoutError\";\n throw isTimeout ? new Error(`Timed out after ${timeoutMs} ms loading ${input}`) : cause;\n }\n };\n}\n\n/** A configured `customFetch` with its bodies read within the size limits. */\nfunction fetchWithBodyLimit(fetchImpl: RemoteFetch, bodyLimit: RemoteBodyLimit): RemoteFetch {\n return async (input, init) => bufferedResponse(await fetchImpl(input, init), input, bodyLimit);\n}\n\n/**\n * The URL each remote document was read from in the end, when redirects moved it. A relative\n * `$ref` resolves against the URL the document was retrieved from (RFC 3986, section 5.1.3), so\n * `/latest` that redirects to `/static/v1/root.yaml` loads `./pet.yaml` from `/static/v1/`.\n * Redocly reads only the body of the response, so the fetch records the URL here.\n */\nexport class RedirectedUrls {\n readonly #finalUrls = new Map<string, string>();\n\n record(requested: string, final: string): void {\n if (requested !== final) {\n this.#finalUrls.set(requested, final);\n }\n }\n\n /** The URL the document of `requested` came from; `requested` when it was not redirected. */\n finalUrlOf(requested: string): string {\n return this.#finalUrls.get(requested) ?? requested;\n }\n}\n\n/** What every hop of a remote fetch is checked against. */\nexport interface RedirectPolicy {\n /** `resolve.http.headers` of the Redocly config; chosen again for the URL of every hop. */\n readonly headers: readonly ConfiguredHeader[];\n /** `--remote-ref-hosts`; undefined allows every host. */\n readonly hosts: readonly string[] | undefined;\n /**\n * The URL of the root document, if it is remote. The developer chose it, so neither it nor its\n * redirects are limited to the hosts (ADR-017): a spec URL that redirects to a CDN loads.\n */\n readonly rootUrl: string | undefined;\n /** Where the final URL of each redirected document is recorded. */\n readonly redirects: RedirectedUrls;\n}\n\n/**\n * Follow redirects one by one. Fetch drops only `Authorization`, `Cookie` and\n * `Proxy-Authorization` on a redirect to another origin and keeps every other header (an\n * `X-Api-Key`), so the configured headers are chosen for each hop's own URL instead, and a hop to\n * a host that is not listed is refused before it is fetched. The first URL was checked by the\n * resolver (or is the root document). Redocly passes only the configured headers in `init`, and\n * it gets an empty list of them (`resolveConfigWithAccess`), so `init.headers` is replaced.\n */\nexport function fetchWithCheckedRedirects(\n fetchImpl: RemoteFetch,\n policy: RedirectPolicy,\n): RemoteFetch {\n return async (input, init = {}) => {\n const hosts = input === policy.rootUrl ? undefined : policy.hosts;\n let url = new URL(input);\n for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {\n if (hop > 0 && hosts !== undefined && !isHostAllowed(url, hosts)) {\n throw refForbiddenError(url.href, {\n reason: \"host-not-allowed\",\n allowedHosts: [...hosts],\n });\n }\n const headers = await headersFor(url, policy.headers);\n const response = await fetchImpl(url.href, { ...init, headers, redirect: \"manual\" });\n const location = response.headers.get(\"location\");\n if (!REDIRECT_STATUSES.has(response.status) || location === null) {\n policy.redirects.record(input, url.href);\n return response;\n }\n url = new URL(location, url);\n }\n throw new Error(`More than ${MAX_REDIRECTS} redirects loading ${input}`);\n };\n}\n\n/** Bounds on the remote documents of one load (threat model T2, ADR-033). */\nexport interface RemoteDocumentLimits extends RemoteBodyLimits {\n /** Remote documents one load may fetch, the root included; one more is refused. */\n readonly maxDocuments: number;\n /** Remote documents fetched at the same time; the others wait for a free slot. */\n readonly maxParallelFetches: number;\n /** Nodes (keys and values) the remote documents of one load may parse into together. */\n readonly maxNodes: number;\n}\n\n/**\n * At most `limit` calls of the fetch run at once; the others wait in order. A finished call hands\n * its slot to the next waiting one.\n */\nexport function fetchWithConcurrencyLimit(fetchImpl: RemoteFetch, limit: number): RemoteFetch {\n let running = 0;\n const waiting: (() => void)[] = [];\n return async (input, init) => {\n if (running < limit) {\n running += 1;\n } else {\n await new Promise<void>((resolve) => waiting.push(resolve));\n }\n try {\n return await fetchImpl(input, init);\n } finally {\n const next = waiting.shift();\n if (next === undefined) {\n running -= 1;\n } else {\n next();\n }\n }\n };\n}\n\nexport interface RemoteFetchOptions {\n /** Time limit of one fetch and its body, unless the Redocly config has its own `customFetch`. */\n readonly timeoutMs: number;\n readonly access: RefAccess;\n /** The root document's URL, whose redirects are not limited to the hosts. */\n readonly rootUrl: string | undefined;\n readonly maxParallelFetches: number;\n readonly bodyLimits: RemoteBodyLimits;\n readonly redirects: RedirectedUrls;\n}\n\n/**\n * The resolve config with the fetch the loader uses. A `customFetch` of the Redocly config is\n * kept and wrapped, so that each redirect is checked, each body is read within the size limits\n * and only a few documents load at once. The\n * configured headers move into that wrapper: Redocly would match them against the raw URL and\n * send them on through redirects.\n */\nexport function resolveConfigWithAccess(\n resolve: ResolveConfig,\n options: RemoteFetchOptions,\n): ResolveConfig {\n // Redocly types `customFetch` as `Function`; it is called as fetch is.\n const configured = resolve.http.customFetch as RemoteFetch | undefined;\n // One limit per load, so that the total covers every document this resolver fetches.\n const bodyLimit = new RemoteBodyLimit(options.bodyLimits);\n const base =\n configured === undefined\n ? fetchWithTimeout(options.timeoutMs, bodyLimit)\n : fetchWithBodyLimit(configured, bodyLimit);\n const checked = fetchWithCheckedRedirects(base, {\n headers: resolve.http.headers,\n hosts: options.access.remoteHosts,\n rootUrl: options.rootUrl,\n redirects: options.redirects,\n });\n // The limit is outside the redirects, so all hops of one document share one slot.\n const customFetch = fetchWithConcurrencyLimit(checked, options.maxParallelFetches);\n return { ...resolve, http: { ...resolve.http, headers: [], customFetch } };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,24 @@ const schemaLoadErrors = require('./schema-load-errors.cjs');
|
|
|
5
5
|
|
|
6
6
|
const PARSED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".yml", ".yaml"]);
|
|
7
7
|
const PARSED_MIME_TYPE = /(json|yaml|openapi)/;
|
|
8
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".html", ".htm"]);
|
|
9
|
+
const TEXT_MIME_TYPE = /^text\/(markdown|x-markdown|html)\b/;
|
|
10
|
+
const REMOTE_URL_RE = /^https?:\/\//;
|
|
11
|
+
const PATH_SEPARATOR_RE = /[/\\]/;
|
|
12
|
+
const EXTENSION_START = ".";
|
|
13
|
+
function extensionOf(path) {
|
|
14
|
+
const name = path.split(PATH_SEPARATOR_RE).at(-1) ?? "";
|
|
15
|
+
const start = name.lastIndexOf(EXTENSION_START);
|
|
16
|
+
return start === -1 ? "" : name.slice(start);
|
|
17
|
+
}
|
|
18
|
+
function documentPath(absoluteRef) {
|
|
19
|
+
return REMOTE_URL_RE.test(absoluteRef) && URL.canParse(absoluteRef) ? new URL(absoluteRef).pathname : absoluteRef;
|
|
20
|
+
}
|
|
8
21
|
function isParsedSource(source, isRoot) {
|
|
9
|
-
const { absoluteRef, mimeType } = source;
|
|
10
|
-
const extension =
|
|
11
|
-
|
|
22
|
+
const { absoluteRef, mimeType = "" } = source;
|
|
23
|
+
const extension = extensionOf(documentPath(absoluteRef));
|
|
24
|
+
const isTextUrl = TEXT_EXTENSIONS.has(extension) || TEXT_MIME_TYPE.test(mimeType);
|
|
25
|
+
return isRoot || PARSED_EXTENSIONS.has(extension) || PARSED_MIME_TYPE.test(mimeType) || REMOTE_URL_RE.test(absoluteRef) && !isTextUrl;
|
|
12
26
|
}
|
|
13
27
|
const NODE_OPENED = "open";
|
|
14
28
|
class RemoteNodeLimit {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-node-limit.cjs","sources":["../../../src/adapters/redocly-loader/remote-node-limit.ts"],"sourcesContent":["import { type Document, parseYaml, type Source } from \"@redocly/openapi-core\";\nimport type { OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Bounds the parsed size of the remote documents of one load (threat model T2). The byte limits\n * do not bound memory: parsing multiplies it by the density of the text, and 250 MiB of `{},`\n * (87 million empty objects, under a quarter of a megabyte as gzip) took the process past its\n * 4 GB heap. The parser reports every node it builds, keys included, and the parse stops when\n * the nodes of all remote documents of the load pass the limit, before their memory is taken.\n */\n\n/** The extensions that Redocly 1.34's `BaseResolver.parseDocument` parses as YAML or JSON. */\nconst PARSED_EXTENSIONS: ReadonlySet<string> = new Set([\".json\", \".yml\", \".yaml\"]);\nconst PARSED_MIME_TYPE = /(json|yaml|openapi)/;\n\n/**
|
|
1
|
+
{"version":3,"file":"remote-node-limit.cjs","sources":["../../../src/adapters/redocly-loader/remote-node-limit.ts"],"sourcesContent":["import { type Document, parseYaml, type Source } from \"@redocly/openapi-core\";\nimport type { OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Bounds the parsed size of the remote documents of one load (threat model T2). The byte limits\n * do not bound memory: parsing multiplies it by the density of the text, and 250 MiB of `{},`\n * (87 million empty objects, under a quarter of a megabyte as gzip) took the process past its\n * 4 GB heap. The parser reports every node it builds, keys included, and the parse stops when\n * the nodes of all remote documents of the load pass the limit, before their memory is taken.\n */\n\n/** The extensions that Redocly 1.34's `BaseResolver.parseDocument` parses as YAML or JSON. */\nconst PARSED_EXTENSIONS: ReadonlySet<string> = new Set([\".json\", \".yml\", \".yaml\"]);\nconst PARSED_MIME_TYPE = /(json|yaml|openapi)/;\n/**\n * The text formats that a `$ref` loads as text, as a `description` from a Markdown file. A URL\n * keeps its document as text only when its extension or type names one of them: the last segment\n * of an API's path often has a dot that is no file type (`/schemas/v1.0`, `/schemas/pet.v2`).\n */\nconst TEXT_EXTENSIONS: ReadonlySet<string> = new Set([\".md\", \".markdown\", \".txt\", \".html\", \".htm\"]);\nconst TEXT_MIME_TYPE = /^text\\/(markdown|x-markdown|html)\\b/;\nconst REMOTE_URL_RE = /^https?:\\/\\//;\nconst PATH_SEPARATOR_RE = /[/\\\\]/;\nconst EXTENSION_START = \".\";\n\n/** The extension of the last segment of a path, with its dot; empty when it has none. */\nfunction extensionOf(path: string): string {\n const name = path.split(PATH_SEPARATOR_RE).at(-1) ?? \"\";\n const start = name.lastIndexOf(EXTENSION_START);\n return start === -1 ? \"\" : name.slice(start);\n}\n\n/**\n * The path of a document: the path of a URL, without its query and fragment, or the file path.\n * Redocly took the extension of the whole URL, so `pet.yaml?token=…` had none it knew.\n */\nfunction documentPath(absoluteRef: string): string {\n return REMOTE_URL_RE.test(absoluteRef) && URL.canParse(absoluteRef)\n ? new URL(absoluteRef).pathname\n : absoluteRef;\n}\n\n/**\n * Whether a source is parsed as YAML or JSON, or kept as text (a Markdown `description`, say).\n * It is parsed when it is the root, when its type or the extension of its path says JSON or YAML,\n * and when it is a URL whose path and type name no text format: an API serves its documents at\n * paths like `/schemas/pet` or `/schemas/v1.0`, often as `text/plain`, and Redocly kept those as\n * text, which failed as a schema. A local file is kept as text unless its name says JSON or YAML,\n * as Redocly keeps it.\n */\nexport function isParsedSource(source: Source, isRoot: boolean): boolean {\n const { absoluteRef, mimeType = \"\" } = source;\n const extension = extensionOf(documentPath(absoluteRef));\n const isTextUrl = TEXT_EXTENSIONS.has(extension) || TEXT_MIME_TYPE.test(mimeType);\n return (\n isRoot ||\n PARSED_EXTENSIONS.has(extension) ||\n PARSED_MIME_TYPE.test(mimeType) ||\n (REMOTE_URL_RE.test(absoluteRef) && !isTextUrl)\n );\n}\n\n/** The parser's event for a node it starts to build. */\nconst NODE_OPENED = \"open\";\n\n/** One instance per load: the count covers every remote document of that load. */\nexport class RemoteNodeLimit {\n readonly #maxNodes: number;\n #nodes = 0;\n\n constructor(maxNodes: number) {\n this.#maxNodes = maxNodes;\n }\n\n /**\n * The parsed document of `source`, as Redocly parses it. Fails with `SCHEMA_REF_FORBIDDEN`\n * (`remote-documents-too-many-nodes`) once the nodes of the load pass the limit.\n */\n parse(source: Source): Document {\n const listener = (event: string): void => {\n if (event !== NODE_OPENED) {\n return;\n }\n this.#nodes += 1;\n const refusal = this.#refusal(source.absoluteRef);\n if (refusal !== undefined) {\n throw refusal;\n }\n };\n return { source, parsed: parseYaml(source.body, { filename: source.absoluteRef, listener }) };\n }\n\n #refusal(url: string): OpenAPITypesafeError | undefined {\n return this.#nodes > this.#maxNodes\n ? refForbiddenError(url, {\n reason: \"remote-documents-too-many-nodes\",\n maxNodes: this.#maxNodes,\n })\n : undefined;\n }\n}\n"],"names":["parseYaml","refForbiddenError"],"mappings":";;;;;AAaA,MAAM,oCAAyC,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,OAAO,CAAC,CAAA;AACjF,MAAM,gBAAA,GAAmB,qBAAA;AAMzB,MAAM,eAAA,uBAA2C,GAAA,CAAI,CAAC,OAAO,WAAA,EAAa,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAC,CAAA;AAClG,MAAM,cAAA,GAAiB,qCAAA;AACvB,MAAM,aAAA,GAAgB,cAAA;AACtB,MAAM,iBAAA,GAAoB,OAAA;AAC1B,MAAM,eAAA,GAAkB,GAAA;AAGxB,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,MAAM,OAAO,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA,CAAE,EAAA,CAAG,EAAE,CAAA,IAAK,EAAA;AACrD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,CAAY,eAAe,CAAA;AAC9C,EAAA,OAAO,KAAA,KAAU,EAAA,GAAK,EAAA,GAAK,IAAA,CAAK,MAAM,KAAK,CAAA;AAC7C;AAMA,SAAS,aAAa,WAAA,EAA6B;AACjD,EAAA,OAAO,aAAA,CAAc,IAAA,CAAK,WAAW,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,WAAW,CAAA,GAC9D,IAAI,GAAA,CAAI,WAAW,CAAA,CAAE,QAAA,GACrB,WAAA;AACN;AAUO,SAAS,cAAA,CAAe,QAAgB,MAAA,EAA0B;AACvE,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,GAAW,EAAA,EAAG,GAAI,MAAA;AACvC,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,YAAA,CAAa,WAAW,CAAC,CAAA;AACvD,EAAA,MAAM,YAAY,eAAA,CAAgB,GAAA,CAAI,SAAS,CAAA,IAAK,cAAA,CAAe,KAAK,QAAQ,CAAA;AAChF,EAAA,OACE,MAAA,IACA,iBAAA,CAAkB,GAAA,CAAI,SAAS,CAAA,IAC/B,gBAAA,CAAiB,IAAA,CAAK,QAAQ,CAAA,IAC7B,aAAA,CAAc,IAAA,CAAK,WAAW,KAAK,CAAC,SAAA;AAEzC;AAGA,MAAM,WAAA,GAAc,MAAA;AAGb,MAAM,eAAA,CAAgB;AAAA,EAClB,SAAA;AAAA,EACT,MAAA,GAAS,CAAA;AAAA,EAET,YAAY,QAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAA,EAA0B;AAC9B,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAwB;AACxC,MAAA,IAAI,UAAU,WAAA,EAAa;AACzB,QAAA;AAAA,MACF;AACA,MAAA,IAAA,CAAK,MAAA,IAAU,CAAA;AACf,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA;AAChD,MAAA,IAAI,YAAY,MAAA,EAAW;AACzB,QAAA,MAAM,OAAA;AAAA,MACR;AAAA,IACF,CAAA;AACA,IAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQA,qBAAA,CAAU,MAAA,CAAO,IAAA,EAAM,EAAE,QAAA,EAAU,MAAA,CAAO,WAAA,EAAa,QAAA,EAAU,CAAA,EAAE;AAAA,EAC9F;AAAA,EAEA,SAAS,GAAA,EAA+C;AACtD,IAAA,OAAO,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,SAAA,GACtBC,mCAAkB,GAAA,EAAK;AAAA,MACrB,MAAA,EAAQ,iCAAA;AAAA,MACR,UAAU,IAAA,CAAK;AAAA,KAChB,CAAA,GACD,MAAA;AAAA,EACN;AACF;;;"}
|
|
@@ -3,10 +3,24 @@ import { refForbiddenError } from './schema-load-errors.mjs';
|
|
|
3
3
|
|
|
4
4
|
const PARSED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".yml", ".yaml"]);
|
|
5
5
|
const PARSED_MIME_TYPE = /(json|yaml|openapi)/;
|
|
6
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".html", ".htm"]);
|
|
7
|
+
const TEXT_MIME_TYPE = /^text\/(markdown|x-markdown|html)\b/;
|
|
8
|
+
const REMOTE_URL_RE = /^https?:\/\//;
|
|
9
|
+
const PATH_SEPARATOR_RE = /[/\\]/;
|
|
10
|
+
const EXTENSION_START = ".";
|
|
11
|
+
function extensionOf(path) {
|
|
12
|
+
const name = path.split(PATH_SEPARATOR_RE).at(-1) ?? "";
|
|
13
|
+
const start = name.lastIndexOf(EXTENSION_START);
|
|
14
|
+
return start === -1 ? "" : name.slice(start);
|
|
15
|
+
}
|
|
16
|
+
function documentPath(absoluteRef) {
|
|
17
|
+
return REMOTE_URL_RE.test(absoluteRef) && URL.canParse(absoluteRef) ? new URL(absoluteRef).pathname : absoluteRef;
|
|
18
|
+
}
|
|
6
19
|
function isParsedSource(source, isRoot) {
|
|
7
|
-
const { absoluteRef, mimeType } = source;
|
|
8
|
-
const extension =
|
|
9
|
-
|
|
20
|
+
const { absoluteRef, mimeType = "" } = source;
|
|
21
|
+
const extension = extensionOf(documentPath(absoluteRef));
|
|
22
|
+
const isTextUrl = TEXT_EXTENSIONS.has(extension) || TEXT_MIME_TYPE.test(mimeType);
|
|
23
|
+
return isRoot || PARSED_EXTENSIONS.has(extension) || PARSED_MIME_TYPE.test(mimeType) || REMOTE_URL_RE.test(absoluteRef) && !isTextUrl;
|
|
10
24
|
}
|
|
11
25
|
const NODE_OPENED = "open";
|
|
12
26
|
class RemoteNodeLimit {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-node-limit.mjs","sources":["../../../src/adapters/redocly-loader/remote-node-limit.ts"],"sourcesContent":["import { type Document, parseYaml, type Source } from \"@redocly/openapi-core\";\nimport type { OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Bounds the parsed size of the remote documents of one load (threat model T2). The byte limits\n * do not bound memory: parsing multiplies it by the density of the text, and 250 MiB of `{},`\n * (87 million empty objects, under a quarter of a megabyte as gzip) took the process past its\n * 4 GB heap. The parser reports every node it builds, keys included, and the parse stops when\n * the nodes of all remote documents of the load pass the limit, before their memory is taken.\n */\n\n/** The extensions that Redocly 1.34's `BaseResolver.parseDocument` parses as YAML or JSON. */\nconst PARSED_EXTENSIONS: ReadonlySet<string> = new Set([\".json\", \".yml\", \".yaml\"]);\nconst PARSED_MIME_TYPE = /(json|yaml|openapi)/;\n\n/**
|
|
1
|
+
{"version":3,"file":"remote-node-limit.mjs","sources":["../../../src/adapters/redocly-loader/remote-node-limit.ts"],"sourcesContent":["import { type Document, parseYaml, type Source } from \"@redocly/openapi-core\";\nimport type { OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Bounds the parsed size of the remote documents of one load (threat model T2). The byte limits\n * do not bound memory: parsing multiplies it by the density of the text, and 250 MiB of `{},`\n * (87 million empty objects, under a quarter of a megabyte as gzip) took the process past its\n * 4 GB heap. The parser reports every node it builds, keys included, and the parse stops when\n * the nodes of all remote documents of the load pass the limit, before their memory is taken.\n */\n\n/** The extensions that Redocly 1.34's `BaseResolver.parseDocument` parses as YAML or JSON. */\nconst PARSED_EXTENSIONS: ReadonlySet<string> = new Set([\".json\", \".yml\", \".yaml\"]);\nconst PARSED_MIME_TYPE = /(json|yaml|openapi)/;\n/**\n * The text formats that a `$ref` loads as text, as a `description` from a Markdown file. A URL\n * keeps its document as text only when its extension or type names one of them: the last segment\n * of an API's path often has a dot that is no file type (`/schemas/v1.0`, `/schemas/pet.v2`).\n */\nconst TEXT_EXTENSIONS: ReadonlySet<string> = new Set([\".md\", \".markdown\", \".txt\", \".html\", \".htm\"]);\nconst TEXT_MIME_TYPE = /^text\\/(markdown|x-markdown|html)\\b/;\nconst REMOTE_URL_RE = /^https?:\\/\\//;\nconst PATH_SEPARATOR_RE = /[/\\\\]/;\nconst EXTENSION_START = \".\";\n\n/** The extension of the last segment of a path, with its dot; empty when it has none. */\nfunction extensionOf(path: string): string {\n const name = path.split(PATH_SEPARATOR_RE).at(-1) ?? \"\";\n const start = name.lastIndexOf(EXTENSION_START);\n return start === -1 ? \"\" : name.slice(start);\n}\n\n/**\n * The path of a document: the path of a URL, without its query and fragment, or the file path.\n * Redocly took the extension of the whole URL, so `pet.yaml?token=…` had none it knew.\n */\nfunction documentPath(absoluteRef: string): string {\n return REMOTE_URL_RE.test(absoluteRef) && URL.canParse(absoluteRef)\n ? new URL(absoluteRef).pathname\n : absoluteRef;\n}\n\n/**\n * Whether a source is parsed as YAML or JSON, or kept as text (a Markdown `description`, say).\n * It is parsed when it is the root, when its type or the extension of its path says JSON or YAML,\n * and when it is a URL whose path and type name no text format: an API serves its documents at\n * paths like `/schemas/pet` or `/schemas/v1.0`, often as `text/plain`, and Redocly kept those as\n * text, which failed as a schema. A local file is kept as text unless its name says JSON or YAML,\n * as Redocly keeps it.\n */\nexport function isParsedSource(source: Source, isRoot: boolean): boolean {\n const { absoluteRef, mimeType = \"\" } = source;\n const extension = extensionOf(documentPath(absoluteRef));\n const isTextUrl = TEXT_EXTENSIONS.has(extension) || TEXT_MIME_TYPE.test(mimeType);\n return (\n isRoot ||\n PARSED_EXTENSIONS.has(extension) ||\n PARSED_MIME_TYPE.test(mimeType) ||\n (REMOTE_URL_RE.test(absoluteRef) && !isTextUrl)\n );\n}\n\n/** The parser's event for a node it starts to build. */\nconst NODE_OPENED = \"open\";\n\n/** One instance per load: the count covers every remote document of that load. */\nexport class RemoteNodeLimit {\n readonly #maxNodes: number;\n #nodes = 0;\n\n constructor(maxNodes: number) {\n this.#maxNodes = maxNodes;\n }\n\n /**\n * The parsed document of `source`, as Redocly parses it. Fails with `SCHEMA_REF_FORBIDDEN`\n * (`remote-documents-too-many-nodes`) once the nodes of the load pass the limit.\n */\n parse(source: Source): Document {\n const listener = (event: string): void => {\n if (event !== NODE_OPENED) {\n return;\n }\n this.#nodes += 1;\n const refusal = this.#refusal(source.absoluteRef);\n if (refusal !== undefined) {\n throw refusal;\n }\n };\n return { source, parsed: parseYaml(source.body, { filename: source.absoluteRef, listener }) };\n }\n\n #refusal(url: string): OpenAPITypesafeError | undefined {\n return this.#nodes > this.#maxNodes\n ? refForbiddenError(url, {\n reason: \"remote-documents-too-many-nodes\",\n maxNodes: this.#maxNodes,\n })\n : undefined;\n }\n}\n"],"names":[],"mappings":";;;AAaA,MAAM,oCAAyC,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,OAAO,CAAC,CAAA;AACjF,MAAM,gBAAA,GAAmB,qBAAA;AAMzB,MAAM,eAAA,uBAA2C,GAAA,CAAI,CAAC,OAAO,WAAA,EAAa,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAC,CAAA;AAClG,MAAM,cAAA,GAAiB,qCAAA;AACvB,MAAM,aAAA,GAAgB,cAAA;AACtB,MAAM,iBAAA,GAAoB,OAAA;AAC1B,MAAM,eAAA,GAAkB,GAAA;AAGxB,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,MAAM,OAAO,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA,CAAE,EAAA,CAAG,EAAE,CAAA,IAAK,EAAA;AACrD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,CAAY,eAAe,CAAA;AAC9C,EAAA,OAAO,KAAA,KAAU,EAAA,GAAK,EAAA,GAAK,IAAA,CAAK,MAAM,KAAK,CAAA;AAC7C;AAMA,SAAS,aAAa,WAAA,EAA6B;AACjD,EAAA,OAAO,aAAA,CAAc,IAAA,CAAK,WAAW,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,WAAW,CAAA,GAC9D,IAAI,GAAA,CAAI,WAAW,CAAA,CAAE,QAAA,GACrB,WAAA;AACN;AAUO,SAAS,cAAA,CAAe,QAAgB,MAAA,EAA0B;AACvE,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,GAAW,EAAA,EAAG,GAAI,MAAA;AACvC,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,YAAA,CAAa,WAAW,CAAC,CAAA;AACvD,EAAA,MAAM,YAAY,eAAA,CAAgB,GAAA,CAAI,SAAS,CAAA,IAAK,cAAA,CAAe,KAAK,QAAQ,CAAA;AAChF,EAAA,OACE,MAAA,IACA,iBAAA,CAAkB,GAAA,CAAI,SAAS,CAAA,IAC/B,gBAAA,CAAiB,IAAA,CAAK,QAAQ,CAAA,IAC7B,aAAA,CAAc,IAAA,CAAK,WAAW,KAAK,CAAC,SAAA;AAEzC;AAGA,MAAM,WAAA,GAAc,MAAA;AAGb,MAAM,eAAA,CAAgB;AAAA,EAClB,SAAA;AAAA,EACT,MAAA,GAAS,CAAA;AAAA,EAET,YAAY,QAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAA,EAA0B;AAC9B,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAwB;AACxC,MAAA,IAAI,UAAU,WAAA,EAAa;AACzB,QAAA;AAAA,MACF;AACA,MAAA,IAAA,CAAK,MAAA,IAAU,CAAA;AACf,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA;AAChD,MAAA,IAAI,YAAY,MAAA,EAAW;AACzB,QAAA,MAAM,OAAA;AAAA,MACR;AAAA,IACF,CAAA;AACA,IAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,CAAU,MAAA,CAAO,IAAA,EAAM,EAAE,QAAA,EAAU,MAAA,CAAO,WAAA,EAAa,QAAA,EAAU,CAAA,EAAE;AAAA,EAC9F;AAAA,EAEA,SAAS,GAAA,EAA+C;AACtD,IAAA,OAAO,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,SAAA,GACtB,kBAAkB,GAAA,EAAK;AAAA,MACrB,MAAA,EAAQ,iCAAA;AAAA,MACR,UAAU,IAAA,CAAK;AAAA,KAChB,CAAA,GACD,MAAA;AAAA,EACN;AACF;;"}
|
|
@@ -38,12 +38,17 @@ function parseJsonText(text2, absoluteRef) {
|
|
|
38
38
|
throw schemaLoadErrors.parseFailedError("json", absoluteRef, cause);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
const TEXT_ORIGIN_NAMES = {
|
|
42
|
+
string: "the schema string",
|
|
43
|
+
stream: "the schema stream"
|
|
44
|
+
};
|
|
41
45
|
function parseYamlText(text2, absoluteRef, origin) {
|
|
46
|
+
const source = new openapiCore.Source(absoluteRef, text2);
|
|
42
47
|
let document;
|
|
43
48
|
try {
|
|
44
|
-
document = openapiCore.
|
|
49
|
+
document = { source, parsed: openapiCore.parseYaml(text2, { filename: TEXT_ORIGIN_NAMES[origin] }) };
|
|
45
50
|
} catch (cause) {
|
|
46
|
-
throw schemaLoadErrors.parseFailedError("yaml", absoluteRef, cause);
|
|
51
|
+
throw schemaLoadErrors.parseFailedError("yaml", absoluteRef, new openapiCore.YamlParseError(cause, source));
|
|
47
52
|
}
|
|
48
53
|
if (typeof document.parsed === "string") {
|
|
49
54
|
throw schemaLoadErrors.pathStringError(text2, origin);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-source.cjs","sources":["../../../src/adapters/redocly-loader/schema-source.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport { text } from \"node:stream/consumers\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n type Document,\n makeDocumentFromString,\n ResolveError,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport parseJson from \"parse-json\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport {\n loadFailedError,\n parseFailedError,\n pathStringError,\n sourceEmptyError,\n sourceInvalidError,\n type TextOrigin,\n urlInvalidError,\n} from \"./schema-load-errors.js\";\n\n/** Parse a schema source of any supported type into a Redocly document (ENGINEERING_PLAN 4.5). */\n\nexport interface ParseSchemaOptions {\n absoluteRef: string;\n resolver: BaseResolver;\n /** Where document text came from, for the message of text that is only a string. */\n textOrigin?: TextOrigin;\n}\n\n/**\n * Read a stream to the end as UTF-8 text, trimmed. A stream that fails rejects with\n * SCHEMA_LOAD_FAILED; listening only for `data` and `end` left the promise pending forever.\n */\nasync function readStream(stream: Readable, absoluteRef: string): Promise<string> {\n try {\n return (await text(stream)).trim();\n } catch (error) {\n throw loadFailedError(absoluteRef, error);\n }\n}\n\n/** A URL source string; a scheme is the same in any case (RFC 3986, section 3.1). */\nconst URL_PREFIX_RE = /^(?:https?|file):\\/\\//i;\n\n/**\n * A string is a URL, JSON (starts with `{`) or YAML. A string that starts like a URL but does not\n * parse as one fails with `SCHEMA_SOURCE_INVALID`, not the parser's `TypeError`.\n */\nfunction parseSchemaText(\n schema: string,\n options: ParseSchemaOptions,\n): Promise<Document> | Document {\n if (URL_PREFIX_RE.test(schema)) {\n const url = URL.parse(schema);\n if (url === null) {\n throw urlInvalidError(schema);\n }\n const absoluteRef = url.protocol === \"file:\" ? fileURLToPath(url) : url.href;\n return parseSchema(url, { absoluteRef, resolver: options.resolver });\n }\n return schema[0] === \"{\"\n ? parseJsonText(schema, options.absoluteRef)\n : parseYamlText(schema, options.absoluteRef, options.textOrigin ?? \"string\");\n}\n\nfunction parseJsonText(text: string, absoluteRef: string): Document {\n try {\n return { source: new Source(absoluteRef, text, \"application/json\"), parsed: parseJson(text) };\n } catch (cause) {\n throw parseFailedError(\"json\", absoluteRef, cause);\n }\n}\n\nfunction parseYamlText(text: string, absoluteRef: string, origin: TextOrigin): Document {\n let document: Document;\n try {\n document = makeDocumentFromString(text, absoluteRef);\n } catch (cause) {\n throw parseFailedError(\"yaml\", absoluteRef, cause);\n }\n // Text that is only a YAML string scalar, such as \"./openapi.yaml\", was meant as a file path.\n if (typeof document.parsed === \"string\") {\n throw pathStringError(text, origin);\n }\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw parseFailedError(\"yaml\", absoluteRef, failure);\n }\n return document;\n}\n\n/** The resolver throws for some failures (a missing file) and returns an error for others. */\nasync function resolveSchemaURL(options: ParseSchemaOptions): Promise<Document> {\n const result = await options.resolver\n .resolveDocument(null, options.absoluteRef, true)\n .catch((cause: unknown) => {\n throw cause instanceof YamlParseError\n ? fileParseFailedError(options.absoluteRef, cause)\n : loadFailedError(options.absoluteRef, readerError(cause));\n });\n if (\"parsed\" in result) {\n return result;\n }\n throw loadFailedError(options.absoluteRef, readerError(result));\n}\n\n/**\n * Redocly wraps a failed read in `ResolveError`, which keeps the reader's error in\n * `originalError`, not in `cause`. Unwrapped, the cause keeps the network reason (refused, DNS).\n */\nfunction readerError(error: unknown): unknown {\n let current = error;\n while (current instanceof ResolveError) {\n current = current.originalError;\n }\n return current;\n}\n\n/**\n * The resolver parses JSON files with its YAML parser too, so the format comes from the file\n * name. Some parser messages (a second YAML document) do not name the file; the error adds it.\n */\nfunction fileParseFailedError(absoluteRef: string, error: YamlParseError): Error {\n const format = absoluteRef.toLowerCase().endsWith(\".json\") ? \"json\" : \"yaml\";\n const cause = error.message.includes(absoluteRef)\n ? error\n : new Error(`${error.message} in \"${absoluteRef}\"`, { cause: error });\n return parseFailedError(format, absoluteRef, cause);\n}\n\nfunction parseSchemaObject(schema: unknown, absoluteRef: string): Document {\n if (typeof schema === \"object\" && !Array.isArray(schema)) {\n return {\n source: new Source(absoluteRef, JSON.stringify(schema), \"application/json\"),\n parsed: schema,\n };\n }\n throw sourceInvalidError(schema);\n}\n\n/** Parse any supported source: URL, stream, buffer, URL or document text, or a plain object. */\nexport async function parseSchema(schema: unknown, options: ParseSchemaOptions): Promise<Document> {\n if (!schema) {\n throw sourceEmptyError(\"Can’t parse empty schema\");\n }\n if (schema instanceof URL) {\n return resolveSchemaURL(options);\n }\n if (schema instanceof Readable) {\n const text = await readStream(schema, options.absoluteRef);\n return parseSchema(text, { ...options, textOrigin: \"stream\" });\n }\n if (schema instanceof Buffer) {\n return parseSchema(schema.toString(\"utf8\"), options);\n }\n if (typeof schema === \"string\") {\n return parseSchemaText(schema, options);\n }\n return parseSchemaObject(schema, options.absoluteRef);\n}\n"],"names":["text","loadFailedError","urlInvalidError","fileURLToPath","Source","parseJson","parseFailedError","makeDocumentFromString","pathStringError","aliasExpansionFailure","YamlParseError","ResolveError","sourceInvalidError","sourceEmptyError","Readable"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-source.cjs","sources":["../../../src/adapters/redocly-loader/schema-source.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport { text } from \"node:stream/consumers\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n type Document,\n parseYaml,\n ResolveError,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport parseJson from \"parse-json\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport {\n loadFailedError,\n parseFailedError,\n pathStringError,\n sourceEmptyError,\n sourceInvalidError,\n type TextOrigin,\n urlInvalidError,\n} from \"./schema-load-errors.js\";\n\n/** Parse a schema source of any supported type into a Redocly document (ENGINEERING_PLAN 4.5). */\n\nexport interface ParseSchemaOptions {\n absoluteRef: string;\n resolver: BaseResolver;\n /** Where document text came from, for the message of text that is only a string. */\n textOrigin?: TextOrigin;\n}\n\n/**\n * Read a stream to the end as UTF-8 text, trimmed. A stream that fails rejects with\n * SCHEMA_LOAD_FAILED; listening only for `data` and `end` left the promise pending forever.\n */\nasync function readStream(stream: Readable, absoluteRef: string): Promise<string> {\n try {\n return (await text(stream)).trim();\n } catch (error) {\n throw loadFailedError(absoluteRef, error);\n }\n}\n\n/** A URL source string; a scheme is the same in any case (RFC 3986, section 3.1). */\nconst URL_PREFIX_RE = /^(?:https?|file):\\/\\//i;\n\n/**\n * A string is a URL, JSON (starts with `{`) or YAML. A string that starts like a URL but does not\n * parse as one fails with `SCHEMA_SOURCE_INVALID`, not the parser's `TypeError`.\n */\nfunction parseSchemaText(\n schema: string,\n options: ParseSchemaOptions,\n): Promise<Document> | Document {\n if (URL_PREFIX_RE.test(schema)) {\n const url = URL.parse(schema);\n if (url === null) {\n throw urlInvalidError(schema);\n }\n const absoluteRef = url.protocol === \"file:\" ? fileURLToPath(url) : url.href;\n return parseSchema(url, { absoluteRef, resolver: options.resolver });\n }\n return schema[0] === \"{\"\n ? parseJsonText(schema, options.absoluteRef)\n : parseYamlText(schema, options.absoluteRef, options.textOrigin ?? \"string\");\n}\n\nfunction parseJsonText(text: string, absoluteRef: string): Document {\n try {\n return { source: new Source(absoluteRef, text, \"application/json\"), parsed: parseJson(text) };\n } catch (cause) {\n throw parseFailedError(\"json\", absoluteRef, cause);\n }\n}\n\n/**\n * How a parser message names document text that is no file. The document itself is named as a\n * file in the working directory, so that its relative `$ref`s resolve there, but no such file\n * exists, and a message that named it sent the user looking for it.\n */\nconst TEXT_ORIGIN_NAMES: Readonly<Record<TextOrigin, string>> = {\n string: \"the schema string\",\n stream: \"the schema stream\",\n};\n\nfunction parseYamlText(text: string, absoluteRef: string, origin: TextOrigin): Document {\n const source = new Source(absoluteRef, text);\n let document: Document;\n try {\n document = { source, parsed: parseYaml(text, { filename: TEXT_ORIGIN_NAMES[origin] }) };\n } catch (cause) {\n // As Redocly's own parse: the error keeps the first line, not the parser's code excerpt.\n throw parseFailedError(\"yaml\", absoluteRef, new YamlParseError(cause as Error, source));\n }\n // Text that is only a YAML string scalar, such as \"./openapi.yaml\", was meant as a file path.\n if (typeof document.parsed === \"string\") {\n throw pathStringError(text, origin);\n }\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw parseFailedError(\"yaml\", absoluteRef, failure);\n }\n return document;\n}\n\n/** The resolver throws for some failures (a missing file) and returns an error for others. */\nasync function resolveSchemaURL(options: ParseSchemaOptions): Promise<Document> {\n const result = await options.resolver\n .resolveDocument(null, options.absoluteRef, true)\n .catch((cause: unknown) => {\n throw cause instanceof YamlParseError\n ? fileParseFailedError(options.absoluteRef, cause)\n : loadFailedError(options.absoluteRef, readerError(cause));\n });\n if (\"parsed\" in result) {\n return result;\n }\n throw loadFailedError(options.absoluteRef, readerError(result));\n}\n\n/**\n * Redocly wraps a failed read in `ResolveError`, which keeps the reader's error in\n * `originalError`, not in `cause`. Unwrapped, the cause keeps the network reason (refused, DNS).\n */\nfunction readerError(error: unknown): unknown {\n let current = error;\n while (current instanceof ResolveError) {\n current = current.originalError;\n }\n return current;\n}\n\n/**\n * The resolver parses JSON files with its YAML parser too, so the format comes from the file\n * name. Some parser messages (a second YAML document) do not name the file; the error adds it.\n */\nfunction fileParseFailedError(absoluteRef: string, error: YamlParseError): Error {\n const format = absoluteRef.toLowerCase().endsWith(\".json\") ? \"json\" : \"yaml\";\n const cause = error.message.includes(absoluteRef)\n ? error\n : new Error(`${error.message} in \"${absoluteRef}\"`, { cause: error });\n return parseFailedError(format, absoluteRef, cause);\n}\n\nfunction parseSchemaObject(schema: unknown, absoluteRef: string): Document {\n if (typeof schema === \"object\" && !Array.isArray(schema)) {\n return {\n source: new Source(absoluteRef, JSON.stringify(schema), \"application/json\"),\n parsed: schema,\n };\n }\n throw sourceInvalidError(schema);\n}\n\n/** Parse any supported source: URL, stream, buffer, URL or document text, or a plain object. */\nexport async function parseSchema(schema: unknown, options: ParseSchemaOptions): Promise<Document> {\n if (!schema) {\n throw sourceEmptyError(\"Can’t parse empty schema\");\n }\n if (schema instanceof URL) {\n return resolveSchemaURL(options);\n }\n if (schema instanceof Readable) {\n const text = await readStream(schema, options.absoluteRef);\n return parseSchema(text, { ...options, textOrigin: \"stream\" });\n }\n if (schema instanceof Buffer) {\n return parseSchema(schema.toString(\"utf8\"), options);\n }\n if (typeof schema === \"string\") {\n return parseSchemaText(schema, options);\n }\n return parseSchemaObject(schema, options.absoluteRef);\n}\n"],"names":["text","loadFailedError","urlInvalidError","fileURLToPath","Source","parseJson","parseFailedError","parseYaml","YamlParseError","pathStringError","aliasExpansionFailure","ResolveError","sourceInvalidError","sourceEmptyError","Readable"],"mappings":";;;;;;;;;;;;;;AAoCA,eAAe,UAAA,CAAW,QAAkB,WAAA,EAAsC;AAChF,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,MAAMA,cAAA,CAAK,MAAM,CAAA,EAAG,IAAA,EAAK;AAAA,EACnC,SAAS,KAAA,EAAO;AACd,IAAA,MAAMC,gCAAA,CAAgB,aAAa,KAAK,CAAA;AAAA,EAC1C;AACF;AAGA,MAAM,aAAA,GAAgB,wBAAA;AAMtB,SAAS,eAAA,CACP,QACA,OAAA,EAC8B;AAC9B,EAAA,IAAI,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA,EAAG;AAC9B,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA;AAC5B,IAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,MAAMC,iCAAgB,MAAM,CAAA;AAAA,IAC9B;AACA,IAAA,MAAM,cAAc,GAAA,CAAI,QAAA,KAAa,UAAUC,sBAAA,CAAc,GAAG,IAAI,GAAA,CAAI,IAAA;AACxE,IAAA,OAAO,YAAY,GAAA,EAAK,EAAE,aAAa,QAAA,EAAU,OAAA,CAAQ,UAAU,CAAA;AAAA,EACrE;AACA,EAAA,OAAO,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,GACjB,cAAc,MAAA,EAAQ,OAAA,CAAQ,WAAW,CAAA,GACzC,cAAc,MAAA,EAAQ,OAAA,CAAQ,WAAA,EAAa,OAAA,CAAQ,cAAc,QAAQ,CAAA;AAC/E;AAEA,SAAS,aAAA,CAAcH,OAAc,WAAA,EAA+B;AAClE,EAAA,IAAI;AACF,IAAA,OAAO,EAAE,MAAA,EAAQ,IAAII,kBAAA,CAAO,WAAA,EAAaJ,KAAAA,EAAM,kBAAkB,CAAA,EAAG,MAAA,EAAQK,kBAAA,CAAUL,KAAI,CAAA,EAAE;AAAA,EAC9F,SAAS,KAAA,EAAO;AACd,IAAA,MAAMM,iCAAA,CAAiB,MAAA,EAAQ,WAAA,EAAa,KAAK,CAAA;AAAA,EACnD;AACF;AAOA,MAAM,iBAAA,GAA0D;AAAA,EAC9D,MAAA,EAAQ,mBAAA;AAAA,EACR,MAAA,EAAQ;AACV,CAAA;AAEA,SAAS,aAAA,CAAcN,KAAAA,EAAc,WAAA,EAAqB,MAAA,EAA8B;AACtF,EAAA,MAAM,MAAA,GAAS,IAAII,kBAAA,CAAO,WAAA,EAAaJ,KAAI,CAAA;AAC3C,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,EAAE,MAAA,EAAQ,MAAA,EAAQO,qBAAA,CAAUP,KAAAA,EAAM,EAAE,QAAA,EAAU,iBAAA,CAAkB,MAAM,CAAA,EAAG,CAAA,EAAE;AAAA,EACxF,SAAS,KAAA,EAAO;AAEd,IAAA,MAAMM,kCAAiB,MAAA,EAAQ,WAAA,EAAa,IAAIE,0BAAA,CAAe,KAAA,EAAgB,MAAM,CAAC,CAAA;AAAA,EACxF;AAEA,EAAA,IAAI,OAAO,QAAA,CAAS,MAAA,KAAW,QAAA,EAAU;AACvC,IAAA,MAAMC,gCAAA,CAAgBT,OAAM,MAAM,CAAA;AAAA,EACpC;AACA,EAAA,MAAM,OAAA,GAAUU,gCAAA,CAAsB,QAAA,CAAS,MAAM,CAAA;AACrD,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAMJ,iCAAA,CAAiB,MAAA,EAAQ,WAAA,EAAa,OAAO,CAAA;AAAA,EACrD;AACA,EAAA,OAAO,QAAA;AACT;AAGA,eAAe,iBAAiB,OAAA,EAAgD;AAC9E,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,QAAA,CAC1B,eAAA,CAAgB,IAAA,EAAM,OAAA,CAAQ,WAAA,EAAa,IAAI,CAAA,CAC/C,KAAA,CAAM,CAAC,KAAA,KAAmB;AACzB,IAAA,MAAM,KAAA,YAAiBE,0BAAA,GACnB,oBAAA,CAAqB,OAAA,CAAQ,WAAA,EAAa,KAAK,CAAA,GAC/CP,gCAAA,CAAgB,OAAA,CAAQ,WAAA,EAAa,WAAA,CAAY,KAAK,CAAC,CAAA;AAAA,EAC7D,CAAC,CAAA;AACH,EAAA,IAAI,YAAY,MAAA,EAAQ;AACtB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAMA,gCAAA,CAAgB,OAAA,CAAQ,WAAA,EAAa,WAAA,CAAY,MAAM,CAAC,CAAA;AAChE;AAMA,SAAS,YAAY,KAAA,EAAyB;AAC5C,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,OAAO,mBAAmBU,wBAAA,EAAc;AACtC,IAAA,OAAA,GAAU,OAAA,CAAQ,aAAA;AAAA,EACpB;AACA,EAAA,OAAO,OAAA;AACT;AAMA,SAAS,oBAAA,CAAqB,aAAqB,KAAA,EAA8B;AAC/E,EAAA,MAAM,SAAS,WAAA,CAAY,WAAA,GAAc,QAAA,CAAS,OAAO,IAAI,MAAA,GAAS,MAAA;AACtE,EAAA,MAAM,QAAQ,KAAA,CAAM,OAAA,CAAQ,SAAS,WAAW,CAAA,GAC5C,QACA,IAAI,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,OAAO,CAAA,KAAA,EAAQ,WAAW,KAAK,EAAE,KAAA,EAAO,OAAO,CAAA;AACtE,EAAA,OAAOL,iCAAA,CAAiB,MAAA,EAAQ,WAAA,EAAa,KAAK,CAAA;AACpD;AAEA,SAAS,iBAAA,CAAkB,QAAiB,WAAA,EAA+B;AACzE,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AACxD,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,IAAIF,kBAAA,CAAO,WAAA,EAAa,KAAK,SAAA,CAAU,MAAM,GAAG,kBAAkB,CAAA;AAAA,MAC1E,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AACA,EAAA,MAAMQ,oCAAmB,MAAM,CAAA;AACjC;AAGA,eAAsB,WAAA,CAAY,QAAiB,OAAA,EAAgD;AACjG,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAMC,kCAAiB,+BAA0B,CAAA;AAAA,EACnD;AACA,EAAA,IAAI,kBAAkB,GAAA,EAAK;AACzB,IAAA,OAAO,iBAAiB,OAAO,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,kBAAkBC,oBAAA,EAAU;AAC9B,IAAA,MAAMd,KAAAA,GAAO,MAAM,UAAA,CAAW,MAAA,EAAQ,QAAQ,WAAW,CAAA;AACzD,IAAA,OAAO,YAAYA,KAAAA,EAAM,EAAE,GAAG,OAAA,EAAS,UAAA,EAAY,UAAU,CAAA;AAAA,EAC/D;AACA,EAAA,IAAI,kBAAkB,MAAA,EAAQ;AAC5B,IAAA,OAAO,WAAA,CAAY,MAAA,CAAO,QAAA,CAAS,MAAM,GAAG,OAAO,CAAA;AAAA,EACrD;AACA,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC9B,IAAA,OAAO,eAAA,CAAgB,QAAQ,OAAO,CAAA;AAAA,EACxC;AACA,EAAA,OAAO,iBAAA,CAAkB,MAAA,EAAQ,OAAA,CAAQ,WAAW,CAAA;AACtD;;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
2
|
import { text } from 'node:stream/consumers';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { YamlParseError, Source, ResolveError,
|
|
4
|
+
import { YamlParseError, Source, ResolveError, parseYaml } from '@redocly/openapi-core';
|
|
5
5
|
import parseJson from 'parse-json';
|
|
6
6
|
import { aliasExpansionFailure } from './alias-limit.mjs';
|
|
7
7
|
import { sourceEmptyError, loadFailedError, urlInvalidError, sourceInvalidError, parseFailedError, pathStringError } from './schema-load-errors.mjs';
|
|
@@ -32,12 +32,17 @@ function parseJsonText(text2, absoluteRef) {
|
|
|
32
32
|
throw parseFailedError("json", absoluteRef, cause);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
const TEXT_ORIGIN_NAMES = {
|
|
36
|
+
string: "the schema string",
|
|
37
|
+
stream: "the schema stream"
|
|
38
|
+
};
|
|
35
39
|
function parseYamlText(text2, absoluteRef, origin) {
|
|
40
|
+
const source = new Source(absoluteRef, text2);
|
|
36
41
|
let document;
|
|
37
42
|
try {
|
|
38
|
-
document =
|
|
43
|
+
document = { source, parsed: parseYaml(text2, { filename: TEXT_ORIGIN_NAMES[origin] }) };
|
|
39
44
|
} catch (cause) {
|
|
40
|
-
throw parseFailedError("yaml", absoluteRef, cause);
|
|
45
|
+
throw parseFailedError("yaml", absoluteRef, new YamlParseError(cause, source));
|
|
41
46
|
}
|
|
42
47
|
if (typeof document.parsed === "string") {
|
|
43
48
|
throw pathStringError(text2, origin);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-source.mjs","sources":["../../../src/adapters/redocly-loader/schema-source.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport { text } from \"node:stream/consumers\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n type Document,\n makeDocumentFromString,\n ResolveError,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport parseJson from \"parse-json\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport {\n loadFailedError,\n parseFailedError,\n pathStringError,\n sourceEmptyError,\n sourceInvalidError,\n type TextOrigin,\n urlInvalidError,\n} from \"./schema-load-errors.js\";\n\n/** Parse a schema source of any supported type into a Redocly document (ENGINEERING_PLAN 4.5). */\n\nexport interface ParseSchemaOptions {\n absoluteRef: string;\n resolver: BaseResolver;\n /** Where document text came from, for the message of text that is only a string. */\n textOrigin?: TextOrigin;\n}\n\n/**\n * Read a stream to the end as UTF-8 text, trimmed. A stream that fails rejects with\n * SCHEMA_LOAD_FAILED; listening only for `data` and `end` left the promise pending forever.\n */\nasync function readStream(stream: Readable, absoluteRef: string): Promise<string> {\n try {\n return (await text(stream)).trim();\n } catch (error) {\n throw loadFailedError(absoluteRef, error);\n }\n}\n\n/** A URL source string; a scheme is the same in any case (RFC 3986, section 3.1). */\nconst URL_PREFIX_RE = /^(?:https?|file):\\/\\//i;\n\n/**\n * A string is a URL, JSON (starts with `{`) or YAML. A string that starts like a URL but does not\n * parse as one fails with `SCHEMA_SOURCE_INVALID`, not the parser's `TypeError`.\n */\nfunction parseSchemaText(\n schema: string,\n options: ParseSchemaOptions,\n): Promise<Document> | Document {\n if (URL_PREFIX_RE.test(schema)) {\n const url = URL.parse(schema);\n if (url === null) {\n throw urlInvalidError(schema);\n }\n const absoluteRef = url.protocol === \"file:\" ? fileURLToPath(url) : url.href;\n return parseSchema(url, { absoluteRef, resolver: options.resolver });\n }\n return schema[0] === \"{\"\n ? parseJsonText(schema, options.absoluteRef)\n : parseYamlText(schema, options.absoluteRef, options.textOrigin ?? \"string\");\n}\n\nfunction parseJsonText(text: string, absoluteRef: string): Document {\n try {\n return { source: new Source(absoluteRef, text, \"application/json\"), parsed: parseJson(text) };\n } catch (cause) {\n throw parseFailedError(\"json\", absoluteRef, cause);\n }\n}\n\nfunction parseYamlText(text: string, absoluteRef: string, origin: TextOrigin): Document {\n let document: Document;\n try {\n document = makeDocumentFromString(text, absoluteRef);\n } catch (cause) {\n throw parseFailedError(\"yaml\", absoluteRef, cause);\n }\n // Text that is only a YAML string scalar, such as \"./openapi.yaml\", was meant as a file path.\n if (typeof document.parsed === \"string\") {\n throw pathStringError(text, origin);\n }\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw parseFailedError(\"yaml\", absoluteRef, failure);\n }\n return document;\n}\n\n/** The resolver throws for some failures (a missing file) and returns an error for others. */\nasync function resolveSchemaURL(options: ParseSchemaOptions): Promise<Document> {\n const result = await options.resolver\n .resolveDocument(null, options.absoluteRef, true)\n .catch((cause: unknown) => {\n throw cause instanceof YamlParseError\n ? fileParseFailedError(options.absoluteRef, cause)\n : loadFailedError(options.absoluteRef, readerError(cause));\n });\n if (\"parsed\" in result) {\n return result;\n }\n throw loadFailedError(options.absoluteRef, readerError(result));\n}\n\n/**\n * Redocly wraps a failed read in `ResolveError`, which keeps the reader's error in\n * `originalError`, not in `cause`. Unwrapped, the cause keeps the network reason (refused, DNS).\n */\nfunction readerError(error: unknown): unknown {\n let current = error;\n while (current instanceof ResolveError) {\n current = current.originalError;\n }\n return current;\n}\n\n/**\n * The resolver parses JSON files with its YAML parser too, so the format comes from the file\n * name. Some parser messages (a second YAML document) do not name the file; the error adds it.\n */\nfunction fileParseFailedError(absoluteRef: string, error: YamlParseError): Error {\n const format = absoluteRef.toLowerCase().endsWith(\".json\") ? \"json\" : \"yaml\";\n const cause = error.message.includes(absoluteRef)\n ? error\n : new Error(`${error.message} in \"${absoluteRef}\"`, { cause: error });\n return parseFailedError(format, absoluteRef, cause);\n}\n\nfunction parseSchemaObject(schema: unknown, absoluteRef: string): Document {\n if (typeof schema === \"object\" && !Array.isArray(schema)) {\n return {\n source: new Source(absoluteRef, JSON.stringify(schema), \"application/json\"),\n parsed: schema,\n };\n }\n throw sourceInvalidError(schema);\n}\n\n/** Parse any supported source: URL, stream, buffer, URL or document text, or a plain object. */\nexport async function parseSchema(schema: unknown, options: ParseSchemaOptions): Promise<Document> {\n if (!schema) {\n throw sourceEmptyError(\"Can’t parse empty schema\");\n }\n if (schema instanceof URL) {\n return resolveSchemaURL(options);\n }\n if (schema instanceof Readable) {\n const text = await readStream(schema, options.absoluteRef);\n return parseSchema(text, { ...options, textOrigin: \"stream\" });\n }\n if (schema instanceof Buffer) {\n return parseSchema(schema.toString(\"utf8\"), options);\n }\n if (typeof schema === \"string\") {\n return parseSchemaText(schema, options);\n }\n return parseSchemaObject(schema, options.absoluteRef);\n}\n"],"names":["text"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-source.mjs","sources":["../../../src/adapters/redocly-loader/schema-source.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport { text } from \"node:stream/consumers\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type BaseResolver,\n type Document,\n parseYaml,\n ResolveError,\n Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport parseJson from \"parse-json\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport {\n loadFailedError,\n parseFailedError,\n pathStringError,\n sourceEmptyError,\n sourceInvalidError,\n type TextOrigin,\n urlInvalidError,\n} from \"./schema-load-errors.js\";\n\n/** Parse a schema source of any supported type into a Redocly document (ENGINEERING_PLAN 4.5). */\n\nexport interface ParseSchemaOptions {\n absoluteRef: string;\n resolver: BaseResolver;\n /** Where document text came from, for the message of text that is only a string. */\n textOrigin?: TextOrigin;\n}\n\n/**\n * Read a stream to the end as UTF-8 text, trimmed. A stream that fails rejects with\n * SCHEMA_LOAD_FAILED; listening only for `data` and `end` left the promise pending forever.\n */\nasync function readStream(stream: Readable, absoluteRef: string): Promise<string> {\n try {\n return (await text(stream)).trim();\n } catch (error) {\n throw loadFailedError(absoluteRef, error);\n }\n}\n\n/** A URL source string; a scheme is the same in any case (RFC 3986, section 3.1). */\nconst URL_PREFIX_RE = /^(?:https?|file):\\/\\//i;\n\n/**\n * A string is a URL, JSON (starts with `{`) or YAML. A string that starts like a URL but does not\n * parse as one fails with `SCHEMA_SOURCE_INVALID`, not the parser's `TypeError`.\n */\nfunction parseSchemaText(\n schema: string,\n options: ParseSchemaOptions,\n): Promise<Document> | Document {\n if (URL_PREFIX_RE.test(schema)) {\n const url = URL.parse(schema);\n if (url === null) {\n throw urlInvalidError(schema);\n }\n const absoluteRef = url.protocol === \"file:\" ? fileURLToPath(url) : url.href;\n return parseSchema(url, { absoluteRef, resolver: options.resolver });\n }\n return schema[0] === \"{\"\n ? parseJsonText(schema, options.absoluteRef)\n : parseYamlText(schema, options.absoluteRef, options.textOrigin ?? \"string\");\n}\n\nfunction parseJsonText(text: string, absoluteRef: string): Document {\n try {\n return { source: new Source(absoluteRef, text, \"application/json\"), parsed: parseJson(text) };\n } catch (cause) {\n throw parseFailedError(\"json\", absoluteRef, cause);\n }\n}\n\n/**\n * How a parser message names document text that is no file. The document itself is named as a\n * file in the working directory, so that its relative `$ref`s resolve there, but no such file\n * exists, and a message that named it sent the user looking for it.\n */\nconst TEXT_ORIGIN_NAMES: Readonly<Record<TextOrigin, string>> = {\n string: \"the schema string\",\n stream: \"the schema stream\",\n};\n\nfunction parseYamlText(text: string, absoluteRef: string, origin: TextOrigin): Document {\n const source = new Source(absoluteRef, text);\n let document: Document;\n try {\n document = { source, parsed: parseYaml(text, { filename: TEXT_ORIGIN_NAMES[origin] }) };\n } catch (cause) {\n // As Redocly's own parse: the error keeps the first line, not the parser's code excerpt.\n throw parseFailedError(\"yaml\", absoluteRef, new YamlParseError(cause as Error, source));\n }\n // Text that is only a YAML string scalar, such as \"./openapi.yaml\", was meant as a file path.\n if (typeof document.parsed === \"string\") {\n throw pathStringError(text, origin);\n }\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw parseFailedError(\"yaml\", absoluteRef, failure);\n }\n return document;\n}\n\n/** The resolver throws for some failures (a missing file) and returns an error for others. */\nasync function resolveSchemaURL(options: ParseSchemaOptions): Promise<Document> {\n const result = await options.resolver\n .resolveDocument(null, options.absoluteRef, true)\n .catch((cause: unknown) => {\n throw cause instanceof YamlParseError\n ? fileParseFailedError(options.absoluteRef, cause)\n : loadFailedError(options.absoluteRef, readerError(cause));\n });\n if (\"parsed\" in result) {\n return result;\n }\n throw loadFailedError(options.absoluteRef, readerError(result));\n}\n\n/**\n * Redocly wraps a failed read in `ResolveError`, which keeps the reader's error in\n * `originalError`, not in `cause`. Unwrapped, the cause keeps the network reason (refused, DNS).\n */\nfunction readerError(error: unknown): unknown {\n let current = error;\n while (current instanceof ResolveError) {\n current = current.originalError;\n }\n return current;\n}\n\n/**\n * The resolver parses JSON files with its YAML parser too, so the format comes from the file\n * name. Some parser messages (a second YAML document) do not name the file; the error adds it.\n */\nfunction fileParseFailedError(absoluteRef: string, error: YamlParseError): Error {\n const format = absoluteRef.toLowerCase().endsWith(\".json\") ? \"json\" : \"yaml\";\n const cause = error.message.includes(absoluteRef)\n ? error\n : new Error(`${error.message} in \"${absoluteRef}\"`, { cause: error });\n return parseFailedError(format, absoluteRef, cause);\n}\n\nfunction parseSchemaObject(schema: unknown, absoluteRef: string): Document {\n if (typeof schema === \"object\" && !Array.isArray(schema)) {\n return {\n source: new Source(absoluteRef, JSON.stringify(schema), \"application/json\"),\n parsed: schema,\n };\n }\n throw sourceInvalidError(schema);\n}\n\n/** Parse any supported source: URL, stream, buffer, URL or document text, or a plain object. */\nexport async function parseSchema(schema: unknown, options: ParseSchemaOptions): Promise<Document> {\n if (!schema) {\n throw sourceEmptyError(\"Can’t parse empty schema\");\n }\n if (schema instanceof URL) {\n return resolveSchemaURL(options);\n }\n if (schema instanceof Readable) {\n const text = await readStream(schema, options.absoluteRef);\n return parseSchema(text, { ...options, textOrigin: \"stream\" });\n }\n if (schema instanceof Buffer) {\n return parseSchema(schema.toString(\"utf8\"), options);\n }\n if (typeof schema === \"string\") {\n return parseSchemaText(schema, options);\n }\n return parseSchemaObject(schema, options.absoluteRef);\n}\n"],"names":["text"],"mappings":"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"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const colors = require('ansi-colors');
|
|
4
|
+
const terminalText = require('../../domain/terminal-text.cjs');
|
|
4
5
|
const urlSecrets = require('../../domain/url-secrets.cjs');
|
|
5
6
|
|
|
6
7
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
7
8
|
|
|
8
9
|
const colors__default = /*#__PURE__*/_interopDefaultCompat(colors);
|
|
9
10
|
|
|
11
|
+
function shownText(message) {
|
|
12
|
+
return terminalText.terminalSafeText(urlSecrets.redactUrlSecrets(message));
|
|
13
|
+
}
|
|
10
14
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
11
15
|
const MILLISECONDS_PER_MINUTE = 6e4;
|
|
12
16
|
function formatDuration(milliseconds) {
|
|
@@ -47,17 +51,17 @@ class TerminalLogger {
|
|
|
47
51
|
}
|
|
48
52
|
const name = groupColor(this.#palette, group)(`openapi-ts:${group ?? "info"}`);
|
|
49
53
|
const time = durationMs === void 0 ? " " : this.#palette.green(` ${formatDuration(durationMs)} `);
|
|
50
|
-
const text =
|
|
54
|
+
const text = shownText(message);
|
|
51
55
|
this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);
|
|
52
56
|
}
|
|
53
57
|
warn(message) {
|
|
54
58
|
if (!this.#options.silent) {
|
|
55
|
-
this.#options.sink.warn(this.#palette.yellow(` \u26A0 ${
|
|
59
|
+
this.#options.sink.warn(this.#palette.yellow(` \u26A0 ${shownText(message)}`));
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
error(message) {
|
|
59
63
|
if (!this.#options.silent) {
|
|
60
|
-
this.#options.sink.error(this.#palette.red(` \u2718 ${
|
|
64
|
+
this.#options.sink.error(this.#palette.red(` \u2718 ${shownText(message)}`));
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-logger.cjs","sources":["../../../src/adapters/terminal-logger/terminal-logger.ts"],"sourcesContent":["import colors from \"ansi-colors\";\nimport type { DebugGroup, Logger } from \"../../application/ports/logger.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\n\n/**\n * The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `\n * errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No\n * line shows the
|
|
1
|
+
{"version":3,"file":"terminal-logger.cjs","sources":["../../../src/adapters/terminal-logger/terminal-logger.ts"],"sourcesContent":["import colors from \"ansi-colors\";\nimport type { DebugGroup, Logger } from \"../../application/ports/logger.js\";\nimport { terminalSafeText } from \"../../domain/terminal-text.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\n\n/**\n * The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `\n * errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No\n * line shows the secrets of a URL, and each message prints as one line: a message quotes the\n * document, whose control characters could forge CI commands (terminal-text.ts).\n */\n\nfunction shownText(message: string): string {\n return terminalSafeText(redactUrlSecrets(message));\n}\n\n/** Where the lines go: `console` in production, a recording sink in tests. */\nexport interface TerminalSink {\n debug(line: string): void;\n warn(line: string): void;\n error(line: string): void;\n}\n\nexport interface TerminalLoggerOptions {\n /**\n * Drop warnings and error lines; debug lines, which `DEBUG` asks for, are still written. The\n * error lines are the problems of a document that fails to load, which the thrown error lists.\n */\n readonly silent: boolean;\n /** The value of `DEBUG`: `*`, `openapi-ts:*` or `openapi-ts:<group>` turn debug lines on. */\n readonly debugFilter: string | undefined;\n readonly useColors: boolean;\n readonly sink: TerminalSink;\n}\n\ntype Colors = ReturnType<typeof colors.create>;\n\nconst MILLISECONDS_PER_SECOND = 1000;\nconst MILLISECONDS_PER_MINUTE = 60_000;\n\n/** A duration as the original prints it: one decimal, in ms, s or m. */\nexport function formatDuration(milliseconds: number): string {\n if (milliseconds < MILLISECONDS_PER_SECOND) {\n return `${Math.round(10 * milliseconds) / 10}ms`;\n }\n if (milliseconds < MILLISECONDS_PER_MINUTE) {\n return `${Math.round(milliseconds / 100) / 10}s`;\n }\n return `${Math.round(milliseconds / 6000) / 10}m`;\n}\n\nfunction groupColor(palette: Colors, group: DebugGroup | undefined): (text: string) => string {\n const byGroup: Readonly<Partial<Record<DebugGroup, (text: string) => string>>> = {\n redoc: palette.cyanBright,\n lint: palette.yellowBright,\n bundle: palette.magentaBright,\n ts: palette.blueBright,\n };\n return (group && byGroup[group]) || palette.whiteBright;\n}\n\n/** Whether `DEBUG` asks for this group (the original check, case-insensitive for the group). */\nexport function isDebugEnabled(filter: string | undefined, group: DebugGroup | undefined): boolean {\n if (!filter) {\n return false;\n }\n return (\n !group ||\n filter === \"*\" ||\n filter === \"openapi-ts:*\" ||\n filter.toLocaleLowerCase() === `openapi-ts:${group.toLocaleLowerCase()}`\n );\n}\n\nclass TerminalLogger implements Logger {\n readonly #options: TerminalLoggerOptions;\n readonly #palette: Colors;\n\n constructor(options: TerminalLoggerOptions) {\n this.#options = options;\n this.#palette = colors.create();\n this.#palette.enabled = options.useColors;\n }\n\n debug(message: string, group?: DebugGroup, durationMs?: number): void {\n if (!isDebugEnabled(this.#options.debugFilter, group)) {\n return;\n }\n const name = groupColor(this.#palette, group)(`openapi-ts:${group ?? \"info\"}`);\n // The original wrote no space without a duration (`openapi-ts:lintIgnored …`).\n const time =\n durationMs === undefined ? \" \" : this.#palette.green(` ${formatDuration(durationMs)} `);\n const text = shownText(message);\n this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);\n }\n\n warn(message: string): void {\n if (!this.#options.silent) {\n this.#options.sink.warn(this.#palette.yellow(` ⚠ ${shownText(message)}`));\n }\n }\n\n error(message: string): void {\n if (!this.#options.silent) {\n this.#options.sink.error(this.#palette.red(` ✘ ${shownText(message)}`));\n }\n }\n}\n\n/** A Logger that writes to a terminal sink. */\nexport function createTerminalLogger(options: TerminalLoggerOptions): Logger {\n return new TerminalLogger(options);\n}\n"],"names":["terminalSafeText","redactUrlSecrets","colors"],"mappings":"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"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import colors from 'ansi-colors';
|
|
2
|
+
import { terminalSafeText } from '../../domain/terminal-text.mjs';
|
|
2
3
|
import { redactUrlSecrets } from '../../domain/url-secrets.mjs';
|
|
3
4
|
|
|
5
|
+
function shownText(message) {
|
|
6
|
+
return terminalSafeText(redactUrlSecrets(message));
|
|
7
|
+
}
|
|
4
8
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
5
9
|
const MILLISECONDS_PER_MINUTE = 6e4;
|
|
6
10
|
function formatDuration(milliseconds) {
|
|
@@ -41,17 +45,17 @@ class TerminalLogger {
|
|
|
41
45
|
}
|
|
42
46
|
const name = groupColor(this.#palette, group)(`openapi-ts:${group ?? "info"}`);
|
|
43
47
|
const time = durationMs === void 0 ? " " : this.#palette.green(` ${formatDuration(durationMs)} `);
|
|
44
|
-
const text =
|
|
48
|
+
const text = shownText(message);
|
|
45
49
|
this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);
|
|
46
50
|
}
|
|
47
51
|
warn(message) {
|
|
48
52
|
if (!this.#options.silent) {
|
|
49
|
-
this.#options.sink.warn(this.#palette.yellow(` \u26A0 ${
|
|
53
|
+
this.#options.sink.warn(this.#palette.yellow(` \u26A0 ${shownText(message)}`));
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
error(message) {
|
|
53
57
|
if (!this.#options.silent) {
|
|
54
|
-
this.#options.sink.error(this.#palette.red(` \u2718 ${
|
|
58
|
+
this.#options.sink.error(this.#palette.red(` \u2718 ${shownText(message)}`));
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-logger.mjs","sources":["../../../src/adapters/terminal-logger/terminal-logger.ts"],"sourcesContent":["import colors from \"ansi-colors\";\nimport type { DebugGroup, Logger } from \"../../application/ports/logger.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\n\n/**\n * The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `\n * errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No\n * line shows the
|
|
1
|
+
{"version":3,"file":"terminal-logger.mjs","sources":["../../../src/adapters/terminal-logger/terminal-logger.ts"],"sourcesContent":["import colors from \"ansi-colors\";\nimport type { DebugGroup, Logger } from \"../../application/ports/logger.js\";\nimport { terminalSafeText } from \"../../domain/terminal-text.js\";\nimport { redactUrlSecrets } from \"../../domain/url-secrets.js\";\n\n/**\n * The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `\n * errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No\n * line shows the secrets of a URL, and each message prints as one line: a message quotes the\n * document, whose control characters could forge CI commands (terminal-text.ts).\n */\n\nfunction shownText(message: string): string {\n return terminalSafeText(redactUrlSecrets(message));\n}\n\n/** Where the lines go: `console` in production, a recording sink in tests. */\nexport interface TerminalSink {\n debug(line: string): void;\n warn(line: string): void;\n error(line: string): void;\n}\n\nexport interface TerminalLoggerOptions {\n /**\n * Drop warnings and error lines; debug lines, which `DEBUG` asks for, are still written. The\n * error lines are the problems of a document that fails to load, which the thrown error lists.\n */\n readonly silent: boolean;\n /** The value of `DEBUG`: `*`, `openapi-ts:*` or `openapi-ts:<group>` turn debug lines on. */\n readonly debugFilter: string | undefined;\n readonly useColors: boolean;\n readonly sink: TerminalSink;\n}\n\ntype Colors = ReturnType<typeof colors.create>;\n\nconst MILLISECONDS_PER_SECOND = 1000;\nconst MILLISECONDS_PER_MINUTE = 60_000;\n\n/** A duration as the original prints it: one decimal, in ms, s or m. */\nexport function formatDuration(milliseconds: number): string {\n if (milliseconds < MILLISECONDS_PER_SECOND) {\n return `${Math.round(10 * milliseconds) / 10}ms`;\n }\n if (milliseconds < MILLISECONDS_PER_MINUTE) {\n return `${Math.round(milliseconds / 100) / 10}s`;\n }\n return `${Math.round(milliseconds / 6000) / 10}m`;\n}\n\nfunction groupColor(palette: Colors, group: DebugGroup | undefined): (text: string) => string {\n const byGroup: Readonly<Partial<Record<DebugGroup, (text: string) => string>>> = {\n redoc: palette.cyanBright,\n lint: palette.yellowBright,\n bundle: palette.magentaBright,\n ts: palette.blueBright,\n };\n return (group && byGroup[group]) || palette.whiteBright;\n}\n\n/** Whether `DEBUG` asks for this group (the original check, case-insensitive for the group). */\nexport function isDebugEnabled(filter: string | undefined, group: DebugGroup | undefined): boolean {\n if (!filter) {\n return false;\n }\n return (\n !group ||\n filter === \"*\" ||\n filter === \"openapi-ts:*\" ||\n filter.toLocaleLowerCase() === `openapi-ts:${group.toLocaleLowerCase()}`\n );\n}\n\nclass TerminalLogger implements Logger {\n readonly #options: TerminalLoggerOptions;\n readonly #palette: Colors;\n\n constructor(options: TerminalLoggerOptions) {\n this.#options = options;\n this.#palette = colors.create();\n this.#palette.enabled = options.useColors;\n }\n\n debug(message: string, group?: DebugGroup, durationMs?: number): void {\n if (!isDebugEnabled(this.#options.debugFilter, group)) {\n return;\n }\n const name = groupColor(this.#palette, group)(`openapi-ts:${group ?? \"info\"}`);\n // The original wrote no space without a duration (`openapi-ts:lintIgnored …`).\n const time =\n durationMs === undefined ? \" \" : this.#palette.green(` ${formatDuration(durationMs)} `);\n const text = shownText(message);\n this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);\n }\n\n warn(message: string): void {\n if (!this.#options.silent) {\n this.#options.sink.warn(this.#palette.yellow(` ⚠ ${shownText(message)}`));\n }\n }\n\n error(message: string): void {\n if (!this.#options.silent) {\n this.#options.sink.error(this.#palette.red(` ✘ ${shownText(message)}`));\n }\n }\n}\n\n/** A Logger that writes to a terminal sink. */\nexport function createTerminalLogger(options: TerminalLoggerOptions): Logger {\n return new TerminalLogger(options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const node_url = require('node:url');
|
|
5
|
+
const locationText = require('../domain/location-text.cjs');
|
|
6
|
+
const defaultPorts = require('./default-ports.cjs');
|
|
7
|
+
const optionErrors = require('./option-errors.cjs');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
10
|
+
|
|
11
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
12
|
+
|
|
13
|
+
const FILE_PROTOCOL = "file:";
|
|
14
|
+
const CWD_PROTOCOLS = /* @__PURE__ */ new Set([FILE_PROTOCOL, "http:", "https:"]);
|
|
15
|
+
const CWD_EXPECTED = "a directory path, or a file:, http: or https: URL";
|
|
16
|
+
function pathDirectoryUrl(directory) {
|
|
17
|
+
return node_url.pathToFileURL(path__default.join(path__default.resolve(directory), path__default.sep));
|
|
18
|
+
}
|
|
19
|
+
function cwdUrlOf(value) {
|
|
20
|
+
if (value === void 0) {
|
|
21
|
+
return pathDirectoryUrl(defaultPorts.processCwd());
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
if (!locationText.hasUrlScheme(value)) {
|
|
25
|
+
return pathDirectoryUrl(value);
|
|
26
|
+
}
|
|
27
|
+
return URL.parse(value, pathDirectoryUrl(defaultPorts.processCwd()).href) ?? void 0;
|
|
28
|
+
}
|
|
29
|
+
if (Buffer.isBuffer(value)) {
|
|
30
|
+
return pathDirectoryUrl(value.toString());
|
|
31
|
+
}
|
|
32
|
+
return value instanceof URL ? value : void 0;
|
|
33
|
+
}
|
|
34
|
+
function isLocalPath(url) {
|
|
35
|
+
try {
|
|
36
|
+
node_url.fileURLToPath(url);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function cwdKindOf(value, url) {
|
|
43
|
+
if (url?.protocol === FILE_PROTOCOL) {
|
|
44
|
+
return "a file: URL that names no local path";
|
|
45
|
+
}
|
|
46
|
+
return url === void 0 && typeof value === "string" ? "a string that is no valid URL" : optionErrors.kindOf(url ?? value);
|
|
47
|
+
}
|
|
48
|
+
function sourceCwdOf(options) {
|
|
49
|
+
const url = cwdUrlOf(options.cwd);
|
|
50
|
+
const isUsable = url !== void 0 && CWD_PROTOCOLS.has(url.protocol) && (url.protocol !== FILE_PROTOCOL || isLocalPath(url));
|
|
51
|
+
if (!isUsable) {
|
|
52
|
+
throw optionErrors.optionInvalid("cwd", CWD_EXPECTED, cwdKindOf(options.cwd, url));
|
|
53
|
+
}
|
|
54
|
+
return url.pathname.endsWith("/") ? url : new URL(`${url.pathname}/`, url);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
exports.sourceCwdOf = sourceCwdOf;
|
|
58
|
+
//# sourceMappingURL=cwd-option.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd-option.cjs","sources":["../../src/api/cwd-option.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { hasUrlScheme } from \"../domain/location-text.js\";\nimport type { GeneratorOptions } from \"../types.js\";\nimport { processCwd } from \"./default-ports.js\";\nimport { kindOf, optionInvalid } from \"./option-errors.js\";\n\n/**\n * The `cwd` option: the directory that relative `$ref`s of an inline source resolve against\n * (ADR-023). A string is read as `localRefRoot` is: a URL when it has a scheme, else a path.\n */\n\nconst FILE_PROTOCOL = \"file:\";\n\n/** A `file:` cwd is a local directory; an `http:` or `https:` one holds remote documents. */\nconst CWD_PROTOCOLS: ReadonlySet<string> = new Set([FILE_PROTOCOL, \"http:\", \"https:\"]);\n\nconst CWD_EXPECTED = \"a directory path, or a file:, http: or https: URL\";\n\n/** A path, resolved against the working directory, as a directory URL; `#` and `%` are encoded. */\nfunction pathDirectoryUrl(directory: string): URL {\n return pathToFileURL(path.join(path.resolve(directory), path.sep));\n}\n\n/** The URL the value names, before its scheme is checked; undefined for another type. */\nfunction cwdUrlOf(value: unknown): URL | undefined {\n if (value === undefined) {\n return pathDirectoryUrl(processCwd());\n }\n if (typeof value === \"string\") {\n if (!hasUrlScheme(value)) {\n return pathDirectoryUrl(value);\n }\n return URL.parse(value, pathDirectoryUrl(processCwd()).href) ?? undefined;\n }\n if (Buffer.isBuffer(value)) {\n return pathDirectoryUrl(value.toString());\n }\n return value instanceof URL ? value : undefined;\n}\n\n/** A `file:` URL with a host, or with an encoded slash, names no path on this platform. */\nfunction isLocalPath(url: URL): boolean {\n try {\n fileURLToPath(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/** What the cwd is instead of a directory, for the error. */\nfunction cwdKindOf(value: unknown, url: URL | undefined): string {\n if (url?.protocol === FILE_PROTOCOL) {\n return \"a file: URL that names no local path\";\n }\n return url === undefined && typeof value === \"string\"\n ? \"a string that is no valid URL\"\n : kindOf(url ?? value);\n}\n\n/**\n * The cwd as a URL that ends in a slash. Fails with `OPTION_INVALID` for another type, a URL of\n * another scheme or a `file:` URL that names no local path.\n */\nexport function sourceCwdOf(options: GeneratorOptions): URL {\n const url = cwdUrlOf(options.cwd);\n const isUsable =\n url !== undefined &&\n CWD_PROTOCOLS.has(url.protocol) &&\n (url.protocol !== FILE_PROTOCOL || isLocalPath(url));\n if (!isUsable) {\n throw optionInvalid(\"cwd\", CWD_EXPECTED, cwdKindOf(options.cwd, url));\n }\n return url.pathname.endsWith(\"/\") ? url : new URL(`${url.pathname}/`, url);\n}\n"],"names":["pathToFileURL","path","processCwd","hasUrlScheme","fileURLToPath","kindOf","optionInvalid"],"mappings":";;;;;;;;;;;;AAYA,MAAM,aAAA,GAAgB,OAAA;AAGtB,MAAM,gCAAqC,IAAI,GAAA,CAAI,CAAC,aAAA,EAAe,OAAA,EAAS,QAAQ,CAAC,CAAA;AAErF,MAAM,YAAA,GAAe,mDAAA;AAGrB,SAAS,iBAAiB,SAAA,EAAwB;AAChD,EAAA,OAAOA,sBAAA,CAAcC,cAAK,IAAA,CAAKA,aAAA,CAAK,QAAQ,SAAS,CAAA,EAAGA,aAAA,CAAK,GAAG,CAAC,CAAA;AACnE;AAGA,SAAS,SAAS,KAAA,EAAiC;AACjD,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,gBAAA,CAAiBC,yBAAY,CAAA;AAAA,EACtC;AACA,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,IAAI,CAACC,yBAAA,CAAa,KAAK,CAAA,EAAG;AACxB,MAAA,OAAO,iBAAiB,KAAK,CAAA;AAAA,IAC/B;AACA,IAAA,OAAO,GAAA,CAAI,MAAM,KAAA,EAAO,gBAAA,CAAiBD,yBAAY,CAAA,CAAE,IAAI,CAAA,IAAK,MAAA;AAAA,EAClE;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAC1B,IAAA,OAAO,gBAAA,CAAiB,KAAA,CAAM,QAAA,EAAU,CAAA;AAAA,EAC1C;AACA,EAAA,OAAO,KAAA,YAAiB,MAAM,KAAA,GAAQ,MAAA;AACxC;AAGA,SAAS,YAAY,GAAA,EAAmB;AACtC,EAAA,IAAI;AACF,IAAAE,sBAAA,CAAc,GAAG,CAAA;AACjB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAGA,SAAS,SAAA,CAAU,OAAgB,GAAA,EAA8B;AAC/D,EAAA,IAAI,GAAA,EAAK,aAAa,aAAA,EAAe;AACnC,IAAA,OAAO,sCAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA,KAAQ,UAAa,OAAO,KAAA,KAAU,WACzC,+BAAA,GACAC,mBAAA,CAAO,OAAO,KAAK,CAAA;AACzB;AAMO,SAAS,YAAY,OAAA,EAAgC;AAC1D,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,OAAA,CAAQ,GAAG,CAAA;AAChC,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,MAAA,IACR,aAAA,CAAc,GAAA,CAAI,GAAA,CAAI,QAAQ,CAAA,KAC7B,GAAA,CAAI,QAAA,KAAa,aAAA,IAAiB,WAAA,CAAY,GAAG,CAAA,CAAA;AACpD,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAMC,2BAAc,KAAA,EAAO,YAAA,EAAc,UAAU,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAC,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,QAAQ,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAC3E;;"}
|