@globio/cli 0.1.9 → 0.2.0

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 CHANGED
@@ -206,10 +206,10 @@ function renderTable(options) {
206
206
  );
207
207
  return lines.join("\n");
208
208
  }
209
- function header(version10, subtitle) {
209
+ function header(version11, subtitle) {
210
210
  const lines = [
211
211
  "",
212
- orange(" \u21D2\u21D2") + reset + " globio " + dim(version10),
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) {
@@ -230,14 +230,14 @@ var globioGradient = gradientString(
230
230
  "#ffba08",
231
231
  "#ffd000"
232
232
  );
233
- function printBanner(version10) {
233
+ function printBanner(version11) {
234
234
  const art = figlet.textSync("Globio", {
235
235
  font: "ANSI Shadow",
236
236
  horizontalLayout: "default"
237
237
  });
238
238
  console.log(globioGradient.multiline(art));
239
239
  console.log(
240
- globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" + version10 + "\x1B[0m"
240
+ globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" + version11 + "\x1B[0m"
241
241
  );
242
242
  console.log("");
243
243
  }
@@ -1249,11 +1249,118 @@ async function functionsToggle(slug, active, options = {}) {
1249
1249
  spinner2.succeed(`${slug} is now ${active ? "active" : "inactive"}`);
1250
1250
  }
1251
1251
 
1252
- // src/index.ts
1252
+ // src/commands/watch.ts
1253
+ var BASE_URL2 = "https://api.globio.stanlink.online";
1253
1254
  var version9 = getCliVersion();
1255
+ async function functionsWatch(slug, options = {}) {
1256
+ const profileName = options.profile ?? config.getActiveProfile();
1257
+ const profile = config.getProfile(profileName ?? "default");
1258
+ if (!profile?.project_api_key) {
1259
+ console.log(
1260
+ failure("No active project.") + reset + " Run: globio projects use <id>"
1261
+ );
1262
+ process.exit(1);
1263
+ }
1264
+ console.log(header(version9));
1265
+ console.log(
1266
+ " " + orange("watching") + reset + " " + slug + dim(" \xB7 press Ctrl+C to stop") + "\n"
1267
+ );
1268
+ const res = await fetch(`${BASE_URL2}/code/functions/${slug}/watch`, {
1269
+ headers: {
1270
+ "X-Globio-Key": profile.project_api_key,
1271
+ Accept: "text/event-stream"
1272
+ }
1273
+ });
1274
+ if (!res.ok || !res.body) {
1275
+ console.log(failure("Failed to connect to watch stream.") + reset);
1276
+ process.exit(1);
1277
+ }
1278
+ const reader = res.body.getReader();
1279
+ const decoder = new TextDecoder();
1280
+ let buffer = "";
1281
+ process.on("SIGINT", () => {
1282
+ console.log("\n" + dim(" Stream closed.") + "\n");
1283
+ void reader.cancel();
1284
+ process.exit(0);
1285
+ });
1286
+ while (true) {
1287
+ const { done, value } = await reader.read();
1288
+ if (done) break;
1289
+ buffer += decoder.decode(value, { stream: true });
1290
+ const chunks = buffer.split("\n\n");
1291
+ buffer = chunks.pop() ?? "";
1292
+ for (const chunk of chunks) {
1293
+ const dataLine = chunk.split("\n").find((line) => line.startsWith("data: "));
1294
+ if (!dataLine) continue;
1295
+ try {
1296
+ renderEvent(JSON.parse(dataLine.slice(6)));
1297
+ } catch {
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ function renderEvent(event) {
1303
+ if (event.type === "connected") {
1304
+ console.log(
1305
+ " " + green("\u25CF") + reset + dim(" connected \u2014 waiting for invocations...\n")
1306
+ );
1307
+ return;
1308
+ }
1309
+ if (event.type === "heartbeat") {
1310
+ return;
1311
+ }
1312
+ if (event.type === "timeout") {
1313
+ console.log(
1314
+ "\n" + dim(" Session timed out after 5 minutes.") + " Run again to resume.\n"
1315
+ );
1316
+ return;
1317
+ }
1318
+ if (event.type !== "invocation" || !event.invoked_at) {
1319
+ return;
1320
+ }
1321
+ const time = new Date(event.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
1322
+ const status = event.success ? green("\u2713") : failure("\u2717");
1323
+ const trigger = dim(`[${event.trigger_type ?? "http"}]`);
1324
+ const duration = dim(`${event.duration_ms ?? 0}ms`);
1325
+ console.log(
1326
+ " " + status + reset + " " + dim(time) + " " + trigger + " " + duration
1327
+ );
1328
+ if (event.input && event.input !== "{}") {
1329
+ try {
1330
+ console.log(
1331
+ " " + dim(" input ") + muted(JSON.stringify(JSON.parse(event.input)))
1332
+ );
1333
+ } catch {
1334
+ }
1335
+ }
1336
+ if (event.logs) {
1337
+ try {
1338
+ const logs = JSON.parse(event.logs);
1339
+ for (const line of logs) {
1340
+ console.log(" " + dim(" log ") + reset + line);
1341
+ }
1342
+ } catch {
1343
+ }
1344
+ }
1345
+ if (event.result && event.result !== "null") {
1346
+ try {
1347
+ console.log(
1348
+ " " + dim(" result ") + muted(JSON.stringify(JSON.parse(event.result)))
1349
+ );
1350
+ } catch {
1351
+ }
1352
+ }
1353
+ if (event.error_message) {
1354
+ console.log(" " + dim(" error ") + failure(event.error_message) + reset);
1355
+ }
1356
+ console.log("");
1357
+ }
1358
+
1359
+ // src/index.ts
1360
+ var version10 = getCliVersion();
1254
1361
  var program = new Command();
1255
- program.name("globio").description("The official Globio CLI").version(version9).addHelpText("beforeAll", () => {
1256
- printBanner(version9);
1362
+ program.name("globio").description("The official Globio CLI").version(version10).addHelpText("beforeAll", () => {
1363
+ printBanner(version10);
1257
1364
  return "";
1258
1365
  }).addHelpText(
1259
1366
  "after",
@@ -1288,6 +1395,7 @@ functions.command("create <slug>").description("Scaffold a new function file loc
1288
1395
  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
1396
  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
1397
  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);
1398
+ functions.command("watch <slug>").description("Stream live function execution logs").option("--profile <name>", "Use a specific profile").action(functionsWatch);
1291
1399
  functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").action(functionsDelete);
1292
1400
  functions.command("enable <slug>").description("Enable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, true, options));
1293
1401
  functions.command("disable <slug>").description("Disable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, false, options));
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globio/cli",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "exports": "./src/index.ts"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globio/cli",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "The official CLI for Globio — game backend as a service",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,173 @@
1
+ import { config } from '../lib/config.js';
2
+ import {
3
+ dim,
4
+ failure,
5
+ getCliVersion,
6
+ green,
7
+ header,
8
+ muted,
9
+ orange,
10
+ reset,
11
+ } from '../lib/banner.js';
12
+
13
+ const BASE_URL = 'https://api.globio.stanlink.online';
14
+ const version = getCliVersion();
15
+
16
+ export async function functionsWatch(
17
+ slug: string,
18
+ options: { profile?: string } = {}
19
+ ) {
20
+ const profileName = options.profile ?? config.getActiveProfile();
21
+ const profile = config.getProfile(profileName ?? 'default');
22
+
23
+ if (!profile?.project_api_key) {
24
+ console.log(
25
+ failure('No active project.') +
26
+ reset +
27
+ ' Run: globio projects use <id>'
28
+ );
29
+ process.exit(1);
30
+ }
31
+
32
+ console.log(header(version));
33
+ console.log(
34
+ ' ' +
35
+ orange('watching') +
36
+ reset +
37
+ ' ' +
38
+ slug +
39
+ dim(' · press Ctrl+C to stop') +
40
+ '\n'
41
+ );
42
+
43
+ const res = await fetch(`${BASE_URL}/code/functions/${slug}/watch`, {
44
+ headers: {
45
+ 'X-Globio-Key': profile.project_api_key,
46
+ Accept: 'text/event-stream',
47
+ },
48
+ });
49
+
50
+ if (!res.ok || !res.body) {
51
+ console.log(failure('Failed to connect to watch stream.') + reset);
52
+ process.exit(1);
53
+ }
54
+
55
+ const reader = res.body.getReader();
56
+ const decoder = new TextDecoder();
57
+ let buffer = '';
58
+
59
+ process.on('SIGINT', () => {
60
+ console.log('\n' + dim(' Stream closed.') + '\n');
61
+ void reader.cancel();
62
+ process.exit(0);
63
+ });
64
+
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+ if (done) break;
68
+
69
+ buffer += decoder.decode(value, { stream: true });
70
+ const chunks = buffer.split('\n\n');
71
+ buffer = chunks.pop() ?? '';
72
+
73
+ for (const chunk of chunks) {
74
+ const dataLine = chunk
75
+ .split('\n')
76
+ .find((line) => line.startsWith('data: '));
77
+
78
+ if (!dataLine) continue;
79
+
80
+ try {
81
+ renderEvent(JSON.parse(dataLine.slice(6)) as WatchEvent);
82
+ } catch {
83
+ // Ignore malformed events.
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ interface WatchEvent {
90
+ type: 'connected' | 'heartbeat' | 'timeout' | 'invocation';
91
+ invoked_at?: number;
92
+ trigger_type?: string;
93
+ duration_ms?: number;
94
+ success?: boolean;
95
+ input?: string | null;
96
+ result?: string | null;
97
+ error_message?: string | null;
98
+ logs?: string | null;
99
+ }
100
+
101
+ function renderEvent(event: WatchEvent) {
102
+ if (event.type === 'connected') {
103
+ console.log(
104
+ ' ' + green('●') + reset + dim(' connected — waiting for invocations...\n')
105
+ );
106
+ return;
107
+ }
108
+
109
+ if (event.type === 'heartbeat') {
110
+ return;
111
+ }
112
+
113
+ if (event.type === 'timeout') {
114
+ console.log(
115
+ '\n' + dim(' Session timed out after 5 minutes.') + ' Run again to resume.\n'
116
+ );
117
+ return;
118
+ }
119
+
120
+ if (event.type !== 'invocation' || !event.invoked_at) {
121
+ return;
122
+ }
123
+
124
+ const time = new Date(event.invoked_at * 1000)
125
+ .toISOString()
126
+ .replace('T', ' ')
127
+ .slice(0, 19);
128
+
129
+ const status = event.success ? green('✓') : failure('✗');
130
+ const trigger = dim(`[${event.trigger_type ?? 'http'}]`);
131
+ const duration = dim(`${event.duration_ms ?? 0}ms`);
132
+
133
+ console.log(
134
+ ' ' + status + reset + ' ' + dim(time) + ' ' + trigger + ' ' + duration
135
+ );
136
+
137
+ if (event.input && event.input !== '{}') {
138
+ try {
139
+ console.log(
140
+ ' ' + dim(' input ') + muted(JSON.stringify(JSON.parse(event.input)))
141
+ );
142
+ } catch {
143
+ // Ignore invalid JSON payloads.
144
+ }
145
+ }
146
+
147
+ if (event.logs) {
148
+ try {
149
+ const logs = JSON.parse(event.logs) as string[];
150
+ for (const line of logs) {
151
+ console.log(' ' + dim(' log ') + reset + line);
152
+ }
153
+ } catch {
154
+ // Ignore invalid log payloads.
155
+ }
156
+ }
157
+
158
+ if (event.result && event.result !== 'null') {
159
+ try {
160
+ console.log(
161
+ ' ' + dim(' result ') + muted(JSON.stringify(JSON.parse(event.result)))
162
+ );
163
+ } catch {
164
+ // Ignore invalid result payloads.
165
+ }
166
+ }
167
+
168
+ if (event.error_message) {
169
+ console.log(' ' + dim(' error ') + failure(event.error_message) + reset);
170
+ }
171
+
172
+ console.log('');
173
+ }
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  functionsDelete,
18
18
  functionsToggle,
19
19
  } from './commands/functions.js';
20
+ import { functionsWatch } from './commands/watch.js';
20
21
  import {
21
22
  migrateFirestore,
22
23
  migrateFirebaseStorage,
@@ -106,6 +107,11 @@ functions
106
107
  .option('-l, --limit <n>', 'Number of entries', '20')
107
108
  .option('--profile <name>', 'Use a specific profile')
108
109
  .action(functionsLogs);
110
+ functions
111
+ .command('watch <slug>')
112
+ .description('Stream live function execution logs')
113
+ .option('--profile <name>', 'Use a specific profile')
114
+ .action(functionsWatch);
109
115
  functions.command('delete <slug>').description('Delete a function').option('--profile <name>', 'Use a specific profile').action(functionsDelete);
110
116
  functions
111
117
  .command('enable <slug>')