playcademy 0.13.15 → 0.13.17
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 +11 -10
- package/dist/templates/api/sample-bucket.ts.template +60 -80
- package/dist/utils.js +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5735,10 +5735,10 @@ import { writeFileSync as writeFileSync4 } from "fs";
|
|
|
5735
5735
|
import { dirname as dirname4, join as join10 } from "path";
|
|
5736
5736
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5737
5737
|
var playcademyEnvTemplate = loadTemplateString("playcademy-env.d.ts");
|
|
5738
|
-
async function ensurePlaycademyTypes(
|
|
5738
|
+
async function ensurePlaycademyTypes() {
|
|
5739
5739
|
try {
|
|
5740
5740
|
const workspace = getWorkspace();
|
|
5741
|
-
const config = await loadConfig(
|
|
5741
|
+
const config = await loadConfig();
|
|
5742
5742
|
const hasDB = hasDatabaseSetup();
|
|
5743
5743
|
const hasKV = hasKVSetup(config);
|
|
5744
5744
|
const hasBucket = hasBucketSetup(config);
|
|
@@ -8206,10 +8206,9 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
8206
8206
|
bucket: bucket ?? void 0,
|
|
8207
8207
|
timeback: timebackConfig ?? void 0
|
|
8208
8208
|
});
|
|
8209
|
-
|
|
8210
|
-
writeFileSync8(configPath, configContent, "utf-8");
|
|
8209
|
+
writeFileSync8(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
|
|
8211
8210
|
if (database || kv || bucket) {
|
|
8212
|
-
await ensurePlaycademyTypes(
|
|
8211
|
+
await ensurePlaycademyTypes();
|
|
8213
8212
|
}
|
|
8214
8213
|
displaySuccessMessage({
|
|
8215
8214
|
configFileName,
|
|
@@ -8841,16 +8840,18 @@ function setupCleanupHandlers(workspace, getServer) {
|
|
|
8841
8840
|
async function setupServerHotReload(serverRef, port, workspace, config, loggerEnabled) {
|
|
8842
8841
|
return startHotReload(
|
|
8843
8842
|
async () => {
|
|
8844
|
-
if (serverRef.current) {
|
|
8845
|
-
await serverRef.current.dispose();
|
|
8846
|
-
}
|
|
8847
8843
|
const newConfig = await loadConfig();
|
|
8848
|
-
|
|
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({
|
|
8849
8850
|
port,
|
|
8850
8851
|
config: newConfig,
|
|
8851
8852
|
logger: loggerEnabled
|
|
8852
8853
|
});
|
|
8853
|
-
serverRef.current =
|
|
8854
|
+
serverRef.current = newServer;
|
|
8854
8855
|
await discoverRoutes(getCustomRoutesDirectory(workspace, newConfig));
|
|
8855
8856
|
},
|
|
8856
8857
|
{ config }
|
|
@@ -4,42 +4,35 @@
|
|
|
4
4
|
* This route will be available at: https://<your-game-slug>.playcademy.gg/api/sample/bucket
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*/
|
|
7
|
+
import type { Context } from 'hono'
|
|
8
|
+
|
|
10
9
|
interface FileInfo {
|
|
11
|
-
/** File key in bucket */
|
|
12
10
|
key: string
|
|
13
|
-
|
|
11
|
+
name: string
|
|
14
12
|
size: number
|
|
15
|
-
/** Upload timestamp */
|
|
16
13
|
uploaded: string
|
|
14
|
+
extension: string
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* GET /api/sample/bucket
|
|
21
19
|
*
|
|
22
|
-
* List or retrieve
|
|
20
|
+
* List all files or retrieve a specific file by key
|
|
21
|
+
* Query params:
|
|
22
|
+
* - key: File key to download (optional)
|
|
23
|
+
* - prefix: Filter files by prefix (optional)
|
|
23
24
|
*/
|
|
24
25
|
export async function GET(c: Context): Promise<Response> {
|
|
25
26
|
try {
|
|
26
27
|
const fileKey = c.req.query('key')
|
|
27
28
|
|
|
28
|
-
// If key provided, get specific file
|
|
29
29
|
if (fileKey) {
|
|
30
30
|
const object = await c.env.BUCKET.get(fileKey)
|
|
31
31
|
|
|
32
32
|
if (!object) {
|
|
33
|
-
return c.json(
|
|
34
|
-
{
|
|
35
|
-
success: false,
|
|
36
|
-
error: 'File not found',
|
|
37
|
-
},
|
|
38
|
-
404,
|
|
39
|
-
)
|
|
33
|
+
return c.json({ success: false, error: 'File not found' }, 404)
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
// Return file with appropriate headers
|
|
43
36
|
return new Response(object.body, {
|
|
44
37
|
headers: {
|
|
45
38
|
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
|
|
@@ -49,28 +42,28 @@ export async function GET(c: Context): Promise<Response> {
|
|
|
49
42
|
})
|
|
50
43
|
}
|
|
51
44
|
|
|
52
|
-
// Otherwise, list files
|
|
53
45
|
const prefix = c.req.query('prefix') || ''
|
|
54
46
|
const listed = await c.env.BUCKET.list({ prefix })
|
|
55
47
|
|
|
56
|
-
const files: FileInfo[] = listed.objects.map(obj =>
|
|
57
|
-
key
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
const files: FileInfo[] = (listed.objects as Array<{ key: string; size: number; uploaded: Date }>).map(obj => {
|
|
49
|
+
const fileName = obj.key.split('/').pop() || obj.key
|
|
50
|
+
const extensionMatch = fileName.match(/\.([^.]+)$/)
|
|
51
|
+
return {
|
|
52
|
+
key: obj.key,
|
|
53
|
+
name: fileName,
|
|
54
|
+
size: obj.size,
|
|
55
|
+
uploaded: obj.uploaded.toISOString(),
|
|
56
|
+
extension: extensionMatch?.[1]?.toLowerCase() || 'unknown',
|
|
57
|
+
}
|
|
58
|
+
})
|
|
61
59
|
|
|
62
60
|
return c.json({
|
|
63
61
|
success: true,
|
|
64
|
-
data: files,
|
|
65
|
-
truncated: listed.truncated,
|
|
62
|
+
data: { files, total: files.length, truncated: listed.truncated },
|
|
66
63
|
})
|
|
67
64
|
} catch (error) {
|
|
68
65
|
return c.json(
|
|
69
|
-
{
|
|
70
|
-
success: false,
|
|
71
|
-
error: 'Failed to access bucket storage',
|
|
72
|
-
details: error instanceof Error ? error.message : String(error),
|
|
73
|
-
},
|
|
66
|
+
{ success: false, error: 'Failed to list files', details: error instanceof Error ? error.message : 'Unknown error' },
|
|
74
67
|
500,
|
|
75
68
|
)
|
|
76
69
|
}
|
|
@@ -80,60 +73,60 @@ export async function GET(c: Context): Promise<Response> {
|
|
|
80
73
|
* PUT /api/sample/bucket
|
|
81
74
|
*
|
|
82
75
|
* Upload a file to bucket storage
|
|
76
|
+
* Query params:
|
|
77
|
+
* - name: File name (required)
|
|
83
78
|
*/
|
|
84
79
|
export async function PUT(c: Context): Promise<Response> {
|
|
85
80
|
try {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return c.json(
|
|
90
|
-
{
|
|
91
|
-
success: false,
|
|
92
|
-
error: 'File key is required',
|
|
93
|
-
},
|
|
94
|
-
400,
|
|
95
|
-
)
|
|
81
|
+
const fileName = c.req.query('name')
|
|
82
|
+
if (!fileName) {
|
|
83
|
+
return c.json({ success: false, error: 'name query param is required' }, 400)
|
|
96
84
|
}
|
|
97
85
|
|
|
98
|
-
// Get file from request body
|
|
99
86
|
const body = await c.req.arrayBuffer()
|
|
100
|
-
|
|
101
87
|
if (!body || body.byteLength === 0) {
|
|
102
|
-
return c.json(
|
|
103
|
-
{
|
|
104
|
-
success: false,
|
|
105
|
-
error: 'File body is required',
|
|
106
|
-
},
|
|
107
|
-
400,
|
|
108
|
-
)
|
|
88
|
+
return c.json({ success: false, error: 'File body is required' }, 400)
|
|
109
89
|
}
|
|
110
90
|
|
|
111
|
-
//
|
|
112
|
-
const
|
|
91
|
+
// Validate file size (10MB limit)
|
|
92
|
+
const maxSize = 10 * 1024 * 1024
|
|
93
|
+
if (body.byteLength > maxSize) {
|
|
94
|
+
return c.json({ success: false, error: `File exceeds ${maxSize / 1024 / 1024}MB limit` }, 400)
|
|
95
|
+
}
|
|
113
96
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
// Sanitize name and create key with timestamp
|
|
98
|
+
const safeName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
99
|
+
const fileKey = `uploads/${Date.now()}-${safeName}`
|
|
100
|
+
|
|
101
|
+
// Map extension to content-type
|
|
102
|
+
const extension = safeName.split('.').pop()?.toLowerCase() || ''
|
|
103
|
+
const contentTypeMap: Record<string, string> = {
|
|
104
|
+
jpg: 'image/jpeg',
|
|
105
|
+
jpeg: 'image/jpeg',
|
|
106
|
+
png: 'image/png',
|
|
107
|
+
gif: 'image/gif',
|
|
108
|
+
webp: 'image/webp',
|
|
109
|
+
pdf: 'application/pdf',
|
|
110
|
+
txt: 'text/plain',
|
|
111
|
+
json: 'application/json',
|
|
112
|
+
}
|
|
113
|
+
const contentType = contentTypeMap[extension] || c.req.header('Content-Type') || 'application/octet-stream'
|
|
114
|
+
|
|
115
|
+
await c.env.BUCKET.put(fileKey, body, { httpMetadata: { contentType } })
|
|
120
116
|
|
|
121
117
|
return c.json({
|
|
122
118
|
success: true,
|
|
123
119
|
data: {
|
|
124
120
|
key: fileKey,
|
|
121
|
+
name: safeName,
|
|
125
122
|
size: body.byteLength,
|
|
126
123
|
uploaded: new Date().toISOString(),
|
|
124
|
+
extension: extension || 'unknown',
|
|
127
125
|
},
|
|
128
|
-
message: 'File uploaded successfully',
|
|
129
126
|
})
|
|
130
127
|
} catch (error) {
|
|
131
128
|
return c.json(
|
|
132
|
-
{
|
|
133
|
-
success: false,
|
|
134
|
-
error: 'Failed to upload file',
|
|
135
|
-
details: error instanceof Error ? error.message : String(error),
|
|
136
|
-
},
|
|
129
|
+
{ success: false, error: 'Failed to upload file', details: error instanceof Error ? error.message : 'Unknown error' },
|
|
137
130
|
500,
|
|
138
131
|
)
|
|
139
132
|
}
|
|
@@ -143,35 +136,22 @@ export async function PUT(c: Context): Promise<Response> {
|
|
|
143
136
|
* DELETE /api/sample/bucket
|
|
144
137
|
*
|
|
145
138
|
* Delete a file from bucket storage
|
|
139
|
+
* Query params:
|
|
140
|
+
* - key: File key to delete (required)
|
|
146
141
|
*/
|
|
147
142
|
export async function DELETE(c: Context): Promise<Response> {
|
|
148
143
|
try {
|
|
149
144
|
const fileKey = c.req.query('key')
|
|
150
145
|
|
|
151
146
|
if (!fileKey) {
|
|
152
|
-
return c.json(
|
|
153
|
-
{
|
|
154
|
-
success: false,
|
|
155
|
-
error: 'File key is required',
|
|
156
|
-
},
|
|
157
|
-
400,
|
|
158
|
-
)
|
|
147
|
+
return c.json({ success: false, error: 'key query param is required' }, 400)
|
|
159
148
|
}
|
|
160
149
|
|
|
161
|
-
// Delete from bucket
|
|
162
150
|
await c.env.BUCKET.delete(fileKey)
|
|
163
|
-
|
|
164
|
-
return c.json({
|
|
165
|
-
success: true,
|
|
166
|
-
message: 'File deleted successfully',
|
|
167
|
-
})
|
|
151
|
+
return c.json({ success: true, message: 'File deleted successfully' })
|
|
168
152
|
} catch (error) {
|
|
169
153
|
return c.json(
|
|
170
|
-
{
|
|
171
|
-
success: false,
|
|
172
|
-
error: 'Failed to delete file',
|
|
173
|
-
details: error instanceof Error ? error.message : String(error),
|
|
174
|
-
},
|
|
154
|
+
{ success: false, error: 'Failed to delete file', details: error instanceof Error ? error.message : 'Unknown error' },
|
|
175
155
|
500,
|
|
176
156
|
)
|
|
177
157
|
}
|
package/dist/utils.js
CHANGED
|
@@ -1549,10 +1549,10 @@ import { writeFileSync as writeFileSync3 } from "fs";
|
|
|
1549
1549
|
import { dirname as dirname4, join as join8 } from "path";
|
|
1550
1550
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1551
1551
|
var playcademyEnvTemplate = loadTemplateString("playcademy-env.d.ts");
|
|
1552
|
-
async function ensurePlaycademyTypes(
|
|
1552
|
+
async function ensurePlaycademyTypes() {
|
|
1553
1553
|
try {
|
|
1554
1554
|
const workspace = getWorkspace();
|
|
1555
|
-
const config = await loadConfig(
|
|
1555
|
+
const config = await loadConfig();
|
|
1556
1556
|
const hasDB = hasDatabaseSetup();
|
|
1557
1557
|
const hasKV = hasKVSetup(config);
|
|
1558
1558
|
const hasBucket = hasBucketSetup(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playcademy",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.17",
|
|
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",
|