@playcademy/vite-plugin 0.1.37 → 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 +776 -743
- 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.36",
|
|
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");
|
|
@@ -189928,7 +189790,7 @@ var createLogger2 = () => {
|
|
|
189928
189790
|
log: performLog2
|
|
189929
189791
|
};
|
|
189930
189792
|
};
|
|
189931
|
-
var
|
|
189793
|
+
var log3 = createLogger2();
|
|
189932
189794
|
async function deleteTimebackResources(client2, courseId) {
|
|
189933
189795
|
const sourcedIds = deriveSourcedIds(courseId);
|
|
189934
189796
|
const { fetchTimebackConfig: fetchTimebackConfig2 } = await Promise.resolve().then(() => (init_verify5(), exports_verify));
|
|
@@ -189976,7 +189838,7 @@ async function deleteTimebackResources(client2, courseId) {
|
|
|
189976
189838
|
lessonType: config2.componentResource.lessonType
|
|
189977
189839
|
})
|
|
189978
189840
|
]);
|
|
189979
|
-
|
|
189841
|
+
log3.info("[TimeBack] Resources soft-deleted", { courseId });
|
|
189980
189842
|
}
|
|
189981
189843
|
init_verify5();
|
|
189982
189844
|
async function createCourse(client2, config2) {
|
|
@@ -189996,12 +189858,12 @@ async function createCourse(client2, config2) {
|
|
|
189996
189858
|
if (!response.sourcedIdPairs?.allocatedSourcedId) {
|
|
189997
189859
|
throw new Error("Course created but TimeBack did not return allocatedSourcedId");
|
|
189998
189860
|
}
|
|
189999
|
-
|
|
189861
|
+
log3.info("[TimeBack] Course created", {
|
|
190000
189862
|
courseId: response.sourcedIdPairs.allocatedSourcedId
|
|
190001
189863
|
});
|
|
190002
189864
|
return response.sourcedIdPairs.allocatedSourcedId;
|
|
190003
189865
|
} catch (error2) {
|
|
190004
|
-
|
|
189866
|
+
log3.error("[TimeBack] Failed to create course", {
|
|
190005
189867
|
title: config2.course.title,
|
|
190006
189868
|
error: error2
|
|
190007
189869
|
});
|
|
@@ -190020,9 +189882,9 @@ async function createComponent(client2, config2, sourcedIds) {
|
|
|
190020
189882
|
prerequisiteCriteria: config2.component.prerequisiteCriteria
|
|
190021
189883
|
};
|
|
190022
189884
|
await client2.oneroster.courseComponents.create({ courseComponent: componentData });
|
|
190023
|
-
|
|
189885
|
+
log3.info("[TimeBack] Component created", { componentId: sourcedIds.component });
|
|
190024
189886
|
} catch (error2) {
|
|
190025
|
-
|
|
189887
|
+
log3.error("[TimeBack] Failed to create component", {
|
|
190026
189888
|
componentId: sourcedIds.component,
|
|
190027
189889
|
error: error2
|
|
190028
189890
|
});
|
|
@@ -190042,11 +189904,11 @@ async function createResource(client2, config2, sourcedIds) {
|
|
|
190042
189904
|
importance: config2.resource.importance,
|
|
190043
189905
|
metadata: config2.resource.metadata
|
|
190044
189906
|
};
|
|
190045
|
-
|
|
189907
|
+
log3.debug("[TimeBack] Creating resource", { resourceId: sourcedIds.resource });
|
|
190046
189908
|
await client2.oneroster.resources.create({ resource: resourceData });
|
|
190047
|
-
|
|
189909
|
+
log3.info("[TimeBack] Resource created", { resourceId: sourcedIds.resource });
|
|
190048
189910
|
} catch (error2) {
|
|
190049
|
-
|
|
189911
|
+
log3.error("[TimeBack] Failed to create resource", {
|
|
190050
189912
|
resourceId: sourcedIds.resource,
|
|
190051
189913
|
error: error2
|
|
190052
189914
|
});
|
|
@@ -190064,17 +189926,17 @@ async function createComponentResourceLink(client2, config2, sourcedIds) {
|
|
|
190064
189926
|
sortOrder: config2.componentResource.sortOrder,
|
|
190065
189927
|
lessonType: config2.componentResource.lessonType
|
|
190066
189928
|
};
|
|
190067
|
-
|
|
189929
|
+
log3.debug("[TimeBack] Creating component-resource link", {
|
|
190068
189930
|
componentResourceId: sourcedIds.componentResource
|
|
190069
189931
|
});
|
|
190070
189932
|
await client2.oneroster.componentResources.create({
|
|
190071
189933
|
componentResource: componentResourceData
|
|
190072
189934
|
});
|
|
190073
|
-
|
|
189935
|
+
log3.info("[TimeBack] Component-resource link created", {
|
|
190074
189936
|
componentResourceId: sourcedIds.componentResource
|
|
190075
189937
|
});
|
|
190076
189938
|
} catch (error2) {
|
|
190077
|
-
|
|
189939
|
+
log3.error("[TimeBack] Failed to create component-resource link", {
|
|
190078
189940
|
componentResourceId: sourcedIds.componentResource,
|
|
190079
189941
|
error: error2
|
|
190080
189942
|
});
|
|
@@ -190098,7 +189960,7 @@ async function setupTimebackResources(client2, config2, options) {
|
|
|
190098
189960
|
...verboseData && { verboseData }
|
|
190099
189961
|
};
|
|
190100
189962
|
} catch (error2) {
|
|
190101
|
-
|
|
189963
|
+
log3.error("[TimeBack] Setup failed", { error: error2 });
|
|
190102
189964
|
throw error2;
|
|
190103
189965
|
}
|
|
190104
189966
|
}
|
|
@@ -190117,9 +189979,9 @@ async function updateCourse(client2, config2, courseId) {
|
|
|
190117
189979
|
metadata: config2.course.metadata
|
|
190118
189980
|
};
|
|
190119
189981
|
await client2.oneroster.courses.update(courseId, courseData);
|
|
190120
|
-
|
|
189982
|
+
log3.info("[TimeBack] Course updated", { courseId });
|
|
190121
189983
|
} catch (error2) {
|
|
190122
|
-
|
|
189984
|
+
log3.error("[TimeBack] Failed to update course", {
|
|
190123
189985
|
courseId,
|
|
190124
189986
|
error: error2
|
|
190125
189987
|
});
|
|
@@ -190138,9 +190000,9 @@ async function updateComponent(client2, config2, sourcedIds) {
|
|
|
190138
190000
|
prerequisiteCriteria: config2.component.prerequisiteCriteria
|
|
190139
190001
|
};
|
|
190140
190002
|
await client2.oneroster.courseComponents.update(sourcedIds.component, componentData);
|
|
190141
|
-
|
|
190003
|
+
log3.info("[TimeBack] Component updated", { componentId: sourcedIds.component });
|
|
190142
190004
|
} catch (error2) {
|
|
190143
|
-
|
|
190005
|
+
log3.error("[TimeBack] Failed to update component", {
|
|
190144
190006
|
componentId: sourcedIds.component,
|
|
190145
190007
|
error: error2
|
|
190146
190008
|
});
|
|
@@ -190161,9 +190023,9 @@ async function updateResource(client2, config2, resourceId) {
|
|
|
190161
190023
|
metadata: config2.resource.metadata
|
|
190162
190024
|
};
|
|
190163
190025
|
await client2.oneroster.resources.update(resourceId, resourceData);
|
|
190164
|
-
|
|
190026
|
+
log3.info("[TimeBack] Resource updated", { resourceId });
|
|
190165
190027
|
} catch (error2) {
|
|
190166
|
-
|
|
190028
|
+
log3.error("[TimeBack] Failed to update resource", {
|
|
190167
190029
|
resourceId,
|
|
190168
190030
|
error: error2
|
|
190169
190031
|
});
|
|
@@ -190182,11 +190044,11 @@ async function updateComponentResourceLink(client2, config2, sourcedIds) {
|
|
|
190182
190044
|
lessonType: config2.componentResource.lessonType
|
|
190183
190045
|
};
|
|
190184
190046
|
await client2.oneroster.componentResources.update(sourcedIds.componentResource, componentResourceData);
|
|
190185
|
-
|
|
190047
|
+
log3.info("[TimeBack] Component-resource link updated", {
|
|
190186
190048
|
componentResourceId: sourcedIds.componentResource
|
|
190187
190049
|
});
|
|
190188
190050
|
} catch (error2) {
|
|
190189
|
-
|
|
190051
|
+
log3.error("[TimeBack] Failed to update component-resource link", {
|
|
190190
190052
|
componentResourceId: sourcedIds.componentResource,
|
|
190191
190053
|
error: error2
|
|
190192
190054
|
});
|
|
@@ -190355,7 +190217,7 @@ async function request({
|
|
|
190355
190217
|
lastError = error2;
|
|
190356
190218
|
if (attempt < retries) {
|
|
190357
190219
|
const delay = Math.pow(HTTP_DEFAULTS.retryBackoffBase, attempt) * 1000;
|
|
190358
|
-
|
|
190220
|
+
log3.warn(`[TimebackClient] Request failed, retrying in ${delay}ms`, {
|
|
190359
190221
|
attempt: attempt + 1,
|
|
190360
190222
|
maxRetries: retries,
|
|
190361
190223
|
status: res.status,
|
|
@@ -190387,7 +190249,7 @@ async function request({
|
|
|
190387
190249
|
lastError = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
190388
190250
|
if (attempt < retries) {
|
|
190389
190251
|
const delay = Math.pow(HTTP_DEFAULTS.retryBackoffBase, attempt) * 1000;
|
|
190390
|
-
|
|
190252
|
+
log3.warn(`[TimebackClient] Network error, retrying in ${delay}ms`, {
|
|
190391
190253
|
attempt: attempt + 1,
|
|
190392
190254
|
maxRetries: retries,
|
|
190393
190255
|
error: lastError.message,
|
|
@@ -190410,11 +190272,158 @@ async function requestCaliper(options) {
|
|
|
190410
190272
|
});
|
|
190411
190273
|
}
|
|
190412
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();
|
|
190413
190422
|
function logTimebackError(operation, error2, context) {
|
|
190414
190423
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
190415
190424
|
if (error2 instanceof TimebackApiError) {
|
|
190416
190425
|
const imsError = error2.details;
|
|
190417
|
-
|
|
190426
|
+
log3.error(`[TimeBack ${operation}] ${errorMessage}`, {
|
|
190418
190427
|
...context,
|
|
190419
190428
|
status: error2.status,
|
|
190420
190429
|
imsx_codeMajor: imsError?.imsx_codeMajor,
|
|
@@ -190425,7 +190434,7 @@ function logTimebackError(operation, error2, context) {
|
|
|
190425
190434
|
console.error(`[TimeBack Error during ${operation}] Full details:`, JSON.stringify(error2.details, null, 2));
|
|
190426
190435
|
}
|
|
190427
190436
|
} else {
|
|
190428
|
-
|
|
190437
|
+
log3.error(`[TimeBack ${operation}] ${errorMessage}`, context);
|
|
190429
190438
|
}
|
|
190430
190439
|
}
|
|
190431
190440
|
function createOneRosterNamespace(client2) {
|
|
@@ -190624,153 +190633,6 @@ function createOneRosterNamespace(client2) {
|
|
|
190624
190633
|
};
|
|
190625
190634
|
}
|
|
190626
190635
|
init_constants();
|
|
190627
|
-
function createCaliperNamespace(client2) {
|
|
190628
|
-
const urls = createOneRosterUrls(client2.getBaseUrl());
|
|
190629
|
-
const caliper = {
|
|
190630
|
-
emit: async (event, sensorUrl) => {
|
|
190631
|
-
const envelope = {
|
|
190632
|
-
sensor: sensorUrl,
|
|
190633
|
-
sendTime: new Date().toISOString(),
|
|
190634
|
-
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190635
|
-
data: [event]
|
|
190636
|
-
};
|
|
190637
|
-
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190638
|
-
},
|
|
190639
|
-
emitBatch: async (events, sensorUrl) => {
|
|
190640
|
-
if (events.length === 0)
|
|
190641
|
-
return;
|
|
190642
|
-
const envelope = {
|
|
190643
|
-
sensor: sensorUrl,
|
|
190644
|
-
sendTime: new Date().toISOString(),
|
|
190645
|
-
dataVersion: CALIPER_CONSTANTS.dataVersion,
|
|
190646
|
-
data: events
|
|
190647
|
-
};
|
|
190648
|
-
return client2["requestCaliper"](CALIPER_ENDPOINTS.events, "POST", envelope);
|
|
190649
|
-
},
|
|
190650
|
-
emitActivityEvent: async (data) => {
|
|
190651
|
-
const event = {
|
|
190652
|
-
"@context": CALIPER_CONSTANTS.context,
|
|
190653
|
-
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190654
|
-
type: TIMEBACK_EVENT_TYPES.activityEvent,
|
|
190655
|
-
eventTime: new Date().toISOString(),
|
|
190656
|
-
profile: CALIPER_CONSTANTS.profile,
|
|
190657
|
-
actor: {
|
|
190658
|
-
id: urls.user(data.studentId),
|
|
190659
|
-
type: TIMEBACK_TYPES.user,
|
|
190660
|
-
email: data.studentEmail
|
|
190661
|
-
},
|
|
190662
|
-
action: TIMEBACK_ACTIONS.completed,
|
|
190663
|
-
object: {
|
|
190664
|
-
id: caliper.buildActivityUrl(data),
|
|
190665
|
-
type: TIMEBACK_TYPES.activityContext,
|
|
190666
|
-
subject: data.subject,
|
|
190667
|
-
app: {
|
|
190668
|
-
name: data.appName
|
|
190669
|
-
},
|
|
190670
|
-
activity: {
|
|
190671
|
-
name: data.activityName
|
|
190672
|
-
},
|
|
190673
|
-
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190674
|
-
process: false
|
|
190675
|
-
},
|
|
190676
|
-
generated: {
|
|
190677
|
-
id: `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
|
|
190678
|
-
type: TIMEBACK_TYPES.activityMetricsCollection,
|
|
190679
|
-
attempt: data.attemptNumber || 1,
|
|
190680
|
-
items: [
|
|
190681
|
-
...data.totalQuestions !== undefined ? [
|
|
190682
|
-
{
|
|
190683
|
-
type: ACTIVITY_METRIC_TYPES.totalQuestions,
|
|
190684
|
-
value: data.totalQuestions
|
|
190685
|
-
}
|
|
190686
|
-
] : [],
|
|
190687
|
-
...data.correctQuestions !== undefined ? [
|
|
190688
|
-
{
|
|
190689
|
-
type: ACTIVITY_METRIC_TYPES.correctQuestions,
|
|
190690
|
-
value: data.correctQuestions
|
|
190691
|
-
}
|
|
190692
|
-
] : [],
|
|
190693
|
-
...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
|
|
190694
|
-
...data.masteredUnits !== undefined ? [
|
|
190695
|
-
{
|
|
190696
|
-
type: ACTIVITY_METRIC_TYPES.masteredUnits,
|
|
190697
|
-
value: data.masteredUnits
|
|
190698
|
-
}
|
|
190699
|
-
] : []
|
|
190700
|
-
],
|
|
190701
|
-
...data.extensions ? { extensions: data.extensions } : {}
|
|
190702
|
-
}
|
|
190703
|
-
};
|
|
190704
|
-
return caliper.emit(event, data.sensorUrl);
|
|
190705
|
-
},
|
|
190706
|
-
emitTimeSpentEvent: async (data) => {
|
|
190707
|
-
const event = {
|
|
190708
|
-
"@context": CALIPER_CONSTANTS.context,
|
|
190709
|
-
id: `urn:uuid:${crypto.randomUUID()}`,
|
|
190710
|
-
type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
|
|
190711
|
-
eventTime: new Date().toISOString(),
|
|
190712
|
-
profile: CALIPER_CONSTANTS.profile,
|
|
190713
|
-
actor: {
|
|
190714
|
-
id: urls.user(data.studentId),
|
|
190715
|
-
type: TIMEBACK_TYPES.user,
|
|
190716
|
-
email: data.studentEmail
|
|
190717
|
-
},
|
|
190718
|
-
action: TIMEBACK_ACTIONS.spentTime,
|
|
190719
|
-
object: {
|
|
190720
|
-
id: caliper.buildActivityUrl(data),
|
|
190721
|
-
type: TIMEBACK_TYPES.activityContext,
|
|
190722
|
-
subject: data.subject,
|
|
190723
|
-
app: {
|
|
190724
|
-
name: data.appName
|
|
190725
|
-
},
|
|
190726
|
-
activity: {
|
|
190727
|
-
name: data.activityName
|
|
190728
|
-
},
|
|
190729
|
-
course: { id: urls.course(data.courseId), name: data.activityName },
|
|
190730
|
-
process: false
|
|
190731
|
-
},
|
|
190732
|
-
generated: {
|
|
190733
|
-
id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
|
|
190734
|
-
type: TIMEBACK_TYPES.timeSpentMetricsCollection,
|
|
190735
|
-
items: [
|
|
190736
|
-
{ type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
|
|
190737
|
-
...data.inactiveTimeSeconds !== undefined ? [
|
|
190738
|
-
{
|
|
190739
|
-
type: TIME_METRIC_TYPES.inactive,
|
|
190740
|
-
value: data.inactiveTimeSeconds
|
|
190741
|
-
}
|
|
190742
|
-
] : [],
|
|
190743
|
-
...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
|
|
190744
|
-
]
|
|
190745
|
-
}
|
|
190746
|
-
};
|
|
190747
|
-
return caliper.emit(event, data.sensorUrl);
|
|
190748
|
-
},
|
|
190749
|
-
buildActivityUrl: (data) => {
|
|
190750
|
-
const base = data.sensorUrl.replace(/\/$/, "");
|
|
190751
|
-
return `${base}/activities/${data.courseId}/${data.activityId}/${crypto.randomUUID()}`;
|
|
190752
|
-
}
|
|
190753
|
-
};
|
|
190754
|
-
return caliper;
|
|
190755
|
-
}
|
|
190756
|
-
function createEduBridgeNamespace(client2) {
|
|
190757
|
-
const enrollments = {
|
|
190758
|
-
listByUser: async (userId) => {
|
|
190759
|
-
const response = await client2["request"](`/edubridge/enrollments/user/${userId}`, "GET");
|
|
190760
|
-
return response.data;
|
|
190761
|
-
}
|
|
190762
|
-
};
|
|
190763
|
-
const analytics = {
|
|
190764
|
-
getEnrollmentFacts: async (enrollmentId) => {
|
|
190765
|
-
return client2["request"](`/edubridge/analytics/enrollment/${enrollmentId}`, "GET");
|
|
190766
|
-
}
|
|
190767
|
-
};
|
|
190768
|
-
return {
|
|
190769
|
-
enrollments,
|
|
190770
|
-
analytics
|
|
190771
|
-
};
|
|
190772
|
-
}
|
|
190773
|
-
init_constants();
|
|
190774
190636
|
init_constants();
|
|
190775
190637
|
|
|
190776
190638
|
class TimebackCache {
|
|
@@ -190792,7 +190654,7 @@ class TimebackCache {
|
|
|
190792
190654
|
}
|
|
190793
190655
|
if (Date.now() > entry.expiresAt) {
|
|
190794
190656
|
this.delete(key);
|
|
190795
|
-
|
|
190657
|
+
log3.debug(`[${this.name}] Cache entry expired`, { key });
|
|
190796
190658
|
this.misses++;
|
|
190797
190659
|
return;
|
|
190798
190660
|
}
|
|
@@ -190807,7 +190669,7 @@ class TimebackCache {
|
|
|
190807
190669
|
}
|
|
190808
190670
|
this.cache.set(key, { value, expiresAt });
|
|
190809
190671
|
this._updateAccessOrder(key);
|
|
190810
|
-
|
|
190672
|
+
log3.debug(`[${this.name}] Cache entry set`, {
|
|
190811
190673
|
key,
|
|
190812
190674
|
expiresAt: new Date(expiresAt).toISOString()
|
|
190813
190675
|
});
|
|
@@ -190822,7 +190684,7 @@ class TimebackCache {
|
|
|
190822
190684
|
clear() {
|
|
190823
190685
|
this.cache.clear();
|
|
190824
190686
|
this.accessOrder = [];
|
|
190825
|
-
|
|
190687
|
+
log3.debug(`[${this.name}] Cache cleared`);
|
|
190826
190688
|
}
|
|
190827
190689
|
size() {
|
|
190828
190690
|
return this.cache.size;
|
|
@@ -190845,7 +190707,7 @@ class TimebackCache {
|
|
|
190845
190707
|
this.delete(key);
|
|
190846
190708
|
}
|
|
190847
190709
|
if (expiredKeys.length > 0) {
|
|
190848
|
-
|
|
190710
|
+
log3.debug(`[${this.name}] Cleaned up expired entries`, {
|
|
190849
190711
|
count: expiredKeys.length
|
|
190850
190712
|
});
|
|
190851
190713
|
}
|
|
@@ -190870,7 +190732,7 @@ class TimebackCache {
|
|
|
190870
190732
|
return;
|
|
190871
190733
|
const oldestKey = this.accessOrder[0];
|
|
190872
190734
|
this.delete(oldestKey);
|
|
190873
|
-
|
|
190735
|
+
log3.debug(`[${this.name}] Evicted LRU entry`, { key: oldestKey });
|
|
190874
190736
|
}
|
|
190875
190737
|
}
|
|
190876
190738
|
|
|
@@ -190930,7 +190792,7 @@ class TimebackCacheManager {
|
|
|
190930
190792
|
this.assessmentLineItemCache.clear();
|
|
190931
190793
|
this.resourceMasteryCache.clear();
|
|
190932
190794
|
this.enrollmentCache.clear();
|
|
190933
|
-
|
|
190795
|
+
log3.info("[TimebackCacheManager] All caches cleared");
|
|
190934
190796
|
}
|
|
190935
190797
|
getStats() {
|
|
190936
190798
|
return {
|
|
@@ -190945,7 +190807,7 @@ class TimebackCacheManager {
|
|
|
190945
190807
|
this.assessmentLineItemCache.cleanup();
|
|
190946
190808
|
this.resourceMasteryCache.cleanup();
|
|
190947
190809
|
this.enrollmentCache.cleanup();
|
|
190948
|
-
|
|
190810
|
+
log3.debug("[TimebackCacheManager] Cache cleanup completed");
|
|
190949
190811
|
}
|
|
190950
190812
|
}
|
|
190951
190813
|
init_constants();
|
|
@@ -190966,7 +190828,7 @@ class MasteryTracker {
|
|
|
190966
190828
|
}
|
|
190967
190829
|
const masterableUnits = await this.resolveMasterableUnits(resourceId);
|
|
190968
190830
|
if (!masterableUnits || masterableUnits <= 0) {
|
|
190969
|
-
|
|
190831
|
+
log3.warn("[MasteryTracker] No masterableUnits configured for course", {
|
|
190970
190832
|
courseId,
|
|
190971
190833
|
resourceId
|
|
190972
190834
|
});
|
|
@@ -190974,7 +190836,7 @@ class MasteryTracker {
|
|
|
190974
190836
|
}
|
|
190975
190837
|
const facts = await this.fetchEnrollmentAnalyticsFacts(studentId, courseId);
|
|
190976
190838
|
if (!facts) {
|
|
190977
|
-
|
|
190839
|
+
log3.warn("[MasteryTracker] Unable to retrieve analytics for mastery-based completion", {
|
|
190978
190840
|
studentId,
|
|
190979
190841
|
courseId
|
|
190980
190842
|
});
|
|
@@ -191014,13 +190876,13 @@ class MasteryTracker {
|
|
|
191014
190876
|
appName
|
|
191015
190877
|
}
|
|
191016
190878
|
});
|
|
191017
|
-
|
|
190879
|
+
log3.info("[MasteryTracker] Created mastery completion entry", {
|
|
191018
190880
|
studentId,
|
|
191019
190881
|
lineItemId,
|
|
191020
190882
|
resultId
|
|
191021
190883
|
});
|
|
191022
190884
|
} catch (error2) {
|
|
191023
|
-
|
|
190885
|
+
log3.error("[MasteryTracker] Failed to create mastery completion entry", {
|
|
191024
190886
|
studentId,
|
|
191025
190887
|
lineItemId,
|
|
191026
190888
|
error: error2
|
|
@@ -191045,7 +190907,7 @@ class MasteryTracker {
|
|
|
191045
190907
|
this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
|
|
191046
190908
|
return masterableUnits;
|
|
191047
190909
|
} catch (error2) {
|
|
191048
|
-
|
|
190910
|
+
log3.error("[MasteryTracker] Failed to fetch resource metadata for mastery config", {
|
|
191049
190911
|
resourceId,
|
|
191050
190912
|
error: error2
|
|
191051
190913
|
});
|
|
@@ -191058,7 +190920,7 @@ class MasteryTracker {
|
|
|
191058
190920
|
const enrollments = await this.edubridgeNamespace.enrollments.listByUser(studentId);
|
|
191059
190921
|
const enrollment = enrollments.find((e2) => e2.course.id === courseId);
|
|
191060
190922
|
if (!enrollment) {
|
|
191061
|
-
|
|
190923
|
+
log3.warn("[MasteryTracker] Enrollment not found for student/course", {
|
|
191062
190924
|
studentId,
|
|
191063
190925
|
courseId
|
|
191064
190926
|
});
|
|
@@ -191067,7 +190929,7 @@ class MasteryTracker {
|
|
|
191067
190929
|
const analytics = await this.edubridgeNamespace.analytics.getEnrollmentFacts(enrollment.id);
|
|
191068
190930
|
return analytics.facts;
|
|
191069
190931
|
} catch (error2) {
|
|
191070
|
-
|
|
190932
|
+
log3.error("[MasteryTracker] Failed to load enrollment analytics facts", {
|
|
191071
190933
|
studentId,
|
|
191072
190934
|
courseId,
|
|
191073
190935
|
error: error2
|
|
@@ -191190,7 +191052,7 @@ class ProgressRecorder {
|
|
|
191190
191052
|
if (score !== undefined) {
|
|
191191
191053
|
await this.createGradebookEntry(actualLineItemId, studentId, currentAttemptNumber, score, totalQuestions, correctQuestions, calculatedXp, masteredUnits, scoreStatus, inProgress, progressData.appName);
|
|
191192
191054
|
} else {
|
|
191193
|
-
|
|
191055
|
+
log3.warn("[ProgressRecorder] Score not provided, skipping gradebook entry", {
|
|
191194
191056
|
studentId,
|
|
191195
191057
|
activityId,
|
|
191196
191058
|
attemptNumber: currentAttemptNumber
|
|
@@ -191237,13 +191099,13 @@ class ProgressRecorder {
|
|
|
191237
191099
|
}
|
|
191238
191100
|
calculateXpForProgress(progressData, totalQuestions, correctQuestions, xpEarned, isFirstAttempt) {
|
|
191239
191101
|
if (xpEarned !== undefined) {
|
|
191240
|
-
|
|
191102
|
+
log3.debug("[ProgressRecorder] Using provided XP", { xpEarned });
|
|
191241
191103
|
return xpEarned;
|
|
191242
191104
|
}
|
|
191243
191105
|
if (progressData.sessionDurationSeconds && totalQuestions && correctQuestions) {
|
|
191244
191106
|
const accuracy = correctQuestions / totalQuestions;
|
|
191245
191107
|
const calculatedXp = calculateXp(progressData.sessionDurationSeconds, accuracy, isFirstAttempt);
|
|
191246
|
-
|
|
191108
|
+
log3.debug("[ProgressRecorder] Calculated XP", {
|
|
191247
191109
|
durationSeconds: progressData.sessionDurationSeconds,
|
|
191248
191110
|
accuracy,
|
|
191249
191111
|
isFirstAttempt,
|
|
@@ -191325,7 +191187,7 @@ class ProgressRecorder {
|
|
|
191325
191187
|
sensorUrl: progressData.sensorUrl,
|
|
191326
191188
|
extensions: extensions || progressData.extensions
|
|
191327
191189
|
}).catch((error2) => {
|
|
191328
|
-
|
|
191190
|
+
log3.error("[ProgressRecorder] Failed to emit activity event", { error: error2 });
|
|
191329
191191
|
});
|
|
191330
191192
|
}
|
|
191331
191193
|
}
|
|
@@ -195371,7 +195233,7 @@ class StudentResolver {
|
|
|
195371
195233
|
if (error2 instanceof TimebackAuthenticationError || error2 instanceof StudentNotFoundError || error2 instanceof TimebackError) {
|
|
195372
195234
|
throw error2;
|
|
195373
195235
|
}
|
|
195374
|
-
|
|
195236
|
+
log3.error("[StudentResolver] Failed to find student by email", {
|
|
195375
195237
|
email,
|
|
195376
195238
|
error: error2
|
|
195377
195239
|
});
|
|
@@ -195395,7 +195257,7 @@ class StudentResolver {
|
|
|
195395
195257
|
this.cacheStudent(email, studentId, studentData);
|
|
195396
195258
|
return studentData;
|
|
195397
195259
|
} catch (error2) {
|
|
195398
|
-
|
|
195260
|
+
log3.error("[StudentResolver] Failed to fetch student by ID, using fallback", {
|
|
195399
195261
|
studentId,
|
|
195400
195262
|
error: error2,
|
|
195401
195263
|
errorMessage: error2 instanceof Error ? error2.message : String(error2),
|
|
@@ -195446,7 +195308,7 @@ class TimebackClient {
|
|
|
195446
195308
|
this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
|
|
195447
195309
|
if (this.credentials) {
|
|
195448
195310
|
this._ensureAuthenticated().catch((error2) => {
|
|
195449
|
-
|
|
195311
|
+
log3.error("[TimebackClient] Auto-authentication failed", { error: error2 });
|
|
195450
195312
|
});
|
|
195451
195313
|
}
|
|
195452
195314
|
}
|
|
@@ -195506,11 +195368,11 @@ class TimebackClient {
|
|
|
195506
195368
|
};
|
|
195507
195369
|
const tokenData = await getTimebackTokenResponse(authConfig);
|
|
195508
195370
|
this.setToken(tokenData.access_token, tokenData.expires_in);
|
|
195509
|
-
|
|
195371
|
+
log3.debug("[TimebackClient] Authentication successful", {
|
|
195510
195372
|
expiresIn: tokenData.expires_in
|
|
195511
195373
|
});
|
|
195512
195374
|
} catch (error2) {
|
|
195513
|
-
|
|
195375
|
+
log3.error("[TimebackClient] Authentication failed", { error: error2 });
|
|
195514
195376
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
195515
195377
|
throw new TimebackAuthenticationError(`TimeBack authentication failed: ${errorMessage}. Verify that your client credentials are correct and not expired. Environment: ${this.baseUrl}.`);
|
|
195516
195378
|
}
|
|
@@ -196216,6 +196078,68 @@ function buildResourceMetadata({
|
|
|
196216
196078
|
}
|
|
196217
196079
|
return metadata2;
|
|
196218
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
|
+
}
|
|
196219
196143
|
var AchievementCompletionType;
|
|
196220
196144
|
((AchievementCompletionType2) => {
|
|
196221
196145
|
AchievementCompletionType2["TIME_PLAYED_SESSION"] = "time_played_session";
|
|
@@ -199521,21 +199445,6 @@ var logger3 = {
|
|
|
199521
199445
|
warn: (msg) => getLogger().warn(msg),
|
|
199522
199446
|
error: (msg) => getLogger().error(msg)
|
|
199523
199447
|
};
|
|
199524
|
-
function detectTimebackCourses() {
|
|
199525
|
-
const coursePattern = /^SANDBOX_TIMEBACK_COURSE_(\d+)_([A-Z_]+)$/i;
|
|
199526
|
-
const courses = [];
|
|
199527
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
199528
|
-
const match2 = key.match(coursePattern);
|
|
199529
|
-
if (match2 && value) {
|
|
199530
|
-
const gradeStr = match2[1];
|
|
199531
|
-
const subjectStr = match2[2];
|
|
199532
|
-
const grade = parseInt(gradeStr, 10);
|
|
199533
|
-
const subject = subjectStr.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
199534
|
-
courses.push({ grade, subject, courseId: value });
|
|
199535
|
-
}
|
|
199536
|
-
}
|
|
199537
|
-
return courses;
|
|
199538
|
-
}
|
|
199539
199448
|
async function seedCoreGames(db) {
|
|
199540
199449
|
const now2 = new Date;
|
|
199541
199450
|
const coreGames = [
|
|
@@ -199560,28 +199469,6 @@ async function seedCoreGames(db) {
|
|
|
199560
199469
|
console.error(`Error seeding core game '${gameData.slug}':`, error2);
|
|
199561
199470
|
}
|
|
199562
199471
|
}
|
|
199563
|
-
if (hasTimebackCredentials()) {
|
|
199564
|
-
const courses = detectTimebackCourses();
|
|
199565
|
-
if (courses.length > 0) {
|
|
199566
|
-
for (const course of courses) {
|
|
199567
|
-
try {
|
|
199568
|
-
await db.insert(gameTimebackIntegrations).values({
|
|
199569
|
-
id: crypto.randomUUID(),
|
|
199570
|
-
gameId: CORE_GAME_UUIDS.PLAYGROUND,
|
|
199571
|
-
courseId: course.courseId,
|
|
199572
|
-
grade: course.grade,
|
|
199573
|
-
subject: course.subject,
|
|
199574
|
-
totalXp: null,
|
|
199575
|
-
lastVerifiedAt: null,
|
|
199576
|
-
createdAt: now2,
|
|
199577
|
-
updatedAt: now2
|
|
199578
|
-
}).onConflictDoNothing();
|
|
199579
|
-
} catch (error2) {
|
|
199580
|
-
console.error(`Error seeding TimeBack integration for playground (${course.subject} grade ${course.grade}):`, error2);
|
|
199581
|
-
}
|
|
199582
|
-
}
|
|
199583
|
-
}
|
|
199584
|
-
}
|
|
199585
199472
|
}
|
|
199586
199473
|
async function seedCurrentProjectGame(db, project) {
|
|
199587
199474
|
const now2 = new Date;
|
|
@@ -199610,24 +199497,6 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
199610
199497
|
updatedAt: now2
|
|
199611
199498
|
};
|
|
199612
199499
|
const [newGame] = await db.insert(games).values(gameRecord).returning();
|
|
199613
|
-
if (hasTimebackCredentials()) {
|
|
199614
|
-
const courses = detectTimebackCourses();
|
|
199615
|
-
if (courses.length > 0) {
|
|
199616
|
-
for (const course of courses) {
|
|
199617
|
-
await db.insert(gameTimebackIntegrations).values({
|
|
199618
|
-
id: crypto.randomUUID(),
|
|
199619
|
-
gameId: newGame.id,
|
|
199620
|
-
courseId: course.courseId,
|
|
199621
|
-
grade: course.grade,
|
|
199622
|
-
subject: course.subject,
|
|
199623
|
-
totalXp: null,
|
|
199624
|
-
lastVerifiedAt: null,
|
|
199625
|
-
createdAt: now2,
|
|
199626
|
-
updatedAt: now2
|
|
199627
|
-
}).onConflictDoNothing();
|
|
199628
|
-
}
|
|
199629
|
-
}
|
|
199630
|
-
}
|
|
199631
199500
|
return newGame;
|
|
199632
199501
|
} catch (error2) {
|
|
199633
199502
|
console.error("❌ Error seeding project game:", error2);
|
|
@@ -199684,12 +199553,23 @@ async function seedSpriteTemplates(db) {
|
|
|
199684
199553
|
}
|
|
199685
199554
|
}
|
|
199686
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
|
+
}
|
|
199687
199566
|
async function seedDemoData(db) {
|
|
199688
199567
|
try {
|
|
199568
|
+
const adminTimebackId = getAdminTimebackId();
|
|
199689
199569
|
for (const [role, user] of Object.entries(DEMO_USERS)) {
|
|
199690
199570
|
const userValues = {
|
|
199691
199571
|
...user,
|
|
199692
|
-
timebackId: role === "admin"
|
|
199572
|
+
timebackId: role === "admin" ? adminTimebackId : null
|
|
199693
199573
|
};
|
|
199694
199574
|
await db.insert(users).values(userValues).onConflictDoNothing();
|
|
199695
199575
|
}
|
|
@@ -205052,9 +204932,11 @@ async function getUserMe(ctx) {
|
|
|
205052
204932
|
const timebackAccount = await db.query.accounts.findFirst({
|
|
205053
204933
|
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
205054
204934
|
});
|
|
204935
|
+
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
|
|
205055
204936
|
return {
|
|
205056
204937
|
...userData,
|
|
205057
|
-
hasTimebackAccount: !!timebackAccount
|
|
204938
|
+
hasTimebackAccount: !!timebackAccount,
|
|
204939
|
+
timeback: timeback3
|
|
205058
204940
|
};
|
|
205059
204941
|
} catch (error2) {
|
|
205060
204942
|
if (error2 instanceof ApiError)
|
|
@@ -209165,7 +209047,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209165
209047
|
throw ApiError.unauthorized("Login required");
|
|
209166
209048
|
try {
|
|
209167
209049
|
const db = getDatabase();
|
|
209168
|
-
const
|
|
209050
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209169
209051
|
where: eq(playerCharacters.userId, user.id),
|
|
209170
209052
|
with: {
|
|
209171
209053
|
accessories: {
|
|
@@ -209175,7 +209057,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209175
209057
|
}
|
|
209176
209058
|
}
|
|
209177
209059
|
});
|
|
209178
|
-
return
|
|
209060
|
+
return pc3 ?? null;
|
|
209179
209061
|
} catch (error2) {
|
|
209180
209062
|
log2.error("Error fetching player character", { error: error2 });
|
|
209181
209063
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -209191,7 +209073,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209191
209073
|
}
|
|
209192
209074
|
try {
|
|
209193
209075
|
const db = getDatabase();
|
|
209194
|
-
const
|
|
209076
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209195
209077
|
where: eq(playerCharacters.userId, userId),
|
|
209196
209078
|
with: {
|
|
209197
209079
|
accessories: {
|
|
@@ -209201,7 +209083,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209201
209083
|
}
|
|
209202
209084
|
}
|
|
209203
209085
|
});
|
|
209204
|
-
return
|
|
209086
|
+
return pc3 ?? null;
|
|
209205
209087
|
} catch (error2) {
|
|
209206
209088
|
log2.error("Error fetching player character by ID", { userId, error: error2 });
|
|
209207
209089
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -210436,48 +210318,12 @@ async function getStudentEnrollments(ctx) {
|
|
|
210436
210318
|
if (!timebackId) {
|
|
210437
210319
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
210438
210320
|
}
|
|
210439
|
-
const db = getDatabase();
|
|
210440
|
-
const isLocal = process.env.PUBLIC_IS_LOCAL === "true";
|
|
210441
|
-
if (isLocal) {
|
|
210442
|
-
log2.debug("[API] Local mode: returning all integrations as mock enrollments", {
|
|
210443
|
-
userId: user.id,
|
|
210444
|
-
timebackId
|
|
210445
|
-
});
|
|
210446
|
-
const allIntegrations = await db.query.gameTimebackIntegrations.findMany();
|
|
210447
|
-
const enrollments2 = allIntegrations.map((integration) => ({
|
|
210448
|
-
gameId: integration.gameId,
|
|
210449
|
-
grade: integration.grade,
|
|
210450
|
-
subject: integration.subject,
|
|
210451
|
-
courseId: integration.courseId
|
|
210452
|
-
}));
|
|
210453
|
-
log2.info("[API] Retrieved mock enrollments (local mode)", {
|
|
210454
|
-
userId: user.id,
|
|
210455
|
-
timebackId,
|
|
210456
|
-
enrollmentCount: enrollments2.length
|
|
210457
|
-
});
|
|
210458
|
-
return { enrollments: enrollments2 };
|
|
210459
|
-
}
|
|
210460
210321
|
log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
|
|
210461
|
-
const
|
|
210462
|
-
const classes = await client2.getEnrollments(timebackId);
|
|
210463
|
-
const courseIds = classes.map((cls) => cls.courseId).filter((id) => Boolean(id));
|
|
210464
|
-
if (courseIds.length === 0) {
|
|
210465
|
-
return { enrollments: [] };
|
|
210466
|
-
}
|
|
210467
|
-
const integrations = await db.query.gameTimebackIntegrations.findMany({
|
|
210468
|
-
where: inArray(gameTimebackIntegrations.courseId, courseIds)
|
|
210469
|
-
});
|
|
210470
|
-
const enrollments = integrations.map((integration) => ({
|
|
210471
|
-
gameId: integration.gameId,
|
|
210472
|
-
grade: integration.grade,
|
|
210473
|
-
subject: integration.subject,
|
|
210474
|
-
courseId: integration.courseId
|
|
210475
|
-
}));
|
|
210322
|
+
const enrollments = await fetchEnrollmentsForUser(timebackId);
|
|
210476
210323
|
log2.info("[API] Retrieved student enrollments", {
|
|
210477
210324
|
userId: user.id,
|
|
210478
210325
|
timebackId,
|
|
210479
|
-
|
|
210480
|
-
mappedEnrollments: enrollments.length
|
|
210326
|
+
enrollmentCount: enrollments.length
|
|
210481
210327
|
});
|
|
210482
210328
|
return { enrollments };
|
|
210483
210329
|
}
|
|
@@ -210985,8 +210831,8 @@ function registerRoutes(app) {
|
|
|
210985
210831
|
return c3.json({ error: "Not Found", message: `Route ${c3.req.path} not found` }, 404);
|
|
210986
210832
|
});
|
|
210987
210833
|
}
|
|
210988
|
-
var version3 =
|
|
210989
|
-
async function
|
|
210834
|
+
var version3 = package_default.version;
|
|
210835
|
+
async function startServer(port, project, options = {}) {
|
|
210990
210836
|
const processedOptions = processServerOptions(port, options);
|
|
210991
210837
|
const db = await setupServerDatabase(processedOptions, project);
|
|
210992
210838
|
const app = createApp(db, {
|
|
@@ -211008,6 +210854,44 @@ async function startServer2(port, project, options = {}) {
|
|
|
211008
210854
|
};
|
|
211009
210855
|
}
|
|
211010
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
|
+
}
|
|
211011
210895
|
// src/lib/sandbox/project-info.ts
|
|
211012
210896
|
import fs6 from "node:fs";
|
|
211013
210897
|
import path5 from "node:path";
|
|
@@ -211046,6 +210930,14 @@ async function extractProjectInfo(viteConfig) {
|
|
|
211046
210930
|
}
|
|
211047
210931
|
|
|
211048
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
|
+
}
|
|
211049
210941
|
function detectTimebackOptions() {
|
|
211050
210942
|
if (process.env.TIMEBACK_LOCAL === "true") {
|
|
211051
210943
|
return {
|
|
@@ -211070,16 +210962,60 @@ function detectTimebackOptions() {
|
|
|
211070
210962
|
}
|
|
211071
210963
|
return;
|
|
211072
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
|
+
}
|
|
211073
211009
|
|
|
211074
211010
|
// src/lib/sandbox/server.ts
|
|
211075
211011
|
function printSandboxInfo(viteConfig, apiPort, realtimePort, projectInfo, realtimeEnabled) {
|
|
211076
211012
|
viteConfig.logger.info("");
|
|
211077
|
-
viteConfig.logger.info(` ${
|
|
211013
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green(import_picocolors6.default.bold("PLAYCADEMY"))} ${import_picocolors6.default.green(`v${version3}`)}`);
|
|
211078
211014
|
viteConfig.logger.info("");
|
|
211079
|
-
viteConfig.logger.info(` ${
|
|
211080
|
-
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`)}`);
|
|
211081
211017
|
if (realtimeEnabled) {
|
|
211082
|
-
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())}`)}`);
|
|
211083
211019
|
}
|
|
211084
211020
|
viteConfig.logger.info("");
|
|
211085
211021
|
}
|
|
@@ -211094,7 +211030,8 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211094
211030
|
databasePath,
|
|
211095
211031
|
realtimeEnabled = false,
|
|
211096
211032
|
realtimePort,
|
|
211097
|
-
logLevel = "info"
|
|
211033
|
+
logLevel = "info",
|
|
211034
|
+
timebackId
|
|
211098
211035
|
} = options;
|
|
211099
211036
|
if (!autoStart || viteConfig.command !== "serve") {
|
|
211100
211037
|
const baseUrl = customUrl ?? `http://localhost:${DEFAULT_PORTS2.SANDBOX}`;
|
|
@@ -211121,10 +211058,16 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211121
211058
|
const sandboxPort = await findAvailablePort(DEFAULT_PORTS2.SANDBOX);
|
|
211122
211059
|
const baseUrl = `http://localhost:${sandboxPort}`;
|
|
211123
211060
|
const projectInfo = await extractProjectInfo(viteConfig);
|
|
211124
|
-
|
|
211061
|
+
let timebackOptions = detectTimebackOptions();
|
|
211062
|
+
if (timebackId) {
|
|
211063
|
+
timebackOptions = {
|
|
211064
|
+
...timebackOptions,
|
|
211065
|
+
studentId: timebackId
|
|
211066
|
+
};
|
|
211067
|
+
}
|
|
211125
211068
|
const finalRealtimePort = realtimePort ?? await findAvailablePort(sandboxPort + 1);
|
|
211126
211069
|
const realtimeUrl = `ws://localhost:${finalRealtimePort}`;
|
|
211127
|
-
const server = await
|
|
211070
|
+
const server = await startServer(sandboxPort, projectInfo, {
|
|
211128
211071
|
verbose,
|
|
211129
211072
|
quiet,
|
|
211130
211073
|
seed,
|
|
@@ -211162,7 +211105,7 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211162
211105
|
}
|
|
211163
211106
|
};
|
|
211164
211107
|
} catch (error2) {
|
|
211165
|
-
viteConfig.logger.error(
|
|
211108
|
+
viteConfig.logger.error(import_picocolors6.default.red(`[Playcademy] Failed to start sandbox: ${error2}`));
|
|
211166
211109
|
return {
|
|
211167
211110
|
baseUrl: `http://localhost:${DEFAULT_PORTS2.SANDBOX}`,
|
|
211168
211111
|
realtimeUrl: "ws://localhost:4322",
|
|
@@ -211387,16 +211330,13 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211387
211330
|
<head>
|
|
211388
211331
|
<meta charset="UTF-8" />
|
|
211389
211332
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
211390
|
-
<title>Playcademy
|
|
211333
|
+
<title>Playcademy Dev</title>
|
|
211391
211334
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
211392
211335
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
211393
211336
|
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet" />
|
|
211337
|
+
<script type="module" src="/@vite/client"></script>
|
|
211394
211338
|
<script type="importmap">
|
|
211395
|
-
{
|
|
211396
|
-
"imports": {
|
|
211397
|
-
"@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest"
|
|
211398
|
-
}
|
|
211399
|
-
}
|
|
211339
|
+
{ "imports": { "@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest" } }
|
|
211400
211340
|
</script>
|
|
211401
211341
|
<style>
|
|
211402
211342
|
* {
|
|
@@ -211404,14 +211344,11 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211404
211344
|
padding: 0;
|
|
211405
211345
|
box-sizing: border-box;
|
|
211406
211346
|
}
|
|
211407
|
-
|
|
211408
211347
|
body {
|
|
211409
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
211410
211348
|
height: 100vh;
|
|
211411
211349
|
overflow: hidden;
|
|
211412
211350
|
}
|
|
211413
|
-
|
|
211414
|
-
.dev-badge {
|
|
211351
|
+
#badge {
|
|
211415
211352
|
position: fixed;
|
|
211416
211353
|
top: 0.5rem;
|
|
211417
211354
|
left: 0.5rem;
|
|
@@ -211426,202 +211363,141 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211426
211363
|
letter-spacing: 0.05em;
|
|
211427
211364
|
transition: background 0.2s ease;
|
|
211428
211365
|
}
|
|
211429
|
-
|
|
211430
|
-
.dev-badge.offline {
|
|
211366
|
+
#badge.offline {
|
|
211431
211367
|
background: rgba(211, 47, 47, 0.9);
|
|
211432
211368
|
}
|
|
211433
|
-
|
|
211434
|
-
.dev-badge-status {
|
|
211435
|
-
font-size: 0.7rem;
|
|
211436
|
-
}
|
|
211437
|
-
|
|
211438
|
-
.dev-badge-status:not(:empty) {
|
|
211439
|
-
margin-left: 0.5rem;
|
|
211440
|
-
}
|
|
211441
|
-
|
|
211442
|
-
.game-container {
|
|
211443
|
-
width: 100vw;
|
|
211444
|
-
height: 100vh;
|
|
211445
|
-
position: relative;
|
|
211446
|
-
overflow: hidden;
|
|
211447
|
-
}
|
|
211448
|
-
|
|
211449
|
-
.game-frame {
|
|
211369
|
+
#frame {
|
|
211450
211370
|
position: absolute;
|
|
211451
211371
|
inset: 0;
|
|
211452
211372
|
border: none;
|
|
211453
211373
|
width: 100%;
|
|
211454
211374
|
height: 100%;
|
|
211455
211375
|
}
|
|
211456
|
-
|
|
211457
|
-
.error {
|
|
211458
|
-
position: absolute;
|
|
211459
|
-
inset: 0;
|
|
211460
|
-
display: flex;
|
|
211461
|
-
flex-direction: column;
|
|
211462
|
-
align-items: center;
|
|
211463
|
-
justify-content: center;
|
|
211464
|
-
color: #d32f2f;
|
|
211465
|
-
background-color: #ffebee;
|
|
211466
|
-
}
|
|
211467
|
-
|
|
211468
211376
|
.hidden {
|
|
211469
211377
|
display: none;
|
|
211470
211378
|
}
|
|
211471
211379
|
</style>
|
|
211472
211380
|
</head>
|
|
211473
211381
|
<body>
|
|
211474
|
-
<div
|
|
211475
|
-
|
|
211476
|
-
</div>
|
|
211477
|
-
|
|
211478
|
-
<div class="game-container">
|
|
211479
|
-
<div class="error hidden" id="error">
|
|
211480
|
-
<div>❌ Failed to load game</div>
|
|
211481
|
-
<div
|
|
211482
|
-
style="margin-top: 0.5rem; font-size: 0.9rem; opacity: 0.8"
|
|
211483
|
-
id="errorMessage"
|
|
211484
|
-
></div>
|
|
211485
|
-
</div>
|
|
211486
|
-
|
|
211487
|
-
<iframe class="game-frame hidden" id="gameFrame" src="/"></iframe>
|
|
211488
|
-
</div>
|
|
211382
|
+
<div id="badge">PLAYCADEMY</div>
|
|
211383
|
+
<iframe id="frame" class="hidden"></iframe>
|
|
211489
211384
|
|
|
211490
211385
|
<script type="module">
|
|
211491
211386
|
import { MessageEvents, messaging } from '@playcademy/sdk'
|
|
211492
211387
|
|
|
211493
|
-
|
|
211494
|
-
const
|
|
211495
|
-
|
|
211496
|
-
|
|
211497
|
-
|
|
211498
|
-
|
|
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
|
+
}
|
|
211499
211396
|
|
|
211500
|
-
|
|
211501
|
-
|
|
211502
|
-
|
|
211397
|
+
// Parse timeback data (role + enrollments)
|
|
211398
|
+
const timeback = (() => {
|
|
211399
|
+
try {
|
|
211400
|
+
return JSON.parse(CONFIG.timebackJson)
|
|
211401
|
+
} catch {
|
|
211402
|
+
return null
|
|
211503
211403
|
}
|
|
211504
|
-
}
|
|
211404
|
+
})()
|
|
211505
211405
|
|
|
211506
|
-
|
|
211406
|
+
// Elements
|
|
211407
|
+
const badge = document.getElementById('badge')
|
|
211408
|
+
const frame = document.getElementById('frame')
|
|
211507
211409
|
|
|
211508
|
-
|
|
211410
|
+
// Debug logging
|
|
211411
|
+
const log = (...args) => window.PLAYCADEMY_DEBUG && console.log('[DevShell]', ...args)
|
|
211412
|
+
|
|
211413
|
+
// Check sandbox connection
|
|
211414
|
+
async function checkSandbox() {
|
|
211509
211415
|
try {
|
|
211510
|
-
const
|
|
211511
|
-
headers: {
|
|
211512
|
-
Authorization: 'Bearer sandbox-demo-token',
|
|
211513
|
-
},
|
|
211416
|
+
const res = await fetch(\`\${CONFIG.sandboxUrl}/api/users/me\`, {
|
|
211417
|
+
headers: { Authorization: 'Bearer sandbox-demo-token' },
|
|
211514
211418
|
})
|
|
211515
|
-
|
|
211516
|
-
|
|
211517
|
-
|
|
211518
|
-
|
|
211519
|
-
|
|
211520
|
-
|
|
211521
|
-
|
|
211522
|
-
|
|
211523
|
-
|
|
211524
|
-
logIfDebug('[PlaycademyDevShell] Sandbox connection failed:', err)
|
|
211525
|
-
badgeStatus.textContent = ' ⚠️'
|
|
211526
|
-
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
|
|
211527
211428
|
}
|
|
211528
211429
|
}
|
|
211529
211430
|
|
|
211530
|
-
//
|
|
211531
|
-
function
|
|
211532
|
-
|
|
211533
|
-
|
|
211534
|
-
|
|
211535
|
-
|
|
211536
|
-
|
|
211537
|
-
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,
|
|
211538
211438
|
token: 'sandbox-demo-token',
|
|
211539
|
-
|
|
211540
|
-
realtimeUrl: '{{REALTIME_URL}}',
|
|
211439
|
+
timeback: timebackData,
|
|
211541
211440
|
}
|
|
211542
211441
|
|
|
211543
|
-
|
|
211544
|
-
if (!gameFrame.contentWindow) return
|
|
211545
|
-
|
|
211546
|
-
messaging.send(MessageEvents.INIT, initPayload, {
|
|
211547
|
-
target: gameFrame.contentWindow,
|
|
211548
|
-
origin: '*',
|
|
211549
|
-
})
|
|
211550
|
-
}
|
|
211442
|
+
let interval, timeout
|
|
211551
211443
|
|
|
211552
|
-
const
|
|
211553
|
-
if (
|
|
211554
|
-
|
|
211555
|
-
|
|
211556
|
-
|
|
211557
|
-
|
|
211558
|
-
clearTimeout(handshakeTimeout)
|
|
211559
|
-
handshakeTimeout = null
|
|
211444
|
+
const send = () => {
|
|
211445
|
+
if (frame.contentWindow) {
|
|
211446
|
+
messaging.send(MessageEvents.INIT, payload, {
|
|
211447
|
+
target: frame.contentWindow,
|
|
211448
|
+
origin: '*',
|
|
211449
|
+
})
|
|
211560
211450
|
}
|
|
211561
211451
|
}
|
|
211562
211452
|
|
|
211563
|
-
|
|
211564
|
-
|
|
211565
|
-
|
|
211566
|
-
logIfDebug('[PlaycademyDevShell] Game iframe loaded, beginning init handshake')
|
|
211567
|
-
|
|
211568
|
-
// Begin handshake: send immediately, then every 300ms
|
|
211569
|
-
sendInit()
|
|
211570
|
-
handshakeInterval = setInterval(sendInit, 300)
|
|
211571
|
-
|
|
211572
|
-
// Stop handshake after 10 seconds
|
|
211573
|
-
handshakeTimeout = setTimeout(() => {
|
|
211574
|
-
stopHandshake()
|
|
211575
|
-
logIfDebug('[PlaycademyDevShell] Init handshake timeout reached')
|
|
211576
|
-
}, 10000)
|
|
211453
|
+
const stop = () => {
|
|
211454
|
+
clearInterval(interval)
|
|
211455
|
+
clearTimeout(timeout)
|
|
211577
211456
|
}
|
|
211578
211457
|
|
|
211579
|
-
|
|
211580
|
-
|
|
211581
|
-
|
|
211582
|
-
|
|
211583
|
-
|
|
211584
|
-
|
|
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)
|
|
211585
211467
|
}
|
|
211586
211468
|
|
|
211587
|
-
// Listen for READY message to stop handshake
|
|
211588
211469
|
messaging.listen(MessageEvents.READY, () => {
|
|
211589
|
-
|
|
211590
|
-
|
|
211470
|
+
stop()
|
|
211471
|
+
log('Game ready')
|
|
211591
211472
|
})
|
|
211592
211473
|
|
|
211593
|
-
|
|
211474
|
+
frame.src = '/'
|
|
211594
211475
|
}
|
|
211595
211476
|
|
|
211596
|
-
|
|
211597
|
-
|
|
211598
|
-
|
|
211599
|
-
|
|
211600
|
-
|
|
211601
|
-
|
|
211602
|
-
|
|
211603
|
-
payload,
|
|
211604
|
-
)
|
|
211605
|
-
// Bridge the message to the local context using CustomEvent
|
|
211606
|
-
messaging.send(type, payload)
|
|
211607
|
-
}
|
|
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)
|
|
211608
211484
|
}
|
|
211609
211485
|
})
|
|
211610
211486
|
|
|
211611
|
-
//
|
|
211612
|
-
|
|
211613
|
-
loadGame()
|
|
211487
|
+
// Start
|
|
211488
|
+
checkSandbox().then(() => initHandshake(timeback))
|
|
211614
211489
|
</script>
|
|
211615
211490
|
</body>
|
|
211616
211491
|
</html>
|
|
211617
211492
|
`;
|
|
211618
211493
|
|
|
211619
211494
|
// src/server/middleware.ts
|
|
211620
|
-
function generateLoaderHTML(sandboxUrl, gameId, realtimeUrl,
|
|
211621
|
-
const shell = showBadge ? shell_with_corner_badge_default : shell_no_badge_default;
|
|
211622
|
-
|
|
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);
|
|
211623
211499
|
}
|
|
211624
|
-
function devServerMiddleware(server, sandbox, gameUrl,
|
|
211500
|
+
function devServerMiddleware(server, sandbox, gameUrl, options) {
|
|
211625
211501
|
server.middlewares.use("/", (req, res, next) => {
|
|
211626
211502
|
if (getCurrentMode() !== "platform") {
|
|
211627
211503
|
next();
|
|
@@ -211633,8 +211509,7 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211633
211509
|
next();
|
|
211634
211510
|
} else {
|
|
211635
211511
|
res.setHeader("Content-Type", "text/html");
|
|
211636
|
-
|
|
211637
|
-
res.end(generateLoaderHTML(sandbox.baseUrl, gameId ?? "", sandbox.realtimeUrl, showBadge, gameUrl));
|
|
211512
|
+
res.end(generateLoaderHTML(sandbox.baseUrl, sandbox.project?.slug ?? "", sandbox.realtimeUrl, options, gameUrl));
|
|
211638
211513
|
}
|
|
211639
211514
|
return;
|
|
211640
211515
|
}
|
|
@@ -211642,6 +211517,190 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211642
211517
|
});
|
|
211643
211518
|
}
|
|
211644
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
|
+
}
|
|
211645
211704
|
// src/server/platform-mode.ts
|
|
211646
211705
|
async function configurePlatformMode(server, viteConfig, options) {
|
|
211647
211706
|
const sandbox = await startSandbox(viteConfig, options.startSandbox, {
|
|
@@ -211654,7 +211713,8 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211654
211713
|
memoryOnly: options.memoryOnly,
|
|
211655
211714
|
databasePath: options.databasePath,
|
|
211656
211715
|
realtimeEnabled: options.realtimeEnabled,
|
|
211657
|
-
realtimePort: options.realtimePort
|
|
211716
|
+
realtimePort: options.realtimePort,
|
|
211717
|
+
timebackId: getEffectiveTimebackId(options.timeback)
|
|
211658
211718
|
});
|
|
211659
211719
|
serverState.sandbox = sandbox;
|
|
211660
211720
|
const backend = await setupCliDevServer({
|
|
@@ -211665,8 +211725,12 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211665
211725
|
});
|
|
211666
211726
|
serverState.backend = backend;
|
|
211667
211727
|
if (sandbox.project) {
|
|
211728
|
+
validateTimebackConfig(viteConfig, options.timeback);
|
|
211668
211729
|
const gameUrl = backend ? `http://localhost:${backend.port}` : undefined;
|
|
211669
|
-
devServerMiddleware(server, sandbox, gameUrl,
|
|
211730
|
+
devServerMiddleware(server, sandbox, gameUrl, {
|
|
211731
|
+
showBadge: options.showBadge,
|
|
211732
|
+
timeback: options.timeback
|
|
211733
|
+
});
|
|
211670
211734
|
}
|
|
211671
211735
|
server.httpServer?.once("listening", () => {
|
|
211672
211736
|
setTimeout(async () => {
|
|
@@ -211677,13 +211741,13 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211677
211741
|
backend: backend?.port,
|
|
211678
211742
|
realtime: sandbox.realtimePort,
|
|
211679
211743
|
vite: vitePort
|
|
211680
|
-
}, projectInfo,
|
|
211744
|
+
}, projectInfo, package_default2.version);
|
|
211681
211745
|
}, 100);
|
|
211682
211746
|
});
|
|
211683
211747
|
}
|
|
211684
211748
|
|
|
211685
211749
|
// src/server/standalone-mode.ts
|
|
211686
|
-
var
|
|
211750
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
211687
211751
|
async function configureStandaloneMode(server, viteConfig, options) {
|
|
211688
211752
|
const backend = await setupCliDevServer({
|
|
211689
211753
|
preferredPort: options.preferredPort,
|
|
@@ -211699,32 +211763,32 @@ async function configureStandaloneMode(server, viteConfig, options) {
|
|
|
211699
211763
|
server.httpServer?.once("listening", () => {
|
|
211700
211764
|
setTimeout(() => {
|
|
211701
211765
|
viteConfig.logger.info("");
|
|
211702
|
-
viteConfig.logger.info(` ${
|
|
211766
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green(import_picocolors9.default.bold("PLAYCADEMY"))} ${import_picocolors9.default.green(`v${package_default2.version}`)}`);
|
|
211703
211767
|
viteConfig.logger.info("");
|
|
211704
|
-
viteConfig.logger.info(` ${
|
|
211705
|
-
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")}`);
|
|
211706
211770
|
viteConfig.logger.info("");
|
|
211707
211771
|
}, 100);
|
|
211708
211772
|
});
|
|
211709
211773
|
}
|
|
211710
211774
|
|
|
211711
|
-
// src/server/mode
|
|
211712
|
-
var { bold:
|
|
211713
|
-
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() {
|
|
211714
211778
|
const now2 = new Date;
|
|
211715
211779
|
const hours = now2.getHours();
|
|
211716
211780
|
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
211717
211781
|
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211718
211782
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211719
211783
|
const displayHours = hours % 12 || 12;
|
|
211720
|
-
return
|
|
211784
|
+
return dim6(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211721
211785
|
}
|
|
211722
211786
|
async function toggleMode(options) {
|
|
211723
211787
|
const currentMode = getCurrentMode();
|
|
211724
211788
|
const newMode = currentMode === "platform" ? "standalone" : "platform";
|
|
211725
211789
|
const viteServer = getViteServerRef();
|
|
211726
211790
|
if (!viteServer) {
|
|
211727
|
-
options.viteConfig.logger.error(`${
|
|
211791
|
+
options.viteConfig.logger.error(`${formatTimestamp4()} ${red2(bold5("[playcademy]"))} ${red2("Cannot toggle mode: no Vite server reference")}`);
|
|
211728
211792
|
return;
|
|
211729
211793
|
}
|
|
211730
211794
|
await cleanupServers();
|
|
@@ -211738,56 +211802,39 @@ async function toggleMode(options) {
|
|
|
211738
211802
|
} else {
|
|
211739
211803
|
await configurePlatformMode(viteServer, options.viteConfig, options.platformModeOptions);
|
|
211740
211804
|
}
|
|
211741
|
-
options.viteConfig.logger.info(`${
|
|
211805
|
+
options.viteConfig.logger.info(`${formatTimestamp4()} ${cyan4(bold5("[playcademy]"))} ${green4("switched to")} ${green4(bold5(newMode))} ${green4("mode")}`);
|
|
211742
211806
|
}
|
|
211807
|
+
var toggleModeHotkey = (options) => ({
|
|
211808
|
+
key: "m",
|
|
211809
|
+
description: "toggle platform/standalone mode",
|
|
211810
|
+
action: () => toggleMode(options)
|
|
211811
|
+
});
|
|
211743
211812
|
|
|
211744
|
-
// src/server/
|
|
211745
|
-
|
|
211746
|
-
|
|
211747
|
-
|
|
211748
|
-
|
|
211749
|
-
|
|
211750
|
-
|
|
211751
|
-
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211752
|
-
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211753
|
-
const displayHours = hours % 12 || 12;
|
|
211754
|
-
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
|
+
];
|
|
211755
211820
|
}
|
|
211756
|
-
|
|
211757
|
-
|
|
211758
|
-
|
|
211759
|
-
|
|
211760
|
-
|
|
211761
|
-
return;
|
|
211762
|
-
}
|
|
211763
|
-
if (currentMode !== "platform") {
|
|
211764
|
-
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)
|
|
211765
211826
|
return;
|
|
211766
|
-
|
|
211767
|
-
|
|
211768
|
-
|
|
211769
|
-
|
|
211770
|
-
|
|
211771
|
-
|
|
211772
|
-
|
|
211773
|
-
|
|
211774
|
-
|
|
211775
|
-
|
|
211776
|
-
|
|
211777
|
-
quiet: true,
|
|
211778
|
-
recreateDb: true,
|
|
211779
|
-
seed: options.platformModeOptions.seed,
|
|
211780
|
-
memoryOnly: options.platformModeOptions.memoryOnly,
|
|
211781
|
-
databasePath: options.platformModeOptions.databasePath,
|
|
211782
|
-
realtimeEnabled: options.platformModeOptions.realtimeEnabled,
|
|
211783
|
-
realtimePort: options.platformModeOptions.realtimePort
|
|
211784
|
-
});
|
|
211785
|
-
serverState.sandbox = sandbox;
|
|
211786
|
-
if (sandbox.project && serverState.backend) {
|
|
211787
|
-
const gameUrl = `http://localhost:${serverState.backend.port}`;
|
|
211788
|
-
devServerMiddleware(viteServer, sandbox, gameUrl, options.platformModeOptions.showBadge);
|
|
211789
|
-
}
|
|
211790
|
-
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);
|
|
211791
211838
|
}
|
|
211792
211839
|
|
|
211793
211840
|
// src/hooks/configure-server.ts
|
|
@@ -211816,7 +211863,8 @@ async function configureServerHook(server, context) {
|
|
|
211816
211863
|
realtimePort: context.options.realtimePort,
|
|
211817
211864
|
showBadge: context.options.showBadge,
|
|
211818
211865
|
preferredBackendPort: preferredPort,
|
|
211819
|
-
configPath: context.options.configPath
|
|
211866
|
+
configPath: context.options.configPath,
|
|
211867
|
+
timeback: context.options.timeback
|
|
211820
211868
|
};
|
|
211821
211869
|
if (context.options.mode === "standalone") {
|
|
211822
211870
|
await configureStandaloneMode(server, context.viteConfig, {
|
|
@@ -211833,26 +211881,10 @@ async function configureServerHook(server, context) {
|
|
|
211833
211881
|
...options,
|
|
211834
211882
|
customShortcuts: [
|
|
211835
211883
|
...options?.customShortcuts || [],
|
|
211836
|
-
{
|
|
211837
|
-
|
|
211838
|
-
|
|
211839
|
-
|
|
211840
|
-
await toggleMode({
|
|
211841
|
-
viteConfig: context.viteConfig,
|
|
211842
|
-
platformModeOptions
|
|
211843
|
-
});
|
|
211844
|
-
}
|
|
211845
|
-
},
|
|
211846
|
-
{
|
|
211847
|
-
key: "d",
|
|
211848
|
-
description: "recreate sandbox database",
|
|
211849
|
-
action: async () => {
|
|
211850
|
-
await recreateSandboxDatabase({
|
|
211851
|
-
viteConfig: context.viteConfig,
|
|
211852
|
-
platformModeOptions
|
|
211853
|
-
});
|
|
211854
|
-
}
|
|
211855
|
-
}
|
|
211884
|
+
...getHotkeys({
|
|
211885
|
+
viteConfig: context.viteConfig,
|
|
211886
|
+
platformModeOptions
|
|
211887
|
+
})
|
|
211856
211888
|
]
|
|
211857
211889
|
});
|
|
211858
211890
|
};
|
|
@@ -211900,7 +211932,8 @@ function resolveOptions(options) {
|
|
|
211900
211932
|
databasePath: sandboxOptions.databasePath,
|
|
211901
211933
|
realtimeEnabled: realtimeOptions.enabled ?? false,
|
|
211902
211934
|
realtimePort: realtimeOptions.port,
|
|
211903
|
-
showBadge: shellOptions.showBadge ?? true
|
|
211935
|
+
showBadge: shellOptions.showBadge ?? true,
|
|
211936
|
+
timeback: options.timeback
|
|
211904
211937
|
};
|
|
211905
211938
|
}
|
|
211906
211939
|
function createPluginContext(options) {
|