playcademy 0.21.0 → 0.21.1-beta.2

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/cli.js CHANGED
@@ -1065,7 +1065,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
1065
1065
  // ../better-auth/package.json
1066
1066
  var package_default = {
1067
1067
  name: "@playcademy/better-auth",
1068
- version: "0.0.12",
1068
+ version: "0.0.13-beta.2",
1069
1069
  type: "module",
1070
1070
  exports: {
1071
1071
  "./server": {
@@ -3024,7 +3024,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as re
3024
3024
  import { join as join13 } from "node:path";
3025
3025
 
3026
3026
  // src/version.ts
3027
- var cliVersion = false ? "0.0.0-dev" : "0.21.0";
3027
+ var cliVersion = false ? "0.0.0-dev" : "0.21.1-beta.2";
3028
3028
 
3029
3029
  // src/lib/init/database.ts
3030
3030
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
package/dist/constants.js CHANGED
@@ -20,7 +20,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
20
20
  // ../better-auth/package.json
21
21
  var package_default = {
22
22
  name: "@playcademy/better-auth",
23
- version: "0.0.12",
23
+ version: "0.0.13-beta.2",
24
24
  type: "module",
25
25
  exports: {
26
26
  "./server": {
package/dist/db.js CHANGED
@@ -36,7 +36,7 @@ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
36
36
  // ../better-auth/package.json
37
37
  var package_default = {
38
38
  name: "@playcademy/better-auth",
39
- version: "0.0.12",
39
+ version: "0.0.13-beta.2",
40
40
  type: "module",
41
41
  exports: {
42
42
  "./server": {
package/dist/index.d.ts CHANGED
@@ -352,6 +352,8 @@ interface CourseMetadata {
352
352
  isCustom?: boolean;
353
353
  /** Signals whether a course is in production with students */
354
354
  publishStatus?: 'draft' | 'testing' | 'published' | 'deactivated';
355
+ /** Whether this course appears in the TimeBack catalog for teachers and parents */
356
+ timebackVisible?: boolean;
355
357
  /** Who to contact when issues reported with questions */
356
358
  contactEmail?: string;
357
359
  /** Primary app identifier */
package/dist/index.js CHANGED
@@ -326,7 +326,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
326
326
  // ../better-auth/package.json
327
327
  var package_default = {
328
328
  name: "@playcademy/better-auth",
329
- version: "0.0.12",
329
+ version: "0.0.13-beta.2",
330
330
  type: "module",
331
331
  exports: {
332
332
  "./server": {
@@ -4057,7 +4057,7 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as rea
4057
4057
  import { join as join13 } from "node:path";
4058
4058
 
4059
4059
  // src/version.ts
4060
- var cliVersion = false ? "0.0.0-dev" : "0.21.0";
4060
+ var cliVersion = false ? "0.0.0-dev" : "0.21.1-beta.2";
4061
4061
 
4062
4062
  // src/lib/init/database.ts
4063
4063
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
@@ -13134,6 +13134,112 @@ function displayCleanupWarning(integrations, gameName, logger2) {
13134
13134
  logger2.newLine();
13135
13135
  }
13136
13136
 
13137
+ // src/lib/timeback/api.ts
13138
+ function getTimebackIntegrationConfig(client, gameId, courseId) {
13139
+ return client.request(
13140
+ `/timeback/integrations/${gameId}/${courseId}`,
13141
+ "GET"
13142
+ );
13143
+ }
13144
+
13145
+ // src/lib/timeback/drift.ts
13146
+ function courseKey(course) {
13147
+ return `${course.grade}:${course.subject.trim().toLowerCase()}`;
13148
+ }
13149
+ function normalizeTotalXp(value) {
13150
+ return value ?? null;
13151
+ }
13152
+ function normalizeText(value) {
13153
+ return value?.trim() ?? "";
13154
+ }
13155
+ function matchCoursesToIntegrations(derivedCourses, platformConfigs) {
13156
+ const platformByCourse = new Map(
13157
+ platformConfigs.map(
13158
+ (config) => [
13159
+ courseKey({ grade: config.integration.grade, subject: config.subject }),
13160
+ config
13161
+ ]
13162
+ )
13163
+ );
13164
+ const matched = [];
13165
+ const ambiguous = [];
13166
+ const newCourses = [];
13167
+ const claimed = /* @__PURE__ */ new Set();
13168
+ for (const course of derivedCourses) {
13169
+ const exactMatch = platformByCourse.get(courseKey(course));
13170
+ if (exactMatch) {
13171
+ matched.push({ course, platformConfig: exactMatch });
13172
+ claimed.add(exactMatch.integration.courseId);
13173
+ } else {
13174
+ const sameGradeConfigs = platformConfigs.filter(
13175
+ (config) => config.integration.grade === course.grade && !claimed.has(config.integration.courseId)
13176
+ );
13177
+ if (sameGradeConfigs.length === 1) {
13178
+ matched.push({ course, platformConfig: sameGradeConfigs[0] });
13179
+ claimed.add(sameGradeConfigs[0].integration.courseId);
13180
+ } else if (sameGradeConfigs.length > 1) {
13181
+ ambiguous.push({ course, candidates: sameGradeConfigs });
13182
+ } else {
13183
+ newCourses.push(course);
13184
+ }
13185
+ }
13186
+ }
13187
+ return { matched, ambiguous, newCourses };
13188
+ }
13189
+ function detectTimebackDrift(derivedCourses, platformConfigs) {
13190
+ const { matched } = matchCoursesToIntegrations(derivedCourses, platformConfigs);
13191
+ return matched.flatMap(({ course, platformConfig: platform }) => {
13192
+ const fields = [];
13193
+ const comparisons = [
13194
+ {
13195
+ field: "title",
13196
+ configValue: normalizeText(course.title),
13197
+ platformValue: normalizeText(platform.title)
13198
+ },
13199
+ {
13200
+ field: "courseCode",
13201
+ configValue: normalizeText(course.courseCode),
13202
+ platformValue: normalizeText(platform.courseCode)
13203
+ },
13204
+ {
13205
+ field: "subject",
13206
+ configValue: normalizeText(course.subject),
13207
+ platformValue: normalizeText(platform.subject)
13208
+ },
13209
+ {
13210
+ field: "totalXp",
13211
+ configValue: normalizeTotalXp(course.totalXp),
13212
+ platformValue: normalizeTotalXp(platform.totalXp)
13213
+ },
13214
+ {
13215
+ field: "masterableUnits",
13216
+ configValue: normalizeTotalXp(course.masterableUnits),
13217
+ platformValue: normalizeTotalXp(platform.masterableUnits)
13218
+ }
13219
+ ];
13220
+ for (const comparison of comparisons) {
13221
+ if (comparison.configValue !== comparison.platformValue) {
13222
+ fields.push({
13223
+ field: comparison.field,
13224
+ configValue: comparison.configValue,
13225
+ platformValue: comparison.platformValue
13226
+ });
13227
+ }
13228
+ }
13229
+ if (fields.length === 0) {
13230
+ return [];
13231
+ }
13232
+ return [
13233
+ {
13234
+ grade: course.grade,
13235
+ subject: course.subject,
13236
+ courseId: platform.integration.courseId,
13237
+ fields
13238
+ }
13239
+ ];
13240
+ });
13241
+ }
13242
+
13137
13243
  // src/lib/timeback/setup.ts
13138
13244
  import { bold as bold11 } from "colorette";
13139
13245
  function displaySetupDryRun(derivedRequest, env, logger2) {
@@ -13170,6 +13276,123 @@ function displaySetupDryRun(derivedRequest, env, logger2) {
13170
13276
  logger2.newLine();
13171
13277
  }
13172
13278
 
13279
+ // src/lib/timeback/update.ts
13280
+ import { confirm as confirm7, select as select6 } from "@inquirer/prompts";
13281
+ import { green as green9, red as red7 } from "colorette";
13282
+ function formatDriftValue(value) {
13283
+ return value === null ? "(none)" : value.toString();
13284
+ }
13285
+ function displayDriftWarning(drifts) {
13286
+ for (const drift of drifts) {
13287
+ logger.bold(`${drift.subject} Grade ${drift.grade}`, 1);
13288
+ for (const field of drift.fields) {
13289
+ const value = `${red7(formatDriftValue(field.platformValue))} \u2192 ${green9(formatDriftValue(field.configValue))}`;
13290
+ logger.data(field.field, value, 2);
13291
+ }
13292
+ logger.newLine();
13293
+ }
13294
+ }
13295
+ async function confirmDriftOverwrite(options) {
13296
+ if (options.force) {
13297
+ return;
13298
+ }
13299
+ if (isNonInteractive()) {
13300
+ throw new Error(
13301
+ "TimeBack config drift detected. Re-run with --force to overwrite platform values."
13302
+ );
13303
+ }
13304
+ const shouldContinue = await confirm7({
13305
+ message: "Overwrite platform values?",
13306
+ default: false
13307
+ });
13308
+ if (!shouldContinue) {
13309
+ throw new Error("TimeBack update canceled");
13310
+ }
13311
+ logger.newLine();
13312
+ }
13313
+ async function fetchPlatformConfigs(client, gameId) {
13314
+ const integrations = await runStep(
13315
+ "Checking for existing TimeBack integrations",
13316
+ () => client.timeback.management.get(gameId),
13317
+ (result) => `Found ${result.length} course(s)`
13318
+ );
13319
+ return Promise.all(
13320
+ integrations.map(
13321
+ (integration) => getTimebackIntegrationConfig(client, gameId, integration.courseId)
13322
+ )
13323
+ );
13324
+ }
13325
+ async function updateExistingCourses(client, gameId, matched) {
13326
+ return runStep(
13327
+ `Updating ${matched.length} existing course(s)`,
13328
+ async () => {
13329
+ const results = [];
13330
+ for (const { course, platformConfig } of matched) {
13331
+ const result = await client.timeback.management.updateIntegration(
13332
+ gameId,
13333
+ platformConfig.integration.courseId,
13334
+ {
13335
+ title: course.title,
13336
+ courseCode: course.courseCode,
13337
+ subject: course.subject,
13338
+ totalXp: course.totalXp ?? null,
13339
+ masterableUnits: course.masterableUnits ?? null
13340
+ }
13341
+ );
13342
+ results.push(result);
13343
+ }
13344
+ return results;
13345
+ },
13346
+ (results) => `Updated ${results.length} course(s)`
13347
+ );
13348
+ }
13349
+ async function resolveAmbiguousCourses(ambiguous) {
13350
+ if (ambiguous.length === 0) {
13351
+ return { matched: [], newCourses: [] };
13352
+ }
13353
+ if (isNonInteractive()) {
13354
+ throw new Error(
13355
+ `Cannot match ${ambiguous.length} course(s) to existing integrations \u2014 multiple integrations exist for the same grade. Re-run interactively to disambiguate.`
13356
+ );
13357
+ }
13358
+ const matched = [];
13359
+ const newCourses = [];
13360
+ for (const { course, candidates } of ambiguous) {
13361
+ const NEW_COURSE_VALUE = "__new__";
13362
+ const chosen = await select6({
13363
+ message: `"${course.subject} Grade ${course.grade}" doesn't match any existing course. Does it replace one?`,
13364
+ choices: [
13365
+ ...candidates.map((config) => ({
13366
+ value: config.integration.courseId,
13367
+ name: `${config.subject} Grade ${config.integration.grade} (${config.integration.courseId})`
13368
+ })),
13369
+ {
13370
+ value: NEW_COURSE_VALUE,
13371
+ name: "No \u2014 create a new course"
13372
+ }
13373
+ ]
13374
+ });
13375
+ if (chosen === NEW_COURSE_VALUE) {
13376
+ newCourses.push(course);
13377
+ } else {
13378
+ const platformConfig = candidates.find((c) => c.integration.courseId === chosen);
13379
+ matched.push({ course, platformConfig });
13380
+ }
13381
+ }
13382
+ return { matched, newCourses };
13383
+ }
13384
+ async function createNewCourses(client, gameId, newCourses, baseConfig) {
13385
+ return runStep(
13386
+ `Creating ${newCourses.length} new course(s)`,
13387
+ () => client.timeback.management.setup({
13388
+ gameId,
13389
+ courses: newCourses,
13390
+ baseConfig
13391
+ }),
13392
+ (results) => `Created ${results.integrations.length} course(s)`
13393
+ );
13394
+ }
13395
+
13173
13396
  // src/lib/timeback/validate.ts
13174
13397
  function hasNumber(value) {
13175
13398
  return typeof value === "number";
@@ -13833,9 +14056,11 @@ export {
13833
14056
  computeSecretsDiff,
13834
14057
  computeSecretsHashes,
13835
14058
  confirmDeploymentPlan,
14059
+ confirmDriftOverwrite,
13836
14060
  connectToLogStream,
13837
14061
  countSqlStatements,
13838
14062
  createClient,
14063
+ createNewCourses,
13839
14064
  createProjectDirectory,
13840
14065
  createSeedWorkerEntry,
13841
14066
  createViteConfig,
@@ -13843,6 +14068,7 @@ export {
13843
14068
  derivePlatformTimebackSetupRequest,
13844
14069
  deriveSourcedIds,
13845
14070
  detectExistingEngine,
14071
+ detectTimebackDrift,
13846
14072
  discoverCustomRoutes,
13847
14073
  discoverMetricsResolver,
13848
14074
  discoverQueueHandlers,
@@ -13854,6 +14080,7 @@ export {
13854
14080
  displayDeploymentDiff,
13855
14081
  displayDomainStatus,
13856
14082
  displayDomainValidationRecords,
14083
+ displayDriftWarning,
13857
14084
  displayIntegrationDetails,
13858
14085
  displayIntegrationList,
13859
14086
  displayLogEntry,
@@ -13872,6 +14099,7 @@ export {
13872
14099
  ensureRootGitignore,
13873
14100
  executeSeedFile,
13874
14101
  fetchLatestVersion,
14102
+ fetchPlatformConfigs,
13875
14103
  fetchTemplate,
13876
14104
  findConfigPath,
13877
14105
  findSingleBuildZip,
@@ -13920,6 +14148,7 @@ export {
13920
14148
  getSchemaInfo,
13921
14149
  getSchemaStatementCount,
13922
14150
  getSlugFromConfig,
14151
+ getTimebackIntegrationConfig,
13923
14152
  getUnappliedMigrations,
13924
14153
  getUserFacingIntegrationNames,
13925
14154
  getWebBaseUrl,
@@ -13976,6 +14205,7 @@ export {
13976
14205
  logAndExit,
13977
14206
  logDeploymentPlanDebug,
13978
14207
  logger,
14208
+ matchCoursesToIntegrations,
13979
14209
  matchesGitignorePattern,
13980
14210
  needsBackend,
13981
14211
  normalizeEnvironment,
@@ -14017,6 +14247,7 @@ export {
14017
14247
  requireEnvironment,
14018
14248
  resetCliContext,
14019
14249
  resetDatabase,
14250
+ resolveAmbiguousCourses,
14020
14251
  resolveChannel,
14021
14252
  resolveSchemaStrategy,
14022
14253
  runInit,
@@ -14044,6 +14275,7 @@ export {
14044
14275
  updateBuildMetadata,
14045
14276
  updateConfigFile,
14046
14277
  updateEnvExample,
14278
+ updateExistingCourses,
14047
14279
  updateViteConfig,
14048
14280
  upgradeStandalone,
14049
14281
  uploadFilesLocal,
@@ -221,11 +221,12 @@ var init_timeback = __esm({
221
221
  });
222
222
 
223
223
  // ../constants/src/workers.ts
224
- var SECRETS_PREFIX;
224
+ var SECRETS_PREFIX, QUEUE_INGRESS_AUTH_HEADER;
225
225
  var init_workers = __esm({
226
226
  "../constants/src/workers.ts"() {
227
227
  "use strict";
228
228
  SECRETS_PREFIX = "secrets_";
229
+ QUEUE_INGRESS_AUTH_HEADER = "x-playcademy-queue-auth";
229
230
  }
230
231
  });
231
232
 
@@ -352,9 +353,9 @@ var init_routes = __esm({
352
353
  // ../edge-play/src/entry/metadata.ts
353
354
  function getRuntimeMetadata() {
354
355
  return {
355
- cliVersion: true ? "0.21.0" : "0.0.0-dev",
356
- sdkVersion: true ? "0.8.0" : "0.0.0-dev",
357
- buildId: true ? "8f15cae30c82" : "dev-source"
356
+ cliVersion: true ? "0.21.1-beta.2" : "0.0.0-dev",
357
+ sdkVersion: true ? "0.8.1-beta.2" : "0.0.0-dev",
358
+ buildId: true ? "2e237b524a89" : "dev-source"
358
359
  };
359
360
  }
360
361
  var init_metadata = __esm({
@@ -4550,6 +4551,7 @@ function registerAssetFallback(app) {
4550
4551
  }
4551
4552
 
4552
4553
  // ../edge-play/src/entry/queue.ts
4554
+ init_src();
4553
4555
  function findQueueKey(queueName, queueHandlers) {
4554
4556
  for (const key of Object.keys(queueHandlers)) {
4555
4557
  if (queueName === key || queueName.endsWith(`--${key}`)) {
@@ -4592,6 +4594,13 @@ function toMessageBatch(payload, fallbackQueueName) {
4592
4594
  }
4593
4595
  function registerQueueIngressRoute(app, queueHandlers) {
4594
4596
  app.post("/__playcademy/queue/:name", async (c) => {
4597
+ const expectedSecret = c.env.QUEUE_INGRESS_SECRET;
4598
+ if (expectedSecret) {
4599
+ const provided = c.req.header(QUEUE_INGRESS_AUTH_HEADER);
4600
+ if (provided !== expectedSecret) {
4601
+ return c.json({ error: "Unauthorized" }, 403);
4602
+ }
4603
+ }
4595
4604
  const name = c.req.param("name");
4596
4605
  const handler = queueHandlers[name];
4597
4606
  if (!handler?.queue) {
@@ -1,7 +1,7 @@
1
1
  {
2
- "cliVersion": "0.21.0",
3
- "sdkVersion": "0.8.0",
4
- "runtimeBuildId": "8f15cae30c82",
5
- "inputFingerprint": "8f15cae30c8264c6e610e256ec1d2942be37da9bbdad8797ae1410a43e96113e",
2
+ "cliVersion": "0.21.1-beta.2",
3
+ "sdkVersion": "0.8.1-beta.2",
4
+ "runtimeBuildId": "2e237b524a89",
5
+ "inputFingerprint": "2e237b524a898fec8226e02ad2237041fc19c6c9551db3cd59565072961c6bb5",
6
6
  "entry": "index.js"
7
7
  }
package/dist/utils.d.ts CHANGED
@@ -91,6 +91,8 @@ interface CourseMetadata {
91
91
  isCustom?: boolean;
92
92
  /** Signals whether a course is in production with students */
93
93
  publishStatus?: 'draft' | 'testing' | 'published' | 'deactivated';
94
+ /** Whether this course appears in the TimeBack catalog for teachers and parents */
95
+ timebackVisible?: boolean;
94
96
  /** Who to contact when issues reported with questions */
95
97
  contactEmail?: string;
96
98
  /** Primary app identifier */
package/dist/utils.js CHANGED
@@ -481,7 +481,7 @@ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
481
481
  // ../better-auth/package.json
482
482
  var package_default = {
483
483
  name: "@playcademy/better-auth",
484
- version: "0.0.12",
484
+ version: "0.0.13-beta.2",
485
485
  type: "module",
486
486
  exports: {
487
487
  "./server": {
@@ -2591,7 +2591,7 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync2, writeFileSync as wr
2591
2591
  import { dirname as dirname4, join as join14 } from "node:path";
2592
2592
 
2593
2593
  // src/version.ts
2594
- var cliVersion = false ? "0.0.0-dev" : "0.21.0";
2594
+ var cliVersion = false ? "0.0.0-dev" : "0.21.1-beta.2";
2595
2595
 
2596
2596
  // src/lib/build/binary-resource.ts
2597
2597
  function writeFileTree(baseDir, files) {
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var cliVersion = false ? "0.0.0-dev" : "0.21.0";
2
+ var cliVersion = false ? "0.0.0-dev" : "0.21.1-beta.2";
3
3
  export {
4
4
  cliVersion
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.21.0",
3
+ "version": "0.21.1-beta.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {