@globio/cli 0.1.9 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +568 -139
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/auth/login.ts +86 -27
- package/src/auth/whoami.ts +27 -2
- package/src/commands/functions.ts +111 -20
- package/src/commands/init.ts +82 -13
- package/src/commands/migrate.ts +105 -54
- package/src/commands/profiles.ts +16 -1
- package/src/commands/projects.ts +141 -24
- package/src/commands/services.ts +11 -1
- package/src/commands/watch.ts +173 -0
- package/src/index.ts +52 -13
- package/src/lib/table.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -206,10 +206,10 @@ function renderTable(options) {
|
|
|
206
206
|
);
|
|
207
207
|
return lines.join("\n");
|
|
208
208
|
}
|
|
209
|
-
function header(
|
|
209
|
+
function header(version11, subtitle) {
|
|
210
210
|
const lines = [
|
|
211
211
|
"",
|
|
212
|
-
orange(" \u21D2\u21D2") + reset + " globio " + dim(
|
|
212
|
+
orange(" \u21D2\u21D2") + reset + " globio " + dim(version11),
|
|
213
213
|
dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")
|
|
214
214
|
];
|
|
215
215
|
if (subtitle) {
|
|
@@ -221,6 +221,10 @@ function header(version10, subtitle) {
|
|
|
221
221
|
function footer(text4) {
|
|
222
222
|
return "\n" + dim(" " + text4) + "\n";
|
|
223
223
|
}
|
|
224
|
+
function jsonOutput(data) {
|
|
225
|
+
console.log(JSON.stringify(data, null, 2));
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
224
228
|
|
|
225
229
|
// src/lib/banner.ts
|
|
226
230
|
var globioGradient = gradientString(
|
|
@@ -230,14 +234,14 @@ var globioGradient = gradientString(
|
|
|
230
234
|
"#ffba08",
|
|
231
235
|
"#ffd000"
|
|
232
236
|
);
|
|
233
|
-
function printBanner(
|
|
237
|
+
function printBanner(version11) {
|
|
234
238
|
const art = figlet.textSync("Globio", {
|
|
235
239
|
font: "ANSI Shadow",
|
|
236
240
|
horizontalLayout: "default"
|
|
237
241
|
});
|
|
238
242
|
console.log(globioGradient.multiline(art));
|
|
239
243
|
console.log(
|
|
240
|
-
globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" +
|
|
244
|
+
globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" + version11 + "\x1B[0m"
|
|
241
245
|
);
|
|
242
246
|
console.log("");
|
|
243
247
|
}
|
|
@@ -262,6 +266,18 @@ async function savePat(token) {
|
|
|
262
266
|
const account = await manageRequest("/account", { token });
|
|
263
267
|
return account;
|
|
264
268
|
}
|
|
269
|
+
function storeProfile(profileName, token, account) {
|
|
270
|
+
const hadProfiles = config.listProfiles().length > 0;
|
|
271
|
+
config.setProfile(profileName, {
|
|
272
|
+
pat: token,
|
|
273
|
+
account_email: account.email,
|
|
274
|
+
account_name: account.display_name ?? account.email,
|
|
275
|
+
created_at: Date.now()
|
|
276
|
+
});
|
|
277
|
+
if (profileName === "default" || !hadProfiles) {
|
|
278
|
+
config.setActiveProfile(profileName);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
265
281
|
function warnOnDuplicateAccount(accountEmail, targetProfileName) {
|
|
266
282
|
const allProfiles = config.listProfiles();
|
|
267
283
|
const duplicate = allProfiles.find((name) => {
|
|
@@ -275,8 +291,23 @@ function warnOnDuplicateAccount(accountEmail, targetProfileName) {
|
|
|
275
291
|
);
|
|
276
292
|
console.log("");
|
|
277
293
|
}
|
|
278
|
-
async function
|
|
279
|
-
const
|
|
294
|
+
async function completeTokenLogin(token, profileName, json = false) {
|
|
295
|
+
const account = await savePat(token);
|
|
296
|
+
if (!json) {
|
|
297
|
+
warnOnDuplicateAccount(account.email, profileName);
|
|
298
|
+
}
|
|
299
|
+
storeProfile(profileName, token, account);
|
|
300
|
+
if (json) {
|
|
301
|
+
jsonOutput({
|
|
302
|
+
success: true,
|
|
303
|
+
email: account.email,
|
|
304
|
+
name: account.display_name ?? account.email,
|
|
305
|
+
profile: profileName
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return account;
|
|
309
|
+
}
|
|
310
|
+
async function runTokenLogin(profileName, json = false) {
|
|
280
311
|
const token = await p.text({
|
|
281
312
|
message: "Paste your personal access token",
|
|
282
313
|
placeholder: "glo_pat_...",
|
|
@@ -293,17 +324,7 @@ async function runTokenLogin(profileName) {
|
|
|
293
324
|
const spinner2 = p.spinner();
|
|
294
325
|
spinner2.start("Validating personal access token...");
|
|
295
326
|
try {
|
|
296
|
-
const account = await
|
|
297
|
-
warnOnDuplicateAccount(account.email, profileName);
|
|
298
|
-
config.setProfile(profileName, {
|
|
299
|
-
pat: token,
|
|
300
|
-
account_email: account.email,
|
|
301
|
-
account_name: account.display_name ?? account.email,
|
|
302
|
-
created_at: Date.now()
|
|
303
|
-
});
|
|
304
|
-
if (profileName === "default" || !hadProfiles) {
|
|
305
|
-
config.setActiveProfile(profileName);
|
|
306
|
-
}
|
|
327
|
+
const account = await completeTokenLogin(token, profileName, json);
|
|
307
328
|
spinner2.stop("Token validated.");
|
|
308
329
|
p.outro(`Logged in as ${account.email}
|
|
309
330
|
Profile: ${profileName}`);
|
|
@@ -313,10 +334,9 @@ Profile: ${profileName}`);
|
|
|
313
334
|
process.exit(1);
|
|
314
335
|
}
|
|
315
336
|
}
|
|
316
|
-
async function runBrowserLogin(profileName) {
|
|
337
|
+
async function runBrowserLogin(profileName, json = false) {
|
|
317
338
|
const state = crypto.randomUUID();
|
|
318
339
|
const spinner2 = p.spinner();
|
|
319
|
-
const hadProfiles = config.listProfiles().length > 0;
|
|
320
340
|
await manageRequest("/cli-auth/request", {
|
|
321
341
|
method: "POST",
|
|
322
342
|
body: { state }
|
|
@@ -342,16 +362,26 @@ async function runBrowserLogin(profileName) {
|
|
|
342
362
|
method: "POST",
|
|
343
363
|
body: { code: status.code }
|
|
344
364
|
});
|
|
345
|
-
|
|
365
|
+
if (!json) {
|
|
366
|
+
warnOnDuplicateAccount(exchange.account.email, profileName);
|
|
367
|
+
}
|
|
346
368
|
config.setProfile(profileName, {
|
|
347
369
|
pat: exchange.token,
|
|
348
370
|
account_email: exchange.account.email,
|
|
349
371
|
account_name: exchange.account.display_name ?? exchange.account.email,
|
|
350
372
|
created_at: Date.now()
|
|
351
373
|
});
|
|
352
|
-
if (profileName === "default" ||
|
|
374
|
+
if (profileName === "default" || config.listProfiles().length === 1) {
|
|
353
375
|
config.setActiveProfile(profileName);
|
|
354
376
|
}
|
|
377
|
+
if (json) {
|
|
378
|
+
jsonOutput({
|
|
379
|
+
success: true,
|
|
380
|
+
email: exchange.account.email,
|
|
381
|
+
name: exchange.account.display_name ?? exchange.account.email,
|
|
382
|
+
profile: profileName
|
|
383
|
+
});
|
|
384
|
+
}
|
|
355
385
|
spinner2.stop("Browser approval received.");
|
|
356
386
|
p.outro(`Logged in as ${exchange.account.email}
|
|
357
387
|
Profile: ${profileName}`);
|
|
@@ -366,9 +396,34 @@ Profile: ${profileName}`);
|
|
|
366
396
|
process.exit(1);
|
|
367
397
|
}
|
|
368
398
|
async function login(options = {}) {
|
|
369
|
-
printBanner(version);
|
|
370
399
|
const profileName = options.profile ?? "default";
|
|
371
400
|
const existing = config.getProfile(profileName);
|
|
401
|
+
if (options.token) {
|
|
402
|
+
try {
|
|
403
|
+
const account = await completeTokenLogin(options.token, profileName, options.json);
|
|
404
|
+
if (!options.json) {
|
|
405
|
+
console.log(`Logged in as ${account.email}
|
|
406
|
+
Profile: ${profileName}`);
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (options.json) {
|
|
411
|
+
jsonOutput({
|
|
412
|
+
success: false,
|
|
413
|
+
error: error instanceof Error ? error.message : "Could not validate token"
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
console.log(failure(error instanceof Error ? error.message : "Could not validate token"));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (options.json) {
|
|
421
|
+
jsonOutput({
|
|
422
|
+
success: false,
|
|
423
|
+
error: "login --json requires --token <pat>"
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
printBanner(version);
|
|
372
427
|
if (existing) {
|
|
373
428
|
const proceed = await p.confirm({
|
|
374
429
|
message: `Already logged in as ${existing.account_email} on profile "${profileName}". Replace?`,
|
|
@@ -379,10 +434,6 @@ async function login(options = {}) {
|
|
|
379
434
|
return;
|
|
380
435
|
}
|
|
381
436
|
}
|
|
382
|
-
if (options.token) {
|
|
383
|
-
await runTokenLogin(profileName);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
437
|
const choice = await p.select({
|
|
387
438
|
message: "Choose a login method",
|
|
388
439
|
options: [
|
|
@@ -395,11 +446,11 @@ async function login(options = {}) {
|
|
|
395
446
|
process.exit(0);
|
|
396
447
|
}
|
|
397
448
|
if (choice === "token") {
|
|
398
|
-
await runTokenLogin(profileName);
|
|
449
|
+
await runTokenLogin(profileName, options.json);
|
|
399
450
|
return;
|
|
400
451
|
}
|
|
401
452
|
try {
|
|
402
|
-
await runBrowserLogin(profileName);
|
|
453
|
+
await runBrowserLogin(profileName, options.json);
|
|
403
454
|
} catch (error) {
|
|
404
455
|
p.outro(failure(error instanceof Error ? error.message : "Could not connect to Globio.") + "\x1B[0m");
|
|
405
456
|
process.exit(1);
|
|
@@ -453,14 +504,36 @@ var version2 = getCliVersion();
|
|
|
453
504
|
async function whoami(options = {}) {
|
|
454
505
|
const profileName = options.profile ?? config.getActiveProfile() ?? "default";
|
|
455
506
|
const profile = config.getProfile(profileName);
|
|
507
|
+
const active = config.getActiveProfile();
|
|
456
508
|
if (!profile) {
|
|
509
|
+
if (options.json) {
|
|
510
|
+
jsonOutput({
|
|
511
|
+
profile: profileName,
|
|
512
|
+
active: false,
|
|
513
|
+
account_email: null,
|
|
514
|
+
account_name: null,
|
|
515
|
+
org_name: null,
|
|
516
|
+
active_project_id: null,
|
|
517
|
+
active_project_name: null
|
|
518
|
+
});
|
|
519
|
+
}
|
|
457
520
|
console.log(
|
|
458
521
|
header(version2) + " " + failure("Not logged in.") + reset + " Run: globio login\n"
|
|
459
522
|
);
|
|
460
523
|
return;
|
|
461
524
|
}
|
|
525
|
+
if (options.json) {
|
|
526
|
+
jsonOutput({
|
|
527
|
+
profile: profileName,
|
|
528
|
+
active: profileName === active,
|
|
529
|
+
account_email: profile.account_email,
|
|
530
|
+
account_name: profile.account_name,
|
|
531
|
+
org_name: profile.org_name ?? null,
|
|
532
|
+
active_project_id: profile.active_project_id ?? null,
|
|
533
|
+
active_project_name: profile.active_project_name ?? null
|
|
534
|
+
});
|
|
535
|
+
}
|
|
462
536
|
const allProfiles = config.listProfiles();
|
|
463
|
-
const active = config.getActiveProfile();
|
|
464
537
|
const otherProfiles = allProfiles.filter((p6) => p6 !== profileName).join(", ") || "\u2014";
|
|
465
538
|
console.log(
|
|
466
539
|
header(
|
|
@@ -607,33 +680,40 @@ function resolveProfileName(profile) {
|
|
|
607
680
|
return profile ?? config.getActiveProfile() ?? "default";
|
|
608
681
|
}
|
|
609
682
|
async function migrateFirestore(options) {
|
|
610
|
-
|
|
611
|
-
|
|
683
|
+
if (!options.json) {
|
|
684
|
+
printBanner(version3);
|
|
685
|
+
p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
686
|
+
}
|
|
612
687
|
const { firestore } = await initFirebase(options.from);
|
|
613
688
|
const profileName = resolveProfileName(options.profile);
|
|
614
689
|
let collections = [];
|
|
615
690
|
if (options.all) {
|
|
616
691
|
const snapshot = await firestore.listCollections();
|
|
617
692
|
collections = snapshot.map((collection) => collection.id);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
`Found ${collections.length} collections: ${collections.join(", ")}`
|
|
621
|
-
)
|
|
622
|
-
|
|
693
|
+
if (!options.json) {
|
|
694
|
+
console.log(
|
|
695
|
+
green(`Found ${collections.length} collections: ${collections.join(", ")}`)
|
|
696
|
+
);
|
|
697
|
+
}
|
|
623
698
|
} else if (options.collection) {
|
|
624
699
|
collections = [options.collection];
|
|
625
700
|
} else {
|
|
701
|
+
if (options.json) {
|
|
702
|
+
jsonOutput({ success: false, error: "Specify --collection <name> or --all" });
|
|
703
|
+
}
|
|
626
704
|
console.log(failure("Specify --collection <name> or --all"));
|
|
627
705
|
process.exit(1);
|
|
628
706
|
}
|
|
629
707
|
const results = {};
|
|
630
708
|
for (const collectionId of collections) {
|
|
631
|
-
|
|
632
|
-
|
|
709
|
+
if (!options.json) {
|
|
710
|
+
console.log("");
|
|
711
|
+
console.log(" " + orange(collectionId));
|
|
712
|
+
}
|
|
633
713
|
const countSnap = await firestore.collection(collectionId).count().get();
|
|
634
714
|
const total = countSnap.data().count;
|
|
635
|
-
const bar = createProgressBar(collectionId);
|
|
636
|
-
bar
|
|
715
|
+
const bar = options.json ? null : createProgressBar(collectionId);
|
|
716
|
+
bar?.start(total, 0);
|
|
637
717
|
results[collectionId] = {
|
|
638
718
|
success: 0,
|
|
639
719
|
failed: 0,
|
|
@@ -669,36 +749,49 @@ async function migrateFirestore(options) {
|
|
|
669
749
|
results[collectionId].failedIds.push(doc.id);
|
|
670
750
|
}
|
|
671
751
|
processed++;
|
|
672
|
-
bar
|
|
752
|
+
bar?.update(processed);
|
|
673
753
|
}
|
|
674
754
|
lastDoc = snapshot.docs[snapshot.docs.length - 1] ?? null;
|
|
675
755
|
}
|
|
676
|
-
bar
|
|
677
|
-
|
|
678
|
-
green(` \u2713 ${results[collectionId].success} documents migrated`)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
)
|
|
691
|
-
);
|
|
756
|
+
bar?.stop();
|
|
757
|
+
if (!options.json) {
|
|
758
|
+
console.log(green(` \u2713 ${results[collectionId].success} documents migrated`));
|
|
759
|
+
if (indexFieldCount > 0) {
|
|
760
|
+
console.log(muted(` Indexes created for ${indexFieldCount} fields`));
|
|
761
|
+
}
|
|
762
|
+
if (results[collectionId].failed > 0) {
|
|
763
|
+
console.log(failure(` \u2717 ${results[collectionId].failed} failed`) + "\x1B[0m");
|
|
764
|
+
console.log(
|
|
765
|
+
muted(
|
|
766
|
+
" Failed IDs: " + results[collectionId].failedIds.slice(0, 10).join(", ") + (results[collectionId].failedIds.length > 10 ? "..." : "")
|
|
767
|
+
)
|
|
768
|
+
);
|
|
769
|
+
}
|
|
692
770
|
}
|
|
693
771
|
}
|
|
772
|
+
const summary = {
|
|
773
|
+
collections: collections.map((name) => ({
|
|
774
|
+
name,
|
|
775
|
+
migrated: results[name]?.success ?? 0,
|
|
776
|
+
failed: results[name]?.failed ?? 0,
|
|
777
|
+
failed_ids: results[name]?.failedIds ?? []
|
|
778
|
+
})),
|
|
779
|
+
total_migrated: collections.reduce((sum, name) => sum + (results[name]?.success ?? 0), 0),
|
|
780
|
+
total_failed: collections.reduce((sum, name) => sum + (results[name]?.failed ?? 0), 0)
|
|
781
|
+
};
|
|
782
|
+
if (options.json) {
|
|
783
|
+
jsonOutput(summary);
|
|
784
|
+
}
|
|
694
785
|
console.log("");
|
|
695
786
|
p3.outro(
|
|
696
787
|
orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
|
|
697
788
|
);
|
|
698
789
|
}
|
|
699
790
|
async function migrateFirebaseStorage(options) {
|
|
700
|
-
|
|
701
|
-
|
|
791
|
+
if (!options.json) {
|
|
792
|
+
printBanner(version3);
|
|
793
|
+
p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
794
|
+
}
|
|
702
795
|
const { storage } = await initFirebase(options.from);
|
|
703
796
|
const profileName = resolveProfileName(options.profile);
|
|
704
797
|
const profile = config.getProfile(profileName);
|
|
@@ -709,9 +802,11 @@ async function migrateFirebaseStorage(options) {
|
|
|
709
802
|
const bucket = storage.bucket(bucketName);
|
|
710
803
|
const prefix = options.folder ? options.folder.replace(/^\//, "") : "";
|
|
711
804
|
const [files] = await bucket.getFiles(prefix ? { prefix } : {});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
805
|
+
if (!options.json) {
|
|
806
|
+
console.log(green(`Found ${files.length} files to migrate`));
|
|
807
|
+
}
|
|
808
|
+
const bar = options.json ? null : createProgressBar("Storage");
|
|
809
|
+
bar?.start(files.length, 0);
|
|
715
810
|
let success = 0;
|
|
716
811
|
let failed = 0;
|
|
717
812
|
for (const file of files) {
|
|
@@ -725,16 +820,13 @@ async function migrateFirebaseStorage(options) {
|
|
|
725
820
|
basename(file.name) || file.name
|
|
726
821
|
);
|
|
727
822
|
formData.append("path", file.name);
|
|
728
|
-
const res = await fetch(
|
|
729
|
-
"
|
|
730
|
-
{
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
body: formData
|
|
736
|
-
}
|
|
737
|
-
);
|
|
823
|
+
const res = await fetch("https://api.globio.stanlink.online/vault/files", {
|
|
824
|
+
method: "POST",
|
|
825
|
+
headers: {
|
|
826
|
+
"X-Globio-Key": profile.project_api_key
|
|
827
|
+
},
|
|
828
|
+
body: formData
|
|
829
|
+
});
|
|
738
830
|
if (!res.ok) {
|
|
739
831
|
throw new Error(`Upload failed: ${res.status}`);
|
|
740
832
|
}
|
|
@@ -742,9 +834,16 @@ async function migrateFirebaseStorage(options) {
|
|
|
742
834
|
} catch {
|
|
743
835
|
failed++;
|
|
744
836
|
}
|
|
745
|
-
bar
|
|
837
|
+
bar?.increment();
|
|
838
|
+
}
|
|
839
|
+
bar?.stop();
|
|
840
|
+
const summary = {
|
|
841
|
+
migrated: success,
|
|
842
|
+
failed
|
|
843
|
+
};
|
|
844
|
+
if (options.json) {
|
|
845
|
+
jsonOutput(summary);
|
|
746
846
|
}
|
|
747
|
-
bar.stop();
|
|
748
847
|
console.log("");
|
|
749
848
|
console.log(green(` \u2713 ${success} files migrated`));
|
|
750
849
|
if (failed > 0) {
|
|
@@ -778,11 +877,46 @@ async function createServerKey(projectId, profileName) {
|
|
|
778
877
|
}
|
|
779
878
|
return created.token;
|
|
780
879
|
}
|
|
880
|
+
function buildSlug(value) {
|
|
881
|
+
return slugify(value).slice(0, 30);
|
|
882
|
+
}
|
|
883
|
+
async function createProjectRecord(input) {
|
|
884
|
+
const result = await manageRequest("/projects", {
|
|
885
|
+
method: "POST",
|
|
886
|
+
body: {
|
|
887
|
+
org_id: input.orgId,
|
|
888
|
+
name: input.name,
|
|
889
|
+
slug: input.slug ?? buildSlug(input.name),
|
|
890
|
+
environment: input.environment ?? "development"
|
|
891
|
+
},
|
|
892
|
+
profileName: input.profileName
|
|
893
|
+
});
|
|
894
|
+
config.setProfile(input.profileName, {
|
|
895
|
+
active_project_id: result.project.id,
|
|
896
|
+
active_project_name: result.project.name,
|
|
897
|
+
org_name: input.orgName,
|
|
898
|
+
project_api_key: result.keys.server
|
|
899
|
+
});
|
|
900
|
+
config.setActiveProfile(input.profileName);
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
781
903
|
async function projectsList(options = {}) {
|
|
782
904
|
const profileName = resolveProfileName2(options.profile);
|
|
783
905
|
config.requireAuth(profileName);
|
|
784
906
|
const projects2 = await manageRequest("/projects", { profileName });
|
|
785
907
|
const activeProjectId = config.getProfile(profileName)?.active_project_id;
|
|
908
|
+
if (options.json) {
|
|
909
|
+
jsonOutput(
|
|
910
|
+
projects2.map((project) => ({
|
|
911
|
+
id: project.id,
|
|
912
|
+
name: project.name,
|
|
913
|
+
org_id: project.org_id,
|
|
914
|
+
org_name: project.org_name,
|
|
915
|
+
environment: project.environment,
|
|
916
|
+
active: project.id === activeProjectId
|
|
917
|
+
}))
|
|
918
|
+
);
|
|
919
|
+
}
|
|
786
920
|
if (!projects2.length) {
|
|
787
921
|
console.log(header(version4) + " " + muted("No projects found.") + "\n");
|
|
788
922
|
return;
|
|
@@ -791,7 +925,9 @@ async function projectsList(options = {}) {
|
|
|
791
925
|
activeProjectId === project.id ? gold(project.name) + reset + " " + orange("\u25CF") + reset : white(project.name),
|
|
792
926
|
muted(project.id),
|
|
793
927
|
muted(project.org_name || project.org_id),
|
|
794
|
-
inactive(
|
|
928
|
+
inactive(
|
|
929
|
+
project.environment === "development" ? "dev" : project.environment === "production" ? "prod" : project.environment === "staging" ? "stg" : project.environment ?? "dev"
|
|
930
|
+
)
|
|
795
931
|
]);
|
|
796
932
|
console.log(header(version4));
|
|
797
933
|
console.log(
|
|
@@ -815,6 +951,9 @@ async function projectsUse(projectId, options = {}) {
|
|
|
815
951
|
const projects2 = await manageRequest("/projects", { profileName });
|
|
816
952
|
const project = projects2.find((item) => item.id === projectId);
|
|
817
953
|
if (!project) {
|
|
954
|
+
if (options.json) {
|
|
955
|
+
jsonOutput({ success: false, error: `Project not found: ${projectId}` });
|
|
956
|
+
}
|
|
818
957
|
console.log(failure(`Project not found: ${projectId}`));
|
|
819
958
|
process.exit(1);
|
|
820
959
|
}
|
|
@@ -827,6 +966,13 @@ async function projectsUse(projectId, options = {}) {
|
|
|
827
966
|
project_api_key: apiKey
|
|
828
967
|
});
|
|
829
968
|
config.setActiveProfile(profileName);
|
|
969
|
+
if (options.json) {
|
|
970
|
+
jsonOutput({
|
|
971
|
+
success: true,
|
|
972
|
+
project_id: project.id,
|
|
973
|
+
project_name: project.name
|
|
974
|
+
});
|
|
975
|
+
}
|
|
830
976
|
console.log(green("Active project: ") + `${project.name} (${project.id})`);
|
|
831
977
|
}
|
|
832
978
|
async function projectsCreate(options = {}) {
|
|
@@ -837,6 +983,45 @@ async function projectsCreate(options = {}) {
|
|
|
837
983
|
console.log(failure("No organizations found. Create one in the console first."));
|
|
838
984
|
process.exit(1);
|
|
839
985
|
}
|
|
986
|
+
const isNonInteractive = Boolean(options.name && options.org);
|
|
987
|
+
if (options.json && !isNonInteractive) {
|
|
988
|
+
jsonOutput({
|
|
989
|
+
success: false,
|
|
990
|
+
error: "projects create --json requires --name <name> and --org <orgId>"
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
if (isNonInteractive) {
|
|
994
|
+
const org = orgs.find((item) => item.id === options.org);
|
|
995
|
+
if (!org) {
|
|
996
|
+
console.log(failure(`Organization not found: ${options.org}`));
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
const result2 = await createProjectRecord({
|
|
1000
|
+
profileName,
|
|
1001
|
+
orgId: org.id,
|
|
1002
|
+
orgName: org.name,
|
|
1003
|
+
name: options.name,
|
|
1004
|
+
environment: options.env ?? "development"
|
|
1005
|
+
});
|
|
1006
|
+
if (options.json) {
|
|
1007
|
+
jsonOutput({
|
|
1008
|
+
success: true,
|
|
1009
|
+
project_id: result2.project.id,
|
|
1010
|
+
project_name: result2.project.name,
|
|
1011
|
+
org_id: org.id,
|
|
1012
|
+
environment: result2.project.environment,
|
|
1013
|
+
client_key: result2.keys.client,
|
|
1014
|
+
server_key: result2.keys.server
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
console.log("");
|
|
1018
|
+
console.log(green("Project created successfully."));
|
|
1019
|
+
console.log(orange("Project: ") + reset + `${result2.project.name} (${result2.project.id})`);
|
|
1020
|
+
console.log(orange("Client key: ") + reset + result2.keys.client);
|
|
1021
|
+
console.log(orange("Server key: ") + reset + result2.keys.server);
|
|
1022
|
+
console.log("");
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
840
1025
|
const orgId = await p4.select({
|
|
841
1026
|
message: "Select an organization",
|
|
842
1027
|
options: orgs.map((org) => ({
|
|
@@ -857,7 +1042,7 @@ async function projectsCreate(options = {}) {
|
|
|
857
1042
|
}),
|
|
858
1043
|
slug: ({ results }) => p4.text({
|
|
859
1044
|
message: "Project slug",
|
|
860
|
-
initialValue:
|
|
1045
|
+
initialValue: buildSlug(String(results.name ?? "")),
|
|
861
1046
|
validate: (value) => !value ? "Project slug is required" : void 0
|
|
862
1047
|
}),
|
|
863
1048
|
environment: () => p4.select({
|
|
@@ -876,23 +1061,14 @@ async function projectsCreate(options = {}) {
|
|
|
876
1061
|
}
|
|
877
1062
|
}
|
|
878
1063
|
);
|
|
879
|
-
const result = await
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
},
|
|
887
|
-
profileName
|
|
1064
|
+
const result = await createProjectRecord({
|
|
1065
|
+
profileName,
|
|
1066
|
+
orgId,
|
|
1067
|
+
orgName: orgs.find((org) => org.id === orgId)?.name,
|
|
1068
|
+
name: String(values.name),
|
|
1069
|
+
slug: String(values.slug),
|
|
1070
|
+
environment: String(values.environment)
|
|
888
1071
|
});
|
|
889
|
-
config.setProfile(profileName, {
|
|
890
|
-
active_project_id: result.project.id,
|
|
891
|
-
active_project_name: result.project.name,
|
|
892
|
-
org_name: orgs.find((org) => org.id === orgId)?.name,
|
|
893
|
-
project_api_key: result.keys.server
|
|
894
|
-
});
|
|
895
|
-
config.setActiveProfile(profileName);
|
|
896
1072
|
console.log("");
|
|
897
1073
|
console.log(green("Project created successfully."));
|
|
898
1074
|
console.log(orange("Project: ") + reset + `${result.project.name} (${result.project.id})`);
|
|
@@ -904,24 +1080,59 @@ async function projectsCreate(options = {}) {
|
|
|
904
1080
|
// src/commands/init.ts
|
|
905
1081
|
var version5 = getCliVersion();
|
|
906
1082
|
async function init(options = {}) {
|
|
907
|
-
printBanner(version5);
|
|
908
|
-
p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
|
|
909
1083
|
const profileName = options.profile ?? config.getActiveProfile() ?? "default";
|
|
910
1084
|
const profile = config.getProfile(profileName);
|
|
911
1085
|
if (!profile) {
|
|
1086
|
+
if (options.json) {
|
|
1087
|
+
jsonOutput({ success: false, error: `Run: npx @globio/cli login --profile ${profileName}` });
|
|
1088
|
+
}
|
|
912
1089
|
console.log(failure("Run: npx @globio/cli login --profile " + profileName));
|
|
913
1090
|
process.exit(1);
|
|
914
1091
|
}
|
|
915
|
-
|
|
916
|
-
|
|
1092
|
+
const isNonInteractive = Boolean(options.name && options.org);
|
|
1093
|
+
let filesCreated = [];
|
|
1094
|
+
if (options.json && !isNonInteractive) {
|
|
1095
|
+
jsonOutput({
|
|
1096
|
+
success: false,
|
|
1097
|
+
error: "init --json requires --name <name> and --org <orgId>"
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
if (!isNonInteractive) {
|
|
1101
|
+
printBanner(version5);
|
|
1102
|
+
p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
|
|
1103
|
+
if (!profile.active_project_id) {
|
|
1104
|
+
await projectsCreate({ profile: profileName });
|
|
1105
|
+
} else {
|
|
1106
|
+
await projectsUse(profile.active_project_id, { profile: profileName });
|
|
1107
|
+
}
|
|
917
1108
|
} else {
|
|
918
|
-
|
|
1109
|
+
const orgId = options.org;
|
|
1110
|
+
const orgName = profile.org_name;
|
|
1111
|
+
const created = await createProjectRecord({
|
|
1112
|
+
profileName,
|
|
1113
|
+
orgId,
|
|
1114
|
+
orgName,
|
|
1115
|
+
name: options.name,
|
|
1116
|
+
slug: options.slug ?? buildSlug(options.name),
|
|
1117
|
+
environment: options.env ?? "development"
|
|
1118
|
+
});
|
|
1119
|
+
void created;
|
|
919
1120
|
}
|
|
920
|
-
const values =
|
|
1121
|
+
const values = isNonInteractive ? {
|
|
1122
|
+
migrateFromFirebase: Boolean(options.from),
|
|
1123
|
+
serviceAccountPath: options.from
|
|
1124
|
+
} : await promptInit();
|
|
921
1125
|
const activeProfile = config.getProfile(profileName);
|
|
922
1126
|
const activeProjectKey = activeProfile?.project_api_key;
|
|
923
1127
|
const { projectId: activeProjectId } = config.requireProject(profileName);
|
|
1128
|
+
const activeProjectName = activeProfile?.active_project_name ?? "unnamed";
|
|
924
1129
|
if (!activeProjectKey) {
|
|
1130
|
+
if (options.json) {
|
|
1131
|
+
jsonOutput({
|
|
1132
|
+
success: false,
|
|
1133
|
+
error: `No project API key cached. Run: npx @globio/cli projects use ${activeProjectId}`
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
925
1136
|
console.log(failure("No project API key cached. Run: npx @globio/cli projects use " + activeProjectId));
|
|
926
1137
|
process.exit(1);
|
|
927
1138
|
}
|
|
@@ -936,16 +1147,24 @@ export const globio = new Globio({
|
|
|
936
1147
|
});
|
|
937
1148
|
`
|
|
938
1149
|
);
|
|
939
|
-
|
|
1150
|
+
filesCreated.push("globio.config.ts");
|
|
1151
|
+
if (!options.json) {
|
|
1152
|
+
printSuccess("Created globio.config.ts");
|
|
1153
|
+
}
|
|
940
1154
|
}
|
|
941
1155
|
if (!existsSync2(".env")) {
|
|
942
1156
|
writeFileSync2(".env", `GLOBIO_API_KEY=${activeProjectKey}
|
|
943
1157
|
`);
|
|
944
|
-
|
|
1158
|
+
filesCreated.push(".env");
|
|
1159
|
+
if (!options.json) {
|
|
1160
|
+
printSuccess("Created .env");
|
|
1161
|
+
}
|
|
945
1162
|
}
|
|
946
1163
|
if (values.migrateFromFirebase && values.serviceAccountPath) {
|
|
947
|
-
|
|
948
|
-
|
|
1164
|
+
if (!options.json) {
|
|
1165
|
+
console.log("");
|
|
1166
|
+
printSuccess("Starting Firebase migration...");
|
|
1167
|
+
}
|
|
949
1168
|
await migrateFirestore({
|
|
950
1169
|
from: values.serviceAccountPath,
|
|
951
1170
|
all: true,
|
|
@@ -961,6 +1180,15 @@ export const globio = new Globio({
|
|
|
961
1180
|
profile: profileName
|
|
962
1181
|
});
|
|
963
1182
|
}
|
|
1183
|
+
if (options.json) {
|
|
1184
|
+
jsonOutput({
|
|
1185
|
+
success: true,
|
|
1186
|
+
project_id: activeProjectId,
|
|
1187
|
+
project_name: activeProjectName,
|
|
1188
|
+
api_key: activeProjectKey,
|
|
1189
|
+
files_created: filesCreated
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
964
1192
|
console.log("");
|
|
965
1193
|
p5.outro(
|
|
966
1194
|
orange("\u21D2\u21D2") + " Your project is ready.\n\n " + muted("Next steps:") + `
|
|
@@ -973,9 +1201,22 @@ export const globio = new Globio({
|
|
|
973
1201
|
|
|
974
1202
|
// src/commands/profiles.ts
|
|
975
1203
|
var version6 = getCliVersion();
|
|
976
|
-
async function profilesList() {
|
|
1204
|
+
async function profilesList(options = {}) {
|
|
977
1205
|
const profiles2 = config.listProfiles();
|
|
978
1206
|
const active = config.getActiveProfile();
|
|
1207
|
+
const wantsJson = options.json ?? process.argv.includes("--json");
|
|
1208
|
+
if (wantsJson) {
|
|
1209
|
+
jsonOutput(
|
|
1210
|
+
profiles2.map((name) => {
|
|
1211
|
+
const profile = config.getProfile(name);
|
|
1212
|
+
return {
|
|
1213
|
+
name,
|
|
1214
|
+
email: profile?.account_email ?? null,
|
|
1215
|
+
active: name === active
|
|
1216
|
+
};
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
979
1220
|
if (!profiles2.length) {
|
|
980
1221
|
console.log(
|
|
981
1222
|
header(version6) + " " + muted("No profiles. Run: globio login") + "\n"
|
|
@@ -1033,6 +1274,14 @@ async function servicesList(options = {}) {
|
|
|
1033
1274
|
serviceStatuses = {};
|
|
1034
1275
|
}
|
|
1035
1276
|
}
|
|
1277
|
+
if (options.json) {
|
|
1278
|
+
jsonOutput(
|
|
1279
|
+
Object.keys(SERVICE_DESCRIPTIONS).map((service) => ({
|
|
1280
|
+
service,
|
|
1281
|
+
enabled: serviceStatuses[service] ?? false
|
|
1282
|
+
}))
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1036
1285
|
const rows = Object.entries(SERVICE_DESCRIPTIONS).map(([slug, desc]) => {
|
|
1037
1286
|
const enabled = serviceStatuses[slug] ?? null;
|
|
1038
1287
|
return [
|
|
@@ -1075,10 +1324,28 @@ var version8 = getCliVersion();
|
|
|
1075
1324
|
function resolveProfileName3(profile) {
|
|
1076
1325
|
return profile ?? config.getActiveProfile() ?? "default";
|
|
1077
1326
|
}
|
|
1327
|
+
function parseJsonField(value) {
|
|
1328
|
+
if (!value) return null;
|
|
1329
|
+
try {
|
|
1330
|
+
return JSON.parse(value);
|
|
1331
|
+
} catch {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1078
1335
|
async function functionsList(options = {}) {
|
|
1079
1336
|
const profileName = resolveProfileName3(options.profile);
|
|
1080
1337
|
const client = getClient(profileName);
|
|
1081
1338
|
const result = await client.code.listFunctions();
|
|
1339
|
+
if (options.json) {
|
|
1340
|
+
jsonOutput(
|
|
1341
|
+
result.success ? result.data.map((fn) => ({
|
|
1342
|
+
slug: fn.slug,
|
|
1343
|
+
type: fn.type,
|
|
1344
|
+
trigger_event: fn.trigger_event,
|
|
1345
|
+
active: fn.active
|
|
1346
|
+
})) : []
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1082
1349
|
if (!result.success || !result.data.length) {
|
|
1083
1350
|
console.log(header(version8) + " " + muted("No functions found.") + "\n");
|
|
1084
1351
|
return;
|
|
@@ -1103,9 +1370,12 @@ async function functionsList(options = {}) {
|
|
|
1103
1370
|
);
|
|
1104
1371
|
console.log("");
|
|
1105
1372
|
}
|
|
1106
|
-
async function functionsCreate(slug,
|
|
1373
|
+
async function functionsCreate(slug, options = {}) {
|
|
1107
1374
|
const filename = `${slug}.js`;
|
|
1108
1375
|
if (existsSync3(filename)) {
|
|
1376
|
+
if (options.json) {
|
|
1377
|
+
jsonOutput({ success: false, file: filename, error: "File already exists" });
|
|
1378
|
+
}
|
|
1109
1379
|
console.log(inactive(`${filename} already exists.`));
|
|
1110
1380
|
return;
|
|
1111
1381
|
}
|
|
@@ -1125,6 +1395,9 @@ async function handler(input, globio) {
|
|
|
1125
1395
|
}
|
|
1126
1396
|
`;
|
|
1127
1397
|
writeFileSync3(filename, template);
|
|
1398
|
+
if (options.json) {
|
|
1399
|
+
jsonOutput({ success: true, file: filename });
|
|
1400
|
+
}
|
|
1128
1401
|
console.log(green(`Created ${filename}`));
|
|
1129
1402
|
console.log(muted(`Deploy with: npx @globio/cli functions deploy ${slug}`));
|
|
1130
1403
|
}
|
|
@@ -1141,8 +1414,8 @@ async function functionsDeploy(slug, options) {
|
|
|
1141
1414
|
const code = readFileSync4(filename, "utf-8");
|
|
1142
1415
|
const profileName = resolveProfileName3(options.profile);
|
|
1143
1416
|
const client = getClient(profileName);
|
|
1144
|
-
const spinner2 = ora(`Deploying ${slug}...`).start();
|
|
1145
1417
|
const existing = await client.code.getFunction(slug);
|
|
1418
|
+
const spinner2 = options.json ? null : ora(`Deploying ${slug}...`).start();
|
|
1146
1419
|
let result;
|
|
1147
1420
|
if (existing.success) {
|
|
1148
1421
|
result = await client.code.updateFunction(slug, {
|
|
@@ -1158,11 +1431,21 @@ async function functionsDeploy(slug, options) {
|
|
|
1158
1431
|
});
|
|
1159
1432
|
}
|
|
1160
1433
|
if (!result.success) {
|
|
1161
|
-
|
|
1434
|
+
if (options.json) {
|
|
1435
|
+
jsonOutput({ success: false, error: result.error.message });
|
|
1436
|
+
}
|
|
1437
|
+
spinner2?.fail("Deploy failed");
|
|
1162
1438
|
console.error(result.error.message);
|
|
1163
1439
|
process.exit(1);
|
|
1164
1440
|
}
|
|
1165
|
-
|
|
1441
|
+
if (options.json) {
|
|
1442
|
+
jsonOutput({
|
|
1443
|
+
success: true,
|
|
1444
|
+
slug,
|
|
1445
|
+
action: existing.success ? "updated" : "created"
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
spinner2?.succeed(existing.success ? `Updated ${slug}` : `Deployed ${slug}`);
|
|
1166
1449
|
}
|
|
1167
1450
|
async function functionsInvoke(slug, options) {
|
|
1168
1451
|
let input = {};
|
|
@@ -1176,14 +1459,23 @@ async function functionsInvoke(slug, options) {
|
|
|
1176
1459
|
}
|
|
1177
1460
|
const profileName = resolveProfileName3(options.profile);
|
|
1178
1461
|
const client = getClient(profileName);
|
|
1179
|
-
const spinner2 = ora(`Invoking ${slug}...`).start();
|
|
1462
|
+
const spinner2 = options.json ? null : ora(`Invoking ${slug}...`).start();
|
|
1180
1463
|
const result = await client.code.invoke(slug, input);
|
|
1181
|
-
spinner2
|
|
1464
|
+
spinner2?.stop();
|
|
1182
1465
|
if (!result.success) {
|
|
1466
|
+
if (options.json) {
|
|
1467
|
+
jsonOutput({ success: false, error: result.error.message });
|
|
1468
|
+
}
|
|
1183
1469
|
console.log(failure("Invocation failed"));
|
|
1184
1470
|
console.error(result.error.message);
|
|
1185
1471
|
return;
|
|
1186
1472
|
}
|
|
1473
|
+
if (options.json) {
|
|
1474
|
+
jsonOutput({
|
|
1475
|
+
result: result.data.result,
|
|
1476
|
+
duration_ms: result.data.duration_ms
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1187
1479
|
console.log("");
|
|
1188
1480
|
console.log(orange("Result:"));
|
|
1189
1481
|
console.log(JSON.stringify(result.data.result, null, 2));
|
|
@@ -1195,6 +1487,21 @@ async function functionsLogs(slug, options) {
|
|
|
1195
1487
|
const profileName = resolveProfileName3(options.profile);
|
|
1196
1488
|
const client = getClient(profileName);
|
|
1197
1489
|
const result = await client.code.getInvocations(slug, limit);
|
|
1490
|
+
if (options.json) {
|
|
1491
|
+
jsonOutput(
|
|
1492
|
+
result.success ? result.data.map((invocation) => ({
|
|
1493
|
+
id: invocation.id,
|
|
1494
|
+
trigger_type: invocation.trigger_type,
|
|
1495
|
+
duration_ms: invocation.duration_ms,
|
|
1496
|
+
success: invocation.success,
|
|
1497
|
+
invoked_at: invocation.invoked_at,
|
|
1498
|
+
logs: parseJsonField(invocation.logs) ?? [],
|
|
1499
|
+
error_message: invocation.error_message ?? null,
|
|
1500
|
+
input: parseJsonField(invocation.input),
|
|
1501
|
+
result: parseJsonField(invocation.result)
|
|
1502
|
+
})) : []
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1198
1505
|
if (!result.success || !result.data.length) {
|
|
1199
1506
|
console.log(header(version8) + " " + muted("No invocations yet.") + "\n");
|
|
1200
1507
|
return;
|
|
@@ -1225,35 +1532,152 @@ async function functionsLogs(slug, options) {
|
|
|
1225
1532
|
async function functionsDelete(slug, options = {}) {
|
|
1226
1533
|
const profileName = resolveProfileName3(options.profile);
|
|
1227
1534
|
const client = getClient(profileName);
|
|
1228
|
-
const spinner2 = ora(`Deleting ${slug}...`).start();
|
|
1535
|
+
const spinner2 = options.json ? null : ora(`Deleting ${slug}...`).start();
|
|
1229
1536
|
const result = await client.code.deleteFunction(slug);
|
|
1230
1537
|
if (!result.success) {
|
|
1231
|
-
|
|
1538
|
+
if (options.json) {
|
|
1539
|
+
jsonOutput({ success: false, error: result.error.message });
|
|
1540
|
+
}
|
|
1541
|
+
spinner2?.fail(`Delete failed for ${slug}`);
|
|
1232
1542
|
console.error(result.error.message);
|
|
1233
1543
|
process.exit(1);
|
|
1234
1544
|
}
|
|
1235
|
-
|
|
1545
|
+
if (options.json) {
|
|
1546
|
+
jsonOutput({ success: true, slug });
|
|
1547
|
+
}
|
|
1548
|
+
spinner2?.succeed(`Deleted ${slug}`);
|
|
1236
1549
|
}
|
|
1237
1550
|
async function functionsToggle(slug, active, options = {}) {
|
|
1238
1551
|
const profileName = resolveProfileName3(options.profile);
|
|
1239
1552
|
const client = getClient(profileName);
|
|
1240
|
-
const spinner2 = ora(
|
|
1241
|
-
`${active ? "Enabling" : "Disabling"} ${slug}...`
|
|
1242
|
-
).start();
|
|
1553
|
+
const spinner2 = options.json ? null : ora(`${active ? "Enabling" : "Disabling"} ${slug}...`).start();
|
|
1243
1554
|
const result = await client.code.toggleFunction(slug, active);
|
|
1244
1555
|
if (!result.success) {
|
|
1245
|
-
|
|
1556
|
+
if (options.json) {
|
|
1557
|
+
jsonOutput({ success: false, error: result.error.message });
|
|
1558
|
+
}
|
|
1559
|
+
spinner2?.fail(`Toggle failed for ${slug}`);
|
|
1246
1560
|
console.error(result.error.message);
|
|
1247
1561
|
process.exit(1);
|
|
1248
1562
|
}
|
|
1249
|
-
|
|
1563
|
+
if (options.json) {
|
|
1564
|
+
jsonOutput({ success: true, slug, active });
|
|
1565
|
+
}
|
|
1566
|
+
spinner2?.succeed(`${slug} is now ${active ? "active" : "inactive"}`);
|
|
1250
1567
|
}
|
|
1251
1568
|
|
|
1252
|
-
// src/
|
|
1569
|
+
// src/commands/watch.ts
|
|
1570
|
+
var BASE_URL2 = "https://api.globio.stanlink.online";
|
|
1253
1571
|
var version9 = getCliVersion();
|
|
1572
|
+
async function functionsWatch(slug, options = {}) {
|
|
1573
|
+
const profileName = options.profile ?? config.getActiveProfile();
|
|
1574
|
+
const profile = config.getProfile(profileName ?? "default");
|
|
1575
|
+
if (!profile?.project_api_key) {
|
|
1576
|
+
console.log(
|
|
1577
|
+
failure("No active project.") + reset + " Run: globio projects use <id>"
|
|
1578
|
+
);
|
|
1579
|
+
process.exit(1);
|
|
1580
|
+
}
|
|
1581
|
+
console.log(header(version9));
|
|
1582
|
+
console.log(
|
|
1583
|
+
" " + orange("watching") + reset + " " + slug + dim(" \xB7 press Ctrl+C to stop") + "\n"
|
|
1584
|
+
);
|
|
1585
|
+
const res = await fetch(`${BASE_URL2}/code/functions/${slug}/watch`, {
|
|
1586
|
+
headers: {
|
|
1587
|
+
"X-Globio-Key": profile.project_api_key,
|
|
1588
|
+
Accept: "text/event-stream"
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
if (!res.ok || !res.body) {
|
|
1592
|
+
console.log(failure("Failed to connect to watch stream.") + reset);
|
|
1593
|
+
process.exit(1);
|
|
1594
|
+
}
|
|
1595
|
+
const reader = res.body.getReader();
|
|
1596
|
+
const decoder = new TextDecoder();
|
|
1597
|
+
let buffer = "";
|
|
1598
|
+
process.on("SIGINT", () => {
|
|
1599
|
+
console.log("\n" + dim(" Stream closed.") + "\n");
|
|
1600
|
+
void reader.cancel();
|
|
1601
|
+
process.exit(0);
|
|
1602
|
+
});
|
|
1603
|
+
while (true) {
|
|
1604
|
+
const { done, value } = await reader.read();
|
|
1605
|
+
if (done) break;
|
|
1606
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1607
|
+
const chunks = buffer.split("\n\n");
|
|
1608
|
+
buffer = chunks.pop() ?? "";
|
|
1609
|
+
for (const chunk of chunks) {
|
|
1610
|
+
const dataLine = chunk.split("\n").find((line) => line.startsWith("data: "));
|
|
1611
|
+
if (!dataLine) continue;
|
|
1612
|
+
try {
|
|
1613
|
+
renderEvent(JSON.parse(dataLine.slice(6)));
|
|
1614
|
+
} catch {
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function renderEvent(event) {
|
|
1620
|
+
if (event.type === "connected") {
|
|
1621
|
+
console.log(
|
|
1622
|
+
" " + green("\u25CF") + reset + dim(" connected \u2014 waiting for invocations...\n")
|
|
1623
|
+
);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (event.type === "heartbeat") {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if (event.type === "timeout") {
|
|
1630
|
+
console.log(
|
|
1631
|
+
"\n" + dim(" Session timed out after 5 minutes.") + " Run again to resume.\n"
|
|
1632
|
+
);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
if (event.type !== "invocation" || !event.invoked_at) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
const time = new Date(event.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
1639
|
+
const status = event.success ? green("\u2713") : failure("\u2717");
|
|
1640
|
+
const trigger = dim(`[${event.trigger_type ?? "http"}]`);
|
|
1641
|
+
const duration = dim(`${event.duration_ms ?? 0}ms`);
|
|
1642
|
+
console.log(
|
|
1643
|
+
" " + status + reset + " " + dim(time) + " " + trigger + " " + duration
|
|
1644
|
+
);
|
|
1645
|
+
if (event.input && event.input !== "{}") {
|
|
1646
|
+
try {
|
|
1647
|
+
console.log(
|
|
1648
|
+
" " + dim(" input ") + muted(JSON.stringify(JSON.parse(event.input)))
|
|
1649
|
+
);
|
|
1650
|
+
} catch {
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (event.logs) {
|
|
1654
|
+
try {
|
|
1655
|
+
const logs = JSON.parse(event.logs);
|
|
1656
|
+
for (const line of logs) {
|
|
1657
|
+
console.log(" " + dim(" log ") + reset + line);
|
|
1658
|
+
}
|
|
1659
|
+
} catch {
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (event.result && event.result !== "null") {
|
|
1663
|
+
try {
|
|
1664
|
+
console.log(
|
|
1665
|
+
" " + dim(" result ") + muted(JSON.stringify(JSON.parse(event.result)))
|
|
1666
|
+
);
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
if (event.error_message) {
|
|
1671
|
+
console.log(" " + dim(" error ") + failure(event.error_message) + reset);
|
|
1672
|
+
}
|
|
1673
|
+
console.log("");
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// src/index.ts
|
|
1677
|
+
var version10 = getCliVersion();
|
|
1254
1678
|
var program = new Command();
|
|
1255
|
-
program.name("globio").description("The official Globio CLI").version(
|
|
1256
|
-
printBanner(
|
|
1679
|
+
program.name("globio").description("The official Globio CLI").version(version10).addHelpText("beforeAll", () => {
|
|
1680
|
+
printBanner(version10);
|
|
1257
1681
|
return "";
|
|
1258
1682
|
}).addHelpText(
|
|
1259
1683
|
"after",
|
|
@@ -1270,30 +1694,35 @@ Examples:
|
|
|
1270
1694
|
Credentials are stored in ~/.globio/profiles/
|
|
1271
1695
|
`
|
|
1272
1696
|
);
|
|
1273
|
-
program.command("login").description("Log in to your Globio account").option("-p, --profile <name>", "Profile name", "default").option("--token", "
|
|
1697
|
+
program.command("login").description("Log in to your Globio account").option("-p, --profile <name>", "Profile name", "default").option("--token <pat>", "Personal access token (non-interactive)").option("--json", "Output as JSON").action(login);
|
|
1274
1698
|
program.command("logout").description("Log out").option("--profile <name>", "Use a specific profile").action(logout);
|
|
1275
|
-
program.command("whoami").description("Show current account and project").option("--profile <name>", "Use a specific profile").action(whoami);
|
|
1699
|
+
program.command("whoami").description("Show current account and project").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(whoami);
|
|
1276
1700
|
program.command("use <profile>").description("Switch active profile").action(useProfile);
|
|
1277
|
-
program.command("init").description("Initialize a Globio project").option("--profile <name>", "
|
|
1278
|
-
var profiles = program.command("profiles").description("Manage login profiles").action(
|
|
1279
|
-
|
|
1701
|
+
program.command("init").description("Initialize a Globio project").option("-p, --profile <name>", "Profile name").option("--name <name>", "Project name").option("--slug <slug>", "Project slug").option("--org <orgId>", "Organization ID").option("--env <environment>", "Environment (development|staging|production)", "development").option("--no-migrate", "Skip Firebase migration prompt").option("--from <path>", "Firebase service account path (triggers migration)").option("--json", "Output as JSON").action(init);
|
|
1702
|
+
var profiles = program.command("profiles").description("Manage login profiles").option("--json", "Output as JSON").action(function() {
|
|
1703
|
+
return profilesList(this.opts());
|
|
1704
|
+
});
|
|
1705
|
+
profiles.command("list").description("List all profiles").option("--json", "Output as JSON").action(function() {
|
|
1706
|
+
return profilesList(this.opts());
|
|
1707
|
+
});
|
|
1280
1708
|
var projects = program.command("projects").description("Manage projects");
|
|
1281
|
-
projects.command("list").description("List projects").option("--profile <name>", "Use a specific profile").action(projectsList);
|
|
1282
|
-
projects.command("create").description("Create a project").option("--
|
|
1283
|
-
projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").action(projectsUse);
|
|
1284
|
-
program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").action(servicesList);
|
|
1709
|
+
projects.command("list").description("List projects").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(projectsList);
|
|
1710
|
+
projects.command("create").description("Create a project").option("--name <name>", "Project name").option("--org <orgId>", "Organization ID").option("--env <environment>", "Environment", "development").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action(projectsCreate);
|
|
1711
|
+
projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(projectsUse);
|
|
1712
|
+
program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(servicesList);
|
|
1285
1713
|
var functions = program.command("functions").alias("fn").description("Manage GlobalCode edge functions");
|
|
1286
|
-
functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").action(functionsList);
|
|
1287
|
-
functions.command("create <slug>").description("Scaffold a new function file locally").option("--profile <name>", "Use a specific profile").action(functionsCreate);
|
|
1288
|
-
functions.command("deploy <slug>").description("Deploy a function to GlobalCode").option("-f, --file <path>", "Path to function file").option("-n, --name <name>", "Display name").option("--profile <name>", "Use a specific profile").action(functionsDeploy);
|
|
1289
|
-
functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").option("--profile <name>", "Use a specific profile").action(functionsInvoke);
|
|
1290
|
-
functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").option("--profile <name>", "Use a specific profile").action(functionsLogs);
|
|
1291
|
-
functions.command("
|
|
1292
|
-
functions.command("
|
|
1293
|
-
functions.command("
|
|
1714
|
+
functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsList);
|
|
1715
|
+
functions.command("create <slug>").description("Scaffold a new function file locally").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsCreate);
|
|
1716
|
+
functions.command("deploy <slug>").description("Deploy a function to GlobalCode").option("-f, --file <path>", "Path to function file").option("-n, --name <name>", "Display name").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsDeploy);
|
|
1717
|
+
functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsInvoke);
|
|
1718
|
+
functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsLogs);
|
|
1719
|
+
functions.command("watch <slug>").description("Stream live function execution logs").option("--profile <name>", "Use a specific profile").action(functionsWatch);
|
|
1720
|
+
functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsDelete);
|
|
1721
|
+
functions.command("enable <slug>").description("Enable a function").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action((slug, options) => functionsToggle(slug, true, options));
|
|
1722
|
+
functions.command("disable <slug>").description("Disable a function").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action((slug, options) => functionsToggle(slug, false, options));
|
|
1294
1723
|
var migrate = program.command("migrate").description("Migrate from Firebase to Globio");
|
|
1295
|
-
migrate.command("firestore").description("Migrate Firestore collections to GlobalDoc").requiredOption("--from <path>", "Path to Firebase service account JSON").option("--collection <name>", "Migrate a specific collection").option("--all", "Migrate all collections").option("--profile <name>", "Use a specific profile").action(migrateFirestore);
|
|
1296
|
-
migrate.command("firebase-storage").description("Migrate Firebase Storage to GlobalVault").requiredOption("--from <path>", "Path to Firebase service account JSON").requiredOption("--bucket <name>", "Firebase Storage bucket").option("--folder <path>", "Migrate a specific folder").option("--all", "Migrate all files").option("--profile <name>", "Use a specific profile").action(migrateFirebaseStorage);
|
|
1724
|
+
migrate.command("firestore").description("Migrate Firestore collections to GlobalDoc").requiredOption("--from <path>", "Path to Firebase service account JSON").option("--collection <name>", "Migrate a specific collection").option("--all", "Migrate all collections").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(migrateFirestore);
|
|
1725
|
+
migrate.command("firebase-storage").description("Migrate Firebase Storage to GlobalVault").requiredOption("--from <path>", "Path to Firebase service account JSON").requiredOption("--bucket <name>", "Firebase Storage bucket").option("--folder <path>", "Migrate a specific folder").option("--all", "Migrate all files").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(migrateFirebaseStorage);
|
|
1297
1726
|
async function main() {
|
|
1298
1727
|
if (process.argv.length <= 2) {
|
|
1299
1728
|
program.help();
|