@vltpkg/vsr 0.0.0-27 → 0.0.0-28

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.
Files changed (80) hide show
  1. package/DEPLOY.md +163 -0
  2. package/LICENSE +114 -10
  3. package/config.ts +221 -0
  4. package/dist/README.md +1 -1
  5. package/dist/bin/vsr.js +8 -6
  6. package/dist/index.js +3 -6
  7. package/dist/index.js.map +2 -2
  8. package/drizzle.config.js +40 -0
  9. package/info/COMPARISONS.md +37 -0
  10. package/info/CONFIGURATION.md +143 -0
  11. package/info/CONTRIBUTING.md +32 -0
  12. package/info/DATABASE_SETUP.md +108 -0
  13. package/info/GRANULAR_ACCESS_TOKENS.md +160 -0
  14. package/info/PROJECT_STRUCTURE.md +291 -0
  15. package/info/ROADMAP.md +27 -0
  16. package/info/SUPPORT.md +39 -0
  17. package/info/TESTING.md +301 -0
  18. package/info/USER_SUPPORT.md +31 -0
  19. package/package.json +49 -6
  20. package/scripts/build-assets.js +31 -0
  21. package/scripts/build-bin.js +63 -0
  22. package/src/assets/public/images/bg.png +0 -0
  23. package/src/assets/public/images/clients/logo-bun.png +0 -0
  24. package/src/assets/public/images/clients/logo-deno.png +0 -0
  25. package/src/assets/public/images/clients/logo-npm.png +0 -0
  26. package/src/assets/public/images/clients/logo-pnpm.png +0 -0
  27. package/src/assets/public/images/clients/logo-vlt.png +0 -0
  28. package/src/assets/public/images/clients/logo-yarn.png +0 -0
  29. package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
  30. package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
  31. package/src/assets/public/images/favicon/favicon.ico +0 -0
  32. package/src/assets/public/images/favicon/favicon.svg +3 -0
  33. package/src/assets/public/images/favicon/site.webmanifest +21 -0
  34. package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
  35. package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
  36. package/src/assets/public/styles/styles.css +231 -0
  37. package/src/bin/demo/package.json +6 -0
  38. package/src/bin/demo/vlt.json +1 -0
  39. package/src/bin/vsr.ts +496 -0
  40. package/src/db/client.ts +590 -0
  41. package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
  42. package/src/db/migrations/0000_initial.sql +29 -0
  43. package/src/db/migrations/0001_uuid_validation.sql +35 -0
  44. package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
  45. package/src/db/migrations/drop.sql +3 -0
  46. package/src/db/migrations/meta/0000_snapshot.json +104 -0
  47. package/src/db/migrations/meta/0001_snapshot.json +155 -0
  48. package/src/db/migrations/meta/_journal.json +20 -0
  49. package/src/db/schema.ts +43 -0
  50. package/src/index.ts +434 -0
  51. package/src/middleware/config.ts +79 -0
  52. package/src/middleware/telemetry.ts +43 -0
  53. package/src/queue/index.ts +97 -0
  54. package/src/routes/access.ts +852 -0
  55. package/src/routes/docs.ts +63 -0
  56. package/src/routes/misc.ts +469 -0
  57. package/src/routes/packages.ts +2823 -0
  58. package/src/routes/ping.ts +39 -0
  59. package/src/routes/search.ts +131 -0
  60. package/src/routes/static.ts +74 -0
  61. package/src/routes/tokens.ts +259 -0
  62. package/src/routes/users.ts +68 -0
  63. package/src/utils/auth.ts +202 -0
  64. package/src/utils/cache.ts +587 -0
  65. package/src/utils/config.ts +50 -0
  66. package/src/utils/database.ts +69 -0
  67. package/src/utils/docs.ts +146 -0
  68. package/src/utils/packages.ts +453 -0
  69. package/src/utils/response.ts +125 -0
  70. package/src/utils/routes.ts +64 -0
  71. package/src/utils/spa.ts +52 -0
  72. package/src/utils/tracing.ts +52 -0
  73. package/src/utils/upstream.ts +172 -0
  74. package/tsconfig.json +16 -0
  75. package/tsconfig.worker.json +3 -0
  76. package/typedoc.mjs +2 -0
  77. package/types.ts +598 -0
  78. package/vitest.config.ts +25 -0
  79. package/vlt.json.example +56 -0
  80. package/wrangler.json +65 -0
@@ -0,0 +1,852 @@
1
+ import { getAuthedUser } from '../utils/auth.ts'
2
+ import { createRoute, z } from '@hono/zod-openapi'
3
+ import type {
4
+ HonoContext,
5
+ AccessResponse,
6
+ AuthUser,
7
+ } from '../../types.ts'
8
+
9
+ interface PackageAccessEntry {
10
+ name: string
11
+ collaborators: Record<string, 'read-only' | 'read-write'>
12
+ }
13
+
14
+ interface PackageListResponse {
15
+ packages: PackageAccessEntry[]
16
+ total: number
17
+ }
18
+
19
+ // Define interfaces for request bodies to ensure type safety
20
+ interface SetAccessRequestBody {
21
+ collaborators?: Record<string, 'read-only' | 'read-write'>
22
+ }
23
+
24
+ interface GrantAccessRequestBody {
25
+ permission: 'read-only' | 'read-write'
26
+ }
27
+
28
+ /**
29
+ * Lists all packages the authenticated user has access to
30
+ */
31
+ export async function listPackagesAccess(c: HonoContext) {
32
+ try {
33
+ const user = await getAuthedUser({ c })
34
+ if (!user?.uuid) {
35
+ return c.json({ error: 'Authentication required' }, 401)
36
+ }
37
+
38
+ // For now, return empty list - this would need to be implemented
39
+ // based on your specific access control requirements
40
+ const response: PackageListResponse = {
41
+ packages: [],
42
+ total: 0,
43
+ }
44
+
45
+ return c.json(response)
46
+ } catch (_error) {
47
+ // Hono middleware will log access errors
48
+ return c.json({ error: 'Failed to list packages' }, 500)
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Gets the access status for a specific package
54
+ */
55
+ export async function getPackageAccessStatus(c: HonoContext) {
56
+ try {
57
+ const { scope, pkg } = c.req.param()
58
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
59
+
60
+ if (!packageName) {
61
+ return c.json({ error: 'Package name required' }, 400)
62
+ }
63
+
64
+ const user = await getAuthedUser({ c })
65
+ if (!user?.uuid) {
66
+ return c.json({ error: 'Authentication required' }, 401)
67
+ }
68
+
69
+ // Check if user has access to this package
70
+ const hasAccess = await checkPackageAccess(c, packageName, user)
71
+
72
+ if (!hasAccess) {
73
+ return c.json({ error: 'Access denied' }, 403)
74
+ }
75
+
76
+ // Return package access information
77
+ const response: AccessResponse = {
78
+ name: packageName,
79
+ collaborators: {
80
+ [user.uuid]: 'read-write', // Default the owner to read-write
81
+ },
82
+ }
83
+
84
+ return c.json(response)
85
+ } catch (_error) {
86
+ // Hono middleware will log package access errors
87
+ return c.json({ error: 'Failed to get package access' }, 500)
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Sets the access status for a specific package
93
+ */
94
+ export async function setPackageAccessStatus(c: HonoContext) {
95
+ try {
96
+ const { scope, pkg } = c.req.param()
97
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
98
+
99
+ if (!packageName) {
100
+ return c.json({ error: 'Package name required' }, 400)
101
+ }
102
+
103
+ const user = await getAuthedUser({ c })
104
+ if (!user?.uuid) {
105
+ return c.json({ error: 'Authentication required' }, 401)
106
+ }
107
+
108
+ const body =
109
+ (await c.req.json()) as unknown as SetAccessRequestBody
110
+
111
+ // Check if user has admin access to this package
112
+ const hasAdminAccess = await checkPackageAdminAccess(
113
+ c,
114
+ packageName,
115
+ user,
116
+ )
117
+
118
+ if (!hasAdminAccess) {
119
+ return c.json({ error: 'Admin access required' }, 403)
120
+ }
121
+
122
+ // Update package access (this would need to be implemented in your database)
123
+ // For now, just return the requested access
124
+ const response: AccessResponse = {
125
+ name: packageName,
126
+ collaborators: body.collaborators ?? {},
127
+ }
128
+
129
+ return c.json(response)
130
+ } catch (_error) {
131
+ // Hono middleware will log package access setting errors
132
+ return c.json({ error: 'Failed to set package access' }, 500)
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Grants access to a user for a specific package
138
+ */
139
+ export async function grantPackageAccess(c: HonoContext) {
140
+ try {
141
+ const { scope, pkg, username } = c.req.param()
142
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
143
+
144
+ if (!packageName || !username) {
145
+ return c.json(
146
+ { error: 'Package name and username required' },
147
+ 400,
148
+ )
149
+ }
150
+
151
+ const user = await getAuthedUser({ c })
152
+ if (!user?.uuid) {
153
+ return c.json({ error: 'Authentication required' }, 401)
154
+ }
155
+
156
+ const body =
157
+ (await c.req.json()) as unknown as GrantAccessRequestBody
158
+
159
+ // Check if user has admin access to this package
160
+ const hasAdminAccess = await checkPackageAdminAccess(
161
+ c,
162
+ packageName,
163
+ user,
164
+ )
165
+
166
+ if (!hasAdminAccess) {
167
+ return c.json({ error: 'Admin access required' }, 403)
168
+ }
169
+
170
+ // Grant access (this would need to be implemented in your database)
171
+ // For now, just return success
172
+ const response: AccessResponse = {
173
+ name: packageName,
174
+ collaborators: {
175
+ [user.uuid]: 'read-write',
176
+ [username]: body.permission,
177
+ },
178
+ }
179
+
180
+ return c.json(response)
181
+ } catch (_error) {
182
+ // Hono middleware will log package access granting errors
183
+ return c.json({ error: 'Failed to grant package access' }, 500)
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Revokes access from a user for a specific package
189
+ */
190
+ export async function revokePackageAccess(c: HonoContext) {
191
+ try {
192
+ const { scope, pkg, username } = c.req.param()
193
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
194
+
195
+ if (!packageName || !username) {
196
+ return c.json(
197
+ { error: 'Package name and username required' },
198
+ 400,
199
+ )
200
+ }
201
+
202
+ const user = await getAuthedUser({ c })
203
+ if (!user?.uuid) {
204
+ return c.json({ error: 'Authentication required' }, 401)
205
+ }
206
+
207
+ // Check if user has admin access to this package
208
+ const hasAdminAccess = await checkPackageAdminAccess(
209
+ c,
210
+ packageName,
211
+ user,
212
+ )
213
+
214
+ if (!hasAdminAccess) {
215
+ return c.json({ error: 'Admin access required' }, 403)
216
+ }
217
+
218
+ // Prevent users from revoking their own access
219
+ if (username === user.uuid) {
220
+ return c.json({ error: 'Cannot revoke your own access' }, 400)
221
+ }
222
+
223
+ // Revoke access (this would need to be implemented in your database)
224
+ // For now, just return success
225
+ const response: AccessResponse = {
226
+ name: packageName,
227
+ collaborators: {
228
+ [user.uuid]: 'read-write',
229
+ // username removed from collaborators
230
+ },
231
+ }
232
+
233
+ return c.json(response)
234
+ } catch (_error) {
235
+ // Hono middleware will log package access revocation errors
236
+ return c.json({ error: 'Failed to revoke package access' }, 500)
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Helper function to check if user has access to a package
242
+ */
243
+ async function checkPackageAccess(
244
+ _c: HonoContext,
245
+ packageName: string,
246
+ user: AuthUser,
247
+ ): Promise<boolean> {
248
+ if (!user.scope || !user.uuid) {
249
+ return false
250
+ }
251
+
252
+ // Check if user has global access or specific package access
253
+ for (const scope of user.scope) {
254
+ if (scope.types.pkg) {
255
+ // Check for global access
256
+ if (scope.values.includes('*') && scope.types.pkg.read) {
257
+ return true
258
+ }
259
+
260
+ // Check for specific package access
261
+ if (
262
+ scope.values.includes(packageName) &&
263
+ scope.types.pkg.read
264
+ ) {
265
+ return true
266
+ }
267
+ }
268
+ }
269
+
270
+ return false
271
+ }
272
+
273
+ /**
274
+ * Helper function to check if user has admin access to a package
275
+ */
276
+ async function checkPackageAdminAccess(
277
+ _c: HonoContext,
278
+ packageName: string,
279
+ user: AuthUser,
280
+ ): Promise<boolean> {
281
+ if (!user.scope || !user.uuid) {
282
+ return false
283
+ }
284
+
285
+ // Check if user has global write access or specific package write access
286
+ for (const scope of user.scope) {
287
+ if (scope.types.pkg) {
288
+ // Check for global write access
289
+ if (scope.values.includes('*') && scope.types.pkg.write) {
290
+ return true
291
+ }
292
+
293
+ // Check for specific package write access
294
+ if (
295
+ scope.values.includes(packageName) &&
296
+ scope.types.pkg.write
297
+ ) {
298
+ return true
299
+ }
300
+ }
301
+ }
302
+
303
+ return false
304
+ }
305
+
306
+ // Route definitions for OpenAPI documentation
307
+ export const getPackageAccessRoute = createRoute({
308
+ method: 'get',
309
+ path: '/-/package/{pkg}/access',
310
+ tags: ['Access Control'],
311
+ summary: 'Get Package Access',
312
+ description: `Get access control information for a package`,
313
+ request: {
314
+ params: z.object({
315
+ pkg: z.string(),
316
+ }),
317
+ },
318
+ responses: {
319
+ 200: {
320
+ content: {
321
+ 'application/json': {
322
+ schema: z.object({
323
+ collaborators: z.record(
324
+ z.enum(['read-only', 'read-write']),
325
+ ),
326
+ }),
327
+ },
328
+ },
329
+ description: 'Package access information',
330
+ },
331
+ 401: {
332
+ content: {
333
+ 'application/json': {
334
+ schema: z.object({
335
+ error: z.string(),
336
+ }),
337
+ },
338
+ },
339
+ description: 'Authentication required',
340
+ },
341
+ 403: {
342
+ content: {
343
+ 'application/json': {
344
+ schema: z.object({
345
+ error: z.string(),
346
+ }),
347
+ },
348
+ },
349
+ description: 'Insufficient permissions',
350
+ },
351
+ 404: {
352
+ content: {
353
+ 'application/json': {
354
+ schema: z.object({
355
+ error: z.string(),
356
+ }),
357
+ },
358
+ },
359
+ description: 'Package not found',
360
+ },
361
+ },
362
+ })
363
+
364
+ export const setPackageAccessRoute = createRoute({
365
+ method: 'post',
366
+ path: '/-/package/{pkg}/access',
367
+ tags: ['Access Control'],
368
+ summary: 'Set Package Access',
369
+ description: `Set access control information for a package`,
370
+ request: {
371
+ params: z.object({
372
+ pkg: z.string(),
373
+ }),
374
+ body: {
375
+ content: {
376
+ 'application/json': {
377
+ schema: z.object({
378
+ collaborators: z
379
+ .record(z.enum(['read-only', 'read-write']))
380
+ .optional(),
381
+ }),
382
+ },
383
+ },
384
+ },
385
+ },
386
+ responses: {
387
+ 200: {
388
+ content: {
389
+ 'application/json': {
390
+ schema: z.object({
391
+ collaborators: z.record(
392
+ z.enum(['read-only', 'read-write']),
393
+ ),
394
+ }),
395
+ },
396
+ },
397
+ description: 'Package access updated successfully',
398
+ },
399
+ 401: {
400
+ content: {
401
+ 'application/json': {
402
+ schema: z.object({
403
+ error: z.string(),
404
+ }),
405
+ },
406
+ },
407
+ description: 'Authentication required',
408
+ },
409
+ 403: {
410
+ content: {
411
+ 'application/json': {
412
+ schema: z.object({
413
+ error: z.string(),
414
+ }),
415
+ },
416
+ },
417
+ description: 'Insufficient permissions',
418
+ },
419
+ 404: {
420
+ content: {
421
+ 'application/json': {
422
+ schema: z.object({
423
+ error: z.string(),
424
+ }),
425
+ },
426
+ },
427
+ description: 'Package not found',
428
+ },
429
+ },
430
+ })
431
+
432
+ export const getScopedPackageAccessRoute = createRoute({
433
+ method: 'get',
434
+ path: '/-/package/{scope}%2f{pkg}/access',
435
+ tags: ['Access Control'],
436
+ summary: 'Get Scoped Package Access',
437
+ description: `Get access control information for a scoped package`,
438
+ request: {
439
+ params: z.object({
440
+ scope: z.string(),
441
+ pkg: z.string(),
442
+ }),
443
+ },
444
+ responses: {
445
+ 200: {
446
+ content: {
447
+ 'application/json': {
448
+ schema: z.object({
449
+ collaborators: z.record(
450
+ z.enum(['read-only', 'read-write']),
451
+ ),
452
+ }),
453
+ },
454
+ },
455
+ description: 'Package access information',
456
+ },
457
+ 401: {
458
+ content: {
459
+ 'application/json': {
460
+ schema: z.object({
461
+ error: z.string(),
462
+ }),
463
+ },
464
+ },
465
+ description: 'Authentication required',
466
+ },
467
+ 403: {
468
+ content: {
469
+ 'application/json': {
470
+ schema: z.object({
471
+ error: z.string(),
472
+ }),
473
+ },
474
+ },
475
+ description: 'Insufficient permissions',
476
+ },
477
+ 404: {
478
+ content: {
479
+ 'application/json': {
480
+ schema: z.object({
481
+ error: z.string(),
482
+ }),
483
+ },
484
+ },
485
+ description: 'Package not found',
486
+ },
487
+ },
488
+ })
489
+
490
+ export const setScopedPackageAccessRoute = createRoute({
491
+ method: 'post',
492
+ path: '/-/package/{scope}%2f{pkg}/access',
493
+ tags: ['Access Control'],
494
+ summary: 'Set Scoped Package Access',
495
+ description: `Set access control information for a scoped package`,
496
+ request: {
497
+ params: z.object({
498
+ scope: z.string(),
499
+ pkg: z.string(),
500
+ }),
501
+ body: {
502
+ content: {
503
+ 'application/json': {
504
+ schema: z.object({
505
+ collaborators: z
506
+ .record(z.enum(['read-only', 'read-write']))
507
+ .optional(),
508
+ }),
509
+ },
510
+ },
511
+ },
512
+ },
513
+ responses: {
514
+ 200: {
515
+ content: {
516
+ 'application/json': {
517
+ schema: z.object({
518
+ collaborators: z.record(
519
+ z.enum(['read-only', 'read-write']),
520
+ ),
521
+ }),
522
+ },
523
+ },
524
+ description: 'Package access updated successfully',
525
+ },
526
+ 401: {
527
+ content: {
528
+ 'application/json': {
529
+ schema: z.object({
530
+ error: z.string(),
531
+ }),
532
+ },
533
+ },
534
+ description: 'Authentication required',
535
+ },
536
+ 403: {
537
+ content: {
538
+ 'application/json': {
539
+ schema: z.object({
540
+ error: z.string(),
541
+ }),
542
+ },
543
+ },
544
+ description: 'Insufficient permissions',
545
+ },
546
+ 404: {
547
+ content: {
548
+ 'application/json': {
549
+ schema: z.object({
550
+ error: z.string(),
551
+ }),
552
+ },
553
+ },
554
+ description: 'Package not found',
555
+ },
556
+ },
557
+ })
558
+
559
+ export const listPackagesAccessRoute = createRoute({
560
+ method: 'get',
561
+ path: '/-/package/list',
562
+ tags: ['Access Control'],
563
+ summary: 'List Package Access',
564
+ description: `List all packages the authenticated user has access to`,
565
+ request: {},
566
+ responses: {
567
+ 200: {
568
+ content: {
569
+ 'application/json': {
570
+ schema: z.object({
571
+ packages: z.array(
572
+ z.object({
573
+ name: z.string(),
574
+ collaborators: z.record(
575
+ z.enum(['read-only', 'read-write']),
576
+ ),
577
+ }),
578
+ ),
579
+ total: z.number(),
580
+ }),
581
+ },
582
+ },
583
+ description: 'List of packages with access information',
584
+ },
585
+ 401: {
586
+ content: {
587
+ 'application/json': {
588
+ schema: z.object({
589
+ error: z.string(),
590
+ }),
591
+ },
592
+ },
593
+ description: 'Authentication required',
594
+ },
595
+ },
596
+ })
597
+
598
+ export const grantPackageAccessRoute = createRoute({
599
+ method: 'put',
600
+ path: '/-/package/{pkg}/collaborators/{username}',
601
+ tags: ['Access Control'],
602
+ summary: 'Grant Package Access',
603
+ description: `Grant access to a package for a specific user`,
604
+ request: {
605
+ params: z.object({
606
+ pkg: z.string(),
607
+ username: z.string(),
608
+ }),
609
+ body: {
610
+ content: {
611
+ 'application/json': {
612
+ schema: z.object({
613
+ permission: z.enum(['read-only', 'read-write']),
614
+ }),
615
+ },
616
+ },
617
+ },
618
+ },
619
+ responses: {
620
+ 200: {
621
+ content: {
622
+ 'application/json': {
623
+ schema: z.object({
624
+ name: z.string(),
625
+ collaborators: z.record(
626
+ z.enum(['read-only', 'read-write']),
627
+ ),
628
+ }),
629
+ },
630
+ },
631
+ description: 'Access granted successfully',
632
+ },
633
+ 401: {
634
+ content: {
635
+ 'application/json': {
636
+ schema: z.object({
637
+ error: z.string(),
638
+ }),
639
+ },
640
+ },
641
+ description: 'Authentication required',
642
+ },
643
+ 403: {
644
+ content: {
645
+ 'application/json': {
646
+ schema: z.object({
647
+ error: z.string(),
648
+ }),
649
+ },
650
+ },
651
+ description: 'Insufficient permissions',
652
+ },
653
+ 404: {
654
+ content: {
655
+ 'application/json': {
656
+ schema: z.object({
657
+ error: z.string(),
658
+ }),
659
+ },
660
+ },
661
+ description: 'Package not found',
662
+ },
663
+ },
664
+ })
665
+
666
+ export const revokePackageAccessRoute = createRoute({
667
+ method: 'delete',
668
+ path: '/-/package/{pkg}/collaborators/{username}',
669
+ tags: ['Access Control'],
670
+ summary: 'Revoke Package Access',
671
+ description: `Revoke access to a package for a specific user`,
672
+ request: {
673
+ params: z.object({
674
+ pkg: z.string(),
675
+ username: z.string(),
676
+ }),
677
+ },
678
+ responses: {
679
+ 200: {
680
+ content: {
681
+ 'application/json': {
682
+ schema: z.object({
683
+ name: z.string(),
684
+ collaborators: z.record(
685
+ z.enum(['read-only', 'read-write']),
686
+ ),
687
+ }),
688
+ },
689
+ },
690
+ description: 'Access revoked successfully',
691
+ },
692
+ 401: {
693
+ content: {
694
+ 'application/json': {
695
+ schema: z.object({
696
+ error: z.string(),
697
+ }),
698
+ },
699
+ },
700
+ description: 'Authentication required',
701
+ },
702
+ 403: {
703
+ content: {
704
+ 'application/json': {
705
+ schema: z.object({
706
+ error: z.string(),
707
+ }),
708
+ },
709
+ },
710
+ description: 'Insufficient permissions',
711
+ },
712
+ 404: {
713
+ content: {
714
+ 'application/json': {
715
+ schema: z.object({
716
+ error: z.string(),
717
+ }),
718
+ },
719
+ },
720
+ description: 'Package not found',
721
+ },
722
+ },
723
+ })
724
+
725
+ export const grantScopedPackageAccessRoute = createRoute({
726
+ method: 'put',
727
+ path: '/-/package/{scope}%2f{pkg}/collaborators/{username}',
728
+ tags: ['Access Control'],
729
+ summary: 'Grant Scoped Package Access',
730
+ description: `Grant access to a scoped package for a specific user`,
731
+ request: {
732
+ params: z.object({
733
+ scope: z.string(),
734
+ pkg: z.string(),
735
+ username: z.string(),
736
+ }),
737
+ body: {
738
+ content: {
739
+ 'application/json': {
740
+ schema: z.object({
741
+ permission: z.enum(['read-only', 'read-write']),
742
+ }),
743
+ },
744
+ },
745
+ },
746
+ },
747
+ responses: {
748
+ 200: {
749
+ content: {
750
+ 'application/json': {
751
+ schema: z.object({
752
+ name: z.string(),
753
+ collaborators: z.record(
754
+ z.enum(['read-only', 'read-write']),
755
+ ),
756
+ }),
757
+ },
758
+ },
759
+ description: 'Access granted successfully',
760
+ },
761
+ 401: {
762
+ content: {
763
+ 'application/json': {
764
+ schema: z.object({
765
+ error: z.string(),
766
+ }),
767
+ },
768
+ },
769
+ description: 'Authentication required',
770
+ },
771
+ 403: {
772
+ content: {
773
+ 'application/json': {
774
+ schema: z.object({
775
+ error: z.string(),
776
+ }),
777
+ },
778
+ },
779
+ description: 'Insufficient permissions',
780
+ },
781
+ 404: {
782
+ content: {
783
+ 'application/json': {
784
+ schema: z.object({
785
+ error: z.string(),
786
+ }),
787
+ },
788
+ },
789
+ description: 'Package not found',
790
+ },
791
+ },
792
+ })
793
+
794
+ export const revokeScopedPackageAccessRoute = createRoute({
795
+ method: 'delete',
796
+ path: '/-/package/{scope}%2f{pkg}/collaborators/{username}',
797
+ tags: ['Access Control'],
798
+ summary: 'Revoke Scoped Package Access',
799
+ description: `Revoke access to a scoped package for a specific user`,
800
+ request: {
801
+ params: z.object({
802
+ scope: z.string(),
803
+ pkg: z.string(),
804
+ username: z.string(),
805
+ }),
806
+ },
807
+ responses: {
808
+ 200: {
809
+ content: {
810
+ 'application/json': {
811
+ schema: z.object({
812
+ name: z.string(),
813
+ collaborators: z.record(
814
+ z.enum(['read-only', 'read-write']),
815
+ ),
816
+ }),
817
+ },
818
+ },
819
+ description: 'Access revoked successfully',
820
+ },
821
+ 401: {
822
+ content: {
823
+ 'application/json': {
824
+ schema: z.object({
825
+ error: z.string(),
826
+ }),
827
+ },
828
+ },
829
+ description: 'Authentication required',
830
+ },
831
+ 403: {
832
+ content: {
833
+ 'application/json': {
834
+ schema: z.object({
835
+ error: z.string(),
836
+ }),
837
+ },
838
+ },
839
+ description: 'Insufficient permissions',
840
+ },
841
+ 404: {
842
+ content: {
843
+ 'application/json': {
844
+ schema: z.object({
845
+ error: z.string(),
846
+ }),
847
+ },
848
+ },
849
+ description: 'Package not found',
850
+ },
851
+ },
852
+ })