openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +12 -5
  2. package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
  3. package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
  4. package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
  5. package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
  6. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
  7. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  8. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
  9. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  10. package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
  11. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
  13. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
  15. package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
  17. package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
  19. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
  21. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
  23. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  24. package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
  25. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  26. package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
  27. package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
  29. package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
  31. package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
  33. package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
  34. package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
  35. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  36. package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
  37. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  38. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
  39. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
  40. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
  41. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
  42. package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
  43. package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
  44. package/dist/api/default-ports.cjs +9 -0
  45. package/dist/api/default-ports.cjs.map +1 -1
  46. package/dist/api/default-ports.mjs +8 -1
  47. package/dist/api/default-ports.mjs.map +1 -1
  48. package/dist/api/discriminator-patch.cjs +5 -1
  49. package/dist/api/discriminator-patch.cjs.map +1 -1
  50. package/dist/api/discriminator-patch.mjs +5 -1
  51. package/dist/api/discriminator-patch.mjs.map +1 -1
  52. package/dist/api/document-refs.cjs +15 -12
  53. package/dist/api/document-refs.cjs.map +1 -1
  54. package/dist/api/document-refs.mjs +15 -12
  55. package/dist/api/document-refs.mjs.map +1 -1
  56. package/dist/api/document-run.cjs +4 -2
  57. package/dist/api/document-run.cjs.map +1 -1
  58. package/dist/api/document-run.mjs +4 -2
  59. package/dist/api/document-run.mjs.map +1 -1
  60. package/dist/api/footer-sink.cjs +25 -2
  61. package/dist/api/footer-sink.cjs.map +1 -1
  62. package/dist/api/footer-sink.mjs +25 -2
  63. package/dist/api/footer-sink.mjs.map +1 -1
  64. package/dist/api/legacy-callback-warning.cjs +4 -1
  65. package/dist/api/legacy-callback-warning.cjs.map +1 -1
  66. package/dist/api/legacy-callback-warning.mjs +4 -1
  67. package/dist/api/legacy-callback-warning.mjs.map +1 -1
  68. package/dist/api/legacy-ts-helpers.cjs.map +1 -1
  69. package/dist/api/legacy-ts-helpers.mjs.map +1 -1
  70. package/dist/api/legacy-utils.cjs.map +1 -1
  71. package/dist/api/legacy-utils.mjs.map +1 -1
  72. package/dist/api/openapi-ts.cjs +2 -1
  73. package/dist/api/openapi-ts.cjs.map +1 -1
  74. package/dist/api/openapi-ts.mjs +3 -2
  75. package/dist/api/openapi-ts.mjs.map +1 -1
  76. package/dist/api/transform-context.cjs +1 -1
  77. package/dist/api/transform-context.cjs.map +1 -1
  78. package/dist/api/transform-context.mjs +1 -1
  79. package/dist/api/transform-context.mjs.map +1 -1
  80. package/dist/api/transform-schema.cjs.map +1 -1
  81. package/dist/api/transform-schema.mjs.map +1 -1
  82. package/dist/application/print/type-printer.cjs +2 -1
  83. package/dist/application/print/type-printer.cjs.map +1 -1
  84. package/dist/application/print/type-printer.mjs +2 -1
  85. package/dist/application/print/type-printer.mjs.map +1 -1
  86. package/dist/application/transform/document/parameters.cjs +1 -1
  87. package/dist/application/transform/document/parameters.cjs.map +1 -1
  88. package/dist/application/transform/document/parameters.mjs +1 -1
  89. package/dist/application/transform/document/parameters.mjs.map +1 -1
  90. package/dist/application/transform/document/paths.cjs +1 -1
  91. package/dist/application/transform/document/paths.cjs.map +1 -1
  92. package/dist/application/transform/document/paths.mjs +1 -1
  93. package/dist/application/transform/document/paths.mjs.map +1 -1
  94. package/dist/application/transform/footer/evaluation.cjs +9 -0
  95. package/dist/application/transform/footer/evaluation.cjs.map +1 -1
  96. package/dist/application/transform/footer/evaluation.mjs +8 -1
  97. package/dist/application/transform/footer/evaluation.mjs.map +1 -1
  98. package/dist/application/transform/footer/type-evaluation.cjs +4 -112
  99. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
  100. package/dist/application/transform/footer/type-evaluation.mjs +4 -112
  101. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
  102. package/dist/application/transform/footer/type-indexing.cjs +107 -0
  103. package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
  104. package/dist/application/transform/footer/type-indexing.mjs +105 -0
  105. package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
  106. package/dist/cli/api-settings.cjs +78 -0
  107. package/dist/cli/api-settings.cjs.map +1 -0
  108. package/dist/cli/api-settings.mjs +73 -0
  109. package/dist/cli/api-settings.mjs.map +1 -0
  110. package/dist/cli/cli-arguments.cjs +1 -0
  111. package/dist/cli/cli-arguments.cjs.map +1 -1
  112. package/dist/cli/cli-arguments.mjs +1 -1
  113. package/dist/cli/cli-arguments.mjs.map +1 -1
  114. package/dist/cli/help-text.cjs +3 -3
  115. package/dist/cli/help-text.cjs.map +1 -1
  116. package/dist/cli/help-text.mjs +3 -3
  117. package/dist/cli/help-text.mjs.map +1 -1
  118. package/dist/cli/redocly-apis.cjs +8 -4
  119. package/dist/cli/redocly-apis.cjs.map +1 -1
  120. package/dist/cli/redocly-apis.mjs +7 -3
  121. package/dist/cli/redocly-apis.mjs.map +1 -1
  122. package/dist/cli/run-cli.cjs +5 -1
  123. package/dist/cli/run-cli.cjs.map +1 -1
  124. package/dist/cli/run-cli.mjs +5 -1
  125. package/dist/cli/run-cli.mjs.map +1 -1
  126. package/dist/domain/errors.cjs +4 -1
  127. package/dist/domain/errors.cjs.map +1 -1
  128. package/dist/domain/errors.mjs +4 -1
  129. package/dist/domain/errors.mjs.map +1 -1
  130. package/dist/domain/ir/constructors.cjs +2 -0
  131. package/dist/domain/ir/constructors.cjs.map +1 -1
  132. package/dist/domain/ir/constructors.mjs +2 -1
  133. package/dist/domain/ir/constructors.mjs.map +1 -1
  134. package/dist/domain/openapi/path-templates.cjs +1 -1
  135. package/dist/domain/openapi/path-templates.cjs.map +1 -1
  136. package/dist/domain/openapi/path-templates.mjs +1 -1
  137. package/dist/domain/openapi/path-templates.mjs.map +1 -1
  138. package/dist/domain/url-credentials.cjs +10 -0
  139. package/dist/domain/url-credentials.cjs.map +1 -0
  140. package/dist/domain/url-credentials.mjs +8 -0
  141. package/dist/domain/url-credentials.mjs.map +1 -0
  142. package/dist/domain/warnings.cjs +9 -1
  143. package/dist/domain/warnings.cjs.map +1 -1
  144. package/dist/domain/warnings.mjs +9 -1
  145. package/dist/domain/warnings.mjs.map +1 -1
  146. package/dist/index.cjs +2 -0
  147. package/dist/index.cjs.map +1 -1
  148. package/dist/index.d.cts +1160 -53
  149. package/dist/index.d.mts +1160 -53
  150. package/dist/index.d.ts +1160 -53
  151. package/dist/index.mjs +1 -0
  152. package/dist/index.mjs.map +1 -1
  153. package/dist/transform/path-item-object.cjs.map +1 -1
  154. package/dist/transform/path-item-object.mjs.map +1 -1
  155. package/package.json +6 -5
  156. package/src/adapters/redocly-loader/configured-headers.ts +65 -0
  157. package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
  158. package/src/adapters/redocly-loader/ref-access.ts +60 -5
  159. package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
  160. package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
  161. package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
  162. package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
  163. package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
  164. package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
  165. package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
  166. package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
  167. package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
  168. package/src/api/default-ports.ts +16 -0
  169. package/src/api/discriminator-patch.ts +7 -2
  170. package/src/api/document-refs.ts +19 -15
  171. package/src/api/document-run.ts +9 -6
  172. package/src/api/footer-sink.ts +34 -2
  173. package/src/api/legacy-callback-warning.ts +7 -3
  174. package/src/api/legacy-ts-helpers.ts +7 -5
  175. package/src/api/legacy-utils.ts +1 -1
  176. package/src/api/openapi-ts.ts +2 -0
  177. package/src/api/run-context.ts +34 -0
  178. package/src/api/transform-context.ts +10 -8
  179. package/src/api/transform-schema.ts +6 -5
  180. package/src/application/print/type-printer.ts +2 -1
  181. package/src/application/transform/document/parameters.ts +2 -1
  182. package/src/application/transform/document/paths.ts +2 -1
  183. package/src/application/transform/footer/evaluation.ts +10 -0
  184. package/src/application/transform/footer/type-evaluation.ts +6 -133
  185. package/src/application/transform/footer/type-indexing.ts +152 -0
  186. package/src/cli/api-settings.ts +109 -0
  187. package/src/cli/cli-arguments.ts +5 -2
  188. package/src/cli/help-text.ts +4 -4
  189. package/src/cli/redocly-apis.ts +13 -7
  190. package/src/cli/run-cli.ts +5 -1
  191. package/src/domain/errors.ts +8 -2
  192. package/src/domain/ir/constructors.ts +3 -0
  193. package/src/domain/ir/nodes.ts +9 -6
  194. package/src/domain/openapi/path-templates.ts +2 -1
  195. package/src/domain/url-credentials.ts +18 -0
  196. package/src/domain/warnings.ts +8 -0
  197. package/src/index.ts +1 -0
  198. package/src/transform/path-item-object.ts +6 -2
  199. package/src/types/generator-options.ts +12 -32
  200. package/src/types/openapi-common.ts +1 -0
  201. package/src/types/openapi-request.ts +3 -0
  202. package/src/types/openapi-schema.ts +2 -0
  203. package/src/types/openapi-security.ts +1 -0
@@ -0,0 +1,152 @@
1
+ import type { IrMember, IrType } from "../../../domain/ir/nodes.js";
2
+ import {
3
+ type Evaluated,
4
+ type Evaluation,
5
+ intersectionType,
6
+ isNullish,
7
+ MISSING,
8
+ NEVER,
9
+ OPAQUE,
10
+ propertyKey,
11
+ unionType,
12
+ } from "./evaluation.js";
13
+ import { declaredFirst, isNeverIntersection, type TypeExpansion } from "./type-rules.js";
14
+
15
+ /**
16
+ * Indexed access (`T["key"]`, `T[number]`) on the generated types, as TypeScript reads it, for
17
+ * the evaluator of the `--enum-values` check (ADR-027).
18
+ */
19
+
20
+ /** The part of the evaluator that indexed access uses. */
21
+ export interface IndexedTypes extends TypeExpansion {
22
+ expand(value: Evaluated): Evaluation;
23
+ index(value: Evaluated, index: IrType): Evaluation;
24
+ }
25
+
26
+ /** `value[index]` for a string literal, `string` or `number` index. */
27
+ export function indexType(types: IndexedTypes, value: Evaluated, index: IrType): Evaluation {
28
+ const owner = types.expand(value);
29
+ if (owner.kind !== "type") {
30
+ return owner;
31
+ }
32
+ if (types.membersOf(owner).length > 1) {
33
+ return unionIndex(types, owner, index);
34
+ }
35
+ const { type } = owner;
36
+ switch (type.kind) {
37
+ case "object":
38
+ return memberType(types, owner, type.members, index);
39
+ case "intersection":
40
+ return partsIndex(types, owner, type.types, index);
41
+ case "union":
42
+ return unionIndex(types, owner, index);
43
+ case "array":
44
+ return isNumberIndex(index) ? flattened(types, owner, type.element) : builtInIndex(index);
45
+ case "keyword":
46
+ return type.keyword === "never" ? owner : builtInIndex(index);
47
+ default:
48
+ return builtInIndex(index);
49
+ }
50
+ }
51
+
52
+ function memberType(
53
+ types: IndexedTypes,
54
+ owner: Evaluated,
55
+ members: readonly IrMember[],
56
+ index: IrType,
57
+ ): Evaluation {
58
+ const key = indexKey(index);
59
+ const property = members.find(
60
+ (member) => member.kind === "property" && key !== null && propertyKey(member.name) === key,
61
+ );
62
+ if (property !== undefined) {
63
+ return flattened(types, owner, property.type);
64
+ }
65
+ const signature = members.find((member) => member.kind === "indexSignature");
66
+ if (signature !== undefined) {
67
+ return flattened(types, owner, signature.type);
68
+ }
69
+ return key === null || isObjectBuiltIn(key) ? OPAQUE : MISSING;
70
+ }
71
+
72
+ function partsIndex(
73
+ types: IndexedTypes,
74
+ owner: Evaluated,
75
+ parts: readonly IrType[],
76
+ index: IrType,
77
+ ): Evaluation {
78
+ if (isNeverIntersection(types, owner, parts)) {
79
+ return { ...owner, type: NEVER };
80
+ }
81
+ const found = parts.map((type) => types.index({ ...owner, type }, index));
82
+ if (found.some((part) => part.kind === "opaque")) {
83
+ return OPAQUE;
84
+ }
85
+ const read = parts.map((type, at) => ({ type, found: found[at] as Evaluation }));
86
+ const kept = declaredFirst(types, owner, indexKey(index), read).flatMap((part) =>
87
+ part.kind === "type" ? [part.type] : [],
88
+ );
89
+ return kept.length === 0 ? MISSING : { ...owner, type: intersectionType(kept) };
90
+ }
91
+
92
+ function unionIndex(types: IndexedTypes, owner: Evaluated, index: IrType): Evaluation {
93
+ const found = types
94
+ .membersOf(owner)
95
+ .map((member) => (member.kind === "type" ? types.index(member, index) : member));
96
+ if (found.some((member) => member.kind === "missing")) {
97
+ return MISSING;
98
+ }
99
+ if (found.some((member) => member.kind === "opaque")) {
100
+ return OPAQUE;
101
+ }
102
+ return { ...owner, type: unionType((found as Evaluated[]).map((member) => member.type)) };
103
+ }
104
+
105
+ /**
106
+ * A member read through `FlattenedDeepRequired`: a value that is only arrays (and `undefined` or
107
+ * `null`) is replaced by its elements, anything else loses `undefined` and `null`.
108
+ */
109
+ function flattened(types: IndexedTypes, owner: Evaluated, type: IrType): Evaluation {
110
+ const member: Evaluated = { ...owner, type };
111
+ if (!owner.deep) {
112
+ return types.expand(member);
113
+ }
114
+ const members = types.membersOf(member);
115
+ if (members.some((candidate) => candidate.kind !== "type")) {
116
+ return OPAQUE;
117
+ }
118
+ const present = (members as Evaluated[]).filter((candidate) => !isNullish(candidate.type));
119
+ const elements = present.map((candidate) => arrayElement(candidate.type));
120
+ if (present.length > 0 && elements.every((element) => element !== null)) {
121
+ return types.expand({ ...owner, type: unionType(elements as IrType[]) });
122
+ }
123
+ return { ...owner, type: unionType(present.map((candidate) => candidate.type)) };
124
+ }
125
+
126
+ /** The element of an expanded member; expanding already dropped `readonly`. */
127
+ function arrayElement(type: IrType): IrType | null {
128
+ return type.kind === "array" ? type.element : null;
129
+ }
130
+
131
+ function indexKey(index: IrType): string | null {
132
+ if (index.kind === "stringLiteral") {
133
+ return index.value;
134
+ }
135
+ return index.kind === "numberLiteral" ? String(index.value) : null;
136
+ }
137
+
138
+ function isNumberIndex(index: IrType): boolean {
139
+ const key = indexKey(index);
140
+ return (index.kind === "keyword" && index.keyword === "number") || /^\d+$/.test(key ?? "");
141
+ }
142
+
143
+ function isObjectBuiltIn(key: string): boolean {
144
+ return key in {};
145
+ }
146
+
147
+ /** A key of a primitive or an array: a built-in member is opaque, any other key is missing. */
148
+ function builtInIndex(index: IrType): Evaluation {
149
+ const key = indexKey(index);
150
+ const isBuiltIn = key === null || key in String.prototype || key in Array.prototype;
151
+ return isBuiltIn || key in Number.prototype ? OPAQUE : MISSING;
152
+ }
@@ -0,0 +1,109 @@
1
+ import { kebabCase } from "scule";
2
+ import { WARNING_CODES, warningText } from "../domain/warnings.js";
3
+ import { GENERATOR_FLAGS, hostList } from "./cli-arguments.js";
4
+ import { cliError } from "./cli-errors.js";
5
+ import { resolveLocationUrl } from "./cli-output.js";
6
+ import type { RefLimitOptions } from "./ref-limits.js";
7
+
8
+ /**
9
+ * The settings of one API in `redocly.yaml` (`apis.<name>.x-openapi-ts`). A setting has the name
10
+ * of its CLI flag, in kebab-case or camelCase (ADR-021, decision 1); a setting the CLI does not
11
+ * read gets a warning, as an unknown flag gets a notice.
12
+ */
13
+
14
+ export type ApiSettings = Readonly<Record<string, unknown>>;
15
+
16
+ /** The setting that names the output file. */
17
+ export const OUTPUT_SETTING = "output";
18
+
19
+ /** The setting that lists the API's output fixes. */
20
+ export const FIX_SETTING = "fix";
21
+
22
+ const LOCAL_REF_ROOT_SETTING = "localRefRoot";
23
+ const REMOTE_REF_HOSTS_SETTING = "remoteRefHosts";
24
+
25
+ /** `--content-never` is accepted and has no effect, as on the command line. */
26
+ const IGNORED_SETTINGS = ["contentNever"] as const;
27
+
28
+ const CAMEL_CASE_SETTINGS = [
29
+ OUTPUT_SETTING,
30
+ FIX_SETTING,
31
+ LOCAL_REF_ROOT_SETTING,
32
+ REMOTE_REF_HOSTS_SETTING,
33
+ ...GENERATOR_FLAGS,
34
+ ...IGNORED_SETTINGS,
35
+ ];
36
+
37
+ const KNOWN_SETTINGS = new Set<string>([
38
+ ...CAMEL_CASE_SETTINGS,
39
+ ...CAMEL_CASE_SETTINGS.map((name) => kebabCase(name)),
40
+ ]);
41
+
42
+ /** The warning for each setting of an API that no code reads. */
43
+ export function unknownSettingWarnings(apiName: string, settings: ApiSettings): string[] {
44
+ return Object.keys(settings)
45
+ .filter((key) => !KNOWN_SETTINGS.has(key))
46
+ .map((key) =>
47
+ warningText(
48
+ `API ${apiName}: the setting \`${key}\` is not an openapi-typesafe option and is ignored`,
49
+ WARNING_CODES.CONFIG_SETTING_UNKNOWN,
50
+ ),
51
+ );
52
+ }
53
+
54
+ /** The value of a setting in camelCase, else in kebab-case; `undefined` when it is absent. */
55
+ function settingValue(settings: ApiSettings, name: string): unknown {
56
+ return name in settings ? settings[name] : settings[kebabCase(name)];
57
+ }
58
+
59
+ function hasSetting(settings: ApiSettings, name: string): boolean {
60
+ return name in settings || kebabCase(name) in settings;
61
+ }
62
+
63
+ function invalidSetting(apiName: string, name: string, expected: string): Error {
64
+ return cliError(
65
+ "CLI_CONFIG_INVALID",
66
+ `API ${apiName}: \`${kebabCase(name)}\` in x-openapi-ts must be ${expected}`,
67
+ );
68
+ }
69
+
70
+ /** `local-ref-root: dir`, relative to the config file; empty means the config file's directory. */
71
+ function localRefRootOf(apiName: string, settings: ApiSettings, configRoot: URL): RefLimitOptions {
72
+ if (!hasSetting(settings, LOCAL_REF_ROOT_SETTING)) {
73
+ return {};
74
+ }
75
+ const value = settingValue(settings, LOCAL_REF_ROOT_SETTING) ?? "";
76
+ if (typeof value !== "string") {
77
+ throw invalidSetting(apiName, LOCAL_REF_ROOT_SETTING, "a directory path");
78
+ }
79
+ return { localRefRoot: resolveLocationUrl(value === "" ? "." : value, configRoot) };
80
+ }
81
+
82
+ /** `remote-ref-hosts: [a, b]` or `a,b`; an empty list or value allows no host. */
83
+ function remoteRefHostsOf(apiName: string, settings: ApiSettings): RefLimitOptions {
84
+ if (!hasSetting(settings, REMOTE_REF_HOSTS_SETTING)) {
85
+ return {};
86
+ }
87
+ const value = settingValue(settings, REMOTE_REF_HOSTS_SETTING) ?? [];
88
+ const entries = [value].flat();
89
+ if (entries.some((entry) => typeof entry !== "string")) {
90
+ throw invalidSetting(apiName, REMOTE_REF_HOSTS_SETTING, "a host list");
91
+ }
92
+ return { remoteRefHosts: hostList(entries) ?? [] };
93
+ }
94
+
95
+ /**
96
+ * The `$ref` limits an API sets (threat model R1, R2). Each one replaces the CLI flag of the same
97
+ * name for this API, as its boolean settings and `fix` do; fails with `CLI_CONFIG_INVALID` for a
98
+ * value of another type.
99
+ */
100
+ export function apiRefLimitOptions(
101
+ apiName: string,
102
+ settings: ApiSettings,
103
+ configRoot: URL,
104
+ ): RefLimitOptions {
105
+ return {
106
+ ...localRefRootOf(apiName, settings, configRoot),
107
+ ...remoteRefHostsOf(apiName, settings),
108
+ };
109
+ }
@@ -122,8 +122,11 @@ function lastString(value: unknown): string | undefined {
122
122
  return optionalString(Array.isArray(value) ? value.at(-1) : value);
123
123
  }
124
124
 
125
- /** `--remote-ref-hosts a.example,b.example`, repeatable; an empty value allows no host. */
126
- function hostList(value: unknown): string[] | undefined {
125
+ /**
126
+ * `--remote-ref-hosts a.example,b.example`, repeatable; an empty value allows no host. The
127
+ * `remote-ref-hosts` setting of a `redocly.yaml` API is read the same way.
128
+ */
129
+ export function hostList(value: unknown): string[] | undefined {
127
130
  if (value === undefined) {
128
131
  return undefined;
129
132
  }
@@ -4,7 +4,7 @@ export const HELP_TEXT = [
4
4
  " $ openapi-typesafe [input] [options]",
5
5
  "",
6
6
  "Options",
7
- " --help Display this",
7
+ " --help Show this help",
8
8
  " --version Display the version",
9
9
  " --redocly [path], -c Specify path to Redocly config (default: redocly.yaml)",
10
10
  " --output, -o Specify output file (if not specified in redocly.yaml)",
@@ -20,8 +20,8 @@ export const HELP_TEXT = [
20
20
  " --additional-properties Treat schema objects as if `additionalProperties: true` is set",
21
21
  " --empty-objects-unknown Generate `unknown` instead of" +
22
22
  " `Record<string, never>` for empty objects",
23
- " --default-non-nullable Set to `false` to ignore default values" +
24
- " when generating non-nullable types",
23
+ " --default-non-nullable A property with a `default` is required" +
24
+ " (default: true; =false makes it optional)",
25
25
  " --properties-required-by-default",
26
26
  " Treat schema objects as if `required` is" +
27
27
  " set to all properties by default",
@@ -35,7 +35,7 @@ export const HELP_TEXT = [
35
35
  " undefined by schema",
36
36
  " --alphabetize Sort object keys alphabetically",
37
37
  " --exclude-deprecated Exclude deprecated types",
38
- " --root-types Export schemas types at root level",
38
+ " --root-types Export `components` types as root-level aliases",
39
39
  " --root-types-no-schema-prefix",
40
40
  ' Do not add "Schema" prefix to types at' +
41
41
  " the root level (should only be used with --root-types)",
@@ -2,6 +2,13 @@ import { kebabCase } from "scule";
2
2
  import type { RedoclyConfig } from "../application/ports/redocly-config-source.js";
3
3
  import type { OutputFixName } from "../domain/output-fixes.js";
4
4
  import type { GeneratorOptions } from "../types.js";
5
+ import {
6
+ type ApiSettings,
7
+ apiRefLimitOptions,
8
+ FIX_SETTING,
9
+ OUTPUT_SETTING,
10
+ unknownSettingWarnings,
11
+ } from "./api-settings.js";
5
12
  import { GENERATOR_FLAGS, type GeneratorFlags } from "./cli-arguments.js";
6
13
  import { cliError } from "./cli-errors.js";
7
14
  import { type CliRun, emitOutput, resolveLocationUrl } from "./cli-output.js";
@@ -14,11 +21,6 @@ import { refLimitOptions } from "./ref-limits.js";
14
21
  /** The key of an API's openapi-typesafe settings in `redocly.yaml`. */
15
22
  const REDOCLY_CONFIG_KEY = "x-openapi-ts";
16
23
 
17
- /** The setting of an API that lists its output fixes. */
18
- const FIX_SETTING = "fix";
19
-
20
- type ApiSettings = Readonly<Record<string, unknown>>;
21
-
22
24
  interface RedoclyApi {
23
25
  readonly name: string;
24
26
  readonly root: string;
@@ -59,19 +61,23 @@ function configRootOf(redocly: RedoclyConfig, cwd: URL): URL {
59
61
  }
60
62
 
61
63
  async function generateApi(run: CliRun, redocly: RedoclyConfig, api: RedoclyApi): Promise<void> {
62
- if (!api.settings?.output) {
64
+ if (!api.settings?.[OUTPUT_SETTING]) {
63
65
  throw cliError(
64
66
  "CLI_CONFIG_OUTPUT_MISSING",
65
67
  `API ${api.name} is missing an \`${REDOCLY_CONFIG_KEY}.output\` key. ` +
66
68
  `See ${MULTIPLE_SCHEMAS_DOCS_URL}`,
67
69
  );
68
70
  }
69
- const output = String(api.settings.output);
71
+ for (const warning of unknownSettingWarnings(api.name, api.settings)) {
72
+ run.deps.logger.warn(warning);
73
+ }
74
+ const output = String(api.settings[OUTPUT_SETTING]);
70
75
  const configRoot = configRootOf(redocly, run.deps.cwd);
71
76
  const options: GeneratorOptions = {
72
77
  ...run.args.generatorFlags,
73
78
  ...apiFlags(api.settings),
74
79
  ...refLimitOptions(run.args, run.deps.cwd),
80
+ ...apiRefLimitOptions(api.name, api.settings, configRoot),
75
81
  ...fixOption(apiFixNames(api.settings) ?? run.args.fix),
76
82
  redocly,
77
83
  };
@@ -1,5 +1,6 @@
1
1
  import type { Logger } from "../application/ports/logger.js";
2
2
  import type { RedoclyConfig } from "../application/ports/redocly-config-source.js";
3
+ import { WARNING_CODES, warningText } from "../domain/warnings.js";
3
4
  import type { SchemaSource } from "../types.js";
4
5
  import { ineffectiveFlagNotices, parseCliArguments } from "./cli-arguments.js";
5
6
  import type { CliDependencies } from "./cli-dependencies.js";
@@ -84,7 +85,10 @@ async function generate(run: CliRun): Promise<unknown[]> {
84
85
  }
85
86
  if (run.args.input) {
86
87
  run.deps.logger.warn(
87
- "APIs are specified both in Redocly Config and CLI argument. Only using Redocly config.",
88
+ warningText(
89
+ "APIs are specified both in Redocly Config and CLI argument. Only using Redocly config.",
90
+ WARNING_CODES.CLI_INPUT_IGNORED,
91
+ ),
88
92
  );
89
93
  }
90
94
  return generateRedoclyApis(run, redocly);
@@ -1,3 +1,5 @@
1
+ import { redactUrlCredentials } from "./url-credentials.js";
2
+
1
3
  /**
2
4
  * Typed errors (ENGINEERING_PLAN 4.6, ADR-011). Every expected failure of the generator and the CLI
3
5
  * is an `OpenAPITypesafeError` with a stable machine-readable `code` and structured `details`. The
@@ -48,13 +50,17 @@ export interface OpenAPITypesafeErrorInit {
48
50
  readonly cause?: unknown;
49
51
  }
50
52
 
51
- /** The base of every expected failure; check `code`, not the class or the message. */
53
+ /**
54
+ * The base of every expected failure; check `code`, not the class or the message. The message
55
+ * never shows the user info of a URL (`url-credentials.ts`).
56
+ */
52
57
  export class OpenAPITypesafeError extends Error {
53
58
  readonly code: ErrorCode;
54
59
  readonly details: ErrorDetails;
55
60
 
56
61
  constructor(init: OpenAPITypesafeErrorInit) {
57
- super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
62
+ const message = redactUrlCredentials(init.message);
63
+ super(message, init.cause === undefined ? undefined : { cause: init.cause });
58
64
  this.name = "OpenAPITypesafeError";
59
65
  this.code = init.code;
60
66
  this.details = init.details ?? {};
@@ -33,6 +33,9 @@ export function irKeyword(keyword: IrKeyword): IrKeywordType {
33
33
  return { kind: "keyword", keyword };
34
34
  }
35
35
 
36
+ /** The encoding of a string literal that does not name one. */
37
+ export const DEFAULT_STRING_ENCODING: IrStringEncoding = "json";
38
+
36
39
  export function irString(value: string, encoding: IrStringEncoding): IrStringLiteralType {
37
40
  return { kind: "stringLiteral", value, encoding };
38
41
  }
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Type IR: the closed set of immutable nodes that describes the TypeScript types and declarations
3
3
  * the generator emits (ENGINEERING_PLAN §4.2, ADR-002). Nodes are plain readonly data with a `kind`
4
- * discriminator; every consumer dispatches with an exhaustive `switch`, so a new kind breaks the
5
- * build wherever it must be handled.
4
+ * discriminator; every consumer in this package dispatches with an exhaustive `switch`, so a new
5
+ * kind breaks the build wherever it must be handled. For users the set is open: a minor release
6
+ * may add a kind (ADR-034), so code outside the package must keep a node of an unknown kind as it
7
+ * is, and a `switch` over `kind` needs a `default` branch.
6
8
  *
7
9
  * Utility types (`Omit`, `Record`, `WithRequired`, `Extract`) are `reference` nodes with type
8
10
  * arguments, built by named constructors: the printer has nothing special to do for them.
@@ -35,7 +37,8 @@ export interface IrKeywordType {
35
37
  export interface IrStringLiteralType {
36
38
  readonly kind: "stringLiteral";
37
39
  readonly value: string;
38
- readonly encoding: IrStringEncoding;
40
+ /** How the printer writes the value; unset is `json`. A detail of the printer. */
41
+ readonly encoding?: IrStringEncoding;
39
42
  }
40
43
 
41
44
  /** Finite number; a negative value prints with a leading minus sign. */
@@ -112,9 +115,9 @@ export interface IrRawType {
112
115
  readonly kind: "raw";
113
116
  readonly text: string;
114
117
  /**
115
- * The outside node the text was printed from, opaque to the IR. While the Node API still
116
- * returns `ts.Node`s (until Phase 6), the bridge hands this node back unchanged, exactly as the
117
- * original did. Removed with the reverse bridge in Phase 6.
118
+ * @internal The TypeScript node the bridge printed the text from. The reverse bridge (the
119
+ * deprecated `ts.Node` callbacks, ADR-004) hands it back unchanged. Set by the generator only:
120
+ * read it as opaque, never set it, and expect it to go away in 2.0.
118
121
  */
119
122
  readonly origin?: unknown;
120
123
  }
@@ -4,7 +4,8 @@
4
4
  * `/a/{name}`, match the same URLs, so a client cannot tell which one a request is for.
5
5
  */
6
6
 
7
- const PATH_PARAMETER_RE = /\{[^}]+\}/g;
7
+ /** `[^{}]` keeps the scan linear: `[^}]` retried from every `{` of a long run of them. */
8
+ const PATH_PARAMETER_RE = /\{[^{}]+\}/g;
8
9
  const PATH_PREFIX = "/";
9
10
  const UNNAMED_PARAMETER = "{}";
10
11
 
@@ -0,0 +1,18 @@
1
+ /**
2
+ * URLs with user info (`https://user:token@host/…`) can reach a message: the CLI input, a `$ref`,
3
+ * a redirect. Messages end up in CI job logs, so the user info is hidden in every one (audit B2,
4
+ * F12). Fetch refuses such URLs anyway, so a message never needs the credentials.
5
+ */
6
+
7
+ /**
8
+ * The user info after a scheme: everything up to an `@` that comes before the end of the
9
+ * authority. The class stops at `/`, so a scan never runs past the next `://`; it stays linear.
10
+ */
11
+ const URL_USER_INFO = /:\/\/[^\s/?#@]*@/g;
12
+
13
+ const REDACTED_USER_INFO = "://***@";
14
+
15
+ /** The text with the user info of every URL in it replaced by `***`. */
16
+ export function redactUrlCredentials(text: string): string {
17
+ return text.replace(URL_USER_INFO, () => REDACTED_USER_INFO);
18
+ }
@@ -6,10 +6,18 @@
6
6
 
7
7
  /** Every warning code. */
8
8
  export const WARNING_CODES = {
9
+ CLI_INPUT_IGNORED: "CLI_INPUT_IGNORED",
10
+ CONFIG_SETTING_UNKNOWN: "CONFIG_SETTING_UNKNOWN",
11
+ DISCRIMINATOR_MAPPING_INVALID: "DISCRIMINATOR_MAPPING_INVALID",
9
12
  DISCRIMINATOR_VALUE_NOT_ALLOWED: "DISCRIMINATOR_VALUE_NOT_ALLOWED",
10
13
  ENUM_VALUES_EXCLUDED: "ENUM_VALUES_EXCLUDED",
14
+ FIX_NEEDS_OPTION: "FIX_NEEDS_OPTION",
15
+ LEGACY_CALLBACK_DEPRECATED: "LEGACY_CALLBACK_DEPRECATED",
16
+ LINT_PROBLEM: "LINT_PROBLEM",
11
17
  PATH_TEMPLATES_IDENTICAL: "PATH_TEMPLATES_IDENTICAL",
12
18
  QUERYSTRING_NEXT_TO_QUERY: "QUERYSTRING_NEXT_TO_QUERY",
19
+ REF_CIRCULAR: "REF_CIRCULAR",
20
+ REF_NOT_FOUND: "REF_NOT_FOUND",
13
21
  } as const;
14
22
 
15
23
  export type WarningCode = (typeof WARNING_CODES)[keyof typeof WARNING_CODES];
package/src/index.ts CHANGED
@@ -48,6 +48,7 @@ export {
48
48
  OutputFix,
49
49
  type OutputFixName,
50
50
  } from "./domain/output-fixes.js";
51
+ export { WARNING_CODES, type WarningCode } from "./domain/warnings.js";
51
52
  export { default as transformComponentsObject } from "./transform/components-object.js";
52
53
  export { default as transformHeaderObject } from "./transform/header-object.js";
53
54
  export { default as transformMediaTypeObject } from "./transform/media-type-object.js";
@@ -1,10 +1,14 @@
1
1
  import { createTransformContext, schemaLocation } from "../api/transform-context.js";
2
2
  import { pathItemType } from "../application/transform/document/path-item.js";
3
3
  import type { IrType } from "../domain/ir/nodes.js";
4
- import type { HttpMethod } from "../domain/openapi/path-item-object.js";
4
+ import type { PathItemMethod } from "../domain/openapi/path-item-object.js";
5
5
  import type { PathItemObject, TransformNodeOptions } from "../types.js";
6
6
 
7
- export type Method = HttpMethod;
7
+ /**
8
+ * The operation methods of a path item: openapi-typescript's eight, plus the OpenAPI 3.2 `query`
9
+ * (ADR-020), as `HttpMethod` of openapi-typesafe-helpers lists them.
10
+ */
11
+ export type Method = PathItemMethod;
8
12
 
9
13
  /**
10
14
  * Transform PathItem nodes (4.8.9)
@@ -3,8 +3,6 @@ import type { Readable } from "node:stream";
3
3
  import type { Config as RedoclyConfig } from "@redocly/openapi-core";
4
4
  import type { ts } from "../adapters/type-ast-bridge/typescript-types.js";
5
5
  import type { FooterEntry } from "../api/footer-entry.js";
6
- import type { Logger } from "../application/ports/logger.js";
7
- import type { DocumentFooter } from "../application/transform/transform-context.js";
8
6
  import type { IrPropertySignature, IrType } from "../domain/ir/nodes.js";
9
7
  import type { OutputFix, OutputFixName } from "../domain/output-fixes.js";
10
8
  import type { ReferenceObject } from "./openapi-common.js";
@@ -74,7 +72,7 @@ export interface OpenAPITSBaseOptions {
74
72
  lint?: boolean;
75
73
  /** (optional) Should logging be suppressed? (necessary for STDOUT) */
76
74
  silent?: boolean;
77
- /** (optional) OpenAPI version. Must be present if parsing raw schema */
75
+ /** Accepted for compatibility with openapi-typescript; not read. */
78
76
  version?: number;
79
77
  /** (optional) Export type instead of interface */
80
78
  exportType?: boolean;
@@ -197,28 +195,22 @@ export interface OpenAPITSIrOptions extends OpenAPITSBaseOptions, IrCallbackOpti
197
195
  /** Either form of the generator options. */
198
196
  export type GeneratorOptions = OpenAPITSOptions | OpenAPITSIrOptions;
199
197
 
200
- /** Context passed to all submodules */
198
+ /**
199
+ * Context passed to all submodules, and to the callbacks as `options.ctx`. To call an exported
200
+ * transformer outside a run, build one with the fields of openapi-typescript's context; the
201
+ * fields openapi-typesafe added are optional.
202
+ */
201
203
  export interface GlobalContext {
202
204
  // user options
203
205
  additionalProperties: boolean;
204
206
  alphabetize: boolean;
205
207
  arrayLength: boolean;
206
- binaryAsBlob: boolean;
208
+ /** `format: binary` and `contentMediaType` strings become `Blob` (default: false). */
209
+ binaryAsBlob?: boolean;
207
210
  defaultNonNullable: boolean;
208
211
  discriminators: {
209
212
  objects: Record<string, DiscriminatorObject>;
210
213
  refsHandled: string[];
211
- /**
212
- * @internal With the `discriminator-values` fix: for each enum the scan added to a
213
- * member and each inherited discriminator object, the enum of the owner's property and the
214
- * location it is typed at.
215
- */
216
- enumParents?: ReadonlyMap<object, { readonly schema: SchemaObject; readonly path: string }>;
217
- /**
218
- * @internal With the `discriminator-values` fix: for each schema, the properties of the
219
- * discriminators it inherits besides `objects[ref]`, which it redefines too.
220
- */
221
- otherProperties?: Readonly<Record<string, readonly string[]>>;
222
214
  };
223
215
  emptyObjectsUnknown: boolean;
224
216
  enum: boolean;
@@ -235,24 +227,12 @@ export interface GlobalContext {
235
227
  * callbacks may add IR declarations or TypeScript nodes.
236
228
  */
237
229
  injectFooter: FooterEntry[];
230
+ pathParamsAsTypes: boolean;
238
231
  /**
239
- * @internal Footer state of this run (enum registry and enum naming), created on first use by
240
- * the transform context adapter. Not an option; do not set it.
241
- */
242
- runFooter?: DocumentFooter;
243
- /**
244
- * @internal The deprecated callback forms this run has warned about (once each). Not an
245
- * option; do not set it.
246
- */
247
- legacyCallbackWarnings?: Set<LegacyCallbackName>;
248
- /**
249
- * @internal Where this run reports warnings and debug output. Not an option; a context without
250
- * it logs to the terminal.
232
+ * Whether the callbacks below take IR (`OpenAPITSIrOptions`) or TypeScript nodes
233
+ * (default: false, TypeScript nodes).
251
234
  */
252
- logger?: Logger;
253
- pathParamsAsTypes: boolean;
254
- /** Whether the callbacks below take IR (`OpenAPITSIrOptions`) or TypeScript nodes. */
255
- irCallbacks: boolean;
235
+ irCallbacks?: boolean;
256
236
  postTransform: GeneratorOptions["postTransform"];
257
237
  propertiesRequiredByDefault: boolean;
258
238
  rootTypes: boolean;
@@ -7,6 +7,7 @@
7
7
  // for ways schemas _can_ be written, not necessarily how they _should_ be.
8
8
 
9
9
  export interface Extensable {
10
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
10
11
  [key: `x-${string}`]: any;
11
12
  }
12
13
 
@@ -67,6 +67,7 @@ export interface ParameterObject extends Extensable {
67
67
  /** The schema defining the type used for the parameter. */
68
68
  schema?: SchemaObject;
69
69
  /** Example of the parameter’s potential value. */
70
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
70
71
  example?: any;
71
72
  /** Examples of the parameter’s potential value. */
72
73
  examples?: { [name: string]: ExampleObject | ReferenceObject };
@@ -111,6 +112,7 @@ export interface MediaTypeObject extends Extensable {
111
112
  * referencing a schema which contains an example, the example value SHALL override the example
112
113
  * provided by the schema.
113
114
  */
115
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
114
116
  example?: any;
115
117
  /**
116
118
  * Examples of the media type. Each example object SHOULD match the media type and specified
@@ -188,6 +190,7 @@ export interface ExampleObject extends Extensable {
188
190
  * represent examples of media types that cannot naturally represented in JSON or YAML, use a
189
191
  * string value to contain the example, escaping where necessary.
190
192
  */
193
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
191
194
  value?: any;
192
195
  /**
193
196
  * A URI that points to the literal example. This provides the capability to reference examples
@@ -21,6 +21,7 @@ export type SchemaObject = {
21
21
  /** Additional external documentation for this schema. */
22
22
  externalDocs?: ExternalDocumentationObject;
23
23
  /** @deprecated */
24
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
24
25
  example?: any;
25
26
  title?: string;
26
27
  description?: string;
@@ -47,6 +48,7 @@ export type SchemaObject = {
47
48
  allOf?: (SchemaObject | ReferenceObject)[];
48
49
  anyOf?: (SchemaObject | ReferenceObject)[];
49
50
  required?: string[];
51
+ // biome-ignore lint/suspicious/noExplicitAny: public type under semver (ADR-034)
50
52
  [key: `x-${string}`]: any;
51
53
  } & (
52
54
  | StringSubtype