@playcademy/vite-plugin 0.1.36 → 0.1.38
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/README.md +64 -0
- package/dist/index.js +851 -761
- package/dist/lib/sandbox/index.d.ts +1 -1
- package/dist/lib/sandbox/server.d.ts +1 -0
- package/dist/lib/sandbox/timeback.d.ts +22 -1
- package/dist/server/hotkeys/cycle-timeback-role.d.ts +9 -0
- package/dist/server/hotkeys/index.d.ts +9 -0
- package/dist/server/hotkeys/recreate-database.d.ts +9 -0
- package/dist/server/hotkeys/toggle-mode.d.ts +9 -0
- package/dist/server/middleware.d.ts +6 -1
- package/dist/server/state.d.ts +10 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/internal.d.ts +10 -3
- package/dist/types/options.d.ts +65 -0
- package/package.json +3 -3
- package/dist/server/mode-switcher.d.ts +0 -9
- package/dist/server/recreate-sandbox-database.d.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -41140,7 +41140,8 @@ var serverState = {
|
|
|
41140
41140
|
sandbox: null,
|
|
41141
41141
|
backend: null,
|
|
41142
41142
|
viteServer: null,
|
|
41143
|
-
currentMode: "platform"
|
|
41143
|
+
currentMode: "platform",
|
|
41144
|
+
timebackRoleOverride: null
|
|
41144
41145
|
};
|
|
41145
41146
|
function hasActiveServers() {
|
|
41146
41147
|
return !!(serverState.backend || serverState.sandbox);
|
|
@@ -41157,6 +41158,12 @@ function getViteServerRef() {
|
|
|
41157
41158
|
function setViteServerRef(server) {
|
|
41158
41159
|
serverState.viteServer = server;
|
|
41159
41160
|
}
|
|
41161
|
+
function getTimebackRoleOverride() {
|
|
41162
|
+
return serverState.timebackRoleOverride;
|
|
41163
|
+
}
|
|
41164
|
+
function setTimebackRoleOverride(role) {
|
|
41165
|
+
serverState.timebackRoleOverride = role;
|
|
41166
|
+
}
|
|
41160
41167
|
|
|
41161
41168
|
// src/server/cleanup.ts
|
|
41162
41169
|
async function cleanupServers() {
|
|
@@ -41176,73 +41183,12 @@ async function cleanupServers() {
|
|
|
41176
41183
|
}
|
|
41177
41184
|
}
|
|
41178
41185
|
|
|
41179
|
-
// src/server/
|
|
41180
|
-
var shutdownHandlersRegistered = false;
|
|
41181
|
-
function setupProcessShutdownHandlers() {
|
|
41182
|
-
if (shutdownHandlersRegistered)
|
|
41183
|
-
return;
|
|
41184
|
-
shutdownHandlersRegistered = true;
|
|
41185
|
-
let isShuttingDown = false;
|
|
41186
|
-
const shutdown = async () => {
|
|
41187
|
-
if (isShuttingDown)
|
|
41188
|
-
return;
|
|
41189
|
-
isShuttingDown = true;
|
|
41190
|
-
await cleanupServers();
|
|
41191
|
-
process.exit(0);
|
|
41192
|
-
};
|
|
41193
|
-
process.on("SIGINT", shutdown);
|
|
41194
|
-
process.on("SIGTERM", shutdown);
|
|
41195
|
-
}
|
|
41196
|
-
|
|
41197
|
-
// src/server/mode-switcher.ts
|
|
41198
|
-
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
41199
|
-
// package.json
|
|
41200
|
-
var package_default = {
|
|
41201
|
-
name: "@playcademy/vite-plugin",
|
|
41202
|
-
version: "0.1.35",
|
|
41203
|
-
type: "module",
|
|
41204
|
-
exports: {
|
|
41205
|
-
".": {
|
|
41206
|
-
import: "./dist/index.js",
|
|
41207
|
-
types: "./dist/index.d.ts"
|
|
41208
|
-
}
|
|
41209
|
-
},
|
|
41210
|
-
main: "dist/index.js",
|
|
41211
|
-
module: "dist/index.js",
|
|
41212
|
-
files: [
|
|
41213
|
-
"dist"
|
|
41214
|
-
],
|
|
41215
|
-
scripts: {
|
|
41216
|
-
build: "rm -rf dist && bun build.ts",
|
|
41217
|
-
docs: "typedoc --skipErrorChecking",
|
|
41218
|
-
pub: "bun publish.ts"
|
|
41219
|
-
},
|
|
41220
|
-
dependencies: {
|
|
41221
|
-
archiver: "^7.0.1",
|
|
41222
|
-
picocolors: "^1.1.1",
|
|
41223
|
-
playcademy: "workspace:*"
|
|
41224
|
-
},
|
|
41225
|
-
devDependencies: {
|
|
41226
|
-
"@inquirer/prompts": "^7.8.6",
|
|
41227
|
-
"@playcademy/sandbox": "workspace:*",
|
|
41228
|
-
"@types/archiver": "^6.0.3",
|
|
41229
|
-
"@types/bun": "latest"
|
|
41230
|
-
},
|
|
41231
|
-
peerDependencies: {
|
|
41232
|
-
typescript: "^5",
|
|
41233
|
-
vite: "^5 || ^6"
|
|
41234
|
-
}
|
|
41235
|
-
};
|
|
41236
|
-
|
|
41237
|
-
// src/lib/backend/server.ts
|
|
41238
|
-
import {
|
|
41239
|
-
loadPlaycademyConfigAndSetWorkspace as loadConfigAndSetWorkspace,
|
|
41240
|
-
startPlaycademyDevServer,
|
|
41241
|
-
startPlaycademyHotReload
|
|
41242
|
-
} from "playcademy/utils";
|
|
41243
|
-
|
|
41244
|
-
// src/lib/logging/adapter.ts
|
|
41186
|
+
// src/server/hotkeys/cycle-timeback-role.ts
|
|
41245
41187
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
41188
|
+
|
|
41189
|
+
// src/types/internal.ts
|
|
41190
|
+
var TIMEBACK_ROLES = ["student", "parent", "teacher", "administrator"];
|
|
41191
|
+
// src/server/hotkeys/cycle-timeback-role.ts
|
|
41246
41192
|
function formatTimestamp() {
|
|
41247
41193
|
const now = new Date;
|
|
41248
41194
|
const hours = now.getHours();
|
|
@@ -41250,117 +41196,32 @@ function formatTimestamp() {
|
|
|
41250
41196
|
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
41251
41197
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
41252
41198
|
const displayHours = hours % 12 || 12;
|
|
41253
|
-
return import_picocolors2.dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
41254
|
-
}
|
|
41255
|
-
function
|
|
41256
|
-
const
|
|
41257
|
-
const
|
|
41258
|
-
|
|
41259
|
-
|
|
41260
|
-
|
|
41261
|
-
|
|
41262
|
-
|
|
41263
|
-
}
|
|
41264
|
-
|
|
41265
|
-
|
|
41266
|
-
function printBanner(viteConfig, servers, projectInfo, pluginVersion) {
|
|
41267
|
-
const INDENT = " ".repeat(2);
|
|
41268
|
-
viteConfig.logger.info("");
|
|
41269
|
-
viteConfig.logger.info(`${INDENT}${import_picocolors3.green(import_picocolors3.bold("PLAYCADEMY"))} ${import_picocolors3.green(`v${pluginVersion}`)}`);
|
|
41270
|
-
viteConfig.logger.info("");
|
|
41271
|
-
viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Game:")} ${import_picocolors3.cyan(projectInfo.slug)}`);
|
|
41272
|
-
viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Sandbox:")} ${import_picocolors3.cyan(`http://localhost:${import_picocolors3.bold(servers.sandbox.toString())}/api`)}`);
|
|
41273
|
-
if (servers.backend) {
|
|
41274
|
-
const backendUrl = servers.vite ? `http://localhost:${import_picocolors3.bold(servers.vite.toString())}/api ${import_picocolors3.dim(`(via ${servers.backend})`)}` : `http://localhost:${import_picocolors3.bold(servers.backend.toString())}/api`;
|
|
41275
|
-
viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Backend:")} ${import_picocolors3.cyan(backendUrl)}`);
|
|
41276
|
-
}
|
|
41277
|
-
if (servers.realtime) {
|
|
41278
|
-
viteConfig.logger.info(`${INDENT}${import_picocolors3.green("➜")} ${import_picocolors3.bold("Realtime:")} ${import_picocolors3.cyan(`ws://localhost:${import_picocolors3.bold(servers.realtime.toString())}`)}`);
|
|
41279
|
-
}
|
|
41280
|
-
viteConfig.logger.info("");
|
|
41281
|
-
}
|
|
41282
|
-
// src/lib/backend/hot-reload.ts
|
|
41283
|
-
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
41284
|
-
function formatChangedPath(changedPath) {
|
|
41285
|
-
if (!changedPath)
|
|
41286
|
-
return;
|
|
41287
|
-
if (changedPath.includes("/api/")) {
|
|
41288
|
-
return changedPath.substring(changedPath.indexOf("/api/"));
|
|
41199
|
+
return import_picocolors2.default.dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
41200
|
+
}
|
|
41201
|
+
function cycleTimebackRole(logger) {
|
|
41202
|
+
const currentRole = getTimebackRoleOverride() ?? "student";
|
|
41203
|
+
const currentIndex = TIMEBACK_ROLES.indexOf(currentRole);
|
|
41204
|
+
const nextIndex = (currentIndex + 1) % TIMEBACK_ROLES.length;
|
|
41205
|
+
const nextRole = TIMEBACK_ROLES[nextIndex];
|
|
41206
|
+
setTimebackRoleOverride(nextRole);
|
|
41207
|
+
logger.info(`${formatTimestamp()} ${import_picocolors2.default.cyan(import_picocolors2.default.bold("[playcademy]"))} ${import_picocolors2.default.dim("(timeback)")} ${import_picocolors2.default.red(currentRole)} → ${import_picocolors2.default.green(nextRole)}`);
|
|
41208
|
+
if (getViteServerRef()) {
|
|
41209
|
+
getViteServerRef()?.ws.send({ type: "full-reload", path: "*" });
|
|
41210
|
+
} else {
|
|
41211
|
+
logger.warn(`${formatTimestamp()} ${import_picocolors2.default.red(import_picocolors2.bold("[playcademy]"))} ${import_picocolors2.dim("(timeback)")} ${import_picocolors2.yellow("Cannot cycle TimeBack role: no Vite server reference")}`);
|
|
41289
41212
|
}
|
|
41290
|
-
return changedPath;
|
|
41291
|
-
}
|
|
41292
|
-
function createHotReloadCallbacks(viteConfig) {
|
|
41293
|
-
return {
|
|
41294
|
-
onSuccess: (changedPath) => {
|
|
41295
|
-
const relativePath = formatChangedPath(changedPath);
|
|
41296
|
-
if (relativePath) {
|
|
41297
|
-
viteConfig.logger.info(`${import_picocolors4.dim("(backend)")} ${import_picocolors4.green("hmr update")} ${import_picocolors4.dim(relativePath)}`, { timestamp: true });
|
|
41298
|
-
} else {
|
|
41299
|
-
viteConfig.logger.info("backend reloaded", { timestamp: true });
|
|
41300
|
-
}
|
|
41301
|
-
},
|
|
41302
|
-
onError: (error) => {
|
|
41303
|
-
viteConfig.logger.error(`backend reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
41304
|
-
}
|
|
41305
|
-
};
|
|
41306
41213
|
}
|
|
41214
|
+
var cycleTimebackRoleHotkey = (options) => ({
|
|
41215
|
+
key: "t",
|
|
41216
|
+
description: "cycle TimeBack role",
|
|
41217
|
+
action: () => cycleTimebackRole(options.viteConfig.logger)
|
|
41218
|
+
});
|
|
41219
|
+
|
|
41220
|
+
// src/server/hotkeys/recreate-database.ts
|
|
41221
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
41307
41222
|
|
|
41308
|
-
// src/lib/backend/server.ts
|
|
41309
|
-
async function tryLoadConfig(viteConfig, configPath) {
|
|
41310
|
-
try {
|
|
41311
|
-
return await loadConfigAndSetWorkspace(configPath);
|
|
41312
|
-
} catch (error) {
|
|
41313
|
-
if (error instanceof Error && !error.message.includes("Could not find")) {
|
|
41314
|
-
viteConfig.logger.warn(`Could not load playcademy.config.js: ${error.message}`);
|
|
41315
|
-
}
|
|
41316
|
-
return null;
|
|
41317
|
-
}
|
|
41318
|
-
}
|
|
41319
|
-
function needsCliDevServer(config) {
|
|
41320
|
-
return !!config.integrations;
|
|
41321
|
-
}
|
|
41322
|
-
async function startServer(options) {
|
|
41323
|
-
const { port, config, platformUrl } = options;
|
|
41324
|
-
return startPlaycademyDevServer({
|
|
41325
|
-
port,
|
|
41326
|
-
config,
|
|
41327
|
-
quiet: true,
|
|
41328
|
-
platformUrl,
|
|
41329
|
-
customLogger: createLoggerAdapter("backend")
|
|
41330
|
-
});
|
|
41331
|
-
}
|
|
41332
|
-
function setupHotReload(serverRef, options) {
|
|
41333
|
-
const watcher = startPlaycademyHotReload(async () => {
|
|
41334
|
-
await serverRef.current.server.dispose();
|
|
41335
|
-
serverRef.current = await startServer(options);
|
|
41336
|
-
}, createHotReloadCallbacks(options.viteConfig));
|
|
41337
|
-
return () => watcher.close();
|
|
41338
|
-
}
|
|
41339
|
-
async function setupCliDevServer(options) {
|
|
41340
|
-
const { preferredPort, viteConfig, platformUrl, configPath } = options;
|
|
41341
|
-
const config = await tryLoadConfig(viteConfig, configPath);
|
|
41342
|
-
if (!config)
|
|
41343
|
-
return null;
|
|
41344
|
-
if (!needsCliDevServer(config))
|
|
41345
|
-
return null;
|
|
41346
|
-
try {
|
|
41347
|
-
const port = await findAvailablePort(preferredPort);
|
|
41348
|
-
const serverOptions = { port, config, platformUrl, viteConfig };
|
|
41349
|
-
const serverRef = { current: await startServer(serverOptions) };
|
|
41350
|
-
const stopHotReload = setupHotReload(serverRef, serverOptions);
|
|
41351
|
-
return {
|
|
41352
|
-
server: serverRef.current.server,
|
|
41353
|
-
port: serverRef.current.port,
|
|
41354
|
-
stopHotReload,
|
|
41355
|
-
cleanup: () => cleanupServerInfo("backend", viteConfig.root, process.pid)
|
|
41356
|
-
};
|
|
41357
|
-
} catch (error) {
|
|
41358
|
-
viteConfig.logger.error(`Failed to start game backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
41359
|
-
return null;
|
|
41360
|
-
}
|
|
41361
|
-
}
|
|
41362
41223
|
// src/lib/sandbox/server.ts
|
|
41363
|
-
var
|
|
41224
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
41364
41225
|
import { DEFAULT_PORTS as DEFAULT_PORTS2 } from "playcademy/constants";
|
|
41365
41226
|
|
|
41366
41227
|
// ../sandbox/dist/server.js
|
|
@@ -50427,8 +50288,8 @@ function assembleStyles() {
|
|
|
50427
50288
|
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
50428
50289
|
Object.defineProperties(styles, {
|
|
50429
50290
|
rgbToAnsi256: {
|
|
50430
|
-
value(red,
|
|
50431
|
-
if (red ===
|
|
50291
|
+
value(red, green, blue) {
|
|
50292
|
+
if (red === green && green === blue) {
|
|
50432
50293
|
if (red < 8) {
|
|
50433
50294
|
return 16;
|
|
50434
50295
|
}
|
|
@@ -50437,7 +50298,7 @@ function assembleStyles() {
|
|
|
50437
50298
|
}
|
|
50438
50299
|
return Math.round((red - 8) / 247 * 24) + 232;
|
|
50439
50300
|
}
|
|
50440
|
-
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(
|
|
50301
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
50441
50302
|
},
|
|
50442
50303
|
enumerable: false
|
|
50443
50304
|
},
|
|
@@ -50473,24 +50334,24 @@ function assembleStyles() {
|
|
|
50473
50334
|
return 90 + (code - 8);
|
|
50474
50335
|
}
|
|
50475
50336
|
let red;
|
|
50476
|
-
let
|
|
50477
|
-
let
|
|
50337
|
+
let green;
|
|
50338
|
+
let blue;
|
|
50478
50339
|
if (code >= 232) {
|
|
50479
50340
|
red = ((code - 232) * 10 + 8) / 255;
|
|
50480
|
-
|
|
50481
|
-
|
|
50341
|
+
green = red;
|
|
50342
|
+
blue = red;
|
|
50482
50343
|
} else {
|
|
50483
50344
|
code -= 16;
|
|
50484
50345
|
const remainder = code % 36;
|
|
50485
50346
|
red = Math.floor(code / 36) / 5;
|
|
50486
|
-
|
|
50487
|
-
|
|
50347
|
+
green = Math.floor(remainder / 6) / 5;
|
|
50348
|
+
blue = remainder % 6 / 5;
|
|
50488
50349
|
}
|
|
50489
|
-
const value = Math.max(red,
|
|
50350
|
+
const value = Math.max(red, green, blue) * 2;
|
|
50490
50351
|
if (value === 0) {
|
|
50491
50352
|
return 30;
|
|
50492
50353
|
}
|
|
50493
|
-
let result = 30 + (Math.round(
|
|
50354
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
50494
50355
|
if (value === 2) {
|
|
50495
50356
|
result += 60;
|
|
50496
50357
|
}
|
|
@@ -50499,7 +50360,7 @@ function assembleStyles() {
|
|
|
50499
50360
|
enumerable: false
|
|
50500
50361
|
},
|
|
50501
50362
|
rgbToAnsi: {
|
|
50502
|
-
value: (red,
|
|
50363
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
50503
50364
|
enumerable: false
|
|
50504
50365
|
},
|
|
50505
50366
|
hexToAnsi: {
|
|
@@ -55966,7 +55827,7 @@ var init_api = __esm(() => {
|
|
|
55966
55827
|
ANSI_BACKGROUND_OFFSET = 10;
|
|
55967
55828
|
wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
55968
55829
|
wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
55969
|
-
wrapAnsi16m = (offset = 0) => (red,
|
|
55830
|
+
wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
55970
55831
|
styles = {
|
|
55971
55832
|
modifier: {
|
|
55972
55833
|
reset: [0, 0],
|
|
@@ -162180,8 +162041,8 @@ var require_colorette = __commonJS2((exports) => {
|
|
|
162180
162041
|
var createColors = ({ useColor = isColorSupported } = {}) => useColor ? colors4 : Object.keys(colors4).reduce((colors5, key) => ({ ...colors5, [key]: String }), {});
|
|
162181
162042
|
var {
|
|
162182
162043
|
reset,
|
|
162183
|
-
bold:
|
|
162184
|
-
dim:
|
|
162044
|
+
bold: bold2,
|
|
162045
|
+
dim: dim2,
|
|
162185
162046
|
italic,
|
|
162186
162047
|
underline,
|
|
162187
162048
|
inverse,
|
|
@@ -162189,11 +162050,11 @@ var require_colorette = __commonJS2((exports) => {
|
|
|
162189
162050
|
strikethrough,
|
|
162190
162051
|
black,
|
|
162191
162052
|
red,
|
|
162192
|
-
green
|
|
162193
|
-
yellow,
|
|
162194
|
-
blue
|
|
162053
|
+
green,
|
|
162054
|
+
yellow: yellow2,
|
|
162055
|
+
blue,
|
|
162195
162056
|
magenta,
|
|
162196
|
-
cyan
|
|
162057
|
+
cyan,
|
|
162197
162058
|
white,
|
|
162198
162059
|
gray,
|
|
162199
162060
|
bgBlack,
|
|
@@ -162239,15 +162100,15 @@ var require_colorette = __commonJS2((exports) => {
|
|
|
162239
162100
|
exports.bgYellowBright = bgYellowBright;
|
|
162240
162101
|
exports.black = black;
|
|
162241
162102
|
exports.blackBright = blackBright;
|
|
162242
|
-
exports.blue =
|
|
162103
|
+
exports.blue = blue;
|
|
162243
162104
|
exports.blueBright = blueBright;
|
|
162244
|
-
exports.bold =
|
|
162105
|
+
exports.bold = bold2;
|
|
162245
162106
|
exports.createColors = createColors;
|
|
162246
|
-
exports.cyan =
|
|
162107
|
+
exports.cyan = cyan;
|
|
162247
162108
|
exports.cyanBright = cyanBright;
|
|
162248
|
-
exports.dim =
|
|
162109
|
+
exports.dim = dim2;
|
|
162249
162110
|
exports.gray = gray;
|
|
162250
|
-
exports.green =
|
|
162111
|
+
exports.green = green;
|
|
162251
162112
|
exports.greenBright = greenBright;
|
|
162252
162113
|
exports.hidden = hidden;
|
|
162253
162114
|
exports.inverse = inverse;
|
|
@@ -162262,7 +162123,7 @@ var require_colorette = __commonJS2((exports) => {
|
|
|
162262
162123
|
exports.underline = underline;
|
|
162263
162124
|
exports.white = white;
|
|
162264
162125
|
exports.whiteBright = whiteBright;
|
|
162265
|
-
exports.yellow =
|
|
162126
|
+
exports.yellow = yellow2;
|
|
162266
162127
|
exports.yellowBright = yellowBright;
|
|
162267
162128
|
});
|
|
162268
162129
|
var require_dist22 = __commonJS2((exports) => {
|
|
@@ -175287,6 +175148,7 @@ var config = {
|
|
|
175287
175148
|
};
|
|
175288
175149
|
process.env.BETTER_AUTH_SECRET = config.auth.betterAuthSecret;
|
|
175289
175150
|
process.env.GAME_JWT_SECRET = config.auth.gameJwtSecret;
|
|
175151
|
+
process.env.PUBLIC_IS_LOCAL = "true";
|
|
175290
175152
|
var RequestError = class extends Error {
|
|
175291
175153
|
constructor(message3, options) {
|
|
175292
175154
|
super(message3, options);
|
|
@@ -175822,9 +175684,9 @@ var serve = (options, listeningListener) => {
|
|
|
175822
175684
|
});
|
|
175823
175685
|
return server;
|
|
175824
175686
|
};
|
|
175825
|
-
var
|
|
175687
|
+
var package_default = {
|
|
175826
175688
|
name: "@playcademy/sandbox",
|
|
175827
|
-
version: "0.
|
|
175689
|
+
version: "0.2.0",
|
|
175828
175690
|
description: "Local development server for Playcademy game development",
|
|
175829
175691
|
type: "module",
|
|
175830
175692
|
exports: {
|
|
@@ -177412,7 +177274,7 @@ var colorStatus = async (status) => {
|
|
|
177412
177274
|
}
|
|
177413
177275
|
return `${status}`;
|
|
177414
177276
|
};
|
|
177415
|
-
async function
|
|
177277
|
+
async function log(fn, prefix2, method, path4, status = 0, elapsed) {
|
|
177416
177278
|
const out2 = prefix2 === "<--" ? `${prefix2} ${method} ${path4}` : `${prefix2} ${method} ${path4} ${await colorStatus(status)} ${elapsed}`;
|
|
177417
177279
|
fn(out2);
|
|
177418
177280
|
}
|
|
@@ -177420,10 +177282,10 @@ var logger2 = (fn = console.log) => {
|
|
|
177420
177282
|
return async function logger2(c3, next) {
|
|
177421
177283
|
const { method, url } = c3.req;
|
|
177422
177284
|
const path4 = url.slice(url.indexOf("/", 8));
|
|
177423
|
-
await
|
|
177285
|
+
await log(fn, "<--", method, path4);
|
|
177424
177286
|
const start2 = Date.now();
|
|
177425
177287
|
await next();
|
|
177426
|
-
await
|
|
177288
|
+
await log(fn, "-->", method, path4, c3.res.status, time(start2));
|
|
177427
177289
|
};
|
|
177428
177290
|
};
|
|
177429
177291
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
@@ -189515,7 +189377,9 @@ var init_constants = __esm3(() => {
|
|
|
189515
189377
|
studentTTL: 600000,
|
|
189516
189378
|
studentMaxSize: 500,
|
|
189517
189379
|
assessmentTTL: 1800000,
|
|
189518
|
-
assessmentMaxSize: 200
|
|
189380
|
+
assessmentMaxSize: 200,
|
|
189381
|
+
enrollmentTTL: 5000,
|
|
189382
|
+
enrollmentMaxSize: 100
|
|
189519
189383
|
};
|
|
189520
189384
|
CONFIG_DEFAULTS = {
|
|
189521
189385
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
@@ -189733,6 +189597,25 @@ class ResourceNotFoundError extends TimebackError {
|
|
|
189733
189597
|
Object.setPrototypeOf(this, ResourceNotFoundError.prototype);
|
|
189734
189598
|
}
|
|
189735
189599
|
}
|
|
189600
|
+
init_constants();
|
|
189601
|
+
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
189602
|
+
var SUBJECT_VALUES = TIMEBACK_SUBJECTS;
|
|
189603
|
+
var GRADE_VALUES = TIMEBACK_GRADE_LEVELS;
|
|
189604
|
+
function isPlaycademyResourceMetadata(value) {
|
|
189605
|
+
if (!isObject2(value)) {
|
|
189606
|
+
return false;
|
|
189607
|
+
}
|
|
189608
|
+
if (!("mastery" in value) || value.mastery === undefined) {
|
|
189609
|
+
return true;
|
|
189610
|
+
}
|
|
189611
|
+
return isObject2(value.mastery);
|
|
189612
|
+
}
|
|
189613
|
+
function isTimebackSubject(value) {
|
|
189614
|
+
return typeof value === "string" && SUBJECT_VALUES.includes(value);
|
|
189615
|
+
}
|
|
189616
|
+
function isTimebackGrade(value) {
|
|
189617
|
+
return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
|
|
189618
|
+
}
|
|
189736
189619
|
var isBrowser2 = () => {
|
|
189737
189620
|
const g52 = globalThis;
|
|
189738
189621
|
return typeof g52.window !== "undefined" && typeof g52.document !== "undefined";
|
|
@@ -189907,7 +189790,7 @@ var createLogger2 = () => {
|
|
|
189907
189790
|
log: performLog2
|
|
189908
189791
|
};
|
|
189909
189792
|
};
|
|
189910
|
-
var
|
|
189793
|
+
var log3 = createLogger2();
|
|
189911
189794
|
async function deleteTimebackResources(client2, courseId) {
|
|
189912
189795
|
const sourcedIds = deriveSourcedIds(courseId);
|
|
189913
189796
|
const { fetchTimebackConfig: fetchTimebackConfig2 } = await Promise.resolve().then(() => (init_verify5(), exports_verify));
|
|
@@ -189955,7 +189838,7 @@ async function deleteTimebackResources(client2, courseId) {
|
|
|
189955
189838
|
lessonType: config2.componentResource.lessonType
|
|
189956
189839
|
})
|
|
189957
189840
|
]);
|
|
189958
|
-
|
|
189841
|
+
log3.info("[TimeBack] Resources soft-deleted", { courseId });
|
|
189959
189842
|
}
|
|
189960
189843
|
init_verify5();
|
|
189961
189844
|
async function createCourse(client2, config2) {
|
|
@@ -189975,12 +189858,12 @@ async function createCourse(client2, config2) {
|
|
|
189975
189858
|
if (!response.sourcedIdPairs?.allocatedSourcedId) {
|
|
189976
189859
|
throw new Error("Course created but TimeBack did not return allocatedSourcedId");
|
|
189977
189860
|
}
|
|
189978
|
-
|
|
189861
|
+
log3.info("[TimeBack] Course created", {
|
|
189979
189862
|
courseId: response.sourcedIdPairs.allocatedSourcedId
|
|
189980
189863
|
});
|
|
189981
189864
|
return response.sourcedIdPairs.allocatedSourcedId;
|
|
189982
189865
|
} catch (error2) {
|
|
189983
|
-
|
|
189866
|
+
log3.error("[TimeBack] Failed to create course", {
|
|
189984
189867
|
title: config2.course.title,
|
|
189985
189868
|
error: error2
|
|
189986
189869
|
});
|
|
@@ -189999,9 +189882,9 @@ async function createComponent(client2, config2, sourcedIds) {
|
|
|
189999
189882
|
prerequisiteCriteria: config2.component.prerequisiteCriteria
|
|
190000
189883
|
};
|
|
190001
189884
|
await client2.oneroster.courseComponents.create({ courseComponent: componentData });
|
|
190002
|
-
|
|
189885
|
+
log3.info("[TimeBack] Component created", { componentId: sourcedIds.component });
|
|
190003
189886
|
} catch (error2) {
|
|
190004
|
-
|
|
189887
|
+
log3.error("[TimeBack] Failed to create component", {
|
|
190005
189888
|
componentId: sourcedIds.component,
|
|
190006
189889
|
error: error2
|
|
190007
189890
|
});
|
|
@@ -190021,11 +189904,11 @@ async function createResource(client2, config2, sourcedIds) {
|
|
|
190021
189904
|
importance: config2.resource.importance,
|
|
190022
189905
|
metadata: config2.resource.metadata
|
|
190023
189906
|
};
|
|
190024
|
-
|
|
189907
|
+
log3.debug("[TimeBack] Creating resource", { resourceId: sourcedIds.resource });
|
|
190025
189908
|
await client2.oneroster.resources.create({ resource: resourceData });
|
|
190026
|
-
|
|
189909
|
+
log3.info("[TimeBack] Resource created", { resourceId: sourcedIds.resource });
|
|
190027
189910
|
} catch (error2) {
|
|
190028
|
-
|
|
189911
|
+
log3.error("[TimeBack] Failed to create resource", {
|
|
190029
189912
|
resourceId: sourcedIds.resource,
|
|
190030
189913
|
error: error2
|
|
190031
189914
|
});
|
|
@@ -190043,17 +189926,17 @@ async function createComponentResourceLink(client2, config2, sourcedIds) {
|
|
|
190043
189926
|
sortOrder: config2.componentResource.sortOrder,
|
|
190044
189927
|
lessonType: config2.componentResource.lessonType
|
|
190045
189928
|
};
|
|
190046
|
-
|
|
189929
|
+
log3.debug("[TimeBack] Creating component-resource link", {
|
|
190047
189930
|
componentResourceId: sourcedIds.componentResource
|
|
190048
189931
|
});
|
|
190049
189932
|
await client2.oneroster.componentResources.create({
|
|
190050
189933
|
componentResource: componentResourceData
|
|
190051
189934
|
});
|
|
190052
|
-
|
|
189935
|
+
log3.info("[TimeBack] Component-resource link created", {
|
|
190053
189936
|
componentResourceId: sourcedIds.componentResource
|
|
190054
189937
|
});
|
|
190055
189938
|
} catch (error2) {
|
|
190056
|
-
|
|
189939
|
+
log3.error("[TimeBack] Failed to create component-resource link", {
|
|
190057
189940
|
componentResourceId: sourcedIds.componentResource,
|
|
190058
189941
|
error: error2
|
|
190059
189942
|
});
|
|
@@ -190077,7 +189960,7 @@ async function setupTimebackResources(client2, config2, options) {
|
|
|
190077
189960
|
...verboseData && { verboseData }
|
|
190078
189961
|
};
|
|
190079
189962
|
} catch (error2) {
|
|
190080
|
-
|
|
189963
|
+
log3.error("[TimeBack] Setup failed", { error: error2 });
|
|
190081
189964
|
throw error2;
|
|
190082
189965
|
}
|
|
190083
189966
|
}
|
|
@@ -190096,9 +189979,9 @@ async function updateCourse(client2, config2, courseId) {
|
|
|
190096
189979
|
metadata: config2.course.metadata
|
|
190097
189980
|
};
|
|
190098
189981
|
await client2.oneroster.courses.update(courseId, courseData);
|
|
190099
|
-
|
|
189982
|
+
log3.info("[TimeBack] Course updated", { courseId });
|
|
190100
189983
|
} catch (error2) {
|
|
190101
|
-
|
|
189984
|
+
log3.error("[TimeBack] Failed to update course", {
|
|
190102
189985
|
courseId,
|
|
190103
189986
|
error: error2
|
|
190104
189987
|
});
|
|
@@ -190117,9 +190000,9 @@ async function updateComponent(client2, config2, sourcedIds) {
|
|
|
190117
190000
|
prerequisiteCriteria: config2.component.prerequisiteCriteria
|
|
190118
190001
|
};
|
|
190119
190002
|
await client2.oneroster.courseComponents.update(sourcedIds.component, componentData);
|
|
190120
|
-
|
|
190003
|
+
log3.info("[TimeBack] Component updated", { componentId: sourcedIds.component });
|
|
190121
190004
|
} catch (error2) {
|
|
190122
|
-
|
|
190005
|
+
log3.error("[TimeBack] Failed to update component", {
|
|
190123
190006
|
componentId: sourcedIds.component,
|
|
190124
190007
|
error: error2
|
|
190125
190008
|
});
|
|
@@ -190140,9 +190023,9 @@ async function updateResource(client2, config2, resourceId) {
|
|
|
190140
190023
|
metadata: config2.resource.metadata
|
|
190141
190024
|
};
|
|
190142
190025
|
await client2.oneroster.resources.update(resourceId, resourceData);
|
|
190143
|
-
|
|
190026
|
+
log3.info("[TimeBack] Resource updated", { resourceId });
|
|
190144
190027
|
} catch (error2) {
|
|
190145
|
-
|
|
190028
|
+
log3.error("[TimeBack] Failed to update resource", {
|
|
190146
190029
|
resourceId,
|
|
190147
190030
|
error: error2
|
|
190148
190031
|
});
|
|
@@ -190161,11 +190044,11 @@ async function updateComponentResourceLink(client2, config2, sourcedIds) {
|
|
|
190161
190044
|
lessonType: config2.componentResource.lessonType
|
|
190162
190045
|
};
|
|
190163
190046
|
await client2.oneroster.componentResources.update(sourcedIds.componentResource, componentResourceData);
|
|
190164
|
-
|
|
190047
|
+
log3.info("[TimeBack] Component-resource link updated", {
|
|
190165
190048
|
componentResourceId: sourcedIds.componentResource
|
|
190166
190049
|
});
|
|
190167
190050
|
} catch (error2) {
|
|
190168
|
-
|
|
190051
|
+
log3.error("[TimeBack] Failed to update component-resource link", {
|
|
190169
190052
|
componentResourceId: sourcedIds.componentResource,
|
|
190170
190053
|
error: error2
|
|
190171
190054
|
});
|
|
@@ -190334,7 +190217,7 @@ async function request({
|
|
|
190334
190217
|
lastError = error2;
|
|
190335
190218
|
if (attempt < retries) {
|
|
190336
190219
|
const delay = Math.pow(HTTP_DEFAULTS.retryBackoffBase, attempt) * 1000;
|
|
190337
|
-
|
|
190220
|
+
log3.warn(`[TimebackClient] Request failed, retrying in ${delay}ms`, {
|
|
190338
190221
|
attempt: attempt + 1,
|
|
190339
190222
|
maxRetries: retries,
|
|
190340
190223
|
status: res.status,
|
|
@@ -190366,7 +190249,7 @@ async function request({
|
|
|
190366
190249
|
lastError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
190367
190250
|
if (attempt < retries) {
|
|
190368
190251
|
const delay = Math.pow(HTTP_DEFAULTS.retryBackoffBase, attempt) * 1000;
|
|
190369
|
-
|
|
190252
|
+
log3.warn(`[TimebackClient] Network error, retrying in ${delay}ms`, {
|
|
190370
190253
|
attempt: attempt + 1,
|
|
190371
190254
|
maxRetries: retries,
|
|
190372
190255
|
error: lastError.message,
|
|
@@ -190389,11 +190272,158 @@ async function requestCaliper(options) {
|
|
|
190389
190272
|
});
|
|
190390
190273
|
}
|
|
190391
190274
|
init_constants();
|
|
190275
|
+
function createCaliperNamespace(client2) {
|
|
190276
|
+
const urls = createOneRosterUrls(client2.getBaseUrl());
|
|
190277
|
+
const caliper = {
|
|
190278
|
+
emit: async (event, sensorUrl) => {
|
|
190279
|
+
const envelope = {
|
|
190280
|
+
sensor: sensorUrl,
|
|
190281
|
+
sendTime: new Date().toISOString(),
|
|
190282
|
+
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190283
|
+
data: [event]
|
|
190284
|
+
};
|
|
190285
|
+
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190286
|
+
},
|
|
190287
|
+
emitBatch: async (events, sensorUrl) => {
|
|
190288
|
+
if (events.length === 0)
|
|
190289
|
+
return;
|
|
190290
|
+
const envelope = {
|
|
190291
|
+
sensor: sensorUrl,
|
|
190292
|
+
sendTime: new Date().toISOString(),
|
|
190293
|
+
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190294
|
+
data: events
|
|
190295
|
+
};
|
|
190296
|
+
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190297
|
+
},
|
|
190298
|
+
emitActivityEvent: async (data) => {
|
|
190299
|
+
const event = {
|
|
190300
|
+
"@context": CALIPER_CONSTANTS.context,
|
|
190301
|
+
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190302
|
+
type: TIMEBACK_EVENT_TYPES.activityEvent,
|
|
190303
|
+
eventTime: new Date().toISOString(),
|
|
190304
|
+
profile: CALIPER_CONSTANTS.profile,
|
|
190305
|
+
actor: {
|
|
190306
|
+
id: urls.user(data.studentId),
|
|
190307
|
+
type: TIMEBACK_TYPES.user,
|
|
190308
|
+
email: data.studentEmail
|
|
190309
|
+
},
|
|
190310
|
+
action: TIMEBACK_ACTIONS.completed,
|
|
190311
|
+
object: {
|
|
190312
|
+
id: caliper.buildActivityUrl(data),
|
|
190313
|
+
type: TIMEBACK_TYPES.activityContext,
|
|
190314
|
+
subject: data.subject,
|
|
190315
|
+
app: {
|
|
190316
|
+
name: data.appName
|
|
190317
|
+
},
|
|
190318
|
+
activity: {
|
|
190319
|
+
name: data.activityName
|
|
190320
|
+
},
|
|
190321
|
+
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190322
|
+
process: false
|
|
190323
|
+
},
|
|
190324
|
+
generated: {
|
|
190325
|
+
id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
|
|
190326
|
+
type: TIMEBACK_TYPES.activityMetricsCollection,
|
|
190327
|
+
attempt: data.attemptNumber || 1,
|
|
190328
|
+
items: [
|
|
190329
|
+
...data.totalQuestions !== undefined ? [
|
|
190330
|
+
{
|
|
190331
|
+
type: ACTIVITY_METRIC_TYPES.totalQuestions,
|
|
190332
|
+
value: data.totalQuestions
|
|
190333
|
+
}
|
|
190334
|
+
] : [],
|
|
190335
|
+
...data.correctQuestions !== undefined ? [
|
|
190336
|
+
{
|
|
190337
|
+
type: ACTIVITY_METRIC_TYPES.correctQuestions,
|
|
190338
|
+
value: data.correctQuestions
|
|
190339
|
+
}
|
|
190340
|
+
] : [],
|
|
190341
|
+
...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
|
|
190342
|
+
...data.masteredUnits !== undefined ? [
|
|
190343
|
+
{
|
|
190344
|
+
type: ACTIVITY_METRIC_TYPES.masteredUnits,
|
|
190345
|
+
value: data.masteredUnits
|
|
190346
|
+
}
|
|
190347
|
+
] : []
|
|
190348
|
+
],
|
|
190349
|
+
...data.extensions ? { extensions: data.extensions } : {}
|
|
190350
|
+
}
|
|
190351
|
+
};
|
|
190352
|
+
return caliper.emit(event, data.sensorUrl);
|
|
190353
|
+
},
|
|
190354
|
+
emitTimeSpentEvent: async (data) => {
|
|
190355
|
+
const event = {
|
|
190356
|
+
"@context": CALIPER_CONSTANTS.context,
|
|
190357
|
+
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190358
|
+
type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
|
|
190359
|
+
eventTime: new Date().toISOString(),
|
|
190360
|
+
profile: CALIPER_CONSTANTS.profile,
|
|
190361
|
+
actor: {
|
|
190362
|
+
id: urls.user(data.studentId),
|
|
190363
|
+
type: TIMEBACK_TYPES.user,
|
|
190364
|
+
email: data.studentEmail
|
|
190365
|
+
},
|
|
190366
|
+
action: TIMEBACK_ACTIONS.spentTime,
|
|
190367
|
+
object: {
|
|
190368
|
+
id: caliper.buildActivityUrl(data),
|
|
190369
|
+
type: TIMEBACK_TYPES.activityContext,
|
|
190370
|
+
subject: data.subject,
|
|
190371
|
+
app: {
|
|
190372
|
+
name: data.appName
|
|
190373
|
+
},
|
|
190374
|
+
activity: {
|
|
190375
|
+
name: data.activityName
|
|
190376
|
+
},
|
|
190377
|
+
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190378
|
+
process: false
|
|
190379
|
+
},
|
|
190380
|
+
generated: {
|
|
190381
|
+
id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
|
|
190382
|
+
type: TIMEBACK_TYPES.timeSpentMetricsCollection,
|
|
190383
|
+
items: [
|
|
190384
|
+
{ type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
|
|
190385
|
+
...data.inactiveTimeSeconds !== undefined ? [
|
|
190386
|
+
{
|
|
190387
|
+
type: TIME_METRIC_TYPES.inactive,
|
|
190388
|
+
value: data.inactiveTimeSeconds
|
|
190389
|
+
}
|
|
190390
|
+
] : [],
|
|
190391
|
+
...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
|
|
190392
|
+
]
|
|
190393
|
+
}
|
|
190394
|
+
};
|
|
190395
|
+
return caliper.emit(event, data.sensorUrl);
|
|
190396
|
+
},
|
|
190397
|
+
buildActivityUrl: (data) => {
|
|
190398
|
+
const base = data.sensorUrl.replace(/\/$/, "");
|
|
190399
|
+
return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
|
|
190400
|
+
}
|
|
190401
|
+
};
|
|
190402
|
+
return caliper;
|
|
190403
|
+
}
|
|
190404
|
+
function createEduBridgeNamespace(client2) {
|
|
190405
|
+
const enrollments = {
|
|
190406
|
+
listByUser: async (userId) => {
|
|
190407
|
+
const response = await client2["request"](`/edubridge/enrollments/user/${userId}`, "GET");
|
|
190408
|
+
return response.data;
|
|
190409
|
+
}
|
|
190410
|
+
};
|
|
190411
|
+
const analytics = {
|
|
190412
|
+
getEnrollmentFacts: async (enrollmentId) => {
|
|
190413
|
+
return client2["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
|
|
190414
|
+
}
|
|
190415
|
+
};
|
|
190416
|
+
return {
|
|
190417
|
+
enrollments,
|
|
190418
|
+
analytics
|
|
190419
|
+
};
|
|
190420
|
+
}
|
|
190421
|
+
init_constants();
|
|
190392
190422
|
function logTimebackError(operation, error2, context) {
|
|
190393
190423
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
190394
190424
|
if (error2 instanceof TimebackApiError) {
|
|
190395
190425
|
const imsError = error2.details;
|
|
190396
|
-
|
|
190426
|
+
log3.error(`[TimeBack ${operation}] ${errorMessage}`, {
|
|
190397
190427
|
...context,
|
|
190398
190428
|
status: error2.status,
|
|
190399
190429
|
imsx_codeMajor: imsError?.imsx_codeMajor,
|
|
@@ -190404,7 +190434,7 @@ function logTimebackError(operation, error2, context) {
|
|
|
190404
190434
|
console.error(`[TimeBack Error during ${operation}] Full details:`, JSON.stringify(error2.details, null, 2));
|
|
190405
190435
|
}
|
|
190406
190436
|
} else {
|
|
190407
|
-
|
|
190437
|
+
log3.error(`[TimeBack ${operation}] ${errorMessage}`, context);
|
|
190408
190438
|
}
|
|
190409
190439
|
}
|
|
190410
190440
|
function createOneRosterNamespace(client2) {
|
|
@@ -190603,153 +190633,6 @@ function createOneRosterNamespace(client2) {
|
|
|
190603
190633
|
};
|
|
190604
190634
|
}
|
|
190605
190635
|
init_constants();
|
|
190606
|
-
function createCaliperNamespace(client2) {
|
|
190607
|
-
const urls = createOneRosterUrls(client2.getBaseUrl());
|
|
190608
|
-
const caliper = {
|
|
190609
|
-
emit: async (event, sensorUrl) => {
|
|
190610
|
-
const envelope = {
|
|
190611
|
-
sensor: sensorUrl,
|
|
190612
|
-
sendTime: new Date().toISOString(),
|
|
190613
|
-
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190614
|
-
data: [event]
|
|
190615
|
-
};
|
|
190616
|
-
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190617
|
-
},
|
|
190618
|
-
emitBatch: async (events, sensorUrl) => {
|
|
190619
|
-
if (events.length === 0)
|
|
190620
|
-
return;
|
|
190621
|
-
const envelope = {
|
|
190622
|
-
sensor: sensorUrl,
|
|
190623
|
-
sendTime: new Date().toISOString(),
|
|
190624
|
-
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190625
|
-
data: events
|
|
190626
|
-
};
|
|
190627
|
-
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190628
|
-
},
|
|
190629
|
-
emitActivityEvent: async (data) => {
|
|
190630
|
-
const event = {
|
|
190631
|
-
"@context": CALIPER_CONSTANTS.context,
|
|
190632
|
-
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190633
|
-
type: TIMEBACK_EVENT_TYPES.activityEvent,
|
|
190634
|
-
eventTime: new Date().toISOString(),
|
|
190635
|
-
profile: CALIPER_CONSTANTS.profile,
|
|
190636
|
-
actor: {
|
|
190637
|
-
id: urls.user(data.studentId),
|
|
190638
|
-
type: TIMEBACK_TYPES.user,
|
|
190639
|
-
email: data.studentEmail
|
|
190640
|
-
},
|
|
190641
|
-
action: TIMEBACK_ACTIONS.completed,
|
|
190642
|
-
object: {
|
|
190643
|
-
id: caliper.buildActivityUrl(data),
|
|
190644
|
-
type: TIMEBACK_TYPES.activityContext,
|
|
190645
|
-
subject: data.subject,
|
|
190646
|
-
app: {
|
|
190647
|
-
name: data.appName
|
|
190648
|
-
},
|
|
190649
|
-
activity: {
|
|
190650
|
-
name: data.activityName
|
|
190651
|
-
},
|
|
190652
|
-
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190653
|
-
process: false
|
|
190654
|
-
},
|
|
190655
|
-
generated: {
|
|
190656
|
-
id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
|
|
190657
|
-
type: TIMEBACK_TYPES.activityMetricsCollection,
|
|
190658
|
-
attempt: data.attemptNumber || 1,
|
|
190659
|
-
items: [
|
|
190660
|
-
...data.totalQuestions !== undefined ? [
|
|
190661
|
-
{
|
|
190662
|
-
type: ACTIVITY_METRIC_TYPES.totalQuestions,
|
|
190663
|
-
value: data.totalQuestions
|
|
190664
|
-
}
|
|
190665
|
-
] : [],
|
|
190666
|
-
...data.correctQuestions !== undefined ? [
|
|
190667
|
-
{
|
|
190668
|
-
type: ACTIVITY_METRIC_TYPES.correctQuestions,
|
|
190669
|
-
value: data.correctQuestions
|
|
190670
|
-
}
|
|
190671
|
-
] : [],
|
|
190672
|
-
...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
|
|
190673
|
-
...data.masteredUnits !== undefined ? [
|
|
190674
|
-
{
|
|
190675
|
-
type: ACTIVITY_METRIC_TYPES.masteredUnits,
|
|
190676
|
-
value: data.masteredUnits
|
|
190677
|
-
}
|
|
190678
|
-
] : []
|
|
190679
|
-
],
|
|
190680
|
-
...data.extensions ? { extensions: data.extensions } : {}
|
|
190681
|
-
}
|
|
190682
|
-
};
|
|
190683
|
-
return caliper.emit(event, data.sensorUrl);
|
|
190684
|
-
},
|
|
190685
|
-
emitTimeSpentEvent: async (data) => {
|
|
190686
|
-
const event = {
|
|
190687
|
-
"@context": CALIPER_CONSTANTS.context,
|
|
190688
|
-
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190689
|
-
type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
|
|
190690
|
-
eventTime: new Date().toISOString(),
|
|
190691
|
-
profile: CALIPER_CONSTANTS.profile,
|
|
190692
|
-
actor: {
|
|
190693
|
-
id: urls.user(data.studentId),
|
|
190694
|
-
type: TIMEBACK_TYPES.user,
|
|
190695
|
-
email: data.studentEmail
|
|
190696
|
-
},
|
|
190697
|
-
action: TIMEBACK_ACTIONS.spentTime,
|
|
190698
|
-
object: {
|
|
190699
|
-
id: caliper.buildActivityUrl(data),
|
|
190700
|
-
type: TIMEBACK_TYPES.activityContext,
|
|
190701
|
-
subject: data.subject,
|
|
190702
|
-
app: {
|
|
190703
|
-
name: data.appName
|
|
190704
|
-
},
|
|
190705
|
-
activity: {
|
|
190706
|
-
name: data.activityName
|
|
190707
|
-
},
|
|
190708
|
-
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190709
|
-
process: false
|
|
190710
|
-
},
|
|
190711
|
-
generated: {
|
|
190712
|
-
id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
|
|
190713
|
-
type: TIMEBACK_TYPES.timeSpentMetricsCollection,
|
|
190714
|
-
items: [
|
|
190715
|
-
{ type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
|
|
190716
|
-
...data.inactiveTimeSeconds !== undefined ? [
|
|
190717
|
-
{
|
|
190718
|
-
type: TIME_METRIC_TYPES.inactive,
|
|
190719
|
-
value: data.inactiveTimeSeconds
|
|
190720
|
-
}
|
|
190721
|
-
] : [],
|
|
190722
|
-
...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
|
|
190723
|
-
]
|
|
190724
|
-
}
|
|
190725
|
-
};
|
|
190726
|
-
return caliper.emit(event, data.sensorUrl);
|
|
190727
|
-
},
|
|
190728
|
-
buildActivityUrl: (data) => {
|
|
190729
|
-
const base = data.sensorUrl.replace(/\/$/, "");
|
|
190730
|
-
return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
|
|
190731
|
-
}
|
|
190732
|
-
};
|
|
190733
|
-
return caliper;
|
|
190734
|
-
}
|
|
190735
|
-
function createEduBridgeNamespace(client2) {
|
|
190736
|
-
const enrollments = {
|
|
190737
|
-
listByUser: async (userId) => {
|
|
190738
|
-
const response = await client2["request"](`/edubridge/enrollments/user/${userId}`, "GET");
|
|
190739
|
-
return response.data;
|
|
190740
|
-
}
|
|
190741
|
-
};
|
|
190742
|
-
const analytics = {
|
|
190743
|
-
getEnrollmentFacts: async (enrollmentId) => {
|
|
190744
|
-
return client2["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
|
|
190745
|
-
}
|
|
190746
|
-
};
|
|
190747
|
-
return {
|
|
190748
|
-
enrollments,
|
|
190749
|
-
analytics
|
|
190750
|
-
};
|
|
190751
|
-
}
|
|
190752
|
-
init_constants();
|
|
190753
190636
|
init_constants();
|
|
190754
190637
|
|
|
190755
190638
|
class TimebackCache {
|
|
@@ -190771,7 +190654,7 @@ class TimebackCache {
|
|
|
190771
190654
|
}
|
|
190772
190655
|
if (Date.now() > entry.expiresAt) {
|
|
190773
190656
|
this.delete(key);
|
|
190774
|
-
|
|
190657
|
+
log3.debug(`[${this.name}] Cache entry expired`, { key });
|
|
190775
190658
|
this.misses++;
|
|
190776
190659
|
return;
|
|
190777
190660
|
}
|
|
@@ -190786,7 +190669,7 @@ class TimebackCache {
|
|
|
190786
190669
|
}
|
|
190787
190670
|
this.cache.set(key, { value, expiresAt });
|
|
190788
190671
|
this._updateAccessOrder(key);
|
|
190789
|
-
|
|
190672
|
+
log3.debug(`[${this.name}] Cache entry set`, {
|
|
190790
190673
|
key,
|
|
190791
190674
|
expiresAt: new Date(expiresAt).toISOString()
|
|
190792
190675
|
});
|
|
@@ -190801,7 +190684,7 @@ class TimebackCache {
|
|
|
190801
190684
|
clear() {
|
|
190802
190685
|
this.cache.clear();
|
|
190803
190686
|
this.accessOrder = [];
|
|
190804
|
-
|
|
190687
|
+
log3.debug(`[${this.name}] Cache cleared`);
|
|
190805
190688
|
}
|
|
190806
190689
|
size() {
|
|
190807
190690
|
return this.cache.size;
|
|
@@ -190824,7 +190707,7 @@ class TimebackCache {
|
|
|
190824
190707
|
this.delete(key);
|
|
190825
190708
|
}
|
|
190826
190709
|
if (expiredKeys.length > 0) {
|
|
190827
|
-
|
|
190710
|
+
log3.debug(`[${this.name}] Cleaned up expired entries`, {
|
|
190828
190711
|
count: expiredKeys.length
|
|
190829
190712
|
});
|
|
190830
190713
|
}
|
|
@@ -190849,7 +190732,7 @@ class TimebackCache {
|
|
|
190849
190732
|
return;
|
|
190850
190733
|
const oldestKey = this.accessOrder[0];
|
|
190851
190734
|
this.delete(oldestKey);
|
|
190852
|
-
|
|
190735
|
+
log3.debug(`[${this.name}] Evicted LRU entry`, { key: oldestKey });
|
|
190853
190736
|
}
|
|
190854
190737
|
}
|
|
190855
190738
|
|
|
@@ -190857,6 +190740,7 @@ class TimebackCacheManager {
|
|
|
190857
190740
|
studentCache;
|
|
190858
190741
|
assessmentLineItemCache;
|
|
190859
190742
|
resourceMasteryCache;
|
|
190743
|
+
enrollmentCache;
|
|
190860
190744
|
constructor() {
|
|
190861
190745
|
this.studentCache = new TimebackCache({
|
|
190862
190746
|
defaultTTL: CACHE_DEFAULTS.studentTTL,
|
|
@@ -190873,6 +190757,11 @@ class TimebackCacheManager {
|
|
|
190873
190757
|
maxSize: CACHE_DEFAULTS.assessmentMaxSize,
|
|
190874
190758
|
name: "ResourceMasteryCache"
|
|
190875
190759
|
});
|
|
190760
|
+
this.enrollmentCache = new TimebackCache({
|
|
190761
|
+
defaultTTL: CACHE_DEFAULTS.enrollmentTTL,
|
|
190762
|
+
maxSize: CACHE_DEFAULTS.enrollmentMaxSize,
|
|
190763
|
+
name: "EnrollmentCache"
|
|
190764
|
+
});
|
|
190876
190765
|
}
|
|
190877
190766
|
getStudent(key) {
|
|
190878
190767
|
return this.studentCache.get(key);
|
|
@@ -190892,37 +190781,36 @@ class TimebackCacheManager {
|
|
|
190892
190781
|
setResourceMasterableUnits(key, value) {
|
|
190893
190782
|
this.resourceMasteryCache.set(key, value);
|
|
190894
190783
|
}
|
|
190784
|
+
getEnrollments(studentId) {
|
|
190785
|
+
return this.enrollmentCache.get(studentId);
|
|
190786
|
+
}
|
|
190787
|
+
setEnrollments(studentId, enrollments) {
|
|
190788
|
+
this.enrollmentCache.set(studentId, enrollments);
|
|
190789
|
+
}
|
|
190895
190790
|
clearAll() {
|
|
190896
190791
|
this.studentCache.clear();
|
|
190897
190792
|
this.assessmentLineItemCache.clear();
|
|
190898
|
-
|
|
190793
|
+
this.resourceMasteryCache.clear();
|
|
190794
|
+
this.enrollmentCache.clear();
|
|
190795
|
+
log3.info("[TimebackCacheManager] All caches cleared");
|
|
190899
190796
|
}
|
|
190900
190797
|
getStats() {
|
|
190901
190798
|
return {
|
|
190902
190799
|
studentCache: this.studentCache.stats(),
|
|
190903
190800
|
assessmentLineItemCache: this.assessmentLineItemCache.stats(),
|
|
190904
|
-
resourceMasteryCache: this.resourceMasteryCache.stats()
|
|
190801
|
+
resourceMasteryCache: this.resourceMasteryCache.stats(),
|
|
190802
|
+
enrollmentCache: this.enrollmentCache.stats()
|
|
190905
190803
|
};
|
|
190906
190804
|
}
|
|
190907
190805
|
cleanup() {
|
|
190908
190806
|
this.studentCache.cleanup();
|
|
190909
190807
|
this.assessmentLineItemCache.cleanup();
|
|
190910
190808
|
this.resourceMasteryCache.cleanup();
|
|
190911
|
-
|
|
190809
|
+
this.enrollmentCache.cleanup();
|
|
190810
|
+
log3.debug("[TimebackCacheManager] Cache cleanup completed");
|
|
190912
190811
|
}
|
|
190913
190812
|
}
|
|
190914
190813
|
init_constants();
|
|
190915
|
-
init_constants();
|
|
190916
|
-
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
190917
|
-
function isPlaycademyResourceMetadata(value) {
|
|
190918
|
-
if (!isObject2(value)) {
|
|
190919
|
-
return false;
|
|
190920
|
-
}
|
|
190921
|
-
if (!("mastery" in value) || value.mastery === undefined) {
|
|
190922
|
-
return true;
|
|
190923
|
-
}
|
|
190924
|
-
return isObject2(value.mastery);
|
|
190925
|
-
}
|
|
190926
190814
|
|
|
190927
190815
|
class MasteryTracker {
|
|
190928
190816
|
cacheManager;
|
|
@@ -190940,7 +190828,7 @@ class MasteryTracker {
|
|
|
190940
190828
|
}
|
|
190941
190829
|
const masterableUnits = await this.resolveMasterableUnits(resourceId);
|
|
190942
190830
|
if (!masterableUnits || masterableUnits <= 0) {
|
|
190943
|
-
|
|
190831
|
+
log3.warn("[MasteryTracker] No masterableUnits configured for course", {
|
|
190944
190832
|
courseId,
|
|
190945
190833
|
resourceId
|
|
190946
190834
|
});
|
|
@@ -190948,7 +190836,7 @@ class MasteryTracker {
|
|
|
190948
190836
|
}
|
|
190949
190837
|
const facts = await this.fetchEnrollmentAnalyticsFacts(studentId, courseId);
|
|
190950
190838
|
if (!facts) {
|
|
190951
|
-
|
|
190839
|
+
log3.warn("[MasteryTracker] Unable to retrieve analytics for mastery-based completion", {
|
|
190952
190840
|
studentId,
|
|
190953
190841
|
courseId
|
|
190954
190842
|
});
|
|
@@ -190988,13 +190876,13 @@ class MasteryTracker {
|
|
|
190988
190876
|
appName
|
|
190989
190877
|
}
|
|
190990
190878
|
});
|
|
190991
|
-
|
|
190879
|
+
log3.info("[MasteryTracker] Created mastery completion entry", {
|
|
190992
190880
|
studentId,
|
|
190993
190881
|
lineItemId,
|
|
190994
190882
|
resultId
|
|
190995
190883
|
});
|
|
190996
190884
|
} catch (error2) {
|
|
190997
|
-
|
|
190885
|
+
log3.error("[MasteryTracker] Failed to create mastery completion entry", {
|
|
190998
190886
|
studentId,
|
|
190999
190887
|
lineItemId,
|
|
191000
190888
|
error: error2
|
|
@@ -191019,7 +190907,7 @@ class MasteryTracker {
|
|
|
191019
190907
|
this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
|
|
191020
190908
|
return masterableUnits;
|
|
191021
190909
|
} catch (error2) {
|
|
191022
|
-
|
|
190910
|
+
log3.error("[MasteryTracker] Failed to fetch resource metadata for mastery config", {
|
|
191023
190911
|
resourceId,
|
|
191024
190912
|
error: error2
|
|
191025
190913
|
});
|
|
@@ -191032,7 +190920,7 @@ class MasteryTracker {
|
|
|
191032
190920
|
const enrollments = await this.edubridgeNamespace.enrollments.listByUser(studentId);
|
|
191033
190921
|
const enrollment = enrollments.find((e2) => e2.course.id === courseId);
|
|
191034
190922
|
if (!enrollment) {
|
|
191035
|
-
|
|
190923
|
+
log3.warn("[MasteryTracker] Enrollment not found for student/course", {
|
|
191036
190924
|
studentId,
|
|
191037
190925
|
courseId
|
|
191038
190926
|
});
|
|
@@ -191041,7 +190929,7 @@ class MasteryTracker {
|
|
|
191041
190929
|
const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
|
|
191042
190930
|
return analytics.facts;
|
|
191043
190931
|
} catch (error2) {
|
|
191044
|
-
|
|
190932
|
+
log3.error("[MasteryTracker] Failed to load enrollment analytics facts", {
|
|
191045
190933
|
studentId,
|
|
191046
190934
|
courseId,
|
|
191047
190935
|
error: error2
|
|
@@ -191164,7 +191052,7 @@ class ProgressRecorder {
|
|
|
191164
191052
|
if (score !== undefined) {
|
|
191165
191053
|
await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp, masteredUnits, scoreStatus, inProgress, progressData.appName);
|
|
191166
191054
|
} else {
|
|
191167
|
-
|
|
191055
|
+
log3.warn("[ProgressRecorder] Score not provided, skipping gradebook entry", {
|
|
191168
191056
|
studentId,
|
|
191169
191057
|
activityId,
|
|
191170
191058
|
attemptNumber: currentAttemptNumber
|
|
@@ -191211,13 +191099,13 @@ class ProgressRecorder {
|
|
|
191211
191099
|
}
|
|
191212
191100
|
calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
|
|
191213
191101
|
if (xpEarned !== undefined) {
|
|
191214
|
-
|
|
191102
|
+
log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
|
|
191215
191103
|
return xpEarned;
|
|
191216
191104
|
}
|
|
191217
191105
|
if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
|
|
191218
191106
|
const accuracy = correctQuestions / totalQuestions;
|
|
191219
191107
|
const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
|
|
191220
|
-
|
|
191108
|
+
log3.debug("[ProgressRecorder] Calculated XP", {
|
|
191221
191109
|
durationSeconds: progressData.sessionDurationSeconds,
|
|
191222
191110
|
accuracy,
|
|
191223
191111
|
isFirstAttempt,
|
|
@@ -191299,7 +191187,7 @@ class ProgressRecorder {
|
|
|
191299
191187
|
sensorUrl: progressData.sensorUrl,
|
|
191300
191188
|
extensions: extensions || progressData.extensions
|
|
191301
191189
|
}).catch((error2) => {
|
|
191302
|
-
|
|
191190
|
+
log3.error("[ProgressRecorder] Failed to emit activity event", { error: error2 });
|
|
191303
191191
|
});
|
|
191304
191192
|
}
|
|
191305
191193
|
}
|
|
@@ -194030,9 +193918,9 @@ class ZodUnion2 extends ZodType2 {
|
|
|
194030
193918
|
return this._def.options;
|
|
194031
193919
|
}
|
|
194032
193920
|
}
|
|
194033
|
-
ZodUnion2.create = (
|
|
193921
|
+
ZodUnion2.create = (types22, params) => {
|
|
194034
193922
|
return new ZodUnion2({
|
|
194035
|
-
options:
|
|
193923
|
+
options: types22,
|
|
194036
193924
|
typeName: ZodFirstPartyTypeKind2.ZodUnion,
|
|
194037
193925
|
...processCreateParams2(params)
|
|
194038
193926
|
});
|
|
@@ -195345,7 +195233,7 @@ class StudentResolver {
|
|
|
195345
195233
|
if (error2 instanceof TimebackAuthenticationError || error2 instanceof StudentNotFoundError || error2 instanceof TimebackError) {
|
|
195346
195234
|
throw error2;
|
|
195347
195235
|
}
|
|
195348
|
-
|
|
195236
|
+
log3.error("[StudentResolver] Failed to find student by email", {
|
|
195349
195237
|
email,
|
|
195350
195238
|
error: error2
|
|
195351
195239
|
});
|
|
@@ -195369,7 +195257,7 @@ class StudentResolver {
|
|
|
195369
195257
|
this.cacheStudent(email, studentId, studentData);
|
|
195370
195258
|
return studentData;
|
|
195371
195259
|
} catch (error2) {
|
|
195372
|
-
|
|
195260
|
+
log3.error("[StudentResolver] Failed to fetch student by ID, using fallback", {
|
|
195373
195261
|
studentId,
|
|
195374
195262
|
error: error2,
|
|
195375
195263
|
errorMessage: error2 instanceof Error ? error2.message : String(error2),
|
|
@@ -195420,7 +195308,7 @@ class TimebackClient {
|
|
|
195420
195308
|
this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
|
|
195421
195309
|
if (this.credentials) {
|
|
195422
195310
|
this._ensureAuthenticated().catch((error2) => {
|
|
195423
|
-
|
|
195311
|
+
log3.error("[TimebackClient] Auto-authentication failed", { error: error2 });
|
|
195424
195312
|
});
|
|
195425
195313
|
}
|
|
195426
195314
|
}
|
|
@@ -195480,11 +195368,11 @@ class TimebackClient {
|
|
|
195480
195368
|
};
|
|
195481
195369
|
const tokenData = await getTimebackTokenResponse(authConfig);
|
|
195482
195370
|
this.setToken(tokenData.access_token, tokenData.expires_in);
|
|
195483
|
-
|
|
195371
|
+
log3.debug("[TimebackClient] Authentication successful", {
|
|
195484
195372
|
expiresIn: tokenData.expires_in
|
|
195485
195373
|
});
|
|
195486
195374
|
} catch (error2) {
|
|
195487
|
-
|
|
195375
|
+
log3.error("[TimebackClient] Authentication failed", { error: error2 });
|
|
195488
195376
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
195489
195377
|
throw new TimebackAuthenticationError(`TimeBack authentication failed: ${errorMessage}. Verify that your client credentials are correct and not expired. Environment: ${this.baseUrl}.`);
|
|
195490
195378
|
}
|
|
@@ -195536,18 +195424,27 @@ class TimebackClient {
|
|
|
195536
195424
|
await this._ensureAuthenticated();
|
|
195537
195425
|
return this.sessionRecorder.record(courseId, studentIdentifier, sessionData);
|
|
195538
195426
|
}
|
|
195539
|
-
async getEnrollments(studentId
|
|
195427
|
+
async getEnrollments(studentId) {
|
|
195428
|
+
const cached = this.cacheManager.getEnrollments(studentId);
|
|
195429
|
+
if (cached) {
|
|
195430
|
+
return cached;
|
|
195431
|
+
}
|
|
195540
195432
|
await this._ensureAuthenticated();
|
|
195541
|
-
const
|
|
195542
|
-
|
|
195543
|
-
|
|
195544
|
-
|
|
195545
|
-
|
|
195546
|
-
|
|
195547
|
-
|
|
195548
|
-
|
|
195549
|
-
|
|
195550
|
-
|
|
195433
|
+
const edubridgeEnrollments = await this.edubridge.enrollments.listByUser(studentId);
|
|
195434
|
+
const enrollments = edubridgeEnrollments.map((enrollment) => {
|
|
195435
|
+
const grades = enrollment.course.grades ? enrollment.course.grades.map((g52) => parseInt(g52, 10)).filter(isTimebackGrade) : null;
|
|
195436
|
+
const subjects = enrollment.course.subjects ? enrollment.course.subjects.filter(isTimebackSubject) : null;
|
|
195437
|
+
return {
|
|
195438
|
+
sourcedId: enrollment.id,
|
|
195439
|
+
title: enrollment.course.title,
|
|
195440
|
+
courseId: enrollment.course.id,
|
|
195441
|
+
status: "active",
|
|
195442
|
+
grades,
|
|
195443
|
+
subjects
|
|
195444
|
+
};
|
|
195445
|
+
});
|
|
195446
|
+
this.cacheManager.setEnrollments(studentId, enrollments);
|
|
195447
|
+
return enrollments;
|
|
195551
195448
|
}
|
|
195552
195449
|
clearCaches() {
|
|
195553
195450
|
this.cacheManager.clearAll();
|
|
@@ -195786,7 +195683,9 @@ var init_constants2 = __esm4(() => {
|
|
|
195786
195683
|
studentTTL: 600000,
|
|
195787
195684
|
studentMaxSize: 500,
|
|
195788
195685
|
assessmentTTL: 1800000,
|
|
195789
|
-
assessmentMaxSize: 200
|
|
195686
|
+
assessmentMaxSize: 200,
|
|
195687
|
+
enrollmentTTL: 5000,
|
|
195688
|
+
enrollmentMaxSize: 100
|
|
195790
195689
|
};
|
|
195791
195690
|
CONFIG_DEFAULTS2 = {
|
|
195792
195691
|
fileNames: ["timeback.config.js", "timeback.config.json"]
|
|
@@ -195854,8 +195753,8 @@ var init_constants2 = __esm4(() => {
|
|
|
195854
195753
|
});
|
|
195855
195754
|
init_constants2();
|
|
195856
195755
|
var isObject3 = (value) => typeof value === "object" && value !== null;
|
|
195857
|
-
var
|
|
195858
|
-
var
|
|
195756
|
+
var SUBJECT_VALUES2 = TIMEBACK_SUBJECTS2;
|
|
195757
|
+
var GRADE_VALUES2 = TIMEBACK_GRADE_LEVELS2;
|
|
195859
195758
|
function isCourseMetadata(value) {
|
|
195860
195759
|
return isObject3(value);
|
|
195861
195760
|
}
|
|
@@ -195871,11 +195770,11 @@ function isPlaycademyResourceMetadata2(value) {
|
|
|
195871
195770
|
}
|
|
195872
195771
|
return isObject3(value.mastery);
|
|
195873
195772
|
}
|
|
195874
|
-
function
|
|
195875
|
-
return typeof value === "string" &&
|
|
195773
|
+
function isTimebackSubject2(value) {
|
|
195774
|
+
return typeof value === "string" && SUBJECT_VALUES2.includes(value);
|
|
195876
195775
|
}
|
|
195877
|
-
function
|
|
195878
|
-
return typeof value === "number" && Number.isInteger(value) &&
|
|
195776
|
+
function isTimebackGrade2(value) {
|
|
195777
|
+
return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES2.includes(value);
|
|
195879
195778
|
}
|
|
195880
195779
|
var UUID_REGEX = /^[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}$/;
|
|
195881
195780
|
function isValidUUID(value) {
|
|
@@ -196179,6 +196078,68 @@ function buildResourceMetadata({
|
|
|
196179
196078
|
}
|
|
196180
196079
|
return metadata2;
|
|
196181
196080
|
}
|
|
196081
|
+
init_src();
|
|
196082
|
+
async function fetchEnrollmentsForUser(timebackId) {
|
|
196083
|
+
const db = getDatabase();
|
|
196084
|
+
const isLocal = process.env.PUBLIC_IS_LOCAL === "true";
|
|
196085
|
+
if (isLocal) {
|
|
196086
|
+
const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
|
|
196087
|
+
return allIntegrations.map((integration) => ({
|
|
196088
|
+
gameId: integration.gameId,
|
|
196089
|
+
grade: integration.grade,
|
|
196090
|
+
subject: integration.subject,
|
|
196091
|
+
courseId: integration.courseId
|
|
196092
|
+
}));
|
|
196093
|
+
}
|
|
196094
|
+
log2.debug("[timeback-enrollments] Fetching student enrollments from TimeBack", { timebackId });
|
|
196095
|
+
try {
|
|
196096
|
+
const client2 = await getTimebackClient();
|
|
196097
|
+
const classes = await client2.getEnrollments(timebackId);
|
|
196098
|
+
const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
|
|
196099
|
+
if (courseIds.length === 0) {
|
|
196100
|
+
return [];
|
|
196101
|
+
}
|
|
196102
|
+
const integrations = await db.query.gameTimebackIntegrations.findMany({
|
|
196103
|
+
where: inArray(gameTimebackIntegrations.courseId, courseIds)
|
|
196104
|
+
});
|
|
196105
|
+
return integrations.map((integration) => ({
|
|
196106
|
+
gameId: integration.gameId,
|
|
196107
|
+
grade: integration.grade,
|
|
196108
|
+
subject: integration.subject,
|
|
196109
|
+
courseId: integration.courseId
|
|
196110
|
+
}));
|
|
196111
|
+
} catch (error2) {
|
|
196112
|
+
log2.warn("[timeback-enrollments] Failed to fetch TimeBack enrollments:", {
|
|
196113
|
+
error: error2,
|
|
196114
|
+
timebackId
|
|
196115
|
+
});
|
|
196116
|
+
return [];
|
|
196117
|
+
}
|
|
196118
|
+
}
|
|
196119
|
+
async function fetchUserRole(timebackId) {
|
|
196120
|
+
log2.debug("[timeback] Fetching user role from TimeBack", { timebackId });
|
|
196121
|
+
try {
|
|
196122
|
+
const client2 = await getTimebackClient();
|
|
196123
|
+
const user = await client2.oneroster.users.get(timebackId);
|
|
196124
|
+
const primaryRole = user.roles.find((r22) => r22.roleType === "primary");
|
|
196125
|
+
const role = primaryRole?.role ?? user.roles[0]?.role ?? "student";
|
|
196126
|
+
log2.debug("[timeback] Resolved user role", { timebackId, role });
|
|
196127
|
+
return role;
|
|
196128
|
+
} catch (error2) {
|
|
196129
|
+
log2.warn("[timeback] Failed to fetch user role, defaulting to student:", {
|
|
196130
|
+
error: error2,
|
|
196131
|
+
timebackId
|
|
196132
|
+
});
|
|
196133
|
+
return "student";
|
|
196134
|
+
}
|
|
196135
|
+
}
|
|
196136
|
+
async function fetchUserTimebackData(timebackId) {
|
|
196137
|
+
const [role, enrollments] = await Promise.all([
|
|
196138
|
+
fetchUserRole(timebackId),
|
|
196139
|
+
fetchEnrollmentsForUser(timebackId)
|
|
196140
|
+
]);
|
|
196141
|
+
return { role, enrollments };
|
|
196142
|
+
}
|
|
196182
196143
|
var AchievementCompletionType;
|
|
196183
196144
|
((AchievementCompletionType2) => {
|
|
196184
196145
|
AchievementCompletionType2["TIME_PLAYED_SESSION"] = "time_played_session";
|
|
@@ -199484,21 +199445,6 @@ var logger3 = {
|
|
|
199484
199445
|
warn: (msg) => getLogger().warn(msg),
|
|
199485
199446
|
error: (msg) => getLogger().error(msg)
|
|
199486
199447
|
};
|
|
199487
|
-
function detectTimebackCourses() {
|
|
199488
|
-
const coursePattern = /^SANDBOX_TIMEBACK_COURSE_(\d+)_([A-Z_]+)$/i;
|
|
199489
|
-
const courses = [];
|
|
199490
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
199491
|
-
const match2 = key.match(coursePattern);
|
|
199492
|
-
if (match2 && value) {
|
|
199493
|
-
const gradeStr = match2[1];
|
|
199494
|
-
const subjectStr = match2[2];
|
|
199495
|
-
const grade = parseInt(gradeStr, 10);
|
|
199496
|
-
const subject = subjectStr.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
199497
|
-
courses.push({ grade, subject, courseId: value });
|
|
199498
|
-
}
|
|
199499
|
-
}
|
|
199500
|
-
return courses;
|
|
199501
|
-
}
|
|
199502
199448
|
async function seedCoreGames(db) {
|
|
199503
199449
|
const now2 = new Date;
|
|
199504
199450
|
const coreGames = [
|
|
@@ -199523,28 +199469,6 @@ async function seedCoreGames(db) {
|
|
|
199523
199469
|
console.error(`Error seeding core game '${gameData.slug}':`, error2);
|
|
199524
199470
|
}
|
|
199525
199471
|
}
|
|
199526
|
-
if (hasTimebackCredentials()) {
|
|
199527
|
-
const courses = detectTimebackCourses();
|
|
199528
|
-
if (courses.length > 0) {
|
|
199529
|
-
for (const course of courses) {
|
|
199530
|
-
try {
|
|
199531
|
-
await db.insert(gameTimebackIntegrations).values({
|
|
199532
|
-
id: crypto.randomUUID(),
|
|
199533
|
-
gameId: CORE_GAME_UUIDS.PLAYGROUND,
|
|
199534
|
-
courseId: course.courseId,
|
|
199535
|
-
grade: course.grade,
|
|
199536
|
-
subject: course.subject,
|
|
199537
|
-
totalXp: null,
|
|
199538
|
-
lastVerifiedAt: null,
|
|
199539
|
-
createdAt: now2,
|
|
199540
|
-
updatedAt: now2
|
|
199541
|
-
}).onConflictDoNothing();
|
|
199542
|
-
} catch (error2) {
|
|
199543
|
-
console.error(`Error seeding TimeBack integration for playground (${course.subject} grade ${course.grade}):`, error2);
|
|
199544
|
-
}
|
|
199545
|
-
}
|
|
199546
|
-
}
|
|
199547
|
-
}
|
|
199548
199472
|
}
|
|
199549
199473
|
async function seedCurrentProjectGame(db, project) {
|
|
199550
199474
|
const now2 = new Date;
|
|
@@ -199573,24 +199497,6 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
199573
199497
|
updatedAt: now2
|
|
199574
199498
|
};
|
|
199575
199499
|
const [newGame] = await db.insert(games).values(gameRecord).returning();
|
|
199576
|
-
if (hasTimebackCredentials()) {
|
|
199577
|
-
const courses = detectTimebackCourses();
|
|
199578
|
-
if (courses.length > 0) {
|
|
199579
|
-
for (const course of courses) {
|
|
199580
|
-
await db.insert(gameTimebackIntegrations).values({
|
|
199581
|
-
id: crypto.randomUUID(),
|
|
199582
|
-
gameId: newGame.id,
|
|
199583
|
-
courseId: course.courseId,
|
|
199584
|
-
grade: course.grade,
|
|
199585
|
-
subject: course.subject,
|
|
199586
|
-
totalXp: null,
|
|
199587
|
-
lastVerifiedAt: null,
|
|
199588
|
-
createdAt: now2,
|
|
199589
|
-
updatedAt: now2
|
|
199590
|
-
}).onConflictDoNothing();
|
|
199591
|
-
}
|
|
199592
|
-
}
|
|
199593
|
-
}
|
|
199594
199500
|
return newGame;
|
|
199595
199501
|
} catch (error2) {
|
|
199596
199502
|
console.error("❌ Error seeding project game:", error2);
|
|
@@ -199647,12 +199553,23 @@ async function seedSpriteTemplates(db) {
|
|
|
199647
199553
|
}
|
|
199648
199554
|
}
|
|
199649
199555
|
}
|
|
199556
|
+
function resolveStudentId(studentId) {
|
|
199557
|
+
if (!studentId)
|
|
199558
|
+
return null;
|
|
199559
|
+
if (studentId === "mock")
|
|
199560
|
+
return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
|
|
199561
|
+
return studentId;
|
|
199562
|
+
}
|
|
199563
|
+
function getAdminTimebackId() {
|
|
199564
|
+
return resolveStudentId(config.timeback.studentId);
|
|
199565
|
+
}
|
|
199650
199566
|
async function seedDemoData(db) {
|
|
199651
199567
|
try {
|
|
199568
|
+
const adminTimebackId = getAdminTimebackId();
|
|
199652
199569
|
for (const [role, user] of Object.entries(DEMO_USERS)) {
|
|
199653
199570
|
const userValues = {
|
|
199654
199571
|
...user,
|
|
199655
|
-
timebackId: role === "admin"
|
|
199572
|
+
timebackId: role === "admin" ? adminTimebackId : null
|
|
199656
199573
|
};
|
|
199657
199574
|
await db.insert(users).values(userValues).onConflictDoNothing();
|
|
199658
199575
|
}
|
|
@@ -205015,9 +204932,11 @@ async function getUserMe(ctx) {
|
|
|
205015
204932
|
const timebackAccount = await db.query.accounts.findFirst({
|
|
205016
204933
|
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
205017
204934
|
});
|
|
204935
|
+
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
|
|
205018
204936
|
return {
|
|
205019
204937
|
...userData,
|
|
205020
|
-
hasTimebackAccount: !!timebackAccount
|
|
204938
|
+
hasTimebackAccount: !!timebackAccount,
|
|
204939
|
+
timeback: timeback3
|
|
205021
204940
|
};
|
|
205022
204941
|
} catch (error2) {
|
|
205023
204942
|
if (error2 instanceof ApiError)
|
|
@@ -209128,7 +209047,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209128
209047
|
throw ApiError.unauthorized("Login required");
|
|
209129
209048
|
try {
|
|
209130
209049
|
const db = getDatabase();
|
|
209131
|
-
const
|
|
209050
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209132
209051
|
where: eq(playerCharacters.userId, user.id),
|
|
209133
209052
|
with: {
|
|
209134
209053
|
accessories: {
|
|
@@ -209138,7 +209057,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209138
209057
|
}
|
|
209139
209058
|
}
|
|
209140
209059
|
});
|
|
209141
|
-
return
|
|
209060
|
+
return pc3 ?? null;
|
|
209142
209061
|
} catch (error2) {
|
|
209143
209062
|
log2.error("Error fetching player character", { error: error2 });
|
|
209144
209063
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -209154,7 +209073,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209154
209073
|
}
|
|
209155
209074
|
try {
|
|
209156
209075
|
const db = getDatabase();
|
|
209157
|
-
const
|
|
209076
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209158
209077
|
where: eq(playerCharacters.userId, userId),
|
|
209159
209078
|
with: {
|
|
209160
209079
|
accessories: {
|
|
@@ -209164,7 +209083,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209164
209083
|
}
|
|
209165
209084
|
}
|
|
209166
209085
|
});
|
|
209167
|
-
return
|
|
209086
|
+
return pc3 ?? null;
|
|
209168
209087
|
} catch (error2) {
|
|
209169
209088
|
log2.error("Error fetching player character by ID", { userId, error: error2 });
|
|
209170
209089
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -209999,11 +209918,11 @@ async function setupTimebackIntegration(ctx) {
|
|
|
209999
209918
|
totalXp: derivedTotalXp,
|
|
210000
209919
|
masterableUnits: derivedMasterableUnits
|
|
210001
209920
|
} = courseConfig;
|
|
210002
|
-
if (!
|
|
209921
|
+
if (!isTimebackSubject2(subject)) {
|
|
210003
209922
|
log2.error("[API] Invalid subject in TimeBack request", { subject });
|
|
210004
209923
|
throw ApiError.badRequest(`Invalid subject "${subject}" provided for course "${title}".`);
|
|
210005
209924
|
}
|
|
210006
|
-
if (!
|
|
209925
|
+
if (!isTimebackGrade2(grade)) {
|
|
210007
209926
|
log2.error("[API] Invalid grade in TimeBack request", { grade });
|
|
210008
209927
|
throw ApiError.badRequest(`Invalid grade "${grade}" provided for course "${title}".`);
|
|
210009
209928
|
}
|
|
@@ -210400,27 +210319,11 @@ async function getStudentEnrollments(ctx) {
|
|
|
210400
210319
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
210401
210320
|
}
|
|
210402
210321
|
log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
|
|
210403
|
-
const
|
|
210404
|
-
const classes = await client2.getEnrollments(timebackId, { limit: 100 });
|
|
210405
|
-
const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
|
|
210406
|
-
if (courseIds.length === 0) {
|
|
210407
|
-
return { enrollments: [] };
|
|
210408
|
-
}
|
|
210409
|
-
const db = getDatabase();
|
|
210410
|
-
const integrations = await db.query.gameTimebackIntegrations.findMany({
|
|
210411
|
-
where: inArray(gameTimebackIntegrations.courseId, courseIds)
|
|
210412
|
-
});
|
|
210413
|
-
const enrollments = integrations.map((integration) => ({
|
|
210414
|
-
gameId: integration.gameId,
|
|
210415
|
-
grade: integration.grade,
|
|
210416
|
-
subject: integration.subject,
|
|
210417
|
-
courseId: integration.courseId
|
|
210418
|
-
}));
|
|
210322
|
+
const enrollments = await fetchEnrollmentsForUser(timebackId);
|
|
210419
210323
|
log2.info("[API] Retrieved student enrollments", {
|
|
210420
210324
|
userId: user.id,
|
|
210421
210325
|
timebackId,
|
|
210422
|
-
|
|
210423
|
-
mappedEnrollments: enrollments.length
|
|
210326
|
+
enrollmentCount: enrollments.length
|
|
210424
210327
|
});
|
|
210425
210328
|
return { enrollments };
|
|
210426
210329
|
}
|
|
@@ -210614,11 +210517,11 @@ timebackRouter.post("/end-activity", async (c3) => {
|
|
|
210614
210517
|
return c3.json(createUnknownErrorResponse(error2), 500);
|
|
210615
210518
|
}
|
|
210616
210519
|
});
|
|
210617
|
-
timebackRouter.get("/enrollments/:
|
|
210618
|
-
const
|
|
210520
|
+
timebackRouter.get("/enrollments/:timebackId", async (c3) => {
|
|
210521
|
+
const timebackId = c3.req.param("timebackId");
|
|
210619
210522
|
const ctx = {
|
|
210620
210523
|
user: c3.get("user"),
|
|
210621
|
-
params: {
|
|
210524
|
+
params: { timebackId },
|
|
210622
210525
|
url: new URL(c3.req.url),
|
|
210623
210526
|
request: c3.req.raw
|
|
210624
210527
|
};
|
|
@@ -210928,8 +210831,8 @@ function registerRoutes(app) {
|
|
|
210928
210831
|
return c3.json({ error: "Not Found", message: `Route ${c3.req.path} not found` }, 404);
|
|
210929
210832
|
});
|
|
210930
210833
|
}
|
|
210931
|
-
var version3 =
|
|
210932
|
-
async function
|
|
210834
|
+
var version3 = package_default.version;
|
|
210835
|
+
async function startServer(port, project, options = {}) {
|
|
210933
210836
|
const processedOptions = processServerOptions(port, options);
|
|
210934
210837
|
const db = await setupServerDatabase(processedOptions, project);
|
|
210935
210838
|
const app = createApp(db, {
|
|
@@ -210951,6 +210854,44 @@ async function startServer2(port, project, options = {}) {
|
|
|
210951
210854
|
};
|
|
210952
210855
|
}
|
|
210953
210856
|
|
|
210857
|
+
// src/lib/logging/adapter.ts
|
|
210858
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
210859
|
+
function formatTimestamp2() {
|
|
210860
|
+
const now2 = new Date;
|
|
210861
|
+
const hours = now2.getHours();
|
|
210862
|
+
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
210863
|
+
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
210864
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
210865
|
+
const displayHours = hours % 12 || 12;
|
|
210866
|
+
return import_picocolors3.dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
210867
|
+
}
|
|
210868
|
+
function createLoggerAdapter(prefix2) {
|
|
210869
|
+
const formattedPrefix = import_picocolors3.dim(`(${prefix2})`);
|
|
210870
|
+
const label = import_picocolors3.cyan(import_picocolors3.bold("[playcademy]"));
|
|
210871
|
+
return {
|
|
210872
|
+
info: (msg) => console.log(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`),
|
|
210873
|
+
warn: (msg) => console.warn(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`),
|
|
210874
|
+
error: (msg) => console.error(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`)
|
|
210875
|
+
};
|
|
210876
|
+
}
|
|
210877
|
+
// src/lib/logging/utils.ts
|
|
210878
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
210879
|
+
function printBanner(viteConfig, servers, projectInfo, pluginVersion) {
|
|
210880
|
+
const INDENT = " ".repeat(2);
|
|
210881
|
+
viteConfig.logger.info("");
|
|
210882
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green(import_picocolors4.bold("PLAYCADEMY"))} ${import_picocolors4.green(`v${pluginVersion}`)}`);
|
|
210883
|
+
viteConfig.logger.info("");
|
|
210884
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Game:")} ${import_picocolors4.cyan(projectInfo.slug)}`);
|
|
210885
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Sandbox:")} ${import_picocolors4.cyan(`http://localhost:${import_picocolors4.bold(servers.sandbox.toString())}/api`)}`);
|
|
210886
|
+
if (servers.backend) {
|
|
210887
|
+
const backendUrl = servers.vite ? `http://localhost:${import_picocolors4.bold(servers.vite.toString())}/api ${import_picocolors4.dim(`(via ${servers.backend})`)}` : `http://localhost:${import_picocolors4.bold(servers.backend.toString())}/api`;
|
|
210888
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Backend:")} ${import_picocolors4.cyan(backendUrl)}`);
|
|
210889
|
+
}
|
|
210890
|
+
if (servers.realtime) {
|
|
210891
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Realtime:")} ${import_picocolors4.cyan(`ws://localhost:${import_picocolors4.bold(servers.realtime.toString())}`)}`);
|
|
210892
|
+
}
|
|
210893
|
+
viteConfig.logger.info("");
|
|
210894
|
+
}
|
|
210954
210895
|
// src/lib/sandbox/project-info.ts
|
|
210955
210896
|
import fs6 from "node:fs";
|
|
210956
210897
|
import path5 from "node:path";
|
|
@@ -210989,6 +210930,14 @@ async function extractProjectInfo(viteConfig) {
|
|
|
210989
210930
|
}
|
|
210990
210931
|
|
|
210991
210932
|
// src/lib/sandbox/timeback.ts
|
|
210933
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
210934
|
+
function getEffectiveTimebackId(timeback) {
|
|
210935
|
+
if (timeback?.timebackId)
|
|
210936
|
+
return timeback.timebackId;
|
|
210937
|
+
if (timeback?.courses && Object.keys(timeback.courses).length > 0)
|
|
210938
|
+
return "mock";
|
|
210939
|
+
return;
|
|
210940
|
+
}
|
|
210992
210941
|
function detectTimebackOptions() {
|
|
210993
210942
|
if (process.env.TIMEBACK_LOCAL === "true") {
|
|
210994
210943
|
return {
|
|
@@ -211013,16 +210962,60 @@ function detectTimebackOptions() {
|
|
|
211013
210962
|
}
|
|
211014
210963
|
return;
|
|
211015
210964
|
}
|
|
210965
|
+
function hasTimebackCredentials2() {
|
|
210966
|
+
const config2 = detectTimebackOptions();
|
|
210967
|
+
if (!config2)
|
|
210968
|
+
return false;
|
|
210969
|
+
if (config2.mode === "local") {
|
|
210970
|
+
return !!(config2.onerosterApiUrl && config2.caliperApiUrl);
|
|
210971
|
+
}
|
|
210972
|
+
return !!(config2.onerosterApiUrl && config2.clientId && config2.clientSecret && config2.authUrl);
|
|
210973
|
+
}
|
|
210974
|
+
function validateTimebackConfig(viteConfig, timeback) {
|
|
210975
|
+
if (!timeback?.courses)
|
|
210976
|
+
return;
|
|
210977
|
+
const realCourses = Object.entries(timeback.courses).filter(([, value]) => value !== "mock");
|
|
210978
|
+
if (realCourses.length > 0 && !hasTimebackCredentials2()) {
|
|
210979
|
+
const courseList = realCourses.map(([key]) => key).join(", ");
|
|
210980
|
+
viteConfig.logger.warn("");
|
|
210981
|
+
viteConfig.logger.warn(import_picocolors5.default.yellow(`⚠️ TimeBack: Real course IDs for ${import_picocolors5.default.bold(courseList)} but credentials missing.`));
|
|
210982
|
+
viteConfig.logger.warn(import_picocolors5.default.dim(` Required: TIMEBACK_API_CLIENT_ID, TIMEBACK_API_CLIENT_SECRET, TIMEBACK_API_AUTH_URL, TIMEBACK_ONEROSTER_API_URL`));
|
|
210983
|
+
viteConfig.logger.warn(import_picocolors5.default.dim(` Or use 'mock' for local testing.`));
|
|
210984
|
+
viteConfig.logger.warn("");
|
|
210985
|
+
}
|
|
210986
|
+
}
|
|
210987
|
+
function generateTimebackJson(timeback) {
|
|
210988
|
+
if (!timeback?.courses || Object.keys(timeback.courses).length === 0) {
|
|
210989
|
+
return;
|
|
210990
|
+
}
|
|
210991
|
+
const enrollments = [];
|
|
210992
|
+
for (const [key, value] of Object.entries(timeback.courses)) {
|
|
210993
|
+
const parts2 = key.split(":");
|
|
210994
|
+
const subject = parts2[0];
|
|
210995
|
+
const gradeStr = parts2[1];
|
|
210996
|
+
const grade = gradeStr ? parseInt(gradeStr, 10) : NaN;
|
|
210997
|
+
if (!subject || isNaN(grade))
|
|
210998
|
+
continue;
|
|
210999
|
+
const courseId = value === "mock" ? `mock-${subject.toLowerCase()}-g${grade}` : value;
|
|
211000
|
+
enrollments.push({ subject, grade, courseId });
|
|
211001
|
+
}
|
|
211002
|
+
if (enrollments.length === 0) {
|
|
211003
|
+
return;
|
|
211004
|
+
}
|
|
211005
|
+
const roleOverride = getTimebackRoleOverride();
|
|
211006
|
+
const role = roleOverride ?? timeback.role ?? "student";
|
|
211007
|
+
return JSON.stringify({ role, enrollments });
|
|
211008
|
+
}
|
|
211016
211009
|
|
|
211017
211010
|
// src/lib/sandbox/server.ts
|
|
211018
211011
|
function printSandboxInfo(viteConfig, apiPort, realtimePort, projectInfo, realtimeEnabled) {
|
|
211019
211012
|
viteConfig.logger.info("");
|
|
211020
|
-
viteConfig.logger.info(` ${
|
|
211013
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green(import_picocolors6.default.bold("PLAYCADEMY"))} ${import_picocolors6.default.green(`v${version3}`)}`);
|
|
211021
211014
|
viteConfig.logger.info("");
|
|
211022
|
-
viteConfig.logger.info(` ${
|
|
211023
|
-
viteConfig.logger.info(` ${
|
|
211015
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green("➜")} ${import_picocolors6.default.bold("Game:")} ${import_picocolors6.default.cyan(projectInfo.slug)}`);
|
|
211016
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green("➜")} ${import_picocolors6.default.bold("Sandbox:")} ${import_picocolors6.default.cyan(`http://localhost:${import_picocolors6.default.bold(apiPort.toString())}/api`)}`);
|
|
211024
211017
|
if (realtimeEnabled) {
|
|
211025
|
-
viteConfig.logger.info(` ${
|
|
211018
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green("➜")} ${import_picocolors6.default.bold("Realtime:")} ${import_picocolors6.default.cyan(`ws://localhost:${import_picocolors6.default.bold(realtimePort.toString())}`)}`);
|
|
211026
211019
|
}
|
|
211027
211020
|
viteConfig.logger.info("");
|
|
211028
211021
|
}
|
|
@@ -211037,7 +211030,8 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211037
211030
|
databasePath,
|
|
211038
211031
|
realtimeEnabled = false,
|
|
211039
211032
|
realtimePort,
|
|
211040
|
-
logLevel = "info"
|
|
211033
|
+
logLevel = "info",
|
|
211034
|
+
timebackId
|
|
211041
211035
|
} = options;
|
|
211042
211036
|
if (!autoStart || viteConfig.command !== "serve") {
|
|
211043
211037
|
const baseUrl = customUrl ?? `http://localhost:${DEFAULT_PORTS2.SANDBOX}`;
|
|
@@ -211064,10 +211058,16 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211064
211058
|
const sandboxPort = await findAvailablePort(DEFAULT_PORTS2.SANDBOX);
|
|
211065
211059
|
const baseUrl = `http://localhost:${sandboxPort}`;
|
|
211066
211060
|
const projectInfo = await extractProjectInfo(viteConfig);
|
|
211067
|
-
|
|
211061
|
+
let timebackOptions = detectTimebackOptions();
|
|
211062
|
+
if (timebackId) {
|
|
211063
|
+
timebackOptions = {
|
|
211064
|
+
...timebackOptions,
|
|
211065
|
+
studentId: timebackId
|
|
211066
|
+
};
|
|
211067
|
+
}
|
|
211068
211068
|
const finalRealtimePort = realtimePort ?? await findAvailablePort(sandboxPort + 1);
|
|
211069
211069
|
const realtimeUrl = `ws://localhost:${finalRealtimePort}`;
|
|
211070
|
-
const server = await
|
|
211070
|
+
const server = await startServer(sandboxPort, projectInfo, {
|
|
211071
211071
|
verbose,
|
|
211072
211072
|
quiet,
|
|
211073
211073
|
seed,
|
|
@@ -211105,7 +211105,7 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211105
211105
|
}
|
|
211106
211106
|
};
|
|
211107
211107
|
} catch (error2) {
|
|
211108
|
-
viteConfig.logger.error(
|
|
211108
|
+
viteConfig.logger.error(import_picocolors6.default.red(`[Playcademy] Failed to start sandbox: ${error2}`));
|
|
211109
211109
|
return {
|
|
211110
211110
|
baseUrl: `http://localhost:${DEFAULT_PORTS2.SANDBOX}`,
|
|
211111
211111
|
realtimeUrl: "ws://localhost:4322",
|
|
@@ -211330,16 +211330,13 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211330
211330
|
<head>
|
|
211331
211331
|
<meta charset="UTF-8" />
|
|
211332
211332
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
211333
|
-
<title>Playcademy
|
|
211333
|
+
<title>Playcademy Dev</title>
|
|
211334
211334
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
211335
211335
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
211336
211336
|
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet" />
|
|
211337
|
+
<script type="module" src="/@vite/client"></script>
|
|
211337
211338
|
<script type="importmap">
|
|
211338
|
-
{
|
|
211339
|
-
"imports": {
|
|
211340
|
-
"@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest"
|
|
211341
|
-
}
|
|
211342
|
-
}
|
|
211339
|
+
{ "imports": { "@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest" } }
|
|
211343
211340
|
</script>
|
|
211344
211341
|
<style>
|
|
211345
211342
|
* {
|
|
@@ -211347,14 +211344,11 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211347
211344
|
padding: 0;
|
|
211348
211345
|
box-sizing: border-box;
|
|
211349
211346
|
}
|
|
211350
|
-
|
|
211351
211347
|
body {
|
|
211352
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
211353
211348
|
height: 100vh;
|
|
211354
211349
|
overflow: hidden;
|
|
211355
211350
|
}
|
|
211356
|
-
|
|
211357
|
-
.dev-badge {
|
|
211351
|
+
#badge {
|
|
211358
211352
|
position: fixed;
|
|
211359
211353
|
top: 0.5rem;
|
|
211360
211354
|
left: 0.5rem;
|
|
@@ -211369,202 +211363,141 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211369
211363
|
letter-spacing: 0.05em;
|
|
211370
211364
|
transition: background 0.2s ease;
|
|
211371
211365
|
}
|
|
211372
|
-
|
|
211373
|
-
.dev-badge.offline {
|
|
211366
|
+
#badge.offline {
|
|
211374
211367
|
background: rgba(211, 47, 47, 0.9);
|
|
211375
211368
|
}
|
|
211376
|
-
|
|
211377
|
-
.dev-badge-status {
|
|
211378
|
-
font-size: 0.7rem;
|
|
211379
|
-
}
|
|
211380
|
-
|
|
211381
|
-
.dev-badge-status:not(:empty) {
|
|
211382
|
-
margin-left: 0.5rem;
|
|
211383
|
-
}
|
|
211384
|
-
|
|
211385
|
-
.game-container {
|
|
211386
|
-
width: 100vw;
|
|
211387
|
-
height: 100vh;
|
|
211388
|
-
position: relative;
|
|
211389
|
-
overflow: hidden;
|
|
211390
|
-
}
|
|
211391
|
-
|
|
211392
|
-
.game-frame {
|
|
211369
|
+
#frame {
|
|
211393
211370
|
position: absolute;
|
|
211394
211371
|
inset: 0;
|
|
211395
211372
|
border: none;
|
|
211396
211373
|
width: 100%;
|
|
211397
211374
|
height: 100%;
|
|
211398
211375
|
}
|
|
211399
|
-
|
|
211400
|
-
.error {
|
|
211401
|
-
position: absolute;
|
|
211402
|
-
inset: 0;
|
|
211403
|
-
display: flex;
|
|
211404
|
-
flex-direction: column;
|
|
211405
|
-
align-items: center;
|
|
211406
|
-
justify-content: center;
|
|
211407
|
-
color: #d32f2f;
|
|
211408
|
-
background-color: #ffebee;
|
|
211409
|
-
}
|
|
211410
|
-
|
|
211411
211376
|
.hidden {
|
|
211412
211377
|
display: none;
|
|
211413
211378
|
}
|
|
211414
211379
|
</style>
|
|
211415
211380
|
</head>
|
|
211416
211381
|
<body>
|
|
211417
|
-
<div
|
|
211418
|
-
|
|
211419
|
-
</div>
|
|
211420
|
-
|
|
211421
|
-
<div class="game-container">
|
|
211422
|
-
<div class="error hidden" id="error">
|
|
211423
|
-
<div>❌ Failed to load game</div>
|
|
211424
|
-
<div
|
|
211425
|
-
style="margin-top: 0.5rem; font-size: 0.9rem; opacity: 0.8"
|
|
211426
|
-
id="errorMessage"
|
|
211427
|
-
></div>
|
|
211428
|
-
</div>
|
|
211429
|
-
|
|
211430
|
-
<iframe class="game-frame hidden" id="gameFrame" src="/"></iframe>
|
|
211431
|
-
</div>
|
|
211382
|
+
<div id="badge">PLAYCADEMY</div>
|
|
211383
|
+
<iframe id="frame" class="hidden"></iframe>
|
|
211432
211384
|
|
|
211433
211385
|
<script type="module">
|
|
211434
211386
|
import { MessageEvents, messaging } from '@playcademy/sdk'
|
|
211435
211387
|
|
|
211436
|
-
|
|
211437
|
-
const
|
|
211438
|
-
|
|
211439
|
-
|
|
211440
|
-
|
|
211441
|
-
|
|
211388
|
+
// Config (injected by vite plugin)
|
|
211389
|
+
const CONFIG = {
|
|
211390
|
+
sandboxUrl: '{{SANDBOX_URL}}',
|
|
211391
|
+
gameId: '{{GAME_ID}}',
|
|
211392
|
+
gameUrl: '{{GAME_URL}}' || undefined,
|
|
211393
|
+
realtimeUrl: '{{REALTIME_URL}}',
|
|
211394
|
+
timebackJson: '{{TIMEBACK_DATA}}',
|
|
211395
|
+
}
|
|
211442
211396
|
|
|
211443
|
-
|
|
211444
|
-
|
|
211445
|
-
|
|
211397
|
+
// Parse timeback data (role + enrollments)
|
|
211398
|
+
const timeback = (() => {
|
|
211399
|
+
try {
|
|
211400
|
+
return JSON.parse(CONFIG.timebackJson)
|
|
211401
|
+
} catch {
|
|
211402
|
+
return null
|
|
211446
211403
|
}
|
|
211447
|
-
}
|
|
211404
|
+
})()
|
|
211448
211405
|
|
|
211449
|
-
|
|
211406
|
+
// Elements
|
|
211407
|
+
const badge = document.getElementById('badge')
|
|
211408
|
+
const frame = document.getElementById('frame')
|
|
211450
211409
|
|
|
211451
|
-
|
|
211410
|
+
// Debug logging
|
|
211411
|
+
const log = (...args) => window.PLAYCADEMY_DEBUG && console.log('[DevShell]', ...args)
|
|
211412
|
+
|
|
211413
|
+
// Check sandbox connection
|
|
211414
|
+
async function checkSandbox() {
|
|
211452
211415
|
try {
|
|
211453
|
-
const
|
|
211454
|
-
headers: {
|
|
211455
|
-
Authorization: 'Bearer sandbox-demo-token',
|
|
211456
|
-
},
|
|
211416
|
+
const res = await fetch(\`\${CONFIG.sandboxUrl}/api/users/me\`, {
|
|
211417
|
+
headers: { Authorization: 'Bearer sandbox-demo-token' },
|
|
211457
211418
|
})
|
|
211458
|
-
|
|
211459
|
-
|
|
211460
|
-
|
|
211461
|
-
|
|
211462
|
-
|
|
211463
|
-
|
|
211464
|
-
|
|
211465
|
-
|
|
211466
|
-
|
|
211467
|
-
logIfDebug('[PlaycademyDevShell] Sandbox connection failed:', err)
|
|
211468
|
-
badgeStatus.textContent = ' ⚠️'
|
|
211469
|
-
devBadge.classList.add('offline')
|
|
211419
|
+
if (!res.ok) throw new Error('Sandbox unavailable')
|
|
211420
|
+
badge.classList.remove('offline')
|
|
211421
|
+
log('Sandbox connected')
|
|
211422
|
+
return true
|
|
211423
|
+
} catch (e) {
|
|
211424
|
+
badge.classList.add('offline')
|
|
211425
|
+
badge.textContent = 'PLAYCADEMY ⚠️'
|
|
211426
|
+
log('Sandbox failed:', e)
|
|
211427
|
+
return false
|
|
211470
211428
|
}
|
|
211471
211429
|
}
|
|
211472
211430
|
|
|
211473
|
-
//
|
|
211474
|
-
function
|
|
211475
|
-
|
|
211476
|
-
|
|
211477
|
-
|
|
211478
|
-
|
|
211479
|
-
|
|
211480
|
-
gameUrl: '{{GAME_URL}}' || undefined,
|
|
211431
|
+
// Init handshake with game iframe
|
|
211432
|
+
function initHandshake(timebackData) {
|
|
211433
|
+
const payload = {
|
|
211434
|
+
baseUrl: CONFIG.sandboxUrl,
|
|
211435
|
+
gameUrl: CONFIG.gameUrl,
|
|
211436
|
+
gameId: CONFIG.gameId,
|
|
211437
|
+
realtimeUrl: CONFIG.realtimeUrl,
|
|
211481
211438
|
token: 'sandbox-demo-token',
|
|
211482
|
-
|
|
211483
|
-
realtimeUrl: '{{REALTIME_URL}}',
|
|
211439
|
+
timeback: timebackData,
|
|
211484
211440
|
}
|
|
211485
211441
|
|
|
211486
|
-
|
|
211487
|
-
if (!gameFrame.contentWindow) return
|
|
211488
|
-
|
|
211489
|
-
messaging.send(MessageEvents.INIT, initPayload, {
|
|
211490
|
-
target: gameFrame.contentWindow,
|
|
211491
|
-
origin: '*',
|
|
211492
|
-
})
|
|
211493
|
-
}
|
|
211442
|
+
let interval, timeout
|
|
211494
211443
|
|
|
211495
|
-
const
|
|
211496
|
-
if (
|
|
211497
|
-
|
|
211498
|
-
|
|
211499
|
-
|
|
211500
|
-
|
|
211501
|
-
clearTimeout(handshakeTimeout)
|
|
211502
|
-
handshakeTimeout = null
|
|
211444
|
+
const send = () => {
|
|
211445
|
+
if (frame.contentWindow) {
|
|
211446
|
+
messaging.send(MessageEvents.INIT, payload, {
|
|
211447
|
+
target: frame.contentWindow,
|
|
211448
|
+
origin: '*',
|
|
211449
|
+
})
|
|
211503
211450
|
}
|
|
211504
211451
|
}
|
|
211505
211452
|
|
|
211506
|
-
|
|
211507
|
-
|
|
211508
|
-
|
|
211509
|
-
logIfDebug('[PlaycademyDevShell] Game iframe loaded, beginning init handshake')
|
|
211510
|
-
|
|
211511
|
-
// Begin handshake: send immediately, then every 300ms
|
|
211512
|
-
sendInit()
|
|
211513
|
-
handshakeInterval = setInterval(sendInit, 300)
|
|
211514
|
-
|
|
211515
|
-
// Stop handshake after 10 seconds
|
|
211516
|
-
handshakeTimeout = setTimeout(() => {
|
|
211517
|
-
stopHandshake()
|
|
211518
|
-
logIfDebug('[PlaycademyDevShell] Init handshake timeout reached')
|
|
211519
|
-
}, 10000)
|
|
211453
|
+
const stop = () => {
|
|
211454
|
+
clearInterval(interval)
|
|
211455
|
+
clearTimeout(timeout)
|
|
211520
211456
|
}
|
|
211521
211457
|
|
|
211522
|
-
|
|
211523
|
-
|
|
211524
|
-
|
|
211525
|
-
|
|
211526
|
-
|
|
211527
|
-
|
|
211458
|
+
frame.onload = () => {
|
|
211459
|
+
frame.classList.remove('hidden')
|
|
211460
|
+
log('Frame loaded, starting handshake')
|
|
211461
|
+
send()
|
|
211462
|
+
interval = setInterval(send, 300)
|
|
211463
|
+
timeout = setTimeout(() => {
|
|
211464
|
+
stop()
|
|
211465
|
+
log('Handshake timeout')
|
|
211466
|
+
}, 10000)
|
|
211528
211467
|
}
|
|
211529
211468
|
|
|
211530
|
-
// Listen for READY message to stop handshake
|
|
211531
211469
|
messaging.listen(MessageEvents.READY, () => {
|
|
211532
|
-
|
|
211533
|
-
|
|
211470
|
+
stop()
|
|
211471
|
+
log('Game ready')
|
|
211534
211472
|
})
|
|
211535
211473
|
|
|
211536
|
-
|
|
211474
|
+
frame.src = '/'
|
|
211537
211475
|
}
|
|
211538
211476
|
|
|
211539
|
-
|
|
211540
|
-
|
|
211541
|
-
|
|
211542
|
-
|
|
211543
|
-
|
|
211544
|
-
|
|
211545
|
-
|
|
211546
|
-
payload,
|
|
211547
|
-
)
|
|
211548
|
-
// Bridge the message to the local context using CustomEvent
|
|
211549
|
-
messaging.send(type, payload)
|
|
211550
|
-
}
|
|
211477
|
+
// Bridge messages from game to shell context
|
|
211478
|
+
window.addEventListener('message', e => {
|
|
211479
|
+
if (e.source !== frame.contentWindow) return
|
|
211480
|
+
const { type, ...payload } = e.data || {}
|
|
211481
|
+
if (type?.startsWith('PLAYCADEMY_')) {
|
|
211482
|
+
log('Message:', type, payload)
|
|
211483
|
+
messaging.send(type, payload)
|
|
211551
211484
|
}
|
|
211552
211485
|
})
|
|
211553
211486
|
|
|
211554
|
-
//
|
|
211555
|
-
|
|
211556
|
-
loadGame()
|
|
211487
|
+
// Start
|
|
211488
|
+
checkSandbox().then(() => initHandshake(timeback))
|
|
211557
211489
|
</script>
|
|
211558
211490
|
</body>
|
|
211559
211491
|
</html>
|
|
211560
211492
|
`;
|
|
211561
211493
|
|
|
211562
211494
|
// src/server/middleware.ts
|
|
211563
|
-
function generateLoaderHTML(sandboxUrl, gameId, realtimeUrl,
|
|
211564
|
-
const shell = showBadge ? shell_with_corner_badge_default : shell_no_badge_default;
|
|
211565
|
-
|
|
211495
|
+
function generateLoaderHTML(sandboxUrl, gameId, realtimeUrl, options, gameUrl) {
|
|
211496
|
+
const shell = options.showBadge ? shell_with_corner_badge_default : shell_no_badge_default;
|
|
211497
|
+
const timebackJson = generateTimebackJson(options.timeback) ?? "null";
|
|
211498
|
+
return shell.replace(/{{SANDBOX_URL}}/g, sandboxUrl).replace(/{{GAME_ID}}/g, gameId).replace(/{{REALTIME_URL}}/g, realtimeUrl).replace(/{{GAME_URL}}/g, gameUrl || "").replace(/{{TIMEBACK_DATA}}/g, timebackJson);
|
|
211566
211499
|
}
|
|
211567
|
-
function devServerMiddleware(server, sandbox, gameUrl,
|
|
211500
|
+
function devServerMiddleware(server, sandbox, gameUrl, options) {
|
|
211568
211501
|
server.middlewares.use("/", (req, res, next) => {
|
|
211569
211502
|
if (getCurrentMode() !== "platform") {
|
|
211570
211503
|
next();
|
|
@@ -211576,8 +211509,7 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211576
211509
|
next();
|
|
211577
211510
|
} else {
|
|
211578
211511
|
res.setHeader("Content-Type", "text/html");
|
|
211579
|
-
|
|
211580
|
-
res.end(generateLoaderHTML(sandbox.baseUrl, gameId ?? "", sandbox.realtimeUrl, showBadge, gameUrl));
|
|
211512
|
+
res.end(generateLoaderHTML(sandbox.baseUrl, sandbox.project?.slug ?? "", sandbox.realtimeUrl, options, gameUrl));
|
|
211581
211513
|
}
|
|
211582
211514
|
return;
|
|
211583
211515
|
}
|
|
@@ -211585,6 +211517,190 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211585
211517
|
});
|
|
211586
211518
|
}
|
|
211587
211519
|
|
|
211520
|
+
// src/server/hotkeys/recreate-database.ts
|
|
211521
|
+
var { bold: bold4, cyan: cyan3, dim: dim4, green: green2, red, yellow: yellow2 } = import_picocolors7.default;
|
|
211522
|
+
function formatTimestamp3() {
|
|
211523
|
+
const now2 = new Date;
|
|
211524
|
+
const hours = now2.getHours();
|
|
211525
|
+
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
211526
|
+
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211527
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211528
|
+
const displayHours = hours % 12 || 12;
|
|
211529
|
+
return dim4(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211530
|
+
}
|
|
211531
|
+
async function recreateSandboxDatabase(options) {
|
|
211532
|
+
const currentMode = getCurrentMode();
|
|
211533
|
+
const viteServer = getViteServerRef();
|
|
211534
|
+
if (!viteServer) {
|
|
211535
|
+
options.viteConfig.logger.error(`${formatTimestamp3()} ${red(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${red("Cannot recreate sandbox database: no Vite server reference")}`);
|
|
211536
|
+
return;
|
|
211537
|
+
}
|
|
211538
|
+
if (currentMode !== "platform") {
|
|
211539
|
+
options.viteConfig.logger.warn(`${formatTimestamp3()} ${yellow2(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${yellow2("can only recreate sandbox database in platform mode (m + enter)")}`);
|
|
211540
|
+
return;
|
|
211541
|
+
}
|
|
211542
|
+
options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan3(bold4("[playcademy]"))} ${dim4("(sandbox)")} recreating database...`);
|
|
211543
|
+
if (serverState.sandbox) {
|
|
211544
|
+
serverState.sandbox.cleanup();
|
|
211545
|
+
serverState.sandbox = null;
|
|
211546
|
+
}
|
|
211547
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
211548
|
+
const sandbox = await startSandbox(options.viteConfig, options.platformModeOptions.startSandbox, {
|
|
211549
|
+
verbose: options.platformModeOptions.verbose,
|
|
211550
|
+
logLevel: options.platformModeOptions.logLevel,
|
|
211551
|
+
customUrl: options.platformModeOptions.sandboxUrl,
|
|
211552
|
+
quiet: true,
|
|
211553
|
+
recreateDb: true,
|
|
211554
|
+
seed: options.platformModeOptions.seed,
|
|
211555
|
+
memoryOnly: options.platformModeOptions.memoryOnly,
|
|
211556
|
+
databasePath: options.platformModeOptions.databasePath,
|
|
211557
|
+
realtimeEnabled: options.platformModeOptions.realtimeEnabled,
|
|
211558
|
+
realtimePort: options.platformModeOptions.realtimePort,
|
|
211559
|
+
timebackId: getEffectiveTimebackId(options.platformModeOptions.timeback)
|
|
211560
|
+
});
|
|
211561
|
+
serverState.sandbox = sandbox;
|
|
211562
|
+
if (sandbox.project && serverState.backend) {
|
|
211563
|
+
const gameUrl = `http://localhost:${serverState.backend.port}`;
|
|
211564
|
+
devServerMiddleware(viteServer, sandbox, gameUrl, {
|
|
211565
|
+
showBadge: options.platformModeOptions.showBadge,
|
|
211566
|
+
timeback: options.platformModeOptions.timeback
|
|
211567
|
+
});
|
|
211568
|
+
}
|
|
211569
|
+
options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan3(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${green2("database recreated")}`);
|
|
211570
|
+
}
|
|
211571
|
+
var recreateDatabaseHotkey = (options) => ({
|
|
211572
|
+
key: "d",
|
|
211573
|
+
description: "recreate sandbox database",
|
|
211574
|
+
action: () => recreateSandboxDatabase(options)
|
|
211575
|
+
});
|
|
211576
|
+
|
|
211577
|
+
// src/server/hotkeys/toggle-mode.ts
|
|
211578
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
211579
|
+
// package.json
|
|
211580
|
+
var package_default2 = {
|
|
211581
|
+
name: "@playcademy/vite-plugin",
|
|
211582
|
+
version: "0.1.37",
|
|
211583
|
+
type: "module",
|
|
211584
|
+
exports: {
|
|
211585
|
+
".": {
|
|
211586
|
+
import: "./dist/index.js",
|
|
211587
|
+
types: "./dist/index.d.ts"
|
|
211588
|
+
}
|
|
211589
|
+
},
|
|
211590
|
+
main: "dist/index.js",
|
|
211591
|
+
module: "dist/index.js",
|
|
211592
|
+
files: [
|
|
211593
|
+
"dist"
|
|
211594
|
+
],
|
|
211595
|
+
scripts: {
|
|
211596
|
+
build: "rm -rf dist && bun build.ts",
|
|
211597
|
+
docs: "typedoc --skipErrorChecking",
|
|
211598
|
+
pub: "bun publish.ts"
|
|
211599
|
+
},
|
|
211600
|
+
dependencies: {
|
|
211601
|
+
archiver: "^7.0.1",
|
|
211602
|
+
picocolors: "^1.1.1",
|
|
211603
|
+
playcademy: "workspace:*"
|
|
211604
|
+
},
|
|
211605
|
+
devDependencies: {
|
|
211606
|
+
"@inquirer/prompts": "^7.8.6",
|
|
211607
|
+
"@playcademy/sandbox": "workspace:*",
|
|
211608
|
+
"@types/archiver": "^6.0.3",
|
|
211609
|
+
"@types/bun": "latest"
|
|
211610
|
+
},
|
|
211611
|
+
peerDependencies: {
|
|
211612
|
+
typescript: "^5",
|
|
211613
|
+
vite: "^5 || ^6"
|
|
211614
|
+
}
|
|
211615
|
+
};
|
|
211616
|
+
|
|
211617
|
+
// src/lib/backend/server.ts
|
|
211618
|
+
import {
|
|
211619
|
+
loadPlaycademyConfigAndSetWorkspace as loadConfigAndSetWorkspace,
|
|
211620
|
+
startPlaycademyDevServer,
|
|
211621
|
+
startPlaycademyHotReload
|
|
211622
|
+
} from "playcademy/utils";
|
|
211623
|
+
|
|
211624
|
+
// src/lib/backend/hot-reload.ts
|
|
211625
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
211626
|
+
function formatChangedPath(changedPath) {
|
|
211627
|
+
if (!changedPath)
|
|
211628
|
+
return;
|
|
211629
|
+
if (changedPath.includes("/api/")) {
|
|
211630
|
+
return changedPath.substring(changedPath.indexOf("/api/"));
|
|
211631
|
+
}
|
|
211632
|
+
return changedPath;
|
|
211633
|
+
}
|
|
211634
|
+
function createHotReloadCallbacks(viteConfig) {
|
|
211635
|
+
return {
|
|
211636
|
+
onSuccess: (changedPath) => {
|
|
211637
|
+
const relativePath = formatChangedPath(changedPath);
|
|
211638
|
+
if (relativePath) {
|
|
211639
|
+
viteConfig.logger.info(`${import_picocolors8.dim("(backend)")} ${import_picocolors8.green("hmr update")} ${import_picocolors8.dim(relativePath)}`, { timestamp: true });
|
|
211640
|
+
} else {
|
|
211641
|
+
viteConfig.logger.info("backend reloaded", { timestamp: true });
|
|
211642
|
+
}
|
|
211643
|
+
},
|
|
211644
|
+
onError: (error2) => {
|
|
211645
|
+
viteConfig.logger.error(`backend reload failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211646
|
+
}
|
|
211647
|
+
};
|
|
211648
|
+
}
|
|
211649
|
+
|
|
211650
|
+
// src/lib/backend/server.ts
|
|
211651
|
+
async function tryLoadConfig(viteConfig, configPath) {
|
|
211652
|
+
try {
|
|
211653
|
+
return await loadConfigAndSetWorkspace(configPath);
|
|
211654
|
+
} catch (error2) {
|
|
211655
|
+
if (error2 instanceof Error && !error2.message.includes("Could not find")) {
|
|
211656
|
+
viteConfig.logger.warn(`Could not load playcademy.config.js: ${error2.message}`);
|
|
211657
|
+
}
|
|
211658
|
+
return null;
|
|
211659
|
+
}
|
|
211660
|
+
}
|
|
211661
|
+
function needsCliDevServer(config2) {
|
|
211662
|
+
return !!config2.integrations;
|
|
211663
|
+
}
|
|
211664
|
+
async function startServer2(options) {
|
|
211665
|
+
const { port, config: config2, platformUrl } = options;
|
|
211666
|
+
return startPlaycademyDevServer({
|
|
211667
|
+
port,
|
|
211668
|
+
config: config2,
|
|
211669
|
+
quiet: true,
|
|
211670
|
+
platformUrl,
|
|
211671
|
+
customLogger: createLoggerAdapter("backend")
|
|
211672
|
+
});
|
|
211673
|
+
}
|
|
211674
|
+
function setupHotReload(serverRef, options) {
|
|
211675
|
+
const watcher = startPlaycademyHotReload(async () => {
|
|
211676
|
+
await serverRef.current.server.dispose();
|
|
211677
|
+
serverRef.current = await startServer2(options);
|
|
211678
|
+
}, createHotReloadCallbacks(options.viteConfig));
|
|
211679
|
+
return () => watcher.close();
|
|
211680
|
+
}
|
|
211681
|
+
async function setupCliDevServer(options) {
|
|
211682
|
+
const { preferredPort, viteConfig, platformUrl, configPath } = options;
|
|
211683
|
+
const config2 = await tryLoadConfig(viteConfig, configPath);
|
|
211684
|
+
if (!config2)
|
|
211685
|
+
return null;
|
|
211686
|
+
if (!needsCliDevServer(config2))
|
|
211687
|
+
return null;
|
|
211688
|
+
try {
|
|
211689
|
+
const port = await findAvailablePort(preferredPort);
|
|
211690
|
+
const serverOptions = { port, config: config2, platformUrl, viteConfig };
|
|
211691
|
+
const serverRef = { current: await startServer2(serverOptions) };
|
|
211692
|
+
const stopHotReload = setupHotReload(serverRef, serverOptions);
|
|
211693
|
+
return {
|
|
211694
|
+
server: serverRef.current.server,
|
|
211695
|
+
port: serverRef.current.port,
|
|
211696
|
+
stopHotReload,
|
|
211697
|
+
cleanup: () => cleanupServerInfo("backend", viteConfig.root, process.pid)
|
|
211698
|
+
};
|
|
211699
|
+
} catch (error2) {
|
|
211700
|
+
viteConfig.logger.error(`Failed to start game backend: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211701
|
+
return null;
|
|
211702
|
+
}
|
|
211703
|
+
}
|
|
211588
211704
|
// src/server/platform-mode.ts
|
|
211589
211705
|
async function configurePlatformMode(server, viteConfig, options) {
|
|
211590
211706
|
const sandbox = await startSandbox(viteConfig, options.startSandbox, {
|
|
@@ -211597,7 +211713,8 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211597
211713
|
memoryOnly: options.memoryOnly,
|
|
211598
211714
|
databasePath: options.databasePath,
|
|
211599
211715
|
realtimeEnabled: options.realtimeEnabled,
|
|
211600
|
-
realtimePort: options.realtimePort
|
|
211716
|
+
realtimePort: options.realtimePort,
|
|
211717
|
+
timebackId: getEffectiveTimebackId(options.timeback)
|
|
211601
211718
|
});
|
|
211602
211719
|
serverState.sandbox = sandbox;
|
|
211603
211720
|
const backend = await setupCliDevServer({
|
|
@@ -211608,8 +211725,12 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211608
211725
|
});
|
|
211609
211726
|
serverState.backend = backend;
|
|
211610
211727
|
if (sandbox.project) {
|
|
211728
|
+
validateTimebackConfig(viteConfig, options.timeback);
|
|
211611
211729
|
const gameUrl = backend ? `http://localhost:${backend.port}` : undefined;
|
|
211612
|
-
devServerMiddleware(server, sandbox, gameUrl,
|
|
211730
|
+
devServerMiddleware(server, sandbox, gameUrl, {
|
|
211731
|
+
showBadge: options.showBadge,
|
|
211732
|
+
timeback: options.timeback
|
|
211733
|
+
});
|
|
211613
211734
|
}
|
|
211614
211735
|
server.httpServer?.once("listening", () => {
|
|
211615
211736
|
setTimeout(async () => {
|
|
@@ -211620,13 +211741,13 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211620
211741
|
backend: backend?.port,
|
|
211621
211742
|
realtime: sandbox.realtimePort,
|
|
211622
211743
|
vite: vitePort
|
|
211623
|
-
}, projectInfo,
|
|
211744
|
+
}, projectInfo, package_default2.version);
|
|
211624
211745
|
}, 100);
|
|
211625
211746
|
});
|
|
211626
211747
|
}
|
|
211627
211748
|
|
|
211628
211749
|
// src/server/standalone-mode.ts
|
|
211629
|
-
var
|
|
211750
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
211630
211751
|
async function configureStandaloneMode(server, viteConfig, options) {
|
|
211631
211752
|
const backend = await setupCliDevServer({
|
|
211632
211753
|
preferredPort: options.preferredPort,
|
|
@@ -211642,32 +211763,32 @@ async function configureStandaloneMode(server, viteConfig, options) {
|
|
|
211642
211763
|
server.httpServer?.once("listening", () => {
|
|
211643
211764
|
setTimeout(() => {
|
|
211644
211765
|
viteConfig.logger.info("");
|
|
211645
|
-
viteConfig.logger.info(` ${
|
|
211766
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green(import_picocolors9.default.bold("PLAYCADEMY"))} ${import_picocolors9.default.green(`v${package_default2.version}`)}`);
|
|
211646
211767
|
viteConfig.logger.info("");
|
|
211647
|
-
viteConfig.logger.info(` ${
|
|
211648
|
-
viteConfig.logger.info(` ${
|
|
211768
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green("➜")} ${import_picocolors9.default.bold("Backend:")} ${import_picocolors9.default.cyan(`http://localhost:${backend.port}`)}`);
|
|
211769
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green("➜")} ${import_picocolors9.default.bold("Sandbox:")} ${import_picocolors9.default.cyan("Disabled")}`);
|
|
211649
211770
|
viteConfig.logger.info("");
|
|
211650
211771
|
}, 100);
|
|
211651
211772
|
});
|
|
211652
211773
|
}
|
|
211653
211774
|
|
|
211654
|
-
// src/server/mode
|
|
211655
|
-
var { bold:
|
|
211656
|
-
function
|
|
211775
|
+
// src/server/hotkeys/toggle-mode.ts
|
|
211776
|
+
var { bold: bold5, cyan: cyan4, dim: dim6, green: green4, red: red2 } = import_picocolors10.default;
|
|
211777
|
+
function formatTimestamp4() {
|
|
211657
211778
|
const now2 = new Date;
|
|
211658
211779
|
const hours = now2.getHours();
|
|
211659
211780
|
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
211660
211781
|
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211661
211782
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211662
211783
|
const displayHours = hours % 12 || 12;
|
|
211663
|
-
return
|
|
211784
|
+
return dim6(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211664
211785
|
}
|
|
211665
211786
|
async function toggleMode(options) {
|
|
211666
211787
|
const currentMode = getCurrentMode();
|
|
211667
211788
|
const newMode = currentMode === "platform" ? "standalone" : "platform";
|
|
211668
211789
|
const viteServer = getViteServerRef();
|
|
211669
211790
|
if (!viteServer) {
|
|
211670
|
-
options.viteConfig.logger.error(`${
|
|
211791
|
+
options.viteConfig.logger.error(`${formatTimestamp4()} ${red2(bold5("[playcademy]"))} ${red2("Cannot toggle mode: no Vite server reference")}`);
|
|
211671
211792
|
return;
|
|
211672
211793
|
}
|
|
211673
211794
|
await cleanupServers();
|
|
@@ -211681,56 +211802,39 @@ async function toggleMode(options) {
|
|
|
211681
211802
|
} else {
|
|
211682
211803
|
await configurePlatformMode(viteServer, options.viteConfig, options.platformModeOptions);
|
|
211683
211804
|
}
|
|
211684
|
-
options.viteConfig.logger.info(`${
|
|
211805
|
+
options.viteConfig.logger.info(`${formatTimestamp4()} ${cyan4(bold5("[playcademy]"))} ${green4("switched to")} ${green4(bold5(newMode))} ${green4("mode")}`);
|
|
211685
211806
|
}
|
|
211807
|
+
var toggleModeHotkey = (options) => ({
|
|
211808
|
+
key: "m",
|
|
211809
|
+
description: "toggle platform/standalone mode",
|
|
211810
|
+
action: () => toggleMode(options)
|
|
211811
|
+
});
|
|
211686
211812
|
|
|
211687
|
-
// src/server/
|
|
211688
|
-
|
|
211689
|
-
|
|
211690
|
-
|
|
211691
|
-
|
|
211692
|
-
|
|
211693
|
-
|
|
211694
|
-
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211695
|
-
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211696
|
-
const displayHours = hours % 12 || 12;
|
|
211697
|
-
return dim5(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211813
|
+
// src/server/hotkeys/index.ts
|
|
211814
|
+
function getHotkeys(options) {
|
|
211815
|
+
return [
|
|
211816
|
+
toggleModeHotkey(options),
|
|
211817
|
+
recreateDatabaseHotkey(options),
|
|
211818
|
+
cycleTimebackRoleHotkey(options)
|
|
211819
|
+
];
|
|
211698
211820
|
}
|
|
211699
|
-
|
|
211700
|
-
|
|
211701
|
-
|
|
211702
|
-
|
|
211703
|
-
|
|
211704
|
-
return;
|
|
211705
|
-
}
|
|
211706
|
-
if (currentMode !== "platform") {
|
|
211707
|
-
options.viteConfig.logger.warn(`${formatTimestamp3()} ${yellow(bold4("[playcademy]"))} ${dim5("(sandbox)")} ${yellow("can only recreate sandbox database in platform mode (m + enter)")}`);
|
|
211821
|
+
|
|
211822
|
+
// src/server/lifecycle.ts
|
|
211823
|
+
var shutdownHandlersRegistered = false;
|
|
211824
|
+
function setupProcessShutdownHandlers() {
|
|
211825
|
+
if (shutdownHandlersRegistered)
|
|
211708
211826
|
return;
|
|
211709
|
-
|
|
211710
|
-
|
|
211711
|
-
|
|
211712
|
-
|
|
211713
|
-
|
|
211714
|
-
|
|
211715
|
-
|
|
211716
|
-
|
|
211717
|
-
|
|
211718
|
-
|
|
211719
|
-
|
|
211720
|
-
quiet: true,
|
|
211721
|
-
recreateDb: true,
|
|
211722
|
-
seed: options.platformModeOptions.seed,
|
|
211723
|
-
memoryOnly: options.platformModeOptions.memoryOnly,
|
|
211724
|
-
databasePath: options.platformModeOptions.databasePath,
|
|
211725
|
-
realtimeEnabled: options.platformModeOptions.realtimeEnabled,
|
|
211726
|
-
realtimePort: options.platformModeOptions.realtimePort
|
|
211727
|
-
});
|
|
211728
|
-
serverState.sandbox = sandbox;
|
|
211729
|
-
if (sandbox.project && serverState.backend) {
|
|
211730
|
-
const gameUrl = `http://localhost:${serverState.backend.port}`;
|
|
211731
|
-
devServerMiddleware(viteServer, sandbox, gameUrl, options.platformModeOptions.showBadge);
|
|
211732
|
-
}
|
|
211733
|
-
options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan4(bold4("[playcademy]"))} ${dim5("(sandbox)")} ${green4("database recreated")}`);
|
|
211827
|
+
shutdownHandlersRegistered = true;
|
|
211828
|
+
let isShuttingDown = false;
|
|
211829
|
+
const shutdown = async () => {
|
|
211830
|
+
if (isShuttingDown)
|
|
211831
|
+
return;
|
|
211832
|
+
isShuttingDown = true;
|
|
211833
|
+
await cleanupServers();
|
|
211834
|
+
process.exit(0);
|
|
211835
|
+
};
|
|
211836
|
+
process.on("SIGINT", shutdown);
|
|
211837
|
+
process.on("SIGTERM", shutdown);
|
|
211734
211838
|
}
|
|
211735
211839
|
|
|
211736
211840
|
// src/hooks/configure-server.ts
|
|
@@ -211759,7 +211863,8 @@ async function configureServerHook(server, context) {
|
|
|
211759
211863
|
realtimePort: context.options.realtimePort,
|
|
211760
211864
|
showBadge: context.options.showBadge,
|
|
211761
211865
|
preferredBackendPort: preferredPort,
|
|
211762
|
-
configPath: context.options.configPath
|
|
211866
|
+
configPath: context.options.configPath,
|
|
211867
|
+
timeback: context.options.timeback
|
|
211763
211868
|
};
|
|
211764
211869
|
if (context.options.mode === "standalone") {
|
|
211765
211870
|
await configureStandaloneMode(server, context.viteConfig, {
|
|
@@ -211776,26 +211881,10 @@ async function configureServerHook(server, context) {
|
|
|
211776
211881
|
...options,
|
|
211777
211882
|
customShortcuts: [
|
|
211778
211883
|
...options?.customShortcuts || [],
|
|
211779
|
-
{
|
|
211780
|
-
|
|
211781
|
-
|
|
211782
|
-
|
|
211783
|
-
await toggleMode({
|
|
211784
|
-
viteConfig: context.viteConfig,
|
|
211785
|
-
platformModeOptions
|
|
211786
|
-
});
|
|
211787
|
-
}
|
|
211788
|
-
},
|
|
211789
|
-
{
|
|
211790
|
-
key: "d",
|
|
211791
|
-
description: "recreate sandbox database",
|
|
211792
|
-
action: async () => {
|
|
211793
|
-
await recreateSandboxDatabase({
|
|
211794
|
-
viteConfig: context.viteConfig,
|
|
211795
|
-
platformModeOptions
|
|
211796
|
-
});
|
|
211797
|
-
}
|
|
211798
|
-
}
|
|
211884
|
+
...getHotkeys({
|
|
211885
|
+
viteConfig: context.viteConfig,
|
|
211886
|
+
platformModeOptions
|
|
211887
|
+
})
|
|
211799
211888
|
]
|
|
211800
211889
|
});
|
|
211801
211890
|
};
|
|
@@ -211843,7 +211932,8 @@ function resolveOptions(options) {
|
|
|
211843
211932
|
databasePath: sandboxOptions.databasePath,
|
|
211844
211933
|
realtimeEnabled: realtimeOptions.enabled ?? false,
|
|
211845
211934
|
realtimePort: realtimeOptions.port,
|
|
211846
|
-
showBadge: shellOptions.showBadge ?? true
|
|
211935
|
+
showBadge: shellOptions.showBadge ?? true,
|
|
211936
|
+
timeback: options.timeback
|
|
211847
211937
|
};
|
|
211848
211938
|
}
|
|
211849
211939
|
function createPluginContext(options) {
|