@playcademy/vite-plugin 0.2.13 → 0.2.14

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 +368 -315
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -54623,307 +54623,6 @@ var init_database_service = __esm(() => {
54623
54623
  init_deployment_util();
54624
54624
  logger7 = log.scope("DatabaseService");
54625
54625
  });
54626
-
54627
- class DeployService {
54628
- ctx;
54629
- constructor(ctx) {
54630
- this.ctx = ctx;
54631
- }
54632
- getCloudflare() {
54633
- const cf = this.ctx.cloudflare;
54634
- if (!cf) {
54635
- logger8.error("Cloudflare provider not available");
54636
- throw new ValidationError("Cloudflare provider not configured");
54637
- }
54638
- return cf;
54639
- }
54640
- async createApiKey(user, slug2, keyName) {
54641
- const { id, key: apiKey } = await this.ctx.providers.auth.createApiKey({
54642
- userId: user.id,
54643
- name: keyName,
54644
- expiresIn: null,
54645
- permissions: {
54646
- games: [`read:${slug2}`, `write:${slug2}`]
54647
- }
54648
- });
54649
- logger8.info("Created new game-scoped API key", {
54650
- userId: user.id,
54651
- slug: slug2,
54652
- keyId: id
54653
- });
54654
- return apiKey;
54655
- }
54656
- async regenerateApiKey(user, slug2, existingKeyId, keyName) {
54657
- if (existingKeyId) {
54658
- logger8.info("Regenerating API key", {
54659
- userId: user.id,
54660
- slug: slug2,
54661
- oldKeyId: existingKeyId
54662
- });
54663
- try {
54664
- await this.ctx.providers.auth.deleteApiKey(existingKeyId);
54665
- logger8.debug("Revoked old API key", { keyId: existingKeyId });
54666
- } catch (error) {
54667
- logger8.warn("Failed to revoke old API key", {
54668
- keyId: existingKeyId,
54669
- error
54670
- });
54671
- }
54672
- }
54673
- return this.createApiKey(user, slug2, keyName);
54674
- }
54675
- async ensureApiKeyOnWorker(user, slug2, deploymentId, headers) {
54676
- const cf = this.getCloudflare();
54677
- const keyName = getGameWorkerApiKeyName(slug2);
54678
- const existingKeys = await this.ctx.providers.auth.listApiKeys(headers);
54679
- const existingKey = existingKeys.find((k) => k.name === keyName);
54680
- let apiKey;
54681
- if (!existingKey) {
54682
- apiKey = await this.createApiKey(user, slug2, keyName);
54683
- } else {
54684
- try {
54685
- const workerSecrets = await cf.listSecrets(deploymentId);
54686
- if (workerSecrets.includes("PLAYCADEMY_API_KEY")) {
54687
- logger8.debug("API key already on worker", { slug: slug2, deploymentId });
54688
- return;
54689
- }
54690
- } catch (error) {
54691
- logger8.warn("Could not check worker secrets, will regenerate key", { error });
54692
- }
54693
- apiKey = await this.regenerateApiKey(user, slug2, existingKey.id, keyName);
54694
- }
54695
- await cf.setSecrets(deploymentId, { PLAYCADEMY_API_KEY: apiKey });
54696
- logger8.info("Set API key on worker", { slug: slug2, deploymentId });
54697
- }
54698
- async* deploy(slug2, request, user, uploadDeps, extractZip) {
54699
- const cf = this.getCloudflare();
54700
- const db2 = this.ctx.db;
54701
- const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
54702
- const hasBackend = !!request.code;
54703
- const hasFrontend = !!request.uploadToken;
54704
- const hasMetadata = !!request.metadata;
54705
- logger8.debug("Deploying game", { slug: slug2, hasBackend, hasFrontend, hasMetadata });
54706
- if (!hasBackend && !hasFrontend && !hasMetadata) {
54707
- throw new ValidationError("Must provide at least one of: uploadToken (frontend), code (backend), or metadata");
54708
- }
54709
- const isProd = isProduction2(this.ctx.config);
54710
- const deploymentId = getDeploymentId(slug2, isProd);
54711
- if (!this.ctx.config.baseUrl) {
54712
- logger8.error("baseUrl not configured for deployment", { slug: slug2 });
54713
- throw new ValidationError("baseUrl is not configured");
54714
- }
54715
- const playcademyBaseUrl = this.ctx.config.baseUrl;
54716
- let frontendAssetsPath;
54717
- let tempDir;
54718
- if (hasFrontend) {
54719
- if (!uploadDeps || !extractZip) {
54720
- logger8.error("Upload dependencies not available for frontend deployment", { slug: slug2 });
54721
- throw new ValidationError("Upload dependencies not configured for frontend deployment");
54722
- }
54723
- yield { type: "status", data: { message: "Fetching temporary files" } };
54724
- const frontendZip = await uploadDeps.getObjectAsByteArray(request.uploadToken);
54725
- if (!frontendZip || frontendZip.length === 0) {
54726
- logger8.error("Frontend upload empty or not found", {
54727
- slug: slug2,
54728
- uploadToken: request.uploadToken
54729
- });
54730
- throw new ValidationError("Uploaded file is empty or not found");
54731
- }
54732
- yield { type: "status", data: { message: "Extracting assets" } };
54733
- const os2 = await import("os");
54734
- const path2 = await import("path");
54735
- tempDir = path2.join(os2.tmpdir(), `playcademy-deploy-${game.id}-${Date.now()}`);
54736
- frontendAssetsPath = path2.join(tempDir, "dist");
54737
- await extractZip(frontendZip, frontendAssetsPath);
54738
- uploadDeps.deleteObject(request.uploadToken).catch(() => {
54739
- logger8.warn("Failed to delete temp object", { key: request.uploadToken });
54740
- });
54741
- }
54742
- const env = {
54743
- GAME_ID: game.id,
54744
- PLAYCADEMY_BASE_URL: playcademyBaseUrl
54745
- };
54746
- const deployMsg = hasBackend ? "Deploying backend code" : "Deploying to platform";
54747
- yield { type: "status", data: { message: deployMsg } };
54748
- const keepAssets = hasBackend && !hasFrontend;
54749
- const deploymentOptions = this.mapBindingsToOptions(request.bindings, request.schema);
54750
- let result;
54751
- try {
54752
- result = await cf.deploy(deploymentId, request.code, env, {
54753
- ...deploymentOptions,
54754
- assetsPath: frontendAssetsPath,
54755
- keepAssets
54756
- });
54757
- } finally {
54758
- if (tempDir) {
54759
- const fs23 = await import("fs/promises");
54760
- fs23.rm(tempDir, { recursive: true, force: true }).catch((err2) => {
54761
- logger8.warn("Failed to clean up temp directory", { tempDir, err: err2 });
54762
- });
54763
- }
54764
- }
54765
- const codeHash = hasBackend ? await generateDeploymentHash(request.code) : null;
54766
- await this.saveDeployment(game.id, result.deploymentId, result.url, codeHash, result.resources);
54767
- if (hasBackend && request._headers) {
54768
- yield { type: "status", data: { message: "Configuring worker secrets" } };
54769
- await this.ensureApiKeyOnWorker(user, slug2, result.deploymentId, request._headers);
54770
- }
54771
- yield { type: "status", data: { message: "Finalizing deployment" } };
54772
- if (hasMetadata || hasFrontend) {
54773
- const updates = { updatedAt: new Date };
54774
- if (hasFrontend) {
54775
- updates.deploymentUrl = result.url;
54776
- }
54777
- if (hasMetadata) {
54778
- if (request.metadata.displayName)
54779
- updates.displayName = request.metadata.displayName;
54780
- if (request.metadata.platform)
54781
- updates.platform = request.metadata.platform;
54782
- if (request.metadata.metadata)
54783
- updates.metadata = request.metadata.metadata;
54784
- }
54785
- await db2.update(games).set(updates).where(eq(games.id, game.id));
54786
- }
54787
- const updatedGame = await db2.query.games.findFirst({
54788
- where: eq(games.id, game.id)
54789
- });
54790
- if (!updatedGame) {
54791
- throw new NotFoundError("Game", game.id);
54792
- }
54793
- logger8.info("Deploy completed", {
54794
- gameId: game.id,
54795
- slug: slug2,
54796
- deploymentId,
54797
- url: result.url,
54798
- hasBackend,
54799
- hasFrontend
54800
- });
54801
- try {
54802
- await this.ctx.services.alerts.notifyGameDeployment({
54803
- slug: slug2,
54804
- displayName: updatedGame.displayName,
54805
- url: result.url,
54806
- hasBackend,
54807
- hasFrontend,
54808
- developer: { id: user.id, email: user.email }
54809
- });
54810
- } catch (err2) {
54811
- logger8.warn("Failed to send deployment alert", { error: err2 });
54812
- }
54813
- yield { type: "complete", data: updatedGame };
54814
- }
54815
- mapBindingsToOptions(bindings, schema2) {
54816
- if (!bindings && !schema2)
54817
- return;
54818
- const workerBindings = {};
54819
- if (bindings?.database?.length)
54820
- workerBindings.d1 = bindings.database;
54821
- if (bindings?.keyValue?.length)
54822
- workerBindings.kv = bindings.keyValue;
54823
- if (bindings?.bucket?.length)
54824
- workerBindings.r2 = bindings.bucket;
54825
- const hasBindings = workerBindings.d1?.length || workerBindings.kv?.length || workerBindings.r2?.length;
54826
- return {
54827
- ...hasBindings && { bindings: workerBindings },
54828
- ...schema2 && { schema: schema2 }
54829
- };
54830
- }
54831
- async notifyDeploymentFailure(slug2, displayName, error, developer) {
54832
- await this.ctx.services.alerts.notifyDeploymentFailure({
54833
- slug: slug2,
54834
- displayName,
54835
- error,
54836
- developer
54837
- }).catch((err2) => {
54838
- logger8.warn("Failed to send failure alert", { error: err2 });
54839
- });
54840
- }
54841
- async saveDeployment(gameId, deploymentId, url, codeHash, resources) {
54842
- const db2 = this.ctx.db;
54843
- await db2.transaction(async (tx) => {
54844
- await tx.update(gameDeployments).set({ isActive: false }).where(eq(gameDeployments.gameId, gameId));
54845
- await tx.insert(gameDeployments).values({
54846
- gameId,
54847
- deploymentId,
54848
- provider: "cloudflare",
54849
- url,
54850
- codeHash,
54851
- resources,
54852
- isActive: true
54853
- });
54854
- });
54855
- logger8.info("Deployment saved", { gameId, deploymentId, resources });
54856
- }
54857
- }
54858
- var logger8;
54859
- var init_deploy_service = __esm(() => {
54860
- init_drizzle_orm();
54861
- init_tables_index();
54862
- init_src2();
54863
- init_config2();
54864
- init_errors();
54865
- init_deployment_util();
54866
- logger8 = log.scope("DeployService");
54867
- });
54868
-
54869
- class DeveloperService {
54870
- ctx;
54871
- constructor(ctx) {
54872
- this.ctx = ctx;
54873
- }
54874
- async apply(user) {
54875
- const db2 = this.ctx.db;
54876
- const fullUser = await db2.query.users.findFirst({
54877
- where: eq(users.id, user.id)
54878
- });
54879
- if (!fullUser) {
54880
- throw new NotFoundError("User", user.id);
54881
- }
54882
- const status = fullUser.developerStatus || "none";
54883
- if (status === "pending") {
54884
- throw new ConflictError("Developer application is already pending");
54885
- }
54886
- if (status === "approved") {
54887
- throw new ConflictError("User is already an approved developer");
54888
- }
54889
- if (status !== "none") {
54890
- logger9.error("Unexpected status during application", {
54891
- userId: user.id,
54892
- status
54893
- });
54894
- throw new ValidationError("Cannot apply with current developer status");
54895
- }
54896
- await db2.update(users).set({ developerStatus: "pending" }).where(eq(users.id, user.id));
54897
- logger9.info("Developer application submitted", {
54898
- userId: user.id,
54899
- newStatus: "pending"
54900
- });
54901
- this.ctx.services.alerts.notifyDeveloperApplication({
54902
- id: user.id,
54903
- email: fullUser.email
54904
- }).catch((err2) => {
54905
- logger9.warn("Failed to send alert", { error: err2 });
54906
- });
54907
- }
54908
- async getStatus(userId) {
54909
- const db2 = this.ctx.db;
54910
- const userData = await db2.query.users.findFirst({
54911
- where: eq(users.id, userId)
54912
- });
54913
- if (!userData) {
54914
- throw new NotFoundError("User", userId);
54915
- }
54916
- return userData.developerStatus || "none";
54917
- }
54918
- }
54919
- var logger9;
54920
- var init_developer_service = __esm(() => {
54921
- init_drizzle_orm();
54922
- init_tables_index();
54923
- init_src2();
54924
- init_errors();
54925
- logger9 = log.scope("DeveloperService");
54926
- });
54927
54626
  var init_d1 = __esm(() => {
54928
54627
  init_src2();
54929
54628
  });
@@ -55110,7 +54809,7 @@ var require_HeaderParser = __commonJS2((exports, module2) => {
55110
54809
  this.maxed = false;
55111
54810
  this.npairs = 0;
55112
54811
  this.maxHeaderPairs = getLimit(cfg, "maxHeaderPairs", 2000);
55113
- this.maxHeaderSize = getLimit(cfg, "maxHeaderSize", 81920);
54812
+ this.maxHeaderSize = getLimit(cfg, "maxHeaderSize", 80 * 1024);
55114
54813
  this.buffer = "";
55115
54814
  this.header = {};
55116
54815
  this.finished = false;
@@ -56173,13 +55872,13 @@ var require_multipart = __commonJS2((exports, module2) => {
56173
55872
  if (typeof boundary !== "string") {
56174
55873
  throw new Error("Multipart: Boundary not found");
56175
55874
  }
56176
- const fieldSizeLimit = getLimit(limits, "fieldSize", 1048576);
55875
+ const fieldSizeLimit = getLimit(limits, "fieldSize", 1 * 1024 * 1024);
56177
55876
  const fileSizeLimit = getLimit(limits, "fileSize", Infinity);
56178
55877
  const filesLimit = getLimit(limits, "files", Infinity);
56179
55878
  const fieldsLimit = getLimit(limits, "fields", Infinity);
56180
55879
  const partsLimit = getLimit(limits, "parts", Infinity);
56181
55880
  const headerPairsLimit = getLimit(limits, "headerPairs", 2000);
56182
- const headerSizeLimit = getLimit(limits, "headerSize", 81920);
55881
+ const headerSizeLimit = getLimit(limits, "headerSize", 80 * 1024);
56183
55882
  let nfiles = 0;
56184
55883
  let nfields = 0;
56185
55884
  let nends = 0;
@@ -56591,7 +56290,7 @@ var require_urlencoded = __commonJS2((exports, module2) => {
56591
56290
  const limits = cfg.limits;
56592
56291
  const parsedConType = cfg.parsedConType;
56593
56292
  this.boy = boy;
56594
- this.fieldSizeLimit = getLimit(limits, "fieldSize", 1048576);
56293
+ this.fieldSizeLimit = getLimit(limits, "fieldSize", 1 * 1024 * 1024);
56595
56294
  this.fieldNameSizeLimit = getLimit(limits, "fieldNameSize", 100);
56596
56295
  this.fieldsLimit = getLimit(limits, "fields", Infinity);
56597
56296
  let charset;
@@ -56857,6 +56556,9 @@ var init_r2 = __esm(() => {
56857
56556
  init_src2();
56858
56557
  init_utils5();
56859
56558
  });
56559
+ var init_queues = __esm(() => {
56560
+ init_src2();
56561
+ });
56860
56562
  function ownKeys(object, enumerableOnly) {
56861
56563
  var keys = Object.keys(object);
56862
56564
  if (Object.getOwnPropertySymbols) {
@@ -56985,6 +56687,7 @@ var init_namespaces = __esm(() => {
56985
56687
  init_d1();
56986
56688
  init_kv();
56987
56689
  init_r2();
56690
+ init_queues();
56988
56691
  init_workers2();
56989
56692
  });
56990
56693
  var init_client2 = __esm(() => {
@@ -56994,6 +56697,7 @@ var init_core = __esm(() => {
56994
56697
  init_client2();
56995
56698
  });
56996
56699
  var CUSTOM_DOMAINS_KV_NAME = "cademy-custom-domains";
56700
+ var QUEUE_NAME_PREFIX = "playcademy";
56997
56701
  var DEFAULT_COMPATIBILITY_DATE2;
56998
56702
  var GAME_WORKER_DOMAIN_PRODUCTION;
56999
56703
  var GAME_WORKER_DOMAIN_STAGING;
@@ -57649,7 +57353,7 @@ is not a problem with esbuild. You need to fix your environment instead.
57649
57353
  let responseCallbacks = {};
57650
57354
  let nextRequestID = 0;
57651
57355
  let nextBuildKey = 0;
57652
- let stdout22 = new Uint8Array(16384);
57356
+ let stdout22 = new Uint8Array(16 * 1024);
57653
57357
  let stdoutUsed = 0;
57654
57358
  let readFromStdout = (chunk) => {
57655
57359
  let limit = stdoutUsed + chunk.length;
@@ -57737,7 +57441,7 @@ is not a problem with esbuild. You need to fix your environment instead.
57737
57441
  isFirstPacket = false;
57738
57442
  let binaryVersion = String.fromCharCode(...bytes);
57739
57443
  if (binaryVersion !== "0.25.10") {
57740
- throw new Error(`Cannot start service: Host version "0.25.10" does not match binary version ${quote(binaryVersion)}`);
57444
+ throw new Error(`Cannot start service: Host version "${"0.25.10"}" does not match binary version ${quote(binaryVersion)}`);
57741
57445
  }
57742
57446
  return;
57743
57447
  }
@@ -57860,7 +57564,7 @@ is not a problem with esbuild. You need to fix your environment instead.
57860
57564
  });
57861
57565
  }
57862
57566
  };
57863
- if ((typeof input === "string" || input instanceof Uint8Array) && input.length > 1048576) {
57567
+ if ((typeof input === "string" || input instanceof Uint8Array) && input.length > 1024 * 1024) {
57864
57568
  let next = start2;
57865
57569
  start2 = () => fs32.writeFile(input, next);
57866
57570
  }
@@ -58926,12 +58630,12 @@ for your current platform.`);
58926
58630
  let pnpapi;
58927
58631
  try {
58928
58632
  pnpapi = (() => {
58929
- throw new Error("Cannot require module pnpapi");
58633
+ throw new Error("Cannot require module " + "pnpapi");
58930
58634
  })();
58931
58635
  } catch (e) {}
58932
58636
  if (pnpapi) {
58933
58637
  const root = pnpapi.getPackageInformation(pnpapi.topLevel).packageLocation;
58934
- const binTargetPath = path2.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-0.25.10-${path2.basename(subpath)}`);
58638
+ const binTargetPath = path2.join(root, "node_modules", ".cache", "esbuild", `pnpapi-${pkg.replace("/", "-")}-${"0.25.10"}-${path2.basename(subpath)}`);
58935
58639
  if (!fs23.existsSync(binTargetPath)) {
58936
58640
  fs23.mkdirSync(path2.dirname(binTargetPath), { recursive: true });
58937
58641
  fs23.copyFileSync(binPath, binTargetPath);
@@ -59139,7 +58843,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
59139
58843
  if (longLivedService)
59140
58844
  return longLivedService;
59141
58845
  let [command, args2] = esbuildCommandAndArgs();
59142
- let child = child_process.spawn(command, args2.concat("--service=0.25.10", "--ping"), {
58846
+ let child = child_process.spawn(command, args2.concat(`--service=${"0.25.10"}`, "--ping"), {
59143
58847
  windowsHide: true,
59144
58848
  stdio: ["pipe", "pipe", "inherit"],
59145
58849
  cwd: defaultWD
@@ -59247,11 +58951,11 @@ More information: The file containing the code for esbuild's JavaScript API (${_
59247
58951
  esbuild: node_exports
59248
58952
  });
59249
58953
  callback(service);
59250
- let stdout22 = child_process.execFileSync(command, args2.concat("--service=0.25.10"), {
58954
+ let stdout22 = child_process.execFileSync(command, args2.concat(`--service=${"0.25.10"}`), {
59251
58955
  cwd: defaultWD,
59252
58956
  windowsHide: true,
59253
58957
  input: stdin,
59254
- maxBuffer: +process.env.ESBUILD_MAX_BUFFER || 16777216
58958
+ maxBuffer: +process.env.ESBUILD_MAX_BUFFER || 16 * 1024 * 1024
59255
58959
  });
59256
58960
  readFromStdout(stdout22);
59257
58961
  afterClose(null);
@@ -59389,8 +59093,20 @@ var esbuild;
59389
59093
  var init_bundler = __esm(() => {
59390
59094
  esbuild = __toESM2(require_main2(), 1);
59391
59095
  });
59096
+ var init_config3 = __esm(() => {
59097
+ init_constants2();
59098
+ });
59099
+ var init_config4 = __esm(() => {
59100
+ init_constants2();
59101
+ });
59102
+ var init_config5 = __esm(() => {
59103
+ init_constants2();
59104
+ });
59392
59105
  var init_workers3 = __esm(() => {
59393
59106
  init_bundler();
59107
+ init_config3();
59108
+ init_config4();
59109
+ init_config5();
59394
59110
  });
59395
59111
  var init_setup = () => {};
59396
59112
  var init_teardown = __esm(() => {
@@ -59407,6 +59123,332 @@ var init_playcademy = __esm(() => {
59407
59123
  init_infra();
59408
59124
  });
59409
59125
 
59126
+ class DeployService {
59127
+ ctx;
59128
+ constructor(ctx) {
59129
+ this.ctx = ctx;
59130
+ }
59131
+ getCloudflare() {
59132
+ const cf = this.ctx.cloudflare;
59133
+ if (!cf) {
59134
+ logger8.error("Cloudflare provider not available");
59135
+ throw new ValidationError("Cloudflare provider not configured");
59136
+ }
59137
+ return cf;
59138
+ }
59139
+ async createApiKey(user, slug2, keyName) {
59140
+ const { id, key: apiKey } = await this.ctx.providers.auth.createApiKey({
59141
+ userId: user.id,
59142
+ name: keyName,
59143
+ expiresIn: null,
59144
+ permissions: {
59145
+ games: [`read:${slug2}`, `write:${slug2}`]
59146
+ }
59147
+ });
59148
+ logger8.info("Created new game-scoped API key", {
59149
+ userId: user.id,
59150
+ slug: slug2,
59151
+ keyId: id
59152
+ });
59153
+ return apiKey;
59154
+ }
59155
+ async regenerateApiKey(user, slug2, existingKeyId, keyName) {
59156
+ if (existingKeyId) {
59157
+ logger8.info("Regenerating API key", {
59158
+ userId: user.id,
59159
+ slug: slug2,
59160
+ oldKeyId: existingKeyId
59161
+ });
59162
+ try {
59163
+ await this.ctx.providers.auth.deleteApiKey(existingKeyId);
59164
+ logger8.debug("Revoked old API key", { keyId: existingKeyId });
59165
+ } catch (error) {
59166
+ logger8.warn("Failed to revoke old API key", {
59167
+ keyId: existingKeyId,
59168
+ error
59169
+ });
59170
+ }
59171
+ }
59172
+ return this.createApiKey(user, slug2, keyName);
59173
+ }
59174
+ async ensureApiKeyOnWorker(user, slug2, deploymentId, headers) {
59175
+ const cf = this.getCloudflare();
59176
+ const keyName = getGameWorkerApiKeyName(slug2);
59177
+ const existingKeys = await this.ctx.providers.auth.listApiKeys(headers);
59178
+ const existingKey = existingKeys.find((k) => k.name === keyName);
59179
+ let apiKey;
59180
+ if (!existingKey) {
59181
+ apiKey = await this.createApiKey(user, slug2, keyName);
59182
+ } else {
59183
+ try {
59184
+ const workerSecrets = await cf.listSecrets(deploymentId);
59185
+ if (workerSecrets.includes("PLAYCADEMY_API_KEY")) {
59186
+ logger8.debug("API key already on worker", { slug: slug2, deploymentId });
59187
+ return;
59188
+ }
59189
+ } catch (error) {
59190
+ logger8.warn("Could not check worker secrets, will regenerate key", { error });
59191
+ }
59192
+ apiKey = await this.regenerateApiKey(user, slug2, existingKey.id, keyName);
59193
+ }
59194
+ await cf.setSecrets(deploymentId, { PLAYCADEMY_API_KEY: apiKey });
59195
+ logger8.info("Set API key on worker", { slug: slug2, deploymentId });
59196
+ }
59197
+ async* deploy(slug2, request, user, uploadDeps, extractZip) {
59198
+ const cf = this.getCloudflare();
59199
+ const db2 = this.ctx.db;
59200
+ const game = await this.ctx.services.game.validateDeveloperAccessBySlug(user, slug2);
59201
+ const hasBackend = !!request.code;
59202
+ const hasFrontend = !!request.uploadToken;
59203
+ const hasMetadata = !!request.metadata;
59204
+ logger8.debug("Deploying game", { slug: slug2, hasBackend, hasFrontend, hasMetadata });
59205
+ if (!hasBackend && !hasFrontend && !hasMetadata) {
59206
+ throw new ValidationError("Must provide at least one of: uploadToken (frontend), code (backend), or metadata");
59207
+ }
59208
+ const isProd = isProduction2(this.ctx.config);
59209
+ const deploymentId = getDeploymentId(slug2, isProd);
59210
+ if (!this.ctx.config.baseUrl) {
59211
+ logger8.error("baseUrl not configured for deployment", { slug: slug2 });
59212
+ throw new ValidationError("baseUrl is not configured");
59213
+ }
59214
+ const playcademyBaseUrl = this.ctx.config.baseUrl;
59215
+ let frontendAssetsPath;
59216
+ let tempDir;
59217
+ if (hasFrontend) {
59218
+ if (!uploadDeps || !extractZip) {
59219
+ logger8.error("Upload dependencies not available for frontend deployment", { slug: slug2 });
59220
+ throw new ValidationError("Upload dependencies not configured for frontend deployment");
59221
+ }
59222
+ yield { type: "status", data: { message: "Fetching temporary files" } };
59223
+ const frontendZip = await uploadDeps.getObjectAsByteArray(request.uploadToken);
59224
+ if (!frontendZip || frontendZip.length === 0) {
59225
+ logger8.error("Frontend upload empty or not found", {
59226
+ slug: slug2,
59227
+ uploadToken: request.uploadToken
59228
+ });
59229
+ throw new ValidationError("Uploaded file is empty or not found");
59230
+ }
59231
+ yield { type: "status", data: { message: "Extracting assets" } };
59232
+ const os2 = await import("os");
59233
+ const path2 = await import("path");
59234
+ tempDir = path2.join(os2.tmpdir(), `playcademy-deploy-${game.id}-${Date.now()}`);
59235
+ frontendAssetsPath = path2.join(tempDir, "dist");
59236
+ await extractZip(frontendZip, frontendAssetsPath);
59237
+ uploadDeps.deleteObject(request.uploadToken).catch(() => {
59238
+ logger8.warn("Failed to delete temp object", { key: request.uploadToken });
59239
+ });
59240
+ }
59241
+ const env = {
59242
+ GAME_ID: game.id,
59243
+ PLAYCADEMY_BASE_URL: playcademyBaseUrl
59244
+ };
59245
+ const deployMsg = hasBackend ? "Deploying backend code" : "Deploying to platform";
59246
+ yield { type: "status", data: { message: deployMsg } };
59247
+ const keepAssets = hasBackend && !hasFrontend;
59248
+ const deploymentOptions = this.mapBindingsToOptions(deploymentId, request.bindings, request.schema);
59249
+ let result;
59250
+ try {
59251
+ result = await cf.deploy(deploymentId, request.code, env, {
59252
+ ...deploymentOptions,
59253
+ assetsPath: frontendAssetsPath,
59254
+ keepAssets
59255
+ });
59256
+ } finally {
59257
+ if (tempDir) {
59258
+ const fs23 = await import("fs/promises");
59259
+ fs23.rm(tempDir, { recursive: true, force: true }).catch((err2) => {
59260
+ logger8.warn("Failed to clean up temp directory", { tempDir, err: err2 });
59261
+ });
59262
+ }
59263
+ }
59264
+ const codeHash = hasBackend ? await generateDeploymentHash(request.code) : null;
59265
+ await this.saveDeployment(game.id, result.deploymentId, result.url, codeHash, result.resources);
59266
+ if (hasBackend && request._headers) {
59267
+ yield { type: "status", data: { message: "Configuring worker secrets" } };
59268
+ await this.ensureApiKeyOnWorker(user, slug2, result.deploymentId, request._headers);
59269
+ }
59270
+ yield { type: "status", data: { message: "Finalizing deployment" } };
59271
+ if (hasMetadata || hasFrontend) {
59272
+ const updates = { updatedAt: new Date };
59273
+ if (hasFrontend) {
59274
+ updates.deploymentUrl = result.url;
59275
+ }
59276
+ if (hasMetadata) {
59277
+ if (request.metadata.displayName)
59278
+ updates.displayName = request.metadata.displayName;
59279
+ if (request.metadata.platform)
59280
+ updates.platform = request.metadata.platform;
59281
+ if (request.metadata.metadata)
59282
+ updates.metadata = request.metadata.metadata;
59283
+ }
59284
+ await db2.update(games).set(updates).where(eq(games.id, game.id));
59285
+ }
59286
+ const updatedGame = await db2.query.games.findFirst({
59287
+ where: eq(games.id, game.id)
59288
+ });
59289
+ if (!updatedGame) {
59290
+ throw new NotFoundError("Game", game.id);
59291
+ }
59292
+ logger8.info("Deploy completed", {
59293
+ gameId: game.id,
59294
+ slug: slug2,
59295
+ deploymentId,
59296
+ url: result.url,
59297
+ hasBackend,
59298
+ hasFrontend
59299
+ });
59300
+ try {
59301
+ await this.ctx.services.alerts.notifyGameDeployment({
59302
+ slug: slug2,
59303
+ displayName: updatedGame.displayName,
59304
+ url: result.url,
59305
+ hasBackend,
59306
+ hasFrontend,
59307
+ developer: { id: user.id, email: user.email }
59308
+ });
59309
+ } catch (err2) {
59310
+ logger8.warn("Failed to send deployment alert", { error: err2 });
59311
+ }
59312
+ yield { type: "complete", data: updatedGame };
59313
+ }
59314
+ mapBindingsToOptions(deploymentId, bindings, schema2) {
59315
+ if (!bindings && !schema2)
59316
+ return;
59317
+ const workerBindings = {};
59318
+ if (bindings?.database?.length)
59319
+ workerBindings.d1 = bindings.database;
59320
+ if (bindings?.keyValue?.length)
59321
+ workerBindings.kv = bindings.keyValue;
59322
+ if (bindings?.bucket?.length)
59323
+ workerBindings.r2 = bindings.bucket;
59324
+ if (bindings?.queues) {
59325
+ const queueNameByKey = new Map;
59326
+ const toBindingName = (queueKey) => `${queueKey.replace(/-/g, "_").toUpperCase()}_QUEUE`;
59327
+ const toQueueName = (queueKey) => `${QUEUE_NAME_PREFIX}-${deploymentId}--${queueKey}`;
59328
+ for (const queueKey of Object.keys(bindings.queues)) {
59329
+ queueNameByKey.set(queueKey, toQueueName(queueKey));
59330
+ }
59331
+ workerBindings.queues = Object.entries(bindings.queues).map(([queueKey, queueConfig]) => {
59332
+ const config2 = queueConfig === true ? {} : queueConfig;
59333
+ const deadLetterQueue = config2.deadLetterQueue && queueNameByKey.get(config2.deadLetterQueue) ? queueNameByKey.get(config2.deadLetterQueue) : undefined;
59334
+ return {
59335
+ bindingName: toBindingName(queueKey),
59336
+ queueName: toQueueName(queueKey),
59337
+ settings: {
59338
+ maxBatchSize: config2.maxBatchSize,
59339
+ maxRetries: config2.maxRetries,
59340
+ maxBatchTimeout: config2.maxBatchTimeout,
59341
+ maxConcurrency: config2.maxConcurrency,
59342
+ retryDelay: config2.retryDelay
59343
+ },
59344
+ deadLetterQueue
59345
+ };
59346
+ });
59347
+ }
59348
+ const hasBindings = workerBindings.d1?.length || workerBindings.kv?.length || workerBindings.r2?.length || workerBindings.queues?.length;
59349
+ return {
59350
+ ...hasBindings && { bindings: workerBindings },
59351
+ ...schema2 && { schema: schema2 }
59352
+ };
59353
+ }
59354
+ async notifyDeploymentFailure(slug2, displayName, error, developer) {
59355
+ await this.ctx.services.alerts.notifyDeploymentFailure({
59356
+ slug: slug2,
59357
+ displayName,
59358
+ error,
59359
+ developer
59360
+ }).catch((err2) => {
59361
+ logger8.warn("Failed to send failure alert", { error: err2 });
59362
+ });
59363
+ }
59364
+ async saveDeployment(gameId, deploymentId, url, codeHash, resources) {
59365
+ const db2 = this.ctx.db;
59366
+ await db2.transaction(async (tx) => {
59367
+ await tx.update(gameDeployments).set({ isActive: false }).where(eq(gameDeployments.gameId, gameId));
59368
+ await tx.insert(gameDeployments).values({
59369
+ gameId,
59370
+ deploymentId,
59371
+ provider: "cloudflare",
59372
+ url,
59373
+ codeHash,
59374
+ resources,
59375
+ isActive: true
59376
+ });
59377
+ });
59378
+ logger8.info("Deployment saved", { gameId, deploymentId, resources });
59379
+ }
59380
+ }
59381
+ var logger8;
59382
+ var init_deploy_service = __esm(() => {
59383
+ init_drizzle_orm();
59384
+ init_playcademy();
59385
+ init_tables_index();
59386
+ init_src2();
59387
+ init_config2();
59388
+ init_errors();
59389
+ init_deployment_util();
59390
+ logger8 = log.scope("DeployService");
59391
+ });
59392
+
59393
+ class DeveloperService {
59394
+ ctx;
59395
+ constructor(ctx) {
59396
+ this.ctx = ctx;
59397
+ }
59398
+ async apply(user) {
59399
+ const db2 = this.ctx.db;
59400
+ const fullUser = await db2.query.users.findFirst({
59401
+ where: eq(users.id, user.id)
59402
+ });
59403
+ if (!fullUser) {
59404
+ throw new NotFoundError("User", user.id);
59405
+ }
59406
+ const status = fullUser.developerStatus || "none";
59407
+ if (status === "pending") {
59408
+ throw new ConflictError("Developer application is already pending");
59409
+ }
59410
+ if (status === "approved") {
59411
+ throw new ConflictError("User is already an approved developer");
59412
+ }
59413
+ if (status !== "none") {
59414
+ logger9.error("Unexpected status during application", {
59415
+ userId: user.id,
59416
+ status
59417
+ });
59418
+ throw new ValidationError("Cannot apply with current developer status");
59419
+ }
59420
+ await db2.update(users).set({ developerStatus: "pending" }).where(eq(users.id, user.id));
59421
+ logger9.info("Developer application submitted", {
59422
+ userId: user.id,
59423
+ newStatus: "pending"
59424
+ });
59425
+ this.ctx.services.alerts.notifyDeveloperApplication({
59426
+ id: user.id,
59427
+ email: fullUser.email
59428
+ }).catch((err2) => {
59429
+ logger9.warn("Failed to send alert", { error: err2 });
59430
+ });
59431
+ }
59432
+ async getStatus(userId) {
59433
+ const db2 = this.ctx.db;
59434
+ const userData = await db2.query.users.findFirst({
59435
+ where: eq(users.id, userId)
59436
+ });
59437
+ if (!userData) {
59438
+ throw new NotFoundError("User", userId);
59439
+ }
59440
+ return userData.developerStatus || "none";
59441
+ }
59442
+ }
59443
+ var logger9;
59444
+ var init_developer_service = __esm(() => {
59445
+ init_drizzle_orm();
59446
+ init_tables_index();
59447
+ init_src2();
59448
+ init_errors();
59449
+ logger9 = log.scope("DeveloperService");
59450
+ });
59451
+
59410
59452
  class DomainService {
59411
59453
  ctx;
59412
59454
  constructor(ctx) {
@@ -129264,7 +129306,18 @@ var init_schemas3 = __esm(() => {
129264
129306
  bindings: exports_external.object({
129265
129307
  database: exports_external.array(exports_external.string()).optional(),
129266
129308
  keyValue: exports_external.array(exports_external.string()).optional(),
129267
- bucket: exports_external.array(exports_external.string()).optional()
129309
+ bucket: exports_external.array(exports_external.string()).optional(),
129310
+ queues: exports_external.record(exports_external.string(), exports_external.union([
129311
+ exports_external.literal(true),
129312
+ exports_external.object({
129313
+ maxBatchSize: exports_external.number().int().positive().optional(),
129314
+ maxRetries: exports_external.number().int().nonnegative().optional(),
129315
+ maxBatchTimeout: exports_external.number().int().positive().optional(),
129316
+ maxConcurrency: exports_external.number().int().positive().optional(),
129317
+ retryDelay: exports_external.number().int().nonnegative().optional(),
129318
+ deadLetterQueue: exports_external.string().optional()
129319
+ })
129320
+ ])).optional()
129268
129321
  }).optional(),
129269
129322
  schema: exports_external.object({
129270
129323
  sql: exports_external.string(),
@@ -143427,7 +143480,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
143427
143480
  // package.json
143428
143481
  var package_default2 = {
143429
143482
  name: "@playcademy/vite-plugin",
143430
- version: "0.2.13",
143483
+ version: "0.2.14",
143431
143484
  type: "module",
143432
143485
  exports: {
143433
143486
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "archiver": "^7.0.1",
24
24
  "picocolors": "^1.1.1",
25
- "playcademy": "0.16.11"
25
+ "playcademy": "0.16.12"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@inquirer/prompts": "^7.8.6",