timeback-studio 0.1.5 → 0.1.7
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 +312 -55
- package/dist/cli/commands/credentials/add.d.ts.map +1 -1
- package/dist/cli/commands/credentials/create-account.d.ts +37 -0
- package/dist/cli/commands/credentials/create-account.d.ts.map +1 -0
- package/dist/cli/commands/credentials/email.d.ts.map +1 -1
- package/dist/cli/commands/credentials/lib/initial.d.ts +1 -1
- package/dist/cli/commands/credentials/lib/initial.d.ts.map +1 -1
- package/dist/cli/commands/serve/config.d.ts +1 -1
- package/dist/cli/commands/serve/config.d.ts.map +1 -1
- package/dist/cli/commands/serve/index.d.ts.map +1 -1
- package/dist/index.js +309 -52
- package/dist/server/controllers/bootstrap.d.ts +3 -0
- package/dist/server/controllers/bootstrap.d.ts.map +1 -1
- package/dist/server/services/status.d.ts +7 -4
- package/dist/server/services/status.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -2950,6 +2950,11 @@ ${t}
|
|
|
2950
2950
|
${import_picocolors2.default.green(C)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(_2.repeat(Math.max(s - i - 1, 1)) + me)}
|
|
2951
2951
|
${c}
|
|
2952
2952
|
${import_picocolors2.default.gray(de + _2.repeat(s + 2) + pe)}
|
|
2953
|
+
`);
|
|
2954
|
+
};
|
|
2955
|
+
var xe = (t = "") => {
|
|
2956
|
+
process.stdout.write(`${import_picocolors2.default.gray(d2)} ${import_picocolors2.default.red(t)}
|
|
2957
|
+
|
|
2953
2958
|
`);
|
|
2954
2959
|
};
|
|
2955
2960
|
var Ie = (t = "") => {
|
|
@@ -16695,7 +16700,7 @@ function intro(title) {
|
|
|
16695
16700
|
}
|
|
16696
16701
|
var outro = {
|
|
16697
16702
|
success: (message = "Done") => Se(green(message)),
|
|
16698
|
-
cancelled: () =>
|
|
16703
|
+
cancelled: () => xe(dim("Cancelled")),
|
|
16699
16704
|
error: (message) => Se(red(message)),
|
|
16700
16705
|
warn: (message) => Se(yellow(message)),
|
|
16701
16706
|
info: (message) => Se(dim(message))
|
|
@@ -16795,8 +16800,8 @@ async function getConfiguredEnvironments() {
|
|
|
16795
16800
|
return configured;
|
|
16796
16801
|
}
|
|
16797
16802
|
function getEnvCredentials() {
|
|
16798
|
-
const clientId = process.env.TIMEBACK_CLIENT_ID;
|
|
16799
|
-
const clientSecret = process.env.TIMEBACK_CLIENT_SECRET;
|
|
16803
|
+
const clientId = process.env.TIMEBACK_API_CLIENT_ID ?? process.env.TIMEBACK_CLIENT_ID;
|
|
16804
|
+
const clientSecret = process.env.TIMEBACK_API_CLIENT_SECRET ?? process.env.TIMEBACK_CLIENT_SECRET;
|
|
16800
16805
|
if (clientId && clientSecret) {
|
|
16801
16806
|
const result = CredentialsSchema.safeParse({ clientId, clientSecret });
|
|
16802
16807
|
if (result.success) {
|
|
@@ -16840,19 +16845,23 @@ async function validateEmailWithTimeback(environment, clientId, clientSecret, em
|
|
|
16840
16845
|
});
|
|
16841
16846
|
try {
|
|
16842
16847
|
const page = await client.oneroster.users.list({
|
|
16843
|
-
where: {
|
|
16848
|
+
where: {
|
|
16849
|
+
email: email3,
|
|
16850
|
+
status: "active"
|
|
16851
|
+
},
|
|
16844
16852
|
limit: 1
|
|
16845
16853
|
});
|
|
16846
16854
|
if (page.data.length === 0) {
|
|
16847
16855
|
return {
|
|
16848
16856
|
valid: false,
|
|
16857
|
+
reason: "not_found",
|
|
16849
16858
|
error: `No user found with email "${email3}" in ${environment}`
|
|
16850
16859
|
};
|
|
16851
16860
|
}
|
|
16852
16861
|
return { valid: true };
|
|
16853
16862
|
} catch (error48) {
|
|
16854
16863
|
const message = error48 instanceof Error ? error48.message : "Unknown error";
|
|
16855
|
-
return { valid: false, error: `Failed to validate email: ${message}` };
|
|
16864
|
+
return { valid: false, reason: "api_error", error: `Failed to validate email: ${message}` };
|
|
16856
16865
|
}
|
|
16857
16866
|
}
|
|
16858
16867
|
|
|
@@ -16868,7 +16877,7 @@ async function promptForCredentials(environment) {
|
|
|
16868
16877
|
}
|
|
16869
16878
|
});
|
|
16870
16879
|
if (isCancelled(clientId))
|
|
16871
|
-
return
|
|
16880
|
+
return { status: "cancelled" };
|
|
16872
16881
|
const clientSecret = await ge({
|
|
16873
16882
|
message: `Client Secret ${dim(`(${environment})`)}`,
|
|
16874
16883
|
validate: (value) => {
|
|
@@ -16878,7 +16887,7 @@ async function promptForCredentials(environment) {
|
|
|
16878
16887
|
}
|
|
16879
16888
|
});
|
|
16880
16889
|
if (isCancelled(clientSecret))
|
|
16881
|
-
return
|
|
16890
|
+
return { status: "cancelled" };
|
|
16882
16891
|
const email3 = await he({
|
|
16883
16892
|
message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
|
|
16884
16893
|
placeholder: "you@example.com",
|
|
@@ -16891,33 +16900,47 @@ async function promptForCredentials(environment) {
|
|
|
16891
16900
|
}
|
|
16892
16901
|
});
|
|
16893
16902
|
if (isCancelled(email3))
|
|
16894
|
-
return
|
|
16903
|
+
return { status: "cancelled" };
|
|
16895
16904
|
if (email3) {
|
|
16896
16905
|
const s = Y2();
|
|
16897
16906
|
s.start("Validating email...");
|
|
16898
16907
|
const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
|
|
16899
16908
|
if (!result.valid) {
|
|
16900
|
-
|
|
16901
|
-
|
|
16909
|
+
const errorMsg = result.error ?? "Email validation failed";
|
|
16910
|
+
s.stop(red(errorMsg));
|
|
16902
16911
|
M2.info("Please contact a Timeback admin to set up your account.");
|
|
16903
|
-
return
|
|
16912
|
+
return {
|
|
16913
|
+
status: "error",
|
|
16914
|
+
error: errorMsg
|
|
16915
|
+
};
|
|
16904
16916
|
}
|
|
16905
16917
|
s.stop(green("Email validated"));
|
|
16906
16918
|
}
|
|
16907
|
-
return {
|
|
16919
|
+
return {
|
|
16920
|
+
status: "ok",
|
|
16921
|
+
credentials: { clientId, clientSecret, email: email3 || undefined }
|
|
16922
|
+
};
|
|
16908
16923
|
}
|
|
16909
16924
|
async function ensureCredentials(options) {
|
|
16910
16925
|
const { env: env2, credentials, introTitle = "Timeback", skipIntro = false } = options;
|
|
16911
16926
|
const existing = credentials[env2];
|
|
16912
|
-
if (existing)
|
|
16913
|
-
return existing;
|
|
16927
|
+
if (existing) {
|
|
16928
|
+
return { status: "ok", credentials: existing, source: "existing" };
|
|
16929
|
+
}
|
|
16914
16930
|
if (!skipIntro) {
|
|
16915
16931
|
intro(introTitle);
|
|
16916
16932
|
}
|
|
16917
16933
|
Me(`No credentials configured for ${env2}.`, "Credential setup required");
|
|
16918
|
-
const
|
|
16919
|
-
if (
|
|
16920
|
-
|
|
16934
|
+
const promptResult = await promptForCredentials(env2);
|
|
16935
|
+
if (promptResult.status === "cancelled") {
|
|
16936
|
+
outro.cancelled();
|
|
16937
|
+
return { status: "cancelled" };
|
|
16938
|
+
}
|
|
16939
|
+
if (promptResult.status === "error") {
|
|
16940
|
+
outro.error("Credential setup failed");
|
|
16941
|
+
return { status: "error", error: promptResult.error };
|
|
16942
|
+
}
|
|
16943
|
+
const newCreds = promptResult.credentials;
|
|
16921
16944
|
const saved = await saveCredentials(env2, newCreds);
|
|
16922
16945
|
if (saved) {
|
|
16923
16946
|
M2.success(`${env2} credentials saved`);
|
|
@@ -16926,7 +16949,7 @@ async function ensureCredentials(options) {
|
|
|
16926
16949
|
M2.warn(`Credentials not saved`);
|
|
16927
16950
|
}
|
|
16928
16951
|
credentials[env2] = newCreds;
|
|
16929
|
-
return newCreds;
|
|
16952
|
+
return { status: "ok", credentials: newCreds, source: "prompted" };
|
|
16930
16953
|
}
|
|
16931
16954
|
// ../internal/cli-infra/src/config/playcademy.ts
|
|
16932
16955
|
var FILE_PATTERNS = ["playcademy.config.ts", "playcademy.config.js", "playcademy.config.json"];
|
|
@@ -17140,6 +17163,8 @@ var ActivityCompletedInput = exports_external.object({
|
|
|
17140
17163
|
metricsId: exports_external.string().optional(),
|
|
17141
17164
|
id: exports_external.string().optional(),
|
|
17142
17165
|
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
17166
|
+
edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
17167
|
+
session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
17143
17168
|
attempt: exports_external.number().int().min(1).optional(),
|
|
17144
17169
|
generatedExtensions: exports_external.object({
|
|
17145
17170
|
pctCompleteApp: exports_external.number().optional()
|
|
@@ -17152,7 +17177,9 @@ var TimeSpentInput = exports_external.object({
|
|
|
17152
17177
|
eventTime: IsoDateTimeString.optional(),
|
|
17153
17178
|
metricsId: exports_external.string().optional(),
|
|
17154
17179
|
id: exports_external.string().optional(),
|
|
17155
|
-
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
17180
|
+
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
17181
|
+
edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
17182
|
+
session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional()
|
|
17156
17183
|
}).strict();
|
|
17157
17184
|
var TimebackEvent = exports_external.union([TimebackActivityEvent, TimebackTimeSpentEvent]);
|
|
17158
17185
|
var CaliperEnvelope = exports_external.object({
|
|
@@ -17342,7 +17369,7 @@ var TimebackConfig = exports_external.object({
|
|
|
17342
17369
|
path: ["courses"]
|
|
17343
17370
|
});
|
|
17344
17371
|
// ../types/src/zod/edubridge.ts
|
|
17345
|
-
var EdubridgeDateString = IsoDateTimeString;
|
|
17372
|
+
var EdubridgeDateString = exports_external.union([IsoDateTimeString, IsoDateString]);
|
|
17346
17373
|
var EduBridgeEnrollment = exports_external.object({
|
|
17347
17374
|
id: exports_external.string(),
|
|
17348
17375
|
role: exports_external.string(),
|
|
@@ -19408,8 +19435,8 @@ async function addCredentials(options = {}) {
|
|
|
19408
19435
|
}
|
|
19409
19436
|
isOverwriting = true;
|
|
19410
19437
|
}
|
|
19411
|
-
const
|
|
19412
|
-
if (
|
|
19438
|
+
const result = await promptForCredentials(env2);
|
|
19439
|
+
if (result.status === "cancelled") {
|
|
19413
19440
|
if (!inline) {
|
|
19414
19441
|
if (isOverwriting) {
|
|
19415
19442
|
outro.info("Existing credentials unchanged");
|
|
@@ -19417,11 +19444,21 @@ async function addCredentials(options = {}) {
|
|
|
19417
19444
|
outro.cancelled();
|
|
19418
19445
|
}
|
|
19419
19446
|
}
|
|
19420
|
-
if (exitOnComplete)
|
|
19447
|
+
if (exitOnComplete) {
|
|
19421
19448
|
process.exit(0);
|
|
19449
|
+
}
|
|
19450
|
+
return;
|
|
19451
|
+
}
|
|
19452
|
+
if (result.status === "error") {
|
|
19453
|
+
if (!inline) {
|
|
19454
|
+
outro.error("Credential setup failed");
|
|
19455
|
+
}
|
|
19456
|
+
if (exitOnComplete) {
|
|
19457
|
+
process.exit(1);
|
|
19458
|
+
}
|
|
19422
19459
|
return;
|
|
19423
19460
|
}
|
|
19424
|
-
await saveCredentials(env2,
|
|
19461
|
+
await saveCredentials(env2, result.credentials);
|
|
19425
19462
|
M2.success(`${env2} credentials saved`);
|
|
19426
19463
|
savedCount++;
|
|
19427
19464
|
}
|
|
@@ -19433,6 +19470,184 @@ async function addCredentials(options = {}) {
|
|
|
19433
19470
|
if (exitOnComplete)
|
|
19434
19471
|
process.exit(0);
|
|
19435
19472
|
}
|
|
19473
|
+
// src/cli/commands/credentials/create-account.ts
|
|
19474
|
+
import { randomUUID } from "node:crypto";
|
|
19475
|
+
import { TimebackClient as TimebackClient2 } from "@timeback/core";
|
|
19476
|
+
async function promptName(label) {
|
|
19477
|
+
const value = await he({
|
|
19478
|
+
message: label,
|
|
19479
|
+
placeholder: "Enter name",
|
|
19480
|
+
validate: (v2) => {
|
|
19481
|
+
if (!v2?.trim())
|
|
19482
|
+
return `${label} is required`;
|
|
19483
|
+
}
|
|
19484
|
+
});
|
|
19485
|
+
if (isCancelled(value))
|
|
19486
|
+
return null;
|
|
19487
|
+
return value.trim();
|
|
19488
|
+
}
|
|
19489
|
+
async function searchOrganizations(client, query) {
|
|
19490
|
+
const allOrgs = await client.oneroster.orgs.listAll({
|
|
19491
|
+
where: { status: "active" },
|
|
19492
|
+
max: 100
|
|
19493
|
+
});
|
|
19494
|
+
if (!query.trim())
|
|
19495
|
+
return allOrgs;
|
|
19496
|
+
const lowerQuery = query.toLowerCase().trim();
|
|
19497
|
+
return allOrgs.filter((org) => org.name?.toLowerCase().includes(lowerQuery));
|
|
19498
|
+
}
|
|
19499
|
+
async function searchForOrganization(client) {
|
|
19500
|
+
const query = await he({
|
|
19501
|
+
message: "Search for organization",
|
|
19502
|
+
placeholder: "Enter organization name to search"
|
|
19503
|
+
});
|
|
19504
|
+
if (isCancelled(query))
|
|
19505
|
+
return null;
|
|
19506
|
+
const s = Y2();
|
|
19507
|
+
s.start("Searching organizations...");
|
|
19508
|
+
let results;
|
|
19509
|
+
try {
|
|
19510
|
+
results = await searchOrganizations(client, query);
|
|
19511
|
+
s.stop(green(`Found ${results.length} result${results.length === 1 ? "" : "s"}`));
|
|
19512
|
+
} catch (error48) {
|
|
19513
|
+
s.stop(red("Failed to search organizations"));
|
|
19514
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
19515
|
+
return null;
|
|
19516
|
+
}
|
|
19517
|
+
if (results.length === 0) {
|
|
19518
|
+
M2.warn("No organizations found matching your search");
|
|
19519
|
+
return promptOrganization(client);
|
|
19520
|
+
}
|
|
19521
|
+
const validResults = results.filter((org) => org.sourcedId);
|
|
19522
|
+
if (validResults.length === 0) {
|
|
19523
|
+
M2.warn("No valid organizations found (missing IDs)");
|
|
19524
|
+
return promptOrganization(client);
|
|
19525
|
+
}
|
|
19526
|
+
const options = validResults.map((org) => ({
|
|
19527
|
+
value: org.sourcedId,
|
|
19528
|
+
label: org.name ?? "Unnamed Organization"
|
|
19529
|
+
}));
|
|
19530
|
+
options.push({ value: "__back__", label: `${dim("←")} Back to options` });
|
|
19531
|
+
const selection = await ve({
|
|
19532
|
+
message: "Select an organization",
|
|
19533
|
+
options
|
|
19534
|
+
});
|
|
19535
|
+
if (isCancelled(selection))
|
|
19536
|
+
return null;
|
|
19537
|
+
if (selection === "__back__") {
|
|
19538
|
+
return promptOrganization(client);
|
|
19539
|
+
}
|
|
19540
|
+
return validResults.find((org) => org.sourcedId === selection) ?? null;
|
|
19541
|
+
}
|
|
19542
|
+
async function promptOrganization(client) {
|
|
19543
|
+
const action = await ve({
|
|
19544
|
+
message: "Organization",
|
|
19545
|
+
options: [
|
|
19546
|
+
{ value: "search", label: "Search for existing organization" },
|
|
19547
|
+
{ value: "create", label: "Create new organization" }
|
|
19548
|
+
]
|
|
19549
|
+
});
|
|
19550
|
+
if (isCancelled(action))
|
|
19551
|
+
return null;
|
|
19552
|
+
if (action === "create") {
|
|
19553
|
+
return createNewOrganization(client);
|
|
19554
|
+
}
|
|
19555
|
+
return searchForOrganization(client);
|
|
19556
|
+
}
|
|
19557
|
+
async function createNewOrganization(client) {
|
|
19558
|
+
const name = await he({
|
|
19559
|
+
message: "Organization name",
|
|
19560
|
+
placeholder: "Enter organization name",
|
|
19561
|
+
validate: (v2) => {
|
|
19562
|
+
if (!v2?.trim())
|
|
19563
|
+
return "Organization name is required";
|
|
19564
|
+
}
|
|
19565
|
+
});
|
|
19566
|
+
if (isCancelled(name))
|
|
19567
|
+
return null;
|
|
19568
|
+
const s = Y2();
|
|
19569
|
+
s.start("Creating organization...");
|
|
19570
|
+
const sourcedId = randomUUID();
|
|
19571
|
+
try {
|
|
19572
|
+
await client.oneroster.orgs.create({
|
|
19573
|
+
sourcedId,
|
|
19574
|
+
name: name.trim(),
|
|
19575
|
+
type: "school",
|
|
19576
|
+
status: "active"
|
|
19577
|
+
});
|
|
19578
|
+
const organization = await client.oneroster.orgs.get(sourcedId);
|
|
19579
|
+
s.stop(green(`Organization "${name}" created`));
|
|
19580
|
+
return organization;
|
|
19581
|
+
} catch (error48) {
|
|
19582
|
+
s.stop(red("Failed to create organization"));
|
|
19583
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
19584
|
+
return null;
|
|
19585
|
+
}
|
|
19586
|
+
}
|
|
19587
|
+
async function createUser(client, email3, givenName, familyName, organizationId) {
|
|
19588
|
+
const s = Y2();
|
|
19589
|
+
s.start("Creating account...");
|
|
19590
|
+
const sourcedId = randomUUID();
|
|
19591
|
+
try {
|
|
19592
|
+
await client.oneroster.users.create({
|
|
19593
|
+
sourcedId,
|
|
19594
|
+
givenName,
|
|
19595
|
+
familyName,
|
|
19596
|
+
email: email3.toLowerCase(),
|
|
19597
|
+
enabledUser: true,
|
|
19598
|
+
status: "active",
|
|
19599
|
+
roles: [
|
|
19600
|
+
{
|
|
19601
|
+
roleType: "primary",
|
|
19602
|
+
role: "administrator",
|
|
19603
|
+
org: { sourcedId: organizationId }
|
|
19604
|
+
}
|
|
19605
|
+
]
|
|
19606
|
+
});
|
|
19607
|
+
const user = await client.oneroster.users.get(sourcedId);
|
|
19608
|
+
s.stop(green("Account created successfully"));
|
|
19609
|
+
return user;
|
|
19610
|
+
} catch (error48) {
|
|
19611
|
+
s.stop(red("Failed to create account"));
|
|
19612
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
19613
|
+
return null;
|
|
19614
|
+
}
|
|
19615
|
+
}
|
|
19616
|
+
async function createAccountFlow(options) {
|
|
19617
|
+
const { environment, clientId, clientSecret, email: email3 } = options;
|
|
19618
|
+
M2.info("");
|
|
19619
|
+
M2.info(`No account found for ${dim(email3)} in ${environment}`);
|
|
19620
|
+
const shouldCreate = await ye({
|
|
19621
|
+
message: "Would you like to create a new account?",
|
|
19622
|
+
initialValue: true
|
|
19623
|
+
});
|
|
19624
|
+
if (isCancelled(shouldCreate)) {
|
|
19625
|
+
return { success: false };
|
|
19626
|
+
}
|
|
19627
|
+
if (!shouldCreate) {
|
|
19628
|
+
return { success: false, declined: true };
|
|
19629
|
+
}
|
|
19630
|
+
const client = new TimebackClient2({
|
|
19631
|
+
env: environment,
|
|
19632
|
+
auth: { clientId, clientSecret }
|
|
19633
|
+
});
|
|
19634
|
+
const givenName = await promptName("First name");
|
|
19635
|
+
if (!givenName)
|
|
19636
|
+
return { success: false };
|
|
19637
|
+
const familyName = await promptName("Last name");
|
|
19638
|
+
if (!familyName)
|
|
19639
|
+
return { success: false };
|
|
19640
|
+
const organization = await promptOrganization(client);
|
|
19641
|
+
if (!organization?.sourcedId)
|
|
19642
|
+
return { success: false };
|
|
19643
|
+
const user = await createUser(client, email3, givenName, familyName, organization.sourcedId);
|
|
19644
|
+
if (!user) {
|
|
19645
|
+
return { success: false };
|
|
19646
|
+
}
|
|
19647
|
+
M2.success(`Account created for ${user.givenName} ${user.familyName}`);
|
|
19648
|
+
return { success: true };
|
|
19649
|
+
}
|
|
19650
|
+
|
|
19436
19651
|
// src/cli/commands/credentials/email.ts
|
|
19437
19652
|
async function updateEmail(options = {}) {
|
|
19438
19653
|
const { exitOnComplete = true, inline = false } = options;
|
|
@@ -19501,32 +19716,58 @@ async function updateEmail(options = {}) {
|
|
|
19501
19716
|
return;
|
|
19502
19717
|
}
|
|
19503
19718
|
const emailUnchanged = email3 === (currentEmail ?? "");
|
|
19504
|
-
if (emailUnchanged) {
|
|
19505
|
-
if (!inline)
|
|
19506
|
-
outro.info("Email unchanged");
|
|
19507
|
-
if (exitOnComplete)
|
|
19508
|
-
process.exit(0);
|
|
19509
|
-
return;
|
|
19510
|
-
}
|
|
19511
19719
|
if (email3) {
|
|
19512
19720
|
const s = Y2();
|
|
19513
|
-
s.start("
|
|
19721
|
+
s.start("Checking account...");
|
|
19514
19722
|
const result = await validateEmailWithTimeback(targetEnv, currentCreds.clientId, currentCreds.clientSecret, email3);
|
|
19515
19723
|
if (!result.valid) {
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19724
|
+
if (result.reason !== "not_found") {
|
|
19725
|
+
s.stop(red("Account check failed"));
|
|
19726
|
+
M2.error(result.error ?? "Unknown error");
|
|
19727
|
+
if (!inline)
|
|
19728
|
+
outro.error("Account check failed");
|
|
19729
|
+
if (exitOnComplete)
|
|
19730
|
+
process.exit(1);
|
|
19731
|
+
return;
|
|
19732
|
+
}
|
|
19733
|
+
s.stop(red("No account found"));
|
|
19734
|
+
const { success: accountCreated, declined } = await createAccountFlow({
|
|
19735
|
+
environment: targetEnv,
|
|
19736
|
+
clientId: currentCreds.clientId,
|
|
19737
|
+
clientSecret: currentCreds.clientSecret,
|
|
19738
|
+
email: email3
|
|
19739
|
+
});
|
|
19740
|
+
if (!emailUnchanged) {
|
|
19741
|
+
await saveCredentials(targetEnv, {
|
|
19742
|
+
...currentCreds,
|
|
19743
|
+
email: email3
|
|
19744
|
+
});
|
|
19745
|
+
M2.success(`Email saved for ${targetEnv}`);
|
|
19746
|
+
}
|
|
19747
|
+
if (!inline) {
|
|
19748
|
+
if (accountCreated || declined) {
|
|
19749
|
+
outro.success();
|
|
19750
|
+
} else {
|
|
19751
|
+
outro.info("Setup incomplete - run this command again to finish");
|
|
19752
|
+
}
|
|
19753
|
+
}
|
|
19754
|
+
if (exitOnComplete)
|
|
19755
|
+
process.exit(0);
|
|
19756
|
+
return;
|
|
19757
|
+
}
|
|
19758
|
+
s.stop(green("Account verified"));
|
|
19759
|
+
if (emailUnchanged) {
|
|
19519
19760
|
if (!inline)
|
|
19520
|
-
outro.
|
|
19761
|
+
outro.info("Email unchanged");
|
|
19521
19762
|
if (exitOnComplete)
|
|
19522
|
-
process.exit(
|
|
19763
|
+
process.exit(0);
|
|
19523
19764
|
return;
|
|
19524
19765
|
}
|
|
19525
19766
|
await saveCredentials(targetEnv, {
|
|
19526
19767
|
...currentCreds,
|
|
19527
19768
|
email: email3
|
|
19528
19769
|
});
|
|
19529
|
-
|
|
19770
|
+
M2.success(`Email updated for ${targetEnv}`);
|
|
19530
19771
|
if (!inline)
|
|
19531
19772
|
outro.success();
|
|
19532
19773
|
if (exitOnComplete)
|
|
@@ -19688,14 +19929,21 @@ async function promptInitialCredentials(options = {}) {
|
|
|
19688
19929
|
outro.cancelled();
|
|
19689
19930
|
process.exit(0);
|
|
19690
19931
|
}
|
|
19932
|
+
const configuredEnvs = [];
|
|
19691
19933
|
for (const env2 of environments) {
|
|
19692
|
-
const
|
|
19693
|
-
if (
|
|
19694
|
-
|
|
19695
|
-
|
|
19934
|
+
const result = await promptForCredentials(env2);
|
|
19935
|
+
if (result.status === "cancelled") {
|
|
19936
|
+
outro.cancelled();
|
|
19937
|
+
process.exit(0);
|
|
19938
|
+
}
|
|
19939
|
+
if (result.status === "error") {
|
|
19940
|
+
outro.error("Credential setup failed");
|
|
19941
|
+
process.exit(1);
|
|
19696
19942
|
}
|
|
19943
|
+
await saveCredentials(env2, result.credentials);
|
|
19944
|
+
M2.success(`${env2} credentials saved`);
|
|
19945
|
+
configuredEnvs.push(env2);
|
|
19697
19946
|
}
|
|
19698
|
-
const configuredEnvs = environments;
|
|
19699
19947
|
let selectedEnv;
|
|
19700
19948
|
if (configuredEnvs.length === 1) {
|
|
19701
19949
|
selectedEnv = configuredEnvs[0];
|
|
@@ -19835,7 +20083,7 @@ function printError3(error48, opts = {}) {
|
|
|
19835
20083
|
parser.printError(error48);
|
|
19836
20084
|
}
|
|
19837
20085
|
// src/cli/lib/courses.ts
|
|
19838
|
-
import { TimebackClient as
|
|
20086
|
+
import { TimebackClient as TimebackClient3 } from "@timeback/core";
|
|
19839
20087
|
async function fetchCoursesByIds(client, ids) {
|
|
19840
20088
|
const courses = [];
|
|
19841
20089
|
for (const id of ids) {
|
|
@@ -19847,7 +20095,7 @@ async function fetchCoursesByIds(client, ids) {
|
|
|
19847
20095
|
return courses;
|
|
19848
20096
|
}
|
|
19849
20097
|
async function fetchCourses(creds, env2, ids) {
|
|
19850
|
-
const client = new
|
|
20098
|
+
const client = new TimebackClient3({
|
|
19851
20099
|
env: env2,
|
|
19852
20100
|
auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
|
|
19853
20101
|
});
|
|
@@ -19869,7 +20117,7 @@ async function checkCoursesManaged(creds, env2, ids) {
|
|
|
19869
20117
|
if (ids.length === 0) {
|
|
19870
20118
|
return { allManaged: true, unmanagedCourses: [] };
|
|
19871
20119
|
}
|
|
19872
|
-
const client = new
|
|
20120
|
+
const client = new TimebackClient3({
|
|
19873
20121
|
env: env2,
|
|
19874
20122
|
auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
|
|
19875
20123
|
});
|
|
@@ -19917,7 +20165,7 @@ async function handleCredentialSetup(options = {}) {
|
|
|
19917
20165
|
};
|
|
19918
20166
|
}
|
|
19919
20167
|
// src/cli/lib/onboarding/import.ts
|
|
19920
|
-
import { TimebackClient as
|
|
20168
|
+
import { TimebackClient as TimebackClient4 } from "@timeback/core";
|
|
19921
20169
|
async function promptImportApp(credentials, configuredEnvs) {
|
|
19922
20170
|
let env2;
|
|
19923
20171
|
if (configuredEnvs.length === 1 && configuredEnvs[0]) {
|
|
@@ -19930,7 +20178,7 @@ async function promptImportApp(credentials, configuredEnvs) {
|
|
|
19930
20178
|
env2 = selectedEnv;
|
|
19931
20179
|
}
|
|
19932
20180
|
const creds = credentials[env2];
|
|
19933
|
-
const client = new
|
|
20181
|
+
const client = new TimebackClient4({
|
|
19934
20182
|
env: env2,
|
|
19935
20183
|
auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
|
|
19936
20184
|
});
|
|
@@ -20058,10 +20306,11 @@ function buildUserConfigFromCourses(courses) {
|
|
|
20058
20306
|
};
|
|
20059
20307
|
}
|
|
20060
20308
|
async function resolveFromCourseIds(courseIds, env2, credentials, configuredEnvs) {
|
|
20061
|
-
const
|
|
20062
|
-
if (
|
|
20309
|
+
const ensureResult = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
20310
|
+
if (ensureResult.status !== "ok") {
|
|
20063
20311
|
return null;
|
|
20064
20312
|
}
|
|
20313
|
+
const creds = ensureResult.credentials;
|
|
20065
20314
|
const courses = await fetchCourses(creds, env2, courseIds);
|
|
20066
20315
|
if (courses.length === 0) {
|
|
20067
20316
|
M2.warn("No courses found for the provided IDs.");
|
|
@@ -22359,7 +22608,8 @@ var cors = (options) => {
|
|
|
22359
22608
|
async function handleBootstrap(c, ctx) {
|
|
22360
22609
|
const { bootstrap } = c.get("services");
|
|
22361
22610
|
const env2 = c.get("env");
|
|
22362
|
-
const
|
|
22611
|
+
const freshCredentials = await getSavedCredentials(env2);
|
|
22612
|
+
const email3 = freshCredentials?.email;
|
|
22363
22613
|
const courseIds = ctx.userConfig.courseIds[env2];
|
|
22364
22614
|
const result = await bootstrap.getBootstrap({ email: email3, courseIds });
|
|
22365
22615
|
return c.json(result);
|
|
@@ -23291,11 +23541,15 @@ class StatusService {
|
|
|
23291
23541
|
}
|
|
23292
23542
|
async getStatus() {
|
|
23293
23543
|
const configuredEnvironments = await getConfiguredEnvironments();
|
|
23544
|
+
const [stagingCreds, productionCreds] = await Promise.all([
|
|
23545
|
+
getSavedCredentials("staging"),
|
|
23546
|
+
getSavedCredentials("production")
|
|
23547
|
+
]);
|
|
23294
23548
|
return {
|
|
23295
23549
|
config: this.ctx.userConfig,
|
|
23296
23550
|
environment: this.ctx.defaultEnvironment,
|
|
23297
23551
|
configuredEnvironments,
|
|
23298
|
-
hasEmail: !!
|
|
23552
|
+
hasEmail: !!stagingCreds?.email || !!productionCreds?.email
|
|
23299
23553
|
};
|
|
23300
23554
|
}
|
|
23301
23555
|
}
|
|
@@ -23440,7 +23694,6 @@ function createEnvMiddleware(ctx, manager) {
|
|
|
23440
23694
|
}, 400);
|
|
23441
23695
|
}
|
|
23442
23696
|
if (!manager.has(env2)) {
|
|
23443
|
-
log7.warn("Environment not configured", { env: env2 });
|
|
23444
23697
|
const error48 = createStudioError("ENV_NOT_CONFIGURED", `Environment '${env2}' not configured`);
|
|
23445
23698
|
return c.json({
|
|
23446
23699
|
success: false,
|
|
@@ -23970,10 +24223,14 @@ function startServer(ctx, serverConfig, configFile) {
|
|
|
23970
24223
|
|
|
23971
24224
|
// src/cli/commands/serve/index.ts
|
|
23972
24225
|
async function launchServer(serverConfig, userConfig, credentials, env2, opts, configFile) {
|
|
23973
|
-
const
|
|
23974
|
-
if (
|
|
24226
|
+
const ensureResult = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
24227
|
+
if (ensureResult.status === "cancelled") {
|
|
24228
|
+
process.exit(0);
|
|
24229
|
+
}
|
|
24230
|
+
if (ensureResult.status === "error") {
|
|
23975
24231
|
process.exit(1);
|
|
23976
24232
|
}
|
|
24233
|
+
const creds = ensureResult.credentials;
|
|
23977
24234
|
const derivedSensors = await resolveSensors({
|
|
23978
24235
|
config: userConfig,
|
|
23979
24236
|
env: env2,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/add.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/add.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFhF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Creation
|
|
3
|
+
*
|
|
4
|
+
* Interactive CLI flow for creating a new Timeback user account
|
|
5
|
+
* when no matching account exists for the provided email.
|
|
6
|
+
*/
|
|
7
|
+
import type { CredentialEnvironment } from '@timeback/internal-cli-infra';
|
|
8
|
+
/**
|
|
9
|
+
* Options for creating a new account.
|
|
10
|
+
*/
|
|
11
|
+
interface CreateAccountOptions {
|
|
12
|
+
environment: CredentialEnvironment;
|
|
13
|
+
clientId: string;
|
|
14
|
+
clientSecret: string;
|
|
15
|
+
email: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Result of the account creation flow.
|
|
19
|
+
*/
|
|
20
|
+
interface CreateAccountResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
/** User explicitly declined to create an account (vs cancelled or failed) */
|
|
23
|
+
declined?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Interactive flow to create a new Timeback account.
|
|
27
|
+
*
|
|
28
|
+
* Called when no matching user is found for the provided email.
|
|
29
|
+
* Guides the user through entering their name and selecting/creating
|
|
30
|
+
* an organization.
|
|
31
|
+
*
|
|
32
|
+
* @param options - Account creation options
|
|
33
|
+
* @returns Result indicating success and the created user
|
|
34
|
+
*/
|
|
35
|
+
export declare function createAccountFlow(options: CreateAccountOptions): Promise<CreateAccountResult>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=create-account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-account.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/create-account.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAGzE;;GAEG;AACH,UAAU,oBAAoB;IAC7B,WAAW,EAAE,qBAAqB,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,UAAU,mBAAmB;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AA2ND;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA0CnG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/email.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/email.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJ7E"}
|
|
@@ -7,7 +7,7 @@ interface PromptInitialCredentialsOptions {
|
|
|
7
7
|
* Runs the first-time credential setup flow.
|
|
8
8
|
*
|
|
9
9
|
* @param options - Options for the prompt
|
|
10
|
-
* @returns The selected environment to use for this run, or null if cancelled
|
|
10
|
+
* @returns The selected environment to use for this run, or null if cancelled/error
|
|
11
11
|
*/
|
|
12
12
|
export declare function promptInitialCredentials(options?: PromptInitialCredentialsOptions): Promise<CredentialEnvironment | null>;
|
|
13
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/credentials/lib/initial.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAEzE,UAAU,+BAA+B;IACxC,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,GAAE,+BAAoC,GAC3C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"initial.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/credentials/lib/initial.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAEzE,UAAU,+BAA+B;IACxC,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,GAAE,+BAAoC,GAC3C,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAoEvC"}
|
|
@@ -42,7 +42,7 @@ export declare function buildUserConfigFromCourses(courses: CourseConfig[]): Loa
|
|
|
42
42
|
* @param env - Target environment
|
|
43
43
|
* @param credentials - Credentials map
|
|
44
44
|
* @param configuredEnvs - List of configured environments
|
|
45
|
-
* @returns Resolved config, or null if cancelled
|
|
45
|
+
* @returns Resolved config, or null if cancelled/error
|
|
46
46
|
*/
|
|
47
47
|
export declare function resolveFromCourseIds(courseIds: string[], env: Environment, credentials: EnvironmentCredentials, configuredEnvs: Environment[]): Promise<ResolvedConfig | null>;
|
|
48
48
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3D,UAAU,yBAAyB;IAClC,QAAQ,EAAE,cAAc,CAAA;IACxB,WAAW,EAAE,sBAAsB,CAAA;IACnC,cAAc,EAAE,WAAW,EAAE,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACxC,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,YAAY,EAClB,kBAAkB,EAAE,WAAW,GAC7B,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAqF3C;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CAqBhF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,CAkBpF;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACzC,SAAS,EAAE,MAAM,EAAE,EACnB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,WAAW,EAAE,GAC3B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3D,UAAU,yBAAyB;IAClC,QAAQ,EAAE,cAAc,CAAA;IACxB,WAAW,EAAE,sBAAsB,CAAA;IACnC,cAAc,EAAE,WAAW,EAAE,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACxC,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,YAAY,EAClB,kBAAkB,EAAE,WAAW,GAC7B,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAqF3C;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CAqBhF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,CAkBpF;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACzC,SAAS,EAAE,MAAM,EAAE,EACnB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,WAAW,EAAE,GAC3B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAgChC;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC9C,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,WAAW,EAAE,EAC7B,UAAU,EAAE,WAAW,EACvB,UAAU,GAAE,aAAkB,GAC5B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0BhC;AAmCD;;;;;;;;;GASG;AACH;;GAEG;AACH,UAAU,qBAAqB;IAC9B,MAAM,EAAE,gBAAgB,CAAA;IACxB,GAAG,EAAE,WAAW,CAAA;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,WAAW,CAAA;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CACnC,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CA2EtC;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,WAAW,EAAE,EAC7B,UAAU,EAAE,WAAW,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAShC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA0M3C;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCzF"}
|
package/dist/index.js
CHANGED
|
@@ -840,6 +840,11 @@ ${t}
|
|
|
840
840
|
${import_picocolors2.default.green(C)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(_2.repeat(Math.max(s - i - 1, 1)) + me)}
|
|
841
841
|
${c}
|
|
842
842
|
${import_picocolors2.default.gray(de + _2.repeat(s + 2) + pe)}
|
|
843
|
+
`);
|
|
844
|
+
};
|
|
845
|
+
var xe = (t = "") => {
|
|
846
|
+
process.stdout.write(`${import_picocolors2.default.gray(d2)} ${import_picocolors2.default.red(t)}
|
|
847
|
+
|
|
843
848
|
`);
|
|
844
849
|
};
|
|
845
850
|
var Ie = (t = "") => {
|
|
@@ -14585,7 +14590,7 @@ function intro(title) {
|
|
|
14585
14590
|
}
|
|
14586
14591
|
var outro = {
|
|
14587
14592
|
success: (message = "Done") => Se(green(message)),
|
|
14588
|
-
cancelled: () =>
|
|
14593
|
+
cancelled: () => xe(dim("Cancelled")),
|
|
14589
14594
|
error: (message) => Se(red(message)),
|
|
14590
14595
|
warn: (message) => Se(yellow(message)),
|
|
14591
14596
|
info: (message) => Se(dim(message))
|
|
@@ -14685,8 +14690,8 @@ async function getConfiguredEnvironments() {
|
|
|
14685
14690
|
return configured;
|
|
14686
14691
|
}
|
|
14687
14692
|
function getEnvCredentials() {
|
|
14688
|
-
const clientId = process.env.TIMEBACK_CLIENT_ID;
|
|
14689
|
-
const clientSecret = process.env.TIMEBACK_CLIENT_SECRET;
|
|
14693
|
+
const clientId = process.env.TIMEBACK_API_CLIENT_ID ?? process.env.TIMEBACK_CLIENT_ID;
|
|
14694
|
+
const clientSecret = process.env.TIMEBACK_API_CLIENT_SECRET ?? process.env.TIMEBACK_CLIENT_SECRET;
|
|
14690
14695
|
if (clientId && clientSecret) {
|
|
14691
14696
|
const result = CredentialsSchema.safeParse({ clientId, clientSecret });
|
|
14692
14697
|
if (result.success) {
|
|
@@ -14730,19 +14735,23 @@ async function validateEmailWithTimeback(environment, clientId, clientSecret, em
|
|
|
14730
14735
|
});
|
|
14731
14736
|
try {
|
|
14732
14737
|
const page = await client.oneroster.users.list({
|
|
14733
|
-
where: {
|
|
14738
|
+
where: {
|
|
14739
|
+
email: email3,
|
|
14740
|
+
status: "active"
|
|
14741
|
+
},
|
|
14734
14742
|
limit: 1
|
|
14735
14743
|
});
|
|
14736
14744
|
if (page.data.length === 0) {
|
|
14737
14745
|
return {
|
|
14738
14746
|
valid: false,
|
|
14747
|
+
reason: "not_found",
|
|
14739
14748
|
error: `No user found with email "${email3}" in ${environment}`
|
|
14740
14749
|
};
|
|
14741
14750
|
}
|
|
14742
14751
|
return { valid: true };
|
|
14743
14752
|
} catch (error48) {
|
|
14744
14753
|
const message = error48 instanceof Error ? error48.message : "Unknown error";
|
|
14745
|
-
return { valid: false, error: `Failed to validate email: ${message}` };
|
|
14754
|
+
return { valid: false, reason: "api_error", error: `Failed to validate email: ${message}` };
|
|
14746
14755
|
}
|
|
14747
14756
|
}
|
|
14748
14757
|
|
|
@@ -14758,7 +14767,7 @@ async function promptForCredentials(environment) {
|
|
|
14758
14767
|
}
|
|
14759
14768
|
});
|
|
14760
14769
|
if (isCancelled(clientId))
|
|
14761
|
-
return
|
|
14770
|
+
return { status: "cancelled" };
|
|
14762
14771
|
const clientSecret = await ge({
|
|
14763
14772
|
message: `Client Secret ${dim(`(${environment})`)}`,
|
|
14764
14773
|
validate: (value) => {
|
|
@@ -14768,7 +14777,7 @@ async function promptForCredentials(environment) {
|
|
|
14768
14777
|
}
|
|
14769
14778
|
});
|
|
14770
14779
|
if (isCancelled(clientSecret))
|
|
14771
|
-
return
|
|
14780
|
+
return { status: "cancelled" };
|
|
14772
14781
|
const email3 = await he({
|
|
14773
14782
|
message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
|
|
14774
14783
|
placeholder: "you@example.com",
|
|
@@ -14781,33 +14790,47 @@ async function promptForCredentials(environment) {
|
|
|
14781
14790
|
}
|
|
14782
14791
|
});
|
|
14783
14792
|
if (isCancelled(email3))
|
|
14784
|
-
return
|
|
14793
|
+
return { status: "cancelled" };
|
|
14785
14794
|
if (email3) {
|
|
14786
14795
|
const s = Y2();
|
|
14787
14796
|
s.start("Validating email...");
|
|
14788
14797
|
const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
|
|
14789
14798
|
if (!result.valid) {
|
|
14790
|
-
|
|
14791
|
-
|
|
14799
|
+
const errorMsg = result.error ?? "Email validation failed";
|
|
14800
|
+
s.stop(red(errorMsg));
|
|
14792
14801
|
M2.info("Please contact a Timeback admin to set up your account.");
|
|
14793
|
-
return
|
|
14802
|
+
return {
|
|
14803
|
+
status: "error",
|
|
14804
|
+
error: errorMsg
|
|
14805
|
+
};
|
|
14794
14806
|
}
|
|
14795
14807
|
s.stop(green("Email validated"));
|
|
14796
14808
|
}
|
|
14797
|
-
return {
|
|
14809
|
+
return {
|
|
14810
|
+
status: "ok",
|
|
14811
|
+
credentials: { clientId, clientSecret, email: email3 || undefined }
|
|
14812
|
+
};
|
|
14798
14813
|
}
|
|
14799
14814
|
async function ensureCredentials(options) {
|
|
14800
14815
|
const { env: env2, credentials, introTitle = "Timeback", skipIntro = false } = options;
|
|
14801
14816
|
const existing = credentials[env2];
|
|
14802
|
-
if (existing)
|
|
14803
|
-
return existing;
|
|
14817
|
+
if (existing) {
|
|
14818
|
+
return { status: "ok", credentials: existing, source: "existing" };
|
|
14819
|
+
}
|
|
14804
14820
|
if (!skipIntro) {
|
|
14805
14821
|
intro(introTitle);
|
|
14806
14822
|
}
|
|
14807
14823
|
Me(`No credentials configured for ${env2}.`, "Credential setup required");
|
|
14808
|
-
const
|
|
14809
|
-
if (
|
|
14810
|
-
|
|
14824
|
+
const promptResult = await promptForCredentials(env2);
|
|
14825
|
+
if (promptResult.status === "cancelled") {
|
|
14826
|
+
outro.cancelled();
|
|
14827
|
+
return { status: "cancelled" };
|
|
14828
|
+
}
|
|
14829
|
+
if (promptResult.status === "error") {
|
|
14830
|
+
outro.error("Credential setup failed");
|
|
14831
|
+
return { status: "error", error: promptResult.error };
|
|
14832
|
+
}
|
|
14833
|
+
const newCreds = promptResult.credentials;
|
|
14811
14834
|
const saved = await saveCredentials(env2, newCreds);
|
|
14812
14835
|
if (saved) {
|
|
14813
14836
|
M2.success(`${env2} credentials saved`);
|
|
@@ -14816,7 +14839,7 @@ async function ensureCredentials(options) {
|
|
|
14816
14839
|
M2.warn(`Credentials not saved`);
|
|
14817
14840
|
}
|
|
14818
14841
|
credentials[env2] = newCreds;
|
|
14819
|
-
return newCreds;
|
|
14842
|
+
return { status: "ok", credentials: newCreds, source: "prompted" };
|
|
14820
14843
|
}
|
|
14821
14844
|
// ../internal/cli-infra/src/config/playcademy.ts
|
|
14822
14845
|
var FILE_PATTERNS = ["playcademy.config.ts", "playcademy.config.js", "playcademy.config.json"];
|
|
@@ -15030,6 +15053,8 @@ var ActivityCompletedInput = exports_external.object({
|
|
|
15030
15053
|
metricsId: exports_external.string().optional(),
|
|
15031
15054
|
id: exports_external.string().optional(),
|
|
15032
15055
|
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
15056
|
+
edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
15057
|
+
session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
15033
15058
|
attempt: exports_external.number().int().min(1).optional(),
|
|
15034
15059
|
generatedExtensions: exports_external.object({
|
|
15035
15060
|
pctCompleteApp: exports_external.number().optional()
|
|
@@ -15042,7 +15067,9 @@ var TimeSpentInput = exports_external.object({
|
|
|
15042
15067
|
eventTime: IsoDateTimeString.optional(),
|
|
15043
15068
|
metricsId: exports_external.string().optional(),
|
|
15044
15069
|
id: exports_external.string().optional(),
|
|
15045
|
-
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
15070
|
+
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
15071
|
+
edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
|
|
15072
|
+
session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional()
|
|
15046
15073
|
}).strict();
|
|
15047
15074
|
var TimebackEvent = exports_external.union([TimebackActivityEvent, TimebackTimeSpentEvent]);
|
|
15048
15075
|
var CaliperEnvelope = exports_external.object({
|
|
@@ -15232,7 +15259,7 @@ var TimebackConfig = exports_external.object({
|
|
|
15232
15259
|
path: ["courses"]
|
|
15233
15260
|
});
|
|
15234
15261
|
// ../types/src/zod/edubridge.ts
|
|
15235
|
-
var EdubridgeDateString = IsoDateTimeString;
|
|
15262
|
+
var EdubridgeDateString = exports_external.union([IsoDateTimeString, IsoDateString]);
|
|
15236
15263
|
var EduBridgeEnrollment = exports_external.object({
|
|
15237
15264
|
id: exports_external.string(),
|
|
15238
15265
|
role: exports_external.string(),
|
|
@@ -17483,8 +17510,8 @@ async function addCredentials(options = {}) {
|
|
|
17483
17510
|
}
|
|
17484
17511
|
isOverwriting = true;
|
|
17485
17512
|
}
|
|
17486
|
-
const
|
|
17487
|
-
if (
|
|
17513
|
+
const result = await promptForCredentials(env2);
|
|
17514
|
+
if (result.status === "cancelled") {
|
|
17488
17515
|
if (!inline) {
|
|
17489
17516
|
if (isOverwriting) {
|
|
17490
17517
|
outro.info("Existing credentials unchanged");
|
|
@@ -17492,11 +17519,21 @@ async function addCredentials(options = {}) {
|
|
|
17492
17519
|
outro.cancelled();
|
|
17493
17520
|
}
|
|
17494
17521
|
}
|
|
17495
|
-
if (exitOnComplete)
|
|
17522
|
+
if (exitOnComplete) {
|
|
17496
17523
|
process.exit(0);
|
|
17524
|
+
}
|
|
17497
17525
|
return;
|
|
17498
17526
|
}
|
|
17499
|
-
|
|
17527
|
+
if (result.status === "error") {
|
|
17528
|
+
if (!inline) {
|
|
17529
|
+
outro.error("Credential setup failed");
|
|
17530
|
+
}
|
|
17531
|
+
if (exitOnComplete) {
|
|
17532
|
+
process.exit(1);
|
|
17533
|
+
}
|
|
17534
|
+
return;
|
|
17535
|
+
}
|
|
17536
|
+
await saveCredentials(env2, result.credentials);
|
|
17500
17537
|
M2.success(`${env2} credentials saved`);
|
|
17501
17538
|
savedCount++;
|
|
17502
17539
|
}
|
|
@@ -17508,6 +17545,184 @@ async function addCredentials(options = {}) {
|
|
|
17508
17545
|
if (exitOnComplete)
|
|
17509
17546
|
process.exit(0);
|
|
17510
17547
|
}
|
|
17548
|
+
// src/cli/commands/credentials/create-account.ts
|
|
17549
|
+
import { randomUUID } from "node:crypto";
|
|
17550
|
+
import { TimebackClient as TimebackClient3 } from "@timeback/core";
|
|
17551
|
+
async function promptName(label) {
|
|
17552
|
+
const value = await he({
|
|
17553
|
+
message: label,
|
|
17554
|
+
placeholder: "Enter name",
|
|
17555
|
+
validate: (v2) => {
|
|
17556
|
+
if (!v2?.trim())
|
|
17557
|
+
return `${label} is required`;
|
|
17558
|
+
}
|
|
17559
|
+
});
|
|
17560
|
+
if (isCancelled(value))
|
|
17561
|
+
return null;
|
|
17562
|
+
return value.trim();
|
|
17563
|
+
}
|
|
17564
|
+
async function searchOrganizations(client, query) {
|
|
17565
|
+
const allOrgs = await client.oneroster.orgs.listAll({
|
|
17566
|
+
where: { status: "active" },
|
|
17567
|
+
max: 100
|
|
17568
|
+
});
|
|
17569
|
+
if (!query.trim())
|
|
17570
|
+
return allOrgs;
|
|
17571
|
+
const lowerQuery = query.toLowerCase().trim();
|
|
17572
|
+
return allOrgs.filter((org) => org.name?.toLowerCase().includes(lowerQuery));
|
|
17573
|
+
}
|
|
17574
|
+
async function searchForOrganization(client) {
|
|
17575
|
+
const query = await he({
|
|
17576
|
+
message: "Search for organization",
|
|
17577
|
+
placeholder: "Enter organization name to search"
|
|
17578
|
+
});
|
|
17579
|
+
if (isCancelled(query))
|
|
17580
|
+
return null;
|
|
17581
|
+
const s = Y2();
|
|
17582
|
+
s.start("Searching organizations...");
|
|
17583
|
+
let results;
|
|
17584
|
+
try {
|
|
17585
|
+
results = await searchOrganizations(client, query);
|
|
17586
|
+
s.stop(green(`Found ${results.length} result${results.length === 1 ? "" : "s"}`));
|
|
17587
|
+
} catch (error48) {
|
|
17588
|
+
s.stop(red("Failed to search organizations"));
|
|
17589
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
17590
|
+
return null;
|
|
17591
|
+
}
|
|
17592
|
+
if (results.length === 0) {
|
|
17593
|
+
M2.warn("No organizations found matching your search");
|
|
17594
|
+
return promptOrganization(client);
|
|
17595
|
+
}
|
|
17596
|
+
const validResults = results.filter((org) => org.sourcedId);
|
|
17597
|
+
if (validResults.length === 0) {
|
|
17598
|
+
M2.warn("No valid organizations found (missing IDs)");
|
|
17599
|
+
return promptOrganization(client);
|
|
17600
|
+
}
|
|
17601
|
+
const options = validResults.map((org) => ({
|
|
17602
|
+
value: org.sourcedId,
|
|
17603
|
+
label: org.name ?? "Unnamed Organization"
|
|
17604
|
+
}));
|
|
17605
|
+
options.push({ value: "__back__", label: `${dim("←")} Back to options` });
|
|
17606
|
+
const selection = await ve({
|
|
17607
|
+
message: "Select an organization",
|
|
17608
|
+
options
|
|
17609
|
+
});
|
|
17610
|
+
if (isCancelled(selection))
|
|
17611
|
+
return null;
|
|
17612
|
+
if (selection === "__back__") {
|
|
17613
|
+
return promptOrganization(client);
|
|
17614
|
+
}
|
|
17615
|
+
return validResults.find((org) => org.sourcedId === selection) ?? null;
|
|
17616
|
+
}
|
|
17617
|
+
async function promptOrganization(client) {
|
|
17618
|
+
const action = await ve({
|
|
17619
|
+
message: "Organization",
|
|
17620
|
+
options: [
|
|
17621
|
+
{ value: "search", label: "Search for existing organization" },
|
|
17622
|
+
{ value: "create", label: "Create new organization" }
|
|
17623
|
+
]
|
|
17624
|
+
});
|
|
17625
|
+
if (isCancelled(action))
|
|
17626
|
+
return null;
|
|
17627
|
+
if (action === "create") {
|
|
17628
|
+
return createNewOrganization(client);
|
|
17629
|
+
}
|
|
17630
|
+
return searchForOrganization(client);
|
|
17631
|
+
}
|
|
17632
|
+
async function createNewOrganization(client) {
|
|
17633
|
+
const name = await he({
|
|
17634
|
+
message: "Organization name",
|
|
17635
|
+
placeholder: "Enter organization name",
|
|
17636
|
+
validate: (v2) => {
|
|
17637
|
+
if (!v2?.trim())
|
|
17638
|
+
return "Organization name is required";
|
|
17639
|
+
}
|
|
17640
|
+
});
|
|
17641
|
+
if (isCancelled(name))
|
|
17642
|
+
return null;
|
|
17643
|
+
const s = Y2();
|
|
17644
|
+
s.start("Creating organization...");
|
|
17645
|
+
const sourcedId = randomUUID();
|
|
17646
|
+
try {
|
|
17647
|
+
await client.oneroster.orgs.create({
|
|
17648
|
+
sourcedId,
|
|
17649
|
+
name: name.trim(),
|
|
17650
|
+
type: "school",
|
|
17651
|
+
status: "active"
|
|
17652
|
+
});
|
|
17653
|
+
const organization = await client.oneroster.orgs.get(sourcedId);
|
|
17654
|
+
s.stop(green(`Organization "${name}" created`));
|
|
17655
|
+
return organization;
|
|
17656
|
+
} catch (error48) {
|
|
17657
|
+
s.stop(red("Failed to create organization"));
|
|
17658
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
17659
|
+
return null;
|
|
17660
|
+
}
|
|
17661
|
+
}
|
|
17662
|
+
async function createUser(client, email3, givenName, familyName, organizationId) {
|
|
17663
|
+
const s = Y2();
|
|
17664
|
+
s.start("Creating account...");
|
|
17665
|
+
const sourcedId = randomUUID();
|
|
17666
|
+
try {
|
|
17667
|
+
await client.oneroster.users.create({
|
|
17668
|
+
sourcedId,
|
|
17669
|
+
givenName,
|
|
17670
|
+
familyName,
|
|
17671
|
+
email: email3.toLowerCase(),
|
|
17672
|
+
enabledUser: true,
|
|
17673
|
+
status: "active",
|
|
17674
|
+
roles: [
|
|
17675
|
+
{
|
|
17676
|
+
roleType: "primary",
|
|
17677
|
+
role: "administrator",
|
|
17678
|
+
org: { sourcedId: organizationId }
|
|
17679
|
+
}
|
|
17680
|
+
]
|
|
17681
|
+
});
|
|
17682
|
+
const user = await client.oneroster.users.get(sourcedId);
|
|
17683
|
+
s.stop(green("Account created successfully"));
|
|
17684
|
+
return user;
|
|
17685
|
+
} catch (error48) {
|
|
17686
|
+
s.stop(red("Failed to create account"));
|
|
17687
|
+
M2.error(error48 instanceof Error ? error48.message : "Unknown error");
|
|
17688
|
+
return null;
|
|
17689
|
+
}
|
|
17690
|
+
}
|
|
17691
|
+
async function createAccountFlow(options) {
|
|
17692
|
+
const { environment, clientId, clientSecret, email: email3 } = options;
|
|
17693
|
+
M2.info("");
|
|
17694
|
+
M2.info(`No account found for ${dim(email3)} in ${environment}`);
|
|
17695
|
+
const shouldCreate = await ye({
|
|
17696
|
+
message: "Would you like to create a new account?",
|
|
17697
|
+
initialValue: true
|
|
17698
|
+
});
|
|
17699
|
+
if (isCancelled(shouldCreate)) {
|
|
17700
|
+
return { success: false };
|
|
17701
|
+
}
|
|
17702
|
+
if (!shouldCreate) {
|
|
17703
|
+
return { success: false, declined: true };
|
|
17704
|
+
}
|
|
17705
|
+
const client = new TimebackClient3({
|
|
17706
|
+
env: environment,
|
|
17707
|
+
auth: { clientId, clientSecret }
|
|
17708
|
+
});
|
|
17709
|
+
const givenName = await promptName("First name");
|
|
17710
|
+
if (!givenName)
|
|
17711
|
+
return { success: false };
|
|
17712
|
+
const familyName = await promptName("Last name");
|
|
17713
|
+
if (!familyName)
|
|
17714
|
+
return { success: false };
|
|
17715
|
+
const organization = await promptOrganization(client);
|
|
17716
|
+
if (!organization?.sourcedId)
|
|
17717
|
+
return { success: false };
|
|
17718
|
+
const user = await createUser(client, email3, givenName, familyName, organization.sourcedId);
|
|
17719
|
+
if (!user) {
|
|
17720
|
+
return { success: false };
|
|
17721
|
+
}
|
|
17722
|
+
M2.success(`Account created for ${user.givenName} ${user.familyName}`);
|
|
17723
|
+
return { success: true };
|
|
17724
|
+
}
|
|
17725
|
+
|
|
17511
17726
|
// src/cli/commands/credentials/email.ts
|
|
17512
17727
|
async function updateEmail(options = {}) {
|
|
17513
17728
|
const { exitOnComplete = true, inline = false } = options;
|
|
@@ -17576,32 +17791,58 @@ async function updateEmail(options = {}) {
|
|
|
17576
17791
|
return;
|
|
17577
17792
|
}
|
|
17578
17793
|
const emailUnchanged = email3 === (currentEmail ?? "");
|
|
17579
|
-
if (emailUnchanged) {
|
|
17580
|
-
if (!inline)
|
|
17581
|
-
outro.info("Email unchanged");
|
|
17582
|
-
if (exitOnComplete)
|
|
17583
|
-
process.exit(0);
|
|
17584
|
-
return;
|
|
17585
|
-
}
|
|
17586
17794
|
if (email3) {
|
|
17587
17795
|
const s = Y2();
|
|
17588
|
-
s.start("
|
|
17796
|
+
s.start("Checking account...");
|
|
17589
17797
|
const result = await validateEmailWithTimeback(targetEnv, currentCreds.clientId, currentCreds.clientSecret, email3);
|
|
17590
17798
|
if (!result.valid) {
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17799
|
+
if (result.reason !== "not_found") {
|
|
17800
|
+
s.stop(red("Account check failed"));
|
|
17801
|
+
M2.error(result.error ?? "Unknown error");
|
|
17802
|
+
if (!inline)
|
|
17803
|
+
outro.error("Account check failed");
|
|
17804
|
+
if (exitOnComplete)
|
|
17805
|
+
process.exit(1);
|
|
17806
|
+
return;
|
|
17807
|
+
}
|
|
17808
|
+
s.stop(red("No account found"));
|
|
17809
|
+
const { success: accountCreated, declined } = await createAccountFlow({
|
|
17810
|
+
environment: targetEnv,
|
|
17811
|
+
clientId: currentCreds.clientId,
|
|
17812
|
+
clientSecret: currentCreds.clientSecret,
|
|
17813
|
+
email: email3
|
|
17814
|
+
});
|
|
17815
|
+
if (!emailUnchanged) {
|
|
17816
|
+
await saveCredentials(targetEnv, {
|
|
17817
|
+
...currentCreds,
|
|
17818
|
+
email: email3
|
|
17819
|
+
});
|
|
17820
|
+
M2.success(`Email saved for ${targetEnv}`);
|
|
17821
|
+
}
|
|
17822
|
+
if (!inline) {
|
|
17823
|
+
if (accountCreated || declined) {
|
|
17824
|
+
outro.success();
|
|
17825
|
+
} else {
|
|
17826
|
+
outro.info("Setup incomplete - run this command again to finish");
|
|
17827
|
+
}
|
|
17828
|
+
}
|
|
17829
|
+
if (exitOnComplete)
|
|
17830
|
+
process.exit(0);
|
|
17831
|
+
return;
|
|
17832
|
+
}
|
|
17833
|
+
s.stop(green("Account verified"));
|
|
17834
|
+
if (emailUnchanged) {
|
|
17594
17835
|
if (!inline)
|
|
17595
|
-
outro.
|
|
17836
|
+
outro.info("Email unchanged");
|
|
17596
17837
|
if (exitOnComplete)
|
|
17597
|
-
process.exit(
|
|
17838
|
+
process.exit(0);
|
|
17598
17839
|
return;
|
|
17599
17840
|
}
|
|
17600
17841
|
await saveCredentials(targetEnv, {
|
|
17601
17842
|
...currentCreds,
|
|
17602
17843
|
email: email3
|
|
17603
17844
|
});
|
|
17604
|
-
|
|
17845
|
+
M2.success(`Email updated for ${targetEnv}`);
|
|
17605
17846
|
if (!inline)
|
|
17606
17847
|
outro.success();
|
|
17607
17848
|
if (exitOnComplete)
|
|
@@ -17763,14 +18004,21 @@ async function promptInitialCredentials(options = {}) {
|
|
|
17763
18004
|
outro.cancelled();
|
|
17764
18005
|
process.exit(0);
|
|
17765
18006
|
}
|
|
18007
|
+
const configuredEnvs = [];
|
|
17766
18008
|
for (const env2 of environments) {
|
|
17767
|
-
const
|
|
17768
|
-
if (
|
|
17769
|
-
|
|
17770
|
-
|
|
18009
|
+
const result = await promptForCredentials(env2);
|
|
18010
|
+
if (result.status === "cancelled") {
|
|
18011
|
+
outro.cancelled();
|
|
18012
|
+
process.exit(0);
|
|
17771
18013
|
}
|
|
18014
|
+
if (result.status === "error") {
|
|
18015
|
+
outro.error("Credential setup failed");
|
|
18016
|
+
process.exit(1);
|
|
18017
|
+
}
|
|
18018
|
+
await saveCredentials(env2, result.credentials);
|
|
18019
|
+
M2.success(`${env2} credentials saved`);
|
|
18020
|
+
configuredEnvs.push(env2);
|
|
17772
18021
|
}
|
|
17773
|
-
const configuredEnvs = environments;
|
|
17774
18022
|
let selectedEnv;
|
|
17775
18023
|
if (configuredEnvs.length === 1) {
|
|
17776
18024
|
selectedEnv = configuredEnvs[0];
|
|
@@ -17807,7 +18055,7 @@ async function handleCredentialSetup(options = {}) {
|
|
|
17807
18055
|
};
|
|
17808
18056
|
}
|
|
17809
18057
|
// src/cli/lib/onboarding/import.ts
|
|
17810
|
-
import { TimebackClient as
|
|
18058
|
+
import { TimebackClient as TimebackClient4 } from "@timeback/core";
|
|
17811
18059
|
async function promptImportApp(credentials, configuredEnvs) {
|
|
17812
18060
|
let env2;
|
|
17813
18061
|
if (configuredEnvs.length === 1 && configuredEnvs[0]) {
|
|
@@ -17820,7 +18068,7 @@ async function promptImportApp(credentials, configuredEnvs) {
|
|
|
17820
18068
|
env2 = selectedEnv;
|
|
17821
18069
|
}
|
|
17822
18070
|
const creds = credentials[env2];
|
|
17823
|
-
const client = new
|
|
18071
|
+
const client = new TimebackClient4({
|
|
17824
18072
|
env: env2,
|
|
17825
18073
|
auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
|
|
17826
18074
|
});
|
|
@@ -17948,10 +18196,11 @@ function buildUserConfigFromCourses(courses) {
|
|
|
17948
18196
|
};
|
|
17949
18197
|
}
|
|
17950
18198
|
async function resolveFromCourseIds(courseIds, env2, credentials, configuredEnvs) {
|
|
17951
|
-
const
|
|
17952
|
-
if (
|
|
18199
|
+
const ensureResult = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
18200
|
+
if (ensureResult.status !== "ok") {
|
|
17953
18201
|
return null;
|
|
17954
18202
|
}
|
|
18203
|
+
const creds = ensureResult.credentials;
|
|
17955
18204
|
const courses = await fetchCourses(creds, env2, courseIds);
|
|
17956
18205
|
if (courses.length === 0) {
|
|
17957
18206
|
M2.warn("No courses found for the provided IDs.");
|
|
@@ -20249,7 +20498,8 @@ var cors = (options) => {
|
|
|
20249
20498
|
async function handleBootstrap(c, ctx) {
|
|
20250
20499
|
const { bootstrap } = c.get("services");
|
|
20251
20500
|
const env2 = c.get("env");
|
|
20252
|
-
const
|
|
20501
|
+
const freshCredentials = await getSavedCredentials(env2);
|
|
20502
|
+
const email3 = freshCredentials?.email;
|
|
20253
20503
|
const courseIds = ctx.userConfig.courseIds[env2];
|
|
20254
20504
|
const result = await bootstrap.getBootstrap({ email: email3, courseIds });
|
|
20255
20505
|
return c.json(result);
|
|
@@ -21181,11 +21431,15 @@ class StatusService {
|
|
|
21181
21431
|
}
|
|
21182
21432
|
async getStatus() {
|
|
21183
21433
|
const configuredEnvironments = await getConfiguredEnvironments();
|
|
21434
|
+
const [stagingCreds, productionCreds] = await Promise.all([
|
|
21435
|
+
getSavedCredentials("staging"),
|
|
21436
|
+
getSavedCredentials("production")
|
|
21437
|
+
]);
|
|
21184
21438
|
return {
|
|
21185
21439
|
config: this.ctx.userConfig,
|
|
21186
21440
|
environment: this.ctx.defaultEnvironment,
|
|
21187
21441
|
configuredEnvironments,
|
|
21188
|
-
hasEmail: !!
|
|
21442
|
+
hasEmail: !!stagingCreds?.email || !!productionCreds?.email
|
|
21189
21443
|
};
|
|
21190
21444
|
}
|
|
21191
21445
|
}
|
|
@@ -21330,7 +21584,6 @@ function createEnvMiddleware(ctx, manager) {
|
|
|
21330
21584
|
}, 400);
|
|
21331
21585
|
}
|
|
21332
21586
|
if (!manager.has(env2)) {
|
|
21333
|
-
log7.warn("Environment not configured", { env: env2 });
|
|
21334
21587
|
const error48 = createStudioError("ENV_NOT_CONFIGURED", `Environment '${env2}' not configured`);
|
|
21335
21588
|
return c.json({
|
|
21336
21589
|
success: false,
|
|
@@ -21860,10 +22113,14 @@ function startServer(ctx, serverConfig, configFile) {
|
|
|
21860
22113
|
|
|
21861
22114
|
// src/cli/commands/serve/index.ts
|
|
21862
22115
|
async function launchServer(serverConfig, userConfig, credentials, env2, opts, configFile) {
|
|
21863
|
-
const
|
|
21864
|
-
if (
|
|
22116
|
+
const ensureResult = await ensureCredentials({ env: env2, credentials, skipIntro: true });
|
|
22117
|
+
if (ensureResult.status === "cancelled") {
|
|
22118
|
+
process.exit(0);
|
|
22119
|
+
}
|
|
22120
|
+
if (ensureResult.status === "error") {
|
|
21865
22121
|
process.exit(1);
|
|
21866
22122
|
}
|
|
22123
|
+
const creds = ensureResult.credentials;
|
|
21867
22124
|
const derivedSensors = await resolveSensors({
|
|
21868
22125
|
config: userConfig,
|
|
21869
22126
|
env: env2,
|
|
@@ -11,6 +11,9 @@ import type { EnvVariables } from '../lib';
|
|
|
11
11
|
*
|
|
12
12
|
* Requires env middleware to set `env` and `client` context variables.
|
|
13
13
|
*
|
|
14
|
+
* NOTE: We read credentials fresh from disk to pick up changes made via CLI
|
|
15
|
+
* (e.g., email updates, account creation) without requiring a server restart.
|
|
16
|
+
*
|
|
14
17
|
* @param c - Hono context with env variables
|
|
15
18
|
* @param ctx - App context
|
|
16
19
|
* @returns JSON response with user and courses
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/server/controllers/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/server/controllers/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAE1C;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,EAAE,GAAG,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEAW7F"}
|
|
@@ -15,10 +15,13 @@ export declare class StatusService {
|
|
|
15
15
|
private readonly ctx;
|
|
16
16
|
constructor(ctx: AppContext);
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
* Build the status payload.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: We read credentials fresh from disk to pick up changes made via CLI
|
|
21
|
+
* (e.g., email updates, account creation) without requiring a server restart.
|
|
22
|
+
*
|
|
23
|
+
* @returns Status payload object
|
|
24
|
+
*/
|
|
22
25
|
getStatus(): Promise<StatusEventPayload>;
|
|
23
26
|
}
|
|
24
27
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/server/services/status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD;;;;;GAKG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAEhC,YAAY,GAAG,EAAE,UAAU,EAE1B;IAED
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/server/services/status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD;;;;;GAKG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAEhC,YAAY,GAAG,EAAE,UAAU,EAE1B;IAED;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAc7C;CACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "timeback-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^0.11.0",
|
|
29
29
|
"@hono/node-server": "^1.19.7",
|
|
30
|
-
"@timeback/core": "0.1.
|
|
30
|
+
"@timeback/core": "0.1.4",
|
|
31
31
|
"c12": "^3.3.3",
|
|
32
32
|
"colorette": "^2.0.20",
|
|
33
33
|
"commander": "^14.0.2",
|