@globio/cli 0.0.1 → 0.1.1
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/.github/workflows/publish.yml +59 -0
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/index.js +643 -1
- package/jsr.json +6 -0
- package/package.json +30 -2
- package/src/auth/login.ts +72 -0
- package/src/auth/logout.ts +8 -0
- package/src/auth/whoami.ts +15 -0
- package/src/commands/functions.ts +190 -0
- package/src/commands/init.ts +75 -0
- package/src/commands/migrate.ts +199 -0
- package/src/commands/projects.ts +16 -0
- package/src/commands/services.ts +27 -0
- package/src/index.ts +102 -0
- package/src/lib/banner.ts +51 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/firebase.ts +19 -0
- package/src/lib/progress.ts +17 -0
- package/src/lib/sdk.ts +13 -0
- package/src/prompts/init.ts +38 -0
- package/src/prompts/migrate.ts +8 -0
- package/tsconfig.json +13 -0
package/dist/index.js
CHANGED
|
@@ -1,2 +1,644 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/auth/login.ts
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import chalk2 from "chalk";
|
|
9
|
+
|
|
10
|
+
// src/lib/config.ts
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import Conf from "conf";
|
|
13
|
+
var store = new Conf({
|
|
14
|
+
projectName: "globio",
|
|
15
|
+
defaults: {}
|
|
16
|
+
});
|
|
17
|
+
var config = {
|
|
18
|
+
get: () => store.store,
|
|
19
|
+
set: (values) => {
|
|
20
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
21
|
+
if (value !== void 0) {
|
|
22
|
+
store.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
clear: () => store.clear(),
|
|
27
|
+
getApiKey: () => store.get("apiKey"),
|
|
28
|
+
requireAuth: () => {
|
|
29
|
+
const key = store.get("apiKey");
|
|
30
|
+
if (!key) {
|
|
31
|
+
console.error(chalk.red("Not logged in. Run: npx @globio/cli login"));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return key;
|
|
35
|
+
},
|
|
36
|
+
requireProject: () => {
|
|
37
|
+
const projectId = store.get("projectId");
|
|
38
|
+
if (!projectId) {
|
|
39
|
+
console.error(
|
|
40
|
+
chalk.red("No active project. Run: npx @globio/cli projects use <projectId>")
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return projectId;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/lib/banner.ts
|
|
49
|
+
import { readFileSync } from "fs";
|
|
50
|
+
import figlet from "figlet";
|
|
51
|
+
import gradientString from "gradient-string";
|
|
52
|
+
var globioGradient = gradientString(
|
|
53
|
+
"#e85d04",
|
|
54
|
+
"#f48c06",
|
|
55
|
+
"#faa307",
|
|
56
|
+
"#ffba08",
|
|
57
|
+
"#ffd000"
|
|
58
|
+
);
|
|
59
|
+
function printBanner(version5) {
|
|
60
|
+
const art = figlet.textSync("Globio", {
|
|
61
|
+
font: "ANSI Shadow",
|
|
62
|
+
horizontalLayout: "default"
|
|
63
|
+
});
|
|
64
|
+
console.log(globioGradient.multiline(art));
|
|
65
|
+
console.log(
|
|
66
|
+
globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" + version5 + "\x1B[0m"
|
|
67
|
+
);
|
|
68
|
+
console.log("");
|
|
69
|
+
}
|
|
70
|
+
function printSuccess(message) {
|
|
71
|
+
console.log("\x1B[38;2;250;163;7m\u2713\x1B[0m " + message);
|
|
72
|
+
}
|
|
73
|
+
var orange = (s) => "\x1B[38;2;244;140;6m" + s + "\x1B[0m";
|
|
74
|
+
var gold = (s) => "\x1B[38;2;255;208;0m" + s + "\x1B[0m";
|
|
75
|
+
var muted = (s) => "\x1B[2m" + s + "\x1B[0m";
|
|
76
|
+
function getCliVersion() {
|
|
77
|
+
const file = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
78
|
+
return JSON.parse(file).version;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/auth/login.ts
|
|
82
|
+
var DEFAULT_BASE_URL = "https://api.globio.stanlink.online";
|
|
83
|
+
var version = getCliVersion();
|
|
84
|
+
async function login() {
|
|
85
|
+
printBanner(version);
|
|
86
|
+
const values = await p.group(
|
|
87
|
+
{
|
|
88
|
+
apiKey: () => p.text({
|
|
89
|
+
message: "Paste your Globio API key",
|
|
90
|
+
placeholder: "gk_live_...",
|
|
91
|
+
validate: (value) => !value ? "API key is required" : void 0
|
|
92
|
+
}),
|
|
93
|
+
projectId: () => p.text({
|
|
94
|
+
message: "Paste your Project ID",
|
|
95
|
+
placeholder: "proj_...",
|
|
96
|
+
validate: (value) => !value ? "Project ID is required" : void 0
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
onCancel: () => {
|
|
101
|
+
p.cancel("Login cancelled.");
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
const spinner2 = p.spinner();
|
|
107
|
+
spinner2.start("Validating credentials...");
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch(`${DEFAULT_BASE_URL}/id/health`, {
|
|
110
|
+
headers: {
|
|
111
|
+
"X-Globio-Key": values.apiKey
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
spinner2.stop("Validation failed.");
|
|
116
|
+
p.outro(chalk2.red("Invalid API key or project ID."));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
config.set({
|
|
120
|
+
apiKey: values.apiKey,
|
|
121
|
+
projectId: values.projectId
|
|
122
|
+
});
|
|
123
|
+
spinner2.stop("Credentials validated.");
|
|
124
|
+
p.outro(
|
|
125
|
+
" Logged in.\n\n " + muted("API Key: ") + orange(values.apiKey) + "\n " + muted("Project: ") + orange(values.projectId)
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
spinner2.stop("");
|
|
129
|
+
p.outro(chalk2.red("Could not connect to Globio. Check your credentials."));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/auth/logout.ts
|
|
135
|
+
import * as p2 from "@clack/prompts";
|
|
136
|
+
import chalk3 from "chalk";
|
|
137
|
+
async function logout() {
|
|
138
|
+
config.clear();
|
|
139
|
+
p2.outro(chalk3.green("Logged out."));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/auth/whoami.ts
|
|
143
|
+
import chalk4 from "chalk";
|
|
144
|
+
async function whoami() {
|
|
145
|
+
const cfg = config.get();
|
|
146
|
+
if (!cfg.apiKey) {
|
|
147
|
+
console.log(chalk4.red("Not logged in."));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log("");
|
|
151
|
+
console.log(chalk4.cyan("API Key: ") + cfg.apiKey);
|
|
152
|
+
console.log(chalk4.cyan("Project: ") + (cfg.projectId ?? "none"));
|
|
153
|
+
console.log("");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/commands/init.ts
|
|
157
|
+
import * as p5 from "@clack/prompts";
|
|
158
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
159
|
+
|
|
160
|
+
// src/prompts/init.ts
|
|
161
|
+
import * as p3 from "@clack/prompts";
|
|
162
|
+
async function promptInit() {
|
|
163
|
+
return p3.group(
|
|
164
|
+
{
|
|
165
|
+
apiKey: () => p3.text({
|
|
166
|
+
message: "Globio API key",
|
|
167
|
+
placeholder: "gk_live_...",
|
|
168
|
+
validate: (value) => !value ? "Required" : void 0
|
|
169
|
+
}),
|
|
170
|
+
projectId: () => p3.text({
|
|
171
|
+
message: "Project ID",
|
|
172
|
+
placeholder: "proj_...",
|
|
173
|
+
validate: (value) => !value ? "Required" : void 0
|
|
174
|
+
}),
|
|
175
|
+
migrateFromFirebase: () => p3.confirm({
|
|
176
|
+
message: "Migrating from Firebase?",
|
|
177
|
+
initialValue: false
|
|
178
|
+
}),
|
|
179
|
+
serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p3.text({
|
|
180
|
+
message: "Path to Firebase service account JSON",
|
|
181
|
+
placeholder: "./serviceAccountKey.json"
|
|
182
|
+
}) : Promise.resolve(void 0)
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
onCancel: () => {
|
|
186
|
+
p3.cancel("Cancelled.");
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/commands/migrate.ts
|
|
194
|
+
import * as p4 from "@clack/prompts";
|
|
195
|
+
import chalk6 from "chalk";
|
|
196
|
+
import { basename } from "path";
|
|
197
|
+
|
|
198
|
+
// src/lib/firebase.ts
|
|
199
|
+
async function initFirebase(serviceAccountPath) {
|
|
200
|
+
const admin = await import("firebase-admin");
|
|
201
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
202
|
+
const serviceAccount = JSON.parse(readFileSync4(serviceAccountPath, "utf-8"));
|
|
203
|
+
if (!admin.default.apps.length) {
|
|
204
|
+
admin.default.initializeApp({
|
|
205
|
+
credential: admin.default.credential.cert(serviceAccount),
|
|
206
|
+
storageBucket: serviceAccount.project_id + ".appspot.com"
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
firestore: admin.default.firestore(),
|
|
211
|
+
storage: admin.default.storage(),
|
|
212
|
+
app: admin.default.app()
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/lib/progress.ts
|
|
217
|
+
import chalk5 from "chalk";
|
|
218
|
+
import cliProgress from "cli-progress";
|
|
219
|
+
function createProgressBar(label) {
|
|
220
|
+
const bar = new cliProgress.SingleBar(
|
|
221
|
+
{
|
|
222
|
+
format: chalk5.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
|
|
223
|
+
barCompleteChar: "\u2588",
|
|
224
|
+
barIncompleteChar: "\u2591",
|
|
225
|
+
hideCursor: true
|
|
226
|
+
},
|
|
227
|
+
cliProgress.Presets.shades_classic
|
|
228
|
+
);
|
|
229
|
+
return bar;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/lib/sdk.ts
|
|
233
|
+
import { Globio } from "@globio/sdk";
|
|
234
|
+
function getClient() {
|
|
235
|
+
const apiKey = config.requireAuth();
|
|
236
|
+
config.requireProject();
|
|
237
|
+
return new Globio({ apiKey });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/commands/migrate.ts
|
|
241
|
+
var version2 = getCliVersion();
|
|
242
|
+
async function migrateFirestore(options) {
|
|
243
|
+
printBanner(version2);
|
|
244
|
+
p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
245
|
+
const { firestore } = await initFirebase(options.from);
|
|
246
|
+
const client = getClient();
|
|
247
|
+
let collections = [];
|
|
248
|
+
if (options.all) {
|
|
249
|
+
const snapshot = await firestore.listCollections();
|
|
250
|
+
collections = snapshot.map((collection) => collection.id);
|
|
251
|
+
console.log(
|
|
252
|
+
chalk6.cyan(
|
|
253
|
+
`Found ${collections.length} collections: ${collections.join(", ")}`
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
} else if (options.collection) {
|
|
257
|
+
collections = [options.collection];
|
|
258
|
+
} else {
|
|
259
|
+
console.log(chalk6.red("Specify --collection <name> or --all"));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
const results = {};
|
|
263
|
+
for (const collectionId of collections) {
|
|
264
|
+
console.log("");
|
|
265
|
+
console.log(" " + orange(collectionId));
|
|
266
|
+
const countSnap = await firestore.collection(collectionId).count().get();
|
|
267
|
+
const total = countSnap.data().count;
|
|
268
|
+
const bar = createProgressBar(collectionId);
|
|
269
|
+
bar.start(total, 0);
|
|
270
|
+
results[collectionId] = {
|
|
271
|
+
success: 0,
|
|
272
|
+
failed: 0,
|
|
273
|
+
failedIds: []
|
|
274
|
+
};
|
|
275
|
+
let lastDoc = null;
|
|
276
|
+
let processed = 0;
|
|
277
|
+
while (processed < total) {
|
|
278
|
+
let query = firestore.collection(collectionId).limit(100);
|
|
279
|
+
if (lastDoc) {
|
|
280
|
+
query = query.startAfter(lastDoc);
|
|
281
|
+
}
|
|
282
|
+
const snapshot = await query.get();
|
|
283
|
+
if (snapshot.empty) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
for (const doc of snapshot.docs) {
|
|
287
|
+
try {
|
|
288
|
+
const result = await client.doc.set(collectionId, doc.id, doc.data());
|
|
289
|
+
if (!result.success) {
|
|
290
|
+
throw new Error(result.error.message);
|
|
291
|
+
}
|
|
292
|
+
results[collectionId].success++;
|
|
293
|
+
} catch {
|
|
294
|
+
results[collectionId].failed++;
|
|
295
|
+
results[collectionId].failedIds.push(doc.id);
|
|
296
|
+
}
|
|
297
|
+
processed++;
|
|
298
|
+
bar.update(processed);
|
|
299
|
+
}
|
|
300
|
+
lastDoc = snapshot.docs[snapshot.docs.length - 1] ?? null;
|
|
301
|
+
}
|
|
302
|
+
bar.stop();
|
|
303
|
+
console.log(
|
|
304
|
+
chalk6.green(` \u2713 ${results[collectionId].success} documents migrated`)
|
|
305
|
+
);
|
|
306
|
+
if (results[collectionId].failed > 0) {
|
|
307
|
+
console.log(chalk6.red(` \u2717 ${results[collectionId].failed} failed`));
|
|
308
|
+
console.log(
|
|
309
|
+
chalk6.gray(
|
|
310
|
+
" Failed IDs: " + results[collectionId].failedIds.slice(0, 10).join(", ") + (results[collectionId].failedIds.length > 10 ? "..." : "")
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.log("");
|
|
316
|
+
p4.outro(
|
|
317
|
+
orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
async function migrateFirebaseStorage(options) {
|
|
321
|
+
printBanner(version2);
|
|
322
|
+
p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
323
|
+
const { storage } = await initFirebase(options.from);
|
|
324
|
+
const client = getClient();
|
|
325
|
+
const bucketName = options.bucket.replace(/^gs:\/\//, "");
|
|
326
|
+
const bucket = storage.bucket(bucketName);
|
|
327
|
+
const prefix = options.folder ? options.folder.replace(/^\//, "") : "";
|
|
328
|
+
const [files] = await bucket.getFiles(prefix ? { prefix } : {});
|
|
329
|
+
console.log(chalk6.cyan(`Found ${files.length} files to migrate`));
|
|
330
|
+
const bar = createProgressBar("Storage");
|
|
331
|
+
bar.start(files.length, 0);
|
|
332
|
+
let success = 0;
|
|
333
|
+
let failed = 0;
|
|
334
|
+
for (const file of files) {
|
|
335
|
+
try {
|
|
336
|
+
const [buffer] = await file.download();
|
|
337
|
+
const uploadFile = new File(
|
|
338
|
+
[new Uint8Array(buffer)],
|
|
339
|
+
basename(file.name) || file.name
|
|
340
|
+
);
|
|
341
|
+
const result = await client.vault.uploadFile(uploadFile, {
|
|
342
|
+
metadata: {
|
|
343
|
+
original_path: file.name
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
if (!result.success) {
|
|
347
|
+
throw new Error(result.error.message);
|
|
348
|
+
}
|
|
349
|
+
success++;
|
|
350
|
+
} catch {
|
|
351
|
+
failed++;
|
|
352
|
+
}
|
|
353
|
+
bar.increment();
|
|
354
|
+
}
|
|
355
|
+
bar.stop();
|
|
356
|
+
console.log("");
|
|
357
|
+
console.log(chalk6.green(` \u2713 ${success} files migrated`));
|
|
358
|
+
if (failed > 0) {
|
|
359
|
+
console.log(chalk6.red(` \u2717 ${failed} failed`));
|
|
360
|
+
}
|
|
361
|
+
p4.outro(
|
|
362
|
+
orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/commands/init.ts
|
|
367
|
+
var version3 = getCliVersion();
|
|
368
|
+
async function init() {
|
|
369
|
+
printBanner(version3);
|
|
370
|
+
p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
|
|
371
|
+
const values = await promptInit();
|
|
372
|
+
config.set({
|
|
373
|
+
apiKey: values.apiKey,
|
|
374
|
+
projectId: values.projectId
|
|
375
|
+
});
|
|
376
|
+
if (!existsSync("globio.config.ts")) {
|
|
377
|
+
writeFileSync(
|
|
378
|
+
"globio.config.ts",
|
|
379
|
+
`import { GlobioClient } from '@globio/sdk';
|
|
380
|
+
|
|
381
|
+
export const globio = new GlobioClient({
|
|
382
|
+
apiKey: process.env.GLOBIO_API_KEY!,
|
|
383
|
+
});
|
|
384
|
+
`
|
|
385
|
+
);
|
|
386
|
+
printSuccess("Created globio.config.ts");
|
|
387
|
+
}
|
|
388
|
+
if (!existsSync(".env")) {
|
|
389
|
+
writeFileSync(".env", `GLOBIO_API_KEY=${values.apiKey}
|
|
390
|
+
`);
|
|
391
|
+
printSuccess("Created .env");
|
|
392
|
+
}
|
|
393
|
+
if (values.migrateFromFirebase && values.serviceAccountPath) {
|
|
394
|
+
console.log("");
|
|
395
|
+
printSuccess("Starting Firebase migration...");
|
|
396
|
+
await migrateFirestore({
|
|
397
|
+
from: values.serviceAccountPath,
|
|
398
|
+
all: true
|
|
399
|
+
});
|
|
400
|
+
const serviceAccount = JSON.parse(
|
|
401
|
+
readFileSync2(values.serviceAccountPath, "utf-8")
|
|
402
|
+
);
|
|
403
|
+
await migrateFirebaseStorage({
|
|
404
|
+
from: values.serviceAccountPath,
|
|
405
|
+
bucket: `${serviceAccount.project_id}.appspot.com`,
|
|
406
|
+
all: true
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
console.log("");
|
|
410
|
+
p5.outro(
|
|
411
|
+
orange("\u21D2\u21D2") + " Your project is ready.\n\n " + muted("Next steps:") + "\n\n npm install @globio/sdk\n npx @globio/cli functions create my-first-function"
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/commands/projects.ts
|
|
416
|
+
import chalk7 from "chalk";
|
|
417
|
+
async function projectsList() {
|
|
418
|
+
const cfg = config.get();
|
|
419
|
+
console.log("");
|
|
420
|
+
console.log(
|
|
421
|
+
chalk7.cyan("Active project: ") + (cfg.projectId ?? chalk7.gray("none"))
|
|
422
|
+
);
|
|
423
|
+
console.log("");
|
|
424
|
+
}
|
|
425
|
+
async function projectsUse(projectId) {
|
|
426
|
+
config.set({ projectId });
|
|
427
|
+
console.log(chalk7.green("Active project set to: ") + chalk7.cyan(projectId));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/commands/services.ts
|
|
431
|
+
import chalk8 from "chalk";
|
|
432
|
+
var ALL_SERVICES = [
|
|
433
|
+
"id",
|
|
434
|
+
"doc",
|
|
435
|
+
"vault",
|
|
436
|
+
"pulse",
|
|
437
|
+
"scope",
|
|
438
|
+
"sync",
|
|
439
|
+
"signal",
|
|
440
|
+
"mart",
|
|
441
|
+
"brain",
|
|
442
|
+
"code"
|
|
443
|
+
];
|
|
444
|
+
async function servicesList() {
|
|
445
|
+
console.log("");
|
|
446
|
+
console.log(chalk8.cyan("Available Globio services:"));
|
|
447
|
+
ALL_SERVICES.forEach((service) => {
|
|
448
|
+
console.log(" " + chalk8.white(service));
|
|
449
|
+
});
|
|
450
|
+
console.log("");
|
|
451
|
+
console.log(
|
|
452
|
+
chalk8.gray("Manage service access via console.globio.stanlink.online")
|
|
453
|
+
);
|
|
454
|
+
console.log("");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/commands/functions.ts
|
|
458
|
+
import chalk9 from "chalk";
|
|
459
|
+
import ora from "ora";
|
|
460
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
461
|
+
async function functionsList() {
|
|
462
|
+
const client = getClient();
|
|
463
|
+
const spinner2 = ora("Fetching functions...").start();
|
|
464
|
+
const result = await client.code.listFunctions();
|
|
465
|
+
spinner2.stop();
|
|
466
|
+
if (!result.success || !result.data.length) {
|
|
467
|
+
console.log(chalk9.gray("No functions found."));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
console.log("");
|
|
471
|
+
result.data.forEach((fn) => {
|
|
472
|
+
const status = fn.active ? "\x1B[32m\u25CF\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
|
|
473
|
+
const type = fn.type === "hook" ? gold("[hook]") : orange("[function]");
|
|
474
|
+
console.log(" " + status + " " + type + " " + fn.slug);
|
|
475
|
+
if (fn.type === "hook" && fn.trigger_event) {
|
|
476
|
+
console.log(muted(" trigger: " + fn.trigger_event));
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
console.log("");
|
|
480
|
+
}
|
|
481
|
+
async function functionsCreate(slug) {
|
|
482
|
+
const filename = `${slug}.js`;
|
|
483
|
+
if (existsSync2(filename)) {
|
|
484
|
+
console.log(chalk9.yellow(`${filename} already exists.`));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const template = `/**
|
|
488
|
+
* Globio Edge Function: ${slug}
|
|
489
|
+
* Invoke: npx @globio/cli functions invoke ${slug} --input '{"key":"value"}'
|
|
490
|
+
*/
|
|
491
|
+
async function handler(input, globio) {
|
|
492
|
+
// input: the payload from the caller
|
|
493
|
+
// globio: injected SDK \u2014 access all Globio services
|
|
494
|
+
// Example: const player = await globio.doc.get('players', input.userId);
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
ok: true,
|
|
498
|
+
received: input,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
writeFileSync2(filename, template);
|
|
503
|
+
console.log(chalk9.green(`Created ${filename}`));
|
|
504
|
+
console.log(
|
|
505
|
+
chalk9.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
async function functionsDeploy(slug, options) {
|
|
509
|
+
const filename = options.file ?? `${slug}.js`;
|
|
510
|
+
if (!existsSync2(filename)) {
|
|
511
|
+
console.log(
|
|
512
|
+
chalk9.red(
|
|
513
|
+
`File not found: ${filename}. Create it with: npx @globio/cli functions create ${slug}`
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
const code = readFileSync3(filename, "utf-8");
|
|
519
|
+
const client = getClient();
|
|
520
|
+
const spinner2 = ora(`Deploying ${slug}...`).start();
|
|
521
|
+
const existing = await client.code.getFunction(slug);
|
|
522
|
+
let result;
|
|
523
|
+
if (existing.success) {
|
|
524
|
+
result = await client.code.updateFunction(slug, {
|
|
525
|
+
code,
|
|
526
|
+
name: options.name ?? slug
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
result = await client.code.createFunction({
|
|
530
|
+
name: options.name ?? slug,
|
|
531
|
+
slug,
|
|
532
|
+
type: "function",
|
|
533
|
+
code
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (!result.success) {
|
|
537
|
+
spinner2.fail("Deploy failed");
|
|
538
|
+
console.error(result.error.message);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
spinner2.succeed(existing.success ? `Updated ${slug}` : `Deployed ${slug}`);
|
|
542
|
+
}
|
|
543
|
+
async function functionsInvoke(slug, options) {
|
|
544
|
+
let input = {};
|
|
545
|
+
if (options.input) {
|
|
546
|
+
try {
|
|
547
|
+
input = JSON.parse(options.input);
|
|
548
|
+
} catch {
|
|
549
|
+
console.error(chalk9.red("--input must be valid JSON"));
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const client = getClient();
|
|
554
|
+
const spinner2 = ora(`Invoking ${slug}...`).start();
|
|
555
|
+
const result = await client.code.invoke(slug, input);
|
|
556
|
+
spinner2.stop();
|
|
557
|
+
if (!result.success) {
|
|
558
|
+
console.log(chalk9.red("Invocation failed"));
|
|
559
|
+
console.error(result.error.message);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
console.log("");
|
|
563
|
+
console.log(orange("Result:"));
|
|
564
|
+
console.log(JSON.stringify(result.data.result, null, 2));
|
|
565
|
+
console.log(muted(`
|
|
566
|
+
Duration: ${result.data.duration_ms}ms`));
|
|
567
|
+
}
|
|
568
|
+
async function functionsLogs(slug, options) {
|
|
569
|
+
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
570
|
+
const client = getClient();
|
|
571
|
+
const spinner2 = ora("Fetching invocations...").start();
|
|
572
|
+
const result = await client.code.getInvocations(slug, limit);
|
|
573
|
+
spinner2.stop();
|
|
574
|
+
if (!result.success || !result.data.length) {
|
|
575
|
+
console.log(chalk9.gray("No invocations yet."));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
console.log("");
|
|
579
|
+
result.data.forEach((inv) => {
|
|
580
|
+
const status = inv.success ? "\x1B[38;2;244;140;6m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
|
|
581
|
+
const date = new Date(inv.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
582
|
+
console.log(
|
|
583
|
+
` ${status} ${chalk9.gray(date)} ${inv.duration_ms}ms ${chalk9.gray(`[${inv.trigger_type}]`)}`
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
console.log("");
|
|
587
|
+
}
|
|
588
|
+
async function functionsDelete(slug) {
|
|
589
|
+
const client = getClient();
|
|
590
|
+
const spinner2 = ora(`Deleting ${slug}...`).start();
|
|
591
|
+
const result = await client.code.deleteFunction(slug);
|
|
592
|
+
if (!result.success) {
|
|
593
|
+
spinner2.fail(`Delete failed for ${slug}`);
|
|
594
|
+
console.error(result.error.message);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
spinner2.succeed(`Deleted ${slug}`);
|
|
598
|
+
}
|
|
599
|
+
async function functionsToggle(slug, active) {
|
|
600
|
+
const client = getClient();
|
|
601
|
+
const spinner2 = ora(
|
|
602
|
+
`${active ? "Enabling" : "Disabling"} ${slug}...`
|
|
603
|
+
).start();
|
|
604
|
+
const result = await client.code.toggleFunction(slug, active);
|
|
605
|
+
if (!result.success) {
|
|
606
|
+
spinner2.fail(`Toggle failed for ${slug}`);
|
|
607
|
+
console.error(result.error.message);
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
spinner2.succeed(`${slug} is now ${active ? "active" : "inactive"}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/index.ts
|
|
614
|
+
var version4 = getCliVersion();
|
|
615
|
+
var program = new Command();
|
|
616
|
+
program.name("globio").description("The official Globio CLI").version(version4).addHelpText("beforeAll", () => {
|
|
617
|
+
printBanner(version4);
|
|
618
|
+
return "";
|
|
619
|
+
});
|
|
620
|
+
program.command("login").description("Log in to your Globio account").action(login);
|
|
621
|
+
program.command("logout").description("Log out").action(logout);
|
|
622
|
+
program.command("whoami").description("Show current account and project").action(whoami);
|
|
623
|
+
program.command("init").description("Initialize a Globio project").action(init);
|
|
624
|
+
var projects = program.command("projects").description("Manage projects");
|
|
625
|
+
projects.command("list").description("List projects").action(projectsList);
|
|
626
|
+
projects.command("use <projectId>").description("Set active project").action(projectsUse);
|
|
627
|
+
program.command("services").description("List available Globio services").action(servicesList);
|
|
628
|
+
var functions = program.command("functions").alias("fn").description("Manage GlobalCode edge functions");
|
|
629
|
+
functions.command("list").description("List all functions").action(functionsList);
|
|
630
|
+
functions.command("create <slug>").description("Scaffold a new function file locally").action(functionsCreate);
|
|
631
|
+
functions.command("deploy <slug>").description("Deploy a function to GlobalCode").option("-f, --file <path>", "Path to function file").option("-n, --name <name>", "Display name").action(functionsDeploy);
|
|
632
|
+
functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").action(functionsInvoke);
|
|
633
|
+
functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").action(functionsLogs);
|
|
634
|
+
functions.command("delete <slug>").description("Delete a function").action(functionsDelete);
|
|
635
|
+
functions.command("enable <slug>").description("Enable a function").action((slug) => functionsToggle(slug, true));
|
|
636
|
+
functions.command("disable <slug>").description("Disable a function").action((slug) => functionsToggle(slug, false));
|
|
637
|
+
var migrate = program.command("migrate").description("Migrate from Firebase to Globio");
|
|
638
|
+
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").action(migrateFirestore);
|
|
639
|
+
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").action(migrateFirebaseStorage);
|
|
640
|
+
if (process.argv.length <= 2) {
|
|
641
|
+
printBanner(version4);
|
|
642
|
+
program.help();
|
|
643
|
+
}
|
|
644
|
+
await program.parseAsync();
|
package/jsr.json
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globio/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The official Globio
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The official CLI for Globio — game backend as a service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
5
7
|
"bin": {
|
|
6
8
|
"globio": "./dist/index.js"
|
|
7
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format esm --out-dir dist --clean",
|
|
12
|
+
"dev": "tsup src/index.ts --format esm --out-dir dist --watch",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@clack/prompts": "^0.9.0",
|
|
17
|
+
"@globio/sdk": "^1.0.0",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"cli-progress": "^3.12.0",
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"conf": "^13.0.0",
|
|
22
|
+
"figlet": "^1.11.0",
|
|
23
|
+
"firebase-admin": "^12.0.0",
|
|
24
|
+
"gradient-string": "^3.0.0",
|
|
25
|
+
"ora": "^8.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/cli-progress": "^3.11.0",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.4.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
8
36
|
"publishConfig": {
|
|
9
37
|
"access": "public"
|
|
10
38
|
}
|