timeback-studio 0.1.3 → 0.1.4
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/bin.js +137 -111
- package/dist/cli/commands/serve/config.d.ts +1 -1
- package/dist/cli/commands/serve/types.d.ts +1 -1
- package/dist/cli/commands/serve/types.d.ts.map +1 -1
- package/dist/cli/lib/onboarding/index.d.ts +1 -1
- package/dist/config/timeback.d.ts +1 -1
- package/dist/index.js +137 -111
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -16926,10 +16926,9 @@ var playcademyParser = {
|
|
|
16926
16926
|
};
|
|
16927
16927
|
|
|
16928
16928
|
// ../internal/cli-infra/src/config/timeback.ts
|
|
16929
|
-
import { existsSync } from "node:fs";
|
|
16930
16929
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
16931
|
-
import { basename, relative, resolve } from "node:path";
|
|
16932
|
-
import {
|
|
16930
|
+
import { basename, extname, relative, resolve } from "node:path";
|
|
16931
|
+
import { loadConfig as c12LoadConfig } from "c12";
|
|
16933
16932
|
// ../types/src/zod/primitives.ts
|
|
16934
16933
|
var IsoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
16935
16934
|
var IsoDateTimeString = exports_external.string().min(1).regex(IsoDateTimeRegex, "must be a valid ISO 8601 datetime");
|
|
@@ -16945,7 +16944,7 @@ var TimebackSubject = exports_external.enum([
|
|
|
16945
16944
|
"Math",
|
|
16946
16945
|
"None",
|
|
16947
16946
|
"Other"
|
|
16948
|
-
]);
|
|
16947
|
+
]).meta({ id: "TimebackSubject", description: "Subject area" });
|
|
16949
16948
|
var TimebackGrade = exports_external.union([
|
|
16950
16949
|
exports_external.literal(-1),
|
|
16951
16950
|
exports_external.literal(0),
|
|
@@ -16962,7 +16961,10 @@ var TimebackGrade = exports_external.union([
|
|
|
16962
16961
|
exports_external.literal(11),
|
|
16963
16962
|
exports_external.literal(12),
|
|
16964
16963
|
exports_external.literal(13)
|
|
16965
|
-
])
|
|
16964
|
+
]).meta({
|
|
16965
|
+
id: "TimebackGrade",
|
|
16966
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
16967
|
+
});
|
|
16966
16968
|
var ScoreStatus = exports_external.enum([
|
|
16967
16969
|
"exempt",
|
|
16968
16970
|
"fully graded",
|
|
@@ -17194,62 +17196,84 @@ var CaliperListEventsParams = exports_external.object({
|
|
|
17194
17196
|
}).strict();
|
|
17195
17197
|
// ../types/src/zod/config.ts
|
|
17196
17198
|
var CourseIds = exports_external.object({
|
|
17197
|
-
staging: exports_external.string().optional(),
|
|
17198
|
-
production: exports_external.string().optional()
|
|
17199
|
-
});
|
|
17200
|
-
var CourseType = exports_external.enum(["base", "hole-filling", "optional"]);
|
|
17201
|
-
var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]);
|
|
17199
|
+
staging: exports_external.string().meta({ description: "Course ID in staging environment" }).optional(),
|
|
17200
|
+
production: exports_external.string().meta({ description: "Course ID in production environment" }).optional()
|
|
17201
|
+
}).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
|
|
17202
|
+
var CourseType = exports_external.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
|
|
17203
|
+
var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
|
|
17202
17204
|
var CourseGoals = exports_external.object({
|
|
17203
|
-
dailyXp: exports_external.number().int().positive().optional(),
|
|
17204
|
-
dailyLessons: exports_external.number().int().positive().optional(),
|
|
17205
|
-
dailyActiveMinutes: exports_external.number().int().positive().optional(),
|
|
17206
|
-
dailyAccuracy: exports_external.number().int().min(0).max(100).optional(),
|
|
17207
|
-
dailyMasteredUnits: exports_external.number().int().positive().optional()
|
|
17208
|
-
});
|
|
17205
|
+
dailyXp: exports_external.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
|
|
17206
|
+
dailyLessons: exports_external.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
|
|
17207
|
+
dailyActiveMinutes: exports_external.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
|
|
17208
|
+
dailyAccuracy: exports_external.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
|
|
17209
|
+
dailyMasteredUnits: exports_external.number().int().positive().meta({ description: "Target units to master per day" }).optional()
|
|
17210
|
+
}).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
|
|
17209
17211
|
var CourseMetrics = exports_external.object({
|
|
17210
|
-
totalXp: exports_external.number().int().positive().optional(),
|
|
17211
|
-
totalLessons: exports_external.number().int().positive().optional(),
|
|
17212
|
-
totalGrades: exports_external.number().int().positive().optional()
|
|
17213
|
-
});
|
|
17212
|
+
totalXp: exports_external.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
|
|
17213
|
+
totalLessons: exports_external.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
|
|
17214
|
+
totalGrades: exports_external.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
|
|
17215
|
+
}).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
|
|
17214
17216
|
var CourseMetadata = exports_external.object({
|
|
17215
17217
|
courseType: CourseType.optional(),
|
|
17216
|
-
isSupplemental: exports_external.boolean().optional(),
|
|
17217
|
-
isCustom: exports_external.boolean().optional(),
|
|
17218
|
+
isSupplemental: exports_external.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
|
|
17219
|
+
isCustom: exports_external.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
|
|
17218
17220
|
publishStatus: PublishStatus.optional(),
|
|
17219
|
-
contactEmail: exports_external.email().optional(),
|
|
17220
|
-
primaryApp: exports_external.string().optional(),
|
|
17221
|
+
contactEmail: exports_external.email().meta({ description: "Contact email for course issues" }).optional(),
|
|
17222
|
+
primaryApp: exports_external.string().meta({ description: "Primary application identifier" }).optional(),
|
|
17221
17223
|
goals: CourseGoals.optional(),
|
|
17222
17224
|
metrics: CourseMetrics.optional()
|
|
17223
|
-
});
|
|
17225
|
+
}).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
|
|
17224
17226
|
var CourseDefaults = exports_external.object({
|
|
17225
|
-
courseCode: exports_external.string().optional(),
|
|
17226
|
-
level: exports_external.string().optional(),
|
|
17227
|
+
courseCode: exports_external.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
|
|
17228
|
+
level: exports_external.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
|
|
17227
17229
|
metadata: CourseMetadata.optional()
|
|
17230
|
+
}).meta({
|
|
17231
|
+
id: "CourseDefaults",
|
|
17232
|
+
description: "Default properties that apply to all courses unless overridden"
|
|
17228
17233
|
});
|
|
17229
17234
|
var CourseEnvOverrides = exports_external.object({
|
|
17230
|
-
level: exports_external.string().optional(),
|
|
17231
|
-
sensor: exports_external.
|
|
17232
|
-
launchUrl: exports_external.
|
|
17235
|
+
level: exports_external.string().meta({ description: "Course level for this environment" }).optional(),
|
|
17236
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
|
|
17237
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this environment" }).optional(),
|
|
17233
17238
|
metadata: CourseMetadata.optional()
|
|
17239
|
+
}).meta({
|
|
17240
|
+
id: "CourseEnvOverrides",
|
|
17241
|
+
description: "Environment-specific course overrides (non-identity fields)"
|
|
17234
17242
|
});
|
|
17235
17243
|
var CourseOverrides = exports_external.object({
|
|
17236
|
-
staging: CourseEnvOverrides.
|
|
17237
|
-
|
|
17238
|
-
})
|
|
17244
|
+
staging: CourseEnvOverrides.meta({
|
|
17245
|
+
description: "Overrides for staging environment"
|
|
17246
|
+
}).optional(),
|
|
17247
|
+
production: CourseEnvOverrides.meta({
|
|
17248
|
+
description: "Overrides for production environment"
|
|
17249
|
+
}).optional()
|
|
17250
|
+
}).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
|
|
17239
17251
|
var CourseConfig = CourseDefaults.extend({
|
|
17240
|
-
subject: TimebackSubject,
|
|
17241
|
-
grade: TimebackGrade.
|
|
17252
|
+
subject: TimebackSubject.meta({ description: "Subject area for this course" }),
|
|
17253
|
+
grade: TimebackGrade.meta({
|
|
17254
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
17255
|
+
}).optional(),
|
|
17242
17256
|
ids: CourseIds.nullable().optional(),
|
|
17243
|
-
sensor: exports_external.
|
|
17244
|
-
launchUrl: exports_external.
|
|
17257
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
|
|
17258
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this course" }).optional(),
|
|
17245
17259
|
overrides: CourseOverrides.optional()
|
|
17260
|
+
}).meta({
|
|
17261
|
+
id: "CourseConfig",
|
|
17262
|
+
description: "Configuration for a single course. Must have either grade or courseCode (or both)."
|
|
17246
17263
|
});
|
|
17247
17264
|
var TimebackConfig = exports_external.object({
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17265
|
+
$schema: exports_external.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
|
|
17266
|
+
name: exports_external.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
|
|
17267
|
+
defaults: CourseDefaults.meta({
|
|
17268
|
+
description: "Default properties applied to all courses"
|
|
17269
|
+
}).optional(),
|
|
17270
|
+
courses: exports_external.array(CourseConfig).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
|
|
17271
|
+
sensor: exports_external.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
|
|
17272
|
+
launchUrl: exports_external.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
|
|
17273
|
+
}).meta({
|
|
17274
|
+
id: "TimebackConfig",
|
|
17275
|
+
title: "Timeback Config",
|
|
17276
|
+
description: "Configuration schema for timeback.config.json files"
|
|
17253
17277
|
}).refine((config2) => {
|
|
17254
17278
|
return config2.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
|
|
17255
17279
|
}, {
|
|
@@ -18264,26 +18288,10 @@ var QtiLessonFeedbackInput = exports_external.object({
|
|
|
18264
18288
|
humanApproved: exports_external.boolean().optional()
|
|
18265
18289
|
}).strict();
|
|
18266
18290
|
// ../internal/cli-infra/src/config/timeback.ts
|
|
18267
|
-
var
|
|
18268
|
-
var
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
if (jitiInstance)
|
|
18272
|
-
return jitiInstance;
|
|
18273
|
-
const { createJiti } = await import("jiti");
|
|
18274
|
-
jitiInstance = createJiti(import.meta.url, { fsCache: false });
|
|
18275
|
-
return jitiInstance;
|
|
18276
|
-
}
|
|
18277
|
-
async function importModule(fullPath) {
|
|
18278
|
-
let module;
|
|
18279
|
-
if (isBun) {
|
|
18280
|
-
const fileUrl = pathToFileURL(fullPath).href;
|
|
18281
|
-
module = await import(fileUrl);
|
|
18282
|
-
} else {
|
|
18283
|
-
const jiti = await getJiti();
|
|
18284
|
-
module = await jiti.import(fullPath);
|
|
18285
|
-
}
|
|
18286
|
-
return module.default ?? module;
|
|
18291
|
+
var CONFIG_FILENAME = "timeback.config.json";
|
|
18292
|
+
var FILE_PATTERNS2 = [CONFIG_FILENAME];
|
|
18293
|
+
function isJsonConfigPath(configPath) {
|
|
18294
|
+
return extname(configPath).toLowerCase() === ".json";
|
|
18287
18295
|
}
|
|
18288
18296
|
function deriveCourseIds(config3) {
|
|
18289
18297
|
const result = { staging: [], production: [] };
|
|
@@ -18301,58 +18309,73 @@ async function readPackageVersion(cwd) {
|
|
|
18301
18309
|
return "0.0.0";
|
|
18302
18310
|
}
|
|
18303
18311
|
}
|
|
18312
|
+
async function loadWithC12(cwd, configPath) {
|
|
18313
|
+
if (configPath && !isJsonConfigPath(configPath)) {
|
|
18314
|
+
throw new Error(`Config file must be JSON (.json): ${configPath}`);
|
|
18315
|
+
}
|
|
18316
|
+
const result = await c12LoadConfig({
|
|
18317
|
+
cwd,
|
|
18318
|
+
name: "timeback",
|
|
18319
|
+
configFile: configPath ?? CONFIG_FILENAME,
|
|
18320
|
+
rcFile: false,
|
|
18321
|
+
packageJson: false,
|
|
18322
|
+
dotenv: false,
|
|
18323
|
+
envName: false,
|
|
18324
|
+
extend: false,
|
|
18325
|
+
omit$Keys: true,
|
|
18326
|
+
defaults: {},
|
|
18327
|
+
overrides: {}
|
|
18328
|
+
});
|
|
18329
|
+
if (!result.config || Object.keys(result.config).length === 0) {
|
|
18330
|
+
return null;
|
|
18331
|
+
}
|
|
18332
|
+
const rawConfig = result.config;
|
|
18333
|
+
if ("extends" in rawConfig) {
|
|
18334
|
+
throw new Error("The 'extends' feature is not supported in timeback.config.json. " + "Please inline all configuration.");
|
|
18335
|
+
}
|
|
18336
|
+
const { $schema: _schema, ...configWithoutSchema } = rawConfig;
|
|
18337
|
+
return {
|
|
18338
|
+
config: configWithoutSchema,
|
|
18339
|
+
configFile: result.configFile ?? resolve(cwd, configPath ?? CONFIG_FILENAME)
|
|
18340
|
+
};
|
|
18341
|
+
}
|
|
18304
18342
|
async function parse6() {
|
|
18305
18343
|
const cwd = process.cwd();
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18313
|
-
try {
|
|
18314
|
-
rawConfig = await importModule(fullPath);
|
|
18315
|
-
foundPath = configPath;
|
|
18316
|
-
break;
|
|
18317
|
-
} catch (err) {
|
|
18318
|
-
loadError = err instanceof Error ? err : new Error(String(err));
|
|
18319
|
-
foundPath = configPath;
|
|
18320
|
-
break;
|
|
18344
|
+
try {
|
|
18345
|
+
const loaded = await loadWithC12(cwd);
|
|
18346
|
+
if (!loaded) {
|
|
18347
|
+
return {
|
|
18348
|
+
success: false,
|
|
18349
|
+
error: `No timeback config found. Create ${CONFIG_FILENAME}`
|
|
18350
|
+
};
|
|
18321
18351
|
}
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18352
|
+
const result = TimebackConfig.safeParse(loaded.config);
|
|
18353
|
+
if (!result.success) {
|
|
18354
|
+
const issues = result.error.issues.map((issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
18355
|
+
`);
|
|
18356
|
+
return {
|
|
18357
|
+
success: false,
|
|
18358
|
+
error: `Invalid config in ${CONFIG_FILENAME}:
|
|
18359
|
+
${issues}`
|
|
18360
|
+
};
|
|
18361
|
+
}
|
|
18362
|
+
const version2 = await readPackageVersion(cwd);
|
|
18363
|
+
const baseConfig = { ...result.data, version: version2 };
|
|
18331
18364
|
return {
|
|
18332
|
-
success:
|
|
18333
|
-
|
|
18365
|
+
success: true,
|
|
18366
|
+
config: {
|
|
18367
|
+
...baseConfig,
|
|
18368
|
+
path: loaded.configFile,
|
|
18369
|
+
courseIds: deriveCourseIds(baseConfig)
|
|
18370
|
+
}
|
|
18334
18371
|
};
|
|
18335
|
-
}
|
|
18336
|
-
const result = TimebackConfig.safeParse(rawConfig);
|
|
18337
|
-
if (!result.success) {
|
|
18338
|
-
const issues = result.error.issues.map((issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
18339
|
-
`);
|
|
18372
|
+
} catch (err) {
|
|
18340
18373
|
return {
|
|
18341
18374
|
success: false,
|
|
18342
|
-
error: `
|
|
18343
|
-
${
|
|
18375
|
+
error: `Failed to load ${CONFIG_FILENAME}:
|
|
18376
|
+
${err instanceof Error ? err.message : String(err)}`
|
|
18344
18377
|
};
|
|
18345
18378
|
}
|
|
18346
|
-
const version2 = await readPackageVersion(cwd);
|
|
18347
|
-
const baseConfig = { ...result.data, version: version2 };
|
|
18348
|
-
return {
|
|
18349
|
-
success: true,
|
|
18350
|
-
config: {
|
|
18351
|
-
...baseConfig,
|
|
18352
|
-
path: `${cwd}/${foundPath}`,
|
|
18353
|
-
courseIds: deriveCourseIds(baseConfig)
|
|
18354
|
-
}
|
|
18355
|
-
};
|
|
18356
18379
|
}
|
|
18357
18380
|
function printError2(error48) {
|
|
18358
18381
|
console.log();
|
|
@@ -18360,13 +18383,16 @@ function printError2(error48) {
|
|
|
18360
18383
|
console.log();
|
|
18361
18384
|
console.log(` ${error48}`);
|
|
18362
18385
|
console.log();
|
|
18363
|
-
console.log(` ${dim("Example timeback.config.
|
|
18386
|
+
console.log(` ${dim("Example timeback.config.json:")}`);
|
|
18364
18387
|
console.log();
|
|
18365
|
-
console.log(` ${yellow("
|
|
18366
|
-
console.log(` ${yellow("
|
|
18367
|
-
console.log(` ${yellow("
|
|
18368
|
-
console.log(` ${yellow("
|
|
18369
|
-
console.log(` ${yellow("
|
|
18388
|
+
console.log(` ${yellow("{")}`);
|
|
18389
|
+
console.log(` ${yellow(' "$schema": "https://timeback.dev/schema.json",')}`);
|
|
18390
|
+
console.log(` ${yellow(' "name": "My Timeback App",')}`);
|
|
18391
|
+
console.log(` ${yellow(' "launchUrl": "https://example.com/play",')}`);
|
|
18392
|
+
console.log(` ${yellow(' "sensor": "https://example.com/sensor",')}`);
|
|
18393
|
+
console.log(` ${yellow(' "courses": [')}`);
|
|
18394
|
+
console.log(` ${yellow(' { "subject": "Math", "grade": 3 }')}`);
|
|
18395
|
+
console.log(` ${yellow(" ]")}`);
|
|
18370
18396
|
console.log(` ${yellow("}")}`);
|
|
18371
18397
|
console.log();
|
|
18372
18398
|
}
|
|
@@ -49,7 +49,7 @@ export declare function getEffectiveSensors(config: LoadedUserConfig, opts: Serv
|
|
|
49
49
|
*
|
|
50
50
|
* Resolution priority:
|
|
51
51
|
* 1. CLI course IDs (if provided)
|
|
52
|
-
* 2. Config file (timeback.config.
|
|
52
|
+
* 2. Config file (timeback.config.json / playcademy.config.ts)
|
|
53
53
|
* 3. Interactive import flow
|
|
54
54
|
*
|
|
55
55
|
* @param courseIds - Course IDs from CLI positional args
|
|
@@ -10,7 +10,7 @@ export interface ServeOptions extends ParserOptions {
|
|
|
10
10
|
export interface ResolvedConfig {
|
|
11
11
|
userConfig: LoadedUserConfig;
|
|
12
12
|
environment: Environment;
|
|
13
|
-
/** Config filename for logging (e.g., 'timeback.config.
|
|
13
|
+
/** Config filename for logging (e.g., 'timeback.config.json') */
|
|
14
14
|
configFile?: string;
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEnF,MAAM,WAAW,YAAa,SAAQ,aAAa;IAClD,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,WAAW,EAAE,WAAW,CAAA;IACxB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEnF,MAAM,WAAW,YAAa,SAAQ,aAAa;IAClD,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,WAAW,EAAE,WAAW,CAAA;IACxB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Onboarding Utilities
|
|
3
3
|
*
|
|
4
|
-
* Handles first-time setup when timeback.config.
|
|
4
|
+
* Handles first-time setup when timeback.config.json is missing.
|
|
5
5
|
* Runs import flow and returns data in-memory (no file writes).
|
|
6
6
|
*/
|
|
7
7
|
import type { Environment, EnvironmentCredentials } from '../../../config';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Timeback Config Loading
|
|
3
3
|
*
|
|
4
|
-
* Utilities for loading and validating the user's timeback.config.
|
|
4
|
+
* Utilities for loading and validating the user's timeback.config.json file.
|
|
5
5
|
*/
|
|
6
6
|
import type { ConfigParseResult, ParserOptions } from '@timeback/internal-cli-infra';
|
|
7
7
|
/**
|
package/dist/index.js
CHANGED
|
@@ -14816,10 +14816,9 @@ var playcademyParser = {
|
|
|
14816
14816
|
};
|
|
14817
14817
|
|
|
14818
14818
|
// ../internal/cli-infra/src/config/timeback.ts
|
|
14819
|
-
import { existsSync } from "node:fs";
|
|
14820
14819
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
14821
|
-
import { basename, relative, resolve } from "node:path";
|
|
14822
|
-
import {
|
|
14820
|
+
import { basename, extname, relative, resolve } from "node:path";
|
|
14821
|
+
import { loadConfig as c12LoadConfig } from "c12";
|
|
14823
14822
|
// ../types/src/zod/primitives.ts
|
|
14824
14823
|
var IsoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
14825
14824
|
var IsoDateTimeString = exports_external.string().min(1).regex(IsoDateTimeRegex, "must be a valid ISO 8601 datetime");
|
|
@@ -14835,7 +14834,7 @@ var TimebackSubject = exports_external.enum([
|
|
|
14835
14834
|
"Math",
|
|
14836
14835
|
"None",
|
|
14837
14836
|
"Other"
|
|
14838
|
-
]);
|
|
14837
|
+
]).meta({ id: "TimebackSubject", description: "Subject area" });
|
|
14839
14838
|
var TimebackGrade = exports_external.union([
|
|
14840
14839
|
exports_external.literal(-1),
|
|
14841
14840
|
exports_external.literal(0),
|
|
@@ -14852,7 +14851,10 @@ var TimebackGrade = exports_external.union([
|
|
|
14852
14851
|
exports_external.literal(11),
|
|
14853
14852
|
exports_external.literal(12),
|
|
14854
14853
|
exports_external.literal(13)
|
|
14855
|
-
])
|
|
14854
|
+
]).meta({
|
|
14855
|
+
id: "TimebackGrade",
|
|
14856
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
14857
|
+
});
|
|
14856
14858
|
var ScoreStatus = exports_external.enum([
|
|
14857
14859
|
"exempt",
|
|
14858
14860
|
"fully graded",
|
|
@@ -15084,62 +15086,84 @@ var CaliperListEventsParams = exports_external.object({
|
|
|
15084
15086
|
}).strict();
|
|
15085
15087
|
// ../types/src/zod/config.ts
|
|
15086
15088
|
var CourseIds = exports_external.object({
|
|
15087
|
-
staging: exports_external.string().optional(),
|
|
15088
|
-
production: exports_external.string().optional()
|
|
15089
|
-
});
|
|
15090
|
-
var CourseType = exports_external.enum(["base", "hole-filling", "optional"]);
|
|
15091
|
-
var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]);
|
|
15089
|
+
staging: exports_external.string().meta({ description: "Course ID in staging environment" }).optional(),
|
|
15090
|
+
production: exports_external.string().meta({ description: "Course ID in production environment" }).optional()
|
|
15091
|
+
}).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
|
|
15092
|
+
var CourseType = exports_external.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
|
|
15093
|
+
var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
|
|
15092
15094
|
var CourseGoals = exports_external.object({
|
|
15093
|
-
dailyXp: exports_external.number().int().positive().optional(),
|
|
15094
|
-
dailyLessons: exports_external.number().int().positive().optional(),
|
|
15095
|
-
dailyActiveMinutes: exports_external.number().int().positive().optional(),
|
|
15096
|
-
dailyAccuracy: exports_external.number().int().min(0).max(100).optional(),
|
|
15097
|
-
dailyMasteredUnits: exports_external.number().int().positive().optional()
|
|
15098
|
-
});
|
|
15095
|
+
dailyXp: exports_external.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
|
|
15096
|
+
dailyLessons: exports_external.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
|
|
15097
|
+
dailyActiveMinutes: exports_external.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
|
|
15098
|
+
dailyAccuracy: exports_external.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
|
|
15099
|
+
dailyMasteredUnits: exports_external.number().int().positive().meta({ description: "Target units to master per day" }).optional()
|
|
15100
|
+
}).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
|
|
15099
15101
|
var CourseMetrics = exports_external.object({
|
|
15100
|
-
totalXp: exports_external.number().int().positive().optional(),
|
|
15101
|
-
totalLessons: exports_external.number().int().positive().optional(),
|
|
15102
|
-
totalGrades: exports_external.number().int().positive().optional()
|
|
15103
|
-
});
|
|
15102
|
+
totalXp: exports_external.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
|
|
15103
|
+
totalLessons: exports_external.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
|
|
15104
|
+
totalGrades: exports_external.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
|
|
15105
|
+
}).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
|
|
15104
15106
|
var CourseMetadata = exports_external.object({
|
|
15105
15107
|
courseType: CourseType.optional(),
|
|
15106
|
-
isSupplemental: exports_external.boolean().optional(),
|
|
15107
|
-
isCustom: exports_external.boolean().optional(),
|
|
15108
|
+
isSupplemental: exports_external.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
|
|
15109
|
+
isCustom: exports_external.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
|
|
15108
15110
|
publishStatus: PublishStatus.optional(),
|
|
15109
|
-
contactEmail: exports_external.email().optional(),
|
|
15110
|
-
primaryApp: exports_external.string().optional(),
|
|
15111
|
+
contactEmail: exports_external.email().meta({ description: "Contact email for course issues" }).optional(),
|
|
15112
|
+
primaryApp: exports_external.string().meta({ description: "Primary application identifier" }).optional(),
|
|
15111
15113
|
goals: CourseGoals.optional(),
|
|
15112
15114
|
metrics: CourseMetrics.optional()
|
|
15113
|
-
});
|
|
15115
|
+
}).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
|
|
15114
15116
|
var CourseDefaults = exports_external.object({
|
|
15115
|
-
courseCode: exports_external.string().optional(),
|
|
15116
|
-
level: exports_external.string().optional(),
|
|
15117
|
+
courseCode: exports_external.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
|
|
15118
|
+
level: exports_external.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
|
|
15117
15119
|
metadata: CourseMetadata.optional()
|
|
15120
|
+
}).meta({
|
|
15121
|
+
id: "CourseDefaults",
|
|
15122
|
+
description: "Default properties that apply to all courses unless overridden"
|
|
15118
15123
|
});
|
|
15119
15124
|
var CourseEnvOverrides = exports_external.object({
|
|
15120
|
-
level: exports_external.string().optional(),
|
|
15121
|
-
sensor: exports_external.
|
|
15122
|
-
launchUrl: exports_external.
|
|
15125
|
+
level: exports_external.string().meta({ description: "Course level for this environment" }).optional(),
|
|
15126
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
|
|
15127
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this environment" }).optional(),
|
|
15123
15128
|
metadata: CourseMetadata.optional()
|
|
15129
|
+
}).meta({
|
|
15130
|
+
id: "CourseEnvOverrides",
|
|
15131
|
+
description: "Environment-specific course overrides (non-identity fields)"
|
|
15124
15132
|
});
|
|
15125
15133
|
var CourseOverrides = exports_external.object({
|
|
15126
|
-
staging: CourseEnvOverrides.
|
|
15127
|
-
|
|
15128
|
-
})
|
|
15134
|
+
staging: CourseEnvOverrides.meta({
|
|
15135
|
+
description: "Overrides for staging environment"
|
|
15136
|
+
}).optional(),
|
|
15137
|
+
production: CourseEnvOverrides.meta({
|
|
15138
|
+
description: "Overrides for production environment"
|
|
15139
|
+
}).optional()
|
|
15140
|
+
}).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
|
|
15129
15141
|
var CourseConfig = CourseDefaults.extend({
|
|
15130
|
-
subject: TimebackSubject,
|
|
15131
|
-
grade: TimebackGrade.
|
|
15142
|
+
subject: TimebackSubject.meta({ description: "Subject area for this course" }),
|
|
15143
|
+
grade: TimebackGrade.meta({
|
|
15144
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
15145
|
+
}).optional(),
|
|
15132
15146
|
ids: CourseIds.nullable().optional(),
|
|
15133
|
-
sensor: exports_external.
|
|
15134
|
-
launchUrl: exports_external.
|
|
15147
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
|
|
15148
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this course" }).optional(),
|
|
15135
15149
|
overrides: CourseOverrides.optional()
|
|
15150
|
+
}).meta({
|
|
15151
|
+
id: "CourseConfig",
|
|
15152
|
+
description: "Configuration for a single course. Must have either grade or courseCode (or both)."
|
|
15136
15153
|
});
|
|
15137
15154
|
var TimebackConfig = exports_external.object({
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15155
|
+
$schema: exports_external.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
|
|
15156
|
+
name: exports_external.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
|
|
15157
|
+
defaults: CourseDefaults.meta({
|
|
15158
|
+
description: "Default properties applied to all courses"
|
|
15159
|
+
}).optional(),
|
|
15160
|
+
courses: exports_external.array(CourseConfig).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
|
|
15161
|
+
sensor: exports_external.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
|
|
15162
|
+
launchUrl: exports_external.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
|
|
15163
|
+
}).meta({
|
|
15164
|
+
id: "TimebackConfig",
|
|
15165
|
+
title: "Timeback Config",
|
|
15166
|
+
description: "Configuration schema for timeback.config.json files"
|
|
15143
15167
|
}).refine((config2) => {
|
|
15144
15168
|
return config2.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
|
|
15145
15169
|
}, {
|
|
@@ -16154,26 +16178,10 @@ var QtiLessonFeedbackInput = exports_external.object({
|
|
|
16154
16178
|
humanApproved: exports_external.boolean().optional()
|
|
16155
16179
|
}).strict();
|
|
16156
16180
|
// ../internal/cli-infra/src/config/timeback.ts
|
|
16157
|
-
var
|
|
16158
|
-
var
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
if (jitiInstance)
|
|
16162
|
-
return jitiInstance;
|
|
16163
|
-
const { createJiti } = await import("jiti");
|
|
16164
|
-
jitiInstance = createJiti(import.meta.url, { fsCache: false });
|
|
16165
|
-
return jitiInstance;
|
|
16166
|
-
}
|
|
16167
|
-
async function importModule(fullPath) {
|
|
16168
|
-
let module;
|
|
16169
|
-
if (isBun) {
|
|
16170
|
-
const fileUrl = pathToFileURL(fullPath).href;
|
|
16171
|
-
module = await import(fileUrl);
|
|
16172
|
-
} else {
|
|
16173
|
-
const jiti = await getJiti();
|
|
16174
|
-
module = await jiti.import(fullPath);
|
|
16175
|
-
}
|
|
16176
|
-
return module.default ?? module;
|
|
16181
|
+
var CONFIG_FILENAME = "timeback.config.json";
|
|
16182
|
+
var FILE_PATTERNS2 = [CONFIG_FILENAME];
|
|
16183
|
+
function isJsonConfigPath(configPath) {
|
|
16184
|
+
return extname(configPath).toLowerCase() === ".json";
|
|
16177
16185
|
}
|
|
16178
16186
|
function deriveCourseIds(config3) {
|
|
16179
16187
|
const result = { staging: [], production: [] };
|
|
@@ -16191,58 +16199,73 @@ async function readPackageVersion(cwd) {
|
|
|
16191
16199
|
return "0.0.0";
|
|
16192
16200
|
}
|
|
16193
16201
|
}
|
|
16202
|
+
async function loadWithC12(cwd, configPath) {
|
|
16203
|
+
if (configPath && !isJsonConfigPath(configPath)) {
|
|
16204
|
+
throw new Error(`Config file must be JSON (.json): ${configPath}`);
|
|
16205
|
+
}
|
|
16206
|
+
const result = await c12LoadConfig({
|
|
16207
|
+
cwd,
|
|
16208
|
+
name: "timeback",
|
|
16209
|
+
configFile: configPath ?? CONFIG_FILENAME,
|
|
16210
|
+
rcFile: false,
|
|
16211
|
+
packageJson: false,
|
|
16212
|
+
dotenv: false,
|
|
16213
|
+
envName: false,
|
|
16214
|
+
extend: false,
|
|
16215
|
+
omit$Keys: true,
|
|
16216
|
+
defaults: {},
|
|
16217
|
+
overrides: {}
|
|
16218
|
+
});
|
|
16219
|
+
if (!result.config || Object.keys(result.config).length === 0) {
|
|
16220
|
+
return null;
|
|
16221
|
+
}
|
|
16222
|
+
const rawConfig = result.config;
|
|
16223
|
+
if ("extends" in rawConfig) {
|
|
16224
|
+
throw new Error("The 'extends' feature is not supported in timeback.config.json. " + "Please inline all configuration.");
|
|
16225
|
+
}
|
|
16226
|
+
const { $schema: _schema, ...configWithoutSchema } = rawConfig;
|
|
16227
|
+
return {
|
|
16228
|
+
config: configWithoutSchema,
|
|
16229
|
+
configFile: result.configFile ?? resolve(cwd, configPath ?? CONFIG_FILENAME)
|
|
16230
|
+
};
|
|
16231
|
+
}
|
|
16194
16232
|
async function parse6() {
|
|
16195
16233
|
const cwd = process.cwd();
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
try {
|
|
16204
|
-
rawConfig = await importModule(fullPath);
|
|
16205
|
-
foundPath = configPath;
|
|
16206
|
-
break;
|
|
16207
|
-
} catch (err) {
|
|
16208
|
-
loadError = err instanceof Error ? err : new Error(String(err));
|
|
16209
|
-
foundPath = configPath;
|
|
16210
|
-
break;
|
|
16234
|
+
try {
|
|
16235
|
+
const loaded = await loadWithC12(cwd);
|
|
16236
|
+
if (!loaded) {
|
|
16237
|
+
return {
|
|
16238
|
+
success: false,
|
|
16239
|
+
error: `No timeback config found. Create ${CONFIG_FILENAME}`
|
|
16240
|
+
};
|
|
16211
16241
|
}
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16242
|
+
const result = TimebackConfig.safeParse(loaded.config);
|
|
16243
|
+
if (!result.success) {
|
|
16244
|
+
const issues = result.error.issues.map((issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
16245
|
+
`);
|
|
16246
|
+
return {
|
|
16247
|
+
success: false,
|
|
16248
|
+
error: `Invalid config in ${CONFIG_FILENAME}:
|
|
16249
|
+
${issues}`
|
|
16250
|
+
};
|
|
16251
|
+
}
|
|
16252
|
+
const version2 = await readPackageVersion(cwd);
|
|
16253
|
+
const baseConfig = { ...result.data, version: version2 };
|
|
16221
16254
|
return {
|
|
16222
|
-
success:
|
|
16223
|
-
|
|
16255
|
+
success: true,
|
|
16256
|
+
config: {
|
|
16257
|
+
...baseConfig,
|
|
16258
|
+
path: loaded.configFile,
|
|
16259
|
+
courseIds: deriveCourseIds(baseConfig)
|
|
16260
|
+
}
|
|
16224
16261
|
};
|
|
16225
|
-
}
|
|
16226
|
-
const result = TimebackConfig.safeParse(rawConfig);
|
|
16227
|
-
if (!result.success) {
|
|
16228
|
-
const issues = result.error.issues.map((issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
16229
|
-
`);
|
|
16262
|
+
} catch (err) {
|
|
16230
16263
|
return {
|
|
16231
16264
|
success: false,
|
|
16232
|
-
error: `
|
|
16233
|
-
${
|
|
16265
|
+
error: `Failed to load ${CONFIG_FILENAME}:
|
|
16266
|
+
${err instanceof Error ? err.message : String(err)}`
|
|
16234
16267
|
};
|
|
16235
16268
|
}
|
|
16236
|
-
const version2 = await readPackageVersion(cwd);
|
|
16237
|
-
const baseConfig = { ...result.data, version: version2 };
|
|
16238
|
-
return {
|
|
16239
|
-
success: true,
|
|
16240
|
-
config: {
|
|
16241
|
-
...baseConfig,
|
|
16242
|
-
path: `${cwd}/${foundPath}`,
|
|
16243
|
-
courseIds: deriveCourseIds(baseConfig)
|
|
16244
|
-
}
|
|
16245
|
-
};
|
|
16246
16269
|
}
|
|
16247
16270
|
function printError2(error48) {
|
|
16248
16271
|
console.log();
|
|
@@ -16250,13 +16273,16 @@ function printError2(error48) {
|
|
|
16250
16273
|
console.log();
|
|
16251
16274
|
console.log(` ${error48}`);
|
|
16252
16275
|
console.log();
|
|
16253
|
-
console.log(` ${dim("Example timeback.config.
|
|
16276
|
+
console.log(` ${dim("Example timeback.config.json:")}`);
|
|
16254
16277
|
console.log();
|
|
16255
|
-
console.log(` ${yellow("
|
|
16256
|
-
console.log(` ${yellow("
|
|
16257
|
-
console.log(` ${yellow("
|
|
16258
|
-
console.log(` ${yellow("
|
|
16259
|
-
console.log(` ${yellow("
|
|
16278
|
+
console.log(` ${yellow("{")}`);
|
|
16279
|
+
console.log(` ${yellow(' "$schema": "https://timeback.dev/schema.json",')}`);
|
|
16280
|
+
console.log(` ${yellow(' "name": "My Timeback App",')}`);
|
|
16281
|
+
console.log(` ${yellow(' "launchUrl": "https://example.com/play",')}`);
|
|
16282
|
+
console.log(` ${yellow(' "sensor": "https://example.com/sensor",')}`);
|
|
16283
|
+
console.log(` ${yellow(' "courses": [')}`);
|
|
16284
|
+
console.log(` ${yellow(' { "subject": "Math", "grade": 3 }')}`);
|
|
16285
|
+
console.log(` ${yellow(" ]")}`);
|
|
16260
16286
|
console.log(` ${yellow("}")}`);
|
|
16261
16287
|
console.log();
|
|
16262
16288
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "timeback-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"@clack/prompts": "^0.11.0",
|
|
29
29
|
"@hono/node-server": "^1.19.7",
|
|
30
30
|
"@timeback/core": "0.1.3",
|
|
31
|
+
"c12": "^3.3.3",
|
|
31
32
|
"colorette": "^2.0.20",
|
|
32
33
|
"commander": "^14.0.2",
|
|
33
34
|
"hono": "^4.11.1",
|
|
34
|
-
"jiti": "^2.6.1",
|
|
35
35
|
"zod": "^4.2.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|