@oa-sdk/spec-bundler 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/LICENSE +201 -0
- package/README.md +240 -0
- package/dist/archiver.d.ts +22 -0
- package/dist/archiver.js +155 -0
- package/dist/category-parser.d.ts +15 -0
- package/dist/category-parser.js +115 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +760 -0
- package/dist/context-generator.d.ts +14 -0
- package/dist/context-generator.js +297 -0
- package/dist/freshness.d.ts +41 -0
- package/dist/freshness.js +365 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +705 -0
- package/dist/link-rewriter.d.ts +65 -0
- package/dist/link-rewriter.js +777 -0
- package/dist/resolver.d.ts +27 -0
- package/dist/resolver.js +276 -0
- package/dist/schema.d.ts +563 -0
- package/dist/schema.js +670 -0
- package/dist/section-slicer.d.ts +12 -0
- package/dist/section-slicer.js +114 -0
- package/dist/topic.d.ts +62 -0
- package/dist/topic.js +262 -0
- package/dist/tree-shaker.d.ts +40 -0
- package/dist/tree-shaker.js +523 -0
- package/dist/types.d.ts +369 -0
- package/dist/types.js +2 -0
- package/package.json +29 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
export const BUNDLE_MANIFEST_SCHEMA = {
|
|
2
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
$id: "https://spec-platform.org/schemas/bundle-manifest.v1.json",
|
|
4
|
+
title: "BundleManifest",
|
|
5
|
+
description: "Declarative configuration schema for @spec-platform/meta-bundler",
|
|
6
|
+
type: "object",
|
|
7
|
+
required: ["name", "version", "output", "targets"],
|
|
8
|
+
properties: {
|
|
9
|
+
$schema: { type: "string" },
|
|
10
|
+
name: { type: "string", description: "Identifier name for the bundle" },
|
|
11
|
+
version: { type: "string", description: "Semantic version of the bundle" },
|
|
12
|
+
description: { type: "string", description: "Human-readable description" },
|
|
13
|
+
output: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Output zip archive or directory path relative to manifest",
|
|
16
|
+
},
|
|
17
|
+
format: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: ["zip", "directory"],
|
|
20
|
+
default: "zip",
|
|
21
|
+
description: "Output bundle format",
|
|
22
|
+
},
|
|
23
|
+
targets: {
|
|
24
|
+
type: "array",
|
|
25
|
+
description: "List of target file mappings",
|
|
26
|
+
items: {
|
|
27
|
+
type: "object",
|
|
28
|
+
required: ["source", "dest"],
|
|
29
|
+
properties: {
|
|
30
|
+
source: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Source file path or glob pattern relative to manifest directory",
|
|
33
|
+
},
|
|
34
|
+
dest: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Destination directory or file path inside bundle",
|
|
37
|
+
},
|
|
38
|
+
exclude: {
|
|
39
|
+
type: "array",
|
|
40
|
+
items: { type: "string" },
|
|
41
|
+
description: "Target-specific exclusion glob patterns",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
exclude: {
|
|
48
|
+
type: "array",
|
|
49
|
+
items: { type: "string" },
|
|
50
|
+
description: "Global exclusion glob patterns",
|
|
51
|
+
},
|
|
52
|
+
deduplicate: {
|
|
53
|
+
type: "object",
|
|
54
|
+
description: "Deduplication and collision resolution rules",
|
|
55
|
+
properties: {
|
|
56
|
+
byPath: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: ["warn-overwrite", "overwrite", "error"],
|
|
59
|
+
default: "warn-overwrite",
|
|
60
|
+
description: "Collision handling strategy when two sources map to same destination",
|
|
61
|
+
},
|
|
62
|
+
byContentHash: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
default: true,
|
|
65
|
+
description: "Whether to report content hash duplicates",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
},
|
|
70
|
+
linkResolution: {
|
|
71
|
+
type: "object",
|
|
72
|
+
description: "Markdown link rewriting and resolution configuration",
|
|
73
|
+
properties: {
|
|
74
|
+
enabled: {
|
|
75
|
+
type: "boolean",
|
|
76
|
+
default: true,
|
|
77
|
+
description: "Whether automatic link resolution is enabled",
|
|
78
|
+
},
|
|
79
|
+
rewriteRelativeLinks: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
default: true,
|
|
82
|
+
description: "Whether to rewrite relative links to bundle coordinates",
|
|
83
|
+
},
|
|
84
|
+
normalizeFileSchemes: {
|
|
85
|
+
type: "boolean",
|
|
86
|
+
default: true,
|
|
87
|
+
description: "Whether to normalize file:/// URIs",
|
|
88
|
+
},
|
|
89
|
+
unbundledPolicy: {
|
|
90
|
+
type: "string",
|
|
91
|
+
enum: ["warn", "error", "preserve", "github-fallback"],
|
|
92
|
+
default: "warn",
|
|
93
|
+
description: "Handling strategy for links pointing to unbundled files",
|
|
94
|
+
},
|
|
95
|
+
gitRepositoryUrl: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Base remote GitHub repository URL for github-fallback policy",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
},
|
|
102
|
+
treeShake: {
|
|
103
|
+
type: "object",
|
|
104
|
+
description: "Declarative tree-shaking reachability configuration",
|
|
105
|
+
required: ["entrypoints"],
|
|
106
|
+
properties: {
|
|
107
|
+
enabled: {
|
|
108
|
+
type: "boolean",
|
|
109
|
+
default: false,
|
|
110
|
+
description: "Whether tree-shaking reachability pruning is enabled",
|
|
111
|
+
},
|
|
112
|
+
entrypoints: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: { type: "string" },
|
|
115
|
+
description: "Entrypoint files from which reachability traversal begins",
|
|
116
|
+
},
|
|
117
|
+
scope: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: { type: "string" },
|
|
120
|
+
description: "Boundary scope patterns allowed to be pulled into the bundle",
|
|
121
|
+
},
|
|
122
|
+
stopAt: {
|
|
123
|
+
type: "array",
|
|
124
|
+
items: { type: "string" },
|
|
125
|
+
description: "Terminal boundary patterns where traversal immediately halts",
|
|
126
|
+
},
|
|
127
|
+
maxDepth: {
|
|
128
|
+
type: "integer",
|
|
129
|
+
minimum: 1,
|
|
130
|
+
description: "Maximum traversal hop depth from entrypoints",
|
|
131
|
+
},
|
|
132
|
+
preserve: {
|
|
133
|
+
type: "array",
|
|
134
|
+
items: { type: "string" },
|
|
135
|
+
description: "Unconditionally preserved file patterns exempt from tree-shaking pruning",
|
|
136
|
+
},
|
|
137
|
+
reportEliminated: {
|
|
138
|
+
type: "boolean",
|
|
139
|
+
default: true,
|
|
140
|
+
description: "Whether to list eliminated files in output report",
|
|
141
|
+
},
|
|
142
|
+
traversalMode: {
|
|
143
|
+
type: "string",
|
|
144
|
+
enum: ["hybrid", "reachability", "explicit"],
|
|
145
|
+
default: "hybrid",
|
|
146
|
+
description: "Reachability traversal mode: hybrid (preserve targets), reachability, or explicit",
|
|
147
|
+
},
|
|
148
|
+
preserveTargets: {
|
|
149
|
+
type: "boolean",
|
|
150
|
+
default: true,
|
|
151
|
+
description: "Whether to unconditionally preserve all declared targets",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
},
|
|
156
|
+
topics: {
|
|
157
|
+
type: "object",
|
|
158
|
+
description: "Declarative named topic profiles for repeatable modular documentation bundling",
|
|
159
|
+
additionalProperties: {
|
|
160
|
+
type: "object",
|
|
161
|
+
required: ["targets"],
|
|
162
|
+
properties: {
|
|
163
|
+
phase: {
|
|
164
|
+
type: "string",
|
|
165
|
+
enum: ["develop", "fix", "test", "review"],
|
|
166
|
+
description: "Work activity lifecycle phase designated for this topic",
|
|
167
|
+
},
|
|
168
|
+
author: {
|
|
169
|
+
type: "string",
|
|
170
|
+
description: "Initial worker or author who established this topic definition",
|
|
171
|
+
},
|
|
172
|
+
description: { type: "string", description: "Human-readable description of the topic" },
|
|
173
|
+
output: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Output zip archive or directory path relative to manifest",
|
|
176
|
+
},
|
|
177
|
+
format: {
|
|
178
|
+
type: "string",
|
|
179
|
+
enum: ["zip", "directory"],
|
|
180
|
+
description: "Output bundle format",
|
|
181
|
+
},
|
|
182
|
+
targets: {
|
|
183
|
+
type: "array",
|
|
184
|
+
description: "List of target file mappings designated for this topic",
|
|
185
|
+
items: {
|
|
186
|
+
type: "object",
|
|
187
|
+
required: ["source", "dest"],
|
|
188
|
+
properties: {
|
|
189
|
+
source: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "Source file path or glob pattern relative to manifest directory",
|
|
192
|
+
},
|
|
193
|
+
dest: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Destination directory or file path inside bundle",
|
|
196
|
+
},
|
|
197
|
+
exclude: {
|
|
198
|
+
type: "array",
|
|
199
|
+
items: { type: "string" },
|
|
200
|
+
description: "Target-specific exclusion glob patterns",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
additionalProperties: false,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
exclude: {
|
|
207
|
+
type: "array",
|
|
208
|
+
items: { type: "string" },
|
|
209
|
+
description: "Topic-specific exclusion glob patterns",
|
|
210
|
+
},
|
|
211
|
+
deduplicate: {
|
|
212
|
+
type: "object",
|
|
213
|
+
description: "Deduplication and collision resolution rules",
|
|
214
|
+
properties: {
|
|
215
|
+
byPath: {
|
|
216
|
+
type: "string",
|
|
217
|
+
enum: ["warn-overwrite", "overwrite", "error"],
|
|
218
|
+
default: "warn-overwrite",
|
|
219
|
+
},
|
|
220
|
+
byContentHash: {
|
|
221
|
+
type: "boolean",
|
|
222
|
+
default: true,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
additionalProperties: false,
|
|
226
|
+
},
|
|
227
|
+
linkResolution: {
|
|
228
|
+
type: "object",
|
|
229
|
+
description: "Markdown link rewriting and resolution configuration",
|
|
230
|
+
properties: {
|
|
231
|
+
enabled: { type: "boolean", default: true },
|
|
232
|
+
rewriteRelativeLinks: { type: "boolean", default: true },
|
|
233
|
+
normalizeFileSchemes: { type: "boolean", default: true },
|
|
234
|
+
unbundledPolicy: {
|
|
235
|
+
type: "string",
|
|
236
|
+
enum: ["warn", "error", "preserve", "github-fallback"],
|
|
237
|
+
default: "warn",
|
|
238
|
+
},
|
|
239
|
+
gitRepositoryUrl: { type: "string" },
|
|
240
|
+
},
|
|
241
|
+
additionalProperties: false,
|
|
242
|
+
},
|
|
243
|
+
treeShake: {
|
|
244
|
+
type: "object",
|
|
245
|
+
description: "Topic-specific tree-shaking and boundary-scoped reachability configuration",
|
|
246
|
+
required: ["entrypoints"],
|
|
247
|
+
properties: {
|
|
248
|
+
enabled: { type: "boolean", default: true },
|
|
249
|
+
entrypoints: {
|
|
250
|
+
type: "array",
|
|
251
|
+
items: { type: "string" },
|
|
252
|
+
description: "Entrypoint files from which reachability traversal begins",
|
|
253
|
+
},
|
|
254
|
+
scope: {
|
|
255
|
+
type: "array",
|
|
256
|
+
items: { type: "string" },
|
|
257
|
+
description: "Boundary scope patterns allowed to be pulled into this topic",
|
|
258
|
+
},
|
|
259
|
+
stopAt: {
|
|
260
|
+
type: "array",
|
|
261
|
+
items: { type: "string" },
|
|
262
|
+
description: "Terminal boundary patterns where traversal immediately halts",
|
|
263
|
+
},
|
|
264
|
+
maxDepth: {
|
|
265
|
+
type: "integer",
|
|
266
|
+
minimum: 1,
|
|
267
|
+
description: "Maximum traversal hop depth from entrypoints",
|
|
268
|
+
},
|
|
269
|
+
preserve: {
|
|
270
|
+
type: "array",
|
|
271
|
+
items: { type: "string" },
|
|
272
|
+
description: "Unconditionally preserved file patterns exempt from tree-shaking pruning",
|
|
273
|
+
},
|
|
274
|
+
reportEliminated: {
|
|
275
|
+
type: "boolean",
|
|
276
|
+
default: true,
|
|
277
|
+
description: "Whether to list eliminated files in output report",
|
|
278
|
+
},
|
|
279
|
+
traversalMode: {
|
|
280
|
+
type: "string",
|
|
281
|
+
enum: ["hybrid", "reachability", "explicit"],
|
|
282
|
+
default: "hybrid",
|
|
283
|
+
},
|
|
284
|
+
preserveTargets: {
|
|
285
|
+
type: "boolean",
|
|
286
|
+
default: true,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
additionalProperties: false,
|
|
290
|
+
},
|
|
291
|
+
scope: {
|
|
292
|
+
type: "object",
|
|
293
|
+
description: "Declarative extraction scope and domain boundary configuration",
|
|
294
|
+
properties: {
|
|
295
|
+
entrypoints: {
|
|
296
|
+
type: "array",
|
|
297
|
+
items: { type: "string" },
|
|
298
|
+
description: "Entrypoint files from which extraction traversal begins",
|
|
299
|
+
},
|
|
300
|
+
include: {
|
|
301
|
+
type: "array",
|
|
302
|
+
items: { type: "string" },
|
|
303
|
+
description: "Allowed boundary patterns for documents pulled into this topic",
|
|
304
|
+
},
|
|
305
|
+
exclude: {
|
|
306
|
+
type: "array",
|
|
307
|
+
items: { type: "string" },
|
|
308
|
+
description: "Patterns strictly excluded from this topic",
|
|
309
|
+
},
|
|
310
|
+
stopAt: {
|
|
311
|
+
type: "array",
|
|
312
|
+
items: { type: "string" },
|
|
313
|
+
description: "Terminal boundary patterns where traversal immediately halts",
|
|
314
|
+
},
|
|
315
|
+
maxDepth: {
|
|
316
|
+
type: "integer",
|
|
317
|
+
minimum: 1,
|
|
318
|
+
description: "Maximum traversal hop depth from entrypoints",
|
|
319
|
+
},
|
|
320
|
+
sections: {
|
|
321
|
+
type: "array",
|
|
322
|
+
items: { type: "string" },
|
|
323
|
+
description: "Section/heading patterns to slice from multi-topic documents",
|
|
324
|
+
},
|
|
325
|
+
direction: {
|
|
326
|
+
type: "string",
|
|
327
|
+
enum: ["downstream", "upstream", "bidirectional"],
|
|
328
|
+
default: "downstream",
|
|
329
|
+
description: "Graph reachability traversal direction",
|
|
330
|
+
},
|
|
331
|
+
upstreamDepth: {
|
|
332
|
+
type: "integer",
|
|
333
|
+
minimum: 1,
|
|
334
|
+
default: 1,
|
|
335
|
+
description: "Maximum traversal depth when traversing upstream",
|
|
336
|
+
},
|
|
337
|
+
includeCategories: {
|
|
338
|
+
type: "array",
|
|
339
|
+
items: {
|
|
340
|
+
type: "string",
|
|
341
|
+
enum: ["feature", "mechanism", "error", "issue", "convention"],
|
|
342
|
+
},
|
|
343
|
+
description: "Document categories permitted in topic bundle",
|
|
344
|
+
},
|
|
345
|
+
excludeCategories: {
|
|
346
|
+
type: "array",
|
|
347
|
+
items: {
|
|
348
|
+
type: "string",
|
|
349
|
+
enum: ["feature", "mechanism", "error", "issue", "convention"],
|
|
350
|
+
},
|
|
351
|
+
description: "Document categories strictly pruned from topic bundle",
|
|
352
|
+
},
|
|
353
|
+
categoryFidelity: {
|
|
354
|
+
type: "object",
|
|
355
|
+
description: "Level of detail/fidelity per category or upstream contracts",
|
|
356
|
+
properties: {
|
|
357
|
+
feature: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
358
|
+
mechanism: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
359
|
+
error: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
360
|
+
issue: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
361
|
+
convention: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
362
|
+
upstream: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
363
|
+
default: { type: "string", enum: ["full", "contract-only", "summary"] },
|
|
364
|
+
},
|
|
365
|
+
additionalProperties: false,
|
|
366
|
+
},
|
|
367
|
+
traversalMode: {
|
|
368
|
+
type: "string",
|
|
369
|
+
enum: ["hybrid", "reachability", "explicit"],
|
|
370
|
+
default: "hybrid",
|
|
371
|
+
},
|
|
372
|
+
preserveTargets: {
|
|
373
|
+
type: "boolean",
|
|
374
|
+
default: true,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
additionalProperties: false,
|
|
378
|
+
},
|
|
379
|
+
context: {
|
|
380
|
+
type: "object",
|
|
381
|
+
description: "Ambient background context, metadata, prompt, and documentation",
|
|
382
|
+
properties: {
|
|
383
|
+
description: { type: "string", description: "Context description" },
|
|
384
|
+
template: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Custom CONTEXT.md template file path relative to manifest, or raw template string",
|
|
387
|
+
},
|
|
388
|
+
domain: { type: "string", description: "Target domain identifier" },
|
|
389
|
+
issue: { type: "string", description: "Associated issue/ticket ID" },
|
|
390
|
+
tags: {
|
|
391
|
+
type: "array",
|
|
392
|
+
items: { type: "string" },
|
|
393
|
+
description: "Category tags",
|
|
394
|
+
},
|
|
395
|
+
prompt: { type: "string", description: "Agent system prompt or developer instructions" },
|
|
396
|
+
persona: { type: "string", description: "Target persona or role" },
|
|
397
|
+
prerequisites: {
|
|
398
|
+
type: "array",
|
|
399
|
+
items: { type: "string" },
|
|
400
|
+
description: "Dependent topic names or prerequisites",
|
|
401
|
+
},
|
|
402
|
+
generateContextDoc: {
|
|
403
|
+
type: "boolean",
|
|
404
|
+
default: true,
|
|
405
|
+
description: "Whether to synthesize CONTEXT.md at bundle root",
|
|
406
|
+
},
|
|
407
|
+
metadata: {
|
|
408
|
+
type: "object",
|
|
409
|
+
description: "Arbitrary structured metadata",
|
|
410
|
+
},
|
|
411
|
+
docs: {
|
|
412
|
+
type: "array",
|
|
413
|
+
description: "Ambient background context documents",
|
|
414
|
+
items: {
|
|
415
|
+
type: "object",
|
|
416
|
+
required: ["source"],
|
|
417
|
+
properties: {
|
|
418
|
+
source: { type: "string", description: "Source path or glob pattern" },
|
|
419
|
+
dest: { type: "string", description: "Destination inside bundle (defaults to 'context/')" },
|
|
420
|
+
exclude: {
|
|
421
|
+
type: "array",
|
|
422
|
+
items: { type: "string" },
|
|
423
|
+
description: "Exclusion patterns",
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
additionalProperties: false,
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
additionalProperties: false,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
additionalProperties: false,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
lockfileMode: {
|
|
437
|
+
type: "string",
|
|
438
|
+
enum: ["consolidated", "split"],
|
|
439
|
+
default: "consolidated",
|
|
440
|
+
description: "Lockfile storage mode (consolidated into bundle.manifest.lock.json or split per topic)",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
additionalProperties: false,
|
|
444
|
+
};
|
|
445
|
+
/**
|
|
446
|
+
* Emits the JSON Schema 2020-12 as formatted JSON string.
|
|
447
|
+
*/
|
|
448
|
+
export function emitJsonSchema() {
|
|
449
|
+
return JSON.stringify(BUNDLE_MANIFEST_SCHEMA, null, 2);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Validates a manifest object against schema invariants and returns a list of human-readable errors.
|
|
453
|
+
*/
|
|
454
|
+
export function validateManifestSchema(raw) {
|
|
455
|
+
const errors = [];
|
|
456
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
457
|
+
errors.push("Manifest root must be a JSON object.");
|
|
458
|
+
return errors;
|
|
459
|
+
}
|
|
460
|
+
const knownTopLevel = new Set([
|
|
461
|
+
"$schema",
|
|
462
|
+
"name",
|
|
463
|
+
"version",
|
|
464
|
+
"description",
|
|
465
|
+
"output",
|
|
466
|
+
"format",
|
|
467
|
+
"phase",
|
|
468
|
+
"author",
|
|
469
|
+
"targets",
|
|
470
|
+
"exclude",
|
|
471
|
+
"deduplicate",
|
|
472
|
+
"linkResolution",
|
|
473
|
+
"treeShake",
|
|
474
|
+
"scope",
|
|
475
|
+
"context",
|
|
476
|
+
"topics",
|
|
477
|
+
"lockfileMode",
|
|
478
|
+
]);
|
|
479
|
+
if (raw.lockfileMode !== undefined &&
|
|
480
|
+
raw.lockfileMode !== "consolidated" &&
|
|
481
|
+
raw.lockfileMode !== "split") {
|
|
482
|
+
errors.push(`Invalid 'lockfileMode' '${raw.lockfileMode}'. Expected 'consolidated' or 'split'.`);
|
|
483
|
+
}
|
|
484
|
+
// Check unknown properties and typos
|
|
485
|
+
for (const key of Object.keys(raw)) {
|
|
486
|
+
if (!knownTopLevel.has(key)) {
|
|
487
|
+
if (key === "target") {
|
|
488
|
+
errors.push("Unknown property 'target'. Did you mean 'targets'?");
|
|
489
|
+
}
|
|
490
|
+
else if (key === "topic") {
|
|
491
|
+
errors.push("Unknown property 'topic'. Did you mean 'topics'?");
|
|
492
|
+
}
|
|
493
|
+
else if (key === "treesake" || key === "treeshake") {
|
|
494
|
+
errors.push(`Unknown property '${key}'. Did you mean 'treeShake'?`);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
errors.push(`Unknown top-level property '${key}'.`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Required fields
|
|
502
|
+
if (!raw.name || typeof raw.name !== "string") {
|
|
503
|
+
errors.push("Missing or invalid required field 'name' (must be a non-empty string).");
|
|
504
|
+
}
|
|
505
|
+
if (!raw.version || typeof raw.version !== "string") {
|
|
506
|
+
errors.push("Missing or invalid required field 'version' (must be a non-empty string).");
|
|
507
|
+
}
|
|
508
|
+
if (!raw.output || typeof raw.output !== "string") {
|
|
509
|
+
errors.push("Missing or invalid required field 'output' (must be a non-empty string).");
|
|
510
|
+
}
|
|
511
|
+
if (!raw.targets || !Array.isArray(raw.targets)) {
|
|
512
|
+
errors.push("Missing or invalid required field 'targets' (must be an array of target mappings).");
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
raw.targets.forEach((t, index) => {
|
|
516
|
+
if (!t || typeof t !== "object" || Array.isArray(t)) {
|
|
517
|
+
errors.push(`targets[${index}]: must be an object.`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (!t.source || typeof t.source !== "string") {
|
|
521
|
+
errors.push(`targets[${index}]: missing required 'source' path or glob pattern.`);
|
|
522
|
+
}
|
|
523
|
+
if (!t.dest || typeof t.dest !== "string") {
|
|
524
|
+
errors.push(`targets[${index}]: missing required 'dest' path.`);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
// Format validation
|
|
529
|
+
if (raw.format && raw.format !== "zip" && raw.format !== "directory") {
|
|
530
|
+
errors.push(`Invalid 'format' '${raw.format}'. Expected 'zip' or 'directory'.`);
|
|
531
|
+
}
|
|
532
|
+
// Work Phase and Author validation
|
|
533
|
+
if (raw.phase !== undefined && !["develop", "fix", "test", "review"].includes(raw.phase)) {
|
|
534
|
+
errors.push(`Invalid 'phase' '${raw.phase}'. Expected 'develop', 'fix', 'test', or 'review'.`);
|
|
535
|
+
}
|
|
536
|
+
if (raw.author !== undefined && typeof raw.author !== "string") {
|
|
537
|
+
errors.push("'author' must be a string.");
|
|
538
|
+
}
|
|
539
|
+
// Deduplicate validation
|
|
540
|
+
if (raw.deduplicate && typeof raw.deduplicate === "object") {
|
|
541
|
+
const validStrategies = ["warn-overwrite", "overwrite", "error"];
|
|
542
|
+
if (raw.deduplicate.byPath &&
|
|
543
|
+
!validStrategies.includes(raw.deduplicate.byPath)) {
|
|
544
|
+
errors.push(`Invalid deduplicate.byPath '${raw.deduplicate.byPath}'. Expected one of: ${validStrategies.join(", ")}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// LinkResolution validation
|
|
548
|
+
if (raw.linkResolution && typeof raw.linkResolution === "object") {
|
|
549
|
+
const validPolicies = ["warn", "error", "preserve", "github-fallback"];
|
|
550
|
+
if (raw.linkResolution.unbundledPolicy &&
|
|
551
|
+
!validPolicies.includes(raw.linkResolution.unbundledPolicy)) {
|
|
552
|
+
errors.push(`Invalid linkResolution.unbundledPolicy '${raw.linkResolution.unbundledPolicy}'. Expected one of: ${validPolicies.join(", ")}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// TreeShake validation
|
|
556
|
+
if (raw.treeShake && typeof raw.treeShake === "object") {
|
|
557
|
+
if (raw.treeShake.enabled !== false && !Array.isArray(raw.treeShake.entrypoints)) {
|
|
558
|
+
errors.push("treeShake.entrypoints must be an array of entrypoint file paths.");
|
|
559
|
+
}
|
|
560
|
+
if (raw.treeShake.maxDepth !== undefined &&
|
|
561
|
+
(typeof raw.treeShake.maxDepth !== "number" || raw.treeShake.maxDepth < 1 || !Number.isInteger(raw.treeShake.maxDepth))) {
|
|
562
|
+
errors.push("treeShake.maxDepth must be a positive integer.");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Topics validation
|
|
566
|
+
if (raw.topics !== undefined) {
|
|
567
|
+
if (!raw.topics || typeof raw.topics !== "object" || Array.isArray(raw.topics)) {
|
|
568
|
+
errors.push("Property 'topics' must be an object dictionary of topic configurations.");
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
for (const [topicName, topicConfig] of Object.entries(raw.topics)) {
|
|
572
|
+
if (!topicConfig || typeof topicConfig !== "object" || Array.isArray(topicConfig)) {
|
|
573
|
+
errors.push(`topics['${topicName}']: must be an object.`);
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const topicAny = topicConfig;
|
|
577
|
+
if (!topicAny.targets || !Array.isArray(topicAny.targets)) {
|
|
578
|
+
errors.push(`topics['${topicName}']: missing required field 'targets' (must be an array).`);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
topicAny.targets.forEach((t, index) => {
|
|
582
|
+
if (!t || typeof t !== "object" || Array.isArray(t)) {
|
|
583
|
+
errors.push(`topics['${topicName}'].targets[${index}]: must be an object.`);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (!t.source || typeof t.source !== "string") {
|
|
587
|
+
errors.push(`topics['${topicName}'].targets[${index}]: missing required 'source'.`);
|
|
588
|
+
}
|
|
589
|
+
if (!t.dest || typeof t.dest !== "string") {
|
|
590
|
+
errors.push(`topics['${topicName}'].targets[${index}]: missing required 'dest'.`);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (topicAny.format && topicAny.format !== "zip" && topicAny.format !== "directory") {
|
|
595
|
+
errors.push(`topics['${topicName}']: invalid 'format' '${topicAny.format}'. Expected 'zip' or 'directory'.`);
|
|
596
|
+
}
|
|
597
|
+
if (topicAny.phase !== undefined &&
|
|
598
|
+
!["develop", "fix", "test", "review"].includes(topicAny.phase)) {
|
|
599
|
+
errors.push(`topics['${topicName}']: invalid 'phase' '${topicAny.phase}'. Expected 'develop', 'fix', 'test', or 'review'.`);
|
|
600
|
+
}
|
|
601
|
+
if (topicAny.author !== undefined && typeof topicAny.author !== "string") {
|
|
602
|
+
errors.push(`topics['${topicName}']: 'author' must be a string.`);
|
|
603
|
+
}
|
|
604
|
+
if (topicAny.treeShake && typeof topicAny.treeShake === "object") {
|
|
605
|
+
if (topicAny.treeShake.enabled !== false && !Array.isArray(topicAny.treeShake.entrypoints)) {
|
|
606
|
+
errors.push(`topics['${topicName}'].treeShake.entrypoints must be an array of entrypoint file paths.`);
|
|
607
|
+
}
|
|
608
|
+
if (topicAny.treeShake.maxDepth !== undefined &&
|
|
609
|
+
(typeof topicAny.treeShake.maxDepth !== "number" || topicAny.treeShake.maxDepth < 1 || !Number.isInteger(topicAny.treeShake.maxDepth))) {
|
|
610
|
+
errors.push(`topics['${topicName}'].treeShake.maxDepth must be a positive integer.`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (topicAny.scope && typeof topicAny.scope === "object") {
|
|
614
|
+
if (topicAny.scope.entrypoints && !Array.isArray(topicAny.scope.entrypoints)) {
|
|
615
|
+
errors.push(`topics['${topicName}'].scope.entrypoints must be an array.`);
|
|
616
|
+
}
|
|
617
|
+
if (topicAny.scope.include && !Array.isArray(topicAny.scope.include)) {
|
|
618
|
+
errors.push(`topics['${topicName}'].scope.include must be an array of glob strings.`);
|
|
619
|
+
}
|
|
620
|
+
if (topicAny.scope.exclude && !Array.isArray(topicAny.scope.exclude)) {
|
|
621
|
+
errors.push(`topics['${topicName}'].scope.exclude must be an array of glob strings.`);
|
|
622
|
+
}
|
|
623
|
+
if (topicAny.scope.stopAt && !Array.isArray(topicAny.scope.stopAt)) {
|
|
624
|
+
errors.push(`topics['${topicName}'].scope.stopAt must be an array of glob strings.`);
|
|
625
|
+
}
|
|
626
|
+
if (topicAny.scope.maxDepth !== undefined &&
|
|
627
|
+
(typeof topicAny.scope.maxDepth !== "number" || topicAny.scope.maxDepth < 1 || !Number.isInteger(topicAny.scope.maxDepth))) {
|
|
628
|
+
errors.push(`topics['${topicName}'].scope.maxDepth must be a positive integer.`);
|
|
629
|
+
}
|
|
630
|
+
if (topicAny.scope.sections && !Array.isArray(topicAny.scope.sections)) {
|
|
631
|
+
errors.push(`topics['${topicName}'].scope.sections must be an array of section heading strings.`);
|
|
632
|
+
}
|
|
633
|
+
if (topicAny.scope.direction !== undefined &&
|
|
634
|
+
!["downstream", "upstream", "bidirectional"].includes(topicAny.scope.direction)) {
|
|
635
|
+
errors.push(`topics['${topicName}'].scope.direction must be 'downstream', 'upstream', or 'bidirectional'.`);
|
|
636
|
+
}
|
|
637
|
+
if (topicAny.scope.upstreamDepth !== undefined &&
|
|
638
|
+
(typeof topicAny.scope.upstreamDepth !== "number" || topicAny.scope.upstreamDepth < 1 || !Number.isInteger(topicAny.scope.upstreamDepth))) {
|
|
639
|
+
errors.push(`topics['${topicName}'].scope.upstreamDepth must be a positive integer.`);
|
|
640
|
+
}
|
|
641
|
+
if (topicAny.scope.includeCategories && !Array.isArray(topicAny.scope.includeCategories)) {
|
|
642
|
+
errors.push(`topics['${topicName}'].scope.includeCategories must be an array of category names.`);
|
|
643
|
+
}
|
|
644
|
+
if (topicAny.scope.excludeCategories && !Array.isArray(topicAny.scope.excludeCategories)) {
|
|
645
|
+
errors.push(`topics['${topicName}'].scope.excludeCategories must be an array of category names.`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (topicAny.context && typeof topicAny.context === "object") {
|
|
649
|
+
if (topicAny.context.tags && !Array.isArray(topicAny.context.tags)) {
|
|
650
|
+
errors.push(`topics['${topicName}'].context.tags must be an array of strings.`);
|
|
651
|
+
}
|
|
652
|
+
if (topicAny.context.docs !== undefined) {
|
|
653
|
+
if (!Array.isArray(topicAny.context.docs)) {
|
|
654
|
+
errors.push(`topics['${topicName}'].context.docs must be an array of target mappings.`);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
topicAny.context.docs.forEach((d, dIdx) => {
|
|
658
|
+
if (!d || typeof d !== "object" || !d.source || typeof d.source !== "string") {
|
|
659
|
+
errors.push(`topics['${topicName}'].context.docs[${dIdx}] must have a valid 'source' path.`);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return errors;
|
|
669
|
+
}
|
|
670
|
+
//# sourceMappingURL=schema.js.map
|