@playcademy/vite-plugin 0.1.37 → 0.1.40
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 +842 -749
- package/dist/lib/sandbox/index.d.ts +1 -1
- package/dist/lib/sandbox/project-info.d.ts +5 -1
- package/dist/lib/sandbox/server.d.ts +3 -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 +21 -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.3",
|
|
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,20 +199445,44 @@ var logger3 = {
|
|
|
199521
199445
|
warn: (msg) => getLogger().warn(msg),
|
|
199522
199446
|
error: (msg) => getLogger().error(msg)
|
|
199523
199447
|
};
|
|
199524
|
-
function
|
|
199525
|
-
|
|
199526
|
-
|
|
199527
|
-
|
|
199528
|
-
|
|
199529
|
-
|
|
199530
|
-
|
|
199531
|
-
|
|
199532
|
-
|
|
199533
|
-
|
|
199534
|
-
|
|
199448
|
+
function resolveStudentId(studentId) {
|
|
199449
|
+
if (!studentId)
|
|
199450
|
+
return null;
|
|
199451
|
+
if (studentId === "mock")
|
|
199452
|
+
return `mock-student-${crypto.randomUUID().slice(0, 8)}`;
|
|
199453
|
+
return studentId;
|
|
199454
|
+
}
|
|
199455
|
+
function getAdminTimebackId() {
|
|
199456
|
+
return resolveStudentId(config.timeback.studentId);
|
|
199457
|
+
}
|
|
199458
|
+
async function seedTimebackIntegrations(db, gameId, courses) {
|
|
199459
|
+
const now2 = new Date;
|
|
199460
|
+
let seededCount = 0;
|
|
199461
|
+
for (const course of courses) {
|
|
199462
|
+
const courseId = course.courseId || `mock-${course.subject.toLowerCase()}-g${course.grade}`;
|
|
199463
|
+
try {
|
|
199464
|
+
const existing = await db.query.gameTimebackIntegrations.findFirst({
|
|
199465
|
+
where: (table14, { and: and3, eq: eq3 }) => and3(eq3(table14.gameId, gameId), eq3(table14.grade, course.grade), eq3(table14.subject, course.subject))
|
|
199466
|
+
});
|
|
199467
|
+
if (existing) {
|
|
199468
|
+
continue;
|
|
199469
|
+
}
|
|
199470
|
+
await db.insert(gameTimebackIntegrations).values({
|
|
199471
|
+
gameId,
|
|
199472
|
+
courseId,
|
|
199473
|
+
grade: course.grade,
|
|
199474
|
+
subject: course.subject,
|
|
199475
|
+
totalXp: course.totalXp ?? 1000,
|
|
199476
|
+
lastVerifiedAt: now2
|
|
199477
|
+
});
|
|
199478
|
+
seededCount++;
|
|
199479
|
+
} catch (error2) {
|
|
199480
|
+
console.error(`❌ Error seeding TimeBack integration for ${course.subject}:${course.grade}:`, error2);
|
|
199535
199481
|
}
|
|
199536
199482
|
}
|
|
199537
|
-
|
|
199483
|
+
if (seededCount > 0) {
|
|
199484
|
+
logger3.info(`\uD83D\uDCDA Seeded ${seededCount} TimeBack integration(s)`);
|
|
199485
|
+
}
|
|
199538
199486
|
}
|
|
199539
199487
|
async function seedCoreGames(db) {
|
|
199540
199488
|
const now2 = new Date;
|
|
@@ -199560,28 +199508,6 @@ async function seedCoreGames(db) {
|
|
|
199560
199508
|
console.error(`Error seeding core game '${gameData.slug}':`, error2);
|
|
199561
199509
|
}
|
|
199562
199510
|
}
|
|
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
199511
|
}
|
|
199586
199512
|
async function seedCurrentProjectGame(db, project) {
|
|
199587
199513
|
const now2 = new Date;
|
|
@@ -199591,6 +199517,9 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
199591
199517
|
});
|
|
199592
199518
|
if (existingGame) {
|
|
199593
199519
|
logger3.info(`\uD83C\uDFAE Game "${project.displayName}" (${project.slug}) already exists`);
|
|
199520
|
+
if (project.timebackCourses && project.timebackCourses.length > 0) {
|
|
199521
|
+
await seedTimebackIntegrations(db, existingGame.id, project.timebackCourses);
|
|
199522
|
+
}
|
|
199594
199523
|
return existingGame;
|
|
199595
199524
|
}
|
|
199596
199525
|
const gameRecord = {
|
|
@@ -199610,23 +199539,11 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
199610
199539
|
updatedAt: now2
|
|
199611
199540
|
};
|
|
199612
199541
|
const [newGame] = await db.insert(games).values(gameRecord).returning();
|
|
199613
|
-
if (
|
|
199614
|
-
|
|
199615
|
-
|
|
199616
|
-
|
|
199617
|
-
|
|
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
|
-
}
|
|
199542
|
+
if (!newGame) {
|
|
199543
|
+
throw new Error("Failed to create game record");
|
|
199544
|
+
}
|
|
199545
|
+
if (project.timebackCourses && project.timebackCourses.length > 0) {
|
|
199546
|
+
await seedTimebackIntegrations(db, newGame.id, project.timebackCourses);
|
|
199630
199547
|
}
|
|
199631
199548
|
return newGame;
|
|
199632
199549
|
} catch (error2) {
|
|
@@ -199686,10 +199603,11 @@ async function seedSpriteTemplates(db) {
|
|
|
199686
199603
|
}
|
|
199687
199604
|
async function seedDemoData(db) {
|
|
199688
199605
|
try {
|
|
199606
|
+
const adminTimebackId = getAdminTimebackId();
|
|
199689
199607
|
for (const [role, user] of Object.entries(DEMO_USERS)) {
|
|
199690
199608
|
const userValues = {
|
|
199691
199609
|
...user,
|
|
199692
|
-
timebackId: role === "admin"
|
|
199610
|
+
timebackId: role === "admin" ? adminTimebackId : null
|
|
199693
199611
|
};
|
|
199694
199612
|
await db.insert(users).values(userValues).onConflictDoNothing();
|
|
199695
199613
|
}
|
|
@@ -205052,9 +204970,11 @@ async function getUserMe(ctx) {
|
|
|
205052
204970
|
const timebackAccount = await db.query.accounts.findFirst({
|
|
205053
204971
|
where: and(eq(accounts.userId, user.id), eq(accounts.providerId, "timeback"))
|
|
205054
204972
|
});
|
|
204973
|
+
const timeback3 = userData.timebackId ? await fetchUserTimebackData(userData.timebackId) : undefined;
|
|
205055
204974
|
return {
|
|
205056
204975
|
...userData,
|
|
205057
|
-
hasTimebackAccount: !!timebackAccount
|
|
204976
|
+
hasTimebackAccount: !!timebackAccount,
|
|
204977
|
+
timeback: timeback3
|
|
205058
204978
|
};
|
|
205059
204979
|
} catch (error2) {
|
|
205060
204980
|
if (error2 instanceof ApiError)
|
|
@@ -209165,7 +209085,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209165
209085
|
throw ApiError.unauthorized("Login required");
|
|
209166
209086
|
try {
|
|
209167
209087
|
const db = getDatabase();
|
|
209168
|
-
const
|
|
209088
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209169
209089
|
where: eq(playerCharacters.userId, user.id),
|
|
209170
209090
|
with: {
|
|
209171
209091
|
accessories: {
|
|
@@ -209175,7 +209095,7 @@ async function getPlayerCharacter(ctx) {
|
|
|
209175
209095
|
}
|
|
209176
209096
|
}
|
|
209177
209097
|
});
|
|
209178
|
-
return
|
|
209098
|
+
return pc3 ?? null;
|
|
209179
209099
|
} catch (error2) {
|
|
209180
209100
|
log2.error("Error fetching player character", { error: error2 });
|
|
209181
209101
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -209191,7 +209111,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209191
209111
|
}
|
|
209192
209112
|
try {
|
|
209193
209113
|
const db = getDatabase();
|
|
209194
|
-
const
|
|
209114
|
+
const pc3 = await db.query.playerCharacters.findFirst({
|
|
209195
209115
|
where: eq(playerCharacters.userId, userId),
|
|
209196
209116
|
with: {
|
|
209197
209117
|
accessories: {
|
|
@@ -209201,7 +209121,7 @@ async function getPlayerCharacterById(ctx) {
|
|
|
209201
209121
|
}
|
|
209202
209122
|
}
|
|
209203
209123
|
});
|
|
209204
|
-
return
|
|
209124
|
+
return pc3 ?? null;
|
|
209205
209125
|
} catch (error2) {
|
|
209206
209126
|
log2.error("Error fetching player character by ID", { userId, error: error2 });
|
|
209207
209127
|
throw ApiError.internal("Failed to get player character", error2);
|
|
@@ -210436,48 +210356,12 @@ async function getStudentEnrollments(ctx) {
|
|
|
210436
210356
|
if (!timebackId) {
|
|
210437
210357
|
throw ApiError.badRequest("Missing timebackId parameter");
|
|
210438
210358
|
}
|
|
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
210359
|
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
|
-
}));
|
|
210360
|
+
const enrollments = await fetchEnrollmentsForUser(timebackId);
|
|
210476
210361
|
log2.info("[API] Retrieved student enrollments", {
|
|
210477
210362
|
userId: user.id,
|
|
210478
210363
|
timebackId,
|
|
210479
|
-
|
|
210480
|
-
mappedEnrollments: enrollments.length
|
|
210364
|
+
enrollmentCount: enrollments.length
|
|
210481
210365
|
});
|
|
210482
210366
|
return { enrollments };
|
|
210483
210367
|
}
|
|
@@ -210985,8 +210869,8 @@ function registerRoutes(app) {
|
|
|
210985
210869
|
return c3.json({ error: "Not Found", message: `Route ${c3.req.path} not found` }, 404);
|
|
210986
210870
|
});
|
|
210987
210871
|
}
|
|
210988
|
-
var version3 =
|
|
210989
|
-
async function
|
|
210872
|
+
var version3 = package_default.version;
|
|
210873
|
+
async function startServer(port, project, options = {}) {
|
|
210990
210874
|
const processedOptions = processServerOptions(port, options);
|
|
210991
210875
|
const db = await setupServerDatabase(processedOptions, project);
|
|
210992
210876
|
const app = createApp(db, {
|
|
@@ -211008,17 +210892,71 @@ async function startServer2(port, project, options = {}) {
|
|
|
211008
210892
|
};
|
|
211009
210893
|
}
|
|
211010
210894
|
|
|
210895
|
+
// src/lib/logging/adapter.ts
|
|
210896
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
210897
|
+
function formatTimestamp2() {
|
|
210898
|
+
const now2 = new Date;
|
|
210899
|
+
const hours = now2.getHours();
|
|
210900
|
+
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
210901
|
+
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
210902
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
210903
|
+
const displayHours = hours % 12 || 12;
|
|
210904
|
+
return import_picocolors3.dim(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
210905
|
+
}
|
|
210906
|
+
function createLoggerAdapter(prefix2) {
|
|
210907
|
+
const formattedPrefix = import_picocolors3.dim(`(${prefix2})`);
|
|
210908
|
+
const label = import_picocolors3.cyan(import_picocolors3.bold("[playcademy]"));
|
|
210909
|
+
return {
|
|
210910
|
+
info: (msg) => console.log(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`),
|
|
210911
|
+
warn: (msg) => console.warn(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`),
|
|
210912
|
+
error: (msg) => console.error(`${formatTimestamp2()} ${label} ${formattedPrefix} ${msg}`)
|
|
210913
|
+
};
|
|
210914
|
+
}
|
|
210915
|
+
// src/lib/logging/utils.ts
|
|
210916
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
210917
|
+
function printBanner(viteConfig, servers, projectInfo, pluginVersion) {
|
|
210918
|
+
const INDENT = " ".repeat(2);
|
|
210919
|
+
viteConfig.logger.info("");
|
|
210920
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green(import_picocolors4.bold("PLAYCADEMY"))} ${import_picocolors4.green(`v${pluginVersion}`)}`);
|
|
210921
|
+
viteConfig.logger.info("");
|
|
210922
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Game:")} ${import_picocolors4.cyan(projectInfo.slug)}`);
|
|
210923
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Sandbox:")} ${import_picocolors4.cyan(`http://localhost:${import_picocolors4.bold(servers.sandbox.toString())}/api`)}`);
|
|
210924
|
+
if (servers.backend) {
|
|
210925
|
+
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`;
|
|
210926
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Backend:")} ${import_picocolors4.cyan(backendUrl)}`);
|
|
210927
|
+
}
|
|
210928
|
+
if (servers.realtime) {
|
|
210929
|
+
viteConfig.logger.info(`${INDENT}${import_picocolors4.green("➜")} ${import_picocolors4.bold("Realtime:")} ${import_picocolors4.cyan(`ws://localhost:${import_picocolors4.bold(servers.realtime.toString())}`)}`);
|
|
210930
|
+
}
|
|
210931
|
+
viteConfig.logger.info("");
|
|
210932
|
+
}
|
|
211011
210933
|
// src/lib/sandbox/project-info.ts
|
|
211012
210934
|
import fs6 from "node:fs";
|
|
211013
210935
|
import path5 from "node:path";
|
|
211014
210936
|
import { loadPlaycademyConfig } from "playcademy/utils";
|
|
211015
|
-
|
|
210937
|
+
function extractTimebackCourses(config2, timebackOptions) {
|
|
210938
|
+
const courses = config2?.integrations?.timeback?.courses;
|
|
210939
|
+
if (!courses || courses.length === 0)
|
|
210940
|
+
return;
|
|
210941
|
+
return courses.map((course) => {
|
|
210942
|
+
const key = `${course.subject}:${course.grade}`;
|
|
210943
|
+
const overrideId = timebackOptions?.courses?.[key];
|
|
210944
|
+
const courseId = overrideId && overrideId !== "mock" ? overrideId : undefined;
|
|
210945
|
+
return {
|
|
210946
|
+
subject: course.subject,
|
|
210947
|
+
grade: course.grade,
|
|
210948
|
+
courseId,
|
|
210949
|
+
totalXp: course.totalXp,
|
|
210950
|
+
masterableUnits: course.masterableUnits
|
|
210951
|
+
};
|
|
210952
|
+
});
|
|
210953
|
+
}
|
|
210954
|
+
async function extractProjectInfo(viteConfig, timebackOptions) {
|
|
211016
210955
|
const projectRoot = viteConfig.root;
|
|
211017
210956
|
const directoryName = path5.basename(projectRoot);
|
|
211018
|
-
let
|
|
210957
|
+
let config2 = null;
|
|
211019
210958
|
try {
|
|
211020
|
-
|
|
211021
|
-
configName = config2?.name;
|
|
210959
|
+
config2 = await loadPlaycademyConfig();
|
|
211022
210960
|
} catch {}
|
|
211023
210961
|
let packageJson = {};
|
|
211024
210962
|
try {
|
|
@@ -211028,7 +210966,7 @@ async function extractProjectInfo(viteConfig) {
|
|
|
211028
210966
|
packageJson = JSON.parse(packageJsonContent);
|
|
211029
210967
|
}
|
|
211030
210968
|
} catch {}
|
|
211031
|
-
const name3 =
|
|
210969
|
+
const name3 = config2?.name || packageJson.name || "";
|
|
211032
210970
|
let slug = name3;
|
|
211033
210971
|
if (slug.includes("/")) {
|
|
211034
210972
|
slug = slug.split("/")[1] || slug;
|
|
@@ -211040,12 +210978,21 @@ async function extractProjectInfo(viteConfig) {
|
|
|
211040
210978
|
return {
|
|
211041
210979
|
slug,
|
|
211042
210980
|
displayName,
|
|
211043
|
-
version:
|
|
211044
|
-
description:
|
|
210981
|
+
version: packageJson.version || "dev",
|
|
210982
|
+
description: packageJson.description,
|
|
210983
|
+
timebackCourses: extractTimebackCourses(config2, timebackOptions)
|
|
211045
210984
|
};
|
|
211046
210985
|
}
|
|
211047
210986
|
|
|
211048
210987
|
// src/lib/sandbox/timeback.ts
|
|
210988
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
210989
|
+
function getEffectiveTimebackId(timeback) {
|
|
210990
|
+
if (timeback?.timebackId)
|
|
210991
|
+
return timeback.timebackId;
|
|
210992
|
+
if (timeback?.courses && Object.keys(timeback.courses).length > 0)
|
|
210993
|
+
return "mock";
|
|
210994
|
+
return;
|
|
210995
|
+
}
|
|
211049
210996
|
function detectTimebackOptions() {
|
|
211050
210997
|
if (process.env.TIMEBACK_LOCAL === "true") {
|
|
211051
210998
|
return {
|
|
@@ -211070,16 +211017,60 @@ function detectTimebackOptions() {
|
|
|
211070
211017
|
}
|
|
211071
211018
|
return;
|
|
211072
211019
|
}
|
|
211020
|
+
function hasTimebackCredentials2() {
|
|
211021
|
+
const config2 = detectTimebackOptions();
|
|
211022
|
+
if (!config2)
|
|
211023
|
+
return false;
|
|
211024
|
+
if (config2.mode === "local") {
|
|
211025
|
+
return !!(config2.onerosterApiUrl && config2.caliperApiUrl);
|
|
211026
|
+
}
|
|
211027
|
+
return !!(config2.onerosterApiUrl && config2.clientId && config2.clientSecret && config2.authUrl);
|
|
211028
|
+
}
|
|
211029
|
+
function validateTimebackConfig(viteConfig, timeback) {
|
|
211030
|
+
if (!timeback?.courses)
|
|
211031
|
+
return;
|
|
211032
|
+
const realCourses = Object.entries(timeback.courses).filter(([, value]) => value !== "mock");
|
|
211033
|
+
if (realCourses.length > 0 && !hasTimebackCredentials2()) {
|
|
211034
|
+
const courseList = realCourses.map(([key]) => key).join(", ");
|
|
211035
|
+
viteConfig.logger.warn("");
|
|
211036
|
+
viteConfig.logger.warn(import_picocolors5.default.yellow(`⚠️ TimeBack: Real course IDs for ${import_picocolors5.default.bold(courseList)} but credentials missing.`));
|
|
211037
|
+
viteConfig.logger.warn(import_picocolors5.default.dim(` Required: TIMEBACK_API_CLIENT_ID, TIMEBACK_API_CLIENT_SECRET, TIMEBACK_API_AUTH_URL, TIMEBACK_ONEROSTER_API_URL`));
|
|
211038
|
+
viteConfig.logger.warn(import_picocolors5.default.dim(` Or use 'mock' for local testing.`));
|
|
211039
|
+
viteConfig.logger.warn("");
|
|
211040
|
+
}
|
|
211041
|
+
}
|
|
211042
|
+
function generateTimebackJson(timeback) {
|
|
211043
|
+
if (!timeback?.courses || Object.keys(timeback.courses).length === 0) {
|
|
211044
|
+
return;
|
|
211045
|
+
}
|
|
211046
|
+
const enrollments = [];
|
|
211047
|
+
for (const [key, value] of Object.entries(timeback.courses)) {
|
|
211048
|
+
const parts2 = key.split(":");
|
|
211049
|
+
const subject = parts2[0];
|
|
211050
|
+
const gradeStr = parts2[1];
|
|
211051
|
+
const grade = gradeStr ? parseInt(gradeStr, 10) : NaN;
|
|
211052
|
+
if (!subject || isNaN(grade))
|
|
211053
|
+
continue;
|
|
211054
|
+
const courseId = value === "mock" ? `mock-${subject.toLowerCase()}-g${grade}` : value;
|
|
211055
|
+
enrollments.push({ subject, grade, courseId });
|
|
211056
|
+
}
|
|
211057
|
+
if (enrollments.length === 0) {
|
|
211058
|
+
return;
|
|
211059
|
+
}
|
|
211060
|
+
const roleOverride = getTimebackRoleOverride();
|
|
211061
|
+
const role = roleOverride ?? timeback.role ?? "student";
|
|
211062
|
+
return JSON.stringify({ role, enrollments });
|
|
211063
|
+
}
|
|
211073
211064
|
|
|
211074
211065
|
// src/lib/sandbox/server.ts
|
|
211075
211066
|
function printSandboxInfo(viteConfig, apiPort, realtimePort, projectInfo, realtimeEnabled) {
|
|
211076
211067
|
viteConfig.logger.info("");
|
|
211077
|
-
viteConfig.logger.info(` ${
|
|
211068
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green(import_picocolors6.default.bold("PLAYCADEMY"))} ${import_picocolors6.default.green(`v${version3}`)}`);
|
|
211078
211069
|
viteConfig.logger.info("");
|
|
211079
|
-
viteConfig.logger.info(` ${
|
|
211080
|
-
viteConfig.logger.info(` ${
|
|
211070
|
+
viteConfig.logger.info(` ${import_picocolors6.default.green("➜")} ${import_picocolors6.default.bold("Game:")} ${import_picocolors6.default.cyan(projectInfo.slug)}`);
|
|
211071
|
+
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
211072
|
if (realtimeEnabled) {
|
|
211082
|
-
viteConfig.logger.info(` ${
|
|
211073
|
+
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
211074
|
}
|
|
211084
211075
|
viteConfig.logger.info("");
|
|
211085
211076
|
}
|
|
@@ -211094,7 +211085,9 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211094
211085
|
databasePath,
|
|
211095
211086
|
realtimeEnabled = false,
|
|
211096
211087
|
realtimePort,
|
|
211097
|
-
logLevel = "info"
|
|
211088
|
+
logLevel = "info",
|
|
211089
|
+
timebackId,
|
|
211090
|
+
timebackOptions
|
|
211098
211091
|
} = options;
|
|
211099
211092
|
if (!autoStart || viteConfig.command !== "serve") {
|
|
211100
211093
|
const baseUrl = customUrl ?? `http://localhost:${DEFAULT_PORTS2.SANDBOX}`;
|
|
@@ -211120,11 +211113,17 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211120
211113
|
try {
|
|
211121
211114
|
const sandboxPort = await findAvailablePort(DEFAULT_PORTS2.SANDBOX);
|
|
211122
211115
|
const baseUrl = `http://localhost:${sandboxPort}`;
|
|
211123
|
-
const projectInfo = await extractProjectInfo(viteConfig);
|
|
211124
|
-
|
|
211116
|
+
const projectInfo = await extractProjectInfo(viteConfig, timebackOptions);
|
|
211117
|
+
let sandboxTimebackOptions = detectTimebackOptions();
|
|
211118
|
+
if (timebackId) {
|
|
211119
|
+
sandboxTimebackOptions = {
|
|
211120
|
+
...sandboxTimebackOptions,
|
|
211121
|
+
studentId: timebackId
|
|
211122
|
+
};
|
|
211123
|
+
}
|
|
211125
211124
|
const finalRealtimePort = realtimePort ?? await findAvailablePort(sandboxPort + 1);
|
|
211126
211125
|
const realtimeUrl = `ws://localhost:${finalRealtimePort}`;
|
|
211127
|
-
const server = await
|
|
211126
|
+
const server = await startServer(sandboxPort, projectInfo, {
|
|
211128
211127
|
verbose,
|
|
211129
211128
|
quiet,
|
|
211130
211129
|
seed,
|
|
@@ -211133,7 +211132,7 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211133
211132
|
recreateDb,
|
|
211134
211133
|
logLevel,
|
|
211135
211134
|
realtime: { enabled: realtimeEnabled, port: finalRealtimePort },
|
|
211136
|
-
timeback:
|
|
211135
|
+
timeback: sandboxTimebackOptions,
|
|
211137
211136
|
logger: createLoggerAdapter("sandbox")
|
|
211138
211137
|
});
|
|
211139
211138
|
writeServerInfo("sandbox", {
|
|
@@ -211162,7 +211161,7 @@ async function startSandbox(viteConfig, autoStart = true, options = {}) {
|
|
|
211162
211161
|
}
|
|
211163
211162
|
};
|
|
211164
211163
|
} catch (error2) {
|
|
211165
|
-
viteConfig.logger.error(
|
|
211164
|
+
viteConfig.logger.error(import_picocolors6.default.red(`[Playcademy] Failed to start sandbox: ${error2}`));
|
|
211166
211165
|
return {
|
|
211167
211166
|
baseUrl: `http://localhost:${DEFAULT_PORTS2.SANDBOX}`,
|
|
211168
211167
|
realtimeUrl: "ws://localhost:4322",
|
|
@@ -211387,16 +211386,13 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211387
211386
|
<head>
|
|
211388
211387
|
<meta charset="UTF-8" />
|
|
211389
211388
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
211390
|
-
<title>Playcademy
|
|
211389
|
+
<title>Playcademy Dev</title>
|
|
211391
211390
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
211392
211391
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
211393
211392
|
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet" />
|
|
211393
|
+
<script type="module" src="/@vite/client"></script>
|
|
211394
211394
|
<script type="importmap">
|
|
211395
|
-
{
|
|
211396
|
-
"imports": {
|
|
211397
|
-
"@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest"
|
|
211398
|
-
}
|
|
211399
|
-
}
|
|
211395
|
+
{ "imports": { "@playcademy/sdk": "https://esm.sh/@playcademy/sdk@latest" } }
|
|
211400
211396
|
</script>
|
|
211401
211397
|
<style>
|
|
211402
211398
|
* {
|
|
@@ -211404,14 +211400,11 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211404
211400
|
padding: 0;
|
|
211405
211401
|
box-sizing: border-box;
|
|
211406
211402
|
}
|
|
211407
|
-
|
|
211408
211403
|
body {
|
|
211409
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
211410
211404
|
height: 100vh;
|
|
211411
211405
|
overflow: hidden;
|
|
211412
211406
|
}
|
|
211413
|
-
|
|
211414
|
-
.dev-badge {
|
|
211407
|
+
#badge {
|
|
211415
211408
|
position: fixed;
|
|
211416
211409
|
top: 0.5rem;
|
|
211417
211410
|
left: 0.5rem;
|
|
@@ -211426,202 +211419,144 @@ var shell_with_corner_badge_default = `<!doctype html>
|
|
|
211426
211419
|
letter-spacing: 0.05em;
|
|
211427
211420
|
transition: background 0.2s ease;
|
|
211428
211421
|
}
|
|
211429
|
-
|
|
211430
|
-
.dev-badge.offline {
|
|
211422
|
+
#badge.offline {
|
|
211431
211423
|
background: rgba(211, 47, 47, 0.9);
|
|
211432
211424
|
}
|
|
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 {
|
|
211425
|
+
#frame {
|
|
211450
211426
|
position: absolute;
|
|
211451
211427
|
inset: 0;
|
|
211452
211428
|
border: none;
|
|
211453
211429
|
width: 100%;
|
|
211454
211430
|
height: 100%;
|
|
211455
211431
|
}
|
|
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
211432
|
.hidden {
|
|
211469
211433
|
display: none;
|
|
211470
211434
|
}
|
|
211471
211435
|
</style>
|
|
211472
211436
|
</head>
|
|
211473
211437
|
<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>
|
|
211438
|
+
<div id="badge">PLAYCADEMY</div>
|
|
211439
|
+
<iframe id="frame" class="hidden"></iframe>
|
|
211489
211440
|
|
|
211490
211441
|
<script type="module">
|
|
211491
211442
|
import { MessageEvents, messaging } from '@playcademy/sdk'
|
|
211492
211443
|
|
|
211493
|
-
|
|
211494
|
-
const
|
|
211495
|
-
|
|
211496
|
-
|
|
211497
|
-
|
|
211498
|
-
|
|
211444
|
+
// Config (injected by vite plugin)
|
|
211445
|
+
const CONFIG = {
|
|
211446
|
+
sandboxUrl: '{{SANDBOX_URL}}',
|
|
211447
|
+
gameId: '{{GAME_ID}}',
|
|
211448
|
+
gameUrl: '{{GAME_URL}}' || undefined,
|
|
211449
|
+
realtimeUrl: '{{REALTIME_URL}}',
|
|
211450
|
+
timebackJson: '{{TIMEBACK_DATA}}',
|
|
211451
|
+
}
|
|
211499
211452
|
|
|
211500
|
-
|
|
211501
|
-
|
|
211502
|
-
|
|
211453
|
+
// Parse timeback data (role + enrollments)
|
|
211454
|
+
const timeback = (() => {
|
|
211455
|
+
try {
|
|
211456
|
+
return JSON.parse(CONFIG.timebackJson)
|
|
211457
|
+
} catch {
|
|
211458
|
+
return null
|
|
211503
211459
|
}
|
|
211504
|
-
}
|
|
211460
|
+
})()
|
|
211505
211461
|
|
|
211506
|
-
|
|
211462
|
+
// Elements
|
|
211463
|
+
const badge = document.getElementById('badge')
|
|
211464
|
+
const frame = document.getElementById('frame')
|
|
211507
211465
|
|
|
211508
|
-
|
|
211466
|
+
// Debug logging
|
|
211467
|
+
const log = (...args) => window.PLAYCADEMY_DEBUG && console.log('[DevShell]', ...args)
|
|
211468
|
+
|
|
211469
|
+
// Check sandbox connection
|
|
211470
|
+
async function checkSandbox() {
|
|
211509
211471
|
try {
|
|
211510
|
-
const
|
|
211511
|
-
headers: {
|
|
211512
|
-
Authorization: 'Bearer sandbox-demo-token',
|
|
211513
|
-
},
|
|
211472
|
+
const res = await fetch(\`\${CONFIG.sandboxUrl}/api/users/me\`, {
|
|
211473
|
+
headers: { Authorization: 'Bearer sandbox-demo-token' },
|
|
211514
211474
|
})
|
|
211515
|
-
|
|
211516
|
-
|
|
211517
|
-
|
|
211518
|
-
|
|
211519
|
-
|
|
211520
|
-
|
|
211521
|
-
|
|
211522
|
-
|
|
211523
|
-
|
|
211524
|
-
logIfDebug('[PlaycademyDevShell] Sandbox connection failed:', err)
|
|
211525
|
-
badgeStatus.textContent = ' ⚠️'
|
|
211526
|
-
devBadge.classList.add('offline')
|
|
211475
|
+
if (!res.ok) throw new Error('Sandbox unavailable')
|
|
211476
|
+
badge.classList.remove('offline')
|
|
211477
|
+
log('Sandbox connected')
|
|
211478
|
+
return true
|
|
211479
|
+
} catch (e) {
|
|
211480
|
+
badge.classList.add('offline')
|
|
211481
|
+
badge.textContent = 'PLAYCADEMY ⚠️'
|
|
211482
|
+
log('Sandbox failed:', e)
|
|
211483
|
+
return false
|
|
211527
211484
|
}
|
|
211528
211485
|
}
|
|
211529
211486
|
|
|
211530
|
-
//
|
|
211531
|
-
function
|
|
211532
|
-
|
|
211533
|
-
|
|
211534
|
-
|
|
211535
|
-
|
|
211536
|
-
|
|
211537
|
-
gameUrl: '{{GAME_URL}}' || undefined,
|
|
211487
|
+
// Init handshake with game iframe
|
|
211488
|
+
function initHandshake(timebackData) {
|
|
211489
|
+
const payload = {
|
|
211490
|
+
baseUrl: CONFIG.sandboxUrl,
|
|
211491
|
+
gameUrl: CONFIG.gameUrl,
|
|
211492
|
+
gameId: CONFIG.gameId,
|
|
211493
|
+
realtimeUrl: CONFIG.realtimeUrl,
|
|
211538
211494
|
token: 'sandbox-demo-token',
|
|
211539
|
-
|
|
211540
|
-
realtimeUrl: '{{REALTIME_URL}}',
|
|
211495
|
+
timeback: timebackData,
|
|
211541
211496
|
}
|
|
211542
211497
|
|
|
211543
|
-
|
|
211544
|
-
if (!gameFrame.contentWindow) return
|
|
211545
|
-
|
|
211546
|
-
messaging.send(MessageEvents.INIT, initPayload, {
|
|
211547
|
-
target: gameFrame.contentWindow,
|
|
211548
|
-
origin: '*',
|
|
211549
|
-
})
|
|
211550
|
-
}
|
|
211498
|
+
let interval, timeout
|
|
211551
211499
|
|
|
211552
|
-
const
|
|
211553
|
-
if (
|
|
211554
|
-
|
|
211555
|
-
|
|
211556
|
-
|
|
211557
|
-
|
|
211558
|
-
clearTimeout(handshakeTimeout)
|
|
211559
|
-
handshakeTimeout = null
|
|
211500
|
+
const send = () => {
|
|
211501
|
+
if (frame.contentWindow) {
|
|
211502
|
+
messaging.send(MessageEvents.INIT, payload, {
|
|
211503
|
+
target: frame.contentWindow,
|
|
211504
|
+
origin: '*',
|
|
211505
|
+
})
|
|
211560
211506
|
}
|
|
211561
211507
|
}
|
|
211562
211508
|
|
|
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)
|
|
211509
|
+
const stop = () => {
|
|
211510
|
+
clearInterval(interval)
|
|
211511
|
+
clearTimeout(timeout)
|
|
211577
211512
|
}
|
|
211578
211513
|
|
|
211579
|
-
|
|
211580
|
-
|
|
211581
|
-
|
|
211582
|
-
|
|
211583
|
-
|
|
211584
|
-
|
|
211514
|
+
frame.onload = () => {
|
|
211515
|
+
frame.classList.remove('hidden')
|
|
211516
|
+
log('Frame loaded, starting handshake')
|
|
211517
|
+
send()
|
|
211518
|
+
interval = setInterval(send, 300)
|
|
211519
|
+
timeout = setTimeout(() => {
|
|
211520
|
+
stop()
|
|
211521
|
+
log('Handshake timeout')
|
|
211522
|
+
}, 10000)
|
|
211585
211523
|
}
|
|
211586
211524
|
|
|
211587
|
-
// Listen for READY message to stop handshake
|
|
211588
211525
|
messaging.listen(MessageEvents.READY, () => {
|
|
211589
|
-
|
|
211590
|
-
|
|
211526
|
+
stop()
|
|
211527
|
+
log('Game ready')
|
|
211591
211528
|
})
|
|
211592
211529
|
|
|
211593
|
-
|
|
211530
|
+
frame.src = '/'
|
|
211594
211531
|
}
|
|
211595
211532
|
|
|
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
|
-
}
|
|
211533
|
+
// Bridge messages from game to shell context
|
|
211534
|
+
window.addEventListener('message', e => {
|
|
211535
|
+
if (e.source !== frame.contentWindow) return
|
|
211536
|
+
const { type, ...payload } = e.data || {}
|
|
211537
|
+
if (type?.startsWith('PLAYCADEMY_')) {
|
|
211538
|
+
log('Message:', type, payload)
|
|
211539
|
+
messaging.send(type, payload)
|
|
211608
211540
|
}
|
|
211609
211541
|
})
|
|
211610
211542
|
|
|
211611
|
-
//
|
|
211612
|
-
|
|
211613
|
-
loadGame()
|
|
211543
|
+
// Start
|
|
211544
|
+
checkSandbox().then(() => initHandshake(timeback))
|
|
211614
211545
|
</script>
|
|
211615
211546
|
</body>
|
|
211616
211547
|
</html>
|
|
211548
|
+
|
|
211549
|
+
|
|
211550
|
+
|
|
211617
211551
|
`;
|
|
211618
211552
|
|
|
211619
211553
|
// src/server/middleware.ts
|
|
211620
|
-
function generateLoaderHTML(sandboxUrl, gameId, realtimeUrl,
|
|
211621
|
-
const shell = showBadge ? shell_with_corner_badge_default : shell_no_badge_default;
|
|
211622
|
-
|
|
211554
|
+
function generateLoaderHTML(sandboxUrl, gameId, realtimeUrl, options, gameUrl) {
|
|
211555
|
+
const shell = options.showBadge ? shell_with_corner_badge_default : shell_no_badge_default;
|
|
211556
|
+
const timebackJson = generateTimebackJson(options.timeback) ?? "null";
|
|
211557
|
+
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
211558
|
}
|
|
211624
|
-
function devServerMiddleware(server, sandbox, gameUrl,
|
|
211559
|
+
function devServerMiddleware(server, sandbox, gameUrl, options) {
|
|
211625
211560
|
server.middlewares.use("/", (req, res, next) => {
|
|
211626
211561
|
if (getCurrentMode() !== "platform") {
|
|
211627
211562
|
next();
|
|
@@ -211633,8 +211568,7 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211633
211568
|
next();
|
|
211634
211569
|
} else {
|
|
211635
211570
|
res.setHeader("Content-Type", "text/html");
|
|
211636
|
-
|
|
211637
|
-
res.end(generateLoaderHTML(sandbox.baseUrl, gameId ?? "", sandbox.realtimeUrl, showBadge, gameUrl));
|
|
211571
|
+
res.end(generateLoaderHTML(sandbox.baseUrl, sandbox.project?.slug ?? "", sandbox.realtimeUrl, options, gameUrl));
|
|
211638
211572
|
}
|
|
211639
211573
|
return;
|
|
211640
211574
|
}
|
|
@@ -211642,6 +211576,190 @@ function devServerMiddleware(server, sandbox, gameUrl, showBadge) {
|
|
|
211642
211576
|
});
|
|
211643
211577
|
}
|
|
211644
211578
|
|
|
211579
|
+
// src/server/hotkeys/recreate-database.ts
|
|
211580
|
+
var { bold: bold4, cyan: cyan3, dim: dim4, green: green2, red, yellow: yellow2 } = import_picocolors7.default;
|
|
211581
|
+
function formatTimestamp3() {
|
|
211582
|
+
const now2 = new Date;
|
|
211583
|
+
const hours = now2.getHours();
|
|
211584
|
+
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
211585
|
+
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211586
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211587
|
+
const displayHours = hours % 12 || 12;
|
|
211588
|
+
return dim4(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211589
|
+
}
|
|
211590
|
+
async function recreateSandboxDatabase(options) {
|
|
211591
|
+
const currentMode = getCurrentMode();
|
|
211592
|
+
const viteServer = getViteServerRef();
|
|
211593
|
+
if (!viteServer) {
|
|
211594
|
+
options.viteConfig.logger.error(`${formatTimestamp3()} ${red(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${red("Cannot recreate sandbox database: no Vite server reference")}`);
|
|
211595
|
+
return;
|
|
211596
|
+
}
|
|
211597
|
+
if (currentMode !== "platform") {
|
|
211598
|
+
options.viteConfig.logger.warn(`${formatTimestamp3()} ${yellow2(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${yellow2("can only recreate sandbox database in platform mode (m + enter)")}`);
|
|
211599
|
+
return;
|
|
211600
|
+
}
|
|
211601
|
+
options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan3(bold4("[playcademy]"))} ${dim4("(sandbox)")} recreating database...`);
|
|
211602
|
+
if (serverState.sandbox) {
|
|
211603
|
+
serverState.sandbox.cleanup();
|
|
211604
|
+
serverState.sandbox = null;
|
|
211605
|
+
}
|
|
211606
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
211607
|
+
const sandbox = await startSandbox(options.viteConfig, options.platformModeOptions.startSandbox, {
|
|
211608
|
+
verbose: options.platformModeOptions.verbose,
|
|
211609
|
+
logLevel: options.platformModeOptions.logLevel,
|
|
211610
|
+
customUrl: options.platformModeOptions.sandboxUrl,
|
|
211611
|
+
quiet: true,
|
|
211612
|
+
recreateDb: true,
|
|
211613
|
+
seed: options.platformModeOptions.seed,
|
|
211614
|
+
memoryOnly: options.platformModeOptions.memoryOnly,
|
|
211615
|
+
databasePath: options.platformModeOptions.databasePath,
|
|
211616
|
+
realtimeEnabled: options.platformModeOptions.realtimeEnabled,
|
|
211617
|
+
realtimePort: options.platformModeOptions.realtimePort,
|
|
211618
|
+
timebackId: getEffectiveTimebackId(options.platformModeOptions.timeback)
|
|
211619
|
+
});
|
|
211620
|
+
serverState.sandbox = sandbox;
|
|
211621
|
+
if (sandbox.project && serverState.backend) {
|
|
211622
|
+
const gameUrl = `http://localhost:${serverState.backend.port}`;
|
|
211623
|
+
devServerMiddleware(viteServer, sandbox, gameUrl, {
|
|
211624
|
+
showBadge: options.platformModeOptions.showBadge,
|
|
211625
|
+
timeback: options.platformModeOptions.timeback
|
|
211626
|
+
});
|
|
211627
|
+
}
|
|
211628
|
+
options.viteConfig.logger.info(`${formatTimestamp3()} ${cyan3(bold4("[playcademy]"))} ${dim4("(sandbox)")} ${green2("database recreated")}`);
|
|
211629
|
+
}
|
|
211630
|
+
var recreateDatabaseHotkey = (options) => ({
|
|
211631
|
+
key: "d",
|
|
211632
|
+
description: "recreate sandbox database",
|
|
211633
|
+
action: () => recreateSandboxDatabase(options)
|
|
211634
|
+
});
|
|
211635
|
+
|
|
211636
|
+
// src/server/hotkeys/toggle-mode.ts
|
|
211637
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
211638
|
+
// package.json
|
|
211639
|
+
var package_default2 = {
|
|
211640
|
+
name: "@playcademy/vite-plugin",
|
|
211641
|
+
version: "0.1.40",
|
|
211642
|
+
type: "module",
|
|
211643
|
+
exports: {
|
|
211644
|
+
".": {
|
|
211645
|
+
import: "./dist/index.js",
|
|
211646
|
+
types: "./dist/index.d.ts"
|
|
211647
|
+
}
|
|
211648
|
+
},
|
|
211649
|
+
main: "dist/index.js",
|
|
211650
|
+
module: "dist/index.js",
|
|
211651
|
+
files: [
|
|
211652
|
+
"dist"
|
|
211653
|
+
],
|
|
211654
|
+
scripts: {
|
|
211655
|
+
build: "rm -rf dist && bun build.ts",
|
|
211656
|
+
docs: "typedoc --skipErrorChecking",
|
|
211657
|
+
pub: "bun publish.ts"
|
|
211658
|
+
},
|
|
211659
|
+
dependencies: {
|
|
211660
|
+
archiver: "^7.0.1",
|
|
211661
|
+
picocolors: "^1.1.1",
|
|
211662
|
+
playcademy: "workspace:*"
|
|
211663
|
+
},
|
|
211664
|
+
devDependencies: {
|
|
211665
|
+
"@inquirer/prompts": "^7.8.6",
|
|
211666
|
+
"@playcademy/sandbox": "workspace:*",
|
|
211667
|
+
"@types/archiver": "^6.0.3",
|
|
211668
|
+
"@types/bun": "latest"
|
|
211669
|
+
},
|
|
211670
|
+
peerDependencies: {
|
|
211671
|
+
typescript: "^5",
|
|
211672
|
+
vite: "^5 || ^6"
|
|
211673
|
+
}
|
|
211674
|
+
};
|
|
211675
|
+
|
|
211676
|
+
// src/lib/backend/server.ts
|
|
211677
|
+
import {
|
|
211678
|
+
loadPlaycademyConfigAndSetWorkspace as loadConfigAndSetWorkspace,
|
|
211679
|
+
startPlaycademyDevServer,
|
|
211680
|
+
startPlaycademyHotReload
|
|
211681
|
+
} from "playcademy/utils";
|
|
211682
|
+
|
|
211683
|
+
// src/lib/backend/hot-reload.ts
|
|
211684
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
211685
|
+
function formatChangedPath(changedPath) {
|
|
211686
|
+
if (!changedPath)
|
|
211687
|
+
return;
|
|
211688
|
+
if (changedPath.includes("/api/")) {
|
|
211689
|
+
return changedPath.substring(changedPath.indexOf("/api/"));
|
|
211690
|
+
}
|
|
211691
|
+
return changedPath;
|
|
211692
|
+
}
|
|
211693
|
+
function createHotReloadCallbacks(viteConfig) {
|
|
211694
|
+
return {
|
|
211695
|
+
onSuccess: (changedPath) => {
|
|
211696
|
+
const relativePath = formatChangedPath(changedPath);
|
|
211697
|
+
if (relativePath) {
|
|
211698
|
+
viteConfig.logger.info(`${import_picocolors8.dim("(backend)")} ${import_picocolors8.green("hmr update")} ${import_picocolors8.dim(relativePath)}`, { timestamp: true });
|
|
211699
|
+
} else {
|
|
211700
|
+
viteConfig.logger.info("backend reloaded", { timestamp: true });
|
|
211701
|
+
}
|
|
211702
|
+
},
|
|
211703
|
+
onError: (error2) => {
|
|
211704
|
+
viteConfig.logger.error(`backend reload failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211705
|
+
}
|
|
211706
|
+
};
|
|
211707
|
+
}
|
|
211708
|
+
|
|
211709
|
+
// src/lib/backend/server.ts
|
|
211710
|
+
async function tryLoadConfig(viteConfig, configPath) {
|
|
211711
|
+
try {
|
|
211712
|
+
return await loadConfigAndSetWorkspace(configPath);
|
|
211713
|
+
} catch (error2) {
|
|
211714
|
+
if (error2 instanceof Error && !error2.message.includes("Could not find")) {
|
|
211715
|
+
viteConfig.logger.warn(`Could not load playcademy.config.js: ${error2.message}`);
|
|
211716
|
+
}
|
|
211717
|
+
return null;
|
|
211718
|
+
}
|
|
211719
|
+
}
|
|
211720
|
+
function needsCliDevServer(config2) {
|
|
211721
|
+
return !!config2.integrations;
|
|
211722
|
+
}
|
|
211723
|
+
async function startServer2(options) {
|
|
211724
|
+
const { port, config: config2, platformUrl } = options;
|
|
211725
|
+
return startPlaycademyDevServer({
|
|
211726
|
+
port,
|
|
211727
|
+
config: config2,
|
|
211728
|
+
quiet: true,
|
|
211729
|
+
platformUrl,
|
|
211730
|
+
customLogger: createLoggerAdapter("backend")
|
|
211731
|
+
});
|
|
211732
|
+
}
|
|
211733
|
+
function setupHotReload(serverRef, options) {
|
|
211734
|
+
const watcher = startPlaycademyHotReload(async () => {
|
|
211735
|
+
await serverRef.current.server.dispose();
|
|
211736
|
+
serverRef.current = await startServer2(options);
|
|
211737
|
+
}, createHotReloadCallbacks(options.viteConfig));
|
|
211738
|
+
return () => watcher.close();
|
|
211739
|
+
}
|
|
211740
|
+
async function setupCliDevServer(options) {
|
|
211741
|
+
const { preferredPort, viteConfig, platformUrl, configPath } = options;
|
|
211742
|
+
const config2 = await tryLoadConfig(viteConfig, configPath);
|
|
211743
|
+
if (!config2)
|
|
211744
|
+
return null;
|
|
211745
|
+
if (!needsCliDevServer(config2))
|
|
211746
|
+
return null;
|
|
211747
|
+
try {
|
|
211748
|
+
const port = await findAvailablePort(preferredPort);
|
|
211749
|
+
const serverOptions = { port, config: config2, platformUrl, viteConfig };
|
|
211750
|
+
const serverRef = { current: await startServer2(serverOptions) };
|
|
211751
|
+
const stopHotReload = setupHotReload(serverRef, serverOptions);
|
|
211752
|
+
return {
|
|
211753
|
+
server: serverRef.current.server,
|
|
211754
|
+
port: serverRef.current.port,
|
|
211755
|
+
stopHotReload,
|
|
211756
|
+
cleanup: () => cleanupServerInfo("backend", viteConfig.root, process.pid)
|
|
211757
|
+
};
|
|
211758
|
+
} catch (error2) {
|
|
211759
|
+
viteConfig.logger.error(`Failed to start game backend: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211760
|
+
return null;
|
|
211761
|
+
}
|
|
211762
|
+
}
|
|
211645
211763
|
// src/server/platform-mode.ts
|
|
211646
211764
|
async function configurePlatformMode(server, viteConfig, options) {
|
|
211647
211765
|
const sandbox = await startSandbox(viteConfig, options.startSandbox, {
|
|
@@ -211654,7 +211772,9 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211654
211772
|
memoryOnly: options.memoryOnly,
|
|
211655
211773
|
databasePath: options.databasePath,
|
|
211656
211774
|
realtimeEnabled: options.realtimeEnabled,
|
|
211657
|
-
realtimePort: options.realtimePort
|
|
211775
|
+
realtimePort: options.realtimePort,
|
|
211776
|
+
timebackId: getEffectiveTimebackId(options.timeback),
|
|
211777
|
+
timebackOptions: options.timeback
|
|
211658
211778
|
});
|
|
211659
211779
|
serverState.sandbox = sandbox;
|
|
211660
211780
|
const backend = await setupCliDevServer({
|
|
@@ -211665,25 +211785,29 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
211665
211785
|
});
|
|
211666
211786
|
serverState.backend = backend;
|
|
211667
211787
|
if (sandbox.project) {
|
|
211788
|
+
validateTimebackConfig(viteConfig, options.timeback);
|
|
211668
211789
|
const gameUrl = backend ? `http://localhost:${backend.port}` : undefined;
|
|
211669
|
-
devServerMiddleware(server, sandbox, gameUrl,
|
|
211790
|
+
devServerMiddleware(server, sandbox, gameUrl, {
|
|
211791
|
+
showBadge: options.showBadge,
|
|
211792
|
+
timeback: options.timeback
|
|
211793
|
+
});
|
|
211670
211794
|
}
|
|
211671
211795
|
server.httpServer?.once("listening", () => {
|
|
211672
211796
|
setTimeout(async () => {
|
|
211673
|
-
const projectInfo = await extractProjectInfo(viteConfig);
|
|
211797
|
+
const projectInfo = await extractProjectInfo(viteConfig, options.timeback);
|
|
211674
211798
|
const vitePort = server.config.server.port;
|
|
211675
211799
|
printBanner(viteConfig, {
|
|
211676
211800
|
sandbox: sandbox.port,
|
|
211677
211801
|
backend: backend?.port,
|
|
211678
211802
|
realtime: sandbox.realtimePort,
|
|
211679
211803
|
vite: vitePort
|
|
211680
|
-
}, projectInfo,
|
|
211804
|
+
}, projectInfo, package_default2.version);
|
|
211681
211805
|
}, 100);
|
|
211682
211806
|
});
|
|
211683
211807
|
}
|
|
211684
211808
|
|
|
211685
211809
|
// src/server/standalone-mode.ts
|
|
211686
|
-
var
|
|
211810
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
211687
211811
|
async function configureStandaloneMode(server, viteConfig, options) {
|
|
211688
211812
|
const backend = await setupCliDevServer({
|
|
211689
211813
|
preferredPort: options.preferredPort,
|
|
@@ -211699,32 +211823,32 @@ async function configureStandaloneMode(server, viteConfig, options) {
|
|
|
211699
211823
|
server.httpServer?.once("listening", () => {
|
|
211700
211824
|
setTimeout(() => {
|
|
211701
211825
|
viteConfig.logger.info("");
|
|
211702
|
-
viteConfig.logger.info(` ${
|
|
211826
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green(import_picocolors9.default.bold("PLAYCADEMY"))} ${import_picocolors9.default.green(`v${package_default2.version}`)}`);
|
|
211703
211827
|
viteConfig.logger.info("");
|
|
211704
|
-
viteConfig.logger.info(` ${
|
|
211705
|
-
viteConfig.logger.info(` ${
|
|
211828
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green("➜")} ${import_picocolors9.default.bold("Backend:")} ${import_picocolors9.default.cyan(`http://localhost:${backend.port}`)}`);
|
|
211829
|
+
viteConfig.logger.info(` ${import_picocolors9.default.green("➜")} ${import_picocolors9.default.bold("Sandbox:")} ${import_picocolors9.default.cyan("Disabled")}`);
|
|
211706
211830
|
viteConfig.logger.info("");
|
|
211707
211831
|
}, 100);
|
|
211708
211832
|
});
|
|
211709
211833
|
}
|
|
211710
211834
|
|
|
211711
|
-
// src/server/mode
|
|
211712
|
-
var { bold:
|
|
211713
|
-
function
|
|
211835
|
+
// src/server/hotkeys/toggle-mode.ts
|
|
211836
|
+
var { bold: bold5, cyan: cyan4, dim: dim6, green: green4, red: red2 } = import_picocolors10.default;
|
|
211837
|
+
function formatTimestamp4() {
|
|
211714
211838
|
const now2 = new Date;
|
|
211715
211839
|
const hours = now2.getHours();
|
|
211716
211840
|
const minutes = now2.getMinutes().toString().padStart(2, "0");
|
|
211717
211841
|
const seconds = now2.getSeconds().toString().padStart(2, "0");
|
|
211718
211842
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
211719
211843
|
const displayHours = hours % 12 || 12;
|
|
211720
|
-
return
|
|
211844
|
+
return dim6(`${displayHours}:${minutes}:${seconds} ${ampm}`);
|
|
211721
211845
|
}
|
|
211722
211846
|
async function toggleMode(options) {
|
|
211723
211847
|
const currentMode = getCurrentMode();
|
|
211724
211848
|
const newMode = currentMode === "platform" ? "standalone" : "platform";
|
|
211725
211849
|
const viteServer = getViteServerRef();
|
|
211726
211850
|
if (!viteServer) {
|
|
211727
|
-
options.viteConfig.logger.error(`${
|
|
211851
|
+
options.viteConfig.logger.error(`${formatTimestamp4()} ${red2(bold5("[playcademy]"))} ${red2("Cannot toggle mode: no Vite server reference")}`);
|
|
211728
211852
|
return;
|
|
211729
211853
|
}
|
|
211730
211854
|
await cleanupServers();
|
|
@@ -211738,56 +211862,39 @@ async function toggleMode(options) {
|
|
|
211738
211862
|
} else {
|
|
211739
211863
|
await configurePlatformMode(viteServer, options.viteConfig, options.platformModeOptions);
|
|
211740
211864
|
}
|
|
211741
|
-
options.viteConfig.logger.info(`${
|
|
211865
|
+
options.viteConfig.logger.info(`${formatTimestamp4()} ${cyan4(bold5("[playcademy]"))} ${green4("switched to")} ${green4(bold5(newMode))} ${green4("mode")}`);
|
|
211742
211866
|
}
|
|
211867
|
+
var toggleModeHotkey = (options) => ({
|
|
211868
|
+
key: "m",
|
|
211869
|
+
description: "toggle platform/standalone mode",
|
|
211870
|
+
action: () => toggleMode(options)
|
|
211871
|
+
});
|
|
211743
211872
|
|
|
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}`);
|
|
211873
|
+
// src/server/hotkeys/index.ts
|
|
211874
|
+
function getHotkeys(options) {
|
|
211875
|
+
return [
|
|
211876
|
+
toggleModeHotkey(options),
|
|
211877
|
+
recreateDatabaseHotkey(options),
|
|
211878
|
+
cycleTimebackRoleHotkey(options)
|
|
211879
|
+
];
|
|
211755
211880
|
}
|
|
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)")}`);
|
|
211881
|
+
|
|
211882
|
+
// src/server/lifecycle.ts
|
|
211883
|
+
var shutdownHandlersRegistered = false;
|
|
211884
|
+
function setupProcessShutdownHandlers() {
|
|
211885
|
+
if (shutdownHandlersRegistered)
|
|
211765
211886
|
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")}`);
|
|
211887
|
+
shutdownHandlersRegistered = true;
|
|
211888
|
+
let isShuttingDown = false;
|
|
211889
|
+
const shutdown = async () => {
|
|
211890
|
+
if (isShuttingDown)
|
|
211891
|
+
return;
|
|
211892
|
+
isShuttingDown = true;
|
|
211893
|
+
await cleanupServers();
|
|
211894
|
+
process.exit(0);
|
|
211895
|
+
};
|
|
211896
|
+
process.on("SIGINT", shutdown);
|
|
211897
|
+
process.on("SIGTERM", shutdown);
|
|
211791
211898
|
}
|
|
211792
211899
|
|
|
211793
211900
|
// src/hooks/configure-server.ts
|
|
@@ -211816,7 +211923,8 @@ async function configureServerHook(server, context) {
|
|
|
211816
211923
|
realtimePort: context.options.realtimePort,
|
|
211817
211924
|
showBadge: context.options.showBadge,
|
|
211818
211925
|
preferredBackendPort: preferredPort,
|
|
211819
|
-
configPath: context.options.configPath
|
|
211926
|
+
configPath: context.options.configPath,
|
|
211927
|
+
timeback: context.options.timeback
|
|
211820
211928
|
};
|
|
211821
211929
|
if (context.options.mode === "standalone") {
|
|
211822
211930
|
await configureStandaloneMode(server, context.viteConfig, {
|
|
@@ -211833,26 +211941,10 @@ async function configureServerHook(server, context) {
|
|
|
211833
211941
|
...options,
|
|
211834
211942
|
customShortcuts: [
|
|
211835
211943
|
...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
|
-
}
|
|
211944
|
+
...getHotkeys({
|
|
211945
|
+
viteConfig: context.viteConfig,
|
|
211946
|
+
platformModeOptions
|
|
211947
|
+
})
|
|
211856
211948
|
]
|
|
211857
211949
|
});
|
|
211858
211950
|
};
|
|
@@ -211900,7 +211992,8 @@ function resolveOptions(options) {
|
|
|
211900
211992
|
databasePath: sandboxOptions.databasePath,
|
|
211901
211993
|
realtimeEnabled: realtimeOptions.enabled ?? false,
|
|
211902
211994
|
realtimePort: realtimeOptions.port,
|
|
211903
|
-
showBadge: shellOptions.showBadge ?? true
|
|
211995
|
+
showBadge: shellOptions.showBadge ?? true,
|
|
211996
|
+
timeback: options.timeback
|
|
211904
211997
|
};
|
|
211905
211998
|
}
|
|
211906
211999
|
function createPluginContext(options) {
|