@vltpkg/vsr 0.2.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.
Files changed (83) hide show
  1. package/.editorconfig +13 -0
  2. package/.prettierrc +7 -0
  3. package/CONTRIBUTING.md +228 -0
  4. package/LICENSE.md +110 -0
  5. package/README.md +373 -0
  6. package/bin/vsr.ts +29 -0
  7. package/config.ts +124 -0
  8. package/debug-npm.js +19 -0
  9. package/drizzle.config.js +33 -0
  10. package/package.json +80 -0
  11. package/pnpm-workspace.yaml +5 -0
  12. package/src/api.ts +2246 -0
  13. package/src/assets/public/images/bg.png +0 -0
  14. package/src/assets/public/images/clients/logo-bun.png +0 -0
  15. package/src/assets/public/images/clients/logo-deno.png +0 -0
  16. package/src/assets/public/images/clients/logo-npm.png +0 -0
  17. package/src/assets/public/images/clients/logo-pnpm.png +0 -0
  18. package/src/assets/public/images/clients/logo-vlt.png +0 -0
  19. package/src/assets/public/images/clients/logo-yarn.png +0 -0
  20. package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
  21. package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
  22. package/src/assets/public/images/favicon/favicon.ico +0 -0
  23. package/src/assets/public/images/favicon/favicon.svg +3 -0
  24. package/src/assets/public/images/favicon/site.webmanifest +21 -0
  25. package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
  26. package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
  27. package/src/assets/public/styles/styles.css +219 -0
  28. package/src/db/client.ts +544 -0
  29. package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
  30. package/src/db/migrations/0000_initial.sql +29 -0
  31. package/src/db/migrations/0001_uuid_validation.sql +35 -0
  32. package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
  33. package/src/db/migrations/drop.sql +3 -0
  34. package/src/db/migrations/meta/0000_snapshot.json +104 -0
  35. package/src/db/migrations/meta/0001_snapshot.json +155 -0
  36. package/src/db/migrations/meta/_journal.json +20 -0
  37. package/src/db/schema.ts +41 -0
  38. package/src/index.ts +709 -0
  39. package/src/routes/access.ts +263 -0
  40. package/src/routes/auth.ts +93 -0
  41. package/src/routes/index.ts +135 -0
  42. package/src/routes/packages.ts +924 -0
  43. package/src/routes/search.ts +50 -0
  44. package/src/routes/static.ts +53 -0
  45. package/src/routes/tokens.ts +102 -0
  46. package/src/routes/users.ts +14 -0
  47. package/src/utils/auth.ts +145 -0
  48. package/src/utils/cache.ts +466 -0
  49. package/src/utils/database.ts +44 -0
  50. package/src/utils/packages.ts +337 -0
  51. package/src/utils/response.ts +100 -0
  52. package/src/utils/routes.ts +47 -0
  53. package/src/utils/spa.ts +14 -0
  54. package/src/utils/tracing.ts +63 -0
  55. package/src/utils/upstream.ts +131 -0
  56. package/test/README.md +91 -0
  57. package/test/access.test.js +760 -0
  58. package/test/cloudflare-waituntil.test.js +141 -0
  59. package/test/db.test.js +447 -0
  60. package/test/dist-tag.test.js +415 -0
  61. package/test/e2e.test.js +904 -0
  62. package/test/hono-context.test.js +250 -0
  63. package/test/integrity-validation.test.js +183 -0
  64. package/test/json-response.test.js +76 -0
  65. package/test/manifest-slimming.test.js +449 -0
  66. package/test/packument-consistency.test.js +351 -0
  67. package/test/packument-version-range.test.js +144 -0
  68. package/test/performance.test.js +162 -0
  69. package/test/route-with-waituntil.test.js +298 -0
  70. package/test/run-tests.js +151 -0
  71. package/test/setup-cache-tests.js +190 -0
  72. package/test/setup.js +64 -0
  73. package/test/stale-while-revalidate.test.js +273 -0
  74. package/test/static-assets.test.js +85 -0
  75. package/test/upstream-routing.test.js +86 -0
  76. package/test/utils/test-helpers.js +84 -0
  77. package/test/waituntil-correct.test.js +208 -0
  78. package/test/waituntil-demo.test.js +138 -0
  79. package/test/waituntil-readme.md +113 -0
  80. package/tsconfig.json +37 -0
  81. package/types.ts +446 -0
  82. package/vitest.config.js +95 -0
  83. package/wrangler.json +58 -0
@@ -0,0 +1,263 @@
1
+ import { getAuthedUser } from '../utils/auth.ts'
2
+ import { parsePackageSpec } from '../utils/upstream.ts'
3
+ import type {
4
+ HonoContext,
5
+ AccessRequest,
6
+ AccessResponse,
7
+ TokenScope,
8
+ AuthUser
9
+ } from '../../types.ts'
10
+
11
+ interface PackageAccessEntry {
12
+ name: string
13
+ collaborators: Record<string, 'read-only' | 'read-write'>
14
+ }
15
+
16
+ interface PackageListResponse {
17
+ packages: PackageAccessEntry[]
18
+ total: number
19
+ }
20
+
21
+ /**
22
+ * Lists all packages the authenticated user has access to
23
+ */
24
+ export async function listPackagesAccess(c: HonoContext) {
25
+ try {
26
+ const user = await getAuthedUser({ c })
27
+ if (!user?.uuid) {
28
+ return c.json({ error: 'Authentication required' }, 401)
29
+ }
30
+
31
+ // For now, return empty list - this would need to be implemented
32
+ // based on your specific access control requirements
33
+ const response: PackageListResponse = {
34
+ packages: [],
35
+ total: 0
36
+ }
37
+
38
+ return c.json(response)
39
+ } catch (error) {
40
+ console.error('[ACCESS ERROR] Failed to list packages:', error)
41
+ return c.json({ error: 'Failed to list packages' }, 500)
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Gets the access status for a specific package
47
+ */
48
+ export async function getPackageAccessStatus(c: HonoContext) {
49
+ try {
50
+ const { scope, pkg } = c.req.param()
51
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
52
+
53
+ if (!packageName) {
54
+ return c.json({ error: 'Package name required' }, 400)
55
+ }
56
+
57
+ const user = await getAuthedUser({ c })
58
+ if (!user?.uuid) {
59
+ return c.json({ error: 'Authentication required' }, 401)
60
+ }
61
+
62
+ // Check if user has access to this package
63
+ const hasAccess = await checkPackageAccess(c, packageName, user)
64
+
65
+ if (!hasAccess) {
66
+ return c.json({ error: 'Access denied' }, 403)
67
+ }
68
+
69
+ // Return package access information
70
+ const response: AccessResponse = {
71
+ name: packageName,
72
+ collaborators: {
73
+ [user.uuid]: 'read-write' // Default the owner to read-write
74
+ }
75
+ }
76
+
77
+ return c.json(response)
78
+ } catch (error) {
79
+ console.error('[ACCESS ERROR] Failed to get package access:', error)
80
+ return c.json({ error: 'Failed to get package access' }, 500)
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Sets the access status for a specific package
86
+ */
87
+ export async function setPackageAccessStatus(c: HonoContext) {
88
+ try {
89
+ const { scope, pkg } = c.req.param()
90
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
91
+
92
+ if (!packageName) {
93
+ return c.json({ error: 'Package name required' }, 400)
94
+ }
95
+
96
+ const user = await getAuthedUser({ c })
97
+ if (!user?.uuid) {
98
+ return c.json({ error: 'Authentication required' }, 401)
99
+ }
100
+
101
+ const body = await c.req.json() as { collaborators: Record<string, 'read-only' | 'read-write'> }
102
+
103
+ // Check if user has admin access to this package
104
+ const hasAdminAccess = await checkPackageAdminAccess(c, packageName, user)
105
+
106
+ if (!hasAdminAccess) {
107
+ return c.json({ error: 'Admin access required' }, 403)
108
+ }
109
+
110
+ // Update package access (this would need to be implemented in your database)
111
+ // For now, just return the requested access
112
+ const response: AccessResponse = {
113
+ name: packageName,
114
+ collaborators: body.collaborators || {}
115
+ }
116
+
117
+ return c.json(response)
118
+ } catch (error) {
119
+ console.error('[ACCESS ERROR] Failed to set package access:', error)
120
+ return c.json({ error: 'Failed to set package access' }, 500)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Grants access to a user for a specific package
126
+ */
127
+ export async function grantPackageAccess(c: HonoContext) {
128
+ try {
129
+ const { scope, pkg, username } = c.req.param()
130
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
131
+
132
+ if (!packageName || !username) {
133
+ return c.json({ error: 'Package name and username required' }, 400)
134
+ }
135
+
136
+ const user = await getAuthedUser({ c })
137
+ if (!user?.uuid) {
138
+ return c.json({ error: 'Authentication required' }, 401)
139
+ }
140
+
141
+ const body = await c.req.json() as AccessRequest
142
+
143
+ // Check if user has admin access to this package
144
+ const hasAdminAccess = await checkPackageAdminAccess(c, packageName, user)
145
+
146
+ if (!hasAdminAccess) {
147
+ return c.json({ error: 'Admin access required' }, 403)
148
+ }
149
+
150
+ // Grant access (this would need to be implemented in your database)
151
+ // For now, just return success
152
+ const response: AccessResponse = {
153
+ name: packageName,
154
+ collaborators: {
155
+ [user.uuid]: 'read-write',
156
+ [username]: body.permission
157
+ }
158
+ }
159
+
160
+ return c.json(response)
161
+ } catch (error) {
162
+ console.error('[ACCESS ERROR] Failed to grant package access:', error)
163
+ return c.json({ error: 'Failed to grant package access' }, 500)
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Revokes access from a user for a specific package
169
+ */
170
+ export async function revokePackageAccess(c: HonoContext) {
171
+ try {
172
+ const { scope, pkg, username } = c.req.param()
173
+ const packageName = scope && pkg ? `${scope}/${pkg}` : scope
174
+
175
+ if (!packageName || !username) {
176
+ return c.json({ error: 'Package name and username required' }, 400)
177
+ }
178
+
179
+ const user = await getAuthedUser({ c })
180
+ if (!user?.uuid) {
181
+ return c.json({ error: 'Authentication required' }, 401)
182
+ }
183
+
184
+ // Check if user has admin access to this package
185
+ const hasAdminAccess = await checkPackageAdminAccess(c, packageName, user)
186
+
187
+ if (!hasAdminAccess) {
188
+ return c.json({ error: 'Admin access required' }, 403)
189
+ }
190
+
191
+ // Prevent users from revoking their own access
192
+ if (username === user.uuid) {
193
+ return c.json({ error: 'Cannot revoke your own access' }, 400)
194
+ }
195
+
196
+ // Revoke access (this would need to be implemented in your database)
197
+ // For now, just return success
198
+ const response: AccessResponse = {
199
+ name: packageName,
200
+ collaborators: {
201
+ [user.uuid]: 'read-write'
202
+ // username removed from collaborators
203
+ }
204
+ }
205
+
206
+ return c.json(response)
207
+ } catch (error) {
208
+ console.error('[ACCESS ERROR] Failed to revoke package access:', error)
209
+ return c.json({ error: 'Failed to revoke package access' }, 500)
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Helper function to check if user has access to a package
215
+ */
216
+ async function checkPackageAccess(c: HonoContext, packageName: string, user: AuthUser): Promise<boolean> {
217
+ if (!user.scope || !user.uuid) {
218
+ return false
219
+ }
220
+
221
+ // Check if user has global access or specific package access
222
+ for (const scope of user.scope) {
223
+ if (scope.types.pkg) {
224
+ // Check for global access
225
+ if (scope.values.includes('*') && scope.types.pkg.read) {
226
+ return true
227
+ }
228
+
229
+ // Check for specific package access
230
+ if (scope.values.includes(packageName) && scope.types.pkg.read) {
231
+ return true
232
+ }
233
+ }
234
+ }
235
+
236
+ return false
237
+ }
238
+
239
+ /**
240
+ * Helper function to check if user has admin access to a package
241
+ */
242
+ async function checkPackageAdminAccess(c: HonoContext, packageName: string, user: AuthUser): Promise<boolean> {
243
+ if (!user.scope || !user.uuid) {
244
+ return false
245
+ }
246
+
247
+ // Check if user has global write access or specific package write access
248
+ for (const scope of user.scope) {
249
+ if (scope.types.pkg) {
250
+ // Check for global write access
251
+ if (scope.values.includes('*') && scope.types.pkg.write) {
252
+ return true
253
+ }
254
+
255
+ // Check for specific package write access
256
+ if (scope.values.includes(packageName) && scope.types.pkg.write) {
257
+ return true
258
+ }
259
+ }
260
+ }
261
+
262
+ return false
263
+ }
@@ -0,0 +1,93 @@
1
+ import { setSignedCookie, getSignedCookie } from 'hono/cookie'
2
+ import { WorkOS } from '@workos-inc/node'
3
+ import type { HonoContext, WorkOSUser, WorkOSAuthResponse, WorkOSAuthResult } from '../../types.ts'
4
+
5
+ export async function requiresAuth(c: HonoContext, next: () => Promise<void>) {
6
+ const workos = new WorkOS(c.env.WORKOS_API_KEY, {
7
+ clientId: c.env.WORKOS_CLIENT_ID
8
+ })
9
+
10
+ try {
11
+ const sessionData = await getSignedCookie(c, c.env.WORKOS_COOKIE_PASSWORD, 'wos')
12
+ if (!sessionData) {
13
+ return c.redirect('/?error=no_session')
14
+ }
15
+
16
+ const session = await workos.userManagement.loadSealedSession({
17
+ sessionData,
18
+ cookiePassword: c.env.WORKOS_COOKIE_PASSWORD,
19
+ })
20
+
21
+ const authResponse = await session.authenticate() as WorkOSAuthResponse
22
+
23
+ if (authResponse.authenticated) {
24
+ // User is authenticated and session data can be used
25
+ const { sessionId, organizationId, role, permissions, user } = authResponse
26
+ c.set('user', user)
27
+ console.log('logged in user', user)
28
+ } else {
29
+ if (authResponse.reason === 'no_session_cookie_provided') {
30
+ // Redirect the user to the login page
31
+ return c.redirect('/?error=unauthorized')
32
+ }
33
+ return c.redirect('/?error=authentication_failed')
34
+ }
35
+ } catch (error) {
36
+ console.error('Auth error:', error)
37
+ return c.redirect('/?error=auth_error')
38
+ }
39
+
40
+ await next()
41
+ }
42
+
43
+ export async function handleLogin(c: HonoContext) {
44
+ const workos = new WorkOS(c.env.WORKOS_API_KEY, {
45
+ clientId: c.env.WORKOS_CLIENT_ID,
46
+ })
47
+
48
+ const authorizationUrl = workos.userManagement.getAuthorizationUrl({
49
+ provider: c.env.WORKOS_PROVIDER,
50
+ redirectUri: c.env.WORKOS_REDIRECT_URI,
51
+ clientId: c.env.WORKOS_CLIENT_ID,
52
+ })
53
+
54
+ return c.redirect(authorizationUrl)
55
+ }
56
+
57
+ export async function handleCallback(c: HonoContext) {
58
+ const workos = new WorkOS(c.env.WORKOS_API_KEY, {
59
+ clientId: c.env.WORKOS_CLIENT_ID,
60
+ })
61
+
62
+ const code = c.req.query('code')
63
+ console.log('code', code)
64
+
65
+ if (!code) {
66
+ return c.redirect('/?error=no_code')
67
+ }
68
+
69
+ try {
70
+ const res = await workos.userManagement.authenticateWithCode({
71
+ code,
72
+ clientId: c.env.WORKOS_CLIENT_ID,
73
+ session: {
74
+ sealSession: true,
75
+ cookiePassword: c.env.WORKOS_COOKIE_PASSWORD
76
+ }
77
+ }) as WorkOSAuthResult
78
+
79
+ console.log('res', res)
80
+
81
+ if (!res.user) {
82
+ return c.redirect('/?error=user_not_found')
83
+ }
84
+
85
+ console.log('user code', res.sealedSession)
86
+ await setSignedCookie(c, 'wos', res.sealedSession, c.env.WORKOS_COOKIE_PASSWORD)
87
+
88
+ return c.json({ user: res.user })
89
+ } catch (error) {
90
+ console.error('Callback error:', error)
91
+ return c.redirect('/?error=code_error')
92
+ }
93
+ }
@@ -0,0 +1,135 @@
1
+ import type { Hono } from 'hono'
2
+ import type { Environment } from '../../types.ts'
3
+
4
+ // Import all route handlers
5
+ import { getUsername, getUserProfile } from './users.ts'
6
+ import { searchPackages } from './search.ts'
7
+ import { handleStaticAssets, handleFavicon, handleRobots, handleManifest } from './static.ts'
8
+ import { getToken, postToken, putToken, deleteToken } from './tokens.ts'
9
+ import { requiresAuth, handleLogin, handleCallback } from './auth.ts'
10
+ import {
11
+ listPackagesAccess,
12
+ getPackageAccessStatus,
13
+ setPackageAccessStatus,
14
+ grantPackageAccess,
15
+ revokePackageAccess
16
+ } from './access.ts'
17
+ import {
18
+ getPackageTarball,
19
+ getPackagePackument
20
+ } from './packages.ts'
21
+
22
+ type HonoApp = Hono<{ Bindings: Environment }>
23
+
24
+ /**
25
+ * Add user-related routes to the app
26
+ */
27
+ export function addUserRoutes(app: HonoApp) {
28
+ app.get('/-/whoami', getUsername as any)
29
+ app.get('/-/user', getUserProfile as any)
30
+ }
31
+
32
+ /**
33
+ * Add search routes to the app
34
+ */
35
+ export function addSearchRoutes(app: HonoApp) {
36
+ app.get('/-/search', searchPackages as any)
37
+ }
38
+
39
+ /**
40
+ * Add static asset routes to the app
41
+ */
42
+ export function addStaticRoutes(app: HonoApp) {
43
+ app.get('/public/*', handleStaticAssets as any)
44
+ app.get('/favicon.ico', handleFavicon as any)
45
+ app.get('/robots.txt', handleRobots as any)
46
+ app.get('/manifest.json', handleManifest as any)
47
+ }
48
+
49
+ /**
50
+ * Add token management routes to the app
51
+ */
52
+ export function addTokenRoutes(app: HonoApp) {
53
+ app.get('/-/tokens/:token', getToken as any)
54
+ app.post('/-/tokens', postToken as any)
55
+ app.put('/-/tokens/:token', putToken as any)
56
+ app.delete('/-/tokens/:token', deleteToken as any)
57
+ }
58
+
59
+ /**
60
+ * Add authentication routes to the app
61
+ */
62
+ export function addAuthRoutes(app: HonoApp) {
63
+ app.get('/-/auth/login', handleLogin as any)
64
+ app.get('/-/auth/callback', handleCallback as any)
65
+ app.use('/-/auth/user', requiresAuth as any)
66
+ }
67
+
68
+ /**
69
+ * Add package access management routes to the app
70
+ */
71
+ export function addAccessRoutes(app: HonoApp) {
72
+ // Package access list
73
+ app.get('/-/package/list', listPackagesAccess as any)
74
+
75
+ // Package access status (unscoped)
76
+ app.get('/-/package/:pkg/access', getPackageAccessStatus as any)
77
+ app.put('/-/package/:pkg/access', setPackageAccessStatus as any)
78
+
79
+ // Package access status (scoped)
80
+ app.get('/-/package/:scope%2f:pkg/access', getPackageAccessStatus as any)
81
+ app.put('/-/package/:scope%2f:pkg/access', setPackageAccessStatus as any)
82
+
83
+ // Package collaborators (unscoped)
84
+ app.put('/-/package/:pkg/collaborators/:username', grantPackageAccess as any)
85
+ app.delete('/-/package/:pkg/collaborators/:username', revokePackageAccess as any)
86
+
87
+ // Package collaborators (scoped)
88
+ app.put('/-/package/:scope%2f:pkg/collaborators/:username', grantPackageAccess as any)
89
+ app.delete('/-/package/:scope%2f:pkg/collaborators/:username', revokePackageAccess as any)
90
+ }
91
+
92
+ /**
93
+ * Add package routes to the app
94
+ */
95
+ export function addPackageRoutes(app: HonoApp) {
96
+ // Package tarball routes
97
+ app.get('/:scope/:pkg/-/:tarball', getPackageTarball as any)
98
+ app.get('/:pkg/-/:tarball', getPackageTarball as any)
99
+
100
+ // Package packument routes (full package metadata)
101
+ app.get('/:scope/:pkg', getPackagePackument as any)
102
+ app.get('/:pkg', getPackagePackument as any)
103
+
104
+ // Note: Additional package routes (manifest, publishing, dist-tags) would be added here
105
+ // They are partially converted in packages.ts but need to be completed
106
+
107
+ // Placeholder for remaining package functionality
108
+ app.all('/*', (c) => c.json({
109
+ error: 'Package route not fully implemented',
110
+ message: 'Some package routes are converted but not all functions are exported yet'
111
+ }, 501) as any)
112
+ }
113
+
114
+ // Re-export all route handlers for direct use
115
+ export {
116
+ getUsername,
117
+ getUserProfile,
118
+ searchPackages,
119
+ handleStaticAssets,
120
+ handleFavicon,
121
+ handleRobots,
122
+ handleManifest,
123
+ getToken,
124
+ postToken,
125
+ putToken,
126
+ deleteToken,
127
+ requiresAuth,
128
+ handleLogin,
129
+ handleCallback,
130
+ listPackagesAccess,
131
+ getPackageAccessStatus,
132
+ setPackageAccessStatus,
133
+ grantPackageAccess,
134
+ revokePackageAccess
135
+ }