@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 +115 -7
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/commands/watch.ts +173 -0
- package/src/index.ts +6 -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) {
|
|
@@ -230,14 +230,14 @@ var globioGradient = gradientString(
|
|
|
230
230
|
"#ffba08",
|
|
231
231
|
"#ffd000"
|
|
232
232
|
);
|
|
233
|
-
function printBanner(
|
|
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" +
|
|
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/
|
|
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(
|
|
1256
|
-
printBanner(
|
|
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
package/package.json
CHANGED
|
@@ -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>')
|