@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,63 @@
1
+ import { Scalar } from '@scalar/hono-api-reference'
2
+ import { SCALAR_API_CONFIG } from '../../config.ts'
3
+
4
+ export const getDocs = Scalar(async c => {
5
+ try {
6
+ // Use the current request's origin instead of hardcoded localhost URL
7
+ const origin = new URL(c.req.url).origin
8
+ const api = await fetch(`${origin}/-/api`)
9
+
10
+ if (!api.ok) {
11
+ throw new Error(
12
+ `API fetch failed: ${api.status} ${api.statusText}`,
13
+ )
14
+ }
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
17
+ const result = (await api.json()) as Record<string, any>
18
+
19
+ // Merge dynamic API spec with static config, with static config taking precedence
20
+ const {
21
+ authentication,
22
+ defaultHttpClient,
23
+ ...configWithoutAuth
24
+ } = SCALAR_API_CONFIG
25
+
26
+ // Fix hardcoded localhost URLs in static config
27
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
28
+ const staticContent = {
29
+ ...SCALAR_API_CONFIG.spec.content,
30
+ servers: [
31
+ {
32
+ url: origin,
33
+ description: 'Current deployment',
34
+ },
35
+ ],
36
+ }
37
+
38
+ const options = {
39
+ // Start with static config to preserve ALL your settings
40
+ ...configWithoutAuth,
41
+ spec: {
42
+ ...SCALAR_API_CONFIG.spec,
43
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
44
+ content: {
45
+ // Dynamic API content first
46
+ ...result,
47
+ // Static config overrides any conflicts (info, servers, security, etc.)
48
+ ...staticContent,
49
+ },
50
+ },
51
+ // Fix custom CSS URL to use current origin
52
+ customCss: `@import '${origin}/public/styles/styles.css';`,
53
+ }
54
+
55
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
56
+ return options as any
57
+ } catch (error) {
58
+ // Fallback to static config if API fetch fails
59
+ // eslint-disable-next-line no-console
60
+ console.error('Failed to fetch API spec:', error)
61
+ return SCALAR_API_CONFIG
62
+ }
63
+ })
@@ -0,0 +1,469 @@
1
+ import { createRoute, z } from '@hono/zod-openapi'
2
+ import type { Context } from 'hono'
3
+ import type { Environment } from '../../types.ts'
4
+
5
+ // Route definitions for miscellaneous endpoints
6
+
7
+ export const auditRoute = createRoute({
8
+ method: 'post',
9
+ path: '/-/npm/audit',
10
+ tags: ['Security'],
11
+ summary: 'Security Audit (Not Implemented)',
12
+ description: `⚠️ **NOT IMPLEMENTED** - This endpoint is planned for future implementation.
13
+
14
+ Currently returns a placeholder response indicating the feature is not available.
15
+
16
+ For actual security auditing, use:
17
+ - \`npm audit\` with the official npm registry
18
+ - Third-party security scanning tools
19
+ - Socket.dev integration (planned for v1.x)
20
+
21
+ \`\`\`bash
22
+ $ npm audit # Will hit this endpoint but get "not implemented" response
23
+ \`\`\``,
24
+ request: {
25
+ body: {
26
+ content: {
27
+ 'application/json': {
28
+ schema: z.object({
29
+ name: z.string().optional(),
30
+ version: z.string().optional(),
31
+ requires: z.record(z.string()).optional(),
32
+ dependencies: z.record(z.any()).optional(),
33
+ }),
34
+ },
35
+ },
36
+ },
37
+ },
38
+ responses: {
39
+ 501: {
40
+ content: {
41
+ 'application/json': {
42
+ schema: z.object({
43
+ error: z.string(),
44
+ message: z.string(),
45
+ feature: z.literal('security_audit'),
46
+ status: z.literal('not_implemented'),
47
+ }),
48
+ },
49
+ },
50
+ description:
51
+ 'Feature not implemented - security auditing is planned for future release',
52
+ },
53
+ 400: {
54
+ content: {
55
+ 'application/json': {
56
+ schema: z.object({
57
+ error: z.string(),
58
+ }),
59
+ },
60
+ },
61
+ description: 'Invalid request format',
62
+ },
63
+ },
64
+ })
65
+
66
+ export const auditQuickRoute = createRoute({
67
+ method: 'post',
68
+ path: '/-/npm/v1/security/audits/quick',
69
+ tags: ['NPM Compatibility'],
70
+ summary: 'Quick Security Audit',
71
+ description: `⚠️ **REDIRECTS TO /-/npm/audit** - This endpoint redirects to the main audit endpoint with a 308 status.
72
+
73
+ The main audit endpoint is currently not implemented and returns a "not implemented" response.`,
74
+ request: {
75
+ body: {
76
+ content: {
77
+ 'application/json': {
78
+ schema: z.object({
79
+ name: z.string().optional(),
80
+ version: z.string().optional(),
81
+ requires: z.record(z.string()).optional(),
82
+ dependencies: z.record(z.any()).optional(),
83
+ }),
84
+ },
85
+ },
86
+ },
87
+ },
88
+ responses: {
89
+ 308: {
90
+ description:
91
+ 'Permanent redirect to /-/npm/audit (which returns 501 Not Implemented)',
92
+ },
93
+ },
94
+ })
95
+
96
+ export const advisoriesBulkRoute = createRoute({
97
+ method: 'post',
98
+ path: '/-/npm/v1/security/advisories/bulk',
99
+ tags: ['NPM Compatibility'],
100
+ summary: 'Bulk Security Advisories',
101
+ description: `⚠️ **REDIRECTS TO /-/npm/audit** - This endpoint redirects to the main audit endpoint with a 308 status.
102
+
103
+ The main audit endpoint is currently not implemented and returns a "not implemented" response.`,
104
+ request: {
105
+ body: {
106
+ content: {
107
+ 'application/json': {
108
+ schema: z.record(z.array(z.string())),
109
+ },
110
+ },
111
+ },
112
+ },
113
+ responses: {
114
+ 308: {
115
+ description:
116
+ 'Permanent redirect to /-/npm/audit (which returns 501 Not Implemented)',
117
+ },
118
+ },
119
+ })
120
+
121
+ // Dashboard/GUI routes
122
+ export const dashboardDataRoute = createRoute({
123
+ method: 'get',
124
+ path: '/dashboard.json',
125
+ tags: ['Dashboard'],
126
+ summary: 'Dashboard Data',
127
+ description: `Get dashboard configuration data for the VSR web interface`,
128
+ request: {},
129
+ responses: {
130
+ 200: {
131
+ content: {
132
+ 'application/json': {
133
+ schema: z.object({
134
+ registry: z.object({
135
+ url: z.string(),
136
+ name: z.string(),
137
+ }),
138
+ features: z.object({
139
+ search: z.boolean(),
140
+ publish: z.boolean(),
141
+ access: z.boolean(),
142
+ }),
143
+ }),
144
+ },
145
+ },
146
+ description: 'Dashboard configuration',
147
+ },
148
+ },
149
+ })
150
+
151
+ export const appDataRoute = createRoute({
152
+ method: 'get',
153
+ path: '/app-data.json',
154
+ tags: ['Dashboard'],
155
+ summary: 'App Data',
156
+ description: `Get application data for the VSR web interface`,
157
+ request: {},
158
+ responses: {
159
+ 200: {
160
+ content: {
161
+ 'application/json': {
162
+ schema: z.object({
163
+ packages: z.array(
164
+ z.object({
165
+ name: z.string(),
166
+ version: z.string(),
167
+ description: z.string().optional(),
168
+ }),
169
+ ),
170
+ stats: z.object({
171
+ totalPackages: z.number(),
172
+ totalDownloads: z.number(),
173
+ }),
174
+ }),
175
+ },
176
+ },
177
+ description: 'Application data',
178
+ },
179
+ },
180
+ })
181
+
182
+ // npm v1 API compatibility redirects
183
+ export const searchRedirectRoute = createRoute({
184
+ method: 'get',
185
+ path: '/-/v1/search',
186
+ tags: ['NPM Compatibility'],
187
+ summary: 'Search',
188
+ description: `Redirect to current search endpoint for npm v1 compatibility`,
189
+ request: {},
190
+ responses: {
191
+ 308: {
192
+ description: 'Permanent redirect to /-/search',
193
+ },
194
+ },
195
+ })
196
+
197
+ export const userRedirectRoute = createRoute({
198
+ method: 'get',
199
+ path: '/-/npm/v1/user',
200
+ tags: ['NPM Compatibility'],
201
+ summary: 'User Profile',
202
+ description: `Redirect to current user endpoint for npm v1 compatibility`,
203
+ request: {},
204
+ responses: {
205
+ 308: {
206
+ description: 'Permanent redirect to /-/user',
207
+ },
208
+ },
209
+ })
210
+
211
+ export const tokensRedirectRoute = createRoute({
212
+ method: 'get',
213
+ path: '/-/npm/v1/tokens',
214
+ tags: ['NPM Compatibility'],
215
+ summary: 'Get Tokens',
216
+ description: `Redirect to current tokens endpoint for npm v1 compatibility`,
217
+ request: {},
218
+ responses: {
219
+ 308: {
220
+ description: 'Permanent redirect to /-/tokens',
221
+ },
222
+ },
223
+ })
224
+
225
+ export const createTokenRedirectRoute = createRoute({
226
+ method: 'post',
227
+ path: '/-/npm/v1/tokens',
228
+ tags: ['NPM Compatibility'],
229
+ summary: 'Create Token',
230
+ description: `Redirect to current create token endpoint for npm v1 compatibility`,
231
+ request: {},
232
+ responses: {
233
+ 308: {
234
+ description: 'Permanent redirect to /-/tokens',
235
+ },
236
+ },
237
+ })
238
+
239
+ export const updateTokenRedirectRoute = createRoute({
240
+ method: 'put',
241
+ path: '/-/npm/v1/tokens',
242
+ tags: ['NPM Compatibility'],
243
+ summary: 'Update Token',
244
+ description: `Redirect to current update token endpoint for npm v1 compatibility`,
245
+ request: {},
246
+ responses: {
247
+ 308: {
248
+ description: 'Permanent redirect to /-/tokens',
249
+ },
250
+ },
251
+ })
252
+
253
+ export const deleteTokenRedirectRoute = createRoute({
254
+ method: 'delete',
255
+ path: '/-/npm/v1/tokens/token/{token}',
256
+ tags: ['NPM Compatibility'],
257
+ summary: 'Delete Token',
258
+ description: `Redirect to current delete token endpoint for npm v1 compatibility`,
259
+ request: {
260
+ params: z.object({
261
+ token: z.string(),
262
+ }),
263
+ },
264
+ responses: {
265
+ 308: {
266
+ description: 'Permanent redirect to /-/tokens/:token',
267
+ },
268
+ },
269
+ })
270
+
271
+ // Static asset routes (for documentation purposes)
272
+ export const staticAssetsRoute = createRoute({
273
+ method: 'get',
274
+ path: '/public/{path}',
275
+ tags: ['Static Assets'],
276
+ summary: 'Static Assets',
277
+ description: `Serve static assets (CSS, JS, images, etc.)`,
278
+ request: {
279
+ params: z.object({
280
+ path: z.string(),
281
+ }),
282
+ },
283
+ responses: {
284
+ 200: {
285
+ content: {
286
+ 'application/octet-stream': {
287
+ schema: z.string().openapi({ format: 'binary' }),
288
+ },
289
+ },
290
+ description: 'Static file content',
291
+ },
292
+ 404: {
293
+ description: 'File not found',
294
+ },
295
+ },
296
+ })
297
+
298
+ export const faviconRoute = createRoute({
299
+ method: 'get',
300
+ path: '/favicon.ico',
301
+ tags: ['Static Assets'],
302
+ summary: 'Favicon',
303
+ description: `Serve the favicon for the registry`,
304
+ request: {},
305
+ responses: {
306
+ 200: {
307
+ content: {
308
+ 'image/x-icon': {
309
+ schema: z.string().openapi({ format: 'binary' }),
310
+ },
311
+ },
312
+ description: 'Favicon file',
313
+ },
314
+ },
315
+ })
316
+
317
+ export const robotsRoute = createRoute({
318
+ method: 'get',
319
+ path: '/robots.txt',
320
+ tags: ['Static Assets'],
321
+ summary: 'Robots.txt',
322
+ description: `Serve robots.txt for web crawlers`,
323
+ request: {},
324
+ responses: {
325
+ 200: {
326
+ content: {
327
+ 'text/plain': {
328
+ schema: z.string(),
329
+ },
330
+ },
331
+ description: 'Robots.txt content',
332
+ },
333
+ },
334
+ })
335
+
336
+ export const webManifestRoute = createRoute({
337
+ method: 'get',
338
+ path: '/manifest.json',
339
+ tags: ['Static Assets'],
340
+ summary: 'Web App Manifest',
341
+ description: `Serve web app manifest for PWA support`,
342
+ request: {},
343
+ responses: {
344
+ 200: {
345
+ content: {
346
+ 'application/json': {
347
+ schema: z.object({
348
+ name: z.string(),
349
+ short_name: z.string(),
350
+ description: z.string(),
351
+ start_url: z.string(),
352
+ display: z.string(),
353
+ theme_color: z.string(),
354
+ background_color: z.string(),
355
+ icons: z.array(
356
+ z.object({
357
+ src: z.string(),
358
+ sizes: z.string(),
359
+ type: z.string(),
360
+ }),
361
+ ),
362
+ }),
363
+ },
364
+ },
365
+ description: 'Web app manifest',
366
+ },
367
+ },
368
+ })
369
+
370
+ // Dashboard route handlers
371
+ export async function handleDashboardData(
372
+ c: Context,
373
+ ): Promise<Response> {
374
+ const env = c.env as Environment
375
+
376
+ // If daemon is enabled, proxy to daemon service
377
+
378
+ if (env.DAEMON_ENABLED) {
379
+ try {
380
+ const DAEMON_URL = 'http://localhost:3000'
381
+ const data = await fetch(`${DAEMON_URL}/dashboard.json`)
382
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
383
+ const jsonData = (await data.json()) as Record<string, any>
384
+ return c.json(jsonData)
385
+ } catch (_error) {
386
+ // Fall through to standalone mode if daemon is unreachable
387
+ }
388
+ }
389
+
390
+ // Standalone mode: provide dashboard data directly
391
+ const dashboardData = {
392
+ registry: {
393
+ url: env.URL || 'http://localhost:1337',
394
+ name: 'vlt serverless registry',
395
+ },
396
+ features: {
397
+ search: true,
398
+ publish: true,
399
+ access: true,
400
+ },
401
+ }
402
+
403
+ return c.json(dashboardData)
404
+ }
405
+
406
+ export async function handleAppData(c: Context): Promise<Response> {
407
+ const env = c.env as Environment
408
+
409
+ // If daemon is enabled, proxy to daemon service
410
+
411
+ if (env.DAEMON_ENABLED) {
412
+ try {
413
+ const DAEMON_URL = 'http://localhost:3000'
414
+ const data = await fetch(`${DAEMON_URL}/app-data.json`)
415
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
416
+ const jsonData = (await data.json()) as Record<string, any>
417
+ return c.json(jsonData)
418
+ } catch (_error) {
419
+ // Fall through to standalone mode if daemon is unreachable
420
+ }
421
+ }
422
+
423
+ // Standalone mode: provide app data directly from the registry database
424
+ try {
425
+ // For now, return empty data structure - in a full implementation,
426
+ // we would query the database for actual package data
427
+ const packages: {
428
+ name: string
429
+ version: string
430
+ description?: string
431
+ }[] = []
432
+
433
+ const appData = {
434
+ packages,
435
+ stats: {
436
+ totalPackages: packages.length,
437
+ totalDownloads: 0, // This would need to be tracked separately
438
+ },
439
+ }
440
+
441
+ return c.json(appData)
442
+ } catch (_error) {
443
+ // If database is not available, return empty data
444
+ const appData = {
445
+ packages: [],
446
+ stats: {
447
+ totalPackages: 0,
448
+ totalDownloads: 0,
449
+ },
450
+ }
451
+
452
+ return c.json(appData)
453
+ }
454
+ }
455
+
456
+ // Security audit handler (not implemented)
457
+ export async function handleSecurityAudit(
458
+ c: Context,
459
+ ): Promise<Response> {
460
+ return c.json(
461
+ {
462
+ error: 'Security audit not implemented',
463
+ message: 'This feature is not yet available in VSR',
464
+ status: 'not_implemented',
465
+ feature: 'security_audit',
466
+ },
467
+ 501,
468
+ )
469
+ }