timeback-studio 0.1.3 → 0.1.5
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 +1546 -472
- package/dist/cli/commands/credentials/lib/initial.d.ts +9 -2
- package/dist/cli/commands/credentials/lib/initial.d.ts.map +1 -1
- package/dist/cli/commands/serve/config.d.ts +51 -7
- package/dist/cli/commands/serve/config.d.ts.map +1 -1
- package/dist/cli/commands/serve/env.d.ts +32 -0
- package/dist/cli/commands/serve/env.d.ts.map +1 -0
- package/dist/cli/commands/serve/index.d.ts +5 -3
- package/dist/cli/commands/serve/index.d.ts.map +1 -1
- package/dist/cli/commands/serve/server.d.ts.map +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/courses.d.ts +32 -0
- package/dist/cli/lib/courses.d.ts.map +1 -1
- package/dist/cli/lib/credentials.d.ts +11 -24
- package/dist/cli/lib/credentials.d.ts.map +1 -1
- package/dist/cli/lib/index.d.ts +2 -2
- package/dist/cli/lib/index.d.ts.map +1 -1
- package/dist/cli/lib/onboarding/index.d.ts +1 -1
- package/dist/cli/lib/types.d.ts +13 -0
- package/dist/cli/lib/types.d.ts.map +1 -0
- package/dist/config/timeback.d.ts +1 -1
- package/dist/config/types.d.ts +2 -8
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.js +15403 -14329
- package/dist/server/controllers/enrollment.d.ts.map +1 -1
- package/dist/server/services/bootstrap.d.ts.map +1 -1
- package/dist/server/services/enrollment.d.ts +5 -3
- package/dist/server/services/enrollment.d.ts.map +1 -1
- package/dist/server/services/types/enrollment.d.ts +6 -1
- package/dist/server/services/types/enrollment.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -3148,7 +3148,7 @@ var {
|
|
|
3148
3148
|
function isCancelled(value) {
|
|
3149
3149
|
return typeof value === "symbol";
|
|
3150
3150
|
}
|
|
3151
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
3151
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
3152
3152
|
var exports_external = {};
|
|
3153
3153
|
__export(exports_external, {
|
|
3154
3154
|
xor: () => xor,
|
|
@@ -3389,7 +3389,7 @@ __export(exports_external, {
|
|
|
3389
3389
|
$brand: () => $brand
|
|
3390
3390
|
});
|
|
3391
3391
|
|
|
3392
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
3392
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/index.js
|
|
3393
3393
|
var exports_core2 = {};
|
|
3394
3394
|
__export(exports_core2, {
|
|
3395
3395
|
version: () => version,
|
|
@@ -3667,7 +3667,7 @@ __export(exports_core2, {
|
|
|
3667
3667
|
$ZodAny: () => $ZodAny
|
|
3668
3668
|
});
|
|
3669
3669
|
|
|
3670
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
3670
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
|
|
3671
3671
|
var NEVER = Object.freeze({
|
|
3672
3672
|
status: "aborted"
|
|
3673
3673
|
});
|
|
@@ -3743,7 +3743,7 @@ function config(newConfig) {
|
|
|
3743
3743
|
Object.assign(globalConfig, newConfig);
|
|
3744
3744
|
return globalConfig;
|
|
3745
3745
|
}
|
|
3746
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
3746
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/util.js
|
|
3747
3747
|
var exports_util = {};
|
|
3748
3748
|
__export(exports_util, {
|
|
3749
3749
|
unwrapMessage: () => unwrapMessage,
|
|
@@ -4417,7 +4417,7 @@ class Class {
|
|
|
4417
4417
|
constructor(..._args) {}
|
|
4418
4418
|
}
|
|
4419
4419
|
|
|
4420
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
4420
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/errors.js
|
|
4421
4421
|
var initializer = (inst, def) => {
|
|
4422
4422
|
inst.name = "$ZodError";
|
|
4423
4423
|
Object.defineProperty(inst, "_zod", {
|
|
@@ -4554,7 +4554,7 @@ function prettifyError(error) {
|
|
|
4554
4554
|
`);
|
|
4555
4555
|
}
|
|
4556
4556
|
|
|
4557
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
4557
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/parse.js
|
|
4558
4558
|
var _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
4559
4559
|
const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
|
|
4560
4560
|
const result = schema._zod.run({ value, issues: [] }, ctx);
|
|
@@ -4641,7 +4641,7 @@ var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
|
4641
4641
|
return _safeParseAsync(_Err)(schema, value, _ctx);
|
|
4642
4642
|
};
|
|
4643
4643
|
var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync($ZodRealError);
|
|
4644
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
4644
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/regexes.js
|
|
4645
4645
|
var exports_regexes = {};
|
|
4646
4646
|
__export(exports_regexes, {
|
|
4647
4647
|
xid: () => xid,
|
|
@@ -4798,7 +4798,7 @@ var sha512_hex = /^[0-9a-fA-F]{128}$/;
|
|
|
4798
4798
|
var sha512_base64 = /* @__PURE__ */ fixedBase64(86, "==");
|
|
4799
4799
|
var sha512_base64url = /* @__PURE__ */ fixedBase64url(86);
|
|
4800
4800
|
|
|
4801
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
4801
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/checks.js
|
|
4802
4802
|
var $ZodCheck = /* @__PURE__ */ $constructor("$ZodCheck", (inst, def) => {
|
|
4803
4803
|
var _a;
|
|
4804
4804
|
inst._zod ?? (inst._zod = {});
|
|
@@ -5345,7 +5345,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (ins
|
|
|
5345
5345
|
};
|
|
5346
5346
|
});
|
|
5347
5347
|
|
|
5348
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
5348
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/doc.js
|
|
5349
5349
|
class Doc {
|
|
5350
5350
|
constructor(args = []) {
|
|
5351
5351
|
this.content = [];
|
|
@@ -5383,14 +5383,14 @@ class Doc {
|
|
|
5383
5383
|
}
|
|
5384
5384
|
}
|
|
5385
5385
|
|
|
5386
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
5386
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/versions.js
|
|
5387
5387
|
var version = {
|
|
5388
5388
|
major: 4,
|
|
5389
5389
|
minor: 3,
|
|
5390
|
-
patch:
|
|
5390
|
+
patch: 6
|
|
5391
5391
|
};
|
|
5392
5392
|
|
|
5393
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
5393
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/schemas.js
|
|
5394
5394
|
var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
|
|
5395
5395
|
var _a;
|
|
5396
5396
|
inst ?? (inst = {});
|
|
@@ -6673,7 +6673,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
6673
6673
|
if (keyResult instanceof Promise) {
|
|
6674
6674
|
throw new Error("Async schemas not supported in object keys currently");
|
|
6675
6675
|
}
|
|
6676
|
-
const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length
|
|
6676
|
+
const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
|
|
6677
6677
|
if (checkNumericKey) {
|
|
6678
6678
|
const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
|
|
6679
6679
|
if (retryResult instanceof Promise) {
|
|
@@ -7352,7 +7352,7 @@ function handleRefineResult(result, payload, input, inst) {
|
|
|
7352
7352
|
payload.issues.push(issue(_iss));
|
|
7353
7353
|
}
|
|
7354
7354
|
}
|
|
7355
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7355
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/index.js
|
|
7356
7356
|
var exports_locales = {};
|
|
7357
7357
|
__export(exports_locales, {
|
|
7358
7358
|
zhTW: () => zh_TW_default,
|
|
@@ -7406,7 +7406,7 @@ __export(exports_locales, {
|
|
|
7406
7406
|
ar: () => ar_default
|
|
7407
7407
|
});
|
|
7408
7408
|
|
|
7409
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7409
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ar.js
|
|
7410
7410
|
var error = () => {
|
|
7411
7411
|
const Sizable = {
|
|
7412
7412
|
string: { unit: "حرف", verb: "أن يحوي" },
|
|
@@ -7512,7 +7512,7 @@ function ar_default() {
|
|
|
7512
7512
|
localeError: error()
|
|
7513
7513
|
};
|
|
7514
7514
|
}
|
|
7515
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7515
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/az.js
|
|
7516
7516
|
var error2 = () => {
|
|
7517
7517
|
const Sizable = {
|
|
7518
7518
|
string: { unit: "simvol", verb: "olmalıdır" },
|
|
@@ -7617,7 +7617,7 @@ function az_default() {
|
|
|
7617
7617
|
localeError: error2()
|
|
7618
7618
|
};
|
|
7619
7619
|
}
|
|
7620
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7620
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/be.js
|
|
7621
7621
|
function getBelarusianPlural(count, one, few, many) {
|
|
7622
7622
|
const absCount = Math.abs(count);
|
|
7623
7623
|
const lastDigit = absCount % 10;
|
|
@@ -7773,7 +7773,7 @@ function be_default() {
|
|
|
7773
7773
|
localeError: error3()
|
|
7774
7774
|
};
|
|
7775
7775
|
}
|
|
7776
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7776
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/bg.js
|
|
7777
7777
|
var error4 = () => {
|
|
7778
7778
|
const Sizable = {
|
|
7779
7779
|
string: { unit: "символа", verb: "да съдържа" },
|
|
@@ -7893,7 +7893,7 @@ function bg_default() {
|
|
|
7893
7893
|
localeError: error4()
|
|
7894
7894
|
};
|
|
7895
7895
|
}
|
|
7896
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
7896
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ca.js
|
|
7897
7897
|
var error5 = () => {
|
|
7898
7898
|
const Sizable = {
|
|
7899
7899
|
string: { unit: "caràcters", verb: "contenir" },
|
|
@@ -8000,7 +8000,7 @@ function ca_default() {
|
|
|
8000
8000
|
localeError: error5()
|
|
8001
8001
|
};
|
|
8002
8002
|
}
|
|
8003
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8003
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/cs.js
|
|
8004
8004
|
var error6 = () => {
|
|
8005
8005
|
const Sizable = {
|
|
8006
8006
|
string: { unit: "znaků", verb: "mít" },
|
|
@@ -8111,7 +8111,7 @@ function cs_default() {
|
|
|
8111
8111
|
localeError: error6()
|
|
8112
8112
|
};
|
|
8113
8113
|
}
|
|
8114
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8114
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/da.js
|
|
8115
8115
|
var error7 = () => {
|
|
8116
8116
|
const Sizable = {
|
|
8117
8117
|
string: { unit: "tegn", verb: "havde" },
|
|
@@ -8226,7 +8226,7 @@ function da_default() {
|
|
|
8226
8226
|
localeError: error7()
|
|
8227
8227
|
};
|
|
8228
8228
|
}
|
|
8229
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8229
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/de.js
|
|
8230
8230
|
var error8 = () => {
|
|
8231
8231
|
const Sizable = {
|
|
8232
8232
|
string: { unit: "Zeichen", verb: "zu haben" },
|
|
@@ -8334,7 +8334,7 @@ function de_default() {
|
|
|
8334
8334
|
localeError: error8()
|
|
8335
8335
|
};
|
|
8336
8336
|
}
|
|
8337
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8337
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/en.js
|
|
8338
8338
|
var error9 = () => {
|
|
8339
8339
|
const Sizable = {
|
|
8340
8340
|
string: { unit: "characters", verb: "to have" },
|
|
@@ -8440,7 +8440,7 @@ function en_default() {
|
|
|
8440
8440
|
localeError: error9()
|
|
8441
8441
|
};
|
|
8442
8442
|
}
|
|
8443
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8443
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/eo.js
|
|
8444
8444
|
var error10 = () => {
|
|
8445
8445
|
const Sizable = {
|
|
8446
8446
|
string: { unit: "karaktrojn", verb: "havi" },
|
|
@@ -8549,7 +8549,7 @@ function eo_default() {
|
|
|
8549
8549
|
localeError: error10()
|
|
8550
8550
|
};
|
|
8551
8551
|
}
|
|
8552
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8552
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/es.js
|
|
8553
8553
|
var error11 = () => {
|
|
8554
8554
|
const Sizable = {
|
|
8555
8555
|
string: { unit: "caracteres", verb: "tener" },
|
|
@@ -8681,7 +8681,7 @@ function es_default() {
|
|
|
8681
8681
|
localeError: error11()
|
|
8682
8682
|
};
|
|
8683
8683
|
}
|
|
8684
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8684
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fa.js
|
|
8685
8685
|
var error12 = () => {
|
|
8686
8686
|
const Sizable = {
|
|
8687
8687
|
string: { unit: "کاراکتر", verb: "داشته باشد" },
|
|
@@ -8795,7 +8795,7 @@ function fa_default() {
|
|
|
8795
8795
|
localeError: error12()
|
|
8796
8796
|
};
|
|
8797
8797
|
}
|
|
8798
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8798
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fi.js
|
|
8799
8799
|
var error13 = () => {
|
|
8800
8800
|
const Sizable = {
|
|
8801
8801
|
string: { unit: "merkkiä", subject: "merkkijonon" },
|
|
@@ -8907,7 +8907,7 @@ function fi_default() {
|
|
|
8907
8907
|
localeError: error13()
|
|
8908
8908
|
};
|
|
8909
8909
|
}
|
|
8910
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
8910
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr.js
|
|
8911
8911
|
var error14 = () => {
|
|
8912
8912
|
const Sizable = {
|
|
8913
8913
|
string: { unit: "caractères", verb: "avoir" },
|
|
@@ -9015,7 +9015,7 @@ function fr_default() {
|
|
|
9015
9015
|
localeError: error14()
|
|
9016
9016
|
};
|
|
9017
9017
|
}
|
|
9018
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9018
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr-CA.js
|
|
9019
9019
|
var error15 = () => {
|
|
9020
9020
|
const Sizable = {
|
|
9021
9021
|
string: { unit: "caractères", verb: "avoir" },
|
|
@@ -9122,7 +9122,7 @@ function fr_CA_default() {
|
|
|
9122
9122
|
localeError: error15()
|
|
9123
9123
|
};
|
|
9124
9124
|
}
|
|
9125
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9125
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/he.js
|
|
9126
9126
|
var error16 = () => {
|
|
9127
9127
|
const TypeNames = {
|
|
9128
9128
|
string: { label: "מחרוזת", gender: "f" },
|
|
@@ -9315,7 +9315,7 @@ function he_default() {
|
|
|
9315
9315
|
localeError: error16()
|
|
9316
9316
|
};
|
|
9317
9317
|
}
|
|
9318
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9318
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hu.js
|
|
9319
9319
|
var error17 = () => {
|
|
9320
9320
|
const Sizable = {
|
|
9321
9321
|
string: { unit: "karakter", verb: "legyen" },
|
|
@@ -9423,7 +9423,7 @@ function hu_default() {
|
|
|
9423
9423
|
localeError: error17()
|
|
9424
9424
|
};
|
|
9425
9425
|
}
|
|
9426
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9426
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hy.js
|
|
9427
9427
|
function getArmenianPlural(count, one, many) {
|
|
9428
9428
|
return Math.abs(count) === 1 ? one : many;
|
|
9429
9429
|
}
|
|
@@ -9570,7 +9570,7 @@ function hy_default() {
|
|
|
9570
9570
|
localeError: error18()
|
|
9571
9571
|
};
|
|
9572
9572
|
}
|
|
9573
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9573
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/id.js
|
|
9574
9574
|
var error19 = () => {
|
|
9575
9575
|
const Sizable = {
|
|
9576
9576
|
string: { unit: "karakter", verb: "memiliki" },
|
|
@@ -9676,7 +9676,7 @@ function id_default() {
|
|
|
9676
9676
|
localeError: error19()
|
|
9677
9677
|
};
|
|
9678
9678
|
}
|
|
9679
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9679
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/is.js
|
|
9680
9680
|
var error20 = () => {
|
|
9681
9681
|
const Sizable = {
|
|
9682
9682
|
string: { unit: "stafi", verb: "að hafa" },
|
|
@@ -9785,7 +9785,7 @@ function is_default() {
|
|
|
9785
9785
|
localeError: error20()
|
|
9786
9786
|
};
|
|
9787
9787
|
}
|
|
9788
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9788
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/it.js
|
|
9789
9789
|
var error21 = () => {
|
|
9790
9790
|
const Sizable = {
|
|
9791
9791
|
string: { unit: "caratteri", verb: "avere" },
|
|
@@ -9893,7 +9893,7 @@ function it_default() {
|
|
|
9893
9893
|
localeError: error21()
|
|
9894
9894
|
};
|
|
9895
9895
|
}
|
|
9896
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
9896
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ja.js
|
|
9897
9897
|
var error22 = () => {
|
|
9898
9898
|
const Sizable = {
|
|
9899
9899
|
string: { unit: "文字", verb: "である" },
|
|
@@ -10000,7 +10000,7 @@ function ja_default() {
|
|
|
10000
10000
|
localeError: error22()
|
|
10001
10001
|
};
|
|
10002
10002
|
}
|
|
10003
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10003
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ka.js
|
|
10004
10004
|
var error23 = () => {
|
|
10005
10005
|
const Sizable = {
|
|
10006
10006
|
string: { unit: "სიმბოლო", verb: "უნდა შეიცავდეს" },
|
|
@@ -10112,7 +10112,7 @@ function ka_default() {
|
|
|
10112
10112
|
localeError: error23()
|
|
10113
10113
|
};
|
|
10114
10114
|
}
|
|
10115
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10115
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/km.js
|
|
10116
10116
|
var error24 = () => {
|
|
10117
10117
|
const Sizable = {
|
|
10118
10118
|
string: { unit: "តួអក្សរ", verb: "គួរមាន" },
|
|
@@ -10223,11 +10223,11 @@ function km_default() {
|
|
|
10223
10223
|
};
|
|
10224
10224
|
}
|
|
10225
10225
|
|
|
10226
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10226
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/kh.js
|
|
10227
10227
|
function kh_default() {
|
|
10228
10228
|
return km_default();
|
|
10229
10229
|
}
|
|
10230
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10230
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ko.js
|
|
10231
10231
|
var error25 = () => {
|
|
10232
10232
|
const Sizable = {
|
|
10233
10233
|
string: { unit: "문자", verb: "to have" },
|
|
@@ -10338,7 +10338,7 @@ function ko_default() {
|
|
|
10338
10338
|
localeError: error25()
|
|
10339
10339
|
};
|
|
10340
10340
|
}
|
|
10341
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10341
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/lt.js
|
|
10342
10342
|
var capitalizeFirstCharacter = (text) => {
|
|
10343
10343
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
10344
10344
|
};
|
|
@@ -10541,7 +10541,7 @@ function lt_default() {
|
|
|
10541
10541
|
localeError: error26()
|
|
10542
10542
|
};
|
|
10543
10543
|
}
|
|
10544
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10544
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/mk.js
|
|
10545
10545
|
var error27 = () => {
|
|
10546
10546
|
const Sizable = {
|
|
10547
10547
|
string: { unit: "знаци", verb: "да имаат" },
|
|
@@ -10650,7 +10650,7 @@ function mk_default() {
|
|
|
10650
10650
|
localeError: error27()
|
|
10651
10651
|
};
|
|
10652
10652
|
}
|
|
10653
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10653
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ms.js
|
|
10654
10654
|
var error28 = () => {
|
|
10655
10655
|
const Sizable = {
|
|
10656
10656
|
string: { unit: "aksara", verb: "mempunyai" },
|
|
@@ -10757,7 +10757,7 @@ function ms_default() {
|
|
|
10757
10757
|
localeError: error28()
|
|
10758
10758
|
};
|
|
10759
10759
|
}
|
|
10760
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10760
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/nl.js
|
|
10761
10761
|
var error29 = () => {
|
|
10762
10762
|
const Sizable = {
|
|
10763
10763
|
string: { unit: "tekens", verb: "heeft" },
|
|
@@ -10867,7 +10867,7 @@ function nl_default() {
|
|
|
10867
10867
|
localeError: error29()
|
|
10868
10868
|
};
|
|
10869
10869
|
}
|
|
10870
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10870
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/no.js
|
|
10871
10871
|
var error30 = () => {
|
|
10872
10872
|
const Sizable = {
|
|
10873
10873
|
string: { unit: "tegn", verb: "å ha" },
|
|
@@ -10975,7 +10975,7 @@ function no_default() {
|
|
|
10975
10975
|
localeError: error30()
|
|
10976
10976
|
};
|
|
10977
10977
|
}
|
|
10978
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
10978
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ota.js
|
|
10979
10979
|
var error31 = () => {
|
|
10980
10980
|
const Sizable = {
|
|
10981
10981
|
string: { unit: "harf", verb: "olmalıdır" },
|
|
@@ -11084,7 +11084,7 @@ function ota_default() {
|
|
|
11084
11084
|
localeError: error31()
|
|
11085
11085
|
};
|
|
11086
11086
|
}
|
|
11087
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11087
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ps.js
|
|
11088
11088
|
var error32 = () => {
|
|
11089
11089
|
const Sizable = {
|
|
11090
11090
|
string: { unit: "توکي", verb: "ولري" },
|
|
@@ -11198,7 +11198,7 @@ function ps_default() {
|
|
|
11198
11198
|
localeError: error32()
|
|
11199
11199
|
};
|
|
11200
11200
|
}
|
|
11201
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11201
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pl.js
|
|
11202
11202
|
var error33 = () => {
|
|
11203
11203
|
const Sizable = {
|
|
11204
11204
|
string: { unit: "znaków", verb: "mieć" },
|
|
@@ -11307,7 +11307,7 @@ function pl_default() {
|
|
|
11307
11307
|
localeError: error33()
|
|
11308
11308
|
};
|
|
11309
11309
|
}
|
|
11310
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11310
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pt.js
|
|
11311
11311
|
var error34 = () => {
|
|
11312
11312
|
const Sizable = {
|
|
11313
11313
|
string: { unit: "caracteres", verb: "ter" },
|
|
@@ -11415,7 +11415,7 @@ function pt_default() {
|
|
|
11415
11415
|
localeError: error34()
|
|
11416
11416
|
};
|
|
11417
11417
|
}
|
|
11418
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11418
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ru.js
|
|
11419
11419
|
function getRussianPlural(count, one, few, many) {
|
|
11420
11420
|
const absCount = Math.abs(count);
|
|
11421
11421
|
const lastDigit = absCount % 10;
|
|
@@ -11571,7 +11571,7 @@ function ru_default() {
|
|
|
11571
11571
|
localeError: error35()
|
|
11572
11572
|
};
|
|
11573
11573
|
}
|
|
11574
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11574
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sl.js
|
|
11575
11575
|
var error36 = () => {
|
|
11576
11576
|
const Sizable = {
|
|
11577
11577
|
string: { unit: "znakov", verb: "imeti" },
|
|
@@ -11680,7 +11680,7 @@ function sl_default() {
|
|
|
11680
11680
|
localeError: error36()
|
|
11681
11681
|
};
|
|
11682
11682
|
}
|
|
11683
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11683
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sv.js
|
|
11684
11684
|
var error37 = () => {
|
|
11685
11685
|
const Sizable = {
|
|
11686
11686
|
string: { unit: "tecken", verb: "att ha" },
|
|
@@ -11790,7 +11790,7 @@ function sv_default() {
|
|
|
11790
11790
|
localeError: error37()
|
|
11791
11791
|
};
|
|
11792
11792
|
}
|
|
11793
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11793
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ta.js
|
|
11794
11794
|
var error38 = () => {
|
|
11795
11795
|
const Sizable = {
|
|
11796
11796
|
string: { unit: "எழுத்துக்கள்", verb: "கொண்டிருக்க வேண்டும்" },
|
|
@@ -11900,7 +11900,7 @@ function ta_default() {
|
|
|
11900
11900
|
localeError: error38()
|
|
11901
11901
|
};
|
|
11902
11902
|
}
|
|
11903
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
11903
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/th.js
|
|
11904
11904
|
var error39 = () => {
|
|
11905
11905
|
const Sizable = {
|
|
11906
11906
|
string: { unit: "ตัวอักษร", verb: "ควรมี" },
|
|
@@ -12010,7 +12010,7 @@ function th_default() {
|
|
|
12010
12010
|
localeError: error39()
|
|
12011
12011
|
};
|
|
12012
12012
|
}
|
|
12013
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12013
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/tr.js
|
|
12014
12014
|
var error40 = () => {
|
|
12015
12015
|
const Sizable = {
|
|
12016
12016
|
string: { unit: "karakter", verb: "olmalı" },
|
|
@@ -12115,7 +12115,7 @@ function tr_default() {
|
|
|
12115
12115
|
localeError: error40()
|
|
12116
12116
|
};
|
|
12117
12117
|
}
|
|
12118
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12118
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uk.js
|
|
12119
12119
|
var error41 = () => {
|
|
12120
12120
|
const Sizable = {
|
|
12121
12121
|
string: { unit: "символів", verb: "матиме" },
|
|
@@ -12224,11 +12224,11 @@ function uk_default() {
|
|
|
12224
12224
|
};
|
|
12225
12225
|
}
|
|
12226
12226
|
|
|
12227
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12227
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ua.js
|
|
12228
12228
|
function ua_default() {
|
|
12229
12229
|
return uk_default();
|
|
12230
12230
|
}
|
|
12231
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12231
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ur.js
|
|
12232
12232
|
var error42 = () => {
|
|
12233
12233
|
const Sizable = {
|
|
12234
12234
|
string: { unit: "حروف", verb: "ہونا" },
|
|
@@ -12338,7 +12338,7 @@ function ur_default() {
|
|
|
12338
12338
|
localeError: error42()
|
|
12339
12339
|
};
|
|
12340
12340
|
}
|
|
12341
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12341
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uz.js
|
|
12342
12342
|
var error43 = () => {
|
|
12343
12343
|
const Sizable = {
|
|
12344
12344
|
string: { unit: "belgi", verb: "bo‘lishi kerak" },
|
|
@@ -12447,7 +12447,7 @@ function uz_default() {
|
|
|
12447
12447
|
localeError: error43()
|
|
12448
12448
|
};
|
|
12449
12449
|
}
|
|
12450
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12450
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/vi.js
|
|
12451
12451
|
var error44 = () => {
|
|
12452
12452
|
const Sizable = {
|
|
12453
12453
|
string: { unit: "ký tự", verb: "có" },
|
|
@@ -12555,7 +12555,7 @@ function vi_default() {
|
|
|
12555
12555
|
localeError: error44()
|
|
12556
12556
|
};
|
|
12557
12557
|
}
|
|
12558
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12558
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-CN.js
|
|
12559
12559
|
var error45 = () => {
|
|
12560
12560
|
const Sizable = {
|
|
12561
12561
|
string: { unit: "字符", verb: "包含" },
|
|
@@ -12664,7 +12664,7 @@ function zh_CN_default() {
|
|
|
12664
12664
|
localeError: error45()
|
|
12665
12665
|
};
|
|
12666
12666
|
}
|
|
12667
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12667
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-TW.js
|
|
12668
12668
|
var error46 = () => {
|
|
12669
12669
|
const Sizable = {
|
|
12670
12670
|
string: { unit: "字元", verb: "擁有" },
|
|
@@ -12771,7 +12771,7 @@ function zh_TW_default() {
|
|
|
12771
12771
|
localeError: error46()
|
|
12772
12772
|
};
|
|
12773
12773
|
}
|
|
12774
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12774
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/yo.js
|
|
12775
12775
|
var error47 = () => {
|
|
12776
12776
|
const Sizable = {
|
|
12777
12777
|
string: { unit: "àmi", verb: "ní" },
|
|
@@ -12878,7 +12878,7 @@ function yo_default() {
|
|
|
12878
12878
|
localeError: error47()
|
|
12879
12879
|
};
|
|
12880
12880
|
}
|
|
12881
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12881
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/registries.js
|
|
12882
12882
|
var _a;
|
|
12883
12883
|
var $output = Symbol("ZodOutput");
|
|
12884
12884
|
var $input = Symbol("ZodInput");
|
|
@@ -12928,7 +12928,7 @@ function registry() {
|
|
|
12928
12928
|
}
|
|
12929
12929
|
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
12930
12930
|
var globalRegistry = globalThis.__zod_globalRegistry;
|
|
12931
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
12931
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/api.js
|
|
12932
12932
|
function _string(Class2, params) {
|
|
12933
12933
|
return new Class2({
|
|
12934
12934
|
type: "string",
|
|
@@ -13848,7 +13848,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
13848
13848
|
const inst = new Class2(def);
|
|
13849
13849
|
return inst;
|
|
13850
13850
|
}
|
|
13851
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
13851
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/to-json-schema.js
|
|
13852
13852
|
function initializeContext(params) {
|
|
13853
13853
|
let target = params?.target ?? "draft-2020-12";
|
|
13854
13854
|
if (target === "draft-4")
|
|
@@ -14044,7 +14044,7 @@ function finalize(ctx, schema) {
|
|
|
14044
14044
|
}
|
|
14045
14045
|
}
|
|
14046
14046
|
}
|
|
14047
|
-
if (refSchema.$ref) {
|
|
14047
|
+
if (refSchema.$ref && refSeen.def) {
|
|
14048
14048
|
for (const key in schema2) {
|
|
14049
14049
|
if (key === "$ref" || key === "allOf")
|
|
14050
14050
|
continue;
|
|
@@ -14193,7 +14193,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
|
|
|
14193
14193
|
extractDefs(ctx, schema);
|
|
14194
14194
|
return finalize(ctx, schema);
|
|
14195
14195
|
};
|
|
14196
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
14196
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-processors.js
|
|
14197
14197
|
var formatMap = {
|
|
14198
14198
|
guid: "uuid",
|
|
14199
14199
|
url: "uri",
|
|
@@ -14738,7 +14738,7 @@ function toJSONSchema(input, params) {
|
|
|
14738
14738
|
extractDefs(ctx, input);
|
|
14739
14739
|
return finalize(ctx, input);
|
|
14740
14740
|
}
|
|
14741
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
14741
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-generator.js
|
|
14742
14742
|
class JSONSchemaGenerator {
|
|
14743
14743
|
get metadataRegistry() {
|
|
14744
14744
|
return this.ctx.metadataRegistry;
|
|
@@ -14797,9 +14797,9 @@ class JSONSchemaGenerator {
|
|
|
14797
14797
|
return plainResult;
|
|
14798
14798
|
}
|
|
14799
14799
|
}
|
|
14800
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
14800
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema.js
|
|
14801
14801
|
var exports_json_schema = {};
|
|
14802
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
14802
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
|
|
14803
14803
|
var exports_schemas2 = {};
|
|
14804
14804
|
__export(exports_schemas2, {
|
|
14805
14805
|
xor: () => xor,
|
|
@@ -14968,7 +14968,7 @@ __export(exports_schemas2, {
|
|
|
14968
14968
|
ZodAny: () => ZodAny
|
|
14969
14969
|
});
|
|
14970
14970
|
|
|
14971
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
14971
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/checks.js
|
|
14972
14972
|
var exports_checks2 = {};
|
|
14973
14973
|
__export(exports_checks2, {
|
|
14974
14974
|
uppercase: () => _uppercase,
|
|
@@ -15002,7 +15002,7 @@ __export(exports_checks2, {
|
|
|
15002
15002
|
endsWith: () => _endsWith
|
|
15003
15003
|
});
|
|
15004
15004
|
|
|
15005
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
15005
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/iso.js
|
|
15006
15006
|
var exports_iso = {};
|
|
15007
15007
|
__export(exports_iso, {
|
|
15008
15008
|
time: () => time2,
|
|
@@ -15043,7 +15043,7 @@ function duration2(params) {
|
|
|
15043
15043
|
return _isoDuration(ZodISODuration, params);
|
|
15044
15044
|
}
|
|
15045
15045
|
|
|
15046
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
15046
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/errors.js
|
|
15047
15047
|
var initializer2 = (inst, issues) => {
|
|
15048
15048
|
$ZodError.init(inst, issues);
|
|
15049
15049
|
inst.name = "ZodError";
|
|
@@ -15078,7 +15078,7 @@ var ZodRealError = $constructor("ZodError", initializer2, {
|
|
|
15078
15078
|
Parent: Error
|
|
15079
15079
|
});
|
|
15080
15080
|
|
|
15081
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
15081
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/parse.js
|
|
15082
15082
|
var parse3 = /* @__PURE__ */ _parse(ZodRealError);
|
|
15083
15083
|
var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
15084
15084
|
var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
@@ -15092,7 +15092,7 @@ var safeDecode2 = /* @__PURE__ */ _safeDecode(ZodRealError);
|
|
|
15092
15092
|
var safeEncodeAsync2 = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
|
|
15093
15093
|
var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
15094
15094
|
|
|
15095
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
15095
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
|
|
15096
15096
|
var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
15097
15097
|
$ZodType.init(inst, def);
|
|
15098
15098
|
Object.assign(inst["~standard"], {
|
|
@@ -16168,7 +16168,7 @@ function json(params) {
|
|
|
16168
16168
|
function preprocess(fn, schema) {
|
|
16169
16169
|
return pipe(transform(fn), schema);
|
|
16170
16170
|
}
|
|
16171
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
16171
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/compat.js
|
|
16172
16172
|
var ZodIssueCode = {
|
|
16173
16173
|
invalid_type: "invalid_type",
|
|
16174
16174
|
too_big: "too_big",
|
|
@@ -16192,7 +16192,7 @@ function getErrorMap() {
|
|
|
16192
16192
|
}
|
|
16193
16193
|
var ZodFirstPartyTypeKind;
|
|
16194
16194
|
(function(ZodFirstPartyTypeKind2) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
16195
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
16195
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/from-json-schema.js
|
|
16196
16196
|
var z2 = {
|
|
16197
16197
|
...exports_schemas2,
|
|
16198
16198
|
...exports_checks2,
|
|
@@ -16653,7 +16653,7 @@ function fromJSONSchema(schema, params) {
|
|
|
16653
16653
|
};
|
|
16654
16654
|
return convertSchema(schema, ctx);
|
|
16655
16655
|
}
|
|
16656
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
16656
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/coerce.js
|
|
16657
16657
|
var exports_coerce = {};
|
|
16658
16658
|
__export(exports_coerce, {
|
|
16659
16659
|
string: () => string3,
|
|
@@ -16678,7 +16678,7 @@ function date4(params) {
|
|
|
16678
16678
|
return _coercedDate(ZodDate, params);
|
|
16679
16679
|
}
|
|
16680
16680
|
|
|
16681
|
-
// ../../node_modules/.bun/zod@4.3.
|
|
16681
|
+
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
16682
16682
|
config(en_default());
|
|
16683
16683
|
// ../internal/cli-infra/src/credentials/schema.ts
|
|
16684
16684
|
var CredentialsSchema = exports_external.object({
|
|
@@ -16688,81 +16688,19 @@ var CredentialsSchema = exports_external.object({
|
|
|
16688
16688
|
});
|
|
16689
16689
|
var ClientIdSchema = exports_external.string().min(1, "Client ID is required").regex(/^[a-z0-9]+$/, "Client ID must contain only lowercase letters and numbers").length(26, "Client ID must be exactly 26 characters");
|
|
16690
16690
|
var ClientSecretSchema = exports_external.string().min(1, "Client secret is required").regex(/^[a-z0-9]+$/, "Client secret must contain only lowercase letters and numbers").max(53, "Client secret must be less than 53 characters");
|
|
16691
|
-
// ../internal/cli-infra/src/
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
env: environment,
|
|
16696
|
-
auth: { clientId, clientSecret }
|
|
16697
|
-
});
|
|
16698
|
-
try {
|
|
16699
|
-
const page = await client.oneroster.users.list({
|
|
16700
|
-
where: { email: email3 },
|
|
16701
|
-
limit: 1
|
|
16702
|
-
});
|
|
16703
|
-
if (page.data.length === 0) {
|
|
16704
|
-
return {
|
|
16705
|
-
valid: false,
|
|
16706
|
-
error: `No user found with email "${email3}" in ${environment}`
|
|
16707
|
-
};
|
|
16708
|
-
}
|
|
16709
|
-
return { valid: true };
|
|
16710
|
-
} catch (error48) {
|
|
16711
|
-
const message = error48 instanceof Error ? error48.message : "Unknown error";
|
|
16712
|
-
return { valid: false, error: `Failed to validate email: ${message}` };
|
|
16713
|
-
}
|
|
16691
|
+
// ../internal/cli-infra/src/ui.ts
|
|
16692
|
+
function intro(title) {
|
|
16693
|
+
console.log("");
|
|
16694
|
+
Ie(bgCyan(black(` ${title} `)));
|
|
16714
16695
|
}
|
|
16696
|
+
var outro = {
|
|
16697
|
+
success: (message = "Done") => Se(green(message)),
|
|
16698
|
+
cancelled: () => Se(dim("Cancelled")),
|
|
16699
|
+
error: (message) => Se(red(message)),
|
|
16700
|
+
warn: (message) => Se(yellow(message)),
|
|
16701
|
+
info: (message) => Se(dim(message))
|
|
16702
|
+
};
|
|
16715
16703
|
|
|
16716
|
-
// ../internal/cli-infra/src/credentials/prompts.ts
|
|
16717
|
-
async function promptForCredentials(environment) {
|
|
16718
|
-
const clientId = await he({
|
|
16719
|
-
message: `Client ID ${dim(`(${environment})`)}`,
|
|
16720
|
-
placeholder: "tb_client_...",
|
|
16721
|
-
validate: (value) => {
|
|
16722
|
-
const result = ClientIdSchema.safeParse(value);
|
|
16723
|
-
if (!result.success)
|
|
16724
|
-
return result.error.issues[0]?.message;
|
|
16725
|
-
}
|
|
16726
|
-
});
|
|
16727
|
-
if (isCancelled(clientId))
|
|
16728
|
-
return null;
|
|
16729
|
-
const clientSecret = await ge({
|
|
16730
|
-
message: `Client Secret ${dim(`(${environment})`)}`,
|
|
16731
|
-
validate: (value) => {
|
|
16732
|
-
const result = ClientSecretSchema.safeParse(value);
|
|
16733
|
-
if (!result.success)
|
|
16734
|
-
return result.error.issues[0]?.message;
|
|
16735
|
-
}
|
|
16736
|
-
});
|
|
16737
|
-
if (isCancelled(clientSecret))
|
|
16738
|
-
return null;
|
|
16739
|
-
const email3 = await he({
|
|
16740
|
-
message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
|
|
16741
|
-
placeholder: "you@example.com",
|
|
16742
|
-
validate: (value) => {
|
|
16743
|
-
if (!value)
|
|
16744
|
-
return;
|
|
16745
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16746
|
-
if (!emailRegex.test(value))
|
|
16747
|
-
return "Please enter a valid email";
|
|
16748
|
-
}
|
|
16749
|
-
});
|
|
16750
|
-
if (isCancelled(email3))
|
|
16751
|
-
return null;
|
|
16752
|
-
if (email3) {
|
|
16753
|
-
const s = Y2();
|
|
16754
|
-
s.start("Validating email...");
|
|
16755
|
-
const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
|
|
16756
|
-
if (!result.valid) {
|
|
16757
|
-
s.stop(red("Email validation failed"));
|
|
16758
|
-
M2.error(result.error ?? "Unknown error");
|
|
16759
|
-
M2.info("Please contact a Timeback admin to set up your account.");
|
|
16760
|
-
return null;
|
|
16761
|
-
}
|
|
16762
|
-
s.stop(green("Email validated"));
|
|
16763
|
-
}
|
|
16764
|
-
return { clientId, clientSecret, email: email3 || undefined };
|
|
16765
|
-
}
|
|
16766
16704
|
// ../internal/cli-infra/src/credentials/store.ts
|
|
16767
16705
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
16768
16706
|
import { homedir, platform as platform2 } from "node:os";
|
|
@@ -16788,12 +16726,12 @@ async function ensureCredentialsDir() {
|
|
|
16788
16726
|
try {
|
|
16789
16727
|
const stats = await stat(CREDENTIALS_DIR);
|
|
16790
16728
|
if (!stats.isDirectory()) {
|
|
16791
|
-
console.log();
|
|
16729
|
+
console.log("");
|
|
16792
16730
|
console.log(` ${red("✖")} ${bold("Cannot save credentials")}`);
|
|
16793
|
-
console.log();
|
|
16731
|
+
console.log("");
|
|
16794
16732
|
console.log(` A file exists at ${dim(CREDENTIALS_DIR)}`);
|
|
16795
16733
|
console.log(` Please remove it or rename it to allow credential storage.`);
|
|
16796
|
-
console.log();
|
|
16734
|
+
console.log("");
|
|
16797
16735
|
return false;
|
|
16798
16736
|
}
|
|
16799
16737
|
} catch {
|
|
@@ -16812,7 +16750,13 @@ async function readCredentialsStore() {
|
|
|
16812
16750
|
async function writeCredentialsStore(store) {
|
|
16813
16751
|
if (!await ensureCredentialsDir())
|
|
16814
16752
|
return false;
|
|
16815
|
-
|
|
16753
|
+
const { staging, production } = store;
|
|
16754
|
+
const cleaned = {};
|
|
16755
|
+
if (staging)
|
|
16756
|
+
cleaned.staging = staging;
|
|
16757
|
+
if (production)
|
|
16758
|
+
cleaned.production = production;
|
|
16759
|
+
await writeFile(CREDENTIALS_FILE, JSON.stringify(cleaned, null, 2), { mode: 384 });
|
|
16816
16760
|
return true;
|
|
16817
16761
|
}
|
|
16818
16762
|
async function getSavedCredentials(environment) {
|
|
@@ -16831,15 +16775,6 @@ async function saveCredentials(environment, credentials) {
|
|
|
16831
16775
|
store[environment] = credentials;
|
|
16832
16776
|
return writeCredentialsStore(store);
|
|
16833
16777
|
}
|
|
16834
|
-
async function getDefaultEnvironment() {
|
|
16835
|
-
const store = await readCredentialsStore();
|
|
16836
|
-
return store.default ?? null;
|
|
16837
|
-
}
|
|
16838
|
-
async function saveDefaultEnvironment(environment) {
|
|
16839
|
-
const store = await readCredentialsStore();
|
|
16840
|
-
store.default = environment;
|
|
16841
|
-
return writeCredentialsStore(store);
|
|
16842
|
-
}
|
|
16843
16778
|
async function clearCredentials(environment) {
|
|
16844
16779
|
if (environment) {
|
|
16845
16780
|
const store = await readCredentialsStore();
|
|
@@ -16884,18 +16819,115 @@ async function loadCredentials(environment) {
|
|
|
16884
16819
|
error: `No credentials found for ${environment}`
|
|
16885
16820
|
};
|
|
16886
16821
|
}
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16822
|
+
async function loadAllCredentials() {
|
|
16823
|
+
const configuredEnvs = await getConfiguredEnvironments();
|
|
16824
|
+
const credentials = {};
|
|
16825
|
+
for (const env2 of configuredEnvs) {
|
|
16826
|
+
const result = await loadCredentials(env2);
|
|
16827
|
+
if (result.success) {
|
|
16828
|
+
credentials[env2] = result.credentials;
|
|
16829
|
+
}
|
|
16830
|
+
}
|
|
16831
|
+
return credentials;
|
|
16832
|
+
}
|
|
16833
|
+
|
|
16834
|
+
// ../internal/cli-infra/src/credentials/validation.ts
|
|
16835
|
+
import { TimebackClient } from "@timeback/core";
|
|
16836
|
+
async function validateEmailWithTimeback(environment, clientId, clientSecret, email3) {
|
|
16837
|
+
const client = new TimebackClient({
|
|
16838
|
+
env: environment,
|
|
16839
|
+
auth: { clientId, clientSecret }
|
|
16840
|
+
});
|
|
16841
|
+
try {
|
|
16842
|
+
const page = await client.oneroster.users.list({
|
|
16843
|
+
where: { email: email3 },
|
|
16844
|
+
limit: 1
|
|
16845
|
+
});
|
|
16846
|
+
if (page.data.length === 0) {
|
|
16847
|
+
return {
|
|
16848
|
+
valid: false,
|
|
16849
|
+
error: `No user found with email "${email3}" in ${environment}`
|
|
16850
|
+
};
|
|
16851
|
+
}
|
|
16852
|
+
return { valid: true };
|
|
16853
|
+
} catch (error48) {
|
|
16854
|
+
const message = error48 instanceof Error ? error48.message : "Unknown error";
|
|
16855
|
+
return { valid: false, error: `Failed to validate email: ${message}` };
|
|
16856
|
+
}
|
|
16857
|
+
}
|
|
16858
|
+
|
|
16859
|
+
// ../internal/cli-infra/src/credentials/prompts.ts
|
|
16860
|
+
async function promptForCredentials(environment) {
|
|
16861
|
+
const clientId = await he({
|
|
16862
|
+
message: `Client ID ${dim(`(${environment})`)}`,
|
|
16863
|
+
placeholder: "tb_client_...",
|
|
16864
|
+
validate: (value) => {
|
|
16865
|
+
const result = ClientIdSchema.safeParse(value);
|
|
16866
|
+
if (!result.success)
|
|
16867
|
+
return result.error.issues[0]?.message;
|
|
16868
|
+
}
|
|
16869
|
+
});
|
|
16870
|
+
if (isCancelled(clientId))
|
|
16871
|
+
return null;
|
|
16872
|
+
const clientSecret = await ge({
|
|
16873
|
+
message: `Client Secret ${dim(`(${environment})`)}`,
|
|
16874
|
+
validate: (value) => {
|
|
16875
|
+
const result = ClientSecretSchema.safeParse(value);
|
|
16876
|
+
if (!result.success)
|
|
16877
|
+
return result.error.issues[0]?.message;
|
|
16878
|
+
}
|
|
16879
|
+
});
|
|
16880
|
+
if (isCancelled(clientSecret))
|
|
16881
|
+
return null;
|
|
16882
|
+
const email3 = await he({
|
|
16883
|
+
message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
|
|
16884
|
+
placeholder: "you@example.com",
|
|
16885
|
+
validate: (value) => {
|
|
16886
|
+
if (!value)
|
|
16887
|
+
return;
|
|
16888
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16889
|
+
if (!emailRegex.test(value))
|
|
16890
|
+
return "Please enter a valid email";
|
|
16891
|
+
}
|
|
16892
|
+
});
|
|
16893
|
+
if (isCancelled(email3))
|
|
16894
|
+
return null;
|
|
16895
|
+
if (email3) {
|
|
16896
|
+
const s = Y2();
|
|
16897
|
+
s.start("Validating email...");
|
|
16898
|
+
const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
|
|
16899
|
+
if (!result.valid) {
|
|
16900
|
+
s.stop(red("Email validation failed"));
|
|
16901
|
+
M2.error(result.error ?? "Unknown error");
|
|
16902
|
+
M2.info("Please contact a Timeback admin to set up your account.");
|
|
16903
|
+
return null;
|
|
16904
|
+
}
|
|
16905
|
+
s.stop(green("Email validated"));
|
|
16906
|
+
}
|
|
16907
|
+
return { clientId, clientSecret, email: email3 || undefined };
|
|
16908
|
+
}
|
|
16909
|
+
async function ensureCredentials(options) {
|
|
16910
|
+
const { env: env2, credentials, introTitle = "Timeback", skipIntro = false } = options;
|
|
16911
|
+
const existing = credentials[env2];
|
|
16912
|
+
if (existing)
|
|
16913
|
+
return existing;
|
|
16914
|
+
if (!skipIntro) {
|
|
16915
|
+
intro(introTitle);
|
|
16916
|
+
}
|
|
16917
|
+
Me(`No credentials configured for ${env2}.`, "Credential setup required");
|
|
16918
|
+
const newCreds = await promptForCredentials(env2);
|
|
16919
|
+
if (!newCreds)
|
|
16920
|
+
return null;
|
|
16921
|
+
const saved = await saveCredentials(env2, newCreds);
|
|
16922
|
+
if (saved) {
|
|
16923
|
+
M2.success(`${env2} credentials saved`);
|
|
16924
|
+
Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
|
|
16925
|
+
} else {
|
|
16926
|
+
M2.warn(`Credentials not saved`);
|
|
16927
|
+
}
|
|
16928
|
+
credentials[env2] = newCreds;
|
|
16929
|
+
return newCreds;
|
|
16891
16930
|
}
|
|
16892
|
-
var outro = {
|
|
16893
|
-
success: (message = "Done") => Se(green(message)),
|
|
16894
|
-
cancelled: () => Se(dim("Cancelled")),
|
|
16895
|
-
error: (message) => Se(red(message)),
|
|
16896
|
-
warn: (message) => Se(yellow(message)),
|
|
16897
|
-
info: (message) => Se(dim(message))
|
|
16898
|
-
};
|
|
16899
16931
|
// ../internal/cli-infra/src/config/playcademy.ts
|
|
16900
16932
|
var FILE_PATTERNS = ["playcademy.config.ts", "playcademy.config.js", "playcademy.config.json"];
|
|
16901
16933
|
function parse5() {
|
|
@@ -16905,18 +16937,18 @@ function parse5() {
|
|
|
16905
16937
|
});
|
|
16906
16938
|
}
|
|
16907
16939
|
function printError(error48) {
|
|
16908
|
-
console.log();
|
|
16940
|
+
console.log("");
|
|
16909
16941
|
console.log(` ${red("✖")} ${bold("Configuration Error")}`);
|
|
16910
|
-
console.log();
|
|
16942
|
+
console.log("");
|
|
16911
16943
|
console.log(` ${error48}`);
|
|
16912
|
-
console.log();
|
|
16944
|
+
console.log("");
|
|
16913
16945
|
console.log(` ${dim("Example playcademy.config.ts:")}`);
|
|
16914
|
-
console.log();
|
|
16946
|
+
console.log("");
|
|
16915
16947
|
console.log(` ${yellow("export default {")}`);
|
|
16916
16948
|
console.log(` ${yellow(" name: 'My Playcademy App',")}`);
|
|
16917
16949
|
console.log(` ${yellow(" // TODO: Define schema")}`);
|
|
16918
16950
|
console.log(` ${yellow("}")}`);
|
|
16919
|
-
console.log();
|
|
16951
|
+
console.log("");
|
|
16920
16952
|
}
|
|
16921
16953
|
var playcademyParser = {
|
|
16922
16954
|
name: "playcademy",
|
|
@@ -16926,10 +16958,8 @@ var playcademyParser = {
|
|
|
16926
16958
|
};
|
|
16927
16959
|
|
|
16928
16960
|
// ../internal/cli-infra/src/config/timeback.ts
|
|
16929
|
-
import { existsSync } from "node:fs";
|
|
16930
16961
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
16931
|
-
import {
|
|
16932
|
-
import { pathToFileURL } from "node:url";
|
|
16962
|
+
import { resolve as resolve2 } from "node:path";
|
|
16933
16963
|
// ../types/src/zod/primitives.ts
|
|
16934
16964
|
var IsoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
16935
16965
|
var IsoDateTimeString = exports_external.string().min(1).regex(IsoDateTimeRegex, "must be a valid ISO 8601 datetime");
|
|
@@ -16945,7 +16975,7 @@ var TimebackSubject = exports_external.enum([
|
|
|
16945
16975
|
"Math",
|
|
16946
16976
|
"None",
|
|
16947
16977
|
"Other"
|
|
16948
|
-
]);
|
|
16978
|
+
]).meta({ id: "TimebackSubject", description: "Subject area" });
|
|
16949
16979
|
var TimebackGrade = exports_external.union([
|
|
16950
16980
|
exports_external.literal(-1),
|
|
16951
16981
|
exports_external.literal(0),
|
|
@@ -16962,7 +16992,10 @@ var TimebackGrade = exports_external.union([
|
|
|
16962
16992
|
exports_external.literal(11),
|
|
16963
16993
|
exports_external.literal(12),
|
|
16964
16994
|
exports_external.literal(13)
|
|
16965
|
-
])
|
|
16995
|
+
]).meta({
|
|
16996
|
+
id: "TimebackGrade",
|
|
16997
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
16998
|
+
});
|
|
16966
16999
|
var ScoreStatus = exports_external.enum([
|
|
16967
17000
|
"exempt",
|
|
16968
17001
|
"fully graded",
|
|
@@ -17194,62 +17227,84 @@ var CaliperListEventsParams = exports_external.object({
|
|
|
17194
17227
|
}).strict();
|
|
17195
17228
|
// ../types/src/zod/config.ts
|
|
17196
17229
|
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"]);
|
|
17230
|
+
staging: exports_external.string().meta({ description: "Course ID in staging environment" }).optional(),
|
|
17231
|
+
production: exports_external.string().meta({ description: "Course ID in production environment" }).optional()
|
|
17232
|
+
}).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
|
|
17233
|
+
var CourseType = exports_external.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
|
|
17234
|
+
var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
|
|
17202
17235
|
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
|
-
});
|
|
17236
|
+
dailyXp: exports_external.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
|
|
17237
|
+
dailyLessons: exports_external.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
|
|
17238
|
+
dailyActiveMinutes: exports_external.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
|
|
17239
|
+
dailyAccuracy: exports_external.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
|
|
17240
|
+
dailyMasteredUnits: exports_external.number().int().positive().meta({ description: "Target units to master per day" }).optional()
|
|
17241
|
+
}).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
|
|
17209
17242
|
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
|
-
});
|
|
17243
|
+
totalXp: exports_external.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
|
|
17244
|
+
totalLessons: exports_external.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
|
|
17245
|
+
totalGrades: exports_external.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
|
|
17246
|
+
}).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
|
|
17214
17247
|
var CourseMetadata = exports_external.object({
|
|
17215
17248
|
courseType: CourseType.optional(),
|
|
17216
|
-
isSupplemental: exports_external.boolean().optional(),
|
|
17217
|
-
isCustom: exports_external.boolean().optional(),
|
|
17249
|
+
isSupplemental: exports_external.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
|
|
17250
|
+
isCustom: exports_external.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
|
|
17218
17251
|
publishStatus: PublishStatus.optional(),
|
|
17219
|
-
contactEmail: exports_external.email().optional(),
|
|
17220
|
-
primaryApp: exports_external.string().optional(),
|
|
17252
|
+
contactEmail: exports_external.email().meta({ description: "Contact email for course issues" }).optional(),
|
|
17253
|
+
primaryApp: exports_external.string().meta({ description: "Primary application identifier" }).optional(),
|
|
17221
17254
|
goals: CourseGoals.optional(),
|
|
17222
17255
|
metrics: CourseMetrics.optional()
|
|
17223
|
-
});
|
|
17256
|
+
}).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
|
|
17224
17257
|
var CourseDefaults = exports_external.object({
|
|
17225
|
-
courseCode: exports_external.string().optional(),
|
|
17226
|
-
level: exports_external.string().optional(),
|
|
17258
|
+
courseCode: exports_external.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
|
|
17259
|
+
level: exports_external.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
|
|
17227
17260
|
metadata: CourseMetadata.optional()
|
|
17261
|
+
}).meta({
|
|
17262
|
+
id: "CourseDefaults",
|
|
17263
|
+
description: "Default properties that apply to all courses unless overridden"
|
|
17228
17264
|
});
|
|
17229
17265
|
var CourseEnvOverrides = exports_external.object({
|
|
17230
|
-
level: exports_external.string().optional(),
|
|
17231
|
-
sensor: exports_external.
|
|
17232
|
-
launchUrl: exports_external.
|
|
17266
|
+
level: exports_external.string().meta({ description: "Course level for this environment" }).optional(),
|
|
17267
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
|
|
17268
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this environment" }).optional(),
|
|
17233
17269
|
metadata: CourseMetadata.optional()
|
|
17270
|
+
}).meta({
|
|
17271
|
+
id: "CourseEnvOverrides",
|
|
17272
|
+
description: "Environment-specific course overrides (non-identity fields)"
|
|
17234
17273
|
});
|
|
17235
17274
|
var CourseOverrides = exports_external.object({
|
|
17236
|
-
staging: CourseEnvOverrides.
|
|
17237
|
-
|
|
17238
|
-
})
|
|
17275
|
+
staging: CourseEnvOverrides.meta({
|
|
17276
|
+
description: "Overrides for staging environment"
|
|
17277
|
+
}).optional(),
|
|
17278
|
+
production: CourseEnvOverrides.meta({
|
|
17279
|
+
description: "Overrides for production environment"
|
|
17280
|
+
}).optional()
|
|
17281
|
+
}).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
|
|
17239
17282
|
var CourseConfig = CourseDefaults.extend({
|
|
17240
|
-
subject: TimebackSubject,
|
|
17241
|
-
grade: TimebackGrade.
|
|
17283
|
+
subject: TimebackSubject.meta({ description: "Subject area for this course" }),
|
|
17284
|
+
grade: TimebackGrade.meta({
|
|
17285
|
+
description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
|
|
17286
|
+
}).optional(),
|
|
17242
17287
|
ids: CourseIds.nullable().optional(),
|
|
17243
|
-
sensor: exports_external.
|
|
17244
|
-
launchUrl: exports_external.
|
|
17288
|
+
sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
|
|
17289
|
+
launchUrl: exports_external.url().meta({ description: "LTI launch URL for this course" }).optional(),
|
|
17245
17290
|
overrides: CourseOverrides.optional()
|
|
17291
|
+
}).meta({
|
|
17292
|
+
id: "CourseConfig",
|
|
17293
|
+
description: "Configuration for a single course. Must have either grade or courseCode (or both)."
|
|
17246
17294
|
});
|
|
17247
17295
|
var TimebackConfig = exports_external.object({
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17296
|
+
$schema: exports_external.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
|
|
17297
|
+
name: exports_external.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
|
|
17298
|
+
defaults: CourseDefaults.meta({
|
|
17299
|
+
description: "Default properties applied to all courses"
|
|
17300
|
+
}).optional(),
|
|
17301
|
+
courses: exports_external.array(CourseConfig).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
|
|
17302
|
+
sensor: exports_external.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
|
|
17303
|
+
launchUrl: exports_external.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
|
|
17304
|
+
}).meta({
|
|
17305
|
+
id: "TimebackConfig",
|
|
17306
|
+
title: "Timeback Config",
|
|
17307
|
+
description: "Configuration schema for timeback.config.json files"
|
|
17253
17308
|
}).refine((config2) => {
|
|
17254
17309
|
return config2.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
|
|
17255
17310
|
}, {
|
|
@@ -17270,18 +17325,24 @@ var TimebackConfig = exports_external.object({
|
|
|
17270
17325
|
message: "Duplicate courseCode found; each must be unique",
|
|
17271
17326
|
path: ["courses"]
|
|
17272
17327
|
}).refine((config2) => {
|
|
17273
|
-
return config2.courses.every((c) =>
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17328
|
+
return config2.courses.every((c) => {
|
|
17329
|
+
if (c.sensor !== undefined || config2.sensor !== undefined) {
|
|
17330
|
+
return true;
|
|
17331
|
+
}
|
|
17332
|
+
const launchUrls = [
|
|
17333
|
+
c.launchUrl,
|
|
17334
|
+
config2.launchUrl,
|
|
17335
|
+
c.overrides?.staging?.launchUrl,
|
|
17336
|
+
c.overrides?.production?.launchUrl
|
|
17337
|
+
].filter(Boolean);
|
|
17338
|
+
return launchUrls.length > 0;
|
|
17339
|
+
});
|
|
17279
17340
|
}, {
|
|
17280
|
-
message: "Each course must have an effective
|
|
17341
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
17281
17342
|
path: ["courses"]
|
|
17282
17343
|
});
|
|
17283
17344
|
// ../types/src/zod/edubridge.ts
|
|
17284
|
-
var EdubridgeDateString =
|
|
17345
|
+
var EdubridgeDateString = IsoDateTimeString;
|
|
17285
17346
|
var EduBridgeEnrollment = exports_external.object({
|
|
17286
17347
|
id: exports_external.string(),
|
|
17287
17348
|
role: exports_external.string(),
|
|
@@ -17345,12 +17406,9 @@ var EmailOrStudentId = exports_external.object({
|
|
|
17345
17406
|
});
|
|
17346
17407
|
var SubjectTrackInput = exports_external.object({
|
|
17347
17408
|
subject: NonEmptyString,
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
});
|
|
17352
|
-
var SubjectTrackUpsertInput = SubjectTrackInput.extend({
|
|
17353
|
-
id: NonEmptyString
|
|
17409
|
+
grade: NonEmptyString,
|
|
17410
|
+
courseId: NonEmptyString,
|
|
17411
|
+
orgSourcedId: NonEmptyString.optional()
|
|
17354
17412
|
});
|
|
17355
17413
|
var EdubridgeListEnrollmentsParams = exports_external.object({
|
|
17356
17414
|
userId: NonEmptyString
|
|
@@ -18263,28 +18321,87 @@ var QtiLessonFeedbackInput = exports_external.object({
|
|
|
18263
18321
|
lessonId: exports_external.string().min(1),
|
|
18264
18322
|
humanApproved: exports_external.boolean().optional()
|
|
18265
18323
|
}).strict();
|
|
18266
|
-
// ../internal/cli-infra/src/config/
|
|
18267
|
-
var
|
|
18268
|
-
var
|
|
18269
|
-
var
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18324
|
+
// ../internal/cli-infra/src/config/constants.ts
|
|
18325
|
+
var CONFIG_FILENAME = "timeback.config.json";
|
|
18326
|
+
var CONFIG_SCHEMA_URL = "https://timeback.dev/schema.json";
|
|
18327
|
+
var DEFAULT_VERSION = "0.0.0";
|
|
18328
|
+
var JSON_INDENT = 2;
|
|
18329
|
+
var TOP_LEVEL_KEY_ORDER = [
|
|
18330
|
+
"$schema",
|
|
18331
|
+
"name",
|
|
18332
|
+
"launchUrl",
|
|
18333
|
+
"sensor",
|
|
18334
|
+
"defaults",
|
|
18335
|
+
"courses"
|
|
18336
|
+
];
|
|
18337
|
+
var COURSE_KEY_ORDER = [
|
|
18338
|
+
"subject",
|
|
18339
|
+
"grade",
|
|
18340
|
+
"courseCode",
|
|
18341
|
+
"level",
|
|
18342
|
+
"sensor",
|
|
18343
|
+
"launchUrl",
|
|
18344
|
+
"ids",
|
|
18345
|
+
"metadata",
|
|
18346
|
+
"overrides"
|
|
18347
|
+
];
|
|
18348
|
+
var IDS_KEY_ORDER = ["staging", "production"];
|
|
18349
|
+
var METADATA_KEY_ORDER = [
|
|
18350
|
+
"courseType",
|
|
18351
|
+
"isSupplemental",
|
|
18352
|
+
"isCustom",
|
|
18353
|
+
"publishStatus",
|
|
18354
|
+
"contactEmail",
|
|
18355
|
+
"primaryApp",
|
|
18356
|
+
"goals",
|
|
18357
|
+
"metrics"
|
|
18358
|
+
];
|
|
18359
|
+
var DEFAULTS_KEY_ORDER = ["courseCode", "level", "metadata"];
|
|
18360
|
+
var OVERRIDES_KEY_ORDER = ["staging", "production"];
|
|
18361
|
+
var ENV_OVERRIDES_KEY_ORDER = ["level", "sensor", "launchUrl", "metadata"];
|
|
18362
|
+
|
|
18363
|
+
// ../internal/cli-infra/src/config/loader.ts
|
|
18364
|
+
import { basename, extname, relative, resolve } from "node:path";
|
|
18365
|
+
import { loadConfig as c12LoadConfig } from "c12";
|
|
18366
|
+
function isJsonConfigPath(configPath) {
|
|
18367
|
+
return extname(configPath).toLowerCase() === ".json";
|
|
18368
|
+
}
|
|
18369
|
+
async function loadWithC12(cwd, configPath) {
|
|
18370
|
+
if (configPath && !isJsonConfigPath(configPath)) {
|
|
18371
|
+
throw new Error(`Config file must be JSON (.json): ${configPath}`);
|
|
18372
|
+
}
|
|
18373
|
+
const result = await c12LoadConfig({
|
|
18374
|
+
cwd,
|
|
18375
|
+
name: "timeback",
|
|
18376
|
+
configFile: configPath ?? CONFIG_FILENAME,
|
|
18377
|
+
rcFile: false,
|
|
18378
|
+
packageJson: false,
|
|
18379
|
+
dotenv: false,
|
|
18380
|
+
envName: false,
|
|
18381
|
+
extend: false,
|
|
18382
|
+
omit$Keys: true,
|
|
18383
|
+
defaults: {},
|
|
18384
|
+
overrides: {}
|
|
18385
|
+
});
|
|
18386
|
+
if (!result.config || Object.keys(result.config).length === 0) {
|
|
18387
|
+
return null;
|
|
18388
|
+
}
|
|
18389
|
+
const rawConfig = result.config;
|
|
18390
|
+
if ("extends" in rawConfig) {
|
|
18391
|
+
throw new Error("The 'extends' feature is not supported in timeback.config.json. " + "Please inline all configuration.");
|
|
18285
18392
|
}
|
|
18286
|
-
|
|
18393
|
+
const { $schema: _schema, ...configWithoutSchema } = rawConfig;
|
|
18394
|
+
return {
|
|
18395
|
+
config: configWithoutSchema,
|
|
18396
|
+
configFile: result.configFile ?? resolve(cwd, configPath ?? CONFIG_FILENAME)
|
|
18397
|
+
};
|
|
18398
|
+
}
|
|
18399
|
+
function getRelativeConfigPath(configPath) {
|
|
18400
|
+
return relative(process.cwd(), configPath);
|
|
18287
18401
|
}
|
|
18402
|
+
|
|
18403
|
+
// ../internal/cli-infra/src/config/timeback.ts
|
|
18404
|
+
var FILE_PATTERNS2 = [CONFIG_FILENAME];
|
|
18288
18405
|
function deriveCourseIds(config3) {
|
|
18289
18406
|
const result = { staging: [], production: [] };
|
|
18290
18407
|
for (const env2 of ENVIRONMENTS) {
|
|
@@ -18294,81 +18411,68 @@ function deriveCourseIds(config3) {
|
|
|
18294
18411
|
}
|
|
18295
18412
|
async function readPackageVersion(cwd) {
|
|
18296
18413
|
try {
|
|
18297
|
-
const pkgPath =
|
|
18414
|
+
const pkgPath = resolve2(cwd, "package.json");
|
|
18298
18415
|
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
18299
|
-
return pkg.version ??
|
|
18416
|
+
return pkg.version ?? DEFAULT_VERSION;
|
|
18300
18417
|
} catch {
|
|
18301
|
-
return
|
|
18418
|
+
return DEFAULT_VERSION;
|
|
18302
18419
|
}
|
|
18303
18420
|
}
|
|
18304
18421
|
async function parse6() {
|
|
18305
18422
|
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;
|
|
18423
|
+
try {
|
|
18424
|
+
const loaded = await loadWithC12(cwd);
|
|
18425
|
+
if (!loaded) {
|
|
18426
|
+
return {
|
|
18427
|
+
success: false,
|
|
18428
|
+
error: `No timeback config found. Create ${CONFIG_FILENAME}`
|
|
18429
|
+
};
|
|
18321
18430
|
}
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18431
|
+
const result = TimebackConfig.safeParse(loaded.config);
|
|
18432
|
+
if (!result.success) {
|
|
18433
|
+
const issues = result.error.issues.map((issue2) => ` - ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
18434
|
+
`);
|
|
18435
|
+
return {
|
|
18436
|
+
success: false,
|
|
18437
|
+
error: `Invalid config in ${CONFIG_FILENAME}:
|
|
18438
|
+
${issues}`
|
|
18439
|
+
};
|
|
18440
|
+
}
|
|
18441
|
+
const version2 = await readPackageVersion(cwd);
|
|
18442
|
+
const baseConfig = { ...result.data, version: version2 };
|
|
18331
18443
|
return {
|
|
18332
|
-
success:
|
|
18333
|
-
|
|
18444
|
+
success: true,
|
|
18445
|
+
config: {
|
|
18446
|
+
...baseConfig,
|
|
18447
|
+
path: loaded.configFile,
|
|
18448
|
+
courseIds: deriveCourseIds(baseConfig)
|
|
18449
|
+
}
|
|
18334
18450
|
};
|
|
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
|
-
`);
|
|
18451
|
+
} catch (err) {
|
|
18340
18452
|
return {
|
|
18341
18453
|
success: false,
|
|
18342
|
-
error: `
|
|
18343
|
-
${
|
|
18454
|
+
error: `Failed to load ${CONFIG_FILENAME}:
|
|
18455
|
+
${err instanceof Error ? err.message : String(err)}`
|
|
18344
18456
|
};
|
|
18345
18457
|
}
|
|
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
18458
|
}
|
|
18357
18459
|
function printError2(error48) {
|
|
18358
|
-
console.log();
|
|
18460
|
+
console.log("");
|
|
18359
18461
|
console.log(` ${red("✖")} ${bold("Configuration Error")}`);
|
|
18360
|
-
console.log();
|
|
18462
|
+
console.log("");
|
|
18361
18463
|
console.log(` ${error48}`);
|
|
18362
|
-
console.log();
|
|
18363
|
-
console.log(` ${dim("Example timeback.config.
|
|
18364
|
-
console.log();
|
|
18365
|
-
console.log(` ${yellow("
|
|
18366
|
-
console.log(` ${yellow("
|
|
18367
|
-
console.log(` ${yellow("
|
|
18368
|
-
console.log(` ${yellow("
|
|
18369
|
-
console.log(` ${yellow(
|
|
18464
|
+
console.log("");
|
|
18465
|
+
console.log(` ${dim("Example timeback.config.json:")}`);
|
|
18466
|
+
console.log("");
|
|
18467
|
+
console.log(` ${yellow("{")}`);
|
|
18468
|
+
console.log(` ${yellow(' "$schema": "https://timeback.dev/schema.json",')}`);
|
|
18469
|
+
console.log(` ${yellow(' "name": "My Timeback App",')}`);
|
|
18470
|
+
console.log(` ${yellow(' "launchUrl": "https://example.com/play",')}`);
|
|
18471
|
+
console.log(` ${yellow(' "courses": [')}`);
|
|
18472
|
+
console.log(` ${yellow(' { "subject": "Math", "grade": 3 }')}`);
|
|
18473
|
+
console.log(` ${yellow(" ]")}`);
|
|
18370
18474
|
console.log(` ${yellow("}")}`);
|
|
18371
|
-
console.log();
|
|
18475
|
+
console.log("");
|
|
18372
18476
|
}
|
|
18373
18477
|
var timebackParser = {
|
|
18374
18478
|
name: "timeback",
|
|
@@ -18376,7 +18480,446 @@ var timebackParser = {
|
|
|
18376
18480
|
parse: parse6,
|
|
18377
18481
|
printError: printError2
|
|
18378
18482
|
};
|
|
18379
|
-
// ../internal/cli-infra/src/config/
|
|
18483
|
+
// ../internal/cli-infra/src/config/generate.ts
|
|
18484
|
+
function orderKeys(obj, keyOrder) {
|
|
18485
|
+
const result = {};
|
|
18486
|
+
const objKeys = Object.keys(obj);
|
|
18487
|
+
const source = obj;
|
|
18488
|
+
for (const key of keyOrder) {
|
|
18489
|
+
if (key in source && source[key] !== undefined) {
|
|
18490
|
+
result[key] = source[key];
|
|
18491
|
+
}
|
|
18492
|
+
}
|
|
18493
|
+
for (const key of objKeys) {
|
|
18494
|
+
if (!(key in result) && source[key] !== undefined) {
|
|
18495
|
+
result[key] = source[key];
|
|
18496
|
+
}
|
|
18497
|
+
}
|
|
18498
|
+
return result;
|
|
18499
|
+
}
|
|
18500
|
+
function filterUndefined(obj) {
|
|
18501
|
+
const result = {};
|
|
18502
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
18503
|
+
if (value !== undefined) {
|
|
18504
|
+
result[key] = value;
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
return result;
|
|
18508
|
+
}
|
|
18509
|
+
function formatCourseIds(ids) {
|
|
18510
|
+
if (!ids)
|
|
18511
|
+
return;
|
|
18512
|
+
const filtered = filterUndefined(ids);
|
|
18513
|
+
if (Object.keys(filtered).length === 0)
|
|
18514
|
+
return;
|
|
18515
|
+
return orderKeys(filtered, IDS_KEY_ORDER);
|
|
18516
|
+
}
|
|
18517
|
+
function formatMetadata(metadata) {
|
|
18518
|
+
if (!metadata)
|
|
18519
|
+
return;
|
|
18520
|
+
const filtered = filterUndefined(metadata);
|
|
18521
|
+
if (Object.keys(filtered).length === 0)
|
|
18522
|
+
return;
|
|
18523
|
+
return orderKeys(filtered, METADATA_KEY_ORDER);
|
|
18524
|
+
}
|
|
18525
|
+
function formatDefaults(defaults) {
|
|
18526
|
+
if (!defaults)
|
|
18527
|
+
return;
|
|
18528
|
+
const result = {};
|
|
18529
|
+
if (defaults.courseCode)
|
|
18530
|
+
result.courseCode = defaults.courseCode;
|
|
18531
|
+
if (defaults.level)
|
|
18532
|
+
result.level = defaults.level;
|
|
18533
|
+
if (defaults.metadata) {
|
|
18534
|
+
const meta3 = formatMetadata(defaults.metadata);
|
|
18535
|
+
if (meta3)
|
|
18536
|
+
result.metadata = meta3;
|
|
18537
|
+
}
|
|
18538
|
+
if (Object.keys(result).length === 0)
|
|
18539
|
+
return;
|
|
18540
|
+
return orderKeys(result, DEFAULTS_KEY_ORDER);
|
|
18541
|
+
}
|
|
18542
|
+
function formatEnvOverrides(overrides) {
|
|
18543
|
+
if (!overrides)
|
|
18544
|
+
return;
|
|
18545
|
+
const result = {};
|
|
18546
|
+
if (overrides.level)
|
|
18547
|
+
result.level = overrides.level;
|
|
18548
|
+
if (overrides.sensor)
|
|
18549
|
+
result.sensor = overrides.sensor;
|
|
18550
|
+
if (overrides.launchUrl)
|
|
18551
|
+
result.launchUrl = overrides.launchUrl;
|
|
18552
|
+
if (overrides.metadata) {
|
|
18553
|
+
const meta3 = formatMetadata(overrides.metadata);
|
|
18554
|
+
if (meta3)
|
|
18555
|
+
result.metadata = meta3;
|
|
18556
|
+
}
|
|
18557
|
+
if (Object.keys(result).length === 0)
|
|
18558
|
+
return;
|
|
18559
|
+
return orderKeys(result, ENV_OVERRIDES_KEY_ORDER);
|
|
18560
|
+
}
|
|
18561
|
+
function formatOverrides(overrides) {
|
|
18562
|
+
if (!overrides) {
|
|
18563
|
+
return;
|
|
18564
|
+
}
|
|
18565
|
+
const result = {};
|
|
18566
|
+
if (overrides.staging) {
|
|
18567
|
+
const staging = formatEnvOverrides(overrides.staging);
|
|
18568
|
+
if (staging)
|
|
18569
|
+
result.staging = staging;
|
|
18570
|
+
}
|
|
18571
|
+
if (overrides.production) {
|
|
18572
|
+
const production = formatEnvOverrides(overrides.production);
|
|
18573
|
+
if (production)
|
|
18574
|
+
result.production = production;
|
|
18575
|
+
}
|
|
18576
|
+
if (Object.keys(result).length === 0) {
|
|
18577
|
+
return;
|
|
18578
|
+
}
|
|
18579
|
+
return orderKeys(result, OVERRIDES_KEY_ORDER);
|
|
18580
|
+
}
|
|
18581
|
+
function formatCourse(course) {
|
|
18582
|
+
const result = {
|
|
18583
|
+
subject: course.subject
|
|
18584
|
+
};
|
|
18585
|
+
if (course.grade !== undefined) {
|
|
18586
|
+
result.grade = course.grade;
|
|
18587
|
+
}
|
|
18588
|
+
if (course.courseCode) {
|
|
18589
|
+
result.courseCode = course.courseCode;
|
|
18590
|
+
}
|
|
18591
|
+
if (course.level) {
|
|
18592
|
+
result.level = course.level;
|
|
18593
|
+
}
|
|
18594
|
+
if (course.sensor) {
|
|
18595
|
+
result.sensor = course.sensor;
|
|
18596
|
+
}
|
|
18597
|
+
if (course.launchUrl) {
|
|
18598
|
+
result.launchUrl = course.launchUrl;
|
|
18599
|
+
}
|
|
18600
|
+
const ids = formatCourseIds(course.ids);
|
|
18601
|
+
if (ids) {
|
|
18602
|
+
result.ids = ids;
|
|
18603
|
+
}
|
|
18604
|
+
const metadata = formatMetadata(course.metadata);
|
|
18605
|
+
if (metadata) {
|
|
18606
|
+
result.metadata = metadata;
|
|
18607
|
+
}
|
|
18608
|
+
const overrides = formatOverrides(course.overrides);
|
|
18609
|
+
if (overrides) {
|
|
18610
|
+
result.overrides = overrides;
|
|
18611
|
+
}
|
|
18612
|
+
return orderKeys(result, COURSE_KEY_ORDER);
|
|
18613
|
+
}
|
|
18614
|
+
function generateConfigContent(config3) {
|
|
18615
|
+
const output = {
|
|
18616
|
+
$schema: CONFIG_SCHEMA_URL,
|
|
18617
|
+
name: config3.name
|
|
18618
|
+
};
|
|
18619
|
+
if (config3.launchUrl) {
|
|
18620
|
+
output.launchUrl = config3.launchUrl;
|
|
18621
|
+
}
|
|
18622
|
+
if (config3.sensor) {
|
|
18623
|
+
output.sensor = config3.sensor;
|
|
18624
|
+
}
|
|
18625
|
+
const defaults = formatDefaults(config3.defaults);
|
|
18626
|
+
if (defaults) {
|
|
18627
|
+
output.defaults = defaults;
|
|
18628
|
+
}
|
|
18629
|
+
output.courses = config3.courses.map(formatCourse);
|
|
18630
|
+
const ordered = orderKeys(output, TOP_LEVEL_KEY_ORDER);
|
|
18631
|
+
return JSON.stringify(ordered, null, JSON_INDENT) + `
|
|
18632
|
+
`;
|
|
18633
|
+
}
|
|
18634
|
+
// ../internal/utils/src/server/spinner.ts
|
|
18635
|
+
import { stdout as stdout2 } from "node:process";
|
|
18636
|
+
|
|
18637
|
+
// ../internal/utils/src/server/terminal.ts
|
|
18638
|
+
import { stdout } from "node:process";
|
|
18639
|
+
var isInteractive = stdout.isTTY ?? false;
|
|
18640
|
+
var cursor = {
|
|
18641
|
+
hide: "\x1B[?25l",
|
|
18642
|
+
show: "\x1B[?25h",
|
|
18643
|
+
up: (n) => `\x1B[${n}A`,
|
|
18644
|
+
down: (n) => `\x1B[${n}B`,
|
|
18645
|
+
forward: (n) => `\x1B[${n}C`,
|
|
18646
|
+
back: (n) => `\x1B[${n}D`,
|
|
18647
|
+
clearLine: "\x1B[K",
|
|
18648
|
+
clearScreen: "\x1B[2J",
|
|
18649
|
+
home: "\x1B[H"
|
|
18650
|
+
};
|
|
18651
|
+
function stripAnsi(str) {
|
|
18652
|
+
return str.replaceAll(/\u001B\[[0-9;]*[a-zA-Z]/g, "");
|
|
18653
|
+
}
|
|
18654
|
+
|
|
18655
|
+
// ../internal/utils/src/server/spinner.ts
|
|
18656
|
+
var SPINNER_FRAMES = [
|
|
18657
|
+
10251,
|
|
18658
|
+
10265,
|
|
18659
|
+
10297,
|
|
18660
|
+
10296,
|
|
18661
|
+
10300,
|
|
18662
|
+
10292,
|
|
18663
|
+
10278,
|
|
18664
|
+
10279,
|
|
18665
|
+
10247,
|
|
18666
|
+
10255
|
|
18667
|
+
].map((code) => String.fromCodePoint(code));
|
|
18668
|
+
var SPINNER_INTERVAL = 80;
|
|
18669
|
+
var CHECK_MARK = "✔";
|
|
18670
|
+
var CROSS_MARK = "✖";
|
|
18671
|
+
var STATUS_LABELS = {
|
|
18672
|
+
pending: "[PENDING]",
|
|
18673
|
+
running: "[RUNNING]",
|
|
18674
|
+
success: "[SUCCESS]",
|
|
18675
|
+
error: "[ERROR]"
|
|
18676
|
+
};
|
|
18677
|
+
|
|
18678
|
+
class Spinner {
|
|
18679
|
+
tasks = new Map;
|
|
18680
|
+
frameIndex = 0;
|
|
18681
|
+
intervalId = null;
|
|
18682
|
+
previousLineCount = 0;
|
|
18683
|
+
printedTasks = new Set;
|
|
18684
|
+
constructor(taskIds, texts) {
|
|
18685
|
+
for (const [index, id] of taskIds.entries()) {
|
|
18686
|
+
this.tasks.set(id, {
|
|
18687
|
+
text: texts[index] ?? "",
|
|
18688
|
+
status: "pending"
|
|
18689
|
+
});
|
|
18690
|
+
}
|
|
18691
|
+
}
|
|
18692
|
+
start() {
|
|
18693
|
+
if (!isInteractive)
|
|
18694
|
+
return;
|
|
18695
|
+
stdout2.write(cursor.hide);
|
|
18696
|
+
this.render();
|
|
18697
|
+
this.intervalId = setInterval(() => {
|
|
18698
|
+
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
18699
|
+
this.render();
|
|
18700
|
+
}, SPINNER_INTERVAL);
|
|
18701
|
+
}
|
|
18702
|
+
updateTask(taskId, status, finalText) {
|
|
18703
|
+
const task = this.tasks.get(taskId);
|
|
18704
|
+
if (!task)
|
|
18705
|
+
return;
|
|
18706
|
+
task.status = status;
|
|
18707
|
+
if (finalText)
|
|
18708
|
+
task.finalText = finalText;
|
|
18709
|
+
if (!isInteractive) {
|
|
18710
|
+
this.renderNonInteractive(taskId, task);
|
|
18711
|
+
}
|
|
18712
|
+
}
|
|
18713
|
+
stop() {
|
|
18714
|
+
if (this.intervalId) {
|
|
18715
|
+
clearInterval(this.intervalId);
|
|
18716
|
+
this.intervalId = null;
|
|
18717
|
+
}
|
|
18718
|
+
if (isInteractive) {
|
|
18719
|
+
this.render();
|
|
18720
|
+
stdout2.write(cursor.show);
|
|
18721
|
+
}
|
|
18722
|
+
}
|
|
18723
|
+
clear() {
|
|
18724
|
+
if (this.intervalId) {
|
|
18725
|
+
clearInterval(this.intervalId);
|
|
18726
|
+
this.intervalId = null;
|
|
18727
|
+
}
|
|
18728
|
+
if (isInteractive && this.previousLineCount > 0) {
|
|
18729
|
+
stdout2.write(cursor.up(this.previousLineCount));
|
|
18730
|
+
for (let i = 0;i < this.previousLineCount; i++) {
|
|
18731
|
+
stdout2.write(`\r${cursor.clearLine}
|
|
18732
|
+
`);
|
|
18733
|
+
}
|
|
18734
|
+
stdout2.write(cursor.up(this.previousLineCount));
|
|
18735
|
+
stdout2.write(cursor.show);
|
|
18736
|
+
}
|
|
18737
|
+
this.previousLineCount = 0;
|
|
18738
|
+
}
|
|
18739
|
+
render() {
|
|
18740
|
+
if (this.previousLineCount > 0) {
|
|
18741
|
+
stdout2.write(cursor.up(this.previousLineCount));
|
|
18742
|
+
}
|
|
18743
|
+
const spinner = SPINNER_FRAMES[this.frameIndex];
|
|
18744
|
+
const visibleTasks = [...this.tasks.values()].filter((t) => t.status !== "pending");
|
|
18745
|
+
for (const task of visibleTasks) {
|
|
18746
|
+
stdout2.write(`\r${cursor.clearLine}`);
|
|
18747
|
+
console.log(this.formatLine(task, spinner));
|
|
18748
|
+
}
|
|
18749
|
+
this.previousLineCount = visibleTasks.length;
|
|
18750
|
+
}
|
|
18751
|
+
formatLine(task, spinner) {
|
|
18752
|
+
switch (task.status) {
|
|
18753
|
+
case "running":
|
|
18754
|
+
return `${blue(spinner ?? "○")} ${task.text}`;
|
|
18755
|
+
case "success":
|
|
18756
|
+
return `${green(CHECK_MARK)} ${task.finalText ?? task.text}`;
|
|
18757
|
+
case "error":
|
|
18758
|
+
return `${red(CROSS_MARK)} Failed: ${task.text}`;
|
|
18759
|
+
default:
|
|
18760
|
+
return task.text;
|
|
18761
|
+
}
|
|
18762
|
+
}
|
|
18763
|
+
renderNonInteractive(taskId, task) {
|
|
18764
|
+
const key = `${taskId}-${task.status}`;
|
|
18765
|
+
if (this.printedTasks.has(key))
|
|
18766
|
+
return;
|
|
18767
|
+
this.printedTasks.add(key);
|
|
18768
|
+
const text = task.status === "success" ? task.finalText ?? task.text : task.text;
|
|
18769
|
+
console.log(`${STATUS_LABELS[task.status]} ${stripAnsi(text)}`);
|
|
18770
|
+
}
|
|
18771
|
+
}
|
|
18772
|
+
// ../internal/utils/src/server/runtime.ts
|
|
18773
|
+
import { join as join2 } from "node:path";
|
|
18774
|
+
var STANDALONE_PATHS = [
|
|
18775
|
+
join2(".timeback", "bin"),
|
|
18776
|
+
join2(".local", "bin"),
|
|
18777
|
+
"/usr/local/bin/timeback",
|
|
18778
|
+
"/opt/homebrew/bin/timeback"
|
|
18779
|
+
];
|
|
18780
|
+
// ../internal/utils/src/server/project.ts
|
|
18781
|
+
import { existsSync } from "node:fs";
|
|
18782
|
+
import { resolve as resolve3 } from "node:path";
|
|
18783
|
+
function detectPackageManager(cwd) {
|
|
18784
|
+
const candidates = [
|
|
18785
|
+
{ pm: "bun", file: "bun.lockb" },
|
|
18786
|
+
{ pm: "bun", file: "bun.lock" },
|
|
18787
|
+
{ pm: "pnpm", file: "pnpm-lock.yaml" },
|
|
18788
|
+
{ pm: "yarn", file: "yarn.lock" },
|
|
18789
|
+
{ pm: "npm", file: "package-lock.json" }
|
|
18790
|
+
];
|
|
18791
|
+
for (const { pm, file: file2 } of candidates) {
|
|
18792
|
+
if (existsSync(resolve3(cwd, file2))) {
|
|
18793
|
+
return { packageManager: pm, reason: "lockfile", lockfile: file2 };
|
|
18794
|
+
}
|
|
18795
|
+
}
|
|
18796
|
+
return { packageManager: "npm", reason: "default" };
|
|
18797
|
+
}
|
|
18798
|
+
function getDlxCommand(pm, tool) {
|
|
18799
|
+
switch (pm) {
|
|
18800
|
+
case "bun":
|
|
18801
|
+
return { cmd: "bunx", args: [tool] };
|
|
18802
|
+
case "pnpm":
|
|
18803
|
+
return { cmd: "pnpm", args: ["dlx", tool] };
|
|
18804
|
+
case "yarn":
|
|
18805
|
+
return { cmd: "yarn", args: ["dlx", tool] };
|
|
18806
|
+
case "npm":
|
|
18807
|
+
default:
|
|
18808
|
+
return { cmd: "npx", args: ["--yes", tool] };
|
|
18809
|
+
}
|
|
18810
|
+
}
|
|
18811
|
+
// ../internal/utils/src/server/prettier.ts
|
|
18812
|
+
import { spawn } from "node:child_process";
|
|
18813
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
18814
|
+
import { resolve as resolve4 } from "node:path";
|
|
18815
|
+
function run(cmd, args, cwd, silent = false) {
|
|
18816
|
+
return new Promise((resolvePromise) => {
|
|
18817
|
+
const child = spawn(cmd, args, {
|
|
18818
|
+
cwd,
|
|
18819
|
+
stdio: silent ? "ignore" : "inherit",
|
|
18820
|
+
shell: process.platform === "win32"
|
|
18821
|
+
});
|
|
18822
|
+
child.on("close", (code) => resolvePromise(code ?? 1));
|
|
18823
|
+
child.on("error", () => resolvePromise(1));
|
|
18824
|
+
});
|
|
18825
|
+
}
|
|
18826
|
+
function getPrettierBin(cwd) {
|
|
18827
|
+
return resolve4(cwd, "node_modules", ".bin", process.platform === "win32" ? "prettier.cmd" : "prettier");
|
|
18828
|
+
}
|
|
18829
|
+
function runPrettierUsingRunner(cwd, args) {
|
|
18830
|
+
const { packageManager } = detectPackageManager(cwd);
|
|
18831
|
+
const dlx = getDlxCommand(packageManager, "prettier");
|
|
18832
|
+
return run(dlx.cmd, [...dlx.args, ...args], cwd, true);
|
|
18833
|
+
}
|
|
18834
|
+
async function formatWithPrettier(options) {
|
|
18835
|
+
const { cwd, filePath, silent = false, label = "file" } = options;
|
|
18836
|
+
const prettierBin = getPrettierBin(cwd);
|
|
18837
|
+
const args = ["--write", filePath];
|
|
18838
|
+
const s = silent ? null : Y2();
|
|
18839
|
+
s?.start(`Formatting ${label}...`);
|
|
18840
|
+
try {
|
|
18841
|
+
let code;
|
|
18842
|
+
if (existsSync2(prettierBin)) {
|
|
18843
|
+
code = await run(prettierBin, args, cwd, true);
|
|
18844
|
+
} else {
|
|
18845
|
+
code = await runPrettierUsingRunner(cwd, args);
|
|
18846
|
+
}
|
|
18847
|
+
if (code === 0) {
|
|
18848
|
+
s?.stop(`Formatted ${label}`);
|
|
18849
|
+
return true;
|
|
18850
|
+
}
|
|
18851
|
+
s?.stop();
|
|
18852
|
+
if (!silent) {
|
|
18853
|
+
M2.warn(`Failed to format ${label}`);
|
|
18854
|
+
}
|
|
18855
|
+
return false;
|
|
18856
|
+
} catch {
|
|
18857
|
+
s?.stop();
|
|
18858
|
+
if (!silent) {
|
|
18859
|
+
M2.warn(`Failed to format ${label}`);
|
|
18860
|
+
}
|
|
18861
|
+
return false;
|
|
18862
|
+
}
|
|
18863
|
+
}
|
|
18864
|
+
// ../internal/cli-infra/src/config/update.ts
|
|
18865
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
18866
|
+
async function updateTimebackConfigFile(configPath, updates, options = {}) {
|
|
18867
|
+
const { format = false, cwd = process.cwd(), silent = true } = options;
|
|
18868
|
+
try {
|
|
18869
|
+
const raw = await readFile3(configPath, "utf8");
|
|
18870
|
+
const parsed = JSON.parse(raw);
|
|
18871
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
18872
|
+
return { success: false, error: "Config file is not a JSON object." };
|
|
18873
|
+
}
|
|
18874
|
+
const { $schema: _schema, ...withoutSchema } = parsed;
|
|
18875
|
+
const current = TimebackConfig.safeParse(withoutSchema);
|
|
18876
|
+
if (!current.success) {
|
|
18877
|
+
return { success: false, error: "Config file is invalid; cannot update." };
|
|
18878
|
+
}
|
|
18879
|
+
const next = {
|
|
18880
|
+
...current.data,
|
|
18881
|
+
...Object.fromEntries(Object.entries(updates).filter(([, v2]) => v2 !== undefined))
|
|
18882
|
+
};
|
|
18883
|
+
const validated = TimebackConfig.safeParse(next);
|
|
18884
|
+
if (!validated.success) {
|
|
18885
|
+
return { success: false, error: "Update would make config invalid; refusing to write." };
|
|
18886
|
+
}
|
|
18887
|
+
await writeFile2(configPath, generateConfigContent(validated.data), "utf8");
|
|
18888
|
+
if (format) {
|
|
18889
|
+
await formatWithPrettier({ cwd, filePath: configPath, silent });
|
|
18890
|
+
}
|
|
18891
|
+
return { success: true, config: validated.data };
|
|
18892
|
+
} catch (err) {
|
|
18893
|
+
return {
|
|
18894
|
+
success: false,
|
|
18895
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
18896
|
+
};
|
|
18897
|
+
}
|
|
18898
|
+
}
|
|
18899
|
+
// ../internal/utils/src/shared/format.ts
|
|
18900
|
+
function pluralize(count, singular, plural) {
|
|
18901
|
+
return count === 1 ? singular : plural ?? `${singular}s`;
|
|
18902
|
+
}
|
|
18903
|
+
// ../internal/utils/src/shared/batching.ts
|
|
18904
|
+
var DEFAULT_BATCH_SIZE = 50;
|
|
18905
|
+
function splitIntoBatches(items, options) {
|
|
18906
|
+
const batchSize = options?.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
18907
|
+
if (batchSize <= 0)
|
|
18908
|
+
return [items.slice()];
|
|
18909
|
+
const batches = [];
|
|
18910
|
+
for (let i = 0;i < items.length; i += batchSize) {
|
|
18911
|
+
batches.push(items.slice(i, i + batchSize));
|
|
18912
|
+
}
|
|
18913
|
+
return batches;
|
|
18914
|
+
}
|
|
18915
|
+
// ../internal/utils/src/shared/promise.ts
|
|
18916
|
+
function extractFulfilled(results) {
|
|
18917
|
+
return results.filter((r2) => r2.status === "fulfilled").flatMap((r2) => r2.value);
|
|
18918
|
+
}
|
|
18919
|
+
function extractRejected(results) {
|
|
18920
|
+
return results.filter((r2) => r2.status === "rejected");
|
|
18921
|
+
}
|
|
18922
|
+
// ../internal/cli-infra/src/config/courses.ts
|
|
18380
18923
|
function toCourseConfig(course, env2) {
|
|
18381
18924
|
const config3 = {
|
|
18382
18925
|
subject: course.subjects?.[0] ?? "None",
|
|
@@ -18413,7 +18956,53 @@ function toCourseConfig(course, env2) {
|
|
|
18413
18956
|
config3.metadata = meta3;
|
|
18414
18957
|
}
|
|
18415
18958
|
}
|
|
18416
|
-
return config3;
|
|
18959
|
+
return config3;
|
|
18960
|
+
}
|
|
18961
|
+
async function inferLaunchUrl(client, courseId) {
|
|
18962
|
+
try {
|
|
18963
|
+
const scopedCourse = client.oneroster.courses(courseId);
|
|
18964
|
+
const components = await scopedCourse.components({ where: { status: "active" } });
|
|
18965
|
+
const componentResourceIds = [];
|
|
18966
|
+
for (const component of components) {
|
|
18967
|
+
if (!component.sourcedId)
|
|
18968
|
+
continue;
|
|
18969
|
+
const crs = await client.oneroster.courses.componentResources({
|
|
18970
|
+
where: { "courseComponent.sourcedId": component.sourcedId, status: "active" }
|
|
18971
|
+
});
|
|
18972
|
+
for (const cr of crs) {
|
|
18973
|
+
if (cr.resource?.sourcedId) {
|
|
18974
|
+
componentResourceIds.push(cr.resource.sourcedId);
|
|
18975
|
+
}
|
|
18976
|
+
}
|
|
18977
|
+
}
|
|
18978
|
+
if (componentResourceIds.length === 0) {
|
|
18979
|
+
return;
|
|
18980
|
+
}
|
|
18981
|
+
const resourceIds = [...new Set(componentResourceIds)];
|
|
18982
|
+
const batches = splitIntoBatches(resourceIds);
|
|
18983
|
+
const batchResults = await Promise.allSettled(batches.map((batch) => client.oneroster.resources.listAll({
|
|
18984
|
+
where: { sourcedId: { in: batch } }
|
|
18985
|
+
})));
|
|
18986
|
+
const hasFailures = batchResults.some((r2) => r2.status === "rejected");
|
|
18987
|
+
if (hasFailures) {
|
|
18988
|
+
return;
|
|
18989
|
+
}
|
|
18990
|
+
const resources = extractFulfilled(batchResults);
|
|
18991
|
+
const launchUrls = new Set;
|
|
18992
|
+
for (const resource of resources) {
|
|
18993
|
+
const metadata = resource.metadata;
|
|
18994
|
+
const launchUrl = metadata?.launchUrl;
|
|
18995
|
+
if (typeof launchUrl === "string" && launchUrl.length > 0) {
|
|
18996
|
+
launchUrls.add(launchUrl);
|
|
18997
|
+
}
|
|
18998
|
+
}
|
|
18999
|
+
if (launchUrls.size === 1) {
|
|
19000
|
+
return [...launchUrls][0];
|
|
19001
|
+
}
|
|
19002
|
+
return;
|
|
19003
|
+
} catch {
|
|
19004
|
+
return;
|
|
19005
|
+
}
|
|
18417
19006
|
}
|
|
18418
19007
|
|
|
18419
19008
|
// ../internal/cli-infra/src/config/index.ts
|
|
@@ -18421,6 +19010,83 @@ function getParser(opts = {}) {
|
|
|
18421
19010
|
return opts.playcademy ? playcademyParser : timebackParser;
|
|
18422
19011
|
}
|
|
18423
19012
|
|
|
19013
|
+
// ../internal/cli-infra/src/sensors/infer.ts
|
|
19014
|
+
function asRecord(value) {
|
|
19015
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
19016
|
+
}
|
|
19017
|
+
function extractCourseIdCandidates(event) {
|
|
19018
|
+
const candidates = new Set;
|
|
19019
|
+
const obj = asRecord(event.object);
|
|
19020
|
+
const course = obj ? asRecord(obj.course) : undefined;
|
|
19021
|
+
const rawCourseId = course?.id;
|
|
19022
|
+
if (typeof rawCourseId === "string" && rawCourseId.length > 0) {
|
|
19023
|
+
candidates.add(rawCourseId);
|
|
19024
|
+
const lastSlash = rawCourseId.lastIndexOf("/");
|
|
19025
|
+
if (lastSlash >= 0 && lastSlash < rawCourseId.length - 1) {
|
|
19026
|
+
candidates.add(rawCourseId.slice(lastSlash + 1));
|
|
19027
|
+
}
|
|
19028
|
+
const coursesMarker = "/courses/";
|
|
19029
|
+
const idx = rawCourseId.indexOf(coursesMarker);
|
|
19030
|
+
if (idx >= 0) {
|
|
19031
|
+
const suffix = rawCourseId.slice(idx + coursesMarker.length);
|
|
19032
|
+
if (suffix)
|
|
19033
|
+
candidates.add(suffix);
|
|
19034
|
+
}
|
|
19035
|
+
}
|
|
19036
|
+
const extensions = asRecord(event.extensions);
|
|
19037
|
+
const extCourseId = extensions?.courseId;
|
|
19038
|
+
if (typeof extCourseId === "string" && extCourseId.length > 0) {
|
|
19039
|
+
candidates.add(extCourseId);
|
|
19040
|
+
}
|
|
19041
|
+
return [...candidates];
|
|
19042
|
+
}
|
|
19043
|
+
function toMillis(isoLike) {
|
|
19044
|
+
if (typeof isoLike !== "string") {
|
|
19045
|
+
return;
|
|
19046
|
+
}
|
|
19047
|
+
const ms = Date.parse(isoLike);
|
|
19048
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
19049
|
+
}
|
|
19050
|
+
async function inferSensorsFromCaliperEvents(client, options) {
|
|
19051
|
+
const { courseIds, maxEvents = 2000, pageSize = 2000, startDate } = options;
|
|
19052
|
+
const target = new Set(courseIds.filter(Boolean));
|
|
19053
|
+
if (target.size === 0) {
|
|
19054
|
+
return [];
|
|
19055
|
+
}
|
|
19056
|
+
const stats = new Map;
|
|
19057
|
+
const limit = Math.min(pageSize, maxEvents);
|
|
19058
|
+
const { events } = await client.caliper.events.list({ limit, startDate });
|
|
19059
|
+
const pageEvents = events ?? [];
|
|
19060
|
+
for (const raw of pageEvents) {
|
|
19061
|
+
const sensor = raw.sensor;
|
|
19062
|
+
if (typeof sensor !== "string" || sensor.length === 0) {
|
|
19063
|
+
continue;
|
|
19064
|
+
}
|
|
19065
|
+
const candidates = extractCourseIdCandidates(raw);
|
|
19066
|
+
const matches = candidates.some((c) => target.has(c));
|
|
19067
|
+
if (!matches) {
|
|
19068
|
+
continue;
|
|
19069
|
+
}
|
|
19070
|
+
const latestMs = toMillis(raw.eventTime);
|
|
19071
|
+
const current = stats.get(sensor);
|
|
19072
|
+
if (current) {
|
|
19073
|
+
current.count += 1;
|
|
19074
|
+
if (latestMs !== undefined) {
|
|
19075
|
+
current.latestMs = Math.max(current.latestMs ?? 0, latestMs);
|
|
19076
|
+
}
|
|
19077
|
+
} else {
|
|
19078
|
+
stats.set(sensor, { count: 1, latestMs });
|
|
19079
|
+
}
|
|
19080
|
+
}
|
|
19081
|
+
const sorted = [...stats.entries()].sort((a, b3) => {
|
|
19082
|
+
const countDiff = b3[1].count - a[1].count;
|
|
19083
|
+
if (countDiff !== 0)
|
|
19084
|
+
return countDiff;
|
|
19085
|
+
return (b3[1].latestMs ?? 0) - (a[1].latestMs ?? 0);
|
|
19086
|
+
}).map(([sensor]) => sensor);
|
|
19087
|
+
return sorted;
|
|
19088
|
+
}
|
|
19089
|
+
|
|
18424
19090
|
// ../internal/cli-infra/src/search/search.ts
|
|
18425
19091
|
function searchCourses(client, query, max = 50) {
|
|
18426
19092
|
return client.oneroster.courses.listAll({
|
|
@@ -18443,11 +19109,11 @@ async function promptSelectEnv(configuredEnvs) {
|
|
|
18443
19109
|
}
|
|
18444
19110
|
async function promptAppName() {
|
|
18445
19111
|
const name = await he({
|
|
18446
|
-
message: "
|
|
19112
|
+
message: "Search for your app",
|
|
18447
19113
|
placeholder: "My Timeback App",
|
|
18448
19114
|
validate: (value) => {
|
|
18449
19115
|
if (!value.trim())
|
|
18450
|
-
return "
|
|
19116
|
+
return "Please enter a search term";
|
|
18451
19117
|
}
|
|
18452
19118
|
});
|
|
18453
19119
|
if (pD(name))
|
|
@@ -18552,6 +19218,48 @@ async function selectCourses(client, initialResults, filterFn) {
|
|
|
18552
19218
|
results = filter(moreResults);
|
|
18553
19219
|
}
|
|
18554
19220
|
}
|
|
19221
|
+
async function enrichCoursesWithLaunchUrls(client, courses) {
|
|
19222
|
+
if (courses.length === 0)
|
|
19223
|
+
return { courses, inferredSensors: [] };
|
|
19224
|
+
const s = Y2();
|
|
19225
|
+
let didStop = false;
|
|
19226
|
+
const stopOnce = (message) => {
|
|
19227
|
+
if (didStop)
|
|
19228
|
+
return;
|
|
19229
|
+
s.stop(message);
|
|
19230
|
+
didStop = true;
|
|
19231
|
+
};
|
|
19232
|
+
s.start("Fetching course details...");
|
|
19233
|
+
const enriched = [];
|
|
19234
|
+
let inferredSensors = [];
|
|
19235
|
+
try {
|
|
19236
|
+
for (const course of courses) {
|
|
19237
|
+
const courseId = course.ids?.staging ?? course.ids?.production;
|
|
19238
|
+
if (!courseId) {
|
|
19239
|
+
enriched.push(course);
|
|
19240
|
+
continue;
|
|
19241
|
+
}
|
|
19242
|
+
const launchUrl = await inferLaunchUrl(client, courseId);
|
|
19243
|
+
if (launchUrl && !course.launchUrl) {
|
|
19244
|
+
enriched.push({ ...course, launchUrl });
|
|
19245
|
+
} else {
|
|
19246
|
+
enriched.push(course);
|
|
19247
|
+
}
|
|
19248
|
+
}
|
|
19249
|
+
const courseIds = enriched.map((c) => c.ids?.staging ?? c.ids?.production).filter((id) => typeof id === "string" && id.length > 0);
|
|
19250
|
+
if (courseIds.length > 0) {
|
|
19251
|
+
const startDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
|
|
19252
|
+
try {
|
|
19253
|
+
inferredSensors = await inferSensorsFromCaliperEvents(client, { courseIds, startDate });
|
|
19254
|
+
} catch {}
|
|
19255
|
+
}
|
|
19256
|
+
stopOnce("Course details loaded");
|
|
19257
|
+
return { courses: enriched, inferredSensors };
|
|
19258
|
+
} catch (error48) {
|
|
19259
|
+
stopOnce("Failed to load course details");
|
|
19260
|
+
throw error48;
|
|
19261
|
+
}
|
|
19262
|
+
}
|
|
18555
19263
|
async function searchAndSelectCourses(options) {
|
|
18556
19264
|
const { client, environment, appName: existingAppName, excludeCourseIds = [] } = options;
|
|
18557
19265
|
const filterExcluded = (courses) => {
|
|
@@ -18574,19 +19282,20 @@ async function searchAndSelectCourses(options) {
|
|
|
18574
19282
|
return { success: false, cancelled: true };
|
|
18575
19283
|
}
|
|
18576
19284
|
const courseConfigs = courses.map((c) => toCourseConfig(c, environment));
|
|
18577
|
-
|
|
19285
|
+
const { courses: enrichedConfigs, inferredSensors } = await enrichCoursesWithLaunchUrls(client, courseConfigs);
|
|
18578
19286
|
return {
|
|
18579
19287
|
success: true,
|
|
18580
19288
|
appName,
|
|
18581
|
-
courses:
|
|
18582
|
-
environment
|
|
19289
|
+
courses: enrichedConfigs,
|
|
19290
|
+
environment,
|
|
19291
|
+
inferredSensors
|
|
18583
19292
|
};
|
|
18584
19293
|
} catch (error48) {
|
|
18585
19294
|
const message = error48 instanceof Error ? error48.message : "Unknown error";
|
|
18586
19295
|
return { success: false, error: message };
|
|
18587
19296
|
}
|
|
18588
19297
|
}
|
|
18589
|
-
// ../internal/cli-infra/src/sensors/
|
|
19298
|
+
// ../internal/cli-infra/src/sensors/utils.ts
|
|
18590
19299
|
function isValidUrl(url2) {
|
|
18591
19300
|
try {
|
|
18592
19301
|
return Boolean(new URL(url2));
|
|
@@ -18594,15 +19303,60 @@ function isValidUrl(url2) {
|
|
|
18594
19303
|
return false;
|
|
18595
19304
|
}
|
|
18596
19305
|
}
|
|
19306
|
+
function getEffectiveLaunchUrl(course, config3, env2) {
|
|
19307
|
+
const envOverride = course.overrides?.[env2]?.launchUrl;
|
|
19308
|
+
if (envOverride)
|
|
19309
|
+
return envOverride;
|
|
19310
|
+
if (course.launchUrl)
|
|
19311
|
+
return course.launchUrl;
|
|
19312
|
+
return config3.launchUrl;
|
|
19313
|
+
}
|
|
19314
|
+
function getEffectiveSensorForCourse(course, config3, env2) {
|
|
19315
|
+
const envSensor = course.overrides?.[env2]?.sensor;
|
|
19316
|
+
if (envSensor) {
|
|
19317
|
+
return envSensor;
|
|
19318
|
+
}
|
|
19319
|
+
if (course.sensor) {
|
|
19320
|
+
return course.sensor;
|
|
19321
|
+
}
|
|
19322
|
+
if (config3.sensor) {
|
|
19323
|
+
return config3.sensor;
|
|
19324
|
+
}
|
|
19325
|
+
const launchUrl = getEffectiveLaunchUrl(course, config3, env2);
|
|
19326
|
+
if (launchUrl) {
|
|
19327
|
+
return deriveSensorFromLaunchUrl(launchUrl);
|
|
19328
|
+
}
|
|
19329
|
+
return;
|
|
19330
|
+
}
|
|
19331
|
+
function deriveEffectiveSensors(config3, env2) {
|
|
19332
|
+
const sensors = new Set;
|
|
19333
|
+
for (const course of config3.courses) {
|
|
19334
|
+
const sensor = getEffectiveSensorForCourse(course, config3, env2);
|
|
19335
|
+
if (sensor) {
|
|
19336
|
+
sensors.add(sensor);
|
|
19337
|
+
}
|
|
19338
|
+
}
|
|
19339
|
+
return Array.from(sensors);
|
|
19340
|
+
}
|
|
19341
|
+
|
|
19342
|
+
// ../internal/cli-infra/src/sensors/prompts.ts
|
|
19343
|
+
function deriveSensorFromLaunchUrl(launchUrl) {
|
|
19344
|
+
try {
|
|
19345
|
+
const url2 = new URL(launchUrl);
|
|
19346
|
+
return url2.origin;
|
|
19347
|
+
} catch {
|
|
19348
|
+
return;
|
|
19349
|
+
}
|
|
19350
|
+
}
|
|
18597
19351
|
async function promptSensor(options) {
|
|
18598
19352
|
const { defaultValue } = options ?? {};
|
|
18599
19353
|
const input = await he({
|
|
18600
|
-
message: "Sensor URL (
|
|
19354
|
+
message: "Sensor URL (for activity metrics)",
|
|
18601
19355
|
placeholder: "https://myapp.example.com",
|
|
18602
|
-
defaultValue,
|
|
19356
|
+
initialValue: defaultValue,
|
|
18603
19357
|
validate: (value) => {
|
|
18604
19358
|
if (!value.trim()) {
|
|
18605
|
-
return "Sensor URL is required
|
|
19359
|
+
return "Sensor URL is required";
|
|
18606
19360
|
}
|
|
18607
19361
|
if (!isValidUrl(value.trim())) {
|
|
18608
19362
|
return "Invalid URL format";
|
|
@@ -18615,25 +19369,6 @@ async function promptSensor(options) {
|
|
|
18615
19369
|
}
|
|
18616
19370
|
return input.trim();
|
|
18617
19371
|
}
|
|
18618
|
-
// ../internal/cli-infra/src/sensors/utils.ts
|
|
18619
|
-
function deriveSensorsFromConfig(config3) {
|
|
18620
|
-
const sensors = new Set;
|
|
18621
|
-
if (config3.sensor) {
|
|
18622
|
-
sensors.add(config3.sensor);
|
|
18623
|
-
}
|
|
18624
|
-
for (const course of config3.courses) {
|
|
18625
|
-
if (course.sensor) {
|
|
18626
|
-
sensors.add(course.sensor);
|
|
18627
|
-
}
|
|
18628
|
-
if (course.overrides?.staging?.sensor) {
|
|
18629
|
-
sensors.add(course.overrides.staging.sensor);
|
|
18630
|
-
}
|
|
18631
|
-
if (course.overrides?.production?.sensor) {
|
|
18632
|
-
sensors.add(course.overrides.production.sensor);
|
|
18633
|
-
}
|
|
18634
|
-
}
|
|
18635
|
-
return Array.from(sensors);
|
|
18636
|
-
}
|
|
18637
19372
|
// src/cli/commands/credentials/add.ts
|
|
18638
19373
|
async function addCredentials(options = {}) {
|
|
18639
19374
|
const { exitOnComplete = true, inline = false } = options;
|
|
@@ -18936,8 +19671,10 @@ async function showCredentialsMenu(options = {}) {
|
|
|
18936
19671
|
}
|
|
18937
19672
|
}
|
|
18938
19673
|
// src/cli/commands/credentials/lib/initial.ts
|
|
18939
|
-
async function promptInitialCredentials() {
|
|
18940
|
-
|
|
19674
|
+
async function promptInitialCredentials(options = {}) {
|
|
19675
|
+
if (!options.skipIntro) {
|
|
19676
|
+
intro("Timeback Studio");
|
|
19677
|
+
}
|
|
18941
19678
|
Me("No credentials found. You need to configure at least one environment.", "First-time setup");
|
|
18942
19679
|
const environments = await fe({
|
|
18943
19680
|
message: "Which environments would you like to configure?",
|
|
@@ -18959,12 +19696,12 @@ async function promptInitialCredentials() {
|
|
|
18959
19696
|
}
|
|
18960
19697
|
}
|
|
18961
19698
|
const configuredEnvs = environments;
|
|
18962
|
-
let
|
|
19699
|
+
let selectedEnv;
|
|
18963
19700
|
if (configuredEnvs.length === 1) {
|
|
18964
|
-
|
|
19701
|
+
selectedEnv = configuredEnvs[0];
|
|
18965
19702
|
} else {
|
|
18966
19703
|
const selected = await ve({
|
|
18967
|
-
message: "Which environment
|
|
19704
|
+
message: "Which environment would you like to use for this session?",
|
|
18968
19705
|
options: configuredEnvs.map((env2) => ({
|
|
18969
19706
|
value: env2,
|
|
18970
19707
|
label: env2.charAt(0).toUpperCase() + env2.slice(1)
|
|
@@ -18974,11 +19711,10 @@ async function promptInitialCredentials() {
|
|
|
18974
19711
|
outro.cancelled();
|
|
18975
19712
|
process.exit(0);
|
|
18976
19713
|
}
|
|
18977
|
-
|
|
19714
|
+
selectedEnv = selected;
|
|
18978
19715
|
}
|
|
18979
|
-
await saveDefaultEnvironment(defaultEnv);
|
|
18980
19716
|
Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
|
|
18981
|
-
return
|
|
19717
|
+
return selectedEnv;
|
|
18982
19718
|
}
|
|
18983
19719
|
// src/config/constants.ts
|
|
18984
19720
|
var configValues = {
|
|
@@ -19125,54 +19861,61 @@ async function fetchCourses(creds, env2, ids) {
|
|
|
19125
19861
|
client.close();
|
|
19126
19862
|
}
|
|
19127
19863
|
}
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19864
|
+
function isManagedCourse(course) {
|
|
19865
|
+
const managedBy = course.metadata?.managedBy;
|
|
19866
|
+
return typeof managedBy === "string" && managedBy.startsWith("timeback@");
|
|
19867
|
+
}
|
|
19868
|
+
async function checkCoursesManaged(creds, env2, ids) {
|
|
19869
|
+
if (ids.length === 0) {
|
|
19870
|
+
return { allManaged: true, unmanagedCourses: [] };
|
|
19871
|
+
}
|
|
19872
|
+
const client = new TimebackClient2({
|
|
19873
|
+
env: env2,
|
|
19874
|
+
auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
|
|
19875
|
+
});
|
|
19876
|
+
try {
|
|
19877
|
+
const s = Y2();
|
|
19878
|
+
s.start(`Fetching remote context...`);
|
|
19879
|
+
const courses = await fetchCoursesByIds(client, ids);
|
|
19880
|
+
s.stop(`Remote context fetched`);
|
|
19881
|
+
const unmanagedCourses = [];
|
|
19882
|
+
const fetchedIds = new Set;
|
|
19883
|
+
const courseMap = new Map;
|
|
19884
|
+
for (const course of courses) {
|
|
19885
|
+
if (course.sourcedId) {
|
|
19886
|
+
fetchedIds.add(course.sourcedId);
|
|
19887
|
+
courseMap.set(course.sourcedId, course);
|
|
19888
|
+
}
|
|
19889
|
+
if (!isManagedCourse(course) && course.sourcedId) {
|
|
19890
|
+
unmanagedCourses.push({ id: course.sourcedId, title: course.title });
|
|
19891
|
+
}
|
|
19892
|
+
}
|
|
19893
|
+
for (const id of ids) {
|
|
19894
|
+
if (!fetchedIds.has(id)) {
|
|
19895
|
+
unmanagedCourses.push({ id });
|
|
19896
|
+
}
|
|
19136
19897
|
}
|
|
19898
|
+
const allManaged = unmanagedCourses.length === 0;
|
|
19899
|
+
return { allManaged, unmanagedCourses };
|
|
19900
|
+
} finally {
|
|
19901
|
+
client.close();
|
|
19137
19902
|
}
|
|
19138
|
-
return credentials;
|
|
19139
19903
|
}
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
if (saved)
|
|
19145
|
-
return saved;
|
|
19146
|
-
const configured = await getConfiguredEnvironments();
|
|
19147
|
-
return configured[0] ?? "staging";
|
|
19148
|
-
}
|
|
19149
|
-
async function handleCredentialSetup() {
|
|
19150
|
-
const selectedEnv = await promptInitialCredentials();
|
|
19151
|
-
if (!selectedEnv)
|
|
19904
|
+
// src/cli/lib/credentials.ts
|
|
19905
|
+
async function handleCredentialSetup(options = {}) {
|
|
19906
|
+
const selectedEnv = await promptInitialCredentials({ skipIntro: options.skipIntro });
|
|
19907
|
+
if (!selectedEnv) {
|
|
19152
19908
|
return null;
|
|
19909
|
+
}
|
|
19153
19910
|
const result = await loadCredentials(selectedEnv);
|
|
19154
|
-
if (!result.success)
|
|
19911
|
+
if (!result.success) {
|
|
19155
19912
|
return null;
|
|
19913
|
+
}
|
|
19156
19914
|
return {
|
|
19157
19915
|
credentials: { [selectedEnv]: result.credentials },
|
|
19158
|
-
|
|
19916
|
+
selectedEnvironment: selectedEnv
|
|
19159
19917
|
};
|
|
19160
19918
|
}
|
|
19161
|
-
async function ensureCredentials(env2, credentials) {
|
|
19162
|
-
const existing = credentials[env2];
|
|
19163
|
-
if (existing)
|
|
19164
|
-
return existing;
|
|
19165
|
-
intro("Timeback Studio");
|
|
19166
|
-
Me(`No credentials configured for ${env2}.`, "Credential setup required");
|
|
19167
|
-
const newCreds = await promptForCredentials(env2);
|
|
19168
|
-
if (!newCreds)
|
|
19169
|
-
return null;
|
|
19170
|
-
await saveCredentials(env2, newCreds);
|
|
19171
|
-
M2.success(`${env2} credentials saved`);
|
|
19172
|
-
Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
|
|
19173
|
-
credentials[env2] = newCreds;
|
|
19174
|
-
return newCreds;
|
|
19175
|
-
}
|
|
19176
19919
|
// src/cli/lib/onboarding/import.ts
|
|
19177
19920
|
import { TimebackClient as TimebackClient3 } from "@timeback/core";
|
|
19178
19921
|
async function promptImportApp(credentials, configuredEnvs) {
|
|
@@ -19234,6 +19977,46 @@ async function promptNoConfig(credentials, configuredEnvs, opts = {}) {
|
|
|
19234
19977
|
}
|
|
19235
19978
|
// src/cli/commands/serve/config.ts
|
|
19236
19979
|
import { basename as basename2 } from "node:path";
|
|
19980
|
+
async function resolveConfigSource(courseIds, opts, defaultEnvironment) {
|
|
19981
|
+
const parserOpts = { playcademy: opts.playcademy };
|
|
19982
|
+
let configuredEnvs = await getConfiguredEnvironments();
|
|
19983
|
+
let credentials = {};
|
|
19984
|
+
if (courseIds.length > 0) {
|
|
19985
|
+
if (configuredEnvs.length > 0) {
|
|
19986
|
+
credentials = await loadAllCredentials();
|
|
19987
|
+
}
|
|
19988
|
+
const resolved2 = await resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnvironment);
|
|
19989
|
+
return resolved2 ? { resolved: resolved2, credentials, configuredEnvs } : null;
|
|
19990
|
+
}
|
|
19991
|
+
const configResult = await loadConfig2(parserOpts);
|
|
19992
|
+
if (configResult.success) {
|
|
19993
|
+
return {
|
|
19994
|
+
resolved: {
|
|
19995
|
+
userConfig: configResult.config,
|
|
19996
|
+
environment: defaultEnvironment,
|
|
19997
|
+
configFile: basename2(configResult.config.path)
|
|
19998
|
+
},
|
|
19999
|
+
credentials,
|
|
20000
|
+
configuredEnvs
|
|
20001
|
+
};
|
|
20002
|
+
}
|
|
20003
|
+
const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
|
|
20004
|
+
if (!isMissingConfig) {
|
|
20005
|
+
printError3(configResult.error, parserOpts);
|
|
20006
|
+
return null;
|
|
20007
|
+
}
|
|
20008
|
+
if (configuredEnvs.length === 0) {
|
|
20009
|
+
const result = await handleCredentialSetup({ skipIntro: true });
|
|
20010
|
+
if (!result)
|
|
20011
|
+
return null;
|
|
20012
|
+
credentials = result.credentials;
|
|
20013
|
+
configuredEnvs = Object.keys(credentials);
|
|
20014
|
+
} else {
|
|
20015
|
+
credentials = await loadAllCredentials();
|
|
20016
|
+
}
|
|
20017
|
+
const resolved = await resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnvironment);
|
|
20018
|
+
return resolved ? { resolved, credentials, configuredEnvs } : null;
|
|
20019
|
+
}
|
|
19237
20020
|
function buildUserConfigFromImport(result) {
|
|
19238
20021
|
const { name, courses, sensor } = result;
|
|
19239
20022
|
const courseIds = {
|
|
@@ -19275,7 +20058,7 @@ function buildUserConfigFromCourses(courses) {
|
|
|
19275
20058
|
};
|
|
19276
20059
|
}
|
|
19277
20060
|
async function resolveFromCourseIds(courseIds, env2, credentials, configuredEnvs) {
|
|
19278
|
-
const creds = await ensureCredentials(env2, credentials);
|
|
20061
|
+
const creds = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
19279
20062
|
if (!creds) {
|
|
19280
20063
|
return null;
|
|
19281
20064
|
}
|
|
@@ -19309,7 +20092,7 @@ async function resolveFromConfigOrImport(credentials, configuredEnvs, defaultEnv
|
|
|
19309
20092
|
}
|
|
19310
20093
|
const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
|
|
19311
20094
|
if (isMissingConfig) {
|
|
19312
|
-
const importResult = await promptNoConfig(credentials, configuredEnvs);
|
|
20095
|
+
const importResult = await promptNoConfig(credentials, configuredEnvs, { skipIntro: true });
|
|
19313
20096
|
if (!importResult)
|
|
19314
20097
|
return null;
|
|
19315
20098
|
return {
|
|
@@ -19323,12 +20106,60 @@ async function resolveFromConfigOrImport(credentials, configuredEnvs, defaultEnv
|
|
|
19323
20106
|
function parseSensors(sensors) {
|
|
19324
20107
|
return sensors.split(",").map((s) => s.trim()).filter(Boolean);
|
|
19325
20108
|
}
|
|
19326
|
-
function
|
|
20109
|
+
function formatCourseExampleLines(courses, max = 3) {
|
|
20110
|
+
if (courses.length === 0)
|
|
20111
|
+
return [];
|
|
20112
|
+
const shown = courses.slice(0, max);
|
|
20113
|
+
const remaining = courses.length - shown.length;
|
|
20114
|
+
return [
|
|
20115
|
+
...shown.map((c) => `- ${cyan(c.title ?? c.id)}`),
|
|
20116
|
+
...remaining > 0 ? [`- ${dim(`(+${remaining} more)`)}`] : []
|
|
20117
|
+
];
|
|
20118
|
+
}
|
|
20119
|
+
async function resolveSensors(options) {
|
|
20120
|
+
const { config: config3, env: env2, opts, creds } = options;
|
|
19327
20121
|
if (opts.sensors) {
|
|
19328
20122
|
return parseSensors(opts.sensors);
|
|
19329
20123
|
}
|
|
19330
|
-
|
|
19331
|
-
|
|
20124
|
+
if (config3.sensor) {
|
|
20125
|
+
return [config3.sensor];
|
|
20126
|
+
}
|
|
20127
|
+
const courseIds = config3.courseIds[env2] ?? [];
|
|
20128
|
+
if (courseIds.length === 0) {
|
|
20129
|
+
return null;
|
|
20130
|
+
}
|
|
20131
|
+
const { allManaged, unmanagedCourses } = await checkCoursesManaged(creds, env2, courseIds);
|
|
20132
|
+
if (allManaged) {
|
|
20133
|
+
const derived = deriveEffectiveSensors(config3, env2);
|
|
20134
|
+
if (derived.length > 0) {
|
|
20135
|
+
return derived;
|
|
20136
|
+
}
|
|
20137
|
+
Me(["Add a top-level `sensor` to `timeback.config.json` or pass `--sensors`."].join(`
|
|
20138
|
+
`), "Next time");
|
|
20139
|
+
M2.error("Could not determine sensor(s) from config file");
|
|
20140
|
+
return null;
|
|
20141
|
+
}
|
|
20142
|
+
const count = unmanagedCourses.length;
|
|
20143
|
+
Me([
|
|
20144
|
+
`${count} ${pluralize(count, "course")} ${count === 1 ? "is" : "are"} unmanaged by Timeback CLI:`,
|
|
20145
|
+
"",
|
|
20146
|
+
...formatCourseExampleLines(unmanagedCourses)
|
|
20147
|
+
].join(`
|
|
20148
|
+
`), "Sensor required");
|
|
20149
|
+
const sensor = await promptSensor({
|
|
20150
|
+
defaultValue: config3.launchUrl ? deriveSensorFromLaunchUrl(config3.launchUrl) : undefined
|
|
20151
|
+
});
|
|
20152
|
+
if (sensor === undefined) {
|
|
20153
|
+
return null;
|
|
20154
|
+
}
|
|
20155
|
+
config3.sensor = sensor;
|
|
20156
|
+
if (config3.path?.endsWith(".json")) {
|
|
20157
|
+
const result = await updateTimebackConfigFile(config3.path, { sensor });
|
|
20158
|
+
if (result.success) {
|
|
20159
|
+
M2.success(`Saved sensor to ${basename2(config3.path)}`);
|
|
20160
|
+
}
|
|
20161
|
+
}
|
|
20162
|
+
return [sensor];
|
|
19332
20163
|
}
|
|
19333
20164
|
async function resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnv) {
|
|
19334
20165
|
const parserOpts = { playcademy: opts.playcademy };
|
|
@@ -19336,7 +20167,59 @@ async function resolveConfig(courseIds, opts, credentials, configuredEnvs, defau
|
|
|
19336
20167
|
return resolved;
|
|
19337
20168
|
}
|
|
19338
20169
|
|
|
19339
|
-
//
|
|
20170
|
+
// src/cli/commands/serve/env.ts
|
|
20171
|
+
async function resolveServeEnvironment(options) {
|
|
20172
|
+
const { userConfig, hasEnvOverride, envOverride } = options;
|
|
20173
|
+
if (hasEnvOverride && envOverride) {
|
|
20174
|
+
return envOverride;
|
|
20175
|
+
}
|
|
20176
|
+
const stagingCount = userConfig.courseIds.staging?.length ?? 0;
|
|
20177
|
+
const productionCount = userConfig.courseIds.production?.length ?? 0;
|
|
20178
|
+
if (stagingCount === 0 && productionCount === 0) {
|
|
20179
|
+
return null;
|
|
20180
|
+
}
|
|
20181
|
+
if (stagingCount > 0 && productionCount === 0) {
|
|
20182
|
+
return "staging";
|
|
20183
|
+
}
|
|
20184
|
+
if (productionCount > 0 && stagingCount === 0) {
|
|
20185
|
+
return "production";
|
|
20186
|
+
}
|
|
20187
|
+
const selected = await ve({
|
|
20188
|
+
message: "Which environment would you like to use?",
|
|
20189
|
+
options: [
|
|
20190
|
+
{
|
|
20191
|
+
value: "staging",
|
|
20192
|
+
label: `Staging (${stagingCount} course${stagingCount === 1 ? "" : "s"})`
|
|
20193
|
+
},
|
|
20194
|
+
{
|
|
20195
|
+
value: "production",
|
|
20196
|
+
label: `Production (${productionCount} course${productionCount === 1 ? "" : "s"})`
|
|
20197
|
+
}
|
|
20198
|
+
]
|
|
20199
|
+
});
|
|
20200
|
+
if (isCancelled(selected)) {
|
|
20201
|
+
outro.cancelled();
|
|
20202
|
+
process.exit(0);
|
|
20203
|
+
}
|
|
20204
|
+
return selected;
|
|
20205
|
+
}
|
|
20206
|
+
async function promptEnvironmentForCourseIds(count) {
|
|
20207
|
+
const plural = count !== 1;
|
|
20208
|
+
const selected = await ve({
|
|
20209
|
+
message: plural ? "Which environment are these course IDs from?" : "Which environment is this course ID from?",
|
|
20210
|
+
options: [
|
|
20211
|
+
{ value: "staging", label: "Staging" },
|
|
20212
|
+
{ value: "production", label: "Production" }
|
|
20213
|
+
]
|
|
20214
|
+
});
|
|
20215
|
+
if (isCancelled(selected)) {
|
|
20216
|
+
outro.cancelled();
|
|
20217
|
+
process.exit(0);
|
|
20218
|
+
}
|
|
20219
|
+
return selected;
|
|
20220
|
+
}
|
|
20221
|
+
|
|
20222
|
+
// ../../node_modules/.bun/@hono+node-server@1.19.9+115df24086ffac64/node_modules/@hono/node-server/dist/index.mjs
|
|
19340
20223
|
import { createServer as createServerHTTP } from "http";
|
|
19341
20224
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
19342
20225
|
import { Http2ServerRequest } from "http2";
|
|
@@ -19732,7 +20615,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
19732
20615
|
});
|
|
19733
20616
|
if (!chunk) {
|
|
19734
20617
|
if (i === 1) {
|
|
19735
|
-
await new Promise((
|
|
20618
|
+
await new Promise((resolve5) => setTimeout(resolve5));
|
|
19736
20619
|
maxReadCount = 3;
|
|
19737
20620
|
continue;
|
|
19738
20621
|
}
|
|
@@ -19870,7 +20753,7 @@ var serve = (options, listeningListener) => {
|
|
|
19870
20753
|
return server;
|
|
19871
20754
|
};
|
|
19872
20755
|
|
|
19873
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
20756
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/compose.js
|
|
19874
20757
|
var compose = (middleware, onError, onNotFound) => {
|
|
19875
20758
|
return (context, next) => {
|
|
19876
20759
|
let index = -1;
|
|
@@ -19914,10 +20797,10 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
19914
20797
|
};
|
|
19915
20798
|
};
|
|
19916
20799
|
|
|
19917
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
20800
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/request/constants.js
|
|
19918
20801
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
19919
20802
|
|
|
19920
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
20803
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/body.js
|
|
19921
20804
|
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
19922
20805
|
const { all = false, dot = false } = options;
|
|
19923
20806
|
const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
|
|
@@ -19985,7 +20868,7 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
19985
20868
|
});
|
|
19986
20869
|
};
|
|
19987
20870
|
|
|
19988
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
20871
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/url.js
|
|
19989
20872
|
var splitPath = (path) => {
|
|
19990
20873
|
const paths = path.split("/");
|
|
19991
20874
|
if (paths[0] === "") {
|
|
@@ -20183,7 +21066,7 @@ var getQueryParams = (url2, key) => {
|
|
|
20183
21066
|
};
|
|
20184
21067
|
var decodeURIComponent_ = decodeURIComponent;
|
|
20185
21068
|
|
|
20186
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21069
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/request.js
|
|
20187
21070
|
var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
|
|
20188
21071
|
var HonoRequest = class {
|
|
20189
21072
|
raw;
|
|
@@ -20294,7 +21177,7 @@ var HonoRequest = class {
|
|
|
20294
21177
|
}
|
|
20295
21178
|
};
|
|
20296
21179
|
|
|
20297
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21180
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/html.js
|
|
20298
21181
|
var HtmlEscapedCallbackPhase = {
|
|
20299
21182
|
Stringify: 1,
|
|
20300
21183
|
BeforeStream: 2,
|
|
@@ -20332,7 +21215,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
|
|
|
20332
21215
|
}
|
|
20333
21216
|
};
|
|
20334
21217
|
|
|
20335
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21218
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/context.js
|
|
20336
21219
|
var TEXT_PLAIN = "text/plain; charset=UTF-8";
|
|
20337
21220
|
var setDefaultContentType = (contentType, headers) => {
|
|
20338
21221
|
return {
|
|
@@ -20498,7 +21381,7 @@ var Context = class {
|
|
|
20498
21381
|
};
|
|
20499
21382
|
};
|
|
20500
21383
|
|
|
20501
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21384
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router.js
|
|
20502
21385
|
var METHOD_NAME_ALL = "ALL";
|
|
20503
21386
|
var METHOD_NAME_ALL_LOWERCASE = "all";
|
|
20504
21387
|
var METHODS = ["get", "post", "put", "delete", "options", "patch"];
|
|
@@ -20506,10 +21389,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
|
|
|
20506
21389
|
var UnsupportedPathError = class extends Error {
|
|
20507
21390
|
};
|
|
20508
21391
|
|
|
20509
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21392
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/constants.js
|
|
20510
21393
|
var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
|
|
20511
21394
|
|
|
20512
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21395
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/hono-base.js
|
|
20513
21396
|
var notFoundHandler = (c) => {
|
|
20514
21397
|
return c.text("404 Not Found", 404);
|
|
20515
21398
|
};
|
|
@@ -20728,7 +21611,7 @@ var Hono = class _Hono {
|
|
|
20728
21611
|
};
|
|
20729
21612
|
};
|
|
20730
21613
|
|
|
20731
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21614
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
20732
21615
|
var emptyParam = [];
|
|
20733
21616
|
function match(method, path) {
|
|
20734
21617
|
const matchers = this.buildAllMatchers();
|
|
@@ -20749,7 +21632,7 @@ function match(method, path) {
|
|
|
20749
21632
|
return match2(method, path);
|
|
20750
21633
|
}
|
|
20751
21634
|
|
|
20752
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21635
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
20753
21636
|
var LABEL_REG_EXP_STR = "[^/]+";
|
|
20754
21637
|
var ONLY_WILDCARD_REG_EXP_STR = ".*";
|
|
20755
21638
|
var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
|
|
@@ -20853,7 +21736,7 @@ var Node = class _Node {
|
|
|
20853
21736
|
}
|
|
20854
21737
|
};
|
|
20855
21738
|
|
|
20856
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21739
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
20857
21740
|
var Trie = class {
|
|
20858
21741
|
#context = { varIndex: 0 };
|
|
20859
21742
|
#root = new Node;
|
|
@@ -20909,7 +21792,7 @@ var Trie = class {
|
|
|
20909
21792
|
}
|
|
20910
21793
|
};
|
|
20911
21794
|
|
|
20912
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21795
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
20913
21796
|
var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
|
|
20914
21797
|
var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
|
|
20915
21798
|
function buildWildcardRegExp(path) {
|
|
@@ -21074,7 +21957,7 @@ var RegExpRouter = class {
|
|
|
21074
21957
|
}
|
|
21075
21958
|
};
|
|
21076
21959
|
|
|
21077
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
21960
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
21078
21961
|
var PreparedRegExpRouter = class {
|
|
21079
21962
|
name = "PreparedRegExpRouter";
|
|
21080
21963
|
#matchers;
|
|
@@ -21146,7 +22029,7 @@ var PreparedRegExpRouter = class {
|
|
|
21146
22029
|
match = match;
|
|
21147
22030
|
};
|
|
21148
22031
|
|
|
21149
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
22032
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/smart-router/router.js
|
|
21150
22033
|
var SmartRouter = class {
|
|
21151
22034
|
name = "SmartRouter";
|
|
21152
22035
|
#routers = [];
|
|
@@ -21201,7 +22084,7 @@ var SmartRouter = class {
|
|
|
21201
22084
|
}
|
|
21202
22085
|
};
|
|
21203
22086
|
|
|
21204
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
22087
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/trie-router/node.js
|
|
21205
22088
|
var emptyParams = /* @__PURE__ */ Object.create(null);
|
|
21206
22089
|
var Node2 = class _Node2 {
|
|
21207
22090
|
#methods;
|
|
@@ -21355,7 +22238,7 @@ var Node2 = class _Node2 {
|
|
|
21355
22238
|
}
|
|
21356
22239
|
};
|
|
21357
22240
|
|
|
21358
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
22241
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/trie-router/router.js
|
|
21359
22242
|
var TrieRouter = class {
|
|
21360
22243
|
name = "TrieRouter";
|
|
21361
22244
|
#node;
|
|
@@ -21377,7 +22260,7 @@ var TrieRouter = class {
|
|
|
21377
22260
|
}
|
|
21378
22261
|
};
|
|
21379
22262
|
|
|
21380
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
22263
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/hono.js
|
|
21381
22264
|
var Hono2 = class extends Hono {
|
|
21382
22265
|
constructor(options = {}) {
|
|
21383
22266
|
super(options);
|
|
@@ -21387,7 +22270,7 @@ var Hono2 = class extends Hono {
|
|
|
21387
22270
|
}
|
|
21388
22271
|
};
|
|
21389
22272
|
|
|
21390
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
22273
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/middleware/cors/index.js
|
|
21391
22274
|
var cors = (options) => {
|
|
21392
22275
|
const defaults = {
|
|
21393
22276
|
origin: "*",
|
|
@@ -21802,10 +22685,6 @@ class Logger {
|
|
|
21802
22685
|
function createLogger(options = {}) {
|
|
21803
22686
|
return new Logger(options);
|
|
21804
22687
|
}
|
|
21805
|
-
// ../internal/utils/src/shared/format.ts
|
|
21806
|
-
function pluralize(count, singular, plural) {
|
|
21807
|
-
return count === 1 ? singular : plural ?? `${singular}s`;
|
|
21808
|
-
}
|
|
21809
22688
|
// src/server/services/bootstrap.ts
|
|
21810
22689
|
var log = createLogger({ scope: "studio:service:bootstrap" });
|
|
21811
22690
|
|
|
@@ -21822,7 +22701,7 @@ class BootstrapService {
|
|
|
21822
22701
|
const courses = await this.fetchCourses(courseIds, errors3);
|
|
21823
22702
|
const courseStructure = await this.fetchCourseStructure(courses, errors3);
|
|
21824
22703
|
const [enrollmentData, enrichedComponentResources] = await Promise.all([
|
|
21825
|
-
this.fetchEnrollments(courseStructure.classes),
|
|
22704
|
+
this.fetchEnrollments(courseStructure.classes, errors3),
|
|
21826
22705
|
this.fetchResources(courseStructure.componentResources, errors3)
|
|
21827
22706
|
]);
|
|
21828
22707
|
const enrichedEnrollments = await this.enrichEnrollmentsWithUsers(enrollmentData, errors3);
|
|
@@ -21914,9 +22793,33 @@ class BootstrapService {
|
|
|
21914
22793
|
return enrollmentData.enrollments;
|
|
21915
22794
|
}
|
|
21916
22795
|
try {
|
|
21917
|
-
const
|
|
21918
|
-
|
|
22796
|
+
const batches = splitIntoBatches(studentUserIds);
|
|
22797
|
+
log.debug("Fetching users in batches", {
|
|
22798
|
+
totalIds: studentUserIds.length,
|
|
22799
|
+
batchCount: batches.length
|
|
21919
22800
|
});
|
|
22801
|
+
const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.users.listAll({
|
|
22802
|
+
where: { sourcedId: { in: batch } }
|
|
22803
|
+
})));
|
|
22804
|
+
const failed = extractRejected(batchResults);
|
|
22805
|
+
if (failed.length === batches.length) {
|
|
22806
|
+
const firstReason = failed[0]?.reason;
|
|
22807
|
+
const message = firstReason?.message ?? String(firstReason);
|
|
22808
|
+
log.warn("All user batches failed", {
|
|
22809
|
+
failedCount: failed.length,
|
|
22810
|
+
totalBatches: batches.length,
|
|
22811
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22812
|
+
});
|
|
22813
|
+
errors3.push(createStudioError("USERS_FETCH_FAILED", message));
|
|
22814
|
+
} else if (failed.length > 0) {
|
|
22815
|
+
log.warn("Some user batches failed", {
|
|
22816
|
+
failedCount: failed.length,
|
|
22817
|
+
totalBatches: batches.length,
|
|
22818
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22819
|
+
});
|
|
22820
|
+
errors3.push(createStudioError("USERS_FETCH_PARTIAL", `${failed.length} of ${batches.length} user batches failed`));
|
|
22821
|
+
}
|
|
22822
|
+
const users = extractFulfilled(batchResults);
|
|
21920
22823
|
const userMap = new Map(users.map((u2) => [u2.sourcedId, u2]));
|
|
21921
22824
|
log.debug("Enriched enrollments with user details", { count: users.length });
|
|
21922
22825
|
return enrollmentData.enrollments.map((enrollment) => ({
|
|
@@ -21937,9 +22840,33 @@ class BootstrapService {
|
|
|
21937
22840
|
}
|
|
21938
22841
|
const uniqueResourceIds = [...new Set(resourceIds)];
|
|
21939
22842
|
try {
|
|
21940
|
-
const
|
|
21941
|
-
|
|
22843
|
+
const batches = splitIntoBatches(uniqueResourceIds);
|
|
22844
|
+
log.debug("Fetching resources in batches", {
|
|
22845
|
+
totalIds: uniqueResourceIds.length,
|
|
22846
|
+
batchCount: batches.length
|
|
21942
22847
|
});
|
|
22848
|
+
const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.resources.listAll({
|
|
22849
|
+
where: { sourcedId: { in: batch } }
|
|
22850
|
+
})));
|
|
22851
|
+
const failed = extractRejected(batchResults);
|
|
22852
|
+
if (failed.length === batches.length) {
|
|
22853
|
+
const firstReason = failed[0]?.reason;
|
|
22854
|
+
const message = firstReason?.message ?? String(firstReason);
|
|
22855
|
+
log.warn("All resource batches failed", {
|
|
22856
|
+
failedCount: failed.length,
|
|
22857
|
+
totalBatches: batches.length,
|
|
22858
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22859
|
+
});
|
|
22860
|
+
errors3.push(createStudioError("RESOURCES_FETCH_FAILED", message));
|
|
22861
|
+
} else if (failed.length > 0) {
|
|
22862
|
+
log.warn("Some resource batches failed", {
|
|
22863
|
+
failedCount: failed.length,
|
|
22864
|
+
totalBatches: batches.length,
|
|
22865
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22866
|
+
});
|
|
22867
|
+
errors3.push(createStudioError("RESOURCES_FETCH_PARTIAL", `${failed.length} of ${batches.length} resource batches failed`));
|
|
22868
|
+
}
|
|
22869
|
+
const resources = extractFulfilled(batchResults);
|
|
21943
22870
|
const resourceMap = new Map(resources.map((r2) => [r2.sourcedId, r2]));
|
|
21944
22871
|
return componentResources.map((cr) => ({
|
|
21945
22872
|
...cr,
|
|
@@ -21982,8 +22909,10 @@ class BootstrapService {
|
|
|
21982
22909
|
return null;
|
|
21983
22910
|
}
|
|
21984
22911
|
}
|
|
21985
|
-
async fetchEnrollments(classes) {
|
|
21986
|
-
const classIds =
|
|
22912
|
+
async fetchEnrollments(classes, errors3) {
|
|
22913
|
+
const classIds = [
|
|
22914
|
+
...new Set(classes.map((c) => c.sourcedId).filter((id) => !!id))
|
|
22915
|
+
];
|
|
21987
22916
|
const enrollments = [];
|
|
21988
22917
|
const studentIds = new Set;
|
|
21989
22918
|
const teacherIds = new Set;
|
|
@@ -21992,11 +22921,35 @@ class BootstrapService {
|
|
|
21992
22921
|
return { enrollments, studentIds, teacherIds, studentsByClass };
|
|
21993
22922
|
}
|
|
21994
22923
|
try {
|
|
21995
|
-
const
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
22924
|
+
const batches = splitIntoBatches(classIds);
|
|
22925
|
+
log.debug("Fetching enrollments in batches", {
|
|
22926
|
+
totalClassIds: classIds.length,
|
|
22927
|
+
batchCount: batches.length
|
|
21999
22928
|
});
|
|
22929
|
+
const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.enrollments.listAll({
|
|
22930
|
+
where: {
|
|
22931
|
+
"class.sourcedId": { in: batch }
|
|
22932
|
+
}
|
|
22933
|
+
})));
|
|
22934
|
+
const failed = extractRejected(batchResults);
|
|
22935
|
+
if (failed.length === batches.length) {
|
|
22936
|
+
const firstReason = failed[0]?.reason;
|
|
22937
|
+
const message = firstReason?.message ?? String(firstReason);
|
|
22938
|
+
log.warn("All enrollment batches failed", {
|
|
22939
|
+
failedCount: failed.length,
|
|
22940
|
+
totalBatches: batches.length,
|
|
22941
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22942
|
+
});
|
|
22943
|
+
errors3.push(createStudioError("ENROLLMENTS_FETCH_FAILED", message));
|
|
22944
|
+
} else if (failed.length > 0) {
|
|
22945
|
+
log.warn("Some enrollment batches failed", {
|
|
22946
|
+
failedCount: failed.length,
|
|
22947
|
+
totalBatches: batches.length,
|
|
22948
|
+
reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
|
|
22949
|
+
});
|
|
22950
|
+
errors3.push(createStudioError("ENROLLMENTS_FETCH_PARTIAL", `${failed.length} of ${batches.length} enrollment batches failed`));
|
|
22951
|
+
}
|
|
22952
|
+
const fetchedEnrollments = extractFulfilled(batchResults);
|
|
22000
22953
|
enrollments.push(...fetchedEnrollments);
|
|
22001
22954
|
for (const enrollment of fetchedEnrollments) {
|
|
22002
22955
|
const userId = enrollment.user?.sourcedId;
|
|
@@ -22022,6 +22975,7 @@ class BootstrapService {
|
|
|
22022
22975
|
} catch (error48) {
|
|
22023
22976
|
const message = getErrorMessage(error48);
|
|
22024
22977
|
log.warn("Failed to fetch enrollments", { error: message });
|
|
22978
|
+
errors3.push(createStudioError("ENROLLMENTS_FETCH_FAILED", message));
|
|
22025
22979
|
}
|
|
22026
22980
|
return { enrollments, studentIds, teacherIds, studentsByClass };
|
|
22027
22981
|
}
|
|
@@ -22036,9 +22990,16 @@ class EnrollmentService {
|
|
|
22036
22990
|
this.client = client;
|
|
22037
22991
|
}
|
|
22038
22992
|
async enroll(options) {
|
|
22039
|
-
const { classId, userId } = options;
|
|
22993
|
+
const { classId, courseId, userId } = options;
|
|
22040
22994
|
const errors3 = [];
|
|
22041
|
-
|
|
22995
|
+
if (!classId && !courseId) {
|
|
22996
|
+
errors3.push(createStudioError("ENV_INVALID", "Either classId or courseId is required"));
|
|
22997
|
+
return { success: false, errors: errors3 };
|
|
22998
|
+
}
|
|
22999
|
+
if (courseId && !classId) {
|
|
23000
|
+
return this.enrollViaCourse(userId, courseId, errors3);
|
|
23001
|
+
}
|
|
23002
|
+
log2.debug("Enrolling student via class", { classId, userId });
|
|
22042
23003
|
try {
|
|
22043
23004
|
const result = await this.client.oneroster.classes(classId).enroll({
|
|
22044
23005
|
sourcedId: userId,
|
|
@@ -22058,6 +23019,24 @@ class EnrollmentService {
|
|
|
22058
23019
|
return this.handleEnrollmentError(error48, "enroll", { classId, userId }, errors3);
|
|
22059
23020
|
}
|
|
22060
23021
|
}
|
|
23022
|
+
async enrollViaCourse(userId, courseId, errors3) {
|
|
23023
|
+
log2.debug("Enrolling student via course (EduBridge)", { courseId, userId });
|
|
23024
|
+
try {
|
|
23025
|
+
const enrollment = await this.client.edubridge.enrollments.enroll(userId, courseId);
|
|
23026
|
+
log2.debug("Student enrolled via EduBridge", {
|
|
23027
|
+
courseId,
|
|
23028
|
+
userId,
|
|
23029
|
+
enrollmentId: enrollment.id
|
|
23030
|
+
});
|
|
23031
|
+
return {
|
|
23032
|
+
success: true,
|
|
23033
|
+
enrollmentId: enrollment.id,
|
|
23034
|
+
errors: errors3
|
|
23035
|
+
};
|
|
23036
|
+
} catch (error48) {
|
|
23037
|
+
return this.handleEnrollmentError(error48, "enroll", { courseId, userId }, errors3);
|
|
23038
|
+
}
|
|
23039
|
+
}
|
|
22061
23040
|
async unenroll(options) {
|
|
22062
23041
|
const { enrollmentId } = options;
|
|
22063
23042
|
const errors3 = [];
|
|
@@ -22337,7 +23316,7 @@ class StudentSearchService {
|
|
|
22337
23316
|
const trimmedQuery = query.trim();
|
|
22338
23317
|
log5.debug("Searching students", { query: trimmedQuery, max });
|
|
22339
23318
|
try {
|
|
22340
|
-
const users = await this.client.oneroster.
|
|
23319
|
+
const users = await this.client.oneroster.users.listAll({
|
|
22341
23320
|
search: trimmedQuery,
|
|
22342
23321
|
where: { status: "active" },
|
|
22343
23322
|
max
|
|
@@ -22415,7 +23394,7 @@ function createManager(ctx) {
|
|
|
22415
23394
|
log6.debug("Manager ready", { environments: manager.names });
|
|
22416
23395
|
return manager;
|
|
22417
23396
|
}
|
|
22418
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
23397
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/factory/index.js
|
|
22419
23398
|
var Factory = class {
|
|
22420
23399
|
initApp;
|
|
22421
23400
|
#defaultAppOptions;
|
|
@@ -22478,7 +23457,7 @@ function createEnvMiddleware(ctx, manager) {
|
|
|
22478
23457
|
await next();
|
|
22479
23458
|
});
|
|
22480
23459
|
}
|
|
22481
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
23460
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/stream.js
|
|
22482
23461
|
var StreamingApi = class {
|
|
22483
23462
|
writer;
|
|
22484
23463
|
encoder;
|
|
@@ -22544,7 +23523,7 @@ var StreamingApi = class {
|
|
|
22544
23523
|
}
|
|
22545
23524
|
};
|
|
22546
23525
|
|
|
22547
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
23526
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/streaming/utils.js
|
|
22548
23527
|
var isOldBunVersion = () => {
|
|
22549
23528
|
const version2 = typeof Bun !== "undefined" ? Bun.version : undefined;
|
|
22550
23529
|
if (version2 === undefined) {
|
|
@@ -22555,15 +23534,14 @@ var isOldBunVersion = () => {
|
|
|
22555
23534
|
return result;
|
|
22556
23535
|
};
|
|
22557
23536
|
|
|
22558
|
-
// ../../node_modules/.bun/hono@4.11.
|
|
23537
|
+
// ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/streaming/sse.js
|
|
22559
23538
|
var SSEStreamingApi = class extends StreamingApi {
|
|
22560
23539
|
constructor(writable, readable) {
|
|
22561
23540
|
super(writable, readable);
|
|
22562
23541
|
}
|
|
22563
23542
|
async writeSSE(message) {
|
|
22564
23543
|
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
22565
|
-
const dataLines = data.split(
|
|
22566
|
-
`).map((line) => {
|
|
23544
|
+
const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
|
|
22567
23545
|
return `data: ${line}`;
|
|
22568
23546
|
}).join(`
|
|
22569
23547
|
`);
|
|
@@ -22579,7 +23557,7 @@ var SSEStreamingApi = class extends StreamingApi {
|
|
|
22579
23557
|
await this.write(sseData);
|
|
22580
23558
|
}
|
|
22581
23559
|
};
|
|
22582
|
-
var
|
|
23560
|
+
var run2 = async (stream, cb, onError) => {
|
|
22583
23561
|
try {
|
|
22584
23562
|
await cb(stream);
|
|
22585
23563
|
} catch (e2) {
|
|
@@ -22612,7 +23590,7 @@ var streamSSE = (c, cb, onError) => {
|
|
|
22612
23590
|
c.header("Content-Type", "text/event-stream");
|
|
22613
23591
|
c.header("Cache-Control", "no-cache");
|
|
22614
23592
|
c.header("Connection", "keep-alive");
|
|
22615
|
-
|
|
23593
|
+
run2(stream, cb, onError);
|
|
22616
23594
|
return c.newResponse(stream.responseReadable);
|
|
22617
23595
|
};
|
|
22618
23596
|
|
|
@@ -22706,8 +23684,11 @@ function runSSE(c, options) {
|
|
|
22706
23684
|
// src/server/controllers/enrollment.ts
|
|
22707
23685
|
var log9 = createLogger({ scope: "studio:route:enrollment" });
|
|
22708
23686
|
var enrollSchema = exports_external.object({
|
|
22709
|
-
|
|
22710
|
-
|
|
23687
|
+
userId: exports_external.string().min(1, "userId is required"),
|
|
23688
|
+
classId: exports_external.string().min(1).optional(),
|
|
23689
|
+
courseId: exports_external.string().min(1).optional()
|
|
23690
|
+
}).refine((data) => data.classId || data.courseId, {
|
|
23691
|
+
message: "Either classId or courseId is required"
|
|
22711
23692
|
});
|
|
22712
23693
|
var unenrollSchema = exports_external.object({
|
|
22713
23694
|
enrollmentId: exports_external.string().min(1, "enrollmentId is required")
|
|
@@ -22720,8 +23701,8 @@ async function handleEnroll(c) {
|
|
|
22720
23701
|
const error48 = createStudioError("ENV_INVALID", "Invalid request payload");
|
|
22721
23702
|
return c.json({ success: false, errors: [error48] }, 400);
|
|
22722
23703
|
}
|
|
22723
|
-
const { classId, userId } = parsedBody.data;
|
|
22724
|
-
const result = await enrollment.enroll({ classId, userId });
|
|
23704
|
+
const { classId, courseId, userId } = parsedBody.data;
|
|
23705
|
+
const result = await enrollment.enroll({ classId, courseId, userId });
|
|
22725
23706
|
return c.json(result, result.success ? 200 : 400);
|
|
22726
23707
|
}
|
|
22727
23708
|
async function handleUnenroll(c) {
|
|
@@ -22972,12 +23953,14 @@ function startServer(ctx, serverConfig, configFile) {
|
|
|
22972
23953
|
const app = createApp(ctx);
|
|
22973
23954
|
serve({ fetch: app.fetch, port: serverConfig.port }, (info) => {
|
|
22974
23955
|
const url2 = `http://localhost:${info.port}`;
|
|
22975
|
-
|
|
22976
|
-
console.log(
|
|
22977
|
-
console.log(
|
|
22978
|
-
console.log();
|
|
22979
|
-
console.log(
|
|
22980
|
-
console.log();
|
|
23956
|
+
const bar = gray("│");
|
|
23957
|
+
console.log(bar);
|
|
23958
|
+
console.log(bar);
|
|
23959
|
+
console.log(`${bar} ${green("┌")} ${bold(ctx.userConfig.name)} ${dim(`v${ctx.userConfig.version}`)}`);
|
|
23960
|
+
console.log(`${bar} ${green("└")} ${cyan(url2)}`);
|
|
23961
|
+
console.log(bar);
|
|
23962
|
+
console.log(`${bar} ${dim("press")} ${bold("h + enter")} ${dim("to show shortcuts")}`);
|
|
23963
|
+
console.log(bar);
|
|
22981
23964
|
if (configFile) {
|
|
22982
23965
|
M2.info(`Loaded ${greenBright(configFile)}`);
|
|
22983
23966
|
}
|
|
@@ -22986,33 +23969,124 @@ function startServer(ctx, serverConfig, configFile) {
|
|
|
22986
23969
|
}
|
|
22987
23970
|
|
|
22988
23971
|
// src/cli/commands/serve/index.ts
|
|
22989
|
-
async function
|
|
22990
|
-
const
|
|
23972
|
+
async function launchServer(serverConfig, userConfig, credentials, env2, opts, configFile) {
|
|
23973
|
+
const creds = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
23974
|
+
if (!creds) {
|
|
23975
|
+
process.exit(1);
|
|
23976
|
+
}
|
|
23977
|
+
const derivedSensors = await resolveSensors({
|
|
23978
|
+
config: userConfig,
|
|
23979
|
+
env: env2,
|
|
23980
|
+
opts,
|
|
23981
|
+
creds
|
|
23982
|
+
});
|
|
23983
|
+
if (derivedSensors === null) {
|
|
23984
|
+
process.exit(1);
|
|
23985
|
+
}
|
|
23986
|
+
const ctx = {
|
|
23987
|
+
serverConfig,
|
|
23988
|
+
userConfig,
|
|
23989
|
+
credentials,
|
|
23990
|
+
defaultEnvironment: env2,
|
|
23991
|
+
derivedSensors
|
|
23992
|
+
};
|
|
23993
|
+
startServer(ctx, serverConfig, configFile);
|
|
23994
|
+
}
|
|
23995
|
+
async function handleCourseIdsCase(courseIds, opts, serverConfig) {
|
|
23996
|
+
const env2 = opts.env ?? await promptEnvironmentForCourseIds(courseIds.length);
|
|
23997
|
+
let configuredEnvs = await getConfiguredEnvironments();
|
|
23998
|
+
let credentials = configuredEnvs.length > 0 ? await loadAllCredentials() : {};
|
|
23999
|
+
const resolvedResult = await resolveConfigSource(courseIds, opts, env2);
|
|
24000
|
+
if (!resolvedResult) {
|
|
24001
|
+
process.exit(1);
|
|
24002
|
+
}
|
|
24003
|
+
const { resolved, credentials: updatedCreds, configuredEnvs: updatedEnvs } = resolvedResult;
|
|
24004
|
+
credentials = updatedCreds;
|
|
24005
|
+
configuredEnvs = updatedEnvs;
|
|
24006
|
+
if (Object.keys(credentials).length === 0 && configuredEnvs.length > 0) {
|
|
24007
|
+
credentials = await loadAllCredentials();
|
|
24008
|
+
}
|
|
24009
|
+
await launchServer(serverConfig, resolved.userConfig, credentials, env2, opts, resolved.configFile);
|
|
24010
|
+
}
|
|
24011
|
+
async function handleConfigFileCase(userConfig, configPath, opts, serverConfig) {
|
|
24012
|
+
const env2 = await resolveServeEnvironment({
|
|
24013
|
+
userConfig,
|
|
24014
|
+
hasEnvOverride: Boolean(opts.env),
|
|
24015
|
+
envOverride: opts.env
|
|
24016
|
+
});
|
|
24017
|
+
if (env2 === null) {
|
|
24018
|
+
Me([
|
|
24019
|
+
`Your config file has no course IDs for ${bold("staging")} or ${bold("production")}.`,
|
|
24020
|
+
"",
|
|
24021
|
+
`Run ${greenBright(bold("timeback resources push"))} to populate course IDs in your config.`,
|
|
24022
|
+
"",
|
|
24023
|
+
`Alternatively, run ${greenBright(bold("timeback studio <courseId...>"))}.`
|
|
24024
|
+
].join(`
|
|
24025
|
+
`), "Note");
|
|
24026
|
+
M2.error("Config incomplete");
|
|
24027
|
+
console.log("");
|
|
24028
|
+
process.exit(1);
|
|
24029
|
+
}
|
|
24030
|
+
const envCourseIds = userConfig.courseIds[env2] ?? [];
|
|
24031
|
+
if (envCourseIds.length === 0) {
|
|
24032
|
+
const otherEnv = env2 === "staging" ? "production" : "staging";
|
|
24033
|
+
Me([
|
|
24034
|
+
`Your config file has no course IDs for ${bold(env2)}.`,
|
|
24035
|
+
"",
|
|
24036
|
+
`Try: ${greenBright(bold(`timeback studio --env ${otherEnv}`))}`,
|
|
24037
|
+
"",
|
|
24038
|
+
`Or run ${greenBright(bold("timeback resources push"))} to populate IDs for ${bold(env2)}.`
|
|
24039
|
+
].join(`
|
|
24040
|
+
`), "Note");
|
|
24041
|
+
M2.error("Config incomplete");
|
|
24042
|
+
console.log("");
|
|
24043
|
+
process.exit(1);
|
|
24044
|
+
}
|
|
24045
|
+
const credentials = await loadAllCredentials();
|
|
24046
|
+
await launchServer(serverConfig, userConfig, credentials, env2, opts, configPath);
|
|
24047
|
+
}
|
|
24048
|
+
async function handleImportFlowCase(opts, serverConfig) {
|
|
22991
24049
|
let configuredEnvs = await getConfiguredEnvironments();
|
|
22992
|
-
let credentials;
|
|
22993
|
-
let
|
|
24050
|
+
let credentials = {};
|
|
24051
|
+
let selectedEnv;
|
|
22994
24052
|
if (configuredEnvs.length === 0) {
|
|
22995
|
-
const result = await handleCredentialSetup();
|
|
22996
|
-
if (!result)
|
|
24053
|
+
const result = await handleCredentialSetup({ skipIntro: true });
|
|
24054
|
+
if (!result) {
|
|
22997
24055
|
process.exit(1);
|
|
24056
|
+
}
|
|
22998
24057
|
credentials = result.credentials;
|
|
22999
|
-
defaultEnvironment = result.defaultEnvironment;
|
|
23000
24058
|
configuredEnvs = Object.keys(credentials);
|
|
24059
|
+
selectedEnv = result.selectedEnvironment;
|
|
23001
24060
|
} else {
|
|
23002
24061
|
credentials = await loadAllCredentials();
|
|
23003
|
-
defaultEnvironment = await resolveDefaultEnvironment(opts.env);
|
|
23004
24062
|
}
|
|
23005
|
-
const
|
|
23006
|
-
|
|
24063
|
+
const env2 = selectedEnv ?? configuredEnvs[0] ?? "staging";
|
|
24064
|
+
const resolvedResult = await resolveConfigSource([], opts, env2);
|
|
24065
|
+
if (!resolvedResult) {
|
|
23007
24066
|
process.exit(1);
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
|
|
23012
|
-
|
|
23013
|
-
|
|
23014
|
-
|
|
23015
|
-
|
|
24067
|
+
}
|
|
24068
|
+
const { resolved } = resolvedResult;
|
|
24069
|
+
await launchServer(serverConfig, resolved.userConfig, credentials, env2, opts, resolved.configFile);
|
|
24070
|
+
}
|
|
24071
|
+
async function serveCommand(courseIds, opts) {
|
|
24072
|
+
intro("Timeback Studio");
|
|
24073
|
+
const serverConfig = getServerConfig();
|
|
24074
|
+
const parserOpts = { playcademy: opts.playcademy };
|
|
24075
|
+
if (courseIds.length > 0) {
|
|
24076
|
+
await handleCourseIdsCase(courseIds, opts, serverConfig);
|
|
24077
|
+
return;
|
|
24078
|
+
}
|
|
24079
|
+
const configResult = await loadConfig2(parserOpts);
|
|
24080
|
+
if (configResult.success) {
|
|
24081
|
+
await handleConfigFileCase(configResult.config, getRelativeConfigPath(configResult.config.path), opts, serverConfig);
|
|
24082
|
+
return;
|
|
24083
|
+
}
|
|
24084
|
+
const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
|
|
24085
|
+
if (!isMissingConfig) {
|
|
24086
|
+
M2.error(configResult.error);
|
|
24087
|
+
process.exit(1);
|
|
24088
|
+
}
|
|
24089
|
+
await handleImportFlowCase(opts, serverConfig);
|
|
23016
24090
|
}
|
|
23017
24091
|
|
|
23018
24092
|
// src/cli/index.ts
|