@openpkg-ts/spec 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,22 +1,256 @@
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",
15
- required: [
16
- "openpkg",
17
- "meta",
18
- "exports"
19
- ],
253
+ required: ["openpkg", "meta", "exports"],
20
254
  properties: {
21
255
  $schema: {
22
256
  type: "string",
@@ -27,14 +261,12 @@ var openpkg_schema_default = {
27
261
  type: "string",
28
262
  description: "OpenPkg specification version",
29
263
  pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$",
30
- const: "0.1.0"
264
+ const: "0.2.0"
31
265
  },
32
266
  meta: {
33
267
  type: "object",
34
268
  description: "Package metadata",
35
- required: [
36
- "name"
37
- ],
269
+ required: ["name"],
38
270
  properties: {
39
271
  name: {
40
272
  type: "string",
@@ -58,14 +290,7 @@ var openpkg_schema_default = {
58
290
  },
59
291
  ecosystem: {
60
292
  type: "string",
61
- description: "Package ecosystem",
62
- enum: [
63
- "js/ts",
64
- "python",
65
- "rust",
66
- "go",
67
- "java"
68
- ]
293
+ description: "Package ecosystem"
69
294
  }
70
295
  }
71
296
  },
@@ -82,16 +307,81 @@ var openpkg_schema_default = {
82
307
  items: {
83
308
  $ref: "#/$defs/typeDef"
84
309
  }
310
+ },
311
+ docs: {
312
+ $ref: "#/$defs/docsMetadata",
313
+ description: "Aggregate documentation coverage metadata"
85
314
  }
86
315
  },
87
316
  $defs: {
317
+ docSignal: {
318
+ type: "string",
319
+ enum: ["description", "params", "returns", "examples"]
320
+ },
321
+ docDrift: {
322
+ type: "object",
323
+ required: ["type", "issue"],
324
+ properties: {
325
+ type: {
326
+ type: "string",
327
+ enum: [
328
+ "param-mismatch",
329
+ "param-type-mismatch",
330
+ "return-type-mismatch",
331
+ "generic-constraint-mismatch",
332
+ "optionality-mismatch",
333
+ "deprecated-mismatch",
334
+ "visibility-mismatch",
335
+ "example-drift",
336
+ "broken-link"
337
+ ]
338
+ },
339
+ target: {
340
+ type: "string",
341
+ description: "Relevant identifier (e.g., parameter name)"
342
+ },
343
+ issue: {
344
+ type: "string",
345
+ description: "Human-friendly drift explanation"
346
+ },
347
+ suggestion: {
348
+ type: "string",
349
+ description: "Optional remediation hint"
350
+ }
351
+ },
352
+ additionalProperties: false
353
+ },
354
+ docsMetadata: {
355
+ type: "object",
356
+ description: "Documentation coverage metadata",
357
+ additionalProperties: false,
358
+ properties: {
359
+ coverageScore: {
360
+ type: "number",
361
+ minimum: 0,
362
+ maximum: 100,
363
+ description: "Documentation coverage value from 0-100."
364
+ },
365
+ missing: {
366
+ type: "array",
367
+ description: "Doc components missing for this entity",
368
+ items: {
369
+ $ref: "#/$defs/docSignal"
370
+ },
371
+ uniqueItems: true
372
+ },
373
+ drift: {
374
+ type: "array",
375
+ description: "Detected documentation drift signals",
376
+ items: {
377
+ $ref: "#/$defs/docDrift"
378
+ }
379
+ }
380
+ }
381
+ },
88
382
  export: {
89
383
  type: "object",
90
- required: [
91
- "id",
92
- "name",
93
- "kind"
94
- ],
384
+ required: ["id", "name", "kind"],
95
385
  properties: {
96
386
  id: {
97
387
  type: "string",
@@ -101,17 +391,26 @@ var openpkg_schema_default = {
101
391
  type: "string",
102
392
  description: "Export name"
103
393
  },
394
+ slug: {
395
+ type: "string",
396
+ description: "Stable slug for linking"
397
+ },
398
+ displayName: {
399
+ type: "string",
400
+ description: "UI-friendly label"
401
+ },
402
+ category: {
403
+ type: "string",
404
+ description: "Grouping hint for navigation"
405
+ },
406
+ importPath: {
407
+ type: "string",
408
+ description: "Recommended import path"
409
+ },
104
410
  kind: {
105
411
  type: "string",
106
412
  description: "Kind of export",
107
- enum: [
108
- "function",
109
- "class",
110
- "variable",
111
- "interface",
112
- "type",
113
- "enum"
114
- ]
413
+ enum: ["function", "class", "variable", "interface", "type", "enum"]
115
414
  },
116
415
  description: {
117
416
  type: "string",
@@ -131,25 +430,40 @@ var openpkg_schema_default = {
131
430
  $ref: "#/$defs/signature"
132
431
  }
133
432
  },
134
- properties: {
433
+ type: {
434
+ description: "Type reference or inline schema for variables",
435
+ oneOf: [{ type: "string" }, { $ref: "#/$defs/schema" }]
436
+ },
437
+ members: {
438
+ type: "array",
439
+ description: "Class/interface/enum members",
440
+ items: { type: "object" }
441
+ },
442
+ tags: {
135
443
  type: "array",
136
- description: "Class/interface properties",
444
+ description: "JSDoc/TSDoc tags",
137
445
  items: {
138
- $ref: "#/$defs/property"
446
+ type: "object",
447
+ required: ["name", "text"],
448
+ properties: {
449
+ name: { type: "string" },
450
+ text: { type: "string" }
451
+ },
452
+ additionalProperties: false
139
453
  }
140
454
  },
141
455
  source: {
142
456
  $ref: "#/$defs/sourceLocation"
457
+ },
458
+ docs: {
459
+ $ref: "#/$defs/docsMetadata",
460
+ description: "Documentation coverage metadata for this export"
143
461
  }
144
462
  }
145
463
  },
146
464
  typeDef: {
147
465
  type: "object",
148
- required: [
149
- "id",
150
- "name",
151
- "kind"
152
- ],
466
+ required: ["id", "name", "kind"],
153
467
  properties: {
154
468
  id: {
155
469
  type: "string",
@@ -159,15 +473,26 @@ var openpkg_schema_default = {
159
473
  type: "string",
160
474
  description: "Type name"
161
475
  },
476
+ slug: {
477
+ type: "string",
478
+ description: "Stable slug for linking"
479
+ },
480
+ displayName: {
481
+ type: "string",
482
+ description: "UI-friendly label"
483
+ },
484
+ category: {
485
+ type: "string",
486
+ description: "Grouping hint for navigation"
487
+ },
488
+ importPath: {
489
+ type: "string",
490
+ description: "Recommended import path"
491
+ },
162
492
  kind: {
163
493
  type: "string",
164
494
  description: "Kind of type definition",
165
- enum: [
166
- "interface",
167
- "type",
168
- "enum",
169
- "class"
170
- ]
495
+ enum: ["interface", "type", "enum", "class"]
171
496
  },
172
497
  description: {
173
498
  type: "string",
@@ -180,11 +505,22 @@ var openpkg_schema_default = {
180
505
  type: "string",
181
506
  description: "Type expression for type aliases"
182
507
  },
183
- properties: {
508
+ members: {
184
509
  type: "array",
185
- description: "Properties for interfaces/classes",
510
+ description: "Members for classes/interfaces/enums",
511
+ items: { type: "object" }
512
+ },
513
+ tags: {
514
+ type: "array",
515
+ description: "JSDoc/TSDoc tags",
186
516
  items: {
187
- $ref: "#/$defs/property"
517
+ type: "object",
518
+ required: ["name", "text"],
519
+ properties: {
520
+ name: { type: "string" },
521
+ text: { type: "string" }
522
+ },
523
+ additionalProperties: false
188
524
  }
189
525
  },
190
526
  source: {
@@ -212,10 +548,7 @@ var openpkg_schema_default = {
212
548
  },
213
549
  parameter: {
214
550
  type: "object",
215
- required: [
216
- "name",
217
- "required"
218
- ],
551
+ required: ["name", "required"],
219
552
  properties: {
220
553
  name: {
221
554
  type: "string",
@@ -225,12 +558,12 @@ var openpkg_schema_default = {
225
558
  type: "boolean",
226
559
  description: "Whether the parameter is required"
227
560
  },
561
+ schema: {
562
+ $ref: "#/$defs/schema"
563
+ },
228
564
  description: {
229
565
  type: "string",
230
566
  description: "Parameter description"
231
- },
232
- schema: {
233
- $ref: "#/$defs/schema"
234
567
  }
235
568
  }
236
569
  },
@@ -246,30 +579,6 @@ var openpkg_schema_default = {
246
579
  }
247
580
  }
248
581
  },
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
582
  schema: {
274
583
  anyOf: [
275
584
  {
@@ -284,17 +593,13 @@ var openpkg_schema_default = {
284
593
  pattern: "^#/types/[A-Za-z0-9_.-]+$"
285
594
  }
286
595
  },
287
- required: [
288
- "$ref"
289
- ],
596
+ required: ["$ref"],
290
597
  additionalProperties: false
291
598
  },
292
599
  {
293
600
  type: "object",
294
601
  not: {
295
- required: [
296
- "$ref"
297
- ]
602
+ required: ["$ref"]
298
603
  },
299
604
  additionalProperties: true
300
605
  }
@@ -302,10 +607,7 @@ var openpkg_schema_default = {
302
607
  },
303
608
  sourceLocation: {
304
609
  type: "object",
305
- required: [
306
- "file",
307
- "line"
308
- ],
610
+ required: ["file", "line"],
309
611
  properties: {
310
612
  file: {
311
613
  type: "string",
@@ -358,204 +660,9 @@ function getValidationErrors(spec) {
358
660
  const result = validateSpec(spec);
359
661
  return result.ok ? [] : result.errors;
360
662
  }
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
663
  export {
556
664
  validateSpec,
557
665
  normalize,
558
- migrate_0_1_0__to__0_2_0 as migrate,
559
666
  getValidationErrors,
560
667
  diffSpec,
561
668
  dereference,