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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
//
|
|
112
|
-
const
|
|
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
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
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.
|
|
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",
|