@openpkg-ts/spec 0.1.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/README.md +66 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +566 -0
- package/package.json +53 -0
- package/schemas/v0.1.0/openpkg.schema.json +314 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @openpkg-ts/spec
|
|
2
|
+
|
|
3
|
+
Shared contract for the OpenPkg specification, including versioned JSON Schema files, TypeScript types, validation helpers, and diff utilities. The package is still under construction; functionality will be filled in as the spec is extracted from the CLI and SDK.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The build compiles ESM output and type declarations under `dist/`. Schemas are published verbatim from the `schemas/` directory.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Usage Examples
|
|
16
|
+
|
|
17
|
+
### Validate & Normalize a Generated Spec
|
|
18
|
+
```ts
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
|
+
import { normalize, validateSpec } from '@openpkg-ts/spec';
|
|
21
|
+
|
|
22
|
+
const raw = await readFile('openpkg.json', 'utf8');
|
|
23
|
+
const spec = JSON.parse(raw);
|
|
24
|
+
const normalized = normalize(spec);
|
|
25
|
+
|
|
26
|
+
const result = validateSpec(normalized);
|
|
27
|
+
if (!result.ok) {
|
|
28
|
+
for (const err of result.errors) {
|
|
29
|
+
console.error(`schema: ${err.instancePath || '/'} ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('✅ spec is valid and normalized');
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Diff Two Spec Snapshots
|
|
38
|
+
```ts
|
|
39
|
+
import { readFile } from 'node:fs/promises';
|
|
40
|
+
import { dereference, diffSpec, normalize } from '@openpkg-ts/spec';
|
|
41
|
+
|
|
42
|
+
const [currentPath, nextPath] = process.argv.slice(2);
|
|
43
|
+
const load = async (file: string) =>
|
|
44
|
+
dereference(normalize(JSON.parse(await readFile(file, 'utf8'))));
|
|
45
|
+
|
|
46
|
+
const current = await load(currentPath);
|
|
47
|
+
const next = await load(nextPath);
|
|
48
|
+
const diff = diffSpec(current, next);
|
|
49
|
+
|
|
50
|
+
console.log('Breaking changes:', diff.breaking);
|
|
51
|
+
console.log('Non-breaking changes:', diff.nonBreaking);
|
|
52
|
+
console.log('Docs-only changes:', diff.docsOnly);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Assert Valid Specs in Tests
|
|
56
|
+
```ts
|
|
57
|
+
import { expect, test } from 'bun:test';
|
|
58
|
+
import { normalize, validateSpec } from '@openpkg-ts/spec';
|
|
59
|
+
import spec from '../openpkg.json' assert { type: 'json' };
|
|
60
|
+
|
|
61
|
+
test('generated spec stays schema-valid', () => {
|
|
62
|
+
const normalized = normalize(spec);
|
|
63
|
+
const result = validateSpec(normalized);
|
|
64
|
+
expect(result.ok).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
declare const SCHEMA_VERSION = "0.1.0";
|
|
2
|
+
declare const SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.1.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 SpecSignatureParameter = {
|
|
17
|
+
name: string
|
|
18
|
+
required?: boolean
|
|
19
|
+
description?: string
|
|
20
|
+
schema: SpecSchema
|
|
21
|
+
};
|
|
22
|
+
type SpecSignatureReturn = {
|
|
23
|
+
schema: SpecSchema
|
|
24
|
+
description?: string
|
|
25
|
+
};
|
|
26
|
+
type SpecSignature = {
|
|
27
|
+
parameters?: SpecSignatureParameter[]
|
|
28
|
+
returns?: SpecSignatureReturn
|
|
29
|
+
description?: string
|
|
30
|
+
};
|
|
31
|
+
type SpecMember = unknown;
|
|
32
|
+
type SpecExportKind = "function" | "class" | "variable" | "interface" | "type" | "enum" | "module" | "namespace" | "reference";
|
|
33
|
+
type SpecTypeKind = "class" | "interface" | "type" | "enum";
|
|
34
|
+
type SpecExport = {
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
kind: SpecExportKind
|
|
38
|
+
signatures?: SpecSignature[]
|
|
39
|
+
members?: SpecMember[]
|
|
40
|
+
type?: string | SpecSchema
|
|
41
|
+
schema?: SpecSchema
|
|
42
|
+
description?: string
|
|
43
|
+
examples?: string[]
|
|
44
|
+
source?: SpecSource
|
|
45
|
+
flags?: Record<string, unknown>
|
|
46
|
+
tags?: SpecTag[]
|
|
47
|
+
};
|
|
48
|
+
type SpecType = {
|
|
49
|
+
id: string
|
|
50
|
+
name: string
|
|
51
|
+
kind: SpecTypeKind
|
|
52
|
+
description?: string
|
|
53
|
+
schema?: SpecSchema
|
|
54
|
+
type?: string | SpecSchema
|
|
55
|
+
members?: SpecMember[]
|
|
56
|
+
source?: SpecSource
|
|
57
|
+
tags?: SpecTag[]
|
|
58
|
+
rawComments?: string
|
|
59
|
+
};
|
|
60
|
+
type OpenPkgMeta = {
|
|
61
|
+
name: string
|
|
62
|
+
version?: string
|
|
63
|
+
description?: string
|
|
64
|
+
license?: string
|
|
65
|
+
repository?: string
|
|
66
|
+
ecosystem?: string
|
|
67
|
+
};
|
|
68
|
+
type OpenPkg = {
|
|
69
|
+
$schema?: string
|
|
70
|
+
openpkg: "0.1.0"
|
|
71
|
+
meta: OpenPkgMeta
|
|
72
|
+
exports: SpecExport[]
|
|
73
|
+
types?: SpecType[]
|
|
74
|
+
examples?: SpecExample[]
|
|
75
|
+
extensions?: SpecExtension
|
|
76
|
+
};
|
|
77
|
+
type SpecError = {
|
|
78
|
+
instancePath: string
|
|
79
|
+
message: string
|
|
80
|
+
keyword: string
|
|
81
|
+
};
|
|
82
|
+
declare function validateSpec(spec: unknown): {
|
|
83
|
+
ok: true
|
|
84
|
+
} | {
|
|
85
|
+
ok: false
|
|
86
|
+
errors: SpecError[]
|
|
87
|
+
};
|
|
88
|
+
declare function assertSpec(spec: unknown): asserts spec is OpenPkg;
|
|
89
|
+
declare function getValidationErrors(spec: unknown): SpecError[];
|
|
90
|
+
declare function normalize(spec: OpenPkg): OpenPkg;
|
|
91
|
+
declare function dereference(spec: OpenPkg): OpenPkg;
|
|
92
|
+
declare function migrate_0_1_0__to__0_2_0(spec: OpenPkg): OpenPkg;
|
|
93
|
+
type SpecDiff = {
|
|
94
|
+
breaking: string[]
|
|
95
|
+
nonBreaking: string[]
|
|
96
|
+
docsOnly: string[]
|
|
97
|
+
};
|
|
98
|
+
declare function diffSpec(a: OpenPkg, b: OpenPkg): SpecDiff;
|
|
99
|
+
export { validateSpec, normalize, migrate_0_1_0__to__0_2_0 as migrate, getValidationErrors, diffSpec, dereference, assertSpec, SpecTypeKind, SpecType, SpecTag, SpecSource, SpecSignatureReturn, SpecSignatureParameter, SpecSignature, SpecSchema, SpecMember, SpecExtension, SpecExportKind, SpecExport, SpecExample, SCHEMA_VERSION, SCHEMA_URL, OpenPkgMeta, OpenPkg, JSON_SCHEMA_DRAFT };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
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";
|
|
4
|
+
var JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
5
|
+
// src/validate.ts
|
|
6
|
+
import Ajv from "ajv/dist/2020.js";
|
|
7
|
+
import addFormats from "ajv-formats";
|
|
8
|
+
// schemas/v0.1.0/openpkg.schema.json
|
|
9
|
+
var openpkg_schema_default = {
|
|
10
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11
|
+
$id: "https://unpkg.com/@openpkg-ts/spec/schemas/v0.1.0/openpkg.schema.json",
|
|
12
|
+
title: "OpenPkg Specification",
|
|
13
|
+
description: "Schema for OpenPkg specification files",
|
|
14
|
+
type: "object",
|
|
15
|
+
required: [
|
|
16
|
+
"openpkg",
|
|
17
|
+
"meta",
|
|
18
|
+
"exports"
|
|
19
|
+
],
|
|
20
|
+
properties: {
|
|
21
|
+
$schema: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Reference to the OpenPkg schema version",
|
|
24
|
+
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)$"
|
|
25
|
+
},
|
|
26
|
+
openpkg: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "OpenPkg specification version",
|
|
29
|
+
pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
30
|
+
const: "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
meta: {
|
|
33
|
+
type: "object",
|
|
34
|
+
description: "Package metadata",
|
|
35
|
+
required: [
|
|
36
|
+
"name"
|
|
37
|
+
],
|
|
38
|
+
properties: {
|
|
39
|
+
name: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Package name"
|
|
42
|
+
},
|
|
43
|
+
version: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Package version"
|
|
46
|
+
},
|
|
47
|
+
description: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Package description"
|
|
50
|
+
},
|
|
51
|
+
license: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Package license"
|
|
54
|
+
},
|
|
55
|
+
repository: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Repository URL"
|
|
58
|
+
},
|
|
59
|
+
ecosystem: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Package ecosystem",
|
|
62
|
+
enum: [
|
|
63
|
+
"js/ts",
|
|
64
|
+
"python",
|
|
65
|
+
"rust",
|
|
66
|
+
"go",
|
|
67
|
+
"java"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
exports: {
|
|
73
|
+
type: "array",
|
|
74
|
+
description: "List of exported items",
|
|
75
|
+
items: {
|
|
76
|
+
$ref: "#/$defs/export"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
types: {
|
|
80
|
+
type: "array",
|
|
81
|
+
description: "List of type definitions",
|
|
82
|
+
items: {
|
|
83
|
+
$ref: "#/$defs/typeDef"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
$defs: {
|
|
88
|
+
export: {
|
|
89
|
+
type: "object",
|
|
90
|
+
required: [
|
|
91
|
+
"id",
|
|
92
|
+
"name",
|
|
93
|
+
"kind"
|
|
94
|
+
],
|
|
95
|
+
properties: {
|
|
96
|
+
id: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Unique identifier for the export"
|
|
99
|
+
},
|
|
100
|
+
name: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Export name"
|
|
103
|
+
},
|
|
104
|
+
kind: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Kind of export",
|
|
107
|
+
enum: [
|
|
108
|
+
"function",
|
|
109
|
+
"class",
|
|
110
|
+
"variable",
|
|
111
|
+
"interface",
|
|
112
|
+
"type",
|
|
113
|
+
"enum"
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
description: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "JSDoc/TSDoc description"
|
|
119
|
+
},
|
|
120
|
+
examples: {
|
|
121
|
+
type: "array",
|
|
122
|
+
description: "Usage examples from documentation",
|
|
123
|
+
items: {
|
|
124
|
+
type: "string"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
signatures: {
|
|
128
|
+
type: "array",
|
|
129
|
+
description: "Function/method signatures",
|
|
130
|
+
items: {
|
|
131
|
+
$ref: "#/$defs/signature"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
properties: {
|
|
135
|
+
type: "array",
|
|
136
|
+
description: "Class/interface properties",
|
|
137
|
+
items: {
|
|
138
|
+
$ref: "#/$defs/property"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
source: {
|
|
142
|
+
$ref: "#/$defs/sourceLocation"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
typeDef: {
|
|
147
|
+
type: "object",
|
|
148
|
+
required: [
|
|
149
|
+
"id",
|
|
150
|
+
"name",
|
|
151
|
+
"kind"
|
|
152
|
+
],
|
|
153
|
+
properties: {
|
|
154
|
+
id: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Unique identifier for the type"
|
|
157
|
+
},
|
|
158
|
+
name: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Type name"
|
|
161
|
+
},
|
|
162
|
+
kind: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Kind of type definition",
|
|
165
|
+
enum: [
|
|
166
|
+
"interface",
|
|
167
|
+
"type",
|
|
168
|
+
"enum",
|
|
169
|
+
"class"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
description: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "JSDoc/TSDoc description"
|
|
175
|
+
},
|
|
176
|
+
schema: {
|
|
177
|
+
$ref: "#/$defs/schema"
|
|
178
|
+
},
|
|
179
|
+
type: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "Type expression for type aliases"
|
|
182
|
+
},
|
|
183
|
+
properties: {
|
|
184
|
+
type: "array",
|
|
185
|
+
description: "Properties for interfaces/classes",
|
|
186
|
+
items: {
|
|
187
|
+
$ref: "#/$defs/property"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
source: {
|
|
191
|
+
$ref: "#/$defs/sourceLocation"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
signature: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
parameters: {
|
|
199
|
+
type: "array",
|
|
200
|
+
items: {
|
|
201
|
+
$ref: "#/$defs/parameter"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
returns: {
|
|
205
|
+
$ref: "#/$defs/returns"
|
|
206
|
+
},
|
|
207
|
+
description: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Signature-level description"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
parameter: {
|
|
214
|
+
type: "object",
|
|
215
|
+
required: [
|
|
216
|
+
"name",
|
|
217
|
+
"required"
|
|
218
|
+
],
|
|
219
|
+
properties: {
|
|
220
|
+
name: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Parameter name"
|
|
223
|
+
},
|
|
224
|
+
required: {
|
|
225
|
+
type: "boolean",
|
|
226
|
+
description: "Whether the parameter is required"
|
|
227
|
+
},
|
|
228
|
+
description: {
|
|
229
|
+
type: "string",
|
|
230
|
+
description: "Parameter description"
|
|
231
|
+
},
|
|
232
|
+
schema: {
|
|
233
|
+
$ref: "#/$defs/schema"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
returns: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
schema: {
|
|
241
|
+
$ref: "#/$defs/schema"
|
|
242
|
+
},
|
|
243
|
+
description: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "Return value description"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
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
|
+
schema: {
|
|
274
|
+
anyOf: [
|
|
275
|
+
{
|
|
276
|
+
type: "boolean"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
$ref: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Reference to another type",
|
|
284
|
+
pattern: "^#/types/[A-Za-z0-9_.-]+$"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
required: [
|
|
288
|
+
"$ref"
|
|
289
|
+
],
|
|
290
|
+
additionalProperties: false
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: "object",
|
|
294
|
+
not: {
|
|
295
|
+
required: [
|
|
296
|
+
"$ref"
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
additionalProperties: true
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
sourceLocation: {
|
|
304
|
+
type: "object",
|
|
305
|
+
required: [
|
|
306
|
+
"file",
|
|
307
|
+
"line"
|
|
308
|
+
],
|
|
309
|
+
properties: {
|
|
310
|
+
file: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "Source file path"
|
|
313
|
+
},
|
|
314
|
+
line: {
|
|
315
|
+
type: "integer",
|
|
316
|
+
description: "Line number in source file",
|
|
317
|
+
minimum: 1
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/validate.ts
|
|
325
|
+
var ajv = new Ajv({
|
|
326
|
+
strict: false,
|
|
327
|
+
allErrors: true,
|
|
328
|
+
allowUnionTypes: true,
|
|
329
|
+
$data: true
|
|
330
|
+
});
|
|
331
|
+
addFormats(ajv);
|
|
332
|
+
var validate = ajv.compile(openpkg_schema_default);
|
|
333
|
+
function validateSpec(spec) {
|
|
334
|
+
const ok = validate(spec);
|
|
335
|
+
if (ok) {
|
|
336
|
+
return { ok: true };
|
|
337
|
+
}
|
|
338
|
+
const errors = (validate.errors ?? []).map((error) => ({
|
|
339
|
+
instancePath: error.instancePath ?? "",
|
|
340
|
+
message: error.message ?? "invalid",
|
|
341
|
+
keyword: error.keyword ?? "unknown"
|
|
342
|
+
}));
|
|
343
|
+
return {
|
|
344
|
+
ok: false,
|
|
345
|
+
errors
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function assertSpec(spec) {
|
|
349
|
+
const result = validateSpec(spec);
|
|
350
|
+
if (!result.ok) {
|
|
351
|
+
const details = result.errors.map((error) => `- ${error.instancePath || "/"} ${error.message}`).join(`
|
|
352
|
+
`);
|
|
353
|
+
throw new Error(`Invalid OpenPkg spec:
|
|
354
|
+
${details}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function getValidationErrors(spec) {
|
|
358
|
+
const result = validateSpec(spec);
|
|
359
|
+
return result.ok ? [] : result.errors;
|
|
360
|
+
}
|
|
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
|
+
export {
|
|
556
|
+
validateSpec,
|
|
557
|
+
normalize,
|
|
558
|
+
migrate_0_1_0__to__0_2_0 as migrate,
|
|
559
|
+
getValidationErrors,
|
|
560
|
+
diffSpec,
|
|
561
|
+
dereference,
|
|
562
|
+
assertSpec,
|
|
563
|
+
SCHEMA_VERSION,
|
|
564
|
+
SCHEMA_URL,
|
|
565
|
+
JSON_SCHEMA_DRAFT
|
|
566
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openpkg-ts/spec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared schema, validation, and diff utilities for OpenPkg specs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openpkg",
|
|
7
|
+
"json-schema",
|
|
8
|
+
"validation",
|
|
9
|
+
"typescript",
|
|
10
|
+
"spec"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/openpkg/openpkg#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/openpkg/openpkg.git",
|
|
16
|
+
"directory": "packages/spec"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Ryan Waits",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"schemas"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "bunup",
|
|
35
|
+
"dev": "bunup --watch",
|
|
36
|
+
"lint": "biome check src/",
|
|
37
|
+
"lint:fix": "biome check --write src/",
|
|
38
|
+
"format": "biome format --write src/"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ajv": "^8.17.1",
|
|
42
|
+
"ajv-formats": "^3.0.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/bun": "latest",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"bunup": "latest",
|
|
48
|
+
"typescript": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://unpkg.com/@openpkg-ts/spec/schemas/v0.1.0/openpkg.schema.json",
|
|
4
|
+
"title": "OpenPkg Specification",
|
|
5
|
+
"description": "Schema for OpenPkg specification files",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"openpkg",
|
|
9
|
+
"meta",
|
|
10
|
+
"exports"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"$schema": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Reference to the OpenPkg schema version",
|
|
16
|
+
"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)$"
|
|
17
|
+
},
|
|
18
|
+
"openpkg": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "OpenPkg specification version",
|
|
21
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
22
|
+
"const": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"meta": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"description": "Package metadata",
|
|
27
|
+
"required": [
|
|
28
|
+
"name"
|
|
29
|
+
],
|
|
30
|
+
"properties": {
|
|
31
|
+
"name": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Package name"
|
|
34
|
+
},
|
|
35
|
+
"version": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Package version"
|
|
38
|
+
},
|
|
39
|
+
"description": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Package description"
|
|
42
|
+
},
|
|
43
|
+
"license": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Package license"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Repository URL"
|
|
50
|
+
},
|
|
51
|
+
"ecosystem": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Package ecosystem",
|
|
54
|
+
"enum": [
|
|
55
|
+
"js/ts",
|
|
56
|
+
"python",
|
|
57
|
+
"rust",
|
|
58
|
+
"go",
|
|
59
|
+
"java"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"exports": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"description": "List of exported items",
|
|
67
|
+
"items": {
|
|
68
|
+
"$ref": "#/$defs/export"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"types": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"description": "List of type definitions",
|
|
74
|
+
"items": {
|
|
75
|
+
"$ref": "#/$defs/typeDef"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"$defs": {
|
|
80
|
+
"export": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": [
|
|
83
|
+
"id",
|
|
84
|
+
"name",
|
|
85
|
+
"kind"
|
|
86
|
+
],
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Unique identifier for the export"
|
|
91
|
+
},
|
|
92
|
+
"name": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Export name"
|
|
95
|
+
},
|
|
96
|
+
"kind": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "Kind of export",
|
|
99
|
+
"enum": [
|
|
100
|
+
"function",
|
|
101
|
+
"class",
|
|
102
|
+
"variable",
|
|
103
|
+
"interface",
|
|
104
|
+
"type",
|
|
105
|
+
"enum"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"description": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "JSDoc/TSDoc description"
|
|
111
|
+
},
|
|
112
|
+
"examples": {
|
|
113
|
+
"type": "array",
|
|
114
|
+
"description": "Usage examples from documentation",
|
|
115
|
+
"items": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"signatures": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"description": "Function/method signatures",
|
|
122
|
+
"items": {
|
|
123
|
+
"$ref": "#/$defs/signature"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"properties": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"description": "Class/interface properties",
|
|
129
|
+
"items": {
|
|
130
|
+
"$ref": "#/$defs/property"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"source": {
|
|
134
|
+
"$ref": "#/$defs/sourceLocation"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"typeDef": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"required": [
|
|
141
|
+
"id",
|
|
142
|
+
"name",
|
|
143
|
+
"kind"
|
|
144
|
+
],
|
|
145
|
+
"properties": {
|
|
146
|
+
"id": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"description": "Unique identifier for the type"
|
|
149
|
+
},
|
|
150
|
+
"name": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "Type name"
|
|
153
|
+
},
|
|
154
|
+
"kind": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Kind of type definition",
|
|
157
|
+
"enum": [
|
|
158
|
+
"interface",
|
|
159
|
+
"type",
|
|
160
|
+
"enum",
|
|
161
|
+
"class"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"description": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"description": "JSDoc/TSDoc description"
|
|
167
|
+
},
|
|
168
|
+
"schema": {
|
|
169
|
+
"$ref": "#/$defs/schema"
|
|
170
|
+
},
|
|
171
|
+
"type": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "Type expression for type aliases"
|
|
174
|
+
},
|
|
175
|
+
"properties": {
|
|
176
|
+
"type": "array",
|
|
177
|
+
"description": "Properties for interfaces/classes",
|
|
178
|
+
"items": {
|
|
179
|
+
"$ref": "#/$defs/property"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"source": {
|
|
183
|
+
"$ref": "#/$defs/sourceLocation"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"signature": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"properties": {
|
|
190
|
+
"parameters": {
|
|
191
|
+
"type": "array",
|
|
192
|
+
"items": {
|
|
193
|
+
"$ref": "#/$defs/parameter"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"returns": {
|
|
197
|
+
"$ref": "#/$defs/returns"
|
|
198
|
+
},
|
|
199
|
+
"description": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"description": "Signature-level description"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"parameter": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"required": [
|
|
208
|
+
"name",
|
|
209
|
+
"required"
|
|
210
|
+
],
|
|
211
|
+
"properties": {
|
|
212
|
+
"name": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Parameter name"
|
|
215
|
+
},
|
|
216
|
+
"required": {
|
|
217
|
+
"type": "boolean",
|
|
218
|
+
"description": "Whether the parameter is required"
|
|
219
|
+
},
|
|
220
|
+
"description": {
|
|
221
|
+
"type": "string",
|
|
222
|
+
"description": "Parameter description"
|
|
223
|
+
},
|
|
224
|
+
"schema": {
|
|
225
|
+
"$ref": "#/$defs/schema"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"returns": {
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"schema": {
|
|
233
|
+
"$ref": "#/$defs/schema"
|
|
234
|
+
},
|
|
235
|
+
"description": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "Return value description"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"property": {
|
|
242
|
+
"type": "object",
|
|
243
|
+
"required": [
|
|
244
|
+
"name",
|
|
245
|
+
"required"
|
|
246
|
+
],
|
|
247
|
+
"properties": {
|
|
248
|
+
"name": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"description": "Property name"
|
|
251
|
+
},
|
|
252
|
+
"required": {
|
|
253
|
+
"type": "boolean",
|
|
254
|
+
"description": "Whether the property is required"
|
|
255
|
+
},
|
|
256
|
+
"description": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"description": "Property description"
|
|
259
|
+
},
|
|
260
|
+
"schema": {
|
|
261
|
+
"$ref": "#/$defs/schema"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"schema": {
|
|
266
|
+
"anyOf": [
|
|
267
|
+
{
|
|
268
|
+
"type": "boolean"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"type": "object",
|
|
272
|
+
"properties": {
|
|
273
|
+
"$ref": {
|
|
274
|
+
"type": "string",
|
|
275
|
+
"description": "Reference to another type",
|
|
276
|
+
"pattern": "^#/types/[A-Za-z0-9_.-]+$"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"required": [
|
|
280
|
+
"$ref"
|
|
281
|
+
],
|
|
282
|
+
"additionalProperties": false
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"type": "object",
|
|
286
|
+
"not": {
|
|
287
|
+
"required": [
|
|
288
|
+
"$ref"
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
"additionalProperties": true
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
"sourceLocation": {
|
|
296
|
+
"type": "object",
|
|
297
|
+
"required": [
|
|
298
|
+
"file",
|
|
299
|
+
"line"
|
|
300
|
+
],
|
|
301
|
+
"properties": {
|
|
302
|
+
"file": {
|
|
303
|
+
"type": "string",
|
|
304
|
+
"description": "Source file path"
|
|
305
|
+
},
|
|
306
|
+
"line": {
|
|
307
|
+
"type": "integer",
|
|
308
|
+
"description": "Line number in source file",
|
|
309
|
+
"minimum": 1
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|