@playcademy/sandbox 0.1.10 → 0.1.11
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/cli.js +101 -17
- package/dist/server.js +101 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -135702,7 +135702,7 @@ var serve = (options, listeningListener) => {
|
|
|
135702
135702
|
// package.json
|
|
135703
135703
|
var package_default = {
|
|
135704
135704
|
name: "@playcademy/sandbox",
|
|
135705
|
-
version: "0.1.
|
|
135705
|
+
version: "0.1.10",
|
|
135706
135706
|
description: "Local development server for Playcademy game development",
|
|
135707
135707
|
type: "module",
|
|
135708
135708
|
exports: {
|
|
@@ -157173,9 +157173,9 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
|
|
|
157173
157173
|
}
|
|
157174
157174
|
// ../cloudflare/src/playcademy/provider.ts
|
|
157175
157175
|
import { execSync } from "node:child_process";
|
|
157176
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
157176
|
+
import { mkdir, readdir as readdir2, readFile as readFile2, rm, stat, writeFile } from "node:fs/promises";
|
|
157177
157177
|
import { tmpdir } from "node:os";
|
|
157178
|
-
import { join as join5 } from "node:path";
|
|
157178
|
+
import { join as join5, relative } from "node:path";
|
|
157179
157179
|
init_src();
|
|
157180
157180
|
|
|
157181
157181
|
// ../cloudflare/src/core/client.ts
|
|
@@ -158126,6 +158126,63 @@ class CloudflareProvider {
|
|
|
158126
158126
|
getClient() {
|
|
158127
158127
|
return this.client;
|
|
158128
158128
|
}
|
|
158129
|
+
async checkIfRequiresR2Strategy(assetsPath) {
|
|
158130
|
+
async function scanDirectory(dir) {
|
|
158131
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
158132
|
+
for (const entry of entries) {
|
|
158133
|
+
const fullPath = join5(dir, entry.name);
|
|
158134
|
+
if (entry.isDirectory()) {
|
|
158135
|
+
if (await scanDirectory(fullPath))
|
|
158136
|
+
return true;
|
|
158137
|
+
} else {
|
|
158138
|
+
const stats = await stat(fullPath);
|
|
158139
|
+
if (stats.size >= 25 * 1024 * 1024) {
|
|
158140
|
+
log2.info("[CloudflareProvider] Large file detected, R2 strategy required", {
|
|
158141
|
+
file: entry.name,
|
|
158142
|
+
size: `${(stats.size / 1024 / 1024).toFixed(2)}MB`
|
|
158143
|
+
});
|
|
158144
|
+
return true;
|
|
158145
|
+
}
|
|
158146
|
+
}
|
|
158147
|
+
}
|
|
158148
|
+
return false;
|
|
158149
|
+
}
|
|
158150
|
+
return await scanDirectory(assetsPath);
|
|
158151
|
+
}
|
|
158152
|
+
async resolveAssetBasePath(dirPath) {
|
|
158153
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
158154
|
+
if (entries.length === 1 && entries[0]?.isDirectory()) {
|
|
158155
|
+
const unwrappedPath = join5(dirPath, entries[0].name);
|
|
158156
|
+
log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
|
|
158157
|
+
wrapper: entries[0].name
|
|
158158
|
+
});
|
|
158159
|
+
return await this.resolveAssetBasePath(unwrappedPath);
|
|
158160
|
+
}
|
|
158161
|
+
return dirPath;
|
|
158162
|
+
}
|
|
158163
|
+
async uploadFilesToR2(dir, baseDir, bucketName) {
|
|
158164
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
158165
|
+
for (const entry of entries) {
|
|
158166
|
+
const fullPath = join5(dir, entry.name);
|
|
158167
|
+
if (entry.isDirectory()) {
|
|
158168
|
+
await this.uploadFilesToR2(fullPath, baseDir, bucketName);
|
|
158169
|
+
} else {
|
|
158170
|
+
const content = await readFile2(fullPath);
|
|
158171
|
+
const relativePath = relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
158172
|
+
const contentType = getContentType(relativePath);
|
|
158173
|
+
await this.client.r2.putObject(bucketName, relativePath, content, contentType);
|
|
158174
|
+
}
|
|
158175
|
+
}
|
|
158176
|
+
}
|
|
158177
|
+
async uploadDirectoryToR2(dirPath, bucketName) {
|
|
158178
|
+
const basePath = await this.resolveAssetBasePath(dirPath);
|
|
158179
|
+
log2.debug("[CloudflareProvider] Uploading assets to R2", {
|
|
158180
|
+
bucketName,
|
|
158181
|
+
basePath,
|
|
158182
|
+
filesFrom: relative(dirPath, basePath) || "(root)"
|
|
158183
|
+
});
|
|
158184
|
+
await this.uploadFilesToR2(basePath, basePath, bucketName);
|
|
158185
|
+
}
|
|
158129
158186
|
async deploy(deploymentId, code, env2, options) {
|
|
158130
158187
|
const opts = normalizeDeploymentOptions(options);
|
|
158131
158188
|
const isFirstDeploy = env2.PLAYCADEMY_API_KEY !== undefined;
|
|
@@ -158209,16 +158266,45 @@ class CloudflareProvider {
|
|
|
158209
158266
|
warnDurableObjectsNotImplemented(opts.bindings.durableObjects);
|
|
158210
158267
|
addResourceBindings(bindings, resourceIds);
|
|
158211
158268
|
let assetsJWT;
|
|
158269
|
+
let assetsStrategy;
|
|
158270
|
+
let assetsR2Bucket;
|
|
158212
158271
|
if (finalAssetsPath) {
|
|
158213
|
-
|
|
158214
|
-
|
|
158215
|
-
|
|
158216
|
-
|
|
158217
|
-
|
|
158218
|
-
|
|
158219
|
-
|
|
158272
|
+
const requiresR2 = await this.checkIfRequiresR2Strategy(finalAssetsPath);
|
|
158273
|
+
if (requiresR2) {
|
|
158274
|
+
assetsStrategy = "r2";
|
|
158275
|
+
assetsR2Bucket = `${deploymentId}-assets`;
|
|
158276
|
+
log2.info("[CloudflareProvider] Using R2 strategy for large files", {
|
|
158277
|
+
deploymentId,
|
|
158278
|
+
bucketName: assetsR2Bucket
|
|
158279
|
+
});
|
|
158280
|
+
await this.client.r2.create(assetsR2Bucket);
|
|
158281
|
+
await this.uploadDirectoryToR2(finalAssetsPath, assetsR2Bucket);
|
|
158282
|
+
bindings.push({
|
|
158283
|
+
type: "r2_bucket",
|
|
158284
|
+
name: "ASSETS",
|
|
158285
|
+
bucket_name: assetsR2Bucket
|
|
158286
|
+
});
|
|
158287
|
+
if (!resources.r2)
|
|
158288
|
+
resources.r2 = [];
|
|
158289
|
+
resources.r2.push({ name: assetsR2Bucket });
|
|
158290
|
+
} else {
|
|
158291
|
+
assetsStrategy = "workers-assets";
|
|
158292
|
+
log2.debug("[CloudflareProvider] Using Workers Assets strategy", {
|
|
158293
|
+
deploymentId,
|
|
158294
|
+
finalAssetsPath
|
|
158295
|
+
});
|
|
158296
|
+
assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
|
|
158297
|
+
bindings.push({ type: "assets", name: "ASSETS" });
|
|
158298
|
+
}
|
|
158299
|
+
} else if (opts.keepAssets) {
|
|
158220
158300
|
bindings.push({ type: "assets", name: "ASSETS" });
|
|
158221
158301
|
}
|
|
158302
|
+
if (assetsStrategy) {
|
|
158303
|
+
resources.assetsStrategy = assetsStrategy;
|
|
158304
|
+
if (assetsR2Bucket) {
|
|
158305
|
+
resources.assetsR2Bucket = assetsR2Bucket;
|
|
158306
|
+
}
|
|
158307
|
+
}
|
|
158222
158308
|
const keepBindingsValue = !isFirstDeploy ? ["secret_text"] : [];
|
|
158223
158309
|
log2.info("[Cloudflare Provider] Deployment configuration", {
|
|
158224
158310
|
deploymentId,
|
|
@@ -158299,7 +158385,8 @@ class CloudflareProvider {
|
|
|
158299
158385
|
await Promise.all([
|
|
158300
158386
|
this.client.d1.delete(deploymentId).catch(() => {}),
|
|
158301
158387
|
this.client.kv.delete(deploymentId).catch(() => {}),
|
|
158302
|
-
this.client.r2.delete(deploymentId).catch(() => {})
|
|
158388
|
+
this.client.r2.delete(deploymentId).catch(() => {}),
|
|
158389
|
+
this.client.r2.delete(`${deploymentId}-assets`).catch(() => {})
|
|
158303
158390
|
]);
|
|
158304
158391
|
}
|
|
158305
158392
|
async deleteCustomDomains(customDomains, gameSlug) {
|
|
@@ -158794,7 +158881,7 @@ async function checkIfNeedsSeeding(db) {
|
|
|
158794
158881
|
}
|
|
158795
158882
|
}
|
|
158796
158883
|
async function setupServerDatabase(processedOptions, project) {
|
|
158797
|
-
const { memoryOnly, recreateDb, databasePath, seed,
|
|
158884
|
+
const { memoryOnly, recreateDb, databasePath, seed, quiet } = processedOptions;
|
|
158798
158885
|
const effectiveDbPath = databasePath ?? process.env.PLAYCADEMY_SANDBOX_DB_PATH;
|
|
158799
158886
|
const resolvedDbPath = memoryOnly || effectiveDbPath === ":memory:" ? ":memory:" : effectiveDbPath ? DatabasePathManager.resolveDatabasePath(effectiveDbPath) : DatabasePathManager.resolveDatabasePath();
|
|
158800
158887
|
if (!memoryOnly && recreateDb && fs5.existsSync(resolvedDbPath)) {
|
|
@@ -158805,9 +158892,6 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
158805
158892
|
}
|
|
158806
158893
|
}
|
|
158807
158894
|
const isNewDb = resolvedDbPath === ":memory:" ? true : !fs5.existsSync(resolvedDbPath);
|
|
158808
|
-
if (verbose && !quiet && resolvedDbPath !== ":memory:") {
|
|
158809
|
-
console.log(`[Sandbox] Database: ${resolvedDbPath}`);
|
|
158810
|
-
}
|
|
158811
158895
|
const db = await setupDatabase(resolvedDbPath);
|
|
158812
158896
|
const shouldSeed = seed && (isNewDb || await checkIfNeedsSeeding(db));
|
|
158813
158897
|
if (shouldSeed) {
|
|
@@ -168788,8 +168872,8 @@ async function getNotificationStats(ctx) {
|
|
|
168788
168872
|
status: notifications.status,
|
|
168789
168873
|
count: sql`count(*)`
|
|
168790
168874
|
}).from(notifications).where(and(...conditions)).groupBy(notifications.status);
|
|
168791
|
-
const statsMap = stats.reduce((acc,
|
|
168792
|
-
acc[
|
|
168875
|
+
const statsMap = stats.reduce((acc, stat2) => {
|
|
168876
|
+
acc[stat2.status] = Number(stat2.count);
|
|
168793
168877
|
return acc;
|
|
168794
168878
|
}, {});
|
|
168795
168879
|
const total = Object.values(statsMap).reduce((sum3, count2) => sum3 + count2, 0);
|
package/dist/server.js
CHANGED
|
@@ -133792,7 +133792,7 @@ var serve = (options, listeningListener) => {
|
|
|
133792
133792
|
// package.json
|
|
133793
133793
|
var package_default = {
|
|
133794
133794
|
name: "@playcademy/sandbox",
|
|
133795
|
-
version: "0.1.
|
|
133795
|
+
version: "0.1.10",
|
|
133796
133796
|
description: "Local development server for Playcademy game development",
|
|
133797
133797
|
type: "module",
|
|
133798
133798
|
exports: {
|
|
@@ -155263,9 +155263,9 @@ async function publishPersonalBestNotification(userId, gameId, rank, newScore, p
|
|
|
155263
155263
|
}
|
|
155264
155264
|
// ../cloudflare/src/playcademy/provider.ts
|
|
155265
155265
|
import { execSync } from "node:child_process";
|
|
155266
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
155266
|
+
import { mkdir, readdir as readdir2, readFile as readFile2, rm, stat, writeFile } from "node:fs/promises";
|
|
155267
155267
|
import { tmpdir } from "node:os";
|
|
155268
|
-
import { join as join5 } from "node:path";
|
|
155268
|
+
import { join as join5, relative } from "node:path";
|
|
155269
155269
|
init_src();
|
|
155270
155270
|
|
|
155271
155271
|
// ../cloudflare/src/core/client.ts
|
|
@@ -156216,6 +156216,63 @@ class CloudflareProvider {
|
|
|
156216
156216
|
getClient() {
|
|
156217
156217
|
return this.client;
|
|
156218
156218
|
}
|
|
156219
|
+
async checkIfRequiresR2Strategy(assetsPath) {
|
|
156220
|
+
async function scanDirectory(dir) {
|
|
156221
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
156222
|
+
for (const entry of entries) {
|
|
156223
|
+
const fullPath = join5(dir, entry.name);
|
|
156224
|
+
if (entry.isDirectory()) {
|
|
156225
|
+
if (await scanDirectory(fullPath))
|
|
156226
|
+
return true;
|
|
156227
|
+
} else {
|
|
156228
|
+
const stats = await stat(fullPath);
|
|
156229
|
+
if (stats.size >= 25 * 1024 * 1024) {
|
|
156230
|
+
log2.info("[CloudflareProvider] Large file detected, R2 strategy required", {
|
|
156231
|
+
file: entry.name,
|
|
156232
|
+
size: `${(stats.size / 1024 / 1024).toFixed(2)}MB`
|
|
156233
|
+
});
|
|
156234
|
+
return true;
|
|
156235
|
+
}
|
|
156236
|
+
}
|
|
156237
|
+
}
|
|
156238
|
+
return false;
|
|
156239
|
+
}
|
|
156240
|
+
return await scanDirectory(assetsPath);
|
|
156241
|
+
}
|
|
156242
|
+
async resolveAssetBasePath(dirPath) {
|
|
156243
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
156244
|
+
if (entries.length === 1 && entries[0]?.isDirectory()) {
|
|
156245
|
+
const unwrappedPath = join5(dirPath, entries[0].name);
|
|
156246
|
+
log2.debug("[CloudflareProvider] Unwrapping wrapper directory", {
|
|
156247
|
+
wrapper: entries[0].name
|
|
156248
|
+
});
|
|
156249
|
+
return await this.resolveAssetBasePath(unwrappedPath);
|
|
156250
|
+
}
|
|
156251
|
+
return dirPath;
|
|
156252
|
+
}
|
|
156253
|
+
async uploadFilesToR2(dir, baseDir, bucketName) {
|
|
156254
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
156255
|
+
for (const entry of entries) {
|
|
156256
|
+
const fullPath = join5(dir, entry.name);
|
|
156257
|
+
if (entry.isDirectory()) {
|
|
156258
|
+
await this.uploadFilesToR2(fullPath, baseDir, bucketName);
|
|
156259
|
+
} else {
|
|
156260
|
+
const content = await readFile2(fullPath);
|
|
156261
|
+
const relativePath = relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
156262
|
+
const contentType = getContentType(relativePath);
|
|
156263
|
+
await this.client.r2.putObject(bucketName, relativePath, content, contentType);
|
|
156264
|
+
}
|
|
156265
|
+
}
|
|
156266
|
+
}
|
|
156267
|
+
async uploadDirectoryToR2(dirPath, bucketName) {
|
|
156268
|
+
const basePath = await this.resolveAssetBasePath(dirPath);
|
|
156269
|
+
log2.debug("[CloudflareProvider] Uploading assets to R2", {
|
|
156270
|
+
bucketName,
|
|
156271
|
+
basePath,
|
|
156272
|
+
filesFrom: relative(dirPath, basePath) || "(root)"
|
|
156273
|
+
});
|
|
156274
|
+
await this.uploadFilesToR2(basePath, basePath, bucketName);
|
|
156275
|
+
}
|
|
156219
156276
|
async deploy(deploymentId, code, env2, options) {
|
|
156220
156277
|
const opts = normalizeDeploymentOptions(options);
|
|
156221
156278
|
const isFirstDeploy = env2.PLAYCADEMY_API_KEY !== undefined;
|
|
@@ -156299,16 +156356,45 @@ class CloudflareProvider {
|
|
|
156299
156356
|
warnDurableObjectsNotImplemented(opts.bindings.durableObjects);
|
|
156300
156357
|
addResourceBindings(bindings, resourceIds);
|
|
156301
156358
|
let assetsJWT;
|
|
156359
|
+
let assetsStrategy;
|
|
156360
|
+
let assetsR2Bucket;
|
|
156302
156361
|
if (finalAssetsPath) {
|
|
156303
|
-
|
|
156304
|
-
|
|
156305
|
-
|
|
156306
|
-
|
|
156307
|
-
|
|
156308
|
-
|
|
156309
|
-
|
|
156362
|
+
const requiresR2 = await this.checkIfRequiresR2Strategy(finalAssetsPath);
|
|
156363
|
+
if (requiresR2) {
|
|
156364
|
+
assetsStrategy = "r2";
|
|
156365
|
+
assetsR2Bucket = `${deploymentId}-assets`;
|
|
156366
|
+
log2.info("[CloudflareProvider] Using R2 strategy for large files", {
|
|
156367
|
+
deploymentId,
|
|
156368
|
+
bucketName: assetsR2Bucket
|
|
156369
|
+
});
|
|
156370
|
+
await this.client.r2.create(assetsR2Bucket);
|
|
156371
|
+
await this.uploadDirectoryToR2(finalAssetsPath, assetsR2Bucket);
|
|
156372
|
+
bindings.push({
|
|
156373
|
+
type: "r2_bucket",
|
|
156374
|
+
name: "ASSETS",
|
|
156375
|
+
bucket_name: assetsR2Bucket
|
|
156376
|
+
});
|
|
156377
|
+
if (!resources.r2)
|
|
156378
|
+
resources.r2 = [];
|
|
156379
|
+
resources.r2.push({ name: assetsR2Bucket });
|
|
156380
|
+
} else {
|
|
156381
|
+
assetsStrategy = "workers-assets";
|
|
156382
|
+
log2.debug("[CloudflareProvider] Using Workers Assets strategy", {
|
|
156383
|
+
deploymentId,
|
|
156384
|
+
finalAssetsPath
|
|
156385
|
+
});
|
|
156386
|
+
assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
|
|
156387
|
+
bindings.push({ type: "assets", name: "ASSETS" });
|
|
156388
|
+
}
|
|
156389
|
+
} else if (opts.keepAssets) {
|
|
156310
156390
|
bindings.push({ type: "assets", name: "ASSETS" });
|
|
156311
156391
|
}
|
|
156392
|
+
if (assetsStrategy) {
|
|
156393
|
+
resources.assetsStrategy = assetsStrategy;
|
|
156394
|
+
if (assetsR2Bucket) {
|
|
156395
|
+
resources.assetsR2Bucket = assetsR2Bucket;
|
|
156396
|
+
}
|
|
156397
|
+
}
|
|
156312
156398
|
const keepBindingsValue = !isFirstDeploy ? ["secret_text"] : [];
|
|
156313
156399
|
log2.info("[Cloudflare Provider] Deployment configuration", {
|
|
156314
156400
|
deploymentId,
|
|
@@ -156389,7 +156475,8 @@ class CloudflareProvider {
|
|
|
156389
156475
|
await Promise.all([
|
|
156390
156476
|
this.client.d1.delete(deploymentId).catch(() => {}),
|
|
156391
156477
|
this.client.kv.delete(deploymentId).catch(() => {}),
|
|
156392
|
-
this.client.r2.delete(deploymentId).catch(() => {})
|
|
156478
|
+
this.client.r2.delete(deploymentId).catch(() => {}),
|
|
156479
|
+
this.client.r2.delete(`${deploymentId}-assets`).catch(() => {})
|
|
156393
156480
|
]);
|
|
156394
156481
|
}
|
|
156395
156482
|
async deleteCustomDomains(customDomains, gameSlug) {
|
|
@@ -156884,7 +156971,7 @@ async function checkIfNeedsSeeding(db) {
|
|
|
156884
156971
|
}
|
|
156885
156972
|
}
|
|
156886
156973
|
async function setupServerDatabase(processedOptions, project) {
|
|
156887
|
-
const { memoryOnly, recreateDb, databasePath, seed,
|
|
156974
|
+
const { memoryOnly, recreateDb, databasePath, seed, quiet } = processedOptions;
|
|
156888
156975
|
const effectiveDbPath = databasePath ?? process.env.PLAYCADEMY_SANDBOX_DB_PATH;
|
|
156889
156976
|
const resolvedDbPath = memoryOnly || effectiveDbPath === ":memory:" ? ":memory:" : effectiveDbPath ? DatabasePathManager.resolveDatabasePath(effectiveDbPath) : DatabasePathManager.resolveDatabasePath();
|
|
156890
156977
|
if (!memoryOnly && recreateDb && fs5.existsSync(resolvedDbPath)) {
|
|
@@ -156895,9 +156982,6 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
156895
156982
|
}
|
|
156896
156983
|
}
|
|
156897
156984
|
const isNewDb = resolvedDbPath === ":memory:" ? true : !fs5.existsSync(resolvedDbPath);
|
|
156898
|
-
if (verbose && !quiet && resolvedDbPath !== ":memory:") {
|
|
156899
|
-
console.log(`[Sandbox] Database: ${resolvedDbPath}`);
|
|
156900
|
-
}
|
|
156901
156985
|
const db = await setupDatabase(resolvedDbPath);
|
|
156902
156986
|
const shouldSeed = seed && (isNewDb || await checkIfNeedsSeeding(db));
|
|
156903
156987
|
if (shouldSeed) {
|
|
@@ -166878,8 +166962,8 @@ async function getNotificationStats(ctx) {
|
|
|
166878
166962
|
status: notifications.status,
|
|
166879
166963
|
count: sql`count(*)`
|
|
166880
166964
|
}).from(notifications).where(and(...conditions)).groupBy(notifications.status);
|
|
166881
|
-
const statsMap = stats.reduce((acc,
|
|
166882
|
-
acc[
|
|
166965
|
+
const statsMap = stats.reduce((acc, stat2) => {
|
|
166966
|
+
acc[stat2.status] = Number(stat2.count);
|
|
166883
166967
|
return acc;
|
|
166884
166968
|
}, {});
|
|
166885
166969
|
const total = Object.values(statsMap).reduce((sum3, count2) => sum3 + count2, 0);
|