@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.
Files changed (3) hide show
  1. package/dist/cli.js +101 -17
  2. package/dist/server.js +101 -17
  3. 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.9",
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
- log2.debug("[CloudflareProvider] Uploading assets", {
158214
- deploymentId,
158215
- finalAssetsPath
158216
- });
158217
- assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
158218
- }
158219
- if (finalAssetsPath || assetsJWT || opts.keepAssets) {
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, verbose, quiet } = processedOptions;
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, stat) => {
168792
- acc[stat.status] = Number(stat.count);
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.9",
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
- log2.debug("[CloudflareProvider] Uploading assets", {
156304
- deploymentId,
156305
- finalAssetsPath
156306
- });
156307
- assetsJWT = await this.client.workers.uploadAssets(this.config.dispatchNamespace, deploymentId, { type: "directory", path: finalAssetsPath });
156308
- }
156309
- if (finalAssetsPath || assetsJWT || opts.keepAssets) {
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, verbose, quiet } = processedOptions;
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, stat) => {
166882
- acc[stat.status] = Number(stat.count);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {