@openpkg-ts/spec 0.2.1 → 0.3.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ryan Waits
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,33 +1,43 @@
1
1
  # @openpkg-ts/spec
2
2
 
3
- Canonical schema, TypeScript types, validation, normalization, deref, diff, and migration helpers for OpenPkg specs.
3
+ OpenPkg schema, types, validation, and diffing utilities.
4
4
 
5
5
  ## Install
6
+
6
7
  ```bash
7
8
  npm install @openpkg-ts/spec
8
9
  ```
9
10
 
10
- ## Quick Helpers
11
- ```ts
12
- import { normalize, validateSpec } from '@openpkg-ts/spec';
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { validateSpec, normalize, diffSpec } from '@openpkg-ts/spec';
13
15
 
14
- const normalized = normalize(spec);
15
- const result = validateSpec(normalized);
16
+ // Validate
17
+ const result = validateSpec(normalize(spec));
16
18
  if (!result.ok) {
17
- throw new Error(result.errors.map((e) => `${e.instancePath || '/'} ${e.message}`).join('\n'));
19
+ console.error(result.errors);
18
20
  }
21
+
22
+ // Diff two specs
23
+ const diff = diffSpec(oldSpec, newSpec);
24
+ console.log(`Coverage delta: ${diff.coverageDelta}%`);
19
25
  ```
20
26
 
21
- ```ts
22
- import { dereference, diffSpec } from '@openpkg-ts/spec';
27
+ ## Exports
23
28
 
24
- const left = dereference(specA);
25
- const right = dereference(specB);
26
- const diff = diffSpec(left, right);
27
- ```
29
+ - `validateSpec` / `assertSpec` - Schema validation
30
+ - `normalize` - Ensure consistent structure
31
+ - `dereference` - Resolve `$ref` pointers
32
+ - `diffSpec` - Compare specs
33
+
34
+ ## Documentation
35
+
36
+ - [Spec Overview](../../docs/spec/overview.md)
37
+ - [Types Reference](../../docs/spec/types.md)
38
+ - [Drift Types](../../docs/spec/drift-types.md)
39
+ - [Diffing](../../docs/spec/diffing.md)
28
40
 
29
- ## See Also
30
- - [SDK generator](../sdk/README.md)
31
- - [CLI usage](../cli/README.md)
41
+ ## License
32
42
 
33
- MIT License
43
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/spec",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Shared schema, validation, and diff utilities for OpenPkg specs",
5
5
  "keywords": [
6
6
  "openpkg",
@@ -4,11 +4,7 @@
4
4
  "title": "OpenPkg Specification",
5
5
  "description": "Schema for OpenPkg specification files",
6
6
  "type": "object",
7
- "required": [
8
- "openpkg",
9
- "meta",
10
- "exports"
11
- ],
7
+ "required": ["openpkg", "meta", "exports"],
12
8
  "properties": {
13
9
  "$schema": {
14
10
  "type": "string",
@@ -24,9 +20,7 @@
24
20
  "meta": {
25
21
  "type": "object",
26
22
  "description": "Package metadata",
27
- "required": [
28
- "name"
29
- ],
23
+ "required": ["name"],
30
24
  "properties": {
31
25
  "name": {
32
26
  "type": "string",
@@ -72,11 +66,7 @@
72
66
  "$defs": {
73
67
  "export": {
74
68
  "type": "object",
75
- "required": [
76
- "id",
77
- "name",
78
- "kind"
79
- ],
69
+ "required": ["id", "name", "kind"],
80
70
  "properties": {
81
71
  "id": {
82
72
  "type": "string",
@@ -105,14 +95,7 @@
105
95
  "kind": {
106
96
  "type": "string",
107
97
  "description": "Kind of export",
108
- "enum": [
109
- "function",
110
- "class",
111
- "variable",
112
- "interface",
113
- "type",
114
- "enum"
115
- ]
98
+ "enum": ["function", "class", "variable", "interface", "type", "enum"]
116
99
  },
117
100
  "description": {
118
101
  "type": "string",
@@ -134,10 +117,7 @@
134
117
  },
135
118
  "type": {
136
119
  "description": "Type reference or inline schema for variables",
137
- "oneOf": [
138
- { "type": "string" },
139
- { "$ref": "#/$defs/schema" }
140
- ]
120
+ "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/schema" }]
141
121
  },
142
122
  "members": {
143
123
  "type": "array",
@@ -164,11 +144,7 @@
164
144
  },
165
145
  "typeDef": {
166
146
  "type": "object",
167
- "required": [
168
- "id",
169
- "name",
170
- "kind"
171
- ],
147
+ "required": ["id", "name", "kind"],
172
148
  "properties": {
173
149
  "id": {
174
150
  "type": "string",
@@ -197,12 +173,7 @@
197
173
  "kind": {
198
174
  "type": "string",
199
175
  "description": "Kind of type definition",
200
- "enum": [
201
- "interface",
202
- "type",
203
- "enum",
204
- "class"
205
- ]
176
+ "enum": ["interface", "type", "enum", "class"]
206
177
  },
207
178
  "description": {
208
179
  "type": "string",
@@ -258,10 +229,7 @@
258
229
  },
259
230
  "parameter": {
260
231
  "type": "object",
261
- "required": [
262
- "name",
263
- "required"
264
- ],
232
+ "required": ["name", "required"],
265
233
  "properties": {
266
234
  "name": {
267
235
  "type": "string",
@@ -302,17 +270,13 @@
302
270
  "pattern": "^#/types/[A-Za-z0-9_.-]+$"
303
271
  }
304
272
  },
305
- "required": [
306
- "$ref"
307
- ],
273
+ "required": ["$ref"],
308
274
  "additionalProperties": false
309
275
  },
310
276
  {
311
277
  "type": "object",
312
278
  "not": {
313
- "required": [
314
- "$ref"
315
- ]
279
+ "required": ["$ref"]
316
280
  },
317
281
  "additionalProperties": true
318
282
  }
@@ -320,10 +284,7 @@
320
284
  },
321
285
  "sourceLocation": {
322
286
  "type": "object",
323
- "required": [
324
- "file",
325
- "line"
326
- ],
287
+ "required": ["file", "line"],
327
288
  "properties": {
328
289
  "file": {
329
290
  "type": "string",
@@ -4,11 +4,7 @@
4
4
  "title": "OpenPkg Specification",
5
5
  "description": "Schema for OpenPkg specification files",
6
6
  "type": "object",
7
- "required": [
8
- "openpkg",
9
- "meta",
10
- "exports"
11
- ],
7
+ "required": ["openpkg", "meta", "exports"],
12
8
  "properties": {
13
9
  "$schema": {
14
10
  "type": "string",
@@ -24,9 +20,7 @@
24
20
  "meta": {
25
21
  "type": "object",
26
22
  "description": "Package metadata",
27
- "required": [
28
- "name"
29
- ],
23
+ "required": ["name"],
30
24
  "properties": {
31
25
  "name": {
32
26
  "type": "string",
@@ -76,12 +70,7 @@
76
70
  "$defs": {
77
71
  "docSignal": {
78
72
  "type": "string",
79
- "enum": [
80
- "description",
81
- "params",
82
- "returns",
83
- "examples"
84
- ]
73
+ "enum": ["description", "params", "returns", "examples"]
85
74
  },
86
75
  "docDrift": {
87
76
  "type": "object",
@@ -89,7 +78,22 @@
89
78
  "properties": {
90
79
  "type": {
91
80
  "type": "string",
92
- "enum": ["param-mismatch", "param-type-mismatch", "return-type-mismatch", "generic-constraint-mismatch", "optionality-mismatch", "deprecated-mismatch", "visibility-mismatch", "example-drift", "broken-link"]
81
+ "enum": [
82
+ "param-mismatch",
83
+ "param-type-mismatch",
84
+ "return-type-mismatch",
85
+ "generic-constraint-mismatch",
86
+ "optionality-mismatch",
87
+ "deprecated-mismatch",
88
+ "visibility-mismatch",
89
+ "async-mismatch",
90
+ "property-type-drift",
91
+ "example-drift",
92
+ "example-syntax-error",
93
+ "example-runtime-error",
94
+ "example-assertion-failed",
95
+ "broken-link"
96
+ ]
93
97
  },
94
98
  "target": {
95
99
  "type": "string",
@@ -136,11 +140,7 @@
136
140
  },
137
141
  "export": {
138
142
  "type": "object",
139
- "required": [
140
- "id",
141
- "name",
142
- "kind"
143
- ],
143
+ "required": ["id", "name", "kind"],
144
144
  "properties": {
145
145
  "id": {
146
146
  "type": "string",
@@ -158,6 +158,10 @@
158
158
  "type": "string",
159
159
  "description": "UI-friendly label"
160
160
  },
161
+ "alias": {
162
+ "type": "string",
163
+ "description": "Export alias if re-exported with a different name (id uses alias, name uses original)"
164
+ },
161
165
  "category": {
162
166
  "type": "string",
163
167
  "description": "Grouping hint for navigation"
@@ -169,14 +173,7 @@
169
173
  "kind": {
170
174
  "type": "string",
171
175
  "description": "Kind of export",
172
- "enum": [
173
- "function",
174
- "class",
175
- "variable",
176
- "interface",
177
- "type",
178
- "enum"
179
- ]
176
+ "enum": ["function", "class", "variable", "interface", "type", "enum", "namespace", "external"]
180
177
  },
181
178
  "description": {
182
179
  "type": "string",
@@ -198,16 +195,22 @@
198
195
  },
199
196
  "type": {
200
197
  "description": "Type reference or inline schema for variables",
201
- "oneOf": [
202
- { "type": "string" },
203
- { "$ref": "#/$defs/schema" }
204
- ]
198
+ "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/schema" }]
205
199
  },
206
200
  "members": {
207
201
  "type": "array",
208
202
  "description": "Class/interface/enum members",
209
203
  "items": { "type": "object" }
210
204
  },
205
+ "extends": {
206
+ "type": "string",
207
+ "description": "Base class or interface that this class/interface extends"
208
+ },
209
+ "implements": {
210
+ "type": "array",
211
+ "description": "Interfaces implemented by this class",
212
+ "items": { "type": "string" }
213
+ },
211
214
  "tags": {
212
215
  "type": "array",
213
216
  "description": "JSDoc/TSDoc tags",
@@ -232,11 +235,7 @@
232
235
  },
233
236
  "typeDef": {
234
237
  "type": "object",
235
- "required": [
236
- "id",
237
- "name",
238
- "kind"
239
- ],
238
+ "required": ["id", "name", "kind"],
240
239
  "properties": {
241
240
  "id": {
242
241
  "type": "string",
@@ -254,6 +253,10 @@
254
253
  "type": "string",
255
254
  "description": "UI-friendly label"
256
255
  },
256
+ "alias": {
257
+ "type": "string",
258
+ "description": "Export alias if re-exported with a different name (id uses alias, name uses original)"
259
+ },
257
260
  "category": {
258
261
  "type": "string",
259
262
  "description": "Grouping hint for navigation"
@@ -265,12 +268,7 @@
265
268
  "kind": {
266
269
  "type": "string",
267
270
  "description": "Kind of type definition",
268
- "enum": [
269
- "interface",
270
- "type",
271
- "enum",
272
- "class"
273
- ]
271
+ "enum": ["interface", "type", "enum", "class", "external"]
274
272
  },
275
273
  "description": {
276
274
  "type": "string",
@@ -288,6 +286,15 @@
288
286
  "description": "Members for classes/interfaces/enums",
289
287
  "items": { "type": "object" }
290
288
  },
289
+ "extends": {
290
+ "type": "string",
291
+ "description": "Base class or interface that this class/interface extends"
292
+ },
293
+ "implements": {
294
+ "type": "array",
295
+ "description": "Interfaces implemented by this class",
296
+ "items": { "type": "string" }
297
+ },
291
298
  "tags": {
292
299
  "type": "array",
293
300
  "description": "JSDoc/TSDoc tags",
@@ -326,10 +333,7 @@
326
333
  },
327
334
  "parameter": {
328
335
  "type": "object",
329
- "required": [
330
- "name",
331
- "required"
332
- ],
336
+ "required": ["name", "required"],
333
337
  "properties": {
334
338
  "name": {
335
339
  "type": "string",
@@ -345,6 +349,13 @@
345
349
  "description": {
346
350
  "type": "string",
347
351
  "description": "Parameter description"
352
+ },
353
+ "default": {
354
+ "description": "Default value for the parameter"
355
+ },
356
+ "rest": {
357
+ "type": "boolean",
358
+ "description": "Whether this is a rest parameter (...args)"
348
359
  }
349
360
  }
350
361
  },
@@ -374,17 +385,13 @@
374
385
  "pattern": "^#/types/[A-Za-z0-9_.-]+$"
375
386
  }
376
387
  },
377
- "required": [
378
- "$ref"
379
- ],
388
+ "required": ["$ref"],
380
389
  "additionalProperties": false
381
390
  },
382
391
  {
383
392
  "type": "object",
384
393
  "not": {
385
- "required": [
386
- "$ref"
387
- ]
394
+ "required": ["$ref"]
388
395
  },
389
396
  "additionalProperties": true
390
397
  }
@@ -392,10 +399,7 @@
392
399
  },
393
400
  "sourceLocation": {
394
401
  "type": "object",
395
- "required": [
396
- "file",
397
- "line"
398
- ],
402
+ "required": ["file", "line"],
399
403
  "properties": {
400
404
  "file": {
401
405
  "type": "string",
package/dist/index.d.ts DELETED
@@ -1,148 +0,0 @@
1
- declare const SCHEMA_VERSION = "0.2.0";
2
- declare const SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.2.0/openpkg.schema.json";
3
- declare const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
4
- type SpecTag = {
5
- name: string;
6
- text: string;
7
- };
8
- type SpecSource = {
9
- file?: string;
10
- line?: number;
11
- url?: string;
12
- };
13
- type SpecSchema = unknown;
14
- type SpecExample = Record<string, unknown>;
15
- type SpecExtension = Record<string, unknown>;
16
- type SpecDocSignal = "description" | "params" | "returns" | "examples";
17
- type SpecDocDrift = {
18
- type: "param-mismatch" | "param-type-mismatch" | "return-type-mismatch" | "generic-constraint-mismatch" | "optionality-mismatch" | "deprecated-mismatch" | "visibility-mismatch" | "example-drift" | "broken-link";
19
- target?: string;
20
- issue: string;
21
- suggestion?: string;
22
- };
23
- type SpecVisibility = "public" | "protected" | "private";
24
- type SpecDocsMetadata = {
25
- coverageScore?: number;
26
- missing?: SpecDocSignal[];
27
- drift?: SpecDocDrift[];
28
- };
29
- type SpecTypeParameter = {
30
- name: string;
31
- constraint?: string;
32
- default?: string;
33
- };
34
- type SpecSignatureParameter = {
35
- name: string;
36
- required?: boolean;
37
- description?: string;
38
- schema: SpecSchema;
39
- };
40
- type SpecSignatureReturn = {
41
- schema: SpecSchema;
42
- description?: string;
43
- tsType?: string;
44
- };
45
- type SpecSignature = {
46
- parameters?: SpecSignatureParameter[];
47
- returns?: SpecSignatureReturn;
48
- description?: string;
49
- typeParameters?: SpecTypeParameter[];
50
- };
51
- type SpecMember = {
52
- id?: string;
53
- name?: string;
54
- kind?: string;
55
- description?: string;
56
- tags?: SpecTag[];
57
- visibility?: SpecVisibility;
58
- flags?: Record<string, unknown>;
59
- schema?: SpecSchema;
60
- signatures?: SpecSignature[];
61
- };
62
- type SpecExportKind = "function" | "class" | "variable" | "interface" | "type" | "enum" | "module" | "namespace" | "reference";
63
- type SpecTypeKind = "class" | "interface" | "type" | "enum";
64
- type SpecExport = {
65
- id: string;
66
- name: string;
67
- slug?: string;
68
- displayName?: string;
69
- category?: string;
70
- importPath?: string;
71
- kind: SpecExportKind;
72
- signatures?: SpecSignature[];
73
- typeParameters?: SpecTypeParameter[];
74
- members?: SpecMember[];
75
- type?: string | SpecSchema;
76
- schema?: SpecSchema;
77
- description?: string;
78
- examples?: string[];
79
- docs?: SpecDocsMetadata;
80
- source?: SpecSource;
81
- deprecated?: boolean;
82
- flags?: Record<string, unknown>;
83
- tags?: SpecTag[];
84
- };
85
- type SpecType = {
86
- id: string;
87
- name: string;
88
- slug?: string;
89
- displayName?: string;
90
- category?: string;
91
- importPath?: string;
92
- kind: SpecTypeKind;
93
- description?: string;
94
- schema?: SpecSchema;
95
- type?: string | SpecSchema;
96
- members?: SpecMember[];
97
- source?: SpecSource;
98
- tags?: SpecTag[];
99
- rawComments?: string;
100
- };
101
- type OpenPkgMeta = {
102
- name: string;
103
- version?: string;
104
- description?: string;
105
- license?: string;
106
- repository?: string;
107
- ecosystem?: string;
108
- };
109
- type OpenPkg = {
110
- $schema?: string;
111
- openpkg: "0.2.0";
112
- meta: OpenPkgMeta;
113
- exports: SpecExport[];
114
- types?: SpecType[];
115
- examples?: SpecExample[];
116
- docs?: SpecDocsMetadata;
117
- extensions?: SpecExtension;
118
- };
119
- declare function dereference(spec: OpenPkg): OpenPkg;
120
- type SpecDiff = {
121
- breaking: string[];
122
- nonBreaking: string[];
123
- docsOnly: string[];
124
- coverageDelta: number;
125
- oldCoverage: number;
126
- newCoverage: number;
127
- newUndocumented: string[];
128
- improvedExports: string[];
129
- regressedExports: string[];
130
- driftIntroduced: number;
131
- driftResolved: number;
132
- };
133
- declare function diffSpec(oldSpec: OpenPkg, newSpec: OpenPkg): SpecDiff;
134
- declare function normalize(spec: OpenPkg): OpenPkg;
135
- type SpecError = {
136
- instancePath: string;
137
- message: string;
138
- keyword: string;
139
- };
140
- declare function validateSpec(spec: unknown): {
141
- ok: true;
142
- } | {
143
- ok: false;
144
- errors: SpecError[];
145
- };
146
- declare function assertSpec(spec: unknown): asserts spec is OpenPkg;
147
- declare function getValidationErrors(spec: unknown): SpecError[];
148
- export { validateSpec, normalize, getValidationErrors, diffSpec, dereference, assertSpec, SpecVisibility, SpecTypeParameter, SpecTypeKind, SpecType, SpecTag, SpecSource, SpecSignatureReturn, SpecSignatureParameter, SpecSignature, SpecSchema, SpecMember, SpecExtension, SpecExportKind, SpecExport, SpecExample, SpecDocsMetadata, SpecDocSignal, SpecDocDrift, SCHEMA_VERSION, SCHEMA_URL, OpenPkgMeta, OpenPkg, JSON_SCHEMA_DRAFT };
package/dist/index.js DELETED
@@ -1,707 +0,0 @@
1
- // src/constants.ts
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
- 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
- }
243
- // src/validate.ts
244
- import Ajv from "ajv/dist/2020.js";
245
- import addFormats from "ajv-formats";
246
- // schemas/v0.2.0/openpkg.schema.json
247
- var openpkg_schema_default = {
248
- $schema: "https://json-schema.org/draft/2020-12/schema",
249
- $id: "https://unpkg.com/@openpkg-ts/spec/schemas/v0.2.0/openpkg.schema.json",
250
- title: "OpenPkg Specification",
251
- description: "Schema for OpenPkg specification files",
252
- type: "object",
253
- required: [
254
- "openpkg",
255
- "meta",
256
- "exports"
257
- ],
258
- properties: {
259
- $schema: {
260
- type: "string",
261
- description: "Reference to the OpenPkg schema version",
262
- pattern: "^(https://raw\\.githubusercontent\\.com/ryanwaits/openpkg/main/schemas/v[0-9]+\\.[0-9]+\\.[0-9]+/openpkg\\.schema\\.json|https://unpkg\\.com/@openpkg-ts/spec/schemas/v[0-9]+\\.[0-9]+\\.[0-9]+/openpkg\\.schema\\.json)$"
263
- },
264
- openpkg: {
265
- type: "string",
266
- description: "OpenPkg specification version",
267
- pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$",
268
- const: "0.2.0"
269
- },
270
- meta: {
271
- type: "object",
272
- description: "Package metadata",
273
- required: [
274
- "name"
275
- ],
276
- properties: {
277
- name: {
278
- type: "string",
279
- description: "Package name"
280
- },
281
- version: {
282
- type: "string",
283
- description: "Package version"
284
- },
285
- description: {
286
- type: "string",
287
- description: "Package description"
288
- },
289
- license: {
290
- type: "string",
291
- description: "Package license"
292
- },
293
- repository: {
294
- type: "string",
295
- description: "Repository URL"
296
- },
297
- ecosystem: {
298
- type: "string",
299
- description: "Package ecosystem"
300
- }
301
- }
302
- },
303
- exports: {
304
- type: "array",
305
- description: "List of exported items",
306
- items: {
307
- $ref: "#/$defs/export"
308
- }
309
- },
310
- types: {
311
- type: "array",
312
- description: "List of type definitions",
313
- items: {
314
- $ref: "#/$defs/typeDef"
315
- }
316
- },
317
- docs: {
318
- $ref: "#/$defs/docsMetadata",
319
- description: "Aggregate documentation coverage metadata"
320
- }
321
- },
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
- },
383
- export: {
384
- type: "object",
385
- required: [
386
- "id",
387
- "name",
388
- "kind"
389
- ],
390
- properties: {
391
- id: {
392
- type: "string",
393
- description: "Unique identifier for the export"
394
- },
395
- name: {
396
- type: "string",
397
- description: "Export name"
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
- },
415
- kind: {
416
- type: "string",
417
- description: "Kind of export",
418
- enum: [
419
- "function",
420
- "class",
421
- "variable",
422
- "interface",
423
- "type",
424
- "enum"
425
- ]
426
- },
427
- description: {
428
- type: "string",
429
- description: "JSDoc/TSDoc description"
430
- },
431
- examples: {
432
- type: "array",
433
- description: "Usage examples from documentation",
434
- items: {
435
- type: "string"
436
- }
437
- },
438
- signatures: {
439
- type: "array",
440
- description: "Function/method signatures",
441
- items: {
442
- $ref: "#/$defs/signature"
443
- }
444
- },
445
- type: {
446
- description: "Type reference or inline schema for variables",
447
- oneOf: [
448
- { type: "string" },
449
- { $ref: "#/$defs/schema" }
450
- ]
451
- },
452
- members: {
453
- type: "array",
454
- description: "Class/interface/enum members",
455
- items: { type: "object" }
456
- },
457
- tags: {
458
- type: "array",
459
- description: "JSDoc/TSDoc tags",
460
- items: {
461
- type: "object",
462
- required: ["name", "text"],
463
- properties: {
464
- name: { type: "string" },
465
- text: { type: "string" }
466
- },
467
- additionalProperties: false
468
- }
469
- },
470
- source: {
471
- $ref: "#/$defs/sourceLocation"
472
- },
473
- docs: {
474
- $ref: "#/$defs/docsMetadata",
475
- description: "Documentation coverage metadata for this export"
476
- }
477
- }
478
- },
479
- typeDef: {
480
- type: "object",
481
- required: [
482
- "id",
483
- "name",
484
- "kind"
485
- ],
486
- properties: {
487
- id: {
488
- type: "string",
489
- description: "Unique identifier for the type"
490
- },
491
- name: {
492
- type: "string",
493
- description: "Type name"
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
- },
511
- kind: {
512
- type: "string",
513
- description: "Kind of type definition",
514
- enum: [
515
- "interface",
516
- "type",
517
- "enum",
518
- "class"
519
- ]
520
- },
521
- description: {
522
- type: "string",
523
- description: "JSDoc/TSDoc description"
524
- },
525
- schema: {
526
- $ref: "#/$defs/schema"
527
- },
528
- type: {
529
- type: "string",
530
- description: "Type expression for type aliases"
531
- },
532
- members: {
533
- type: "array",
534
- description: "Members for classes/interfaces/enums",
535
- items: { type: "object" }
536
- },
537
- tags: {
538
- type: "array",
539
- description: "JSDoc/TSDoc tags",
540
- items: {
541
- type: "object",
542
- required: ["name", "text"],
543
- properties: {
544
- name: { type: "string" },
545
- text: { type: "string" }
546
- },
547
- additionalProperties: false
548
- }
549
- },
550
- source: {
551
- $ref: "#/$defs/sourceLocation"
552
- }
553
- }
554
- },
555
- signature: {
556
- type: "object",
557
- properties: {
558
- parameters: {
559
- type: "array",
560
- items: {
561
- $ref: "#/$defs/parameter"
562
- }
563
- },
564
- returns: {
565
- $ref: "#/$defs/returns"
566
- },
567
- description: {
568
- type: "string",
569
- description: "Signature-level description"
570
- }
571
- }
572
- },
573
- parameter: {
574
- type: "object",
575
- required: [
576
- "name",
577
- "required"
578
- ],
579
- properties: {
580
- name: {
581
- type: "string",
582
- description: "Parameter name"
583
- },
584
- required: {
585
- type: "boolean",
586
- description: "Whether the parameter is required"
587
- },
588
- schema: {
589
- $ref: "#/$defs/schema"
590
- },
591
- description: {
592
- type: "string",
593
- description: "Parameter description"
594
- }
595
- }
596
- },
597
- returns: {
598
- type: "object",
599
- properties: {
600
- schema: {
601
- $ref: "#/$defs/schema"
602
- },
603
- description: {
604
- type: "string",
605
- description: "Return value description"
606
- }
607
- }
608
- },
609
- schema: {
610
- anyOf: [
611
- {
612
- type: "boolean"
613
- },
614
- {
615
- type: "object",
616
- properties: {
617
- $ref: {
618
- type: "string",
619
- description: "Reference to another type",
620
- pattern: "^#/types/[A-Za-z0-9_.-]+$"
621
- }
622
- },
623
- required: [
624
- "$ref"
625
- ],
626
- additionalProperties: false
627
- },
628
- {
629
- type: "object",
630
- not: {
631
- required: [
632
- "$ref"
633
- ]
634
- },
635
- additionalProperties: true
636
- }
637
- ]
638
- },
639
- sourceLocation: {
640
- type: "object",
641
- required: [
642
- "file",
643
- "line"
644
- ],
645
- properties: {
646
- file: {
647
- type: "string",
648
- description: "Source file path"
649
- },
650
- line: {
651
- type: "integer",
652
- description: "Line number in source file",
653
- minimum: 1
654
- }
655
- }
656
- }
657
- }
658
- };
659
-
660
- // src/validate.ts
661
- var ajv = new Ajv({
662
- strict: false,
663
- allErrors: true,
664
- allowUnionTypes: true,
665
- $data: true
666
- });
667
- addFormats(ajv);
668
- var validate = ajv.compile(openpkg_schema_default);
669
- function validateSpec(spec) {
670
- const ok = validate(spec);
671
- if (ok) {
672
- return { ok: true };
673
- }
674
- const errors = (validate.errors ?? []).map((error) => ({
675
- instancePath: error.instancePath ?? "",
676
- message: error.message ?? "invalid",
677
- keyword: error.keyword ?? "unknown"
678
- }));
679
- return {
680
- ok: false,
681
- errors
682
- };
683
- }
684
- function assertSpec(spec) {
685
- const result = validateSpec(spec);
686
- if (!result.ok) {
687
- const details = result.errors.map((error) => `- ${error.instancePath || "/"} ${error.message}`).join(`
688
- `);
689
- throw new Error(`Invalid OpenPkg spec:
690
- ${details}`);
691
- }
692
- }
693
- function getValidationErrors(spec) {
694
- const result = validateSpec(spec);
695
- return result.ok ? [] : result.errors;
696
- }
697
- export {
698
- validateSpec,
699
- normalize,
700
- getValidationErrors,
701
- diffSpec,
702
- dereference,
703
- assertSpec,
704
- SCHEMA_VERSION,
705
- SCHEMA_URL,
706
- JSON_SCHEMA_DRAFT
707
- };