@playcademy/vite-plugin 1.0.1-beta.4 → 1.1.0-beta.1

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 CHANGED
@@ -23746,7 +23746,7 @@ import path from "node:path";
23746
23746
  // package.json
23747
23747
  var package_default = {
23748
23748
  name: "@playcademy/vite-plugin",
23749
- version: "1.0.1-beta.4",
23749
+ version: "1.1.0-beta.1",
23750
23750
  type: "module",
23751
23751
  exports: {
23752
23752
  ".": {
@@ -23974,6 +23974,172 @@ function configResolvedHook(resolvedConfig, context) {
23974
23974
  // src/hooks/configure-server.ts
23975
23975
  import { DEFAULT_PORTS as DEFAULT_PORTS3 } from "playcademy/constants";
23976
23976
 
23977
+ // src/lib/assets/index.ts
23978
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
23979
+ import {
23980
+ ASSET_DEV_ROUTE_PREFIX,
23981
+ hasCustomAssetsRoute,
23982
+ openLocalAssetBucket,
23983
+ readLocalAsset,
23984
+ readLocalManifestScript
23985
+ } from "playcademy/bucket";
23986
+ import { loadPlaycademyConfig } from "playcademy/utils";
23987
+
23988
+ // ../utils/src/vite-logger.ts
23989
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
23990
+ var { bold, cyan, dim } = import_picocolors2.default;
23991
+ function formatTimestamp() {
23992
+ const now = new Date;
23993
+ const hours = now.getHours();
23994
+ const minutes = now.getMinutes().toString().padStart(2, "0");
23995
+ const seconds = now.getSeconds().toString().padStart(2, "0");
23996
+ const ampm = hours >= 12 ? "PM" : "AM";
23997
+ const displayHours = hours % 12 || 12;
23998
+ return dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
23999
+ }
24000
+ function createLogPrefix(entity, domain) {
24001
+ const timestamp = formatTimestamp();
24002
+ const label = bold(cyan(`[${entity}]`));
24003
+ if (domain) {
24004
+ return `${timestamp} ${label} ${dim(`(${domain})`)}`;
24005
+ }
24006
+ return `${timestamp} ${label}`;
24007
+ }
24008
+
24009
+ // src/lib/logging/adapter.ts
24010
+ function createLoggerAdapter(domain) {
24011
+ return {
24012
+ info: (msg) => console.log(`${createLogPrefix("playcademy", domain)} ${msg}`),
24013
+ warn: (msg) => console.warn(`${createLogPrefix("playcademy", domain)} ${msg}`),
24014
+ error: (msg) => console.error(`${createLogPrefix("playcademy", domain)} ${msg}`)
24015
+ };
24016
+ }
24017
+ // src/lib/logging/utils.ts
24018
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
24019
+
24020
+ // ../utils/src/string.ts
24021
+ function pluralize(count, singular, pluralForm) {
24022
+ return count === 1 ? singular : pluralForm || `${singular}s`;
24023
+ }
24024
+
24025
+ // src/lib/logging/utils.ts
24026
+ function createBackendBannerOptions(backendPort, vitePort) {
24027
+ if (!backendPort) {
24028
+ return;
24029
+ }
24030
+ return { port: backendPort, vitePort };
24031
+ }
24032
+ function createTimebackBannerOptions(timebackMode, courseCount, enrolledCount) {
24033
+ if (!timebackMode || !courseCount) {
24034
+ return;
24035
+ }
24036
+ return {
24037
+ courseCount,
24038
+ enrolledCount: enrolledCount ?? courseCount,
24039
+ mode: timebackMode
24040
+ };
24041
+ }
24042
+ function printBanner(viteConfig, options) {
24043
+ const INDENT = " ".repeat(2);
24044
+ const { version, gameName, sandbox, backend, timeback } = options;
24045
+ viteConfig.logger.info("");
24046
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green(import_picocolors3.bold("PLAYCADEMY"))} ${import_picocolors3.green(`v${version}`)}`);
24047
+ viteConfig.logger.info("");
24048
+ if (gameName) {
24049
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Project:")} ${import_picocolors3.cyan(gameName)}`);
24050
+ }
24051
+ if (sandbox?.enabled) {
24052
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Sandbox:")} ${import_picocolors3.cyan(`http://localhost:${import_picocolors3.bold(sandbox.port.toString())}/api`)}`);
24053
+ } else if (sandbox) {
24054
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Sandbox:")} ${import_picocolors3.cyan("Disabled")}`);
24055
+ }
24056
+ if (backend) {
24057
+ const backendUrl = backend.vitePort ? `http://localhost:${import_picocolors3.bold(backend.vitePort.toString())}/api ${import_picocolors3.dim(`(via ${backend.port})`)}` : `http://localhost:${import_picocolors3.bold(backend.port.toString())}/api`;
24058
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Backend:")} ${import_picocolors3.cyan(backendUrl)}`);
24059
+ }
24060
+ if (timeback && timeback.courseCount > 0) {
24061
+ const enrollmentInfo = timeback.enrolledCount !== timeback.courseCount ? ` / ${timeback.enrolledCount} enrolled` : "";
24062
+ const label = `${timeback.courseCount} ${pluralize(timeback.courseCount, "course")}${enrollmentInfo} ${import_picocolors3.dim(`(${timeback.mode})`)}`;
24063
+ viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Timeback:")} ${import_picocolors3.cyan(label)}`);
24064
+ }
24065
+ viteConfig.logger.info("");
24066
+ }
24067
+ // src/lib/assets/index.ts
24068
+ var logger = createLoggerAdapter("assets");
24069
+ async function loadConfigSafely(configPath) {
24070
+ try {
24071
+ return await loadPlaycademyConfig(configPath);
24072
+ } catch {
24073
+ return null;
24074
+ }
24075
+ }
24076
+ var session = null;
24077
+ var hasWarnedNoManifest = false;
24078
+ async function getBucket() {
24079
+ if (!session) {
24080
+ session = await openLocalAssetBucket();
24081
+ }
24082
+ return session.bucket;
24083
+ }
24084
+ async function disposeAssetBucket() {
24085
+ if (session) {
24086
+ await session.dispose();
24087
+ session = null;
24088
+ }
24089
+ hasWarnedNoManifest = false;
24090
+ }
24091
+ async function getDevManifestScript(configPath) {
24092
+ const config = await loadConfigSafely(configPath);
24093
+ if (!config?.integrations?.bucket) {
24094
+ return null;
24095
+ }
24096
+ const script = await readLocalManifestScript(await getBucket());
24097
+ if (!script) {
24098
+ if (!hasWarnedNoManifest) {
24099
+ hasWarnedNoManifest = true;
24100
+ logger.warn(`No local asset manifest found (run ${import_picocolors4.greenBright("playcademy bucket sync")} to serve bucket assets in local dev)`);
24101
+ }
24102
+ return null;
24103
+ }
24104
+ return script;
24105
+ }
24106
+ async function registerAssetMiddleware(server, projectRoot, configPath) {
24107
+ const config = await loadConfigSafely(configPath);
24108
+ if (!config?.integrations?.bucket) {
24109
+ return;
24110
+ }
24111
+ if (hasCustomAssetsRoute(projectRoot, config)) {
24112
+ return;
24113
+ }
24114
+ server.middlewares.use(ASSET_DEV_ROUTE_PREFIX, (req, res, next) => {
24115
+ serveAsset(req, res, next);
24116
+ });
24117
+ }
24118
+ async function serveAsset(req, res, next) {
24119
+ const raw = (req.url ?? "").split("?")[0].replace(/^\/+/, "");
24120
+ let servingKey;
24121
+ try {
24122
+ servingKey = decodeURIComponent(raw);
24123
+ } catch {
24124
+ res.statusCode = 400;
24125
+ res.end("Malformed asset path");
24126
+ return;
24127
+ }
24128
+ if (!servingKey) {
24129
+ next();
24130
+ return;
24131
+ }
24132
+ const asset = await readLocalAsset(await getBucket(), servingKey);
24133
+ if (!asset) {
24134
+ next();
24135
+ return;
24136
+ }
24137
+ res.statusCode = 200;
24138
+ res.setHeader("Content-Type", asset.contentType);
24139
+ res.setHeader("Cache-Control", "no-store");
24140
+ res.end(Buffer.from(asset.body));
24141
+ }
24142
+
23977
24143
  // src/server/state.ts
23978
24144
  var serverState = {
23979
24145
  sandbox: null,
@@ -24016,6 +24182,9 @@ function setPlatformRoleOverride(role) {
24016
24182
 
24017
24183
  // src/server/cleanup.ts
24018
24184
  async function cleanupServers() {
24185
+ try {
24186
+ await disposeAssetBucket();
24187
+ } catch {}
24019
24188
  if (serverState.backend) {
24020
24189
  try {
24021
24190
  await serverState.backend.server.dispose();
@@ -24053,31 +24222,10 @@ function getNextMode(mode) {
24053
24222
  }
24054
24223
 
24055
24224
  // src/server/recreate-sandbox.ts
24056
- var import_picocolors7 = __toESM(require_picocolors(), 1);
24057
-
24058
- // ../utils/src/vite-logger.ts
24059
- var import_picocolors2 = __toESM(require_picocolors(), 1);
24060
- var { bold, cyan, dim } = import_picocolors2.default;
24061
- function formatTimestamp() {
24062
- const now = new Date;
24063
- const hours = now.getHours();
24064
- const minutes = now.getMinutes().toString().padStart(2, "0");
24065
- const seconds = now.getSeconds().toString().padStart(2, "0");
24066
- const ampm = hours >= 12 ? "PM" : "AM";
24067
- const displayHours = hours % 12 || 12;
24068
- return dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
24069
- }
24070
- function createLogPrefix(entity, domain) {
24071
- const timestamp = formatTimestamp();
24072
- const label = bold(cyan(`[${entity}]`));
24073
- if (domain) {
24074
- return `${timestamp} ${label} ${dim(`(${domain})`)}`;
24075
- }
24076
- return `${timestamp} ${label}`;
24077
- }
24225
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
24078
24226
 
24079
24227
  // src/lib/sandbox/server.ts
24080
- var import_picocolors6 = __toESM(require_picocolors(), 1);
24228
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
24081
24229
  import { DEFAULT_PORTS as DEFAULT_PORTS2 } from "playcademy/constants";
24082
24230
 
24083
24231
  // ../sandbox/dist/server.js
@@ -24089,7 +24237,7 @@ import { Readable } from "stream";
24089
24237
  import crypto2 from "crypto";
24090
24238
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
24091
24239
  import { createServer } from "node:net";
24092
- import { homedir as homedir2 } from "node:os";
24240
+ import * as os from "node:os";
24093
24241
  import { join } from "node:path";
24094
24242
  import { mkdir, writeFile } from "fs/promises";
24095
24243
  import { join as join2 } from "path";
@@ -24103,7 +24251,7 @@ import * as o3 from "path";
24103
24251
  import fs22 from "node:fs";
24104
24252
  import { dirname, isAbsolute, join as join4 } from "node:path";
24105
24253
  import process2 from "process";
24106
- import os from "os";
24254
+ import os2 from "os";
24107
24255
  import tty from "tty";
24108
24256
  import { randomUUID } from "crypto";
24109
24257
  import fs3 from "node:fs";
@@ -25100,7 +25248,7 @@ var init_dist = __esm(() => {
25100
25248
  outgoingEnded = Symbol("outgoingEnded");
25101
25249
  });
25102
25250
  function getRegistryPath() {
25103
- const home = homedir2();
25251
+ const home = os.homedir();
25104
25252
  const dir = join(home, ".playcademy");
25105
25253
  if (!existsSync(dir)) {
25106
25254
  mkdirSync(dir, { recursive: true });
@@ -25194,7 +25342,7 @@ var package_default2;
25194
25342
  var init_package = __esm(() => {
25195
25343
  package_default2 = {
25196
25344
  name: "@playcademy/sandbox",
25197
- version: "0.5.1-beta.4",
25345
+ version: "0.5.1-beta.5",
25198
25346
  description: "Local development server for Playcademy game development",
25199
25347
  type: "module",
25200
25348
  exports: {
@@ -25646,7 +25794,7 @@ function createLogger(scopeName) {
25646
25794
  var colors;
25647
25795
  var levelPriority;
25648
25796
  var customHandler;
25649
- var log;
25797
+ var log2;
25650
25798
  var init_src2 = __esm(() => {
25651
25799
  colors = {
25652
25800
  reset: "\x1B[0m",
@@ -25664,7 +25812,7 @@ var init_src2 = __esm(() => {
25664
25812
  warn: 2,
25665
25813
  error: 3
25666
25814
  };
25667
- log = createLogger();
25815
+ log2 = createLogger();
25668
25816
  });
25669
25817
  function formatSSE(eventType, data) {
25670
25818
  const payload = `event: ${eventType}
@@ -25701,7 +25849,7 @@ function toApiError(err2) {
25701
25849
  return ApiError.fromDomain(err2);
25702
25850
  }
25703
25851
  const message = err2 instanceof Error ? err2.message : "Unknown error";
25704
- log.error("[api-hono] Unexpected error", { error: err2 });
25852
+ log2.error("[api-hono] Unexpected error", { error: err2 });
25705
25853
  return ApiError.internal(message);
25706
25854
  }
25707
25855
  function errorResponse(c, err2) {
@@ -25742,7 +25890,7 @@ function createStreamHandle(buildContext) {
25742
25890
  } catch (error) {
25743
25891
  const apiError = toApiError(error);
25744
25892
  const logLevel = apiError.status >= 500 ? "error" : "warn";
25745
- log[logLevel]("[api-hono] Stream error", {
25893
+ log2[logLevel]("[api-hono] Stream error", {
25746
25894
  status: apiError.status,
25747
25895
  code: apiError.code,
25748
25896
  message: apiError.message,
@@ -33225,9 +33373,9 @@ var init_delete = __esm(() => {
33225
33373
  init_tracing();
33226
33374
  init_utils();
33227
33375
  PgDeleteBase = class PgDeleteBase2 extends QueryPromise {
33228
- constructor(table2, session, dialect, withList) {
33376
+ constructor(table2, session2, dialect, withList) {
33229
33377
  super();
33230
- this.session = session;
33378
+ this.session = session2;
33231
33379
  this.dialect = dialect;
33232
33380
  this.config = { table: table2, withList };
33233
33381
  }
@@ -33365,7 +33513,7 @@ var init_dialect = __esm(() => {
33365
33513
  constructor(config2) {
33366
33514
  this.casing = new CasingCache(config2?.casing);
33367
33515
  }
33368
- async migrate(migrations, session, config2) {
33516
+ async migrate(migrations, session2, config2) {
33369
33517
  const migrationsTable = typeof config2 === "string" ? "__drizzle_migrations" : config2.migrationsTable ?? "__drizzle_migrations";
33370
33518
  const migrationsSchema = typeof config2 === "string" ? "drizzle" : config2.migrationsSchema ?? "drizzle";
33371
33519
  const migrationTableCreate = sql`
@@ -33375,11 +33523,11 @@ var init_dialect = __esm(() => {
33375
33523
  created_at bigint
33376
33524
  )
33377
33525
  `;
33378
- await session.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(migrationsSchema)}`);
33379
- await session.execute(migrationTableCreate);
33380
- const dbMigrations = await session.all(sql`select id, hash, created_at from ${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)} order by created_at desc limit 1`);
33526
+ await session2.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(migrationsSchema)}`);
33527
+ await session2.execute(migrationTableCreate);
33528
+ const dbMigrations = await session2.all(sql`select id, hash, created_at from ${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)} order by created_at desc limit 1`);
33381
33529
  const lastDbMigration = dbMigrations[0];
33382
- await session.transaction(async (tx) => {
33530
+ await session2.transaction(async (tx) => {
33383
33531
  for await (const migration of migrations) {
33384
33532
  if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
33385
33533
  for (const stmt of migration.sql) {
@@ -34026,7 +34174,7 @@ var init_select2 = __esm(() => {
34026
34174
  isPartialSelect;
34027
34175
  session;
34028
34176
  dialect;
34029
- constructor({ table: table2, fields, isPartialSelect, session, dialect, withList, distinct }) {
34177
+ constructor({ table: table2, fields, isPartialSelect, session: session2, dialect, withList, distinct }) {
34030
34178
  super();
34031
34179
  this.config = {
34032
34180
  withList,
@@ -34036,7 +34184,7 @@ var init_select2 = __esm(() => {
34036
34184
  setOperators: []
34037
34185
  };
34038
34186
  this.isPartialSelect = isPartialSelect;
34039
- this.session = session;
34187
+ this.session = session2;
34040
34188
  this.dialect = dialect;
34041
34189
  this._ = {
34042
34190
  selectedFields: fields
@@ -34200,13 +34348,13 @@ var init_select2 = __esm(() => {
34200
34348
  PgSelectBase = class PgSelectBase2 extends PgSelectQueryBuilderBase {
34201
34349
  static [entityKind] = "PgSelect";
34202
34350
  _prepare(name2) {
34203
- const { session, config: config2, dialect, joinsNotNullableMap, authToken } = this;
34204
- if (!session) {
34351
+ const { session: session2, config: config2, dialect, joinsNotNullableMap, authToken } = this;
34352
+ if (!session2) {
34205
34353
  throw new Error("Cannot execute a query on a query builder. Please use a database instance instead.");
34206
34354
  }
34207
34355
  return tracer.startActiveSpan("drizzle.prepareQuery", () => {
34208
34356
  const fieldsList = orderSelectedFields(config2.fields);
34209
- const query = session.prepareQuery(dialect.sqlToQuery(this.getSQL()), fieldsList, name2, true);
34357
+ const query = session2.prepareQuery(dialect.sqlToQuery(this.getSQL()), fieldsList, name2, true);
34210
34358
  query.joinsNotNullableMap = joinsNotNullableMap;
34211
34359
  return query.setToken(authToken);
34212
34360
  });
@@ -34329,9 +34477,9 @@ var init_insert = __esm(() => {
34329
34477
  init_utils();
34330
34478
  init_query_builder2();
34331
34479
  PgInsertBuilder = class PgInsertBuilder2 {
34332
- constructor(table2, session, dialect, withList, overridingSystemValue_) {
34480
+ constructor(table2, session2, dialect, withList, overridingSystemValue_) {
34333
34481
  this.table = table2;
34334
- this.session = session;
34482
+ this.session = session2;
34335
34483
  this.dialect = dialect;
34336
34484
  this.withList = withList;
34337
34485
  this.overridingSystemValue_ = overridingSystemValue_;
@@ -34371,9 +34519,9 @@ var init_insert = __esm(() => {
34371
34519
  }
34372
34520
  };
34373
34521
  PgInsertBase = class PgInsertBase2 extends QueryPromise {
34374
- constructor(table2, values, session, dialect, withList, select2, overridingSystemValue_) {
34522
+ constructor(table2, values, session2, dialect, withList, select2, overridingSystemValue_) {
34375
34523
  super();
34376
- this.session = session;
34524
+ this.session = session2;
34377
34525
  this.dialect = dialect;
34378
34526
  this.config = { table: table2, values, withList, select: select2, overridingSystemValue_ };
34379
34527
  }
@@ -34451,9 +34599,9 @@ var init_refresh_materialized_view = __esm(() => {
34451
34599
  init_query_promise();
34452
34600
  init_tracing();
34453
34601
  PgRefreshMaterializedView = class PgRefreshMaterializedView2 extends QueryPromise {
34454
- constructor(view, session, dialect) {
34602
+ constructor(view, session2, dialect) {
34455
34603
  super();
34456
- this.session = session;
34604
+ this.session = session2;
34457
34605
  this.dialect = dialect;
34458
34606
  this.config = { view };
34459
34607
  }
@@ -34513,9 +34661,9 @@ var init_update = __esm(() => {
34513
34661
  init_utils();
34514
34662
  init_view_common();
34515
34663
  PgUpdateBuilder = class PgUpdateBuilder2 {
34516
- constructor(table2, session, dialect, withList) {
34664
+ constructor(table2, session2, dialect, withList) {
34517
34665
  this.table = table2;
34518
- this.session = session;
34666
+ this.session = session2;
34519
34667
  this.dialect = dialect;
34520
34668
  this.withList = withList;
34521
34669
  }
@@ -34530,9 +34678,9 @@ var init_update = __esm(() => {
34530
34678
  }
34531
34679
  };
34532
34680
  PgUpdateBase = class PgUpdateBase2 extends QueryPromise {
34533
- constructor(table2, set, session, dialect, withList) {
34681
+ constructor(table2, set, session2, dialect, withList) {
34534
34682
  super();
34535
- this.session = session;
34683
+ this.session = session2;
34536
34684
  this.dialect = dialect;
34537
34685
  this.config = { set, table: table2, withList, joins: [] };
34538
34686
  this.tableName = getTableLikeName(table2);
@@ -34720,14 +34868,14 @@ var init_query = __esm(() => {
34720
34868
  init_relations();
34721
34869
  init_tracing();
34722
34870
  RelationalQueryBuilder = class RelationalQueryBuilder2 {
34723
- constructor(fullSchema, schema, tableNamesMap, table2, tableConfig, dialect, session) {
34871
+ constructor(fullSchema, schema, tableNamesMap, table2, tableConfig, dialect, session2) {
34724
34872
  this.fullSchema = fullSchema;
34725
34873
  this.schema = schema;
34726
34874
  this.tableNamesMap = tableNamesMap;
34727
34875
  this.table = table2;
34728
34876
  this.tableConfig = tableConfig;
34729
34877
  this.dialect = dialect;
34730
- this.session = session;
34878
+ this.session = session2;
34731
34879
  }
34732
34880
  static [entityKind] = "PgRelationalQueryBuilder";
34733
34881
  findMany(config2) {
@@ -34738,7 +34886,7 @@ var init_query = __esm(() => {
34738
34886
  }
34739
34887
  };
34740
34888
  PgRelationalQuery = class PgRelationalQuery2 extends QueryPromise {
34741
- constructor(fullSchema, schema, tableNamesMap, table2, tableConfig, dialect, session, config2, mode) {
34889
+ constructor(fullSchema, schema, tableNamesMap, table2, tableConfig, dialect, session2, config2, mode) {
34742
34890
  super();
34743
34891
  this.fullSchema = fullSchema;
34744
34892
  this.schema = schema;
@@ -34746,7 +34894,7 @@ var init_query = __esm(() => {
34746
34894
  this.table = table2;
34747
34895
  this.tableConfig = tableConfig;
34748
34896
  this.dialect = dialect;
34749
- this.session = session;
34897
+ this.session = session2;
34750
34898
  this.config = config2;
34751
34899
  this.mode = mode;
34752
34900
  }
@@ -34842,24 +34990,24 @@ var init_db = __esm(() => {
34842
34990
  init_raw();
34843
34991
  init_refresh_materialized_view();
34844
34992
  PgDatabase = class PgDatabase2 {
34845
- constructor(dialect, session, schema) {
34993
+ constructor(dialect, session2, schema) {
34846
34994
  this.dialect = dialect;
34847
- this.session = session;
34995
+ this.session = session2;
34848
34996
  this._ = schema ? {
34849
34997
  schema: schema.schema,
34850
34998
  fullSchema: schema.fullSchema,
34851
34999
  tableNamesMap: schema.tableNamesMap,
34852
- session
35000
+ session: session2
34853
35001
  } : {
34854
35002
  schema: undefined,
34855
35003
  fullSchema: {},
34856
35004
  tableNamesMap: {},
34857
- session
35005
+ session: session2
34858
35006
  };
34859
35007
  this.query = {};
34860
35008
  if (this._.schema) {
34861
35009
  for (const [tableName, columns] of Object.entries(this._.schema)) {
34862
- this.query[tableName] = new RelationalQueryBuilder(schema.fullSchema, this._.schema, this._.tableNamesMap, schema.fullSchema[tableName], columns, dialect, session);
35010
+ this.query[tableName] = new RelationalQueryBuilder(schema.fullSchema, this._.schema, this._.tableNamesMap, schema.fullSchema[tableName], columns, dialect, session2);
34863
35011
  }
34864
35012
  }
34865
35013
  }
@@ -35114,8 +35262,8 @@ var init_session = __esm(() => {
35114
35262
  }
35115
35263
  };
35116
35264
  PgTransaction = class PgTransaction2 extends PgDatabase {
35117
- constructor(dialect, session, schema, nestedIndex = 0) {
35118
- super(dialect, session, schema);
35265
+ constructor(dialect, session2, schema, nestedIndex = 0) {
35266
+ super(dialect, session2, schema);
35119
35267
  this.schema = schema;
35120
35268
  this.nestedIndex = nestedIndex;
35121
35269
  }
@@ -45982,13 +46130,13 @@ async function extractZipToDirectory(zipBuffer, targetDir) {
45982
46130
  }
45983
46131
  });
45984
46132
  await Promise.all(extractPromises);
45985
- log.debug("[utils/zip] Extracted ZIP to directory", {
46133
+ log2.debug("[utils/zip] Extracted ZIP to directory", {
45986
46134
  targetDir,
45987
46135
  fileCount: Object.keys(zip.files).length
45988
46136
  });
45989
46137
  } catch (error) {
45990
46138
  const errorMessage2 = error instanceof Error ? error.message : String(error);
45991
- log.error("[utils/zip] Failed to extract ZIP", { targetDir, error });
46139
+ log2.error("[utils/zip] Failed to extract ZIP", { targetDir, error });
45992
46140
  throw new Error(`Failed to extract ZIP to ${targetDir}: ${errorMessage2}`, { cause: error });
45993
46141
  }
45994
46142
  }
@@ -46410,7 +46558,6 @@ class DeployJobService {
46410
46558
  "app.deploy_job.outcome": "succeeded",
46411
46559
  "app.deploy_job.run_duration_ms": DeployJobService.elapsedMsSince(new Date(runStartedAt))
46412
46560
  });
46413
- await this.deps.cache.refreshGameOrigins().catch(catchAttrs("deploy_job.cache_refresh"));
46414
46561
  await this.deleteCodeBundle(jobId);
46415
46562
  } catch (error) {
46416
46563
  clearInterval(heartbeat);
@@ -50276,7 +50423,7 @@ ${file}:${line3}:${column2}: ERROR: ${pluginText}${e.text}`;
50276
50423
  return result;
50277
50424
  }
50278
50425
  var fs23 = __require2("fs");
50279
- var os2 = __require2("os");
50426
+ var os22 = __require2("os");
50280
50427
  var path2 = __require2("path");
50281
50428
  var ESBUILD_BINARY_PATH = process.env.ESBUILD_BINARY_PATH || ESBUILD_BINARY_PATH;
50282
50429
  var isValidBinaryPath = (x) => !!x && x !== "/usr/bin/esbuild";
@@ -50318,7 +50465,7 @@ ${file}:${line3}:${column2}: ERROR: ${pluginText}${e.text}`;
50318
50465
  let pkg;
50319
50466
  let subpath;
50320
50467
  let isWASM = false;
50321
- let platformKey = `${process.platform} ${os2.arch()} ${os2.endianness()}`;
50468
+ let platformKey = `${process.platform} ${os22.arch()} ${os22.endianness()}`;
50322
50469
  if (platformKey in knownWindowsPackages) {
50323
50470
  pkg = knownWindowsPackages[platformKey];
50324
50471
  subpath = "esbuild.exe";
@@ -50463,7 +50610,7 @@ for your current platform.`);
50463
50610
  var crypto32 = __require2("crypto");
50464
50611
  var path22 = __require2("path");
50465
50612
  var fs222 = __require2("fs");
50466
- var os22 = __require2("os");
50613
+ var os222 = __require2("os");
50467
50614
  var tty2 = __require2("tty");
50468
50615
  var worker_threads;
50469
50616
  if (process.env.ESBUILD_WORKER_THREADS !== "0") {
@@ -50774,7 +50921,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
50774
50921
  afterClose(null);
50775
50922
  };
50776
50923
  var randomFileName = () => {
50777
- return path22.join(os22.tmpdir(), `esbuild-${crypto32.randomBytes(32).toString("hex")}`);
50924
+ return path22.join(os222.tmpdir(), `esbuild-${crypto32.randomBytes(32).toString("hex")}`);
50778
50925
  };
50779
50926
  var workerThreadService = null;
50780
50927
  var startWorkerThreadService = (worker_threads2) => {
@@ -51015,7 +51162,8 @@ class DeployService {
51015
51162
  expiresIn: null,
51016
51163
  permissions: {
51017
51164
  games: [`read:${slug}`, `write:${slug}`]
51018
- }
51165
+ },
51166
+ rateLimitEnabled: false
51019
51167
  });
51020
51168
  setAttribute("app.deploy.api_key_outcome", "created");
51021
51169
  return apiKey;
@@ -51076,9 +51224,9 @@ class DeployService {
51076
51224
  throw new ValidationError("Uploaded file is empty or not found");
51077
51225
  }
51078
51226
  setAttribute("app.deploy.asset_upload_size", frontendZip.length);
51079
- const os2 = await import("os");
51227
+ const os22 = await import("os");
51080
51228
  const path2 = await import("path");
51081
- const tempDir = path2.join(os2.tmpdir(), `playcademy-deploy-${gameId}-${Date.now()}`);
51229
+ const tempDir = path2.join(os22.tmpdir(), `playcademy-deploy-${gameId}-${Date.now()}`);
51082
51230
  const assetsPath = path2.join(tempDir, "dist");
51083
51231
  await withSpan("deploy.extract_assets", () => extractZip(frontendZip, assetsPath));
51084
51232
  uploadDeps.deleteObject(uploadToken).catch(catchAttrs("deploy.temp_cleanup"));
@@ -51686,6 +51834,31 @@ var init_game_service = __esm(() => {
51686
51834
  static changedFields(data) {
51687
51835
  return Object.entries(data).filter(([, value]) => value !== undefined).map(([key]) => key).toSorted().join(",");
51688
51836
  }
51837
+ static safeOrigin(game2) {
51838
+ if (!game2 || game2.gameType !== "external" || !game2.externalUrl) {
51839
+ return;
51840
+ }
51841
+ try {
51842
+ return new URL(game2.externalUrl).origin;
51843
+ } catch {
51844
+ return;
51845
+ }
51846
+ }
51847
+ cleanupStaleOrigin(origin, excludeGameId) {
51848
+ if (!this.deps.corsKvs) {
51849
+ return;
51850
+ }
51851
+ const corsKvs = this.deps.corsKvs;
51852
+ const db2 = this.deps.db;
51853
+ (async () => {
51854
+ const sharesOrigin = await db2.select({ id: games.id }).from(games).where(and(ne(games.id, excludeGameId), eq(games.gameType, "external"), or(eq(games.externalUrl, origin), like(games.externalUrl, `${origin}/%`)))).limit(1);
51855
+ if (sharesOrigin.length === 0) {
51856
+ await corsKvs.deleteOrigin(origin);
51857
+ } else {
51858
+ setAttribute("app.cors_kvs.delete_skipped", true);
51859
+ }
51860
+ })().catch(catchAttrs("cors_kvs.delete_origin", {}));
51861
+ }
51689
51862
  async list(caller) {
51690
51863
  const db2 = this.deps.db;
51691
51864
  const isAdmin = caller?.role === "admin";
@@ -51988,6 +52161,18 @@ var init_game_service = __esm(() => {
51988
52161
  "app.game.next_visibility": data.visibility !== undefined ? gameResponse.visibility : undefined
51989
52162
  });
51990
52163
  GameService2.recordGameShape(gameResponse);
52164
+ if (this.deps.corsKvs) {
52165
+ const prevOrigin = GameService2.safeOrigin(existingGame);
52166
+ const nextOrigin = GameService2.safeOrigin(gameResponse);
52167
+ if (nextOrigin) {
52168
+ setAttribute("app.cors_kvs.origin", nextOrigin);
52169
+ this.deps.corsKvs.putOrigin(nextOrigin).catch(catchAttrs("cors_kvs.put_origin", {}));
52170
+ }
52171
+ if (prevOrigin && prevOrigin !== nextOrigin) {
52172
+ setAttribute("app.cors_kvs.prev_origin", prevOrigin);
52173
+ this.cleanupStaleOrigin(prevOrigin, gameResponse.id);
52174
+ }
52175
+ }
51991
52176
  return gameResponse;
51992
52177
  }
51993
52178
  async updateMetadata(gameId, data, user) {
@@ -52106,6 +52291,11 @@ var init_game_service = __esm(() => {
52106
52291
  setAttribute("app.game.api_key_cleanup_error", errorMessage(error));
52107
52292
  }
52108
52293
  }
52294
+ const corsOrigin = GameService2.safeOrigin(gameToDelete);
52295
+ if (this.deps.corsKvs && corsOrigin) {
52296
+ setAttribute("app.cors_kvs.origin", corsOrigin);
52297
+ this.cleanupStaleOrigin(corsOrigin, gameId);
52298
+ }
52109
52299
  return {
52110
52300
  slug: gameToDelete.slug,
52111
52301
  displayName: gameToDelete.displayName
@@ -52234,12 +52424,13 @@ var init_logs_service = __esm(() => {
52234
52424
  init_deployment_util();
52235
52425
  });
52236
52426
  function createGameServices(deps) {
52237
- const { db: db2, config: config2, cloudflare: cloudflare2, auth: auth2, storage, cache, alerts } = deps;
52427
+ const { db: db2, config: config2, cloudflare: cloudflare2, corsKvs, auth: auth2, storage, cache, alerts } = deps;
52238
52428
  const game2 = new GameService({
52239
52429
  db: db2,
52240
52430
  alerts,
52241
52431
  cache,
52242
52432
  cloudflare: cloudflare2,
52433
+ corsKvs,
52243
52434
  deleteApiKeyByName: auth2.deleteApiKeyByName.bind(auth2)
52244
52435
  });
52245
52436
  const gameMember = new GameMemberService({
@@ -52260,7 +52451,6 @@ function createGameServices(deps) {
52260
52451
  db: db2,
52261
52452
  uploadBucket: config2.uploadBucket,
52262
52453
  storage,
52263
- cache,
52264
52454
  validateDeveloperAccessBySlug: (user, slug) => game2.validateDeveloperAccessBySlug(user, slug),
52265
52455
  runDeploy: (slug, request, user, uploadDeps, extractZip) => deploy.deploy(slug, request, user, uploadDeps, extractZip),
52266
52456
  notifyDeploymentFailure: (slug, displayName, error, developerInfo) => deploy.notifyDeploymentFailure(slug, displayName, error, developerInfo)
@@ -53131,6 +53321,9 @@ class DomainService {
53131
53321
  addEvent("domain.cloudflare_hostname_created", {
53132
53322
  "app.domain.cloudflare_id": cfHostname.id
53133
53323
  });
53324
+ const corsOrigin = `https://${hostname}`;
53325
+ setAttribute("app.cors_kvs.origin", corsOrigin);
53326
+ this.deps.corsKvs?.putOrigin(corsOrigin).catch(catchAttrs("cors_kvs.put_origin", {}));
53134
53327
  return customHostname;
53135
53328
  }
53136
53329
  async list(slug, environment, user) {
@@ -53214,6 +53407,9 @@ class DomainService {
53214
53407
  "app.domain.status": dbHostname.status,
53215
53408
  "app.domain.ssl_status": dbHostname.sslStatus
53216
53409
  });
53410
+ const corsOrigin = `https://${hostname}`;
53411
+ setAttribute("app.cors_kvs.origin", corsOrigin);
53412
+ this.deps.corsKvs?.deleteOrigin(corsOrigin).catch(catchAttrs("cors_kvs.delete_origin", {}));
53217
53413
  }
53218
53414
  }
53219
53415
  var init_domain_service = __esm(() => {
@@ -53504,12 +53700,14 @@ var init_secrets_service = __esm(() => {
53504
53700
  init_deployment_util();
53505
53701
  INTERNAL_SECRET_KEYS = ["PLAYCADEMY_API_KEY", "GAME_ID", "PLAYCADEMY_BASE_URL"];
53506
53702
  });
53703
+ var ASSET_ROUTE_PREFIX = "/api/assets/";
53507
53704
  var ROUTES;
53508
53705
  var init_constants3 = __esm(() => {
53509
53706
  init_src();
53510
53707
  ROUTES = {
53511
53708
  INDEX: "/api",
53512
53709
  HEALTH: "/api/health",
53710
+ ASSETS: `${ASSET_ROUTE_PREFIX}*`,
53513
53711
  TIMEBACK: {
53514
53712
  END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
53515
53713
  GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
@@ -53664,7 +53862,8 @@ class SeedService {
53664
53862
  }, {
53665
53863
  bindings: { d1: [deploymentId], r2: [], kv: [] },
53666
53864
  keepAssets: false,
53667
- compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
53865
+ compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
53866
+ observability: false
53668
53867
  });
53669
53868
  if (secrets && Object.keys(secrets).length > 0) {
53670
53869
  await cf.setSecrets(seedDeploymentId, prefixSecrets(secrets));
@@ -58092,6 +58291,7 @@ var SINGLE_EMOJI_REGEX;
58092
58291
  var init_emoji = __esm(() => {
58093
58292
  SINGLE_EMOJI_REGEX = /^(?:(?:\p{Regional_Indicator}{2})|(?:[#*0-9]\uFE0F?\u20E3)|(?:\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?)*))$/u;
58094
58293
  });
58294
+ var HttpUrlSchema;
58095
58295
  var GameEmojiSchema;
58096
58296
  var GameMetadataRecordSchema;
58097
58297
  var InsertGameSchema;
@@ -58118,6 +58318,7 @@ var init_schemas2 = __esm(() => {
58118
58318
  init_src();
58119
58319
  init_emoji();
58120
58320
  init_table5();
58321
+ HttpUrlSchema = exports_external.string().url().refine((url2) => /^https?:\/\//i.test(url2), { message: "URL must use http or https" });
58121
58322
  GameEmojiSchema = exports_external.string().max(16).refine((value) => value.length === 0 || isSingleEmoji(value), {
58122
58323
  message: "Emoji must be a single emoji."
58123
58324
  });
@@ -58142,7 +58343,7 @@ var init_schemas2 = __esm(() => {
58142
58343
  gameType: exports_external.enum(gameTypeEnum.enumValues).default("hosted"),
58143
58344
  visibility: exports_external.enum(gameVisibilityEnum.enumValues).default("visible"),
58144
58345
  deploymentUrl: exports_external.string().nullable().optional(),
58145
- externalUrl: exports_external.string().url().nullable().optional()
58346
+ externalUrl: HttpUrlSchema.nullable().optional()
58146
58347
  }).omit({
58147
58348
  slug: true,
58148
58349
  version: true
@@ -58165,7 +58366,7 @@ var init_schemas2 = __esm(() => {
58165
58366
  gameType: exports_external.enum(gameTypeEnum.enumValues).optional(),
58166
58367
  visibility: exports_external.enum(gameVisibilityEnum.enumValues).optional(),
58167
58368
  deploymentUrl: exports_external.string().nullable().optional(),
58168
- externalUrl: exports_external.string().url().nullable().optional()
58369
+ externalUrl: HttpUrlSchema.nullable().optional()
58169
58370
  }).omit({
58170
58371
  id: true,
58171
58372
  slug: true,
@@ -58195,7 +58396,7 @@ var init_schemas2 = __esm(() => {
58195
58396
  metadata: GameMetadataRecordSchema.optional().default({}),
58196
58397
  gameType: exports_external.enum(gameTypeEnum.enumValues).optional().default("hosted"),
58197
58398
  visibility: exports_external.enum(gameVisibilityEnum.enumValues).optional(),
58198
- externalUrl: exports_external.string().url().optional()
58399
+ externalUrl: HttpUrlSchema.optional()
58199
58400
  }).refine((data) => {
58200
58401
  if (data.gameType === "external" && !data.externalUrl) {
58201
58402
  return false;
@@ -63132,6 +63333,7 @@ function createPlatformServices(deps) {
63132
63333
  db: db2,
63133
63334
  config: config2,
63134
63335
  cloudflare: cloudflare2,
63336
+ corsKvs,
63135
63337
  storage: storage2,
63136
63338
  r2Storage,
63137
63339
  timebackClient,
@@ -63153,7 +63355,7 @@ function createPlatformServices(deps) {
63153
63355
  });
63154
63356
  const kv = new KVService({ db: db2, cloudflare: cloudflare2, validateDeveloperAccessBySlug });
63155
63357
  const secrets = new SecretsService({ config: config2, cloudflare: cloudflare2, validateDeveloperAccessBySlug });
63156
- const domain = new DomainService({ db: db2, cloudflare: cloudflare2, validateDeveloperAccessBySlug });
63358
+ const domain = new DomainService({ db: db2, cloudflare: cloudflare2, corsKvs, validateDeveloperAccessBySlug });
63157
63359
  const database = new DatabaseService({
63158
63360
  db: db2,
63159
63361
  config: config2,
@@ -63818,7 +64020,7 @@ var init_standalone = __esm(() => {
63818
64020
  init_verify_service();
63819
64021
  });
63820
64022
  function createServices(ctx) {
63821
- const { db: db2, config: config2, providers, cloudflare: cloudflare2, timeback: timeback2, discord } = ctx;
64023
+ const { db: db2, config: config2, providers, cloudflare: cloudflare2, timeback: timeback2, discord, corsKvs } = ctx;
63822
64024
  const { auth: auth2, storage: storage2, r2Storage, cache } = providers;
63823
64025
  const infra2 = createInfraServices({
63824
64026
  db: db2,
@@ -63832,6 +64034,7 @@ function createServices(ctx) {
63832
64034
  db: db2,
63833
64035
  config: config2,
63834
64036
  cloudflare: cloudflare2,
64037
+ corsKvs,
63835
64038
  auth: auth2,
63836
64039
  storage: storage2,
63837
64040
  cache,
@@ -63841,6 +64044,7 @@ function createServices(ctx) {
63841
64044
  db: db2,
63842
64045
  config: config2,
63843
64046
  cloudflare: cloudflare2,
64047
+ corsKvs,
63844
64048
  storage: storage2,
63845
64049
  r2Storage,
63846
64050
  timebackClient: timeback2,
@@ -63870,14 +64074,14 @@ function buildTimebackClient() {
63870
64074
  }
63871
64075
  const { mode, onerosterApiUrl, caliperApiUrl, clientId, clientSecret, authUrl } = config.timeback;
63872
64076
  if (mode === "local") {
63873
- log.debug("[Sandbox] Initializing TimeBack client (local mode)", { onerosterApiUrl });
64077
+ log2.debug("[Sandbox] Initializing TimeBack client (local mode)", { onerosterApiUrl });
63874
64078
  return new TimebackClient({
63875
64079
  baseUrl: onerosterApiUrl,
63876
64080
  caliperUrl: caliperApiUrl
63877
64081
  });
63878
64082
  }
63879
64083
  if (mode === "remote" && clientId && clientSecret && authUrl) {
63880
- log.debug("[Sandbox] Initializing TimeBack client (remote mode)", { onerosterApiUrl });
64084
+ log2.debug("[Sandbox] Initializing TimeBack client (remote mode)", { onerosterApiUrl });
63881
64085
  return new TimebackClient({
63882
64086
  baseUrl: onerosterApiUrl,
63883
64087
  caliperUrl: caliperApiUrl,
@@ -63912,7 +64116,7 @@ function createSandboxAuthProvider() {
63912
64116
  expiresAt: params.expiresIn ? new Date(Date.now() + params.expiresIn * 1000) : null
63913
64117
  };
63914
64118
  sandboxApiKeys.set(id, apiKey);
63915
- log.debug("[SandboxAuthProvider] Created API key", { id, name: params.name });
64119
+ log2.debug("[SandboxAuthProvider] Created API key", { id, name: params.name });
63916
64120
  return { id, key };
63917
64121
  },
63918
64122
  async listApiKeys(_headers) {
@@ -63920,13 +64124,13 @@ function createSandboxAuthProvider() {
63920
64124
  },
63921
64125
  async deleteApiKey(keyId) {
63922
64126
  sandboxApiKeys.delete(keyId);
63923
- log.debug("[SandboxAuthProvider] Deleted API key", { keyId });
64127
+ log2.debug("[SandboxAuthProvider] Deleted API key", { keyId });
63924
64128
  },
63925
64129
  async deleteApiKeyByName(name3, userId) {
63926
64130
  for (const [id, key] of sandboxApiKeys.entries()) {
63927
64131
  if (key.name === name3 && key.userId === userId) {
63928
64132
  sandboxApiKeys.delete(id);
63929
- log.debug("[SandboxAuthProvider] Deleted API key by name", { id, name: name3 });
64133
+ log2.debug("[SandboxAuthProvider] Deleted API key by name", { id, name: name3 });
63930
64134
  return id;
63931
64135
  }
63932
64136
  }
@@ -63949,7 +64153,7 @@ function createSandboxAuthProvider() {
63949
64153
  const payload2 = JSON.parse(atob(parts2[1]));
63950
64154
  if (payload2.sub && payload2.uid) {
63951
64155
  if (payload2.exp && payload2.exp < Date.now()) {
63952
- log.debug("[SandboxAuthProvider] Token expired");
64156
+ log2.debug("[SandboxAuthProvider] Token expired");
63953
64157
  return null;
63954
64158
  }
63955
64159
  return payload2;
@@ -64000,7 +64204,7 @@ function createSandboxAuthProvider() {
64000
64204
  const payload = JSON.parse(atob(parts2[1]));
64001
64205
  if (payload.jti && payload.sub && payload.game) {
64002
64206
  if (payload.exp && payload.exp < Date.now()) {
64003
- log.debug("[SandboxAuthProvider] Log stream token expired");
64207
+ log2.debug("[SandboxAuthProvider] Log stream token expired");
64004
64208
  return null;
64005
64209
  }
64006
64210
  return { jti: payload.jti, sub: payload.sub, game: payload.game };
@@ -64021,16 +64225,6 @@ var init_auth_provider = __esm(() => {
64021
64225
  });
64022
64226
  function createSandboxCacheProvider() {
64023
64227
  return {
64024
- async refreshGameOrigins() {
64025
- gameOrigins = ["http://localhost:3000", "http://localhost:5173"];
64026
- lastRefreshTime = Date.now();
64027
- },
64028
- getGameOriginState() {
64029
- return {
64030
- origins: gameOrigins,
64031
- lastRefreshTime
64032
- };
64033
- },
64034
64228
  async get(key) {
64035
64229
  const entry = cache.get(key);
64036
64230
  if (!entry) {
@@ -64055,17 +64249,12 @@ function createSandboxCacheProvider() {
64055
64249
  }
64056
64250
  function clearSandboxCache() {
64057
64251
  cache.clear();
64058
- gameOrigins = [];
64059
- lastRefreshTime = 0;
64060
64252
  }
64061
64253
  var cache;
64062
- var gameOrigins;
64063
- var lastRefreshTime = 0;
64064
64254
  var init_cache_provider = __esm(() => {
64065
64255
  cache = new Map;
64066
- gameOrigins = [];
64067
64256
  });
64068
- function getBucket(bucketName) {
64257
+ function getBucket2(bucketName) {
64069
64258
  let bucket = storage2.get(bucketName);
64070
64259
  if (!bucket) {
64071
64260
  bucket = new Map;
@@ -64076,7 +64265,7 @@ function getBucket(bucketName) {
64076
64265
  function createSandboxStorageProvider() {
64077
64266
  return {
64078
64267
  async listObjects(bucketName, prefix) {
64079
- const bucket = getBucket(bucketName);
64268
+ const bucket = getBucket2(bucketName);
64080
64269
  const files = [];
64081
64270
  for (const [key, data] of bucket.entries()) {
64082
64271
  if (!prefix || key.startsWith(prefix)) {
@@ -64091,7 +64280,7 @@ function createSandboxStorageProvider() {
64091
64280
  return files;
64092
64281
  },
64093
64282
  async getObject(bucketName, key) {
64094
- const bucket = getBucket(bucketName);
64283
+ const bucket = getBucket2(bucketName);
64095
64284
  const data = bucket.get(key);
64096
64285
  if (!data) {
64097
64286
  return null;
@@ -64103,18 +64292,18 @@ function createSandboxStorageProvider() {
64103
64292
  };
64104
64293
  },
64105
64294
  async putObject(bucketName, key, body2, options) {
64106
- const bucket = getBucket(bucketName);
64295
+ const bucket = getBucket2(bucketName);
64107
64296
  bucket.set(key, { body: body2, contentType: options?.contentType });
64108
- log.debug("[SandboxStorageProvider] Stored object", {
64297
+ log2.debug("[SandboxStorageProvider] Stored object", {
64109
64298
  bucket: bucketName,
64110
64299
  key,
64111
64300
  size: body2.length
64112
64301
  });
64113
64302
  },
64114
64303
  async deleteObject(bucketName, key) {
64115
- const bucket = getBucket(bucketName);
64304
+ const bucket = getBucket2(bucketName);
64116
64305
  bucket.delete(key);
64117
- log.debug("[SandboxStorageProvider] Deleted object", { bucket: bucketName, key });
64306
+ log2.debug("[SandboxStorageProvider] Deleted object", { bucket: bucketName, key });
64118
64307
  },
64119
64308
  async generatePresignedPutUrl(bucketName, key, _contentType, _expiresIn) {
64120
64309
  const baseUrl = "http://localhost:3000";
@@ -64180,7 +64369,7 @@ function createSandboxContext(options) {
64180
64369
  };
64181
64370
  Object.assign(services, createServices(ctx));
64182
64371
  cachedServiceContext = ctx;
64183
- log.debug("[Sandbox] ServiceContext initialized", {
64372
+ log2.debug("[Sandbox] ServiceContext initialized", {
64184
64373
  sstStage: config2.sstStage,
64185
64374
  baseUrl: config2.baseUrl,
64186
64375
  hasTimeback: Boolean(timeback2)
@@ -73500,7 +73689,7 @@ var require_has_flag = __commonJS2((exports, module2) => {
73500
73689
  };
73501
73690
  });
73502
73691
  var require_supports_color = __commonJS2((exports, module2) => {
73503
- var os2 = __require2("os");
73692
+ var os22 = __require2("os");
73504
73693
  var tty2 = __require2("tty");
73505
73694
  var hasFlag = require_has_flag();
73506
73695
  var { env } = process;
@@ -73548,7 +73737,7 @@ var require_supports_color = __commonJS2((exports, module2) => {
73548
73737
  return min;
73549
73738
  }
73550
73739
  if (process.platform === "win32") {
73551
- const osRelease = os2.release().split(".");
73740
+ const osRelease = os22.release().split(".");
73552
73741
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
73553
73742
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
73554
73743
  }
@@ -78662,8 +78851,8 @@ function assembleStyles() {
78662
78851
  styles2.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
78663
78852
  Object.defineProperties(styles2, {
78664
78853
  rgbToAnsi256: {
78665
- value(red, green, blue) {
78666
- if (red === green && green === blue) {
78854
+ value(red, green2, blue2) {
78855
+ if (red === green2 && green2 === blue2) {
78667
78856
  if (red < 8) {
78668
78857
  return 16;
78669
78858
  }
@@ -78672,7 +78861,7 @@ function assembleStyles() {
78672
78861
  }
78673
78862
  return Math.round((red - 8) / 247 * 24) + 232;
78674
78863
  }
78675
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
78864
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green2 / 255 * 5) + Math.round(blue2 / 255 * 5);
78676
78865
  },
78677
78866
  enumerable: false
78678
78867
  },
@@ -78708,24 +78897,24 @@ function assembleStyles() {
78708
78897
  return 90 + (code - 8);
78709
78898
  }
78710
78899
  let red;
78711
- let green;
78712
- let blue;
78900
+ let green2;
78901
+ let blue2;
78713
78902
  if (code >= 232) {
78714
78903
  red = ((code - 232) * 10 + 8) / 255;
78715
- green = red;
78716
- blue = red;
78904
+ green2 = red;
78905
+ blue2 = red;
78717
78906
  } else {
78718
78907
  code -= 16;
78719
78908
  const remainder = code % 36;
78720
78909
  red = Math.floor(code / 36) / 5;
78721
- green = Math.floor(remainder / 6) / 5;
78722
- blue = remainder % 6 / 5;
78910
+ green2 = Math.floor(remainder / 6) / 5;
78911
+ blue2 = remainder % 6 / 5;
78723
78912
  }
78724
- const value = Math.max(red, green, blue) * 2;
78913
+ const value = Math.max(red, green2, blue2) * 2;
78725
78914
  if (value === 0) {
78726
78915
  return 30;
78727
78916
  }
78728
- let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
78917
+ let result = 30 + (Math.round(blue2) << 2 | Math.round(green2) << 1 | Math.round(red));
78729
78918
  if (value === 2) {
78730
78919
  result += 60;
78731
78920
  }
@@ -78734,7 +78923,7 @@ function assembleStyles() {
78734
78923
  enumerable: false
78735
78924
  },
78736
78925
  rgbToAnsi: {
78737
- value: (red, green, blue) => styles2.ansi256ToAnsi(styles2.rgbToAnsi256(red, green, blue)),
78926
+ value: (red, green2, blue2) => styles2.ansi256ToAnsi(styles2.rgbToAnsi256(red, green2, blue2)),
78738
78927
  enumerable: false
78739
78928
  },
78740
78929
  hexToAnsi: {
@@ -78800,7 +78989,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
78800
78989
  return min2;
78801
78990
  }
78802
78991
  if (process2.platform === "win32") {
78803
- const osRelease = os.release().split(".");
78992
+ const osRelease = os2.release().split(".");
78804
78993
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
78805
78994
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
78806
78995
  }
@@ -84201,7 +84390,7 @@ var init_api2 = __esm(() => {
84201
84390
  ANSI_BACKGROUND_OFFSET = 10;
84202
84391
  wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
84203
84392
  wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
84204
- wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
84393
+ wrapAnsi16m = (offset = 0) => (red, green2, blue2) => `\x1B[${38 + offset};2;${red};${green2};${blue2}m`;
84205
84394
  styles2 = {
84206
84395
  modifier: {
84207
84396
  reset: [0, 0],
@@ -94067,7 +94256,7 @@ Is ${source_default.bold.blue(this.base.name)} schema created or renamed from an
94067
94256
  });
94068
94257
  require_supports_colors = __commonJS22({
94069
94258
  "../node_modules/.pnpm/colors@1.4.0/node_modules/colors/lib/system/supports-colors.js"(exports, module2) {
94070
- var os2 = __require22("os");
94259
+ var os22 = __require22("os");
94071
94260
  var hasFlag2 = require_has_flag2();
94072
94261
  var env2 = process.env;
94073
94262
  var forceColor = undefined;
@@ -94105,7 +94294,7 @@ Is ${source_default.bold.blue(this.base.name)} schema created or renamed from an
94105
94294
  }
94106
94295
  var min2 = forceColor ? 1 : 0;
94107
94296
  if (process.platform === "win32") {
94108
- var osRelease = os2.release().split(".");
94297
+ var osRelease = os22.release().split(".");
94109
94298
  if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
94110
94299
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
94111
94300
  }
@@ -121883,8 +122072,8 @@ var require_colorette = __commonJS2((exports) => {
121883
122072
  var createColors = ({ useColor = isColorSupported } = {}) => useColor ? colors3 : Object.keys(colors3).reduce((colors4, key) => ({ ...colors4, [key]: String }), {});
121884
122073
  var {
121885
122074
  reset,
121886
- bold: bold2,
121887
- dim: dim2,
122075
+ bold: bold22,
122076
+ dim: dim22,
121888
122077
  italic,
121889
122078
  underline,
121890
122079
  inverse,
@@ -121892,11 +122081,11 @@ var require_colorette = __commonJS2((exports) => {
121892
122081
  strikethrough,
121893
122082
  black,
121894
122083
  red,
121895
- green,
122084
+ green: green2,
121896
122085
  yellow,
121897
- blue,
122086
+ blue: blue2,
121898
122087
  magenta,
121899
- cyan: cyan2,
122088
+ cyan: cyan3,
121900
122089
  white,
121901
122090
  gray,
121902
122091
  bgBlack,
@@ -121909,7 +122098,7 @@ var require_colorette = __commonJS2((exports) => {
121909
122098
  bgWhite,
121910
122099
  blackBright,
121911
122100
  redBright,
121912
- greenBright,
122101
+ greenBright: greenBright2,
121913
122102
  yellowBright,
121914
122103
  blueBright,
121915
122104
  magentaBright,
@@ -121942,16 +122131,16 @@ var require_colorette = __commonJS2((exports) => {
121942
122131
  exports.bgYellowBright = bgYellowBright;
121943
122132
  exports.black = black;
121944
122133
  exports.blackBright = blackBright;
121945
- exports.blue = blue;
122134
+ exports.blue = blue2;
121946
122135
  exports.blueBright = blueBright;
121947
- exports.bold = bold2;
122136
+ exports.bold = bold22;
121948
122137
  exports.createColors = createColors;
121949
- exports.cyan = cyan2;
122138
+ exports.cyan = cyan3;
121950
122139
  exports.cyanBright = cyanBright;
121951
- exports.dim = dim2;
122140
+ exports.dim = dim22;
121952
122141
  exports.gray = gray;
121953
- exports.green = green;
121954
- exports.greenBright = greenBright;
122142
+ exports.green = green2;
122143
+ exports.greenBright = greenBright2;
121955
122144
  exports.hidden = hidden;
121956
122145
  exports.inverse = inverse;
121957
122146
  exports.isColorSupported = isColorSupported;
@@ -122130,7 +122319,7 @@ function processServerOptions(_port, options) {
122130
122319
  if (customLogger2) {
122131
122320
  setLogger(customLogger2);
122132
122321
  setCustomLogHandler((level, message, context2, scope) => {
122133
- const scopePrefix = scope ? `${import_picocolors3.default.bold(`[${scope}]`)} ` : "";
122322
+ const scopePrefix = scope ? `${import_picocolors5.default.bold(`[${scope}]`)} ` : "";
122134
122323
  let formattedMessage = `${scopePrefix}${message}`;
122135
122324
  if (context2) {
122136
122325
  const colorized = import_json_colorizer.colorize(context2);
@@ -122154,13 +122343,13 @@ ${stripped}`;
122154
122343
  };
122155
122344
  }
122156
122345
  var import_json_colorizer;
122157
- var import_picocolors3;
122346
+ var import_picocolors5;
122158
122347
  var init_options = __esm(() => {
122159
122348
  init_src2();
122160
122349
  init_config();
122161
122350
  init_logging();
122162
122351
  import_json_colorizer = __toESM2(require_dist2(), 1);
122163
- import_picocolors3 = __toESM2(require_picocolors2(), 1);
122352
+ import_picocolors5 = __toESM2(require_picocolors2(), 1);
122164
122353
  });
122165
122354
  var healthRouter;
122166
122355
  var init_health = __esm(() => {
@@ -122483,27 +122672,6 @@ var init_utils11 = __esm(() => {
122483
122672
  init_timeback_util();
122484
122673
  init_validation_util();
122485
122674
  });
122486
- var getAllowedOrigins;
122487
- var admin;
122488
- var init_admin_controller = __esm(() => {
122489
- init_utils11();
122490
- getAllowedOrigins = requireAdmin(async (ctx) => {
122491
- const shouldRefresh = ctx.url.searchParams.get("refresh") === "true";
122492
- if (shouldRefresh) {
122493
- await ctx.providers.cache.refreshGameOrigins();
122494
- }
122495
- const { origins, lastRefreshTime: lastRefreshTime2 } = ctx.providers.cache.getGameOriginState();
122496
- return {
122497
- origins,
122498
- count: origins.length,
122499
- lastRefresh: lastRefreshTime2 > 0 ? new Date(lastRefreshTime2).toISOString() : null,
122500
- cacheAge: lastRefreshTime2 > 0 ? Date.now() - lastRefreshTime2 : null
122501
- };
122502
- });
122503
- admin = defineControllerNames("admin", {
122504
- getAllowedOrigins
122505
- });
122506
- });
122507
122675
  var listFiles;
122508
122676
  var getFile;
122509
122677
  var putFile;
@@ -123996,7 +124164,6 @@ var init_verify_controller = __esm(() => {
123996
124164
  });
123997
124165
  });
123998
124166
  var init_controllers = __esm(() => {
123999
- init_admin_controller();
124000
124167
  init_bucket_controller();
124001
124168
  init_database_controller();
124002
124169
  init_deploy_controller();
@@ -124088,7 +124255,7 @@ async function getMockTimebackData(db2, timebackId, gameId) {
124088
124255
  const { role, organizations } = getMockStudentProfile();
124089
124256
  const allEnrollments = await getMockEnrollments(db2);
124090
124257
  const enrollments = gameId ? filterEnrollmentsByGame(allEnrollments, gameId) : allEnrollments;
124091
- log.debug("[Timeback] Sandbox is using mock data", {
124258
+ log2.debug("[Timeback] Sandbox is using mock data", {
124092
124259
  timebackId,
124093
124260
  role,
124094
124261
  enrollments: enrollments.map((e) => `${e.subject}:${e.grade}`),
@@ -124293,7 +124460,7 @@ var init_deploy = __esm(() => {
124293
124460
  init_src2();
124294
124461
  init_api();
124295
124462
  init_uploads();
124296
- logger4 = log.scope("SandboxDeploy");
124463
+ logger4 = log2.scope("SandboxDeploy");
124297
124464
  gameDeployRouter = new Hono2;
124298
124465
  gameDeployRouter.post("/:slug/deploy", async (c2) => {
124299
124466
  const user = c2.get("user");
@@ -124991,7 +125158,7 @@ var init_lti = __esm(() => {
124991
125158
  init_src2();
124992
125159
  init_constants();
124993
125160
  init_api();
124994
- logger5 = log.scope("SandboxLti");
125161
+ logger5 = log2.scope("SandboxLti");
124995
125162
  ltiRouter = new Hono2;
124996
125163
  ltiRouter.post("/launch", async (c2) => {
124997
125164
  const db2 = c2.get("db");
@@ -125178,68 +125345,10 @@ var init_server = __esm(() => {
125178
125345
  });
125179
125346
  init_server();
125180
125347
 
125181
- // src/lib/logging/adapter.ts
125182
- function createLoggerAdapter(domain) {
125183
- return {
125184
- info: (msg) => console.log(`${createLogPrefix("playcademy", domain)} ${msg}`),
125185
- warn: (msg) => console.warn(`${createLogPrefix("playcademy", domain)} ${msg}`),
125186
- error: (msg) => console.error(`${createLogPrefix("playcademy", domain)} ${msg}`)
125187
- };
125188
- }
125189
- // src/lib/logging/utils.ts
125190
- var import_picocolors4 = __toESM(require_picocolors(), 1);
125191
-
125192
- // ../utils/src/string.ts
125193
- function pluralize(count2, singular, plural) {
125194
- return count2 === 1 ? singular : plural || `${singular}s`;
125195
- }
125196
-
125197
- // src/lib/logging/utils.ts
125198
- function createBackendBannerOptions(backendPort, vitePort) {
125199
- if (!backendPort) {
125200
- return;
125201
- }
125202
- return { port: backendPort, vitePort };
125203
- }
125204
- function createTimebackBannerOptions(timebackMode, courseCount, enrolledCount) {
125205
- if (!timebackMode || !courseCount) {
125206
- return;
125207
- }
125208
- return {
125209
- courseCount,
125210
- enrolledCount: enrolledCount ?? courseCount,
125211
- mode: timebackMode
125212
- };
125213
- }
125214
- function printBanner(viteConfig, options) {
125215
- const INDENT = " ".repeat(2);
125216
- const { version: version4, gameName, sandbox, backend, timeback } = options;
125217
- viteConfig.logger.info("");
125218
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green(import_picocolors4.bold("PLAYCADEMY"))} ${import_picocolors4.green(`v${version4}`)}`);
125219
- viteConfig.logger.info("");
125220
- if (gameName) {
125221
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Project:")} ${import_picocolors4.cyan(gameName)}`);
125222
- }
125223
- if (sandbox?.enabled) {
125224
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Sandbox:")} ${import_picocolors4.cyan(`http://localhost:${import_picocolors4.bold(sandbox.port.toString())}/api`)}`);
125225
- } else if (sandbox) {
125226
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Sandbox:")} ${import_picocolors4.cyan("Disabled")}`);
125227
- }
125228
- if (backend) {
125229
- const backendUrl = backend.vitePort ? `http://localhost:${import_picocolors4.bold(backend.vitePort.toString())}/api ${import_picocolors4.dim(`(via ${backend.port})`)}` : `http://localhost:${import_picocolors4.bold(backend.port.toString())}/api`;
125230
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Backend:")} ${import_picocolors4.cyan(backendUrl)}`);
125231
- }
125232
- if (timeback && timeback.courseCount > 0) {
125233
- const enrollmentInfo = timeback.enrolledCount !== timeback.courseCount ? ` / ${timeback.enrolledCount} enrolled` : "";
125234
- const label = `${timeback.courseCount} ${pluralize(timeback.courseCount, "course")}${enrollmentInfo} ${import_picocolors4.dim(`(${timeback.mode})`)}`;
125235
- viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Timeback:")} ${import_picocolors4.cyan(label)}`);
125236
- }
125237
- viteConfig.logger.info("");
125238
- }
125239
125348
  // src/lib/sandbox/project-info.ts
125240
125349
  import fs5 from "node:fs";
125241
125350
  import path3 from "node:path";
125242
- import { loadPlaycademyConfig } from "playcademy/utils";
125351
+ import { loadPlaycademyConfig as loadPlaycademyConfig2 } from "playcademy/utils";
125243
125352
 
125244
125353
  // ../utils/src/uuid.ts
125245
125354
  var UUID_REGEX2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
@@ -125460,7 +125569,7 @@ async function extractProjectInfo(viteConfig, timebackOptions) {
125460
125569
  const directoryName = path3.basename(projectRoot);
125461
125570
  let config2 = null;
125462
125571
  try {
125463
- config2 = await loadPlaycademyConfig();
125572
+ config2 = await loadPlaycademyConfig2();
125464
125573
  } catch {}
125465
125574
  let packageJson = {};
125466
125575
  try {
@@ -125492,7 +125601,7 @@ async function extractProjectInfo(viteConfig, timebackOptions) {
125492
125601
  }
125493
125602
 
125494
125603
  // src/lib/sandbox/timeback.ts
125495
- var import_picocolors5 = __toESM(require_picocolors(), 1);
125604
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
125496
125605
  // ../constants/src/domains.ts
125497
125606
  var POSTHOG_CONFIG = {
125498
125607
  apiKey: "phc_z7aVQnB4P9FHm91g0BSfJTlIU6lISCBVhIJEFdfnYfX",
@@ -125557,9 +125666,9 @@ function validateTimebackConfig(viteConfig, timeback3) {
125557
125666
  if (realCourses.length > 0 && !hasTimebackCredentials2()) {
125558
125667
  const courseList = realCourses.map(([key]) => key).join(", ");
125559
125668
  viteConfig.logger.warn("");
125560
- viteConfig.logger.warn(import_picocolors5.default.yellow(`⚠️ TimeBack: Real course IDs for ${import_picocolors5.default.bold(courseList)} but credentials missing.`));
125561
- viteConfig.logger.warn(import_picocolors5.default.dim(` Required: TIMEBACK_API_CLIENT_ID, TIMEBACK_API_CLIENT_SECRET, TIMEBACK_API_AUTH_URL, TIMEBACK_ONEROSTER_API_URL`));
125562
- viteConfig.logger.warn(import_picocolors5.default.dim(` Or use 'mock' for local testing.`));
125669
+ viteConfig.logger.warn(import_picocolors6.default.yellow(`⚠️ TimeBack: Real course IDs for ${import_picocolors6.default.bold(courseList)} but credentials missing.`));
125670
+ viteConfig.logger.warn(import_picocolors6.default.dim(` Required: TIMEBACK_API_CLIENT_ID, TIMEBACK_API_CLIENT_SECRET, TIMEBACK_API_AUTH_URL, TIMEBACK_ONEROSTER_API_URL`));
125671
+ viteConfig.logger.warn(import_picocolors6.default.dim(` Or use 'mock' for local testing.`));
125563
125672
  viteConfig.logger.warn("");
125564
125673
  }
125565
125674
  }
@@ -125678,7 +125787,7 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
125678
125787
  }
125679
125788
  };
125680
125789
  } catch (error2) {
125681
- viteConfig.logger.error(import_picocolors6.default.red(`[Playcademy] Failed to start sandbox: ${error2}`));
125790
+ viteConfig.logger.error(import_picocolors7.default.red(`[Playcademy] Failed to start sandbox: ${error2}`));
125682
125791
  return {
125683
125792
  baseUrl: `http://localhost:${DEFAULT_PORTS2.SANDBOX}`,
125684
125793
  port: DEFAULT_PORTS2.SANDBOX,
@@ -126151,11 +126260,11 @@ async function recreateSandbox(options) {
126151
126260
  const viteServer = getViteServerRef();
126152
126261
  const prefix2 = createLogPrefix("playcademy", "sandbox");
126153
126262
  if (!viteServer) {
126154
- viteConfig.logger.error(`${prefix2} ${import_picocolors7.red("Cannot recreate sandbox database: no Vite server reference")}`);
126263
+ viteConfig.logger.error(`${prefix2} ${import_picocolors8.red("Cannot recreate sandbox database: no Vite server reference")}`);
126155
126264
  return { success: false, error: "No Vite server reference" };
126156
126265
  }
126157
126266
  if (!isShellMode(currentMode)) {
126158
- viteConfig.logger.warn(`${prefix2} ${import_picocolors7.yellow("can only recreate sandbox database in platform or demo mode")}`);
126267
+ viteConfig.logger.warn(`${prefix2} ${import_picocolors8.yellow("can only recreate sandbox database in platform or demo mode")}`);
126159
126268
  return { success: false, error: "Not in a shell-backed mode" };
126160
126269
  }
126161
126270
  viteConfig.logger.info(`${prefix2} recreating database...`);
@@ -126188,7 +126297,7 @@ async function recreateSandbox(options) {
126188
126297
  }
126189
126298
  });
126190
126299
  }
126191
- viteConfig.logger.info(`${prefix2} ${import_picocolors7.green("database recreated")}`);
126300
+ viteConfig.logger.info(`${prefix2} ${import_picocolors8.green("database recreated")}`);
126192
126301
  return { success: true };
126193
126302
  }
126194
126303
 
@@ -126235,38 +126344,38 @@ function setupConfigWatcher(server, viteConfig, platformModeOptions) {
126235
126344
  }
126236
126345
 
126237
126346
  // src/server/hotkeys/cycle-platform-role.ts
126238
- var import_picocolors8 = __toESM(require_picocolors(), 1);
126347
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
126239
126348
 
126240
126349
  // src/types/internal.ts
126241
126350
  var TIMEBACK_ROLES = ["student", "parent", "teacher", "administrator"];
126242
126351
  var PLATFORM_ROLES = ["player", "developer", "admin"];
126243
126352
  // src/server/hotkeys/cycle-platform-role.ts
126244
- function cyclePlatformRole(logger) {
126353
+ function cyclePlatformRole(logger6) {
126245
126354
  const currentRole = getPlatformRoleOverride() ?? "player";
126246
126355
  const currentIndex = PLATFORM_ROLES.indexOf(currentRole);
126247
126356
  const nextIndex = (currentIndex + 1) % PLATFORM_ROLES.length;
126248
126357
  const nextRole = PLATFORM_ROLES[nextIndex];
126249
126358
  const prefix2 = createLogPrefix("playcademy", "user");
126250
126359
  setPlatformRoleOverride(nextRole);
126251
- logger.info(`${prefix2} ${import_picocolors8.red(currentRole)} → ${import_picocolors8.green(nextRole)}`);
126360
+ logger6.info(`${prefix2} ${import_picocolors9.red(currentRole)} → ${import_picocolors9.green(nextRole)}`);
126252
126361
  if (getViteServerRef()) {
126253
126362
  getViteServerRef()?.ws.send({ type: "full-reload", path: "*" });
126254
126363
  } else {
126255
- logger.warn(`${prefix2} ${import_picocolors8.yellow("Cannot cycle platform role: no Vite server reference")}`);
126364
+ logger6.warn(`${prefix2} ${import_picocolors9.yellow("Cannot cycle platform role: no Vite server reference")}`);
126256
126365
  }
126257
126366
  }
126258
126367
  function cyclePlatformRoleHotkey(options) {
126259
126368
  return {
126260
126369
  key: "p",
126261
- description: `${import_picocolors8.cyan(import_picocolors8.bold("[playcademy]"))} cycle platform role`,
126370
+ description: `${import_picocolors9.cyan(import_picocolors9.bold("[playcademy]"))} cycle platform role`,
126262
126371
  action: () => cyclePlatformRole(options.viteConfig.logger)
126263
126372
  };
126264
126373
  }
126265
126374
 
126266
126375
  // src/server/hotkeys/cycle-timeback-role.ts
126267
- var import_picocolors9 = __toESM(require_picocolors(), 1);
126268
- var { bold: bold5, cyan: cyan4, green: green4, red: red3, yellow: yellow3 } = import_picocolors9.default;
126269
- function cycleTimebackRole(logger) {
126376
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
126377
+ var { bold: bold5, cyan: cyan4, green: green4, red: red3, yellow: yellow3 } = import_picocolors10.default;
126378
+ function cycleTimebackRole(logger6) {
126270
126379
  const currentRole = getTimebackRoleOverride() ?? "student";
126271
126380
  const currentIndex = TIMEBACK_ROLES.indexOf(currentRole);
126272
126381
  const nextIndex = (currentIndex + 1) % TIMEBACK_ROLES.length;
@@ -126274,11 +126383,11 @@ function cycleTimebackRole(logger) {
126274
126383
  setTimebackRoleOverride(nextRole);
126275
126384
  getSandboxRef()?.setRole?.(nextRole);
126276
126385
  const prefix2 = createLogPrefix("playcademy", "timeback");
126277
- logger.info(`${prefix2} ${red3(currentRole)} → ${green4(nextRole)}`);
126386
+ logger6.info(`${prefix2} ${red3(currentRole)} → ${green4(nextRole)}`);
126278
126387
  if (getViteServerRef()) {
126279
126388
  getViteServerRef()?.ws.send({ type: "full-reload", path: "*" });
126280
126389
  } else {
126281
- logger.warn(`${prefix2} ${yellow3("Cannot cycle TimeBack role: no Vite server reference")}`);
126390
+ logger6.warn(`${prefix2} ${yellow3("Cannot cycle TimeBack role: no Vite server reference")}`);
126282
126391
  }
126283
126392
  }
126284
126393
  function cycleTimebackRoleHotkey(options) {
@@ -126290,8 +126399,8 @@ function cycleTimebackRoleHotkey(options) {
126290
126399
  }
126291
126400
 
126292
126401
  // src/server/hotkeys/recreate-database.ts
126293
- var import_picocolors10 = __toESM(require_picocolors(), 1);
126294
- var { bold: bold6, cyan: cyan5 } = import_picocolors10.default;
126402
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
126403
+ var { bold: bold6, cyan: cyan5 } = import_picocolors11.default;
126295
126404
  async function recreateSandboxDatabase(options) {
126296
126405
  await recreateSandbox({
126297
126406
  viteConfig: options.viteConfig,
@@ -126307,7 +126416,7 @@ function recreateDatabaseHotkey(options) {
126307
126416
  }
126308
126417
 
126309
126418
  // src/server/hotkeys/toggle-mode.ts
126310
- var import_picocolors12 = __toESM(require_picocolors(), 1);
126419
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
126311
126420
 
126312
126421
  // src/lib/backend/server.ts
126313
126422
  import {
@@ -126317,7 +126426,7 @@ import {
126317
126426
  } from "playcademy/utils";
126318
126427
 
126319
126428
  // src/lib/backend/hot-reload.ts
126320
- var import_picocolors11 = __toESM(require_picocolors(), 1);
126429
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
126321
126430
  import path5 from "node:path";
126322
126431
  function createHotReloadCallbacks(viteConfig) {
126323
126432
  function formatPath(changedPath) {
@@ -126334,7 +126443,7 @@ function createHotReloadCallbacks(viteConfig) {
126334
126443
  onSuccess: (changedPath) => {
126335
126444
  const relativePath = formatPath(changedPath);
126336
126445
  if (relativePath) {
126337
- viteConfig.logger.info(`${prefix2} ${import_picocolors11.green("hmr update")} ${import_picocolors11.dim(relativePath)}`);
126446
+ viteConfig.logger.info(`${prefix2} ${import_picocolors12.green("hmr update")} ${import_picocolors12.dim(relativePath)}`);
126338
126447
  } else {
126339
126448
  viteConfig.logger.info(`${prefix2} reloaded`);
126340
126449
  }
@@ -126516,7 +126625,7 @@ async function toggleMode(options) {
126516
126625
  const viteServer = getViteServerRef();
126517
126626
  const prefix2 = createLogPrefix("playcademy");
126518
126627
  if (!viteServer) {
126519
- options.viteConfig.logger.error(`${prefix2} ${import_picocolors12.red("Cannot toggle mode: no Vite server reference")}`);
126628
+ options.viteConfig.logger.error(`${prefix2} ${import_picocolors13.red("Cannot toggle mode: no Vite server reference")}`);
126520
126629
  return;
126521
126630
  }
126522
126631
  await cleanupServers();
@@ -126530,12 +126639,12 @@ async function toggleMode(options) {
126530
126639
  } else {
126531
126640
  await configurePlatformMode(viteServer, options.viteConfig, options.platformModeOptions);
126532
126641
  }
126533
- options.viteConfig.logger.info(`${prefix2} ${import_picocolors12.green("switched to")} ${import_picocolors12.green(import_picocolors12.bold(newMode))} ${import_picocolors12.green("mode")}`);
126642
+ options.viteConfig.logger.info(`${prefix2} ${import_picocolors13.green("switched to")} ${import_picocolors13.green(import_picocolors13.bold(newMode))} ${import_picocolors13.green("mode")}`);
126534
126643
  }
126535
126644
  function toggleModeHotkey(options) {
126536
126645
  return {
126537
126646
  key: "m",
126538
- description: `${import_picocolors12.cyan(import_picocolors12.bold("[playcademy]"))} cycle platform/demo/standalone mode`,
126647
+ description: `${import_picocolors13.cyan(import_picocolors13.bold("[playcademy]"))} cycle platform/demo/standalone mode`,
126539
126648
  action: () => toggleMode(options)
126540
126649
  };
126541
126650
  }
@@ -126578,6 +126687,7 @@ async function configureServerHook(server, context) {
126578
126687
  setViteServerRef(server);
126579
126688
  setCurrentMode(context.options.mode);
126580
126689
  setupProcessShutdownHandlers();
126690
+ await registerAssetMiddleware(server, context.viteConfig.root, context.options.configPath);
126581
126691
  if (hasActiveServers()) {
126582
126692
  await cleanupServers();
126583
126693
  }
@@ -126656,17 +126766,24 @@ function buildInitCall() {
126656
126766
  ].join(",");
126657
126767
  return `posthog.init('${POSTHOG_CONFIG.apiKey}',{${config2}},'${POSTHOG_INSTANCE_NAME}');`;
126658
126768
  }
126659
- function transformIndexHtmlHook(context) {
126660
- if (context.viteConfig?.command !== "build") {
126661
- return;
126769
+ async function transformIndexHtmlHook(context) {
126770
+ const isBuild = context.viteConfig?.command === "build";
126771
+ const tags = [];
126772
+ if (!isBuild) {
126773
+ const manifestScript = await getDevManifestScript(context.options.configPath);
126774
+ if (manifestScript) {
126775
+ const body2 = manifestScript.replace(/^<script>/, "").replace(/<\/script>$/, "");
126776
+ tags.push({ tag: "script", children: body2, injectTo: "head" });
126777
+ }
126662
126778
  }
126663
- return [
126664
- {
126779
+ if (isBuild) {
126780
+ tags.push({
126665
126781
  tag: "script",
126666
126782
  children: SERVER_TIMING_PATCH + POSTHOG_LOADER + buildInitCall(),
126667
126783
  injectTo: "head"
126668
- }
126669
- ];
126784
+ });
126785
+ }
126786
+ return tags;
126670
126787
  }
126671
126788
 
126672
126789
  // src/hooks/write-bundle.ts