playcademy 0.13.16 → 0.13.18

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/dist/index.js CHANGED
@@ -8840,16 +8840,18 @@ function setupCleanupHandlers(workspace, getServer) {
8840
8840
  async function setupServerHotReload(serverRef, port, workspace, config, loggerEnabled) {
8841
8841
  return startHotReload(
8842
8842
  async () => {
8843
- if (serverRef.current) {
8844
- await serverRef.current.dispose();
8845
- }
8846
8843
  const newConfig = await loadConfig();
8847
- const { server } = await startDevServer({
8844
+ await bundleBackend(newConfig, { sourcemap: false, minify: false });
8845
+ const oldServer = serverRef.current;
8846
+ if (oldServer) {
8847
+ await oldServer.dispose();
8848
+ }
8849
+ const { server: newServer } = await startDevServer({
8848
8850
  port,
8849
8851
  config: newConfig,
8850
8852
  logger: loggerEnabled
8851
8853
  });
8852
- serverRef.current = server;
8854
+ serverRef.current = newServer;
8853
8855
  await discoverRoutes(getCustomRoutesDirectory(workspace, newConfig));
8854
8856
  },
8855
8857
  { config }
@@ -4,42 +4,33 @@
4
4
  * This route will be available at: https://<your-game-slug>.playcademy.gg/api/sample/bucket
5
5
  */
6
6
 
7
- /**
8
- * File metadata for bucket storage
9
- */
10
7
  interface FileInfo {
11
- /** File key in bucket */
12
8
  key: string
13
- /** File size in bytes */
9
+ name: string
14
10
  size: number
15
- /** Upload timestamp */
16
11
  uploaded: string
12
+ extension: string
17
13
  }
18
14
 
19
15
  /**
20
16
  * GET /api/sample/bucket
21
17
  *
22
- * List or retrieve files from bucket storage
18
+ * List all files or retrieve a specific file by key
19
+ * Query params:
20
+ * - key: File key to download (optional)
21
+ * - prefix: Filter files by prefix (optional)
23
22
  */
24
23
  export async function GET(c: Context): Promise<Response> {
25
24
  try {
26
25
  const fileKey = c.req.query('key')
27
26
 
28
- // If key provided, get specific file
29
27
  if (fileKey) {
30
28
  const object = await c.env.BUCKET.get(fileKey)
31
29
 
32
30
  if (!object) {
33
- return c.json(
34
- {
35
- success: false,
36
- error: 'File not found',
37
- },
38
- 404,
39
- )
31
+ return c.json({ success: false, error: 'File not found' }, 404)
40
32
  }
41
33
 
42
- // Return file with appropriate headers
43
34
  return new Response(object.body, {
44
35
  headers: {
45
36
  'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
@@ -49,28 +40,28 @@ export async function GET(c: Context): Promise<Response> {
49
40
  })
50
41
  }
51
42
 
52
- // Otherwise, list files
53
43
  const prefix = c.req.query('prefix') || ''
54
44
  const listed = await c.env.BUCKET.list({ prefix })
55
45
 
56
- const files: FileInfo[] = listed.objects.map(obj => ({
57
- key: obj.key,
58
- size: obj.size,
59
- uploaded: obj.uploaded.toISOString(),
60
- }))
46
+ const files: FileInfo[] = (listed.objects as Array<{ key: string; size: number; uploaded: Date }>).map(obj => {
47
+ const fileName = obj.key.split('/').pop() || obj.key
48
+ const extensionMatch = fileName.match(/\.([^.]+)$/)
49
+ return {
50
+ key: obj.key,
51
+ name: fileName,
52
+ size: obj.size,
53
+ uploaded: obj.uploaded.toISOString(),
54
+ extension: extensionMatch?.[1]?.toLowerCase() || 'unknown',
55
+ }
56
+ })
61
57
 
62
58
  return c.json({
63
59
  success: true,
64
- data: files,
65
- truncated: listed.truncated,
60
+ data: { files, total: files.length, truncated: listed.truncated },
66
61
  })
67
62
  } catch (error) {
68
63
  return c.json(
69
- {
70
- success: false,
71
- error: 'Failed to access bucket storage',
72
- details: error instanceof Error ? error.message : String(error),
73
- },
64
+ { success: false, error: 'Failed to list files', details: error instanceof Error ? error.message : 'Unknown error' },
74
65
  500,
75
66
  )
76
67
  }
@@ -80,60 +71,60 @@ export async function GET(c: Context): Promise<Response> {
80
71
  * PUT /api/sample/bucket
81
72
  *
82
73
  * Upload a file to bucket storage
74
+ * Query params:
75
+ * - name: File name (required)
83
76
  */
84
77
  export async function PUT(c: Context): Promise<Response> {
85
78
  try {
86
- const fileKey = c.req.query('key')
87
-
88
- if (!fileKey) {
89
- return c.json(
90
- {
91
- success: false,
92
- error: 'File key is required',
93
- },
94
- 400,
95
- )
79
+ const fileName = c.req.query('name')
80
+ if (!fileName) {
81
+ return c.json({ success: false, error: 'name query param is required' }, 400)
96
82
  }
97
83
 
98
- // Get file from request body
99
84
  const body = await c.req.arrayBuffer()
100
-
101
85
  if (!body || body.byteLength === 0) {
102
- return c.json(
103
- {
104
- success: false,
105
- error: 'File body is required',
106
- },
107
- 400,
108
- )
86
+ return c.json({ success: false, error: 'File body is required' }, 400)
109
87
  }
110
88
 
111
- // Get content type from header or default to binary
112
- const contentType = c.req.header('Content-Type') || 'application/octet-stream'
89
+ // Validate file size (10MB limit)
90
+ const maxSize = 10 * 1024 * 1024
91
+ if (body.byteLength > maxSize) {
92
+ return c.json({ success: false, error: `File exceeds ${maxSize / 1024 / 1024}MB limit` }, 400)
93
+ }
113
94
 
114
- // Upload to bucket
115
- await c.env.BUCKET.put(fileKey, body, {
116
- httpMetadata: {
117
- contentType,
118
- },
119
- })
95
+ // Sanitize name and create key with timestamp
96
+ const safeName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_')
97
+ const fileKey = `uploads/${Date.now()}-${safeName}`
98
+
99
+ // Map extension to content-type
100
+ const extension = safeName.split('.').pop()?.toLowerCase() || ''
101
+ const contentTypeMap: Record<string, string> = {
102
+ jpg: 'image/jpeg',
103
+ jpeg: 'image/jpeg',
104
+ png: 'image/png',
105
+ gif: 'image/gif',
106
+ webp: 'image/webp',
107
+ pdf: 'application/pdf',
108
+ txt: 'text/plain',
109
+ json: 'application/json',
110
+ }
111
+ const contentType = contentTypeMap[extension] || c.req.header('Content-Type') || 'application/octet-stream'
112
+
113
+ await c.env.BUCKET.put(fileKey, body, { httpMetadata: { contentType } })
120
114
 
121
115
  return c.json({
122
116
  success: true,
123
117
  data: {
124
118
  key: fileKey,
119
+ name: safeName,
125
120
  size: body.byteLength,
126
121
  uploaded: new Date().toISOString(),
122
+ extension: extension || 'unknown',
127
123
  },
128
- message: 'File uploaded successfully',
129
124
  })
130
125
  } catch (error) {
131
126
  return c.json(
132
- {
133
- success: false,
134
- error: 'Failed to upload file',
135
- details: error instanceof Error ? error.message : String(error),
136
- },
127
+ { success: false, error: 'Failed to upload file', details: error instanceof Error ? error.message : 'Unknown error' },
137
128
  500,
138
129
  )
139
130
  }
@@ -143,35 +134,22 @@ export async function PUT(c: Context): Promise<Response> {
143
134
  * DELETE /api/sample/bucket
144
135
  *
145
136
  * Delete a file from bucket storage
137
+ * Query params:
138
+ * - key: File key to delete (required)
146
139
  */
147
140
  export async function DELETE(c: Context): Promise<Response> {
148
141
  try {
149
142
  const fileKey = c.req.query('key')
150
143
 
151
144
  if (!fileKey) {
152
- return c.json(
153
- {
154
- success: false,
155
- error: 'File key is required',
156
- },
157
- 400,
158
- )
145
+ return c.json({ success: false, error: 'key query param is required' }, 400)
159
146
  }
160
147
 
161
- // Delete from bucket
162
148
  await c.env.BUCKET.delete(fileKey)
163
-
164
- return c.json({
165
- success: true,
166
- message: 'File deleted successfully',
167
- })
149
+ return c.json({ success: true, message: 'File deleted successfully' })
168
150
  } catch (error) {
169
151
  return c.json(
170
- {
171
- success: false,
172
- error: 'Failed to delete file',
173
- details: error instanceof Error ? error.message : String(error),
174
- },
152
+ { success: false, error: 'Failed to delete file', details: error instanceof Error ? error.message : 'Unknown error' },
175
153
  500,
176
154
  )
177
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.13.16",
3
+ "version": "0.13.18",
4
4
  "type": "module",
5
5
  "module": "./dist/index.js",
6
6
  "main": "./dist/index.js",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@inquirer/prompts": "^7.8.6",
43
- "@playcademy/sdk": "0.1.6",
43
+ "@playcademy/sdk": "0.1.7",
44
44
  "better-sqlite3": "^12.4.1",
45
45
  "chokidar": "^4.0.3",
46
46
  "colorette": "^2.0.20",