@openpkg-ts/spec 0.1.0 → 0.2.1

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/dist/index.js CHANGED
@@ -1,14 +1,252 @@
1
1
  // src/constants.ts
2
- var SCHEMA_VERSION = "0.1.0";
3
- var SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.1.0/openpkg.schema.json";
2
+ var SCHEMA_VERSION = "0.2.0";
3
+ var SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.2.0/openpkg.schema.json";
4
4
  var JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
5
+ // src/deref.ts
6
+ function dereference(spec) {
7
+ const clone = JSON.parse(JSON.stringify(spec));
8
+ const typeLookup = buildTypeLookup(clone.types);
9
+ const visit = (value, seen) => {
10
+ if (Array.isArray(value)) {
11
+ return value.map((item) => visit(item, new Set(seen)));
12
+ }
13
+ if (value && typeof value === "object") {
14
+ const record = value;
15
+ const ref = readTypeRef(record);
16
+ if (ref) {
17
+ return resolveTypeRef(ref, typeLookup, seen);
18
+ }
19
+ const next = {};
20
+ for (const [key, nested] of Object.entries(record)) {
21
+ next[key] = visit(nested, seen);
22
+ }
23
+ return next;
24
+ }
25
+ return value;
26
+ };
27
+ clone.exports = clone.exports.map((item) => visit(item, new Set));
28
+ if (clone.types) {
29
+ clone.types = clone.types.map((item) => visit(item, new Set));
30
+ }
31
+ return clone;
32
+ }
33
+ function buildTypeLookup(types) {
34
+ const map = new Map;
35
+ if (!Array.isArray(types)) {
36
+ return map;
37
+ }
38
+ for (const type of types) {
39
+ if (type && typeof type.id === "string") {
40
+ map.set(type.id, type);
41
+ }
42
+ }
43
+ return map;
44
+ }
45
+ function readTypeRef(value) {
46
+ const ref = value.$ref;
47
+ if (typeof ref !== "string") {
48
+ return null;
49
+ }
50
+ const prefix = "#/types/";
51
+ if (!ref.startsWith(prefix)) {
52
+ return null;
53
+ }
54
+ return ref.slice(prefix.length);
55
+ }
56
+ function resolveTypeRef(id, lookup, seen) {
57
+ if (seen.has(id)) {
58
+ return { $ref: `#/types/${id}` };
59
+ }
60
+ const target = lookup.get(id);
61
+ if (!target) {
62
+ return { $ref: `#/types/${id}` };
63
+ }
64
+ seen.add(id);
65
+ if (target.schema) {
66
+ return JSON.parse(JSON.stringify(target.schema));
67
+ }
68
+ return JSON.parse(JSON.stringify(target));
69
+ }
70
+ // src/diff.ts
71
+ function diffSpec(oldSpec, newSpec) {
72
+ const result = {
73
+ breaking: [],
74
+ nonBreaking: [],
75
+ docsOnly: [],
76
+ coverageDelta: 0,
77
+ oldCoverage: 0,
78
+ newCoverage: 0,
79
+ newUndocumented: [],
80
+ improvedExports: [],
81
+ regressedExports: [],
82
+ driftIntroduced: 0,
83
+ driftResolved: 0
84
+ };
85
+ diffCollections(result, oldSpec.exports, newSpec.exports);
86
+ diffCollections(result, oldSpec.types ?? [], newSpec.types ?? []);
87
+ result.oldCoverage = oldSpec.docs?.coverageScore ?? 0;
88
+ result.newCoverage = newSpec.docs?.coverageScore ?? 0;
89
+ result.coverageDelta = Math.round((result.newCoverage - result.oldCoverage) * 10) / 10;
90
+ const oldExportMap = toExportMap(oldSpec.exports);
91
+ const newExportMap = toExportMap(newSpec.exports);
92
+ for (const [id, newExport] of newExportMap.entries()) {
93
+ const oldExport = oldExportMap.get(id);
94
+ const newScore = newExport.docs?.coverageScore ?? 0;
95
+ const newDriftCount = newExport.docs?.drift?.length ?? 0;
96
+ if (!oldExport) {
97
+ if (newScore < 100 || (newExport.docs?.missing?.length ?? 0) > 0) {
98
+ result.newUndocumented.push(id);
99
+ }
100
+ result.driftIntroduced += newDriftCount;
101
+ continue;
102
+ }
103
+ const oldScore = oldExport.docs?.coverageScore ?? 0;
104
+ const oldDriftCount = oldExport.docs?.drift?.length ?? 0;
105
+ if (newScore > oldScore) {
106
+ result.improvedExports.push(id);
107
+ } else if (newScore < oldScore) {
108
+ result.regressedExports.push(id);
109
+ }
110
+ if (newDriftCount > oldDriftCount) {
111
+ result.driftIntroduced += newDriftCount - oldDriftCount;
112
+ } else if (oldDriftCount > newDriftCount) {
113
+ result.driftResolved += oldDriftCount - newDriftCount;
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ function toExportMap(exports) {
119
+ const map = new Map;
120
+ for (const exp of exports) {
121
+ if (exp && typeof exp.id === "string") {
122
+ map.set(exp.id, exp);
123
+ }
124
+ }
125
+ return map;
126
+ }
127
+ function diffCollections(result, oldItems, newItems) {
128
+ const oldMap = toMap(oldItems);
129
+ const newMap = toMap(newItems);
130
+ for (const [id, oldItem] of oldMap.entries()) {
131
+ const newItem = newMap.get(id);
132
+ if (!newItem) {
133
+ result.breaking.push(id);
134
+ continue;
135
+ }
136
+ const docOnly = isDocOnlyChange(oldItem, newItem);
137
+ const identical = isDeepEqual(oldItem, newItem);
138
+ if (identical) {
139
+ continue;
140
+ }
141
+ if (docOnly) {
142
+ result.docsOnly.push(id);
143
+ continue;
144
+ }
145
+ result.breaking.push(id);
146
+ }
147
+ for (const id of newMap.keys()) {
148
+ if (!oldMap.has(id)) {
149
+ result.nonBreaking.push(id);
150
+ }
151
+ }
152
+ }
153
+ function toMap(items) {
154
+ const map = new Map;
155
+ for (const item of items) {
156
+ if (item && typeof item.id === "string") {
157
+ map.set(item.id, item);
158
+ }
159
+ }
160
+ return map;
161
+ }
162
+ var DOC_KEYS = new Set(["description", "examples", "tags", "source", "rawComments"]);
163
+ function isDocOnlyChange(a, b) {
164
+ const structuralA = normalizeForComparison(removeDocFields(a));
165
+ const structuralB = normalizeForComparison(removeDocFields(b));
166
+ if (structuralA !== structuralB) {
167
+ return false;
168
+ }
169
+ const fullA = normalizeForComparison(a);
170
+ const fullB = normalizeForComparison(b);
171
+ return fullA !== fullB;
172
+ }
173
+ function isDeepEqual(a, b) {
174
+ return normalizeForComparison(a) === normalizeForComparison(b);
175
+ }
176
+ function removeDocFields(value) {
177
+ if (Array.isArray(value)) {
178
+ return value.map((item) => removeDocFields(item));
179
+ }
180
+ if (!value || typeof value !== "object") {
181
+ return value;
182
+ }
183
+ const entries = Object.entries(value).filter(([key]) => !DOC_KEYS.has(key));
184
+ const cleaned = {};
185
+ for (const [key, val] of entries) {
186
+ cleaned[key] = removeDocFields(val);
187
+ }
188
+ return cleaned;
189
+ }
190
+ function normalizeForComparison(value) {
191
+ return JSON.stringify(sortKeys(value));
192
+ }
193
+ function sortKeys(value) {
194
+ if (Array.isArray(value)) {
195
+ return value.map((item) => sortKeys(item));
196
+ }
197
+ if (!value || typeof value !== "object") {
198
+ return value;
199
+ }
200
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
201
+ const result = {};
202
+ for (const [key, val] of entries) {
203
+ result[key] = sortKeys(val);
204
+ }
205
+ return result;
206
+ }
207
+ // src/normalize.ts
208
+ var DEFAULT_ECOSYSTEM = "js/ts";
209
+ var arrayFieldsByExport = ["signatures", "members", "examples", "tags"];
210
+ var arrayFieldsByType = ["members", "tags"];
211
+ function normalize(spec) {
212
+ const normalized = JSON.parse(JSON.stringify(spec));
213
+ normalized.meta = {
214
+ ecosystem: normalized.meta?.ecosystem ?? DEFAULT_ECOSYSTEM,
215
+ ...normalized.meta
216
+ };
217
+ normalized.exports = Array.isArray(normalized.exports) ? [...normalized.exports] : [];
218
+ normalized.exports.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
219
+ normalized.exports = normalized.exports.map((item) => normalizeExport(item));
220
+ const types = Array.isArray(normalized.types) ? [...normalized.types] : [];
221
+ types.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
222
+ normalized.types = types.map((item) => normalizeType(item));
223
+ return normalized;
224
+ }
225
+ function normalizeExport(item) {
226
+ const clone = JSON.parse(JSON.stringify(item));
227
+ for (const field of arrayFieldsByExport) {
228
+ if (!Array.isArray(clone[field])) {
229
+ clone[field] = [];
230
+ }
231
+ }
232
+ return clone;
233
+ }
234
+ function normalizeType(item) {
235
+ const clone = JSON.parse(JSON.stringify(item));
236
+ for (const field of arrayFieldsByType) {
237
+ if (!Array.isArray(clone[field])) {
238
+ clone[field] = [];
239
+ }
240
+ }
241
+ return clone;
242
+ }
5
243
  // src/validate.ts
6
244
  import Ajv from "ajv/dist/2020.js";
7
245
  import addFormats from "ajv-formats";
8
- // schemas/v0.1.0/openpkg.schema.json
246
+ // schemas/v0.2.0/openpkg.schema.json
9
247
  var openpkg_schema_default = {
10
248
  $schema: "https://json-schema.org/draft/2020-12/schema",
11
- $id: "https://unpkg.com/@openpkg-ts/spec/schemas/v0.1.0/openpkg.schema.json",
249
+ $id: "https://unpkg.com/@openpkg-ts/spec/schemas/v0.2.0/openpkg.schema.json",
12
250
  title: "OpenPkg Specification",
13
251
  description: "Schema for OpenPkg specification files",
14
252
  type: "object",
@@ -27,7 +265,7 @@ var openpkg_schema_default = {
27
265
  type: "string",
28
266
  description: "OpenPkg specification version",
29
267
  pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$",
30
- const: "0.1.0"
268
+ const: "0.2.0"
31
269
  },
32
270
  meta: {
33
271
  type: "object",
@@ -58,14 +296,7 @@ var openpkg_schema_default = {
58
296
  },
59
297
  ecosystem: {
60
298
  type: "string",
61
- description: "Package ecosystem",
62
- enum: [
63
- "js/ts",
64
- "python",
65
- "rust",
66
- "go",
67
- "java"
68
- ]
299
+ description: "Package ecosystem"
69
300
  }
70
301
  }
71
302
  },
@@ -82,9 +313,73 @@ var openpkg_schema_default = {
82
313
  items: {
83
314
  $ref: "#/$defs/typeDef"
84
315
  }
316
+ },
317
+ docs: {
318
+ $ref: "#/$defs/docsMetadata",
319
+ description: "Aggregate documentation coverage metadata"
85
320
  }
86
321
  },
87
322
  $defs: {
323
+ docSignal: {
324
+ type: "string",
325
+ enum: [
326
+ "description",
327
+ "params",
328
+ "returns",
329
+ "examples"
330
+ ]
331
+ },
332
+ docDrift: {
333
+ type: "object",
334
+ required: ["type", "issue"],
335
+ properties: {
336
+ type: {
337
+ type: "string",
338
+ enum: ["param-mismatch", "param-type-mismatch", "return-type-mismatch", "generic-constraint-mismatch", "optionality-mismatch", "deprecated-mismatch", "visibility-mismatch", "example-drift", "broken-link"]
339
+ },
340
+ target: {
341
+ type: "string",
342
+ description: "Relevant identifier (e.g., parameter name)"
343
+ },
344
+ issue: {
345
+ type: "string",
346
+ description: "Human-friendly drift explanation"
347
+ },
348
+ suggestion: {
349
+ type: "string",
350
+ description: "Optional remediation hint"
351
+ }
352
+ },
353
+ additionalProperties: false
354
+ },
355
+ docsMetadata: {
356
+ type: "object",
357
+ description: "Documentation coverage metadata",
358
+ additionalProperties: false,
359
+ properties: {
360
+ coverageScore: {
361
+ type: "number",
362
+ minimum: 0,
363
+ maximum: 100,
364
+ description: "Documentation coverage value from 0-100."
365
+ },
366
+ missing: {
367
+ type: "array",
368
+ description: "Doc components missing for this entity",
369
+ items: {
370
+ $ref: "#/$defs/docSignal"
371
+ },
372
+ uniqueItems: true
373
+ },
374
+ drift: {
375
+ type: "array",
376
+ description: "Detected documentation drift signals",
377
+ items: {
378
+ $ref: "#/$defs/docDrift"
379
+ }
380
+ }
381
+ }
382
+ },
88
383
  export: {
89
384
  type: "object",
90
385
  required: [
@@ -101,6 +396,22 @@ var openpkg_schema_default = {
101
396
  type: "string",
102
397
  description: "Export name"
103
398
  },
399
+ slug: {
400
+ type: "string",
401
+ description: "Stable slug for linking"
402
+ },
403
+ displayName: {
404
+ type: "string",
405
+ description: "UI-friendly label"
406
+ },
407
+ category: {
408
+ type: "string",
409
+ description: "Grouping hint for navigation"
410
+ },
411
+ importPath: {
412
+ type: "string",
413
+ description: "Recommended import path"
414
+ },
104
415
  kind: {
105
416
  type: "string",
106
417
  description: "Kind of export",
@@ -131,15 +442,37 @@ var openpkg_schema_default = {
131
442
  $ref: "#/$defs/signature"
132
443
  }
133
444
  },
134
- properties: {
445
+ type: {
446
+ description: "Type reference or inline schema for variables",
447
+ oneOf: [
448
+ { type: "string" },
449
+ { $ref: "#/$defs/schema" }
450
+ ]
451
+ },
452
+ members: {
135
453
  type: "array",
136
- description: "Class/interface properties",
454
+ description: "Class/interface/enum members",
455
+ items: { type: "object" }
456
+ },
457
+ tags: {
458
+ type: "array",
459
+ description: "JSDoc/TSDoc tags",
137
460
  items: {
138
- $ref: "#/$defs/property"
461
+ type: "object",
462
+ required: ["name", "text"],
463
+ properties: {
464
+ name: { type: "string" },
465
+ text: { type: "string" }
466
+ },
467
+ additionalProperties: false
139
468
  }
140
469
  },
141
470
  source: {
142
471
  $ref: "#/$defs/sourceLocation"
472
+ },
473
+ docs: {
474
+ $ref: "#/$defs/docsMetadata",
475
+ description: "Documentation coverage metadata for this export"
143
476
  }
144
477
  }
145
478
  },
@@ -159,6 +492,22 @@ var openpkg_schema_default = {
159
492
  type: "string",
160
493
  description: "Type name"
161
494
  },
495
+ slug: {
496
+ type: "string",
497
+ description: "Stable slug for linking"
498
+ },
499
+ displayName: {
500
+ type: "string",
501
+ description: "UI-friendly label"
502
+ },
503
+ category: {
504
+ type: "string",
505
+ description: "Grouping hint for navigation"
506
+ },
507
+ importPath: {
508
+ type: "string",
509
+ description: "Recommended import path"
510
+ },
162
511
  kind: {
163
512
  type: "string",
164
513
  description: "Kind of type definition",
@@ -180,11 +529,22 @@ var openpkg_schema_default = {
180
529
  type: "string",
181
530
  description: "Type expression for type aliases"
182
531
  },
183
- properties: {
532
+ members: {
184
533
  type: "array",
185
- description: "Properties for interfaces/classes",
534
+ description: "Members for classes/interfaces/enums",
535
+ items: { type: "object" }
536
+ },
537
+ tags: {
538
+ type: "array",
539
+ description: "JSDoc/TSDoc tags",
186
540
  items: {
187
- $ref: "#/$defs/property"
541
+ type: "object",
542
+ required: ["name", "text"],
543
+ properties: {
544
+ name: { type: "string" },
545
+ text: { type: "string" }
546
+ },
547
+ additionalProperties: false
188
548
  }
189
549
  },
190
550
  source: {
@@ -225,12 +585,12 @@ var openpkg_schema_default = {
225
585
  type: "boolean",
226
586
  description: "Whether the parameter is required"
227
587
  },
588
+ schema: {
589
+ $ref: "#/$defs/schema"
590
+ },
228
591
  description: {
229
592
  type: "string",
230
593
  description: "Parameter description"
231
- },
232
- schema: {
233
- $ref: "#/$defs/schema"
234
594
  }
235
595
  }
236
596
  },
@@ -246,30 +606,6 @@ var openpkg_schema_default = {
246
606
  }
247
607
  }
248
608
  },
249
- property: {
250
- type: "object",
251
- required: [
252
- "name",
253
- "required"
254
- ],
255
- properties: {
256
- name: {
257
- type: "string",
258
- description: "Property name"
259
- },
260
- required: {
261
- type: "boolean",
262
- description: "Whether the property is required"
263
- },
264
- description: {
265
- type: "string",
266
- description: "Property description"
267
- },
268
- schema: {
269
- $ref: "#/$defs/schema"
270
- }
271
- }
272
- },
273
609
  schema: {
274
610
  anyOf: [
275
611
  {
@@ -358,204 +694,9 @@ function getValidationErrors(spec) {
358
694
  const result = validateSpec(spec);
359
695
  return result.ok ? [] : result.errors;
360
696
  }
361
- // src/normalize.ts
362
- var DEFAULT_ECOSYSTEM = "js/ts";
363
- var arrayFieldsByExport = ["signatures", "members", "examples", "tags"];
364
- var arrayFieldsByType = ["members", "tags"];
365
- function normalize(spec) {
366
- const normalized = JSON.parse(JSON.stringify(spec));
367
- normalized.meta = {
368
- ecosystem: normalized.meta?.ecosystem ?? DEFAULT_ECOSYSTEM,
369
- ...normalized.meta
370
- };
371
- normalized.exports = Array.isArray(normalized.exports) ? [...normalized.exports] : [];
372
- normalized.exports.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
373
- normalized.exports = normalized.exports.map((item) => normalizeExport(item));
374
- const types = Array.isArray(normalized.types) ? [...normalized.types] : [];
375
- types.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
376
- normalized.types = types.map((item) => normalizeType(item));
377
- normalized.examples = normalized.examples ?? [];
378
- normalized.extensions = normalized.extensions ?? {};
379
- return normalized;
380
- }
381
- function normalizeExport(item) {
382
- const clone = JSON.parse(JSON.stringify(item));
383
- for (const field of arrayFieldsByExport) {
384
- if (!Array.isArray(clone[field])) {
385
- clone[field] = [];
386
- }
387
- }
388
- return clone;
389
- }
390
- function normalizeType(item) {
391
- const clone = JSON.parse(JSON.stringify(item));
392
- for (const field of arrayFieldsByType) {
393
- if (!Array.isArray(clone[field])) {
394
- clone[field] = [];
395
- }
396
- }
397
- return clone;
398
- }
399
- // src/deref.ts
400
- function dereference(spec) {
401
- const clone = JSON.parse(JSON.stringify(spec));
402
- const typeLookup = buildTypeLookup(clone.types);
403
- const visit = (value, seen) => {
404
- if (Array.isArray(value)) {
405
- return value.map((item) => visit(item, new Set(seen)));
406
- }
407
- if (value && typeof value === "object") {
408
- const record = value;
409
- const ref = readTypeRef(record);
410
- if (ref) {
411
- return resolveTypeRef(ref, typeLookup, seen);
412
- }
413
- const next = {};
414
- for (const [key, nested] of Object.entries(record)) {
415
- next[key] = visit(nested, seen);
416
- }
417
- return next;
418
- }
419
- return value;
420
- };
421
- clone.exports = clone.exports.map((item) => visit(item, new Set));
422
- if (clone.types) {
423
- clone.types = clone.types.map((item) => visit(item, new Set));
424
- }
425
- return clone;
426
- }
427
- function buildTypeLookup(types) {
428
- const map = new Map;
429
- if (!Array.isArray(types)) {
430
- return map;
431
- }
432
- for (const type of types) {
433
- if (type && typeof type.id === "string") {
434
- map.set(type.id, type);
435
- }
436
- }
437
- return map;
438
- }
439
- function readTypeRef(value) {
440
- const ref = value["$ref"];
441
- if (typeof ref !== "string") {
442
- return null;
443
- }
444
- const prefix = "#/types/";
445
- if (!ref.startsWith(prefix)) {
446
- return null;
447
- }
448
- return ref.slice(prefix.length);
449
- }
450
- function resolveTypeRef(id, lookup, seen) {
451
- if (seen.has(id)) {
452
- return { $ref: `#/types/${id}` };
453
- }
454
- const target = lookup.get(id);
455
- if (!target) {
456
- return { $ref: `#/types/${id}` };
457
- }
458
- seen.add(id);
459
- if (target.schema) {
460
- return JSON.parse(JSON.stringify(target.schema));
461
- }
462
- return JSON.parse(JSON.stringify(target));
463
- }
464
- // src/migrate/v0_1_0__to__0_2_0.ts
465
- function migrate_0_1_0__to__0_2_0(spec) {
466
- return spec;
467
- }
468
- // src/diff.ts
469
- function diffSpec(a, b) {
470
- const result = { breaking: [], nonBreaking: [], docsOnly: [] };
471
- diffCollections(result, a.exports, b.exports);
472
- diffCollections(result, a.types ?? [], b.types ?? []);
473
- return result;
474
- }
475
- function diffCollections(result, oldItems, newItems) {
476
- const oldMap = toMap(oldItems);
477
- const newMap = toMap(newItems);
478
- for (const [id, oldItem] of oldMap.entries()) {
479
- const newItem = newMap.get(id);
480
- if (!newItem) {
481
- result.breaking.push(id);
482
- continue;
483
- }
484
- const docOnly = isDocOnlyChange(oldItem, newItem);
485
- const identical = isDeepEqual(oldItem, newItem);
486
- if (identical) {
487
- continue;
488
- }
489
- if (docOnly) {
490
- result.docsOnly.push(id);
491
- continue;
492
- }
493
- result.breaking.push(id);
494
- }
495
- for (const id of newMap.keys()) {
496
- if (!oldMap.has(id)) {
497
- result.nonBreaking.push(id);
498
- }
499
- }
500
- }
501
- function toMap(items) {
502
- const map = new Map;
503
- for (const item of items) {
504
- if (item && typeof item.id === "string") {
505
- map.set(item.id, item);
506
- }
507
- }
508
- return map;
509
- }
510
- var DOC_KEYS = new Set(["description", "examples", "tags", "source", "rawComments"]);
511
- function isDocOnlyChange(a, b) {
512
- const structuralA = normalizeForComparison(removeDocFields(a));
513
- const structuralB = normalizeForComparison(removeDocFields(b));
514
- if (structuralA !== structuralB) {
515
- return false;
516
- }
517
- const fullA = normalizeForComparison(a);
518
- const fullB = normalizeForComparison(b);
519
- return fullA !== fullB;
520
- }
521
- function isDeepEqual(a, b) {
522
- return normalizeForComparison(a) === normalizeForComparison(b);
523
- }
524
- function removeDocFields(value) {
525
- if (Array.isArray(value)) {
526
- return value.map((item) => removeDocFields(item));
527
- }
528
- if (!value || typeof value !== "object") {
529
- return value;
530
- }
531
- const entries = Object.entries(value).filter(([key]) => !DOC_KEYS.has(key));
532
- const cleaned = {};
533
- for (const [key, val] of entries) {
534
- cleaned[key] = removeDocFields(val);
535
- }
536
- return cleaned;
537
- }
538
- function normalizeForComparison(value) {
539
- return JSON.stringify(sortKeys(value));
540
- }
541
- function sortKeys(value) {
542
- if (Array.isArray(value)) {
543
- return value.map((item) => sortKeys(item));
544
- }
545
- if (!value || typeof value !== "object") {
546
- return value;
547
- }
548
- const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
549
- const result = {};
550
- for (const [key, val] of entries) {
551
- result[key] = sortKeys(val);
552
- }
553
- return result;
554
- }
555
697
  export {
556
698
  validateSpec,
557
699
  normalize,
558
- migrate_0_1_0__to__0_2_0 as migrate,
559
700
  getValidationErrors,
560
701
  diffSpec,
561
702
  dereference,