@playcademy/vite-plugin 0.2.20 → 0.2.21

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 (2) hide show
  1. package/dist/index.js +154 -84
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -25334,7 +25334,7 @@ var package_default;
25334
25334
  var init_package = __esm(() => {
25335
25335
  package_default = {
25336
25336
  name: "@playcademy/sandbox",
25337
- version: "0.3.14",
25337
+ version: "0.3.15",
25338
25338
  description: "Local development server for Playcademy game development",
25339
25339
  type: "module",
25340
25340
  exports: {
@@ -45219,10 +45219,33 @@ class DeployJobService {
45219
45219
  }
45220
45220
  await this.deps.storage.deleteObject(bucketName, this.getCodeBundleObjectKey(jobId)).catch(() => {});
45221
45221
  }
45222
+ assertValidCodeUploadToken(token, gameId) {
45223
+ const expectedPrefix = `uploads-temp/${gameId}/`;
45224
+ if (!token.startsWith(expectedPrefix) || !token.endsWith(".js")) {
45225
+ throw new ValidationError("Invalid backend code upload token");
45226
+ }
45227
+ }
45228
+ async loadUploadedCode(codeUploadToken, gameId) {
45229
+ this.assertValidCodeUploadToken(codeUploadToken, gameId);
45230
+ const bucketName = this.getUploadBucket();
45231
+ const bytes = await this.deps.storage.getObjectBytes(bucketName, codeUploadToken);
45232
+ if (!bytes) {
45233
+ throw new ValidationError("Backend code upload not found");
45234
+ }
45235
+ return new TextDecoder().decode(bytes);
45236
+ }
45237
+ deleteUploadedCode(codeUploadToken, gameId) {
45238
+ this.assertValidCodeUploadToken(codeUploadToken, gameId);
45239
+ const bucketName = this.getUploadBucket();
45240
+ this.deps.storage.deleteObject(bucketName, codeUploadToken).catch(() => {
45241
+ logger2.warn("Failed to delete temp code upload", { key: codeUploadToken });
45242
+ });
45243
+ }
45222
45244
  sanitizeRequestForPersistence(request) {
45223
45245
  const sanitized = { ...request };
45224
45246
  delete sanitized._headers;
45225
45247
  delete sanitized.code;
45248
+ delete sanitized.codeUploadToken;
45226
45249
  return sanitized;
45227
45250
  }
45228
45251
  getLeaseExpiry() {
@@ -45260,6 +45283,16 @@ class DeployJobService {
45260
45283
  async create(slug, request, user) {
45261
45284
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
45262
45285
  const jobId = crypto.randomUUID();
45286
+ let codeSource = "none";
45287
+ if (request.code) {
45288
+ codeSource = "inline";
45289
+ } else if (request.codeUploadToken) {
45290
+ codeSource = "presigned-upload";
45291
+ }
45292
+ logger2.info("Deploy job backend code source", { slug, codeSource });
45293
+ if (codeSource === "presigned-upload") {
45294
+ request.code = await this.loadUploadedCode(request.codeUploadToken, game.id);
45295
+ }
45263
45296
  const sanitizedRequest = this.sanitizeRequestForPersistence(request);
45264
45297
  if (request.code) {
45265
45298
  await this.storeCodeBundle(jobId, request.code);
@@ -45288,6 +45321,9 @@ class DeployJobService {
45288
45321
  throw new ValidationError("Failed to create deploy job");
45289
45322
  }
45290
45323
  logger2.info("Deploy job created", { jobId: job.id, gameId: game.id, slug, userId: user.id });
45324
+ if (codeSource === "presigned-upload") {
45325
+ this.deleteUploadedCode(request.codeUploadToken, game.id);
45326
+ }
45291
45327
  return this.toResponse(job);
45292
45328
  }
45293
45329
  async get(jobId, slug, user) {
@@ -54126,6 +54162,12 @@ class UploadService {
54126
54162
  constructor(deps) {
54127
54163
  this.deps = deps;
54128
54164
  }
54165
+ static getContentType(fileName) {
54166
+ if (fileName.endsWith(".js")) {
54167
+ return "application/javascript";
54168
+ }
54169
+ return "application/zip";
54170
+ }
54129
54171
  async initiate(request, user) {
54130
54172
  const { fileName, gameId } = request;
54131
54173
  const bucketName = this.deps.uploadBucket;
@@ -54137,7 +54179,7 @@ class UploadService {
54137
54179
  const version2 = ulid();
54138
54180
  const tempS3Key = `uploads-temp/${gameId}/${version2}/${fileName}`;
54139
54181
  logger17.debug("Initiating upload", { userId: user.id, gameId, fileName, version: version2 });
54140
- const presignedUrl = await this.deps.generatePresignedPutUrl(bucketName, tempS3Key, "application/zip");
54182
+ const presignedUrl = await this.deps.generatePresignedPutUrl(bucketName, tempS3Key, UploadService.getContentType(fileName));
54141
54183
  logger17.info("Presigned URL generated", {
54142
54184
  userId: user.id,
54143
54185
  gameId,
@@ -117650,6 +117692,7 @@ var UpdateGameStateSchema;
117650
117692
  var InsertGameDeploymentSchema;
117651
117693
  var InsertGameDeployJobSchema;
117652
117694
  var UpsertGameMetadataSchema;
117695
+ var ALLOWED_UPLOAD_EXTENSIONS;
117653
117696
  var InitiateUploadSchema;
117654
117697
  var AddCustomHostnameSchema;
117655
117698
  var SetSecretsRequestSchema;
@@ -117750,9 +117793,10 @@ var init_schemas2 = __esm(() => {
117750
117793
  message: "External games require an externalUrl",
117751
117794
  path: ["externalUrl"]
117752
117795
  });
117796
+ ALLOWED_UPLOAD_EXTENSIONS = [".zip", ".js"];
117753
117797
  InitiateUploadSchema = exports_external.object({
117754
- fileName: exports_external.string().min(1).refine((name4) => name4.endsWith(".zip"), {
117755
- message: "File must be a .zip file"
117798
+ fileName: exports_external.string().min(1).refine((name4) => ALLOWED_UPLOAD_EXTENSIONS.some((ext2) => name4.endsWith(ext2)), {
117799
+ message: `File must be one of: ${ALLOWED_UPLOAD_EXTENSIONS.join(", ")}`
117756
117800
  }),
117757
117801
  gameId: exports_external.string().uuid()
117758
117802
  });
@@ -117785,6 +117829,7 @@ var init_schemas2 = __esm(() => {
117785
117829
  DeployRequestSchema = exports_external.object({
117786
117830
  uploadToken: exports_external.string().optional(),
117787
117831
  code: exports_external.string().optional(),
117832
+ codeUploadToken: exports_external.string().optional(),
117788
117833
  config: exports_external.unknown().optional(),
117789
117834
  bindings: exports_external.object({
117790
117835
  database: exports_external.array(exports_external.string()).optional(),
@@ -117812,6 +117857,9 @@ var init_schemas2 = __esm(() => {
117812
117857
  platform: exports_external.string().optional(),
117813
117858
  metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
117814
117859
  }).optional()
117860
+ }).refine((data) => !(data.code && data.codeUploadToken), {
117861
+ message: "Specify either code or codeUploadToken, not both",
117862
+ path: ["codeUploadToken"]
117815
117863
  });
117816
117864
  });
117817
117865
  function validateRelativePath(path22, fieldName) {
@@ -120773,6 +120821,97 @@ var init_crud = __esm(() => {
120773
120821
  gameCrudRouter.put("/:slug", handle2(games2.upsertBySlug));
120774
120822
  gameCrudRouter.delete("/:gameId", handle2(games2.remove, { status: 204 }));
120775
120823
  });
120824
+ async function initiateHandler(c2) {
120825
+ const user = c2.get("user");
120826
+ if (!user) {
120827
+ return c2.json({ error: { code: "UNAUTHORIZED", message: "Authentication required" } }, 401);
120828
+ }
120829
+ let body2;
120830
+ try {
120831
+ body2 = await c2.req.json();
120832
+ } catch {
120833
+ return c2.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
120834
+ }
120835
+ if (!body2.fileName || !body2.gameId) {
120836
+ return c2.json({
120837
+ error: {
120838
+ code: "VALIDATION_FAILED",
120839
+ message: "Validation failed: fileName and gameId are required"
120840
+ }
120841
+ }, 422);
120842
+ }
120843
+ const uploadToken = `sandbox-upload-${randomUUID3()}`;
120844
+ const version4 = `v${Date.now()}`;
120845
+ pendingUploads.set(uploadToken, {
120846
+ gameId: body2.gameId,
120847
+ fileName: body2.fileName
120848
+ });
120849
+ const host = c2.req.header("host") || "localhost:3000";
120850
+ const protocol = host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "https";
120851
+ const presignedUrl = `${protocol}://${host}/api/games/uploads/sandbox/${uploadToken}`;
120852
+ return c2.json({
120853
+ uploadToken,
120854
+ presignedUrl,
120855
+ gameId: body2.gameId,
120856
+ version: version4
120857
+ });
120858
+ }
120859
+ function consumeUploadedCode(token) {
120860
+ const upload = pendingUploads.get(token);
120861
+ if (!upload?.data) {
120862
+ return;
120863
+ }
120864
+ const { data } = upload;
120865
+ pendingUploads.delete(token);
120866
+ return data;
120867
+ }
120868
+ async function finalizeHandler(c2) {
120869
+ const user = c2.get("user");
120870
+ if (!user) {
120871
+ return c2.json({ error: "Authentication required" }, 401);
120872
+ }
120873
+ let body2;
120874
+ try {
120875
+ body2 = await c2.req.json();
120876
+ } catch {
120877
+ return c2.json({ error: "Invalid JSON body" }, 400);
120878
+ }
120879
+ if (!body2.uploadToken) {
120880
+ return c2.json({ error: "uploadToken is required" }, 400);
120881
+ }
120882
+ const upload = pendingUploads.get(body2.uploadToken);
120883
+ if (!upload) {
120884
+ return c2.json({ error: "Invalid or expired upload token" }, 400);
120885
+ }
120886
+ pendingUploads.delete(body2.uploadToken);
120887
+ return c2.json({
120888
+ success: true,
120889
+ gameId: upload.gameId,
120890
+ fileName: upload.fileName
120891
+ });
120892
+ }
120893
+ var gameUploadsRouter;
120894
+ var pendingUploads;
120895
+ var init_uploads = __esm(() => {
120896
+ init_dist4();
120897
+ gameUploadsRouter = new Hono2;
120898
+ pendingUploads = new Map;
120899
+ gameUploadsRouter.post("/uploads/initiate", initiateHandler);
120900
+ gameUploadsRouter.post("/uploads/initiate/", initiateHandler);
120901
+ gameUploadsRouter.put("/uploads/sandbox/:token", async (c2) => {
120902
+ const token = c2.req.param("token");
120903
+ const upload = pendingUploads.get(token);
120904
+ if (!upload) {
120905
+ return c2.json({ error: "Invalid upload token" }, 400);
120906
+ }
120907
+ if (upload.fileName.endsWith(".js")) {
120908
+ upload.data = await c2.req.text();
120909
+ }
120910
+ return c2.text("", 200);
120911
+ });
120912
+ gameUploadsRouter.post("/uploads/finalize", finalizeHandler);
120913
+ gameUploadsRouter.post("/uploads/finalize/", finalizeHandler);
120914
+ });
120776
120915
  var logger66;
120777
120916
  var gameDeployRouter;
120778
120917
  var init_deploy = __esm(() => {
@@ -120783,6 +120922,7 @@ var init_deploy = __esm(() => {
120783
120922
  init_tables_index();
120784
120923
  init_src2();
120785
120924
  init_api();
120925
+ init_uploads();
120786
120926
  logger66 = log.scope("SandboxDeploy");
120787
120927
  gameDeployRouter = new Hono2;
120788
120928
  gameDeployRouter.post("/:slug/deploy", async (c2) => {
@@ -120853,6 +120993,14 @@ var init_deploy = __esm(() => {
120853
120993
  }).returning();
120854
120994
  game = inserted[0];
120855
120995
  }
120996
+ let backendCode = body2.code;
120997
+ if (!backendCode && body2.codeUploadToken) {
120998
+ backendCode = consumeUploadedCode(body2.codeUploadToken);
120999
+ if (!backendCode) {
121000
+ return c2.json({ error: { code: "BAD_REQUEST", message: "Backend code upload not found" } }, 400);
121001
+ }
121002
+ }
121003
+ const hasBackend = Boolean(backendCode);
120856
121004
  const events = [
120857
121005
  { type: "status", message: "Deployment queued", createdAt: now2.toISOString() },
120858
121006
  { type: "status", message: "Deployment started", createdAt: now2.toISOString() },
@@ -120864,7 +121012,7 @@ var init_deploy = __esm(() => {
120864
121012
  },
120865
121013
  { type: "status", message: "Extracting assets", createdAt: now2.toISOString() }
120866
121014
  ] : [],
120867
- ...body2.code ? [
121015
+ ...hasBackend ? [
120868
121016
  {
120869
121017
  type: "status",
120870
121018
  message: "Deploying backend code",
@@ -121114,84 +121262,6 @@ var init_shop = __esm(() => {
121114
121262
  gameShopRouter.patch("/:gameId/items/:itemId/shop-listing", handle2(shopListings2.updateForGameItem));
121115
121263
  gameShopRouter.delete("/:gameId/items/:itemId/shop-listing", handle2(shopListings2.deleteForGameItem, { status: 204 }));
121116
121264
  });
121117
- async function initiateHandler(c2) {
121118
- const user = c2.get("user");
121119
- if (!user) {
121120
- return c2.json({ error: { code: "UNAUTHORIZED", message: "Authentication required" } }, 401);
121121
- }
121122
- let body2;
121123
- try {
121124
- body2 = await c2.req.json();
121125
- } catch {
121126
- return c2.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
121127
- }
121128
- if (!body2.fileName || !body2.gameId) {
121129
- return c2.json({
121130
- error: {
121131
- code: "VALIDATION_FAILED",
121132
- message: "Validation failed: fileName and gameId are required"
121133
- }
121134
- }, 422);
121135
- }
121136
- const uploadToken = `sandbox-upload-${randomUUID3()}`;
121137
- const version4 = `v${Date.now()}`;
121138
- pendingUploads.set(uploadToken, {
121139
- gameId: body2.gameId,
121140
- fileName: body2.fileName
121141
- });
121142
- const host = c2.req.header("host") || "localhost:3000";
121143
- const protocol = host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "https";
121144
- const presignedUrl = `${protocol}://${host}/api/games/uploads/sandbox/${uploadToken}`;
121145
- return c2.json({
121146
- uploadToken,
121147
- presignedUrl,
121148
- gameId: body2.gameId,
121149
- version: version4
121150
- });
121151
- }
121152
- async function finalizeHandler(c2) {
121153
- const user = c2.get("user");
121154
- if (!user) {
121155
- return c2.json({ error: "Authentication required" }, 401);
121156
- }
121157
- let body2;
121158
- try {
121159
- body2 = await c2.req.json();
121160
- } catch {
121161
- return c2.json({ error: "Invalid JSON body" }, 400);
121162
- }
121163
- if (!body2.uploadToken) {
121164
- return c2.json({ error: "uploadToken is required" }, 400);
121165
- }
121166
- const upload = pendingUploads.get(body2.uploadToken);
121167
- if (!upload) {
121168
- return c2.json({ error: "Invalid or expired upload token" }, 400);
121169
- }
121170
- pendingUploads.delete(body2.uploadToken);
121171
- return c2.json({
121172
- success: true,
121173
- gameId: upload.gameId,
121174
- fileName: upload.fileName
121175
- });
121176
- }
121177
- var gameUploadsRouter;
121178
- var pendingUploads;
121179
- var init_uploads = __esm(() => {
121180
- init_dist4();
121181
- gameUploadsRouter = new Hono2;
121182
- pendingUploads = new Map;
121183
- gameUploadsRouter.post("/uploads/initiate", initiateHandler);
121184
- gameUploadsRouter.post("/uploads/initiate/", initiateHandler);
121185
- gameUploadsRouter.put("/uploads/sandbox/:token", async (c2) => {
121186
- const token = c2.req.param("token");
121187
- if (!pendingUploads.has(token)) {
121188
- return c2.json({ error: "Invalid upload token" }, 400);
121189
- }
121190
- return c2.text("", 200);
121191
- });
121192
- gameUploadsRouter.post("/uploads/finalize", finalizeHandler);
121193
- gameUploadsRouter.post("/uploads/finalize/", finalizeHandler);
121194
- });
121195
121265
  var gameVerifyRouter;
121196
121266
  var init_verify2 = __esm(() => {
121197
121267
  init_dist4();
@@ -123059,7 +123129,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
123059
123129
  // package.json
123060
123130
  var package_default2 = {
123061
123131
  name: "@playcademy/vite-plugin",
123062
- version: "0.2.20",
123132
+ version: "0.2.21",
123063
123133
  type: "module",
123064
123134
  exports: {
123065
123135
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -21,14 +21,14 @@
21
21
  "dependencies": {
22
22
  "archiver": "^7.0.1",
23
23
  "picocolors": "^1.1.1",
24
- "playcademy": "0.18.0"
24
+ "playcademy": "0.18.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@electric-sql/pglite": "^0.3.16",
28
28
  "@inquirer/prompts": "^7.8.6",
29
29
  "@playcademy/constants": "0.0.1",
30
- "@playcademy/sandbox": "0.3.14",
31
- "@playcademy/sdk": "0.3.1",
30
+ "@playcademy/sandbox": "0.3.15",
31
+ "@playcademy/sdk": "0.3.2",
32
32
  "@playcademy/types": "0.0.1",
33
33
  "@playcademy/utils": "0.0.1",
34
34
  "@types/archiver": "^6.0.3",