playcademy 0.14.13 → 0.14.14-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.js CHANGED
@@ -1708,7 +1708,19 @@ init_file_loader();
1708
1708
  import { dirname as dirname2, resolve as resolve2 } from "path";
1709
1709
 
1710
1710
  // src/constants/api.ts
1711
- var DEFAULT_API_ROUTES_DIRECTORY = "server/api";
1711
+ import { join as join2 } from "node:path";
1712
+
1713
+ // src/constants/server.ts
1714
+ import { join } from "node:path";
1715
+ var SERVER_ROOT_DIRECTORY = "server";
1716
+ var SERVER_LIB_DIRECTORY = join(SERVER_ROOT_DIRECTORY, "lib");
1717
+ var PUBLIC_DIRECTORY = "public";
1718
+ var ACADEMICS_PUBLIC_DIRECTORY = join(PUBLIC_DIRECTORY, "academics");
1719
+ var PROBLEM_SETS_DIRECTORY = join(ACADEMICS_PUBLIC_DIRECTORY, "problem-sets");
1720
+ var PROBLEM_SETS_URL_PATH = "academics/problem-sets";
1721
+
1722
+ // src/constants/api.ts
1723
+ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
1712
1724
 
1713
1725
  // ../../package.json
1714
1726
  var package_default = {
@@ -1757,9 +1769,11 @@ var package_default = {
1757
1769
  "sync-engine-assets": "bun scripts/sync-engine-assets.ts",
1758
1770
  "sync-vite-templates": "bun scripts/sync-vite-templates.ts",
1759
1771
  "sync-godot-template": "bun scripts/sync-godot-template.ts",
1772
+ "sync-problem-sets": "sst shell -- bun scripts/sync-problem-sets.ts",
1760
1773
  "sync:all": "bun scripts/sync-all.ts",
1761
1774
  cf: "sst shell -- bun scripts/setup-cloudflare-dispatch.ts",
1762
1775
  "list-s3-bucket": "sst shell -- bun scripts/list-s3-bucket.ts",
1776
+ "invalidate-cdn": "sst shell -- bun scripts/invalidate-cdn.ts",
1763
1777
  doctor: "bunx sst shell -- bun scripts/doctor.ts",
1764
1778
  format: "bun run --filter '*' format",
1765
1779
  lint: "bun run --filter '*' lint",
@@ -1849,24 +1863,50 @@ var CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
1849
1863
  var CLOUDFLARE_BINDINGS = {
1850
1864
  /** R2 bucket binding name */
1851
1865
  BUCKET: "BUCKET",
1866
+ /** Problem sets bucket binding name */
1867
+ BUCKET_PROBLEM_SETS: "PROBLEM_SETS",
1852
1868
  /** KV namespace binding name */
1853
1869
  KV: "KV",
1854
1870
  /** D1 database binding name */
1855
1871
  DB: "DB"
1856
1872
  };
1857
1873
 
1874
+ // src/constants/database.ts
1875
+ import { join as join3 } from "path";
1876
+ var DEFAULT_DATABASE_DIRECTORY = join3("server", "db");
1877
+ var DRIZZLE_CONFIG_FILES = ["drizzle.config.ts", "drizzle.config.js"];
1878
+
1879
+ // src/constants/godot.ts
1880
+ import { join as join4 } from "node:path";
1881
+ var GODOT_BUILD_DIRECTORIES = {
1882
+ /** Root build directory (cleared before each export) */
1883
+ ROOT: "build",
1884
+ /** Web export subdirectory */
1885
+ WEB: join4("build", "web")
1886
+ };
1887
+ var GODOT_BUILD_OUTPUTS = {
1888
+ /** Exported web build entry point */
1889
+ INDEX_HTML: join4("build", "web", "index.html"),
1890
+ /** Packaged zip file (created by Godot export) */
1891
+ ZIP: join4("build", "web_playcademy.zip")
1892
+ };
1893
+
1858
1894
  // src/constants/paths.ts
1859
- import { join } from "path";
1895
+ import { join as join5 } from "path";
1860
1896
  var WORKSPACE_NAME = ".playcademy";
1861
1897
  var CLI_DIRECTORIES = {
1862
1898
  /** Root directory for CLI artifacts in workspace */
1863
1899
  WORKSPACE: WORKSPACE_NAME,
1864
1900
  /** Database directory within workspace */
1865
- DATABASE: join(WORKSPACE_NAME, "db"),
1901
+ DATABASE: join5(WORKSPACE_NAME, "db"),
1866
1902
  /** KV storage directory within workspace */
1867
- KV: join(WORKSPACE_NAME, "kv"),
1903
+ KV: join5(WORKSPACE_NAME, "kv"),
1868
1904
  /** Bucket storage directory within workspace */
1869
- BUCKET: join(WORKSPACE_NAME, "bucket")
1905
+ BUCKET: join5(WORKSPACE_NAME, "bucket")
1906
+ };
1907
+ var CLI_DEFAULT_OUTPUTS = {
1908
+ /** Default worker bundle output for debug command */
1909
+ WORKER_BUNDLE: join5(WORKSPACE_NAME, "worker-bundle.js")
1870
1910
  };
1871
1911
 
1872
1912
  // src/constants/ports.ts
@@ -1884,6 +1924,15 @@ var CONFIG_FILE_NAMES = [
1884
1924
  "playcademy.config.mjs"
1885
1925
  ];
1886
1926
 
1927
+ // ../constants/src/domains.ts
1928
+ var APEX_DOMAIN = "playcademy.net";
1929
+ var CDN_DOMAINS = {
1930
+ /** Production CDN: https://cdn.playcademy.net */
1931
+ production: `https://cdn.${APEX_DOMAIN}`,
1932
+ /** Development CDN: https://cdn.dev.playcademy.net */
1933
+ staging: `https://cdn.dev.${APEX_DOMAIN}`
1934
+ };
1935
+
1887
1936
  // ../constants/src/overworld.ts
1888
1937
  var ITEM_SLUGS = {
1889
1938
  /** Primary platform currency */
@@ -2987,15 +3036,16 @@ function processConfigVariables(config) {
2987
3036
  }
2988
3037
 
2989
3038
  // src/lib/dev/server.ts
3039
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2 } from "fs";
2990
3040
  import { mkdir as mkdir2 } from "fs/promises";
2991
- import { join as join12 } from "path";
3041
+ import { join as join16 } from "path";
2992
3042
  import { Log, LogLevel, Miniflare } from "miniflare";
2993
3043
 
2994
3044
  // ../utils/src/port.ts
2995
3045
  import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2996
3046
  import { createServer } from "node:net";
2997
3047
  import { homedir } from "node:os";
2998
- import { join as join2 } from "node:path";
3048
+ import { join as join6 } from "node:path";
2999
3049
  async function isPortAvailableOnHost(port, host) {
3000
3050
  return new Promise((resolve4) => {
3001
3051
  const server = createServer();
@@ -3034,11 +3084,11 @@ async function findAvailablePort(startPort = 4321) {
3034
3084
  }
3035
3085
  function getRegistryPath() {
3036
3086
  const home = homedir();
3037
- const dir = join2(home, ".playcademy");
3087
+ const dir = join6(home, ".playcademy");
3038
3088
  if (!existsSync2(dir)) {
3039
3089
  mkdirSync(dir, { recursive: true });
3040
3090
  }
3041
- return join2(dir, ".proc");
3091
+ return join6(dir, ".proc");
3042
3092
  }
3043
3093
  function readRegistry() {
3044
3094
  const registryPath = getRegistryPath();
@@ -3084,7 +3134,7 @@ import { PlaycademyClient } from "@playcademy/sdk";
3084
3134
  // ../utils/src/package-manager.ts
3085
3135
  import { execSync } from "child_process";
3086
3136
  import { existsSync as existsSync3 } from "fs";
3087
- import { join as join3 } from "path";
3137
+ import { join as join7 } from "path";
3088
3138
  function isCommandAvailable(command) {
3089
3139
  try {
3090
3140
  execSync(`command -v ${command}`, { stdio: "ignore" });
@@ -3094,16 +3144,16 @@ function isCommandAvailable(command) {
3094
3144
  }
3095
3145
  }
3096
3146
  function detectPackageManager(cwd = process.cwd()) {
3097
- if (existsSync3(join3(cwd, "bun.lock")) || existsSync3(join3(cwd, "bun.lockb"))) {
3147
+ if (existsSync3(join7(cwd, "bun.lock")) || existsSync3(join7(cwd, "bun.lockb"))) {
3098
3148
  return "bun";
3099
3149
  }
3100
- if (existsSync3(join3(cwd, "pnpm-lock.yaml"))) {
3150
+ if (existsSync3(join7(cwd, "pnpm-lock.yaml"))) {
3101
3151
  return "pnpm";
3102
3152
  }
3103
- if (existsSync3(join3(cwd, "yarn.lock"))) {
3153
+ if (existsSync3(join7(cwd, "yarn.lock"))) {
3104
3154
  return "yarn";
3105
3155
  }
3106
- if (existsSync3(join3(cwd, "package-lock.json"))) {
3156
+ if (existsSync3(join7(cwd, "package-lock.json"))) {
3107
3157
  return "npm";
3108
3158
  }
3109
3159
  return detectByCommandAvailability();
@@ -3180,7 +3230,7 @@ init_package_json();
3180
3230
  // src/lib/secrets/env.ts
3181
3231
  init_file_loader();
3182
3232
  import { existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
3183
- import { join as join4 } from "path";
3233
+ import { join as join8 } from "path";
3184
3234
  function parseEnvFile(contents) {
3185
3235
  const secrets = {};
3186
3236
  for (const line of contents.split("\n")) {
@@ -3216,10 +3266,10 @@ async function readEnvFile(workspace) {
3216
3266
  return secrets;
3217
3267
  }
3218
3268
  function getLoadedEnvFiles(workspace) {
3219
- return ENV_FILES.filter((filename) => existsSync4(join4(workspace, filename)));
3269
+ return ENV_FILES.filter((filename) => existsSync4(join8(workspace, filename)));
3220
3270
  }
3221
3271
  function hasEnvFile(workspace) {
3222
- return ENV_FILES.some((filename) => existsSync4(join4(workspace, filename)));
3272
+ return ENV_FILES.some((filename) => existsSync4(join8(workspace, filename)));
3223
3273
  }
3224
3274
 
3225
3275
  // src/lib/templates/loader.ts
@@ -3246,12 +3296,12 @@ function loadTemplateString(filename) {
3246
3296
  // src/lib/core/import.ts
3247
3297
  import { mkdtempSync, rmSync } from "fs";
3248
3298
  import { tmpdir } from "os";
3249
- import { join as join5 } from "path";
3299
+ import { join as join9 } from "path";
3250
3300
  import { pathToFileURL } from "url";
3251
3301
  import * as esbuild from "esbuild";
3252
3302
  async function importTypescriptFile(filePath, bundleOptions) {
3253
- const tempDir = mkdtempSync(join5(tmpdir(), "playcademy-import-"));
3254
- const outFile = join5(tempDir, "bundle.mjs");
3303
+ const tempDir = mkdtempSync(join9(tmpdir(), "playcademy-import-"));
3304
+ const outFile = join9(tempDir, "bundle.mjs");
3255
3305
  try {
3256
3306
  await esbuild.build({
3257
3307
  entryPoints: [filePath],
@@ -3280,10 +3330,10 @@ async function importTypescriptDefault(filePath, bundleOptions) {
3280
3330
 
3281
3331
  // src/lib/deploy/bundle.ts
3282
3332
  import { existsSync as existsSync6 } from "fs";
3283
- import { join as join7 } from "path";
3333
+ import { join as join11 } from "path";
3284
3334
 
3285
3335
  // ../edge-play/src/entry.ts
3286
- var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n *\n * DO NOT REMOVE any code wrapped by \u26A0\uFE0F BUILD_MARKER: <marker> \u26A0\uFE0F\n */\n\nimport { Hono } from 'hono'\n\nimport {\n registerApiNotFoundHandler,\n registerAssetFallback,\n registerCors,\n registerEnvSetup,\n registerPlaycademyUser,\n registerSdkInit,\n} from './entry/middleware'\nimport { setupProcessGlobal } from './entry/setup'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { RuntimeConfig } from './entry/types'\nimport type { HonoEnv } from './types'\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: CUSTOM_ROUTE_IMPORTS \u26A0\uFE0F\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: RuntimeConfig\n\n// Setup process global polyfill for SDK compatibility\nsetupProcessGlobal()\n\n// Create Hono app\nconst app = new Hono<HonoEnv>()\n\n// Register middleware\nregisterCors(app)\nregisterEnvSetup(app, PLAYCADEMY_CONFIG)\nregisterSdkInit(app, PLAYCADEMY_CONFIG)\nregisterPlaycademyUser(app)\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: SESSION_MIDDLEWARE \u26A0\uFE0F\n\n// Register built-in integration routes based on enabled integrations\n// This function conditionally imports and registers routes like:\n// - GET /api (always included)\n// - GET /api/health (always included)\n// - POST /api/integrations/timeback/end-activity (if timeback enabled)\n//\n// Uses dynamic imports for tree-shaking: if an integration is not enabled,\n// its route code is completely removed from the bundle.\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: CUSTOM_ROUTES \u26A0\uFE0F\n\n// Register API 404 handler\n// Returns JSON error for unmatched /api/* routes\n// Must be registered after all API routes\nregisterApiNotFoundHandler(app)\n\n// Register static asset fallback handler\n// Serves frontend assets from Workers Assets binding\n// MUST be registered last as it uses a wildcard GET route (app.get('*', ...))\n//\n// In production: Serves frontend assets from Workers Assets binding\n// In local dev: Returns 404 (Vite serves the frontend separately)\nregisterAssetFallback(app)\n\nexport default app\n";
3336
+ var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n *\n * DO NOT REMOVE any code wrapped by \u26A0\uFE0F BUILD_MARKER: <marker> \u26A0\uFE0F\n */\n\nimport { Hono } from 'hono'\n\nimport {\n registerApiNotFoundHandler,\n registerAssetFallback,\n registerCors,\n registerEnvSetup,\n registerLocalProblemSets,\n registerPlaycademyUser,\n registerSdkInit,\n} from './entry/middleware'\nimport { setupProcessGlobal } from './entry/setup'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { RuntimeConfig } from './entry/types'\nimport type { HonoEnv } from './types'\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: CUSTOM_ROUTE_IMPORTS \u26A0\uFE0F\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: RuntimeConfig\n\n// Setup process global polyfill for SDK compatibility\nsetupProcessGlobal()\n\n// Create Hono app\nconst app = new Hono<HonoEnv>()\n\n// Register middleware\nregisterCors(app)\nregisterEnvSetup(app, PLAYCADEMY_CONFIG)\nregisterSdkInit(app, PLAYCADEMY_CONFIG)\nregisterPlaycademyUser(app)\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: SESSION_MIDDLEWARE \u26A0\uFE0F\n\n// Register built-in integration routes based on enabled integrations\n// This function conditionally imports and registers routes like:\n// - GET /api (always included)\n// - GET /api/health (always included)\n// - POST /api/integrations/timeback/end-activity (if timeback enabled)\n//\n// Uses dynamic imports for tree-shaking: if an integration is not enabled,\n// its route code is completely removed from the bundle.\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\n// DO NOT REMOVE THE BELOW COMMENT\n// \u26A0\uFE0F BUILD_MARKER: CUSTOM_ROUTES \u26A0\uFE0F\n\n// Register local problem sets (dev only)\n// Serves .playcademy/problems/* from environment bindings in local development\n// No-op in production (bindings not injected)\nregisterLocalProblemSets(app)\n\n// Register API 404 handler\n// Returns JSON error for unmatched /api/* routes\n// Must be registered after all API routes\nregisterApiNotFoundHandler(app)\n\n// Register static asset fallback handler\n// Serves frontend assets from Workers Assets binding\n// MUST be registered last as it uses a wildcard GET route (app.get('*', ...))\n//\n// In production: Serves frontend assets from Workers Assets binding\n// In local dev: Returns 404 (Vite serves the frontend separately)\nregisterAssetFallback(app)\n\nexport default app\n";
3287
3337
 
3288
3338
  // ../utils/src/path.ts
3289
3339
  import fs from "node:fs";
@@ -3553,7 +3603,7 @@ function textLoaderPlugin() {
3553
3603
  init_file_loader();
3554
3604
  import { mkdir, writeFile } from "fs/promises";
3555
3605
  import { tmpdir as tmpdir2 } from "os";
3556
- import { join as join6, relative } from "path";
3606
+ import { join as join10, relative } from "path";
3557
3607
 
3558
3608
  // src/lib/deploy/hash.ts
3559
3609
  import { createHash } from "crypto";
@@ -3572,7 +3622,7 @@ async function discoverRoutes(apiDir) {
3572
3622
  const routes = await Promise.all(
3573
3623
  files.map(async (file) => {
3574
3624
  const routePath = filePathToRoutePath(file);
3575
- const absolutePath = join6(apiDir, file);
3625
+ const absolutePath = join10(apiDir, file);
3576
3626
  const relativePath = relative(getWorkspace(), absolutePath);
3577
3627
  const methods = await detectExportedMethods(absolutePath);
3578
3628
  return {
@@ -3633,10 +3683,10 @@ async function transpileRoute(filePath) {
3633
3683
  if (!result.outputFiles?.[0]) {
3634
3684
  throw new Error("Transpilation failed: no output");
3635
3685
  }
3636
- const tempDir = join6(tmpdir2(), "playcademy-dev");
3686
+ const tempDir = join10(tmpdir2(), "playcademy-dev");
3637
3687
  await mkdir(tempDir, { recursive: true });
3638
3688
  const hash = hashContent(filePath).slice(0, 12);
3639
- const jsPath = join6(tempDir, `${hash}.mjs`);
3689
+ const jsPath = join10(tempDir, `${hash}.mjs`);
3640
3690
  await writeFile(jsPath, result.outputFiles[0].text);
3641
3691
  return jsPath;
3642
3692
  }
@@ -3647,7 +3697,7 @@ async function discoverCustomRoutes(config) {
3647
3697
  const workspace = getWorkspace();
3648
3698
  const customRoutesConfig = config.integrations?.customRoutes;
3649
3699
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
3650
- const customRoutes = await discoverRoutes(join7(workspace, customRoutesDir));
3700
+ const customRoutes = await discoverRoutes(join11(workspace, customRoutesDir));
3651
3701
  const customRouteData = customRoutes.map((r) => ({
3652
3702
  path: r.path,
3653
3703
  file: r.file,
@@ -3659,15 +3709,15 @@ async function discoverCustomRoutes(config) {
3659
3709
  function resolveEmbeddedSourcePaths() {
3660
3710
  const workspace = getWorkspace();
3661
3711
  const distDir = new URL(".", import.meta.url).pathname;
3662
- const embeddedEdgeSrc = join7(distDir, "edge-play", "src");
3712
+ const embeddedEdgeSrc = join11(distDir, "edge-play", "src");
3663
3713
  const isBuiltPackage = existsSync6(embeddedEdgeSrc);
3664
3714
  const monorepoRoot = getMonorepoRoot();
3665
- const monorepoEdgeSrc = join7(monorepoRoot, "packages/edge-play/src");
3715
+ const monorepoEdgeSrc = join11(monorepoRoot, "packages/edge-play/src");
3666
3716
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
3667
- const cliPackageRoot = isBuiltPackage ? join7(distDir, "../../..") : join7(monorepoRoot, "packages/cli");
3668
- const cliNodeModules = isBuiltPackage ? join7(cliPackageRoot, "node_modules") : monorepoRoot;
3669
- const workspaceNodeModules = join7(workspace, "node_modules");
3670
- const constantsEntry = isBuiltPackage ? join7(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join7(monorepoRoot, "packages", "constants", "src", "index.ts");
3717
+ const cliPackageRoot = isBuiltPackage ? join11(distDir, "../../..") : join11(monorepoRoot, "packages/cli");
3718
+ const cliNodeModules = isBuiltPackage ? join11(cliPackageRoot, "node_modules") : monorepoRoot;
3719
+ const workspaceNodeModules = join11(workspace, "node_modules");
3720
+ const constantsEntry = isBuiltPackage ? join11(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join11(monorepoRoot, "packages", "constants", "src", "index.ts");
3671
3721
  return {
3672
3722
  isBuiltPackage,
3673
3723
  edgePlaySrc,
@@ -3732,22 +3782,22 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
3732
3782
  // │ Example: import * as route from '@game-api/hello.ts' │
3733
3783
  // │ Resolves to: /user-project/server/api/hello.ts │
3734
3784
  // └─────────────────────────────────────────────────────────────────┘
3735
- "@game-api": join7(workspace, customRoutesDir),
3785
+ "@game-api": join11(workspace, customRoutesDir),
3736
3786
  // ┌─ User's server lib directory ───────────────────────────────────┐
3737
3787
  // │ @game-server is a virtual module for server utilities/config │
3738
3788
  // │ Example: import { getAuth } from '@game-server/lib/auth' │
3739
3789
  // │ Resolves to: /user-project/server/lib/auth.ts │
3740
3790
  // └─────────────────────────────────────────────────────────────────┘
3741
- "@game-server": join7(workspace, "server"),
3791
+ "@game-server": join11(workspace, "server"),
3742
3792
  // ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
3743
3793
  // │ Workers don't have fs, path, os, etc. Redirect to polyfills │
3744
3794
  // │ that throw helpful errors if user code tries to use them. │
3745
3795
  // └─────────────────────────────────────────────────────────────────┘
3746
- fs: join7(edgePlaySrc, "polyfills.js"),
3747
- "fs/promises": join7(edgePlaySrc, "polyfills.js"),
3748
- path: join7(edgePlaySrc, "polyfills.js"),
3749
- os: join7(edgePlaySrc, "polyfills.js"),
3750
- process: join7(edgePlaySrc, "polyfills.js")
3796
+ fs: join11(edgePlaySrc, "polyfills.js"),
3797
+ "fs/promises": join11(edgePlaySrc, "polyfills.js"),
3798
+ path: join11(edgePlaySrc, "polyfills.js"),
3799
+ os: join11(edgePlaySrc, "polyfills.js"),
3800
+ process: join11(edgePlaySrc, "polyfills.js")
3751
3801
  },
3752
3802
  // ──── Build Plugins ────
3753
3803
  plugins: [textLoaderPlugin()],
@@ -3821,9 +3871,12 @@ function generateEntryCode(customRoutes, customRoutesDir, hasAuth) {
3821
3871
  }
3822
3872
 
3823
3873
  // src/lib/init/prompts.ts
3824
- import { checkbox, confirm, input, select } from "@inquirer/prompts";
3874
+ import { checkbox, confirm, input, select, Separator as Separator3 } from "@inquirer/prompts";
3825
3875
  import { bold as bold4, cyan as cyan2 } from "colorette";
3826
3876
 
3877
+ // src/lib/timeback/problem-sets.ts
3878
+ import { Separator as Separator2 } from "@inquirer/prompts";
3879
+
3827
3880
  // src/lib/init/auth.ts
3828
3881
  var authTemplate = loadTemplateString("auth/auth.ts");
3829
3882
  var authCatchAllTemplate = loadTemplateString("auth/auth-catch-all.ts");
@@ -3832,12 +3885,12 @@ var protectedRouteTemplate = loadTemplateString("api/sample-protected.ts");
3832
3885
 
3833
3886
  // src/lib/init/database.ts
3834
3887
  import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
3835
- import { join as join8 } from "path";
3888
+ import { join as join12 } from "path";
3836
3889
 
3837
3890
  // package.json
3838
3891
  var package_default2 = {
3839
3892
  name: "playcademy",
3840
- version: "0.14.12",
3893
+ version: "0.14.14",
3841
3894
  type: "module",
3842
3895
  exports: {
3843
3896
  ".": {
@@ -3916,8 +3969,7 @@ var version = package_default2.version;
3916
3969
 
3917
3970
  // src/lib/init/database.ts
3918
3971
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
3919
- var dbSchemaUsersTemplate = loadTemplateString("database/db-schema-users.ts");
3920
- var dbSchemaScoresTemplate = loadTemplateString("database/db-schema-scores.ts");
3972
+ var dbSchemaExampleTemplate = loadTemplateString("database/db-schema-example.ts");
3921
3973
  var dbSchemaIndexTemplate = loadTemplateString("database/db-schema-index.ts");
3922
3974
  var dbIndexTemplate = loadTemplateString("database/db-index.ts");
3923
3975
  var dbTypesTemplate = loadTemplateString("database/db-types.ts");
@@ -3925,9 +3977,7 @@ var dbSeedTemplate = loadTemplateString("database/db-seed.ts");
3925
3977
  var packageTemplate = loadTemplateString("database/package.json");
3926
3978
  function hasDatabaseSetup() {
3927
3979
  const workspace = getWorkspace();
3928
- const drizzleConfigPath = join8(workspace, "drizzle.config.ts");
3929
- const drizzleConfigJsPath = join8(workspace, "drizzle.config.js");
3930
- return existsSync7(drizzleConfigPath) || existsSync7(drizzleConfigJsPath);
3980
+ return DRIZZLE_CONFIG_FILES.some((filename) => existsSync7(join12(workspace, filename)));
3931
3981
  }
3932
3982
 
3933
3983
  // src/lib/init/scaffold.ts
@@ -3951,16 +4001,16 @@ function hasBucketSetup(config) {
3951
4001
  init_file_loader();
3952
4002
  import { execSync as execSync2 } from "child_process";
3953
4003
  import { existsSync as existsSync10, writeFileSync as writeFileSync5 } from "fs";
3954
- import { dirname as dirname4, join as join11 } from "path";
4004
+ import { dirname as dirname4, join as join15 } from "path";
3955
4005
  import { fileURLToPath as fileURLToPath2 } from "url";
3956
4006
 
3957
4007
  // src/lib/deploy/backend.ts
3958
4008
  import { existsSync as existsSync8 } from "node:fs";
3959
- import { join as join9 } from "node:path";
4009
+ import { join as join13 } from "node:path";
3960
4010
  function getCustomRoutesDirectory(projectPath, config) {
3961
4011
  const customRoutes = config?.integrations?.customRoutes;
3962
4012
  const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
3963
- return join9(projectPath, customRoutesDir);
4013
+ return join13(projectPath, customRoutesDir);
3964
4014
  }
3965
4015
  function hasLocalCustomRoutes(projectPath, config) {
3966
4016
  const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
@@ -3970,7 +4020,7 @@ function hasLocalCustomRoutes(projectPath, config) {
3970
4020
  // src/lib/init/tsconfig.ts
3971
4021
  init_file_loader();
3972
4022
  import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3973
- import { join as join10 } from "path";
4023
+ import { join as join14 } from "path";
3974
4024
  function hasPlaycademyEnv(config) {
3975
4025
  return config.include?.includes("playcademy-env.d.ts") ?? false;
3976
4026
  }
@@ -4027,7 +4077,7 @@ function addToIncludeArrayPreservingComments(content) {
4027
4077
  }
4028
4078
  async function ensureTsconfigIncludes(workspace) {
4029
4079
  for (const filename of TSCONFIG_FILES) {
4030
- const configPath = join10(workspace, filename);
4080
+ const configPath = join14(workspace, filename);
4031
4081
  if (!existsSync9(configPath)) {
4032
4082
  continue;
4033
4083
  }
@@ -4077,8 +4127,8 @@ function hasAnyBackend(features) {
4077
4127
  return Object.values(features).some(Boolean);
4078
4128
  }
4079
4129
  async function setupPlaycademyDependencies(workspace) {
4080
- const playcademyDir = join11(workspace, CLI_DIRECTORIES.WORKSPACE);
4081
- const playcademyPkgPath = join11(playcademyDir, "package.json");
4130
+ const playcademyDir = join15(workspace, CLI_DIRECTORIES.WORKSPACE);
4131
+ const playcademyPkgPath = join15(playcademyDir, "package.json");
4082
4132
  const __dirname = dirname4(fileURLToPath2(import.meta.url));
4083
4133
  const cliPkg = await loadPackageJson({ cwd: __dirname, searchUp: true, required: true });
4084
4134
  const workersTypesVersion = cliPkg?.devDependencies?.["@cloudflare/workers-types"] || "latest";
@@ -4163,14 +4213,14 @@ async function ensurePlaycademyTypes(options = {}) {
4163
4213
  const bindingsStr = generateBindingsTypeString(features);
4164
4214
  const secretsStr = await generateSecretsTypeString(workspace, verbose);
4165
4215
  const hasAuth = !!config.integrations?.auth;
4166
- const hasAuthFile = existsSync10(join11(workspace, "server/lib/auth.ts"));
4216
+ const hasAuthFile = existsSync10(join15(workspace, "server/lib/auth.ts"));
4167
4217
  const authVariablesString = generateAuthVariablesString(hasAuth, hasAuthFile);
4168
4218
  let envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
4169
4219
  envContent = envContent.replace("{{SECRETS}}", secretsStr);
4170
4220
  envContent = envContent.replace("{{AUTH_IMPORT}}", authVariablesString.authImport);
4171
4221
  envContent = envContent.replace("{{VARIABLES}}", authVariablesString.variables);
4172
4222
  envContent = envContent.replace("{{CONTEXT_VARS}}", authVariablesString.contextVars);
4173
- const envPath = join11(workspace, "playcademy-env.d.ts");
4223
+ const envPath = join15(workspace, "playcademy-env.d.ts");
4174
4224
  writeFileSync5(envPath, envContent);
4175
4225
  if (verbose) {
4176
4226
  logger.success(`Generated <playcademy-env.d.ts>`);
@@ -4231,8 +4281,9 @@ async function startDevServer(options) {
4231
4281
  const dbDir = hasDatabase ? await ensureDatabaseDirectory() : void 0;
4232
4282
  const hasKV = hasKVSetup(config);
4233
4283
  const kvDir = hasKV ? await ensureKvDirectory() : void 0;
4284
+ const bucketDir = await ensureBucketDirectory();
4234
4285
  const hasBucket = hasBucketSetup(config);
4235
- const bucketDir = hasBucket ? await ensureBucketDirectory() : void 0;
4286
+ clearProblemSetsBucket(bucketDir);
4236
4287
  const workspace = getWorkspace();
4237
4288
  const envSecrets = await readEnvFile(workspace);
4238
4289
  const log2 = logger2 ? new FilteredLog(LogLevel.INFO, customLogger) : new Log(LogLevel.NONE);
@@ -4261,7 +4312,10 @@ async function startDevServer(options) {
4261
4312
  d1Persist: dbDir,
4262
4313
  kvNamespaces: hasKV ? [CLOUDFLARE_BINDINGS.KV] : [],
4263
4314
  kvPersist: kvDir,
4264
- r2Buckets: hasBucket ? [CLOUDFLARE_BINDINGS.BUCKET] : [],
4315
+ r2Buckets: [
4316
+ ...hasBucket ? [CLOUDFLARE_BINDINGS.BUCKET] : [],
4317
+ CLOUDFLARE_BINDINGS.BUCKET_PROBLEM_SETS
4318
+ ],
4265
4319
  r2Persist: bucketDir,
4266
4320
  compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
4267
4321
  /**
@@ -4276,14 +4330,13 @@ async function startDevServer(options) {
4276
4330
  */
4277
4331
  compatibilityFlags: ["nodejs_compat"]
4278
4332
  });
4279
- if (hasDatabase) {
4280
- await initializeDatabase(mf);
4281
- }
4333
+ if (hasDatabase) await initializeDatabase(mf);
4334
+ await initializeProblemSets(mf);
4282
4335
  await writeBackendServerInfo(port);
4283
4336
  return { server: mf, port };
4284
4337
  }
4285
4338
  async function ensureDatabaseDirectory() {
4286
- const dbDir = join12(getWorkspace(), CLI_DIRECTORIES.DATABASE);
4339
+ const dbDir = join16(getWorkspace(), CLI_DIRECTORIES.DATABASE);
4287
4340
  try {
4288
4341
  await mkdir2(dbDir, { recursive: true });
4289
4342
  } catch (error) {
@@ -4292,7 +4345,7 @@ async function ensureDatabaseDirectory() {
4292
4345
  return dbDir;
4293
4346
  }
4294
4347
  async function ensureKvDirectory() {
4295
- const kvDir = join12(getWorkspace(), CLI_DIRECTORIES.KV);
4348
+ const kvDir = join16(getWorkspace(), CLI_DIRECTORIES.KV);
4296
4349
  try {
4297
4350
  await mkdir2(kvDir, { recursive: true });
4298
4351
  } catch (error) {
@@ -4301,7 +4354,7 @@ async function ensureKvDirectory() {
4301
4354
  return kvDir;
4302
4355
  }
4303
4356
  async function ensureBucketDirectory() {
4304
- const bucketDir = join12(getWorkspace(), CLI_DIRECTORIES.BUCKET);
4357
+ const bucketDir = join16(getWorkspace(), CLI_DIRECTORIES.BUCKET);
4305
4358
  try {
4306
4359
  await mkdir2(bucketDir, { recursive: true });
4307
4360
  } catch (error) {
@@ -4309,10 +4362,40 @@ async function ensureBucketDirectory() {
4309
4362
  }
4310
4363
  return bucketDir;
4311
4364
  }
4365
+ function clearProblemSetsBucket(bucketDir) {
4366
+ const problemSetsBucketDir = join16(bucketDir, "PROBLEM_SETS");
4367
+ if (existsSync11(problemSetsBucketDir)) {
4368
+ try {
4369
+ rmSync2(problemSetsBucketDir, { recursive: true, force: true });
4370
+ } catch {
4371
+ }
4372
+ }
4373
+ }
4312
4374
  async function initializeDatabase(mf) {
4313
4375
  const d1 = await mf.getD1Database(CLOUDFLARE_BINDINGS.DB);
4314
4376
  await d1.exec("SELECT 1");
4315
4377
  }
4378
+ async function initializeProblemSets(mf) {
4379
+ const problemSetsDir = join16(getWorkspace(), PROBLEM_SETS_DIRECTORY);
4380
+ if (!existsSync11(problemSetsDir)) {
4381
+ return;
4382
+ }
4383
+ try {
4384
+ const bucket = await mf.getR2Bucket(CLOUDFLARE_BINDINGS.BUCKET_PROBLEM_SETS);
4385
+ for (const object of (await bucket.list()).objects) {
4386
+ await bucket.delete(object.key);
4387
+ }
4388
+ const files = readdirSync2(problemSetsDir);
4389
+ for (const file of files) {
4390
+ if (file.endsWith(".qti.json")) {
4391
+ const filePath = join16(problemSetsDir, file);
4392
+ const content = readFileSync5(filePath, "utf-8");
4393
+ await bucket.put(join16(PROBLEM_SETS_URL_PATH, file), content);
4394
+ }
4395
+ }
4396
+ } catch {
4397
+ }
4398
+ }
4316
4399
  async function writeBackendServerInfo(port) {
4317
4400
  writeServerInfo("backend", {
4318
4401
  pid: process.pid,
@@ -4324,7 +4407,8 @@ async function writeBackendServerInfo(port) {
4324
4407
  }
4325
4408
 
4326
4409
  // src/lib/dev/reload.ts
4327
- import { join as join13, relative as relative2 } from "path";
4410
+ import { readFileSync as readFileSync6 } from "fs";
4411
+ import { basename, join as join17, relative as relative2 } from "path";
4328
4412
  import chokidar from "chokidar";
4329
4413
  import { bold as bold5, cyan as cyan3, dim as dim4, green as green2 } from "colorette";
4330
4414
  function formatTime() {
@@ -4341,9 +4425,9 @@ function startHotReload(onReload, options = {}) {
4341
4425
  const customRoutesConfig = options.config?.integrations?.customRoutes;
4342
4426
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
4343
4427
  const watchPaths = [
4344
- join13(workspace, customRoutesDir),
4345
- join13(workspace, "playcademy.config.js"),
4346
- join13(workspace, "playcademy.config.json")
4428
+ join17(workspace, customRoutesDir),
4429
+ join17(workspace, "playcademy.config.js"),
4430
+ join17(workspace, "playcademy.config.json")
4347
4431
  ];
4348
4432
  const watcher = chokidar.watch(watchPaths, {
4349
4433
  persistent: true,
@@ -4358,10 +4442,10 @@ function startHotReload(onReload, options = {}) {
4358
4442
  const relativePath = relative2(workspace, changedPath);
4359
4443
  const timestamp = dim4(formatTime());
4360
4444
  const brand = bold5(cyan3("[playcademy]"));
4361
- const event = eventType === "changed" ? green2("reload") : green2(eventType || "reload");
4445
+ const event = eventType === "changed" ? green2("update") : green2(eventType || "update");
4362
4446
  console.log(`${timestamp} ${brand} ${event} ${dim4(relativePath)}`);
4363
4447
  } else {
4364
- logger.success("Reloaded");
4448
+ logger.success("Updated");
4365
4449
  }
4366
4450
  });
4367
4451
  const logError = options.onError || ((error) => {
@@ -4379,6 +4463,62 @@ function startHotReload(onReload, options = {}) {
4379
4463
  watcher.on("change", createReloadHandler("changed"));
4380
4464
  watcher.on("add", createReloadHandler("added"));
4381
4465
  watcher.on("unlink", createReloadHandler("removed"));
4466
+ let problemSetsWatcher;
4467
+ if (options.miniflareRef) {
4468
+ problemSetsWatcher = watchProblemSets(options.miniflareRef, options.onProblemSetUpdate);
4469
+ }
4470
+ return {
4471
+ close: () => {
4472
+ watcher.close();
4473
+ problemSetsWatcher?.close();
4474
+ }
4475
+ };
4476
+ }
4477
+ function watchProblemSets(miniflareRef, onUpdate) {
4478
+ const workspace = getWorkspace();
4479
+ const problemSetsPath = join17(workspace, PROBLEM_SETS_DIRECTORY);
4480
+ const watcher = chokidar.watch(problemSetsPath, {
4481
+ persistent: true,
4482
+ ignoreInitial: true,
4483
+ awaitWriteFinish: {
4484
+ stabilityThreshold: 100,
4485
+ pollInterval: 100
4486
+ }
4487
+ });
4488
+ const logUpdate = (filePath, eventType) => {
4489
+ const relativePath = relative2(workspace, filePath);
4490
+ if (onUpdate) {
4491
+ onUpdate(relativePath, eventType);
4492
+ return;
4493
+ }
4494
+ const timestamp = dim4(formatTime());
4495
+ const brand = bold5(cyan3("[playcademy]"));
4496
+ const event = green2(eventType);
4497
+ console.log(`${timestamp} ${brand} ${event} ${dim4(relativePath)}`);
4498
+ };
4499
+ const updateProblemSet = async (filePath, operation) => {
4500
+ if (!filePath.endsWith(".qti.json")) return;
4501
+ const mf = miniflareRef.current;
4502
+ if (!mf) return;
4503
+ try {
4504
+ const bucket = await mf.getR2Bucket(CLOUDFLARE_BINDINGS.BUCKET_PROBLEM_SETS);
4505
+ const filename = basename(filePath);
4506
+ const key = `${PROBLEM_SETS_URL_PATH}/${filename}`;
4507
+ if (operation === "remove") {
4508
+ await bucket.delete(key);
4509
+ } else {
4510
+ const content = readFileSync6(filePath, "utf-8");
4511
+ await bucket.put(key, content);
4512
+ }
4513
+ logUpdate(filePath, operation);
4514
+ } catch (error) {
4515
+ if (error instanceof Error && error.message.includes("disposed")) return;
4516
+ logger.error(`Failed to ${operation} problem set: ${getErrorMessage(error)}`);
4517
+ }
4518
+ };
4519
+ watcher.on("change", (filePath) => updateProblemSet(filePath, "update"));
4520
+ watcher.on("add", (filePath) => updateProblemSet(filePath, "add"));
4521
+ watcher.on("unlink", (filePath) => updateProblemSet(filePath, "remove"));
4382
4522
  return watcher;
4383
4523
  }
4384
4524
  export {
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "playcademy",
4
- version: "0.14.12",
4
+ version: "0.14.14",
5
5
  type: "module",
6
6
  exports: {
7
7
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.14.13",
3
+ "version": "0.14.14-alpha.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {