@levino/shipyard-docs 0.6.2 → 0.6.3

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.
@@ -0,0 +1,404 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { singleVersionSchema, versionConfigSchema } from './index'
3
+
4
+ describe('singleVersionSchema', () => {
5
+ describe('valid configurations', () => {
6
+ it('should accept minimal version config with only version field', () => {
7
+ const result = singleVersionSchema.safeParse({
8
+ version: 'v1.0',
9
+ })
10
+ expect(result.success).toBe(true)
11
+ expect(result.data).toEqual({ version: 'v1.0' })
12
+ })
13
+
14
+ it('should accept version with label', () => {
15
+ const result = singleVersionSchema.safeParse({
16
+ version: 'v2.0',
17
+ label: 'Version 2.0 (Latest)',
18
+ })
19
+ expect(result.success).toBe(true)
20
+ expect(result.data).toEqual({
21
+ version: 'v2.0',
22
+ label: 'Version 2.0 (Latest)',
23
+ })
24
+ })
25
+
26
+ it('should accept version with custom path', () => {
27
+ const result = singleVersionSchema.safeParse({
28
+ version: '2.0.0',
29
+ path: 'v2',
30
+ })
31
+ expect(result.success).toBe(true)
32
+ expect(result.data).toEqual({
33
+ version: '2.0.0',
34
+ path: 'v2',
35
+ })
36
+ })
37
+
38
+ it('should accept version with unreleased banner', () => {
39
+ const result = singleVersionSchema.safeParse({
40
+ version: 'next',
41
+ banner: 'unreleased',
42
+ })
43
+ expect(result.success).toBe(true)
44
+ expect(result.data).toEqual({
45
+ version: 'next',
46
+ banner: 'unreleased',
47
+ })
48
+ })
49
+
50
+ it('should accept version with unmaintained banner', () => {
51
+ const result = singleVersionSchema.safeParse({
52
+ version: 'v1.0',
53
+ banner: 'unmaintained',
54
+ })
55
+ expect(result.success).toBe(true)
56
+ expect(result.data).toEqual({
57
+ version: 'v1.0',
58
+ banner: 'unmaintained',
59
+ })
60
+ })
61
+
62
+ it('should accept full configuration with all fields', () => {
63
+ const config = {
64
+ version: 'v2.0',
65
+ label: 'Version 2.0',
66
+ path: 'v2',
67
+ banner: 'unreleased' as const,
68
+ }
69
+ const result = singleVersionSchema.safeParse(config)
70
+ expect(result.success).toBe(true)
71
+ expect(result.data).toEqual(config)
72
+ })
73
+
74
+ it('should accept various version string formats', () => {
75
+ const formats = ['v1', 'v1.0', 'v1.0.0', '1.0', 'latest', 'next', 'beta']
76
+ for (const version of formats) {
77
+ const result = singleVersionSchema.safeParse({ version })
78
+ expect(result.success).toBe(true)
79
+ expect(result.data?.version).toBe(version)
80
+ }
81
+ })
82
+ })
83
+
84
+ describe('invalid configurations', () => {
85
+ it('should reject empty object', () => {
86
+ const result = singleVersionSchema.safeParse({})
87
+ expect(result.success).toBe(false)
88
+ expect(result.error?.issues[0].path).toContain('version')
89
+ })
90
+
91
+ it('should reject missing version field', () => {
92
+ const result = singleVersionSchema.safeParse({
93
+ label: 'Some Label',
94
+ })
95
+ expect(result.success).toBe(false)
96
+ expect(result.error?.issues[0].code).toBe('invalid_type')
97
+ })
98
+
99
+ it('should reject non-string version', () => {
100
+ const result = singleVersionSchema.safeParse({
101
+ version: 123,
102
+ })
103
+ expect(result.success).toBe(false)
104
+ expect(result.error?.issues[0].path).toContain('version')
105
+ })
106
+
107
+ it('should reject invalid banner value', () => {
108
+ const result = singleVersionSchema.safeParse({
109
+ version: 'v1.0',
110
+ banner: 'deprecated',
111
+ })
112
+ expect(result.success).toBe(false)
113
+ expect(result.error?.issues[0].path).toContain('banner')
114
+ })
115
+
116
+ it('should reject non-string label', () => {
117
+ const result = singleVersionSchema.safeParse({
118
+ version: 'v1.0',
119
+ label: 123,
120
+ })
121
+ expect(result.success).toBe(false)
122
+ expect(result.error?.issues[0].path).toContain('label')
123
+ })
124
+
125
+ it('should reject non-string path', () => {
126
+ const result = singleVersionSchema.safeParse({
127
+ version: 'v1.0',
128
+ path: ['v1'],
129
+ })
130
+ expect(result.success).toBe(false)
131
+ expect(result.error?.issues[0].path).toContain('path')
132
+ })
133
+ })
134
+ })
135
+
136
+ describe('versionConfigSchema', () => {
137
+ describe('valid configurations', () => {
138
+ it('should accept minimal valid configuration', () => {
139
+ const config = {
140
+ current: 'v1.0',
141
+ available: [{ version: 'v1.0' }],
142
+ }
143
+ const result = versionConfigSchema.safeParse(config)
144
+ expect(result.success).toBe(true)
145
+ expect(result.data?.current).toBe('v1.0')
146
+ expect(result.data?.available).toHaveLength(1)
147
+ expect(result.data?.deprecated).toEqual([])
148
+ })
149
+
150
+ it('should accept configuration with multiple versions', () => {
151
+ const config = {
152
+ current: 'v2.0',
153
+ available: [
154
+ { version: 'v2.0', label: 'Latest' },
155
+ { version: 'v1.0', label: 'Legacy' },
156
+ ],
157
+ }
158
+ const result = versionConfigSchema.safeParse(config)
159
+ expect(result.success).toBe(true)
160
+ expect(result.data?.available).toHaveLength(2)
161
+ })
162
+
163
+ it('should accept configuration with deprecated versions', () => {
164
+ const config = {
165
+ current: 'v2.0',
166
+ available: [{ version: 'v2.0' }, { version: 'v1.0' }],
167
+ deprecated: ['v1.0'],
168
+ }
169
+ const result = versionConfigSchema.safeParse(config)
170
+ expect(result.success).toBe(true)
171
+ expect(result.data?.deprecated).toEqual(['v1.0'])
172
+ })
173
+
174
+ it('should accept configuration with stable version', () => {
175
+ const config = {
176
+ current: 'latest',
177
+ available: [{ version: 'latest', path: 'latest' }, { version: 'v2.0' }],
178
+ stable: 'v2.0',
179
+ }
180
+ const result = versionConfigSchema.safeParse(config)
181
+ expect(result.success).toBe(true)
182
+ expect(result.data?.stable).toBe('v2.0')
183
+ })
184
+
185
+ it('should accept full configuration with all fields', () => {
186
+ const config = {
187
+ current: 'v2.0',
188
+ available: [
189
+ { version: 'v2.0', label: 'Version 2.0 (Latest)' },
190
+ { version: 'v1.0', label: 'Version 1.0', banner: 'unmaintained' },
191
+ ],
192
+ deprecated: ['v1.0'],
193
+ stable: 'v2.0',
194
+ }
195
+ const result = versionConfigSchema.safeParse(config)
196
+ expect(result.success).toBe(true)
197
+ expect(result.data).toEqual({
198
+ current: 'v2.0',
199
+ available: [
200
+ { version: 'v2.0', label: 'Version 2.0 (Latest)' },
201
+ { version: 'v1.0', label: 'Version 1.0', banner: 'unmaintained' },
202
+ ],
203
+ deprecated: ['v1.0'],
204
+ stable: 'v2.0',
205
+ })
206
+ })
207
+
208
+ it('should default deprecated to empty array when not provided', () => {
209
+ const config = {
210
+ current: 'v1.0',
211
+ available: [{ version: 'v1.0' }],
212
+ }
213
+ const result = versionConfigSchema.safeParse(config)
214
+ expect(result.success).toBe(true)
215
+ expect(result.data?.deprecated).toEqual([])
216
+ })
217
+
218
+ it('should allow empty deprecated array explicitly', () => {
219
+ const config = {
220
+ current: 'v1.0',
221
+ available: [{ version: 'v1.0' }],
222
+ deprecated: [],
223
+ }
224
+ const result = versionConfigSchema.safeParse(config)
225
+ expect(result.success).toBe(true)
226
+ expect(result.data?.deprecated).toEqual([])
227
+ })
228
+ })
229
+
230
+ describe('invalid configurations', () => {
231
+ it('should reject empty object', () => {
232
+ const result = versionConfigSchema.safeParse({})
233
+ expect(result.success).toBe(false)
234
+ })
235
+
236
+ it('should reject missing current field', () => {
237
+ const result = versionConfigSchema.safeParse({
238
+ available: [{ version: 'v1.0' }],
239
+ })
240
+ expect(result.success).toBe(false)
241
+ expect(result.error?.issues[0].path).toContain('current')
242
+ })
243
+
244
+ it('should reject missing available field', () => {
245
+ const result = versionConfigSchema.safeParse({
246
+ current: 'v1.0',
247
+ })
248
+ expect(result.success).toBe(false)
249
+ expect(result.error?.issues[0].path).toContain('available')
250
+ })
251
+
252
+ it('should reject empty available array', () => {
253
+ const result = versionConfigSchema.safeParse({
254
+ current: 'v1.0',
255
+ available: [],
256
+ })
257
+ expect(result.success).toBe(false)
258
+ expect(result.error?.issues[0].code).toBe('too_small')
259
+ })
260
+
261
+ it('should reject non-string current', () => {
262
+ const result = versionConfigSchema.safeParse({
263
+ current: 123,
264
+ available: [{ version: 'v1.0' }],
265
+ })
266
+ expect(result.success).toBe(false)
267
+ expect(result.error?.issues[0].path).toContain('current')
268
+ })
269
+
270
+ it('should reject non-array available', () => {
271
+ const result = versionConfigSchema.safeParse({
272
+ current: 'v1.0',
273
+ available: { version: 'v1.0' },
274
+ })
275
+ expect(result.success).toBe(false)
276
+ expect(result.error?.issues[0].path).toContain('available')
277
+ })
278
+
279
+ it('should reject invalid version in available array', () => {
280
+ const result = versionConfigSchema.safeParse({
281
+ current: 'v1.0',
282
+ available: [{ notVersion: 'v1.0' }],
283
+ })
284
+ expect(result.success).toBe(false)
285
+ })
286
+
287
+ it('should reject non-array deprecated', () => {
288
+ const result = versionConfigSchema.safeParse({
289
+ current: 'v1.0',
290
+ available: [{ version: 'v1.0' }],
291
+ deprecated: 'v0.9',
292
+ })
293
+ expect(result.success).toBe(false)
294
+ expect(result.error?.issues[0].path).toContain('deprecated')
295
+ })
296
+
297
+ it('should reject non-string items in deprecated array', () => {
298
+ const result = versionConfigSchema.safeParse({
299
+ current: 'v1.0',
300
+ available: [{ version: 'v1.0' }],
301
+ deprecated: [123],
302
+ })
303
+ expect(result.success).toBe(false)
304
+ })
305
+
306
+ it('should reject non-string stable', () => {
307
+ const result = versionConfigSchema.safeParse({
308
+ current: 'v1.0',
309
+ available: [{ version: 'v1.0' }],
310
+ stable: 123,
311
+ })
312
+ expect(result.success).toBe(false)
313
+ expect(result.error?.issues[0].path).toContain('stable')
314
+ })
315
+ })
316
+
317
+ describe('edge cases', () => {
318
+ it('should accept current version not in available list', () => {
319
+ // This is a logical error but not a schema validation error
320
+ const config = {
321
+ current: 'v3.0',
322
+ available: [{ version: 'v2.0' }, { version: 'v1.0' }],
323
+ }
324
+ const result = versionConfigSchema.safeParse(config)
325
+ // Schema allows this - it's up to runtime logic to validate
326
+ expect(result.success).toBe(true)
327
+ })
328
+
329
+ it('should accept deprecated version not in available list', () => {
330
+ // This is a logical error but not a schema validation error
331
+ const config = {
332
+ current: 'v2.0',
333
+ available: [{ version: 'v2.0' }],
334
+ deprecated: ['v0.5'],
335
+ }
336
+ const result = versionConfigSchema.safeParse(config)
337
+ // Schema allows this - it's up to runtime logic to validate
338
+ expect(result.success).toBe(true)
339
+ })
340
+
341
+ it('should accept stable version not in available list', () => {
342
+ // This is a logical error but not a schema validation error
343
+ const config = {
344
+ current: 'v2.0',
345
+ available: [{ version: 'v2.0' }],
346
+ stable: 'v1.0',
347
+ }
348
+ const result = versionConfigSchema.safeParse(config)
349
+ // Schema allows this - it's up to runtime logic to validate
350
+ expect(result.success).toBe(true)
351
+ })
352
+
353
+ it('should handle very long version strings', () => {
354
+ const config = {
355
+ current: `v${'1'.repeat(100)}`,
356
+ available: [{ version: `v${'1'.repeat(100)}` }],
357
+ }
358
+ const result = versionConfigSchema.safeParse(config)
359
+ expect(result.success).toBe(true)
360
+ })
361
+
362
+ it('should handle special characters in version strings', () => {
363
+ const config = {
364
+ current: 'v2.0-beta.1+build.123',
365
+ available: [{ version: 'v2.0-beta.1+build.123' }],
366
+ }
367
+ const result = versionConfigSchema.safeParse(config)
368
+ expect(result.success).toBe(true)
369
+ })
370
+
371
+ it('should handle unicode in label', () => {
372
+ const config = {
373
+ current: 'v1.0',
374
+ available: [{ version: 'v1.0', label: '版本 1.0 🚀' }],
375
+ }
376
+ const result = versionConfigSchema.safeParse(config)
377
+ expect(result.success).toBe(true)
378
+ expect(result.data?.available[0].label).toBe('版本 1.0 🚀')
379
+ })
380
+
381
+ it('should handle many versions in available array', () => {
382
+ const versions = Array.from({ length: 50 }, (_, i) => ({
383
+ version: `v${50 - i}.0`,
384
+ }))
385
+ const config = {
386
+ current: 'v50.0',
387
+ available: versions,
388
+ }
389
+ const result = versionConfigSchema.safeParse(config)
390
+ expect(result.success).toBe(true)
391
+ expect(result.data?.available).toHaveLength(50)
392
+ })
393
+
394
+ it('should handle duplicate versions in available (schema allows, logic should check)', () => {
395
+ const config = {
396
+ current: 'v1.0',
397
+ available: [{ version: 'v1.0' }, { version: 'v1.0' }],
398
+ }
399
+ const result = versionConfigSchema.safeParse(config)
400
+ // Schema allows duplicates - runtime should handle
401
+ expect(result.success).toBe(true)
402
+ })
403
+ })
404
+ })
@@ -1,4 +1,9 @@
1
+ import type { SingleVersionConfig, VersionConfig } from './index'
2
+
1
3
  declare module 'virtual:shipyard-docs-configs' {
4
+ /**
5
+ * Registry of all docs configurations keyed by routeBasePath.
6
+ */
2
7
  export const docsConfigs: Record<
3
8
  string,
4
9
  {
@@ -7,6 +12,69 @@ declare module 'virtual:shipyard-docs-configs' {
7
12
  showLastUpdateAuthor: boolean
8
13
  routeBasePath: string
9
14
  collectionName: string
15
+ llmsTxtEnabled: boolean
16
+ versions?: VersionConfig
10
17
  }
11
18
  >
19
+
20
+ /**
21
+ * Get the version configuration for a specific docs instance.
22
+ * Returns undefined if versioning is not enabled for this routeBasePath.
23
+ *
24
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
25
+ * @returns The version configuration or undefined
26
+ */
27
+ export function getVersionConfig(
28
+ routeBasePath?: string,
29
+ ): VersionConfig | undefined
30
+
31
+ /**
32
+ * Get the current/default version for a docs instance.
33
+ * Returns undefined if versioning is not enabled.
34
+ *
35
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
36
+ * @returns The current version string or undefined
37
+ */
38
+ export function getCurrentVersion(routeBasePath?: string): string | undefined
39
+
40
+ /**
41
+ * Get all available versions for a docs instance.
42
+ * Returns an empty array if versioning is not enabled.
43
+ *
44
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
45
+ * @returns Array of available version configurations
46
+ */
47
+ export function getAvailableVersions(
48
+ routeBasePath?: string,
49
+ ): SingleVersionConfig[]
50
+
51
+ /**
52
+ * Check if a version is deprecated.
53
+ * Returns false if versioning is not enabled or version not found.
54
+ *
55
+ * @param version - The version string to check
56
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
57
+ * @returns True if the version is deprecated
58
+ */
59
+ export function isVersionDeprecated(
60
+ version: string,
61
+ routeBasePath?: string,
62
+ ): boolean
63
+
64
+ /**
65
+ * Get the stable version for a docs instance.
66
+ * Returns undefined if versioning is not enabled.
67
+ *
68
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
69
+ * @returns The stable version string or undefined
70
+ */
71
+ export function getStableVersion(routeBasePath?: string): string | undefined
72
+
73
+ /**
74
+ * Check if versioning is enabled for a docs instance.
75
+ *
76
+ * @param routeBasePath - The route base path of the docs instance (default: 'docs')
77
+ * @returns True if versioning is enabled
78
+ */
79
+ export function hasVersioning(routeBasePath?: string): boolean
12
80
  }