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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Academic Content Constants
3
+ *
4
+ * Constants for academic problem sets and educational content served via TimeBack.
5
+ */
6
+
7
+ /**
8
+ * Problem Sets Storage
9
+ */
10
+ export const PROBLEM_SETS = {
11
+ /** Base S3 prefix for problem sets */
12
+ S3_PREFIX: 'academics/problems/v1',
13
+
14
+ /** CDN manifest path */
15
+ MANIFEST_PATH: 'academics/problems/v1/index.json',
16
+
17
+ /** Expected directory structure: {subject}/{grade}/{domain}/{standard}.qti.json */
18
+ DIRECTORY_PATTERN: '{subject}/{grade}/{domain}',
19
+
20
+ /** Current manifest schema version */
21
+ MANIFEST_VERSION: 1,
22
+
23
+ /** Local directory in monorepo */
24
+ SOURCE_DIR: 'static/academics/problems',
25
+
26
+ /** Default XP per problem item */
27
+ DEFAULT_XP_PER_ITEM: 10,
28
+ } as const
@@ -6,15 +6,32 @@
6
6
  */
7
7
 
8
8
  /**
9
- * Playcademy domain names
9
+ * Core Playcademy domains
10
+ */
11
+ export const APEX_DOMAIN = 'playcademy.net'
12
+ export const DEV_DOMAIN = 'playcademy.dev'
13
+ export const GAMES_DOMAIN = 'playcademy.gg'
14
+
15
+ /**
16
+ * Playcademy domain names grouped
10
17
  */
11
18
  export const PLAYCADEMY_DOMAINS = {
12
19
  /** Primary domain (hub, landing pages) */
13
- apex: 'playcademy.net',
14
- /** Marketing/alternative domain */
15
- apexAlt: 'playcademy.com',
20
+ apex: APEX_DOMAIN,
21
+ /** Development/utility domain */
22
+ dev: DEV_DOMAIN,
16
23
  /** Game backend worker domain */
17
- games: 'playcademy.gg',
24
+ games: GAMES_DOMAIN,
25
+ } as const
26
+
27
+ /**
28
+ * CDN domains by environment
29
+ */
30
+ export const CDN_DOMAINS = {
31
+ /** Production CDN: https://cdn.playcademy.net */
32
+ production: `https://cdn.${APEX_DOMAIN}`,
33
+ /** Development CDN: https://cdn.dev.playcademy.net */
34
+ staging: `https://cdn.dev.${APEX_DOMAIN}`,
18
35
  } as const
19
36
 
20
37
  /**
@@ -6,6 +6,7 @@
6
6
  * and other constants that need to be consistent across multiple packages.
7
7
  */
8
8
 
9
+ export * from './academics'
9
10
  export * from './auth'
10
11
  export * from './domains'
11
12
  export * from './env-vars'
@@ -7,15 +7,7 @@ export { GAME_WORKER_DOMAINS, PLAYCADEMY_BASE_URLS, PLAYCADEMY_DOMAINS } from '@
7
7
  * Default directory for custom API routes
8
8
  * Used when integrations.customRoutes.directory is not specified in config
9
9
  */
10
- declare const DEFAULT_API_ROUTES_DIRECTORY = "server/api";
11
- /**
12
- * Server root directory (fixed location)
13
- */
14
- declare const SERVER_ROOT_DIRECTORY = "server";
15
- /**
16
- * Server library/utilities directory (fixed location)
17
- */
18
- declare const SERVER_LIB_DIRECTORY = "server/lib";
10
+ declare const DEFAULT_API_ROUTES_DIRECTORY: string;
19
11
  /**
20
12
  * Auth routes subdirectory name within API directory (fixed structure)
21
13
  */
@@ -92,6 +84,8 @@ declare const CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
92
84
  declare const CLOUDFLARE_BINDINGS: {
93
85
  /** R2 bucket binding name */
94
86
  readonly BUCKET: "BUCKET";
87
+ /** Problem sets bucket binding name */
88
+ readonly BUCKET_PROBLEM_SETS: "PROBLEM_SETS";
95
89
  /** KV namespace binding name */
96
90
  readonly KV: "KV";
97
91
  /** D1 database binding name */
@@ -141,19 +135,30 @@ declare const TSCONFIG_FILES: readonly ["tsconfig.app.json", "tsconfig.json"];
141
135
  /**
142
136
  * Default directory for database files (configurable via playcademy.config)
143
137
  */
144
- declare const DEFAULT_DATABASE_DIRECTORY = "db";
138
+ declare const DEFAULT_DATABASE_DIRECTORY: string;
145
139
  /**
146
140
  * Schema subdirectory name within database directory (fixed structure)
147
141
  */
148
142
  declare const SCHEMA_SUBDIRECTORY = "schema";
149
143
  /**
150
- * Schema entry point file name (fixed structure)
151
- */
152
- declare const SCHEMA_INDEX_FILE = "index.ts";
144
+ * Database file names (fixed structure)
145
+ */
146
+ declare const DB_FILES: {
147
+ /** Index file name */
148
+ readonly INDEX: "index.ts";
149
+ /** Seed file name */
150
+ readonly SEED: "seed.ts";
151
+ /** Types file name */
152
+ readonly TYPES: "types.ts";
153
+ /** Schema index file name */
154
+ readonly SCHEMA_INDEX: "index.ts";
155
+ /** Example schema file name */
156
+ readonly EXAMPLE_SCHEMA: "example.ts";
157
+ };
153
158
  /**
154
- * Default seed file name (can be overridden with --file option)
159
+ * Drizzle config file names
155
160
  */
156
- declare const DEFAULT_SEED_FILE_NAME = "seed.ts";
161
+ declare const DRIZZLE_CONFIG_FILES: readonly ["drizzle.config.ts", "drizzle.config.js"];
157
162
 
158
163
  /**
159
164
  * Godot engine integration constants
@@ -179,16 +184,16 @@ declare const GODOT_BUILD_DIRECTORIES: {
179
184
  /** Root build directory (cleared before each export) */
180
185
  readonly ROOT: "build";
181
186
  /** Web export subdirectory */
182
- readonly WEB: "build/web";
187
+ readonly WEB: string;
183
188
  };
184
189
  /**
185
190
  * Build output file paths
186
191
  */
187
192
  declare const GODOT_BUILD_OUTPUTS: {
188
193
  /** Exported web build entry point */
189
- readonly INDEX_HTML: "build/web/index.html";
194
+ readonly INDEX_HTML: string;
190
195
  /** Packaged zip file (created by Godot export) */
191
- readonly ZIP: "build/web_playcademy.zip";
196
+ readonly ZIP: string;
192
197
  };
193
198
  /**
194
199
  * Platform-specific Godot executable discovery patterns
@@ -247,7 +252,7 @@ declare const CLI_USER_DIRECTORIES: {
247
252
  */
248
253
  declare const CLI_DEFAULT_OUTPUTS: {
249
254
  /** Default worker bundle output for debug command */
250
- readonly WORKER_BUNDLE: ".playcademy/worker-bundle.js";
255
+ readonly WORKER_BUNDLE: string;
251
256
  };
252
257
  /**
253
258
  * CLI file names
@@ -271,9 +276,38 @@ declare const DEFAULT_PORTS: {
271
276
  readonly BACKEND: 8788;
272
277
  };
273
278
 
279
+ /**
280
+ * Server directory structure constants
281
+ */
282
+ /**
283
+ * Server root directory (fixed location)
284
+ */
285
+ declare const SERVER_ROOT_DIRECTORY = "server";
286
+ /**
287
+ * Server library/utilities directory (fixed location)
288
+ */
289
+ declare const SERVER_LIB_DIRECTORY: string;
290
+ /**
291
+ * Public assets directory (fixed location)
292
+ */
293
+ declare const PUBLIC_DIRECTORY = "public";
294
+ /**
295
+ * Academic content subdirectory within public/
296
+ */
297
+ declare const ACADEMICS_PUBLIC_DIRECTORY: string;
298
+ /**
299
+ * Problem sets directory (frontend static assets)
300
+ * Used by TimeBack integration for educational content
301
+ */
302
+ declare const PROBLEM_SETS_DIRECTORY: string;
303
+ /**
304
+ * Problem sets URL path (for serving via worker/middleware)
305
+ */
306
+ declare const PROBLEM_SETS_URL_PATH = "academics/problem-sets";
307
+
274
308
  /**
275
309
  * Config file names to search for
276
310
  */
277
311
  declare const CONFIG_FILE_NAMES: string[];
278
312
 
279
- export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_SEED_FILE_NAME, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_GAME_URL, PLAYCADEMY_AUTH_VERSION, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_INDEX_FILE, SCHEMA_SUBDIRECTORY, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TSCONFIG_FILES, WORKSPACE_NAME };
313
+ export { ACADEMICS_PUBLIC_DIRECTORY, AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DB_FILES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_GAME_URL, PLAYCADEMY_AUTH_VERSION, PROBLEM_SETS_DIRECTORY, PROBLEM_SETS_URL_PATH, PUBLIC_DIRECTORY, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TSCONFIG_FILES, WORKSPACE_NAME };
package/dist/constants.js CHANGED
@@ -1,7 +1,17 @@
1
1
  // src/constants/api.ts
2
- var DEFAULT_API_ROUTES_DIRECTORY = "server/api";
2
+ import { join as join2 } from "node:path";
3
+
4
+ // src/constants/server.ts
5
+ import { join } from "node:path";
3
6
  var SERVER_ROOT_DIRECTORY = "server";
4
- var SERVER_LIB_DIRECTORY = "server/lib";
7
+ var SERVER_LIB_DIRECTORY = join(SERVER_ROOT_DIRECTORY, "lib");
8
+ var PUBLIC_DIRECTORY = "public";
9
+ var ACADEMICS_PUBLIC_DIRECTORY = join(PUBLIC_DIRECTORY, "academics");
10
+ var PROBLEM_SETS_DIRECTORY = join(ACADEMICS_PUBLIC_DIRECTORY, "problem-sets");
11
+ var PROBLEM_SETS_URL_PATH = "academics/problem-sets";
12
+
13
+ // src/constants/api.ts
14
+ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
5
15
  var AUTH_API_SUBDIRECTORY = "auth";
6
16
  var SAMPLE_API_SUBDIRECTORY = "sample";
7
17
  var SAMPLE_CUSTOM_FILENAME = "custom.ts";
@@ -56,9 +66,11 @@ var package_default = {
56
66
  "sync-engine-assets": "bun scripts/sync-engine-assets.ts",
57
67
  "sync-vite-templates": "bun scripts/sync-vite-templates.ts",
58
68
  "sync-godot-template": "bun scripts/sync-godot-template.ts",
69
+ "sync-problem-sets": "sst shell -- bun scripts/sync-problem-sets.ts",
59
70
  "sync:all": "bun scripts/sync-all.ts",
60
71
  cf: "sst shell -- bun scripts/setup-cloudflare-dispatch.ts",
61
72
  "list-s3-bucket": "sst shell -- bun scripts/list-s3-bucket.ts",
73
+ "invalidate-cdn": "sst shell -- bun scripts/invalidate-cdn.ts",
62
74
  doctor: "bunx sst shell -- bun scripts/doctor.ts",
63
75
  format: "bun run --filter '*' format",
64
76
  lint: "bun run --filter '*' lint",
@@ -158,6 +170,8 @@ var CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
158
170
  var CLOUDFLARE_BINDINGS = {
159
171
  /** R2 bucket binding name */
160
172
  BUCKET: "BUCKET",
173
+ /** Problem sets bucket binding name */
174
+ BUCKET_PROBLEM_SETS: "PROBLEM_SETS",
161
175
  /** KV namespace binding name */
162
176
  KV: "KV",
163
177
  /** D1 database binding name */
@@ -166,12 +180,25 @@ var CLOUDFLARE_BINDINGS = {
166
180
  var MINIFLARE_D1_DIRECTORY = "miniflare-D1DatabaseObject";
167
181
 
168
182
  // src/constants/database.ts
169
- var DEFAULT_DATABASE_DIRECTORY = "db";
183
+ import { join as join3 } from "path";
184
+ var DEFAULT_DATABASE_DIRECTORY = join3("server", "db");
170
185
  var SCHEMA_SUBDIRECTORY = "schema";
171
- var SCHEMA_INDEX_FILE = "index.ts";
172
- var DEFAULT_SEED_FILE_NAME = "seed.ts";
186
+ var DB_FILES = {
187
+ /** Index file name */
188
+ INDEX: "index.ts",
189
+ /** Seed file name */
190
+ SEED: "seed.ts",
191
+ /** Types file name */
192
+ TYPES: "types.ts",
193
+ /** Schema index file name */
194
+ SCHEMA_INDEX: "index.ts",
195
+ /** Example schema file name */
196
+ EXAMPLE_SCHEMA: "example.ts"
197
+ };
198
+ var DRIZZLE_CONFIG_FILES = ["drizzle.config.ts", "drizzle.config.js"];
173
199
 
174
200
  // src/constants/godot.ts
201
+ import { join as join4 } from "node:path";
175
202
  var GODOT_PROJECT_FILE = "project.godot";
176
203
  var GODOT_EXPORT_PRESETS_FILE = "export_presets.cfg";
177
204
  var GODOT_WEB_PLATFORM = 'platform="Web"';
@@ -179,13 +206,13 @@ var GODOT_BUILD_DIRECTORIES = {
179
206
  /** Root build directory (cleared before each export) */
180
207
  ROOT: "build",
181
208
  /** Web export subdirectory */
182
- WEB: "build/web"
209
+ WEB: join4("build", "web")
183
210
  };
184
211
  var GODOT_BUILD_OUTPUTS = {
185
212
  /** Exported web build entry point */
186
- INDEX_HTML: "build/web/index.html",
213
+ INDEX_HTML: join4("build", "web", "index.html"),
187
214
  /** Packaged zip file (created by Godot export) */
188
- ZIP: "build/web_playcademy.zip"
215
+ ZIP: join4("build", "web_playcademy.zip")
189
216
  };
190
217
  var GODOT_EXECUTABLE_PATTERNS = {
191
218
  /** macOS app bundle suffix */
@@ -208,17 +235,17 @@ var CALLBACK_PATH = "/callback";
208
235
  var SSO_AUTH_TIMEOUT_MS = 3e4;
209
236
 
210
237
  // src/constants/paths.ts
211
- import { join } from "path";
238
+ import { join as join5 } from "path";
212
239
  var WORKSPACE_NAME = ".playcademy";
213
240
  var CLI_DIRECTORIES = {
214
241
  /** Root directory for CLI artifacts in workspace */
215
242
  WORKSPACE: WORKSPACE_NAME,
216
243
  /** Database directory within workspace */
217
- DATABASE: join(WORKSPACE_NAME, "db"),
244
+ DATABASE: join5(WORKSPACE_NAME, "db"),
218
245
  /** KV storage directory within workspace */
219
- KV: join(WORKSPACE_NAME, "kv"),
246
+ KV: join5(WORKSPACE_NAME, "kv"),
220
247
  /** Bucket storage directory within workspace */
221
- BUCKET: join(WORKSPACE_NAME, "bucket")
248
+ BUCKET: join5(WORKSPACE_NAME, "bucket")
222
249
  };
223
250
  var CLI_USER_DIRECTORIES = {
224
251
  /** User config directory for auth, games store, etc. */
@@ -226,7 +253,7 @@ var CLI_USER_DIRECTORIES = {
226
253
  };
227
254
  var CLI_DEFAULT_OUTPUTS = {
228
255
  /** Default worker bundle output for debug command */
229
- WORKER_BUNDLE: ".playcademy/worker-bundle.js"
256
+ WORKER_BUNDLE: join5(WORKSPACE_NAME, "worker-bundle.js")
230
257
  };
231
258
  var CLI_FILES = {
232
259
  /** Auth store file in user config directory */
@@ -253,13 +280,22 @@ var CONFIG_FILE_NAMES = [
253
280
  ];
254
281
 
255
282
  // ../constants/src/domains.ts
283
+ var APEX_DOMAIN = "playcademy.net";
284
+ var DEV_DOMAIN = "playcademy.dev";
285
+ var GAMES_DOMAIN = "playcademy.gg";
256
286
  var PLAYCADEMY_DOMAINS = {
257
287
  /** Primary domain (hub, landing pages) */
258
- apex: "playcademy.net",
259
- /** Marketing/alternative domain */
260
- apexAlt: "playcademy.com",
288
+ apex: APEX_DOMAIN,
289
+ /** Development/utility domain */
290
+ dev: DEV_DOMAIN,
261
291
  /** Game backend worker domain */
262
- games: "playcademy.gg"
292
+ games: GAMES_DOMAIN
293
+ };
294
+ var CDN_DOMAINS = {
295
+ /** Production CDN: https://cdn.playcademy.net */
296
+ production: `https://cdn.${APEX_DOMAIN}`,
297
+ /** Development CDN: https://cdn.dev.playcademy.net */
298
+ staging: `https://cdn.dev.${APEX_DOMAIN}`
263
299
  };
264
300
  var PLAYCADEMY_BASE_URLS = {
265
301
  production: "https://hub.playcademy.net",
@@ -301,6 +337,7 @@ var BADGES = {
301
337
  FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
302
338
  };
303
339
  export {
340
+ ACADEMICS_PUBLIC_DIRECTORY,
304
341
  AUTH_API_SUBDIRECTORY,
305
342
  AUTH_CONFIG_FILE,
306
343
  AUTH_PROVIDER_NAMES,
@@ -315,10 +352,11 @@ export {
315
352
  CLOUDFLARE_BINDINGS,
316
353
  CLOUDFLARE_COMPATIBILITY_DATE,
317
354
  CONFIG_FILE_NAMES,
355
+ DB_FILES,
318
356
  DEFAULT_API_ROUTES_DIRECTORY,
319
357
  DEFAULT_DATABASE_DIRECTORY,
320
358
  DEFAULT_PORTS,
321
- DEFAULT_SEED_FILE_NAME,
359
+ DRIZZLE_CONFIG_FILES,
322
360
  ENV_EXAMPLE_FILE,
323
361
  ENV_FILES,
324
362
  GAME_WORKER_DOMAINS,
@@ -334,12 +372,14 @@ export {
334
372
  PLAYCADEMY_AUTH_VERSION,
335
373
  PLAYCADEMY_BASE_URLS,
336
374
  PLAYCADEMY_DOMAINS,
375
+ PROBLEM_SETS_DIRECTORY,
376
+ PROBLEM_SETS_URL_PATH,
377
+ PUBLIC_DIRECTORY,
337
378
  SAMPLE_API_SUBDIRECTORY,
338
379
  SAMPLE_BUCKET_FILENAME,
339
380
  SAMPLE_CUSTOM_FILENAME,
340
381
  SAMPLE_DATABASE_FILENAME,
341
382
  SAMPLE_KV_FILENAME,
342
- SCHEMA_INDEX_FILE,
343
383
  SCHEMA_SUBDIRECTORY,
344
384
  SERVER_LIB_DIRECTORY,
345
385
  SERVER_ROOT_DIRECTORY,
package/dist/db.js CHANGED
@@ -1430,7 +1430,21 @@ var init_file_loader = __esm({
1430
1430
 
1431
1431
  // src/lib/db/path.ts
1432
1432
  import { copyFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
1433
- import { join as join2 } from "path";
1433
+ import { join as join6 } from "path";
1434
+
1435
+ // src/constants/api.ts
1436
+ import { join as join2 } from "node:path";
1437
+
1438
+ // src/constants/server.ts
1439
+ import { join } from "node:path";
1440
+ var SERVER_ROOT_DIRECTORY = "server";
1441
+ var SERVER_LIB_DIRECTORY = join(SERVER_ROOT_DIRECTORY, "lib");
1442
+ var PUBLIC_DIRECTORY = "public";
1443
+ var ACADEMICS_PUBLIC_DIRECTORY = join(PUBLIC_DIRECTORY, "academics");
1444
+ var PROBLEM_SETS_DIRECTORY = join(ACADEMICS_PUBLIC_DIRECTORY, "problem-sets");
1445
+
1446
+ // src/constants/api.ts
1447
+ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
1434
1448
 
1435
1449
  // ../../package.json
1436
1450
  var package_default = {
@@ -1479,9 +1493,11 @@ var package_default = {
1479
1493
  "sync-engine-assets": "bun scripts/sync-engine-assets.ts",
1480
1494
  "sync-vite-templates": "bun scripts/sync-vite-templates.ts",
1481
1495
  "sync-godot-template": "bun scripts/sync-godot-template.ts",
1496
+ "sync-problem-sets": "sst shell -- bun scripts/sync-problem-sets.ts",
1482
1497
  "sync:all": "bun scripts/sync-all.ts",
1483
1498
  cf: "sst shell -- bun scripts/setup-cloudflare-dispatch.ts",
1484
1499
  "list-s3-bucket": "sst shell -- bun scripts/list-s3-bucket.ts",
1500
+ "invalidate-cdn": "sst shell -- bun scripts/invalidate-cdn.ts",
1485
1501
  doctor: "bunx sst shell -- bun scripts/doctor.ts",
1486
1502
  format: "bun run --filter '*' format",
1487
1503
  lint: "bun run --filter '*' lint",
@@ -1564,6 +1580,8 @@ var BUCKET_ALWAYS_SKIP = [".git", ".DS_Store", ".gitignore", ...ENV_FILES];
1564
1580
  var CLOUDFLARE_BINDINGS = {
1565
1581
  /** R2 bucket binding name */
1566
1582
  BUCKET: "BUCKET",
1583
+ /** Problem sets bucket binding name */
1584
+ BUCKET_PROBLEM_SETS: "PROBLEM_SETS",
1567
1585
  /** KV namespace binding name */
1568
1586
  KV: "KV",
1569
1587
  /** D1 database binding name */
@@ -1571,18 +1589,41 @@ var CLOUDFLARE_BINDINGS = {
1571
1589
  };
1572
1590
  var MINIFLARE_D1_DIRECTORY = "miniflare-D1DatabaseObject";
1573
1591
 
1592
+ // src/constants/database.ts
1593
+ import { join as join3 } from "path";
1594
+ var DEFAULT_DATABASE_DIRECTORY = join3("server", "db");
1595
+
1596
+ // src/constants/godot.ts
1597
+ import { join as join4 } from "node:path";
1598
+ var GODOT_BUILD_DIRECTORIES = {
1599
+ /** Root build directory (cleared before each export) */
1600
+ ROOT: "build",
1601
+ /** Web export subdirectory */
1602
+ WEB: join4("build", "web")
1603
+ };
1604
+ var GODOT_BUILD_OUTPUTS = {
1605
+ /** Exported web build entry point */
1606
+ INDEX_HTML: join4("build", "web", "index.html"),
1607
+ /** Packaged zip file (created by Godot export) */
1608
+ ZIP: join4("build", "web_playcademy.zip")
1609
+ };
1610
+
1574
1611
  // src/constants/paths.ts
1575
- import { join } from "path";
1612
+ import { join as join5 } from "path";
1576
1613
  var WORKSPACE_NAME = ".playcademy";
1577
1614
  var CLI_DIRECTORIES = {
1578
1615
  /** Root directory for CLI artifacts in workspace */
1579
1616
  WORKSPACE: WORKSPACE_NAME,
1580
1617
  /** Database directory within workspace */
1581
- DATABASE: join(WORKSPACE_NAME, "db"),
1618
+ DATABASE: join5(WORKSPACE_NAME, "db"),
1582
1619
  /** KV storage directory within workspace */
1583
- KV: join(WORKSPACE_NAME, "kv"),
1620
+ KV: join5(WORKSPACE_NAME, "kv"),
1584
1621
  /** Bucket storage directory within workspace */
1585
- BUCKET: join(WORKSPACE_NAME, "bucket")
1622
+ BUCKET: join5(WORKSPACE_NAME, "bucket")
1623
+ };
1624
+ var CLI_DEFAULT_OUTPUTS = {
1625
+ /** Default worker bundle output for debug command */
1626
+ WORKER_BUNDLE: join5(WORKSPACE_NAME, "worker-bundle.js")
1586
1627
  };
1587
1628
  var CLI_FILES = {
1588
1629
  /** Auth store file in user config directory */
@@ -1593,6 +1634,15 @@ var CLI_FILES = {
1593
1634
  INITIAL_DATABASE: "initial.sqlite"
1594
1635
  };
1595
1636
 
1637
+ // ../constants/src/domains.ts
1638
+ var APEX_DOMAIN = "playcademy.net";
1639
+ var CDN_DOMAINS = {
1640
+ /** Production CDN: https://cdn.playcademy.net */
1641
+ production: `https://cdn.${APEX_DOMAIN}`,
1642
+ /** Development CDN: https://cdn.dev.playcademy.net */
1643
+ staging: `https://cdn.dev.${APEX_DOMAIN}`
1644
+ };
1645
+
1596
1646
  // ../constants/src/overworld.ts
1597
1647
  var ITEM_SLUGS = {
1598
1648
  /** Primary platform currency */
@@ -1633,11 +1683,11 @@ var ensureDirectoryExists = (dir) => {
1633
1683
  }
1634
1684
  };
1635
1685
  var findMiniflareDatabase = (dbDir) => {
1636
- const miniflareDir = join2(dbDir, MINIFLARE_D1_DIRECTORY);
1686
+ const miniflareDir = join6(dbDir, MINIFLARE_D1_DIRECTORY);
1637
1687
  if (!existsSync(miniflareDir)) return null;
1638
1688
  const sqliteFiles = readdirSync(miniflareDir).filter((file) => file.endsWith(".sqlite"));
1639
1689
  if (sqliteFiles.length === 0) return null;
1640
- return join2(miniflareDir, sqliteFiles[0]);
1690
+ return join6(miniflareDir, sqliteFiles[0]);
1641
1691
  };
1642
1692
  var migrateInitialDbToTarget = (initialPath, targetPath) => {
1643
1693
  if (!existsSync(initialPath)) return;
@@ -1645,7 +1695,7 @@ var migrateInitialDbToTarget = (initialPath, targetPath) => {
1645
1695
  unlinkSync(initialPath);
1646
1696
  };
1647
1697
  function getDevDbPath() {
1648
- const initialDbPath = join2(DB_DIRECTORY, INITIAL_DB_NAME);
1698
+ const initialDbPath = join6(DB_DIRECTORY, INITIAL_DB_NAME);
1649
1699
  ensureDirectoryExists(DB_DIRECTORY);
1650
1700
  const miniflareDbPath = findMiniflareDatabase(DB_DIRECTORY);
1651
1701
  if (miniflareDbPath) {
@@ -1709,7 +1759,7 @@ async function bundleSeedWorker(seedFilePath, projectPath) {
1709
1759
  // src/lib/db/reset.ts
1710
1760
  import { execSync as execSync2 } from "child_process";
1711
1761
  import { rmSync as rmSync2 } from "fs";
1712
- import { join as join5 } from "path";
1762
+ import { join as join9 } from "path";
1713
1763
 
1714
1764
  // ../utils/src/ansi.ts
1715
1765
  var colors = {
@@ -1938,7 +1988,7 @@ init_package_json();
1938
1988
  // ../utils/src/package-manager.ts
1939
1989
  import { execSync } from "child_process";
1940
1990
  import { existsSync as existsSync2 } from "fs";
1941
- import { join as join3 } from "path";
1991
+ import { join as join7 } from "path";
1942
1992
  function isCommandAvailable(command) {
1943
1993
  try {
1944
1994
  execSync(`command -v ${command}`, { stdio: "ignore" });
@@ -1948,16 +1998,16 @@ function isCommandAvailable(command) {
1948
1998
  }
1949
1999
  }
1950
2000
  function detectPackageManager(cwd = process.cwd()) {
1951
- if (existsSync2(join3(cwd, "bun.lock")) || existsSync2(join3(cwd, "bun.lockb"))) {
2001
+ if (existsSync2(join7(cwd, "bun.lock")) || existsSync2(join7(cwd, "bun.lockb"))) {
1952
2002
  return "bun";
1953
2003
  }
1954
- if (existsSync2(join3(cwd, "pnpm-lock.yaml"))) {
2004
+ if (existsSync2(join7(cwd, "pnpm-lock.yaml"))) {
1955
2005
  return "pnpm";
1956
2006
  }
1957
- if (existsSync2(join3(cwd, "yarn.lock"))) {
2007
+ if (existsSync2(join7(cwd, "yarn.lock"))) {
1958
2008
  return "yarn";
1959
2009
  }
1960
- if (existsSync2(join3(cwd, "package-lock.json"))) {
2010
+ if (existsSync2(join7(cwd, "package-lock.json"))) {
1961
2011
  return "npm";
1962
2012
  }
1963
2013
  return detectByCommandAvailability();
@@ -2882,12 +2932,12 @@ var currentDir = dirname(fileURLToPath(import.meta.url));
2882
2932
  // src/lib/core/import.ts
2883
2933
  import { mkdtempSync, rmSync } from "fs";
2884
2934
  import { tmpdir } from "os";
2885
- import { join as join4 } from "path";
2935
+ import { join as join8 } from "path";
2886
2936
  import { pathToFileURL } from "url";
2887
2937
  import * as esbuild from "esbuild";
2888
2938
  async function importTypescriptFile(filePath, bundleOptions) {
2889
- const tempDir = mkdtempSync(join4(tmpdir(), "playcademy-import-"));
2890
- const outFile = join4(tempDir, "bundle.mjs");
2939
+ const tempDir = mkdtempSync(join8(tmpdir(), "playcademy-import-"));
2940
+ const outFile = join8(tempDir, "bundle.mjs");
2891
2941
  try {
2892
2942
  await esbuild.build({
2893
2943
  entryPoints: [filePath],
@@ -2910,7 +2960,7 @@ async function importTypescriptFile(filePath, bundleOptions) {
2910
2960
  // src/lib/db/reset.ts
2911
2961
  async function resetDatabase(workspace, mf, options = { debug: false }) {
2912
2962
  const { debug } = options;
2913
- const dbDir = join5(workspace, CLI_DIRECTORIES.DATABASE);
2963
+ const dbDir = join9(workspace, CLI_DIRECTORIES.DATABASE);
2914
2964
  await runStep(
2915
2965
  "Resetting database...",
2916
2966
  async () => {
@@ -205,6 +205,66 @@ async function serveFromR2(c: Context<HonoEnv>, bucket: R2Bucket): Promise<Respo
205
205
  return c.json({ error: 'Not Found', message: 'Asset not found', path }, 404)
206
206
  }
207
207
 
208
+ /**
209
+ * Register problem sets middleware (dev and prod)
210
+ *
211
+ * Serves TimeBack problem sets from public/timeback/problems/ directory.
212
+ *
213
+ * In local development:
214
+ * - Problem sets are loaded from public/timeback/problems/ at dev server startup
215
+ * - Stored in PROBLEM_SETS R2 bucket binding
216
+ * - Served at /timeback/problems/*.qti.json
217
+ *
218
+ * In production:
219
+ * - Problem sets uploaded with public/ assets during deploy
220
+ * - Served from Workers Assets or R2 (depending on asset strategy)
221
+ * - Falls through to asset fallback handler if PROBLEM_SETS binding not present
222
+ *
223
+ * Problem sets are customized by developers in their repo after running:
224
+ * - `playcademy init` (downloads initial problem sets)
225
+ * - `playcademy timeback problem-sets download` (downloads additional sets)
226
+ */
227
+ export function registerLocalProblemSets(app: Hono<HonoEnv>): void {
228
+ app.use('/academics/problem-sets/*', async (c, next) => {
229
+ // Only handle if PROBLEM_SETS binding exists (standalone dev mode)
230
+ // In production or Vite mode, pass through to asset fallback
231
+ const problemSets = c.env.PROBLEM_SETS as R2Bucket | undefined
232
+
233
+ if (!problemSets) {
234
+ return next()
235
+ }
236
+
237
+ const path = new URL(c.req.url).pathname
238
+
239
+ /**
240
+ * Security: Prevent directory traversal attacks
241
+ *
242
+ * Only allow paths under /academics/problem-sets/ with flat filenames
243
+ * Reject paths with ".." (parent directory traversal)
244
+ */
245
+ if (path.includes('..')) {
246
+ return c.json({ error: 'Invalid path' }, 400)
247
+ }
248
+
249
+ try {
250
+ // Remove leading slash from path for R2 key
251
+ const key = path.slice(1)
252
+ const object = await problemSets.get(key)
253
+
254
+ if (object) {
255
+ return c.json(JSON.parse(await object.text()), 200, {
256
+ 'Cache-Control': 'no-cache',
257
+ 'Content-Type': 'application/json',
258
+ })
259
+ }
260
+ } catch {
261
+ // Error accessing R2 - fall through
262
+ }
263
+
264
+ return next()
265
+ })
266
+ }
267
+
208
268
  /**
209
269
  * Register asset fallback handler
210
270
  *