schemastore-to-typescript 0.2.12

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/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "schemastore-to-typescript",
3
+ "version": "0.2.12",
4
+ "description": "Fetch JSON schemas from the JSON Schema Store catalog and compile them to TypeScript typings",
5
+ "keywords": [
6
+ "cli",
7
+ "json-schema",
8
+ "schemastore",
9
+ "typescript"
10
+ ],
11
+ "homepage": "https://github.com/langri-sha/schemastore-to-typescript#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/langri-sha/schemastore-to-typescript/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/langri-sha/schemastore-to-typescript.git"
18
+ },
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "Filip Dupanović",
22
+ "email": "filip.dupanovic@gmail.com",
23
+ "url": "https://langri-sha.com",
24
+ "organization": false
25
+ },
26
+ "type": "module",
27
+ "main": "dist/index.js",
28
+ "bin": {
29
+ "schemastore-to-typescript": "dist/cli.js"
30
+ },
31
+ "scripts": {
32
+ "change": "pnpm exec projen change",
33
+ "default": "pnpm exec projen default",
34
+ "prepare": "husky",
35
+ "prepublishOnly": "rm -rf dist; tsc --project tsconfig.build.json",
36
+ "renovate": "pnpm exec projen renovate",
37
+ "test": "vitest"
38
+ },
39
+ "dependencies": {
40
+ "commander": "15.0.0",
41
+ "debug": "4.4.3",
42
+ "env-paths": "4.0.0",
43
+ "es-main": "1.4.0",
44
+ "got": "16.0.0",
45
+ "json-schema-to-typescript": "16.0.0",
46
+ "keyv": "5.6.0",
47
+ "keyv-file": "5.3.5"
48
+ },
49
+ "devDependencies": {
50
+ "@langri-sha/eslint-config": "0.9.15",
51
+ "@langri-sha/lint-staged": "0.9.7",
52
+ "@langri-sha/prettier": "0.4.7",
53
+ "@langri-sha/projen-project": "0.31.0",
54
+ "@langri-sha/tsconfig": "1.0.1",
55
+ "@langri-sha/vitest": "0.2.0",
56
+ "@types/debug": "4.1.13",
57
+ "@types/node": "24.13.6",
58
+ "beachball": "2.66.0",
59
+ "eslint": "10.11.0",
60
+ "husky": "9.1.7",
61
+ "lint-staged": "17.5.1",
62
+ "prettier": "3.9.8",
63
+ "projen": "0.86.5",
64
+ "tsx": "4.23.13",
65
+ "typescript": "5.9.3",
66
+ "vitest": "5.0.1"
67
+ },
68
+ "packageManager": "pnpm@12.5.1",
69
+ "devEngines": {
70
+ "runtime": {
71
+ "name": "node",
72
+ "version": ">= 24.16.0"
73
+ }
74
+ },
75
+ "publishConfig": {
76
+ "access": "public"
77
+ },
78
+ "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"pnpm exec projen\".",
79
+ "types": "dist/index.d.ts"
80
+ }
package/readme.md ADDED
@@ -0,0 +1,52 @@
1
+ # schemastore-to-typescript
2
+
3
+ Fetch [JSON Schema] from the [JSON Schema Store] catalog and compile [TypeScript
4
+ typings].
5
+
6
+ The tool first fetches the schema catalog from the JSON Schema Store API,
7
+ searches for the requested schema by name (case-insensitive), and then downloads
8
+ and compiles the schema to TypeScript definitions.
9
+
10
+ ## Features
11
+
12
+ - uses [`json-schema-to-typescript`] for TypeScript generation
13
+ - fetches schemas from the [JSON Schema Store catalog API]
14
+ - case-insensitive schema name matching
15
+ - offline cache using [`got`] for both catalog and schema requests
16
+
17
+ ## Usage
18
+
19
+ ```sh
20
+ npm install -D schemastore-to-typescript
21
+ ```
22
+
23
+ Name a schema as it appears in the catalog, and optionally where to write its
24
+ typings — `<schema>.d.ts` in the working directory by default:
25
+
26
+ ```sh
27
+ schemastore-to-typescript renovate src/renovate.ts
28
+ schemastore-to-typescript 'cargo manifest' src/cargo.ts
29
+ ```
30
+
31
+ Requests are cached in the user cache directory, such as
32
+ `~/.cache/schemastore-to-typescript-nodejs` on Linux. Pass `--no-cache` to skip
33
+ it.
34
+
35
+ The same is available as a function, which resolves to the compiled module and
36
+ takes `false` as its second argument to skip the cache:
37
+
38
+ ```ts
39
+ import { compile } from 'schemastore-to-typescript'
40
+
41
+ const typings = await compile('swcrc')
42
+ ```
43
+
44
+ [`got`]: https://www.npmjs.com/package/got
45
+ [`json-schema-to-typescript`]:
46
+ https://www.npmjs.com/package/json-schema-to-typescript
47
+ [json schema store]: https://www.schemastore.org/
48
+ [json schema store catalog api]:
49
+ https://www.schemastore.org/api/json/catalog.json
50
+ [json schema]: https://json-schema.org/
51
+ [typescript typings]:
52
+ https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html
package/src/cli.ts ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'node:fs/promises'
3
+ import * as path from 'node:path'
4
+
5
+ import { Command } from 'commander'
6
+ import createDebug from 'debug'
7
+ import esMain from 'es-main'
8
+
9
+ import { compile } from './index.js'
10
+
11
+ const debug = createDebug('schema-store-to-typescript')
12
+
13
+ export const program: Command = new Command()
14
+ .name('schemastore-to-typescript')
15
+ .description('Compile a JSON schema store schema to TypeScript definitions')
16
+ .argument('<schema>', 'JSON schema store schema')
17
+ .argument('[output]', 'Path to output the TypeScript definition')
18
+ .option('-n, --no-cache', 'Skip caching requests')
19
+ .showHelpAfterError()
20
+ .action(async (schema, output, { cache }) => {
21
+ output ??= `${schema}.d.ts`
22
+
23
+ const compiled = await compile(schema, cache)
24
+ await fs.writeFile(
25
+ // Using `process.cwd()` as escape hatch for `process.chdir()` not being
26
+ // available in workers. See: https://github.com/vitest-dev/vitest/issues/1436.
27
+ path.resolve(process.cwd(), output),
28
+ compiled,
29
+ )
30
+ })
31
+
32
+ if (esMain(import.meta)) {
33
+ debug('Parsing arguments', process.argv)
34
+ program
35
+ .parseAsync(process.argv)
36
+ .then(() => {
37
+ process.exit(0)
38
+ })
39
+ .catch((error) => {
40
+ console.error(error)
41
+ process.exit(1)
42
+ })
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,256 @@
1
+ import * as path from 'node:path'
2
+
3
+ import createDebug from 'debug'
4
+ import envPaths from 'env-paths'
5
+ import got, { HTTPError } from 'got'
6
+ import {
7
+ type JSONSchema,
8
+ compile as compileSource,
9
+ } from 'json-schema-to-typescript'
10
+ import Keyv from 'keyv'
11
+ import { KeyvFile } from 'keyv-file'
12
+
13
+ import { normalizeSchema } from './normalize.js'
14
+
15
+ const debug = createDebug('schema-store-to-typescript')
16
+ const paths = envPaths('schemastore-to-typescript')
17
+
18
+ const store = new KeyvFile({
19
+ filename: path.join(paths.cache, 'requests.json'),
20
+ writeDelay: 0,
21
+ })
22
+
23
+ const keyv = new Keyv({ store })
24
+
25
+ // Both are declared private in keyv-file's typings, but flushing the cache
26
+ // needs them — see `flushCache`.
27
+ const flushable = store as unknown as {
28
+ save(): Promise<unknown>
29
+ saveToDisk(): Promise<unknown>
30
+ }
31
+
32
+ /**
33
+ * `keyv-file@5.3.5` coalesces a `set` onto an in-flight save: `saveToDisk`
34
+ * snapshots the store synchronously at entry, then awaits `mkdir` and
35
+ * `writeFile`, and only clears the in-flight promise afterwards. A `set`
36
+ * landing in that window gets that promise back — it resolves, but the value
37
+ * was never in the snapshot and no further save is scheduled, so it stays in
38
+ * memory for the rest of the process.
39
+ *
40
+ * `compile` writes the catalog and then the schema straight into that window,
41
+ * which loses the schema. `writeDelay` is no help: the window is the write
42
+ * itself, not the timer.
43
+ *
44
+ * So settle whatever is in flight first — its `writeFile` would otherwise race
45
+ * ours and could land last with the older snapshot — and then write the
46
+ * current one unconditionally.
47
+ */
48
+ const flushCache = async () => {
49
+ await flushable.save()
50
+ await flushable.saveToDisk()
51
+ }
52
+
53
+ interface CatalogSchema {
54
+ name: string
55
+ description: string
56
+ fileMatch?: string[]
57
+ url: string
58
+ }
59
+
60
+ interface Catalog {
61
+ $schema: string
62
+ version: number
63
+ schemas: CatalogSchema[]
64
+ }
65
+
66
+ // Holds literal instance data rather than nested schemas, so it must not be
67
+ // walked as if it were part of the schema shape.
68
+ const nonSchemaKeys = new Set(['default', 'examples', 'const', 'enum'])
69
+
70
+ /**
71
+ * Some published schemas (e.g. Renovate's `registryAliases`) combine a
72
+ * `$ref` with a concrete `additionalProperties` schema on the same node — a
73
+ * JSON Schema anti-pattern that `json-schema-to-typescript` compiles into an
74
+ * interface whose own properties conflict with its index signature,
75
+ * producing uncompilable TypeScript (TS2411). `additionalProperties` is the
76
+ * more specific, deliberate declaration, so drop the `$ref` in favor of it.
77
+ *
78
+ * A node declaring `additionalProperties` at all compiles to an interface
79
+ * carrying a string index signature, and an optional member cannot sit beside
80
+ * one: its `undefined` is not assignable to the index type, which is TS2411
81
+ * again (Cargo's `[features]`, `[patch]` and `[lints]`). Where the index type
82
+ * is `unknown` the compiler does allow it, but the named members are then
83
+ * advisory — the same index signature accepts them either way, and following
84
+ * one is how Cargo's `package.metadata` reaches `quikrun`, a mutually
85
+ * recursive schema that overflows the compiler's stack and takes the whole
86
+ * manifest down with it.
87
+ *
88
+ * So optional members are dropped in favour of `additionalProperties`, which
89
+ * is the general contract they were there to narrow. Required ones stay: they
90
+ * are a promise the type has to keep.
91
+ */
92
+ const sanitizeSchema = <T>(node: T): T => {
93
+ if (Array.isArray(node)) {
94
+ return node.map(sanitizeSchema) as unknown as T
95
+ }
96
+
97
+ if (node === null || typeof node !== 'object') {
98
+ return node
99
+ }
100
+
101
+ const sanitized: Record<string, unknown> = {}
102
+
103
+ for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
104
+ sanitized[key] = nonSchemaKeys.has(key) ? value : sanitizeSchema(value)
105
+ }
106
+
107
+ if (
108
+ typeof sanitized.$ref === 'string' &&
109
+ typeof sanitized.additionalProperties === 'object' &&
110
+ sanitized.additionalProperties !== null
111
+ ) {
112
+ delete sanitized.$ref
113
+ }
114
+
115
+ if (
116
+ sanitized.additionalProperties !== undefined &&
117
+ sanitized.additionalProperties !== false &&
118
+ typeof sanitized.properties === 'object' &&
119
+ sanitized.properties !== null
120
+ ) {
121
+ const required = new Set(
122
+ Array.isArray(sanitized.required) ? sanitized.required : [],
123
+ )
124
+
125
+ const properties = Object.fromEntries(
126
+ Object.entries(sanitized.properties).filter(([property]) =>
127
+ required.has(property),
128
+ ),
129
+ )
130
+
131
+ if (Object.keys(properties).length > 0) {
132
+ sanitized.properties = properties
133
+ } else {
134
+ delete sanitized.properties
135
+ }
136
+ }
137
+
138
+ return sanitized as T
139
+ }
140
+
141
+ export const compile = async (
142
+ name: string,
143
+ cache: boolean = true,
144
+ ): Promise<string> => {
145
+ const catalogUrl = 'https://www.schemastore.org/api/json/catalog.json'
146
+
147
+ let catalog: Catalog
148
+ let schema: JSONSchema
149
+
150
+ const fetchWithRetry = async <T>(
151
+ url: string,
152
+ retryOnCacheError: boolean = true,
153
+ ): Promise<T> => {
154
+ try {
155
+ return await got(url, {
156
+ cache: cache ? keyv : undefined,
157
+ headers: {
158
+ accept: 'application/json',
159
+ },
160
+ decompress: true,
161
+ }).json<T>()
162
+ } catch (e) {
163
+ debug(`Error fetching ${url}:`, e)
164
+
165
+ if (
166
+ retryOnCacheError &&
167
+ cache &&
168
+ e instanceof Error &&
169
+ (e.message.includes('Unexpected token') ||
170
+ e.message.includes('is not valid JSON'))
171
+ ) {
172
+ debug(`Clearing cache for ${url} and retrying...`)
173
+ try {
174
+ await keyv.delete(`GET:${url}`)
175
+ } catch (clearError) {
176
+ debug(`Failed to clear cache:`, clearError)
177
+ }
178
+
179
+ return fetchWithRetry<T>(url, false)
180
+ }
181
+
182
+ throw e
183
+ }
184
+ }
185
+
186
+ try {
187
+ // First, fetch the catalog
188
+ catalog = await fetchWithRetry<Catalog>(catalogUrl)
189
+ } catch (e) {
190
+ debug(e)
191
+
192
+ if (e instanceof HTTPError) {
193
+ throw new Error(
194
+ `Couldn't retrieve catalog from ${catalogUrl}. [${e.code}]`,
195
+ { cause: e },
196
+ )
197
+ }
198
+
199
+ throw e
200
+ }
201
+
202
+ const schemaMap = new Map<string, CatalogSchema>()
203
+ for (const schema of catalog.schemas) {
204
+ schemaMap.set(schema.name.toLowerCase(), schema)
205
+ }
206
+
207
+ const catalogSchema = schemaMap.get(name.toLowerCase())
208
+
209
+ if (!catalogSchema) {
210
+ throw new Error(
211
+ `Couldn't find schema ${name} in the catalog. Are you sure it exists?`,
212
+ )
213
+ }
214
+
215
+ try {
216
+ // Fetch the actual schema from the found URL
217
+ debug(`Fetching schema from: ${catalogSchema.url}`)
218
+
219
+ schema = await fetchWithRetry<JSONSchema>(catalogSchema.url)
220
+
221
+ if (typeof schema === 'object' && schema !== null) {
222
+ schema.title = catalogSchema.name
223
+ }
224
+ } catch (e) {
225
+ debug(e)
226
+
227
+ if (e instanceof HTTPError) {
228
+ if (e.response.statusCode === 404) {
229
+ throw new Error(
230
+ `Couldn't find schema ${name} at ${catalogSchema.url}. Are you sure it exists?`,
231
+ { cause: e },
232
+ )
233
+ }
234
+
235
+ throw new Error(
236
+ `Couldn't retrieve schema from ${catalogSchema.url}. [${e.code}] Status: ${e.response.statusCode}`,
237
+ { cause: e },
238
+ )
239
+ }
240
+
241
+ if (e instanceof Error && e.message.includes('is not valid JSON')) {
242
+ throw new Error(
243
+ `Failed to parse JSON from ${catalogSchema.url}. The server might be returning invalid or compressed content. Error: ${e.message}`,
244
+ { cause: e },
245
+ )
246
+ }
247
+
248
+ throw e
249
+ }
250
+
251
+ if (cache) {
252
+ await flushCache()
253
+ }
254
+
255
+ return compileSource(sanitizeSchema(normalizeSchema(schema)), name)
256
+ }
@@ -0,0 +1,274 @@
1
+ import type { JSONSchema } from 'json-schema-to-typescript'
2
+
3
+ type Node = Record<string, unknown>
4
+
5
+ // Maps the JSON pointer of a schema node to the `definitions` name each of its
6
+ // `$defs` members moves to.
7
+ type Renames = Map<string, Map<string, string>>
8
+
9
+ const isObject = (value: unknown): value is Node =>
10
+ typeof value === 'object' && value !== null && !Array.isArray(value)
11
+
12
+ // Hold literal instance data rather than nested schemas, so they must not be
13
+ // walked as if they were part of the schema shape.
14
+ const literalKeys = new Set(['const', 'default', 'enum', 'examples'])
15
+
16
+ // Keyed by name rather than by keyword: a member of one of these called
17
+ // `$defs` is a subschema that happens to be named `$defs`, not a definition
18
+ // block.
19
+ const namedSchemaKeys = new Set([
20
+ '$defs',
21
+ 'definitions',
22
+ 'dependencies',
23
+ 'dependentSchemas',
24
+ 'patternProperties',
25
+ 'properties',
26
+ ])
27
+
28
+ const escapeToken = (name: string) =>
29
+ name.replaceAll('~', '~0').replaceAll('/', '~1')
30
+
31
+ const unescapeToken = (token: string) =>
32
+ token.replaceAll('~1', '/').replaceAll('~0', '~')
33
+
34
+ const join = (path: string, token: string) =>
35
+ path === '' ? token : `${path}/${token}`
36
+
37
+ const resolvePointer = (document: Node, ref: string): unknown => {
38
+ if (ref === '#') {
39
+ return document
40
+ }
41
+
42
+ if (!ref.startsWith('#/')) {
43
+ return undefined
44
+ }
45
+
46
+ let node: unknown = document
47
+
48
+ for (const token of ref.slice(2).split('/')) {
49
+ if (Array.isArray(node)) {
50
+ node = node[Number(token)]
51
+ } else if (isObject(node)) {
52
+ node = node[unescapeToken(token)]
53
+ } else {
54
+ return undefined
55
+ }
56
+ }
57
+
58
+ return node
59
+ }
60
+
61
+ const collectRenames = (
62
+ node: unknown,
63
+ path: string,
64
+ inSchema: boolean,
65
+ renames: Renames,
66
+ ): void => {
67
+ if (Array.isArray(node)) {
68
+ for (const [index, item] of node.entries()) {
69
+ collectRenames(item, join(path, String(index)), inSchema, renames)
70
+ }
71
+
72
+ return
73
+ }
74
+
75
+ if (!isObject(node)) {
76
+ return
77
+ }
78
+
79
+ if (!inSchema) {
80
+ for (const [name, value] of Object.entries(node)) {
81
+ collectRenames(value, join(path, escapeToken(name)), true, renames)
82
+ }
83
+
84
+ return
85
+ }
86
+
87
+ if (isObject(node.$defs)) {
88
+ const taken = new Set(
89
+ isObject(node.definitions) ? Object.keys(node.definitions) : [],
90
+ )
91
+ const mapping = new Map<string, string>()
92
+
93
+ for (const name of Object.keys(node.$defs)) {
94
+ let target = name
95
+
96
+ for (let suffix = 2; taken.has(target); suffix++) {
97
+ target = `${name}_${suffix}`
98
+ }
99
+
100
+ taken.add(target)
101
+ mapping.set(name, target)
102
+ }
103
+
104
+ renames.set(path, mapping)
105
+ }
106
+
107
+ for (const [key, value] of Object.entries(node)) {
108
+ if (literalKeys.has(key)) {
109
+ continue
110
+ }
111
+
112
+ collectRenames(
113
+ value,
114
+ join(path, escapeToken(key)),
115
+ !namedSchemaKeys.has(key),
116
+ renames,
117
+ )
118
+ }
119
+ }
120
+
121
+ const rewriteRef = (ref: string, renames: Renames): string => {
122
+ if (!ref.startsWith('#/')) {
123
+ return ref
124
+ }
125
+
126
+ const tokens = ref.slice(2).split('/')
127
+ const rewritten: string[] = []
128
+ let path = ''
129
+
130
+ for (let index = 0; index < tokens.length; index++) {
131
+ const token = tokens[index]
132
+ const name = tokens[index + 1]
133
+ const mapping = token === '$defs' ? renames.get(path) : undefined
134
+ const target =
135
+ mapping && name !== undefined
136
+ ? mapping.get(unescapeToken(name))
137
+ : undefined
138
+
139
+ if (target === undefined) {
140
+ rewritten.push(token)
141
+ path = join(path, token)
142
+ continue
143
+ }
144
+
145
+ rewritten.push('definitions', escapeToken(target))
146
+ path = join(join(path, token), name as string)
147
+ index++
148
+ }
149
+
150
+ return `#/${rewritten.join('/')}`
151
+ }
152
+
153
+ const applyRenames = (
154
+ node: unknown,
155
+ path: string,
156
+ inSchema: boolean,
157
+ renames: Renames,
158
+ ): unknown => {
159
+ if (Array.isArray(node)) {
160
+ return node.map((item, index) =>
161
+ applyRenames(item, join(path, String(index)), inSchema, renames),
162
+ )
163
+ }
164
+
165
+ if (!isObject(node)) {
166
+ return node
167
+ }
168
+
169
+ if (!inSchema) {
170
+ return Object.fromEntries(
171
+ Object.entries(node).map(([name, value]) => [
172
+ name,
173
+ applyRenames(value, join(path, escapeToken(name)), true, renames),
174
+ ]),
175
+ )
176
+ }
177
+
178
+ const mapping = renames.get(path)
179
+ const renamed: Node = {}
180
+
181
+ for (const [key, value] of Object.entries(node)) {
182
+ if (literalKeys.has(key)) {
183
+ renamed[key] = value
184
+ continue
185
+ }
186
+
187
+ if (key === '$ref' && typeof value === 'string') {
188
+ renamed[key] = rewriteRef(value, renames)
189
+ continue
190
+ }
191
+
192
+ if (key === '$defs' && mapping) {
193
+ continue
194
+ }
195
+
196
+ renamed[key] = applyRenames(
197
+ value,
198
+ join(path, escapeToken(key)),
199
+ !namedSchemaKeys.has(key),
200
+ renames,
201
+ )
202
+ }
203
+
204
+ if (mapping) {
205
+ const defs = node.$defs as Node
206
+ const definitions: Node = isObject(renamed.definitions)
207
+ ? renamed.definitions
208
+ : {}
209
+
210
+ for (const [name, target] of mapping) {
211
+ definitions[target] = applyRenames(
212
+ defs[name],
213
+ join(join(path, '$defs'), escapeToken(name)),
214
+ true,
215
+ renames,
216
+ )
217
+ }
218
+
219
+ renamed.definitions = definitions
220
+ }
221
+
222
+ return renamed
223
+ }
224
+
225
+ /**
226
+ * A document whose root is `{$ref}` describes the referenced definition, with
227
+ * any sibling keyword layered on top of it — `title` above all, which is what
228
+ * names the exported type. The definition stays where it is, so pointers into
229
+ * it keep resolving; only a copy is hoisted.
230
+ */
231
+ const inlineRootRef = (document: Node): Node => {
232
+ let root = document
233
+ const seen = new Set<string>()
234
+
235
+ while (typeof root.$ref === 'string' && !seen.has(root.$ref)) {
236
+ const target = resolvePointer(document, root.$ref)
237
+
238
+ if (!isObject(target) || target === root) {
239
+ break
240
+ }
241
+
242
+ seen.add(root.$ref)
243
+
244
+ const siblings = { ...root }
245
+ delete siblings.$ref
246
+
247
+ root = { ...target, ...siblings }
248
+ }
249
+
250
+ return root
251
+ }
252
+
253
+ /**
254
+ * `json-schema-to-typescript` resolves references against draft-07's
255
+ * vocabulary: it only knows `definitions`, and it expects the root to be a
256
+ * schema rather than a reference to one. Draft 2020-12 documents that spell
257
+ * their definitions `$defs` or that point the root at one therefore fail to
258
+ * resolve, with `Refs should have been resolved by the resolver!`.
259
+ *
260
+ * Restate them in the shape it does resolve, without disturbing documents that
261
+ * are already in it.
262
+ */
263
+ export const normalizeSchema = (schema: JSONSchema): JSONSchema => {
264
+ if (!isObject(schema)) {
265
+ return schema
266
+ }
267
+
268
+ const renames: Renames = new Map()
269
+ collectRenames(schema, '', true, renames)
270
+
271
+ return inlineRootRef(
272
+ applyRenames(schema, '', true, renames) as Node,
273
+ ) as JSONSchema
274
+ }