@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/LICENSE +21 -0
- package/README.md +13 -46
- package/dist/index.d.ts +117 -68
- package/dist/index.js +394 -287
- package/package.json +4 -3
- package/schemas/v0.1.0/openpkg.schema.json +75 -88
- package/schemas/v0.2.0/openpkg.schema.json +378 -0
package/dist/index.js
CHANGED
|
@@ -1,22 +1,256 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
|
-
var SCHEMA_VERSION = "0.
|
|
3
|
-
var SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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: "
|
|
444
|
+
description: "JSDoc/TSDoc tags",
|
|
137
445
|
items: {
|
|
138
|
-
|
|
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
|
-
|
|
508
|
+
members: {
|
|
184
509
|
type: "array",
|
|
185
|
-
description: "
|
|
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
|
-
|
|
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,
|