@globio/cli 0.2.2 → 1.0.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/README.md +230 -26
- package/dist/index.js +249 -12
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/commands/hooks.ts +324 -0
- package/src/index.ts +74 -1
package/README.md
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# @globio/cli
|
|
2
2
|
|
|
3
|
-
The official CLI for [Globio](https://globio.stanlink.online)
|
|
4
|
-
|
|
3
|
+
The official CLI for [Globio](https://globio.stanlink.online) —
|
|
4
|
+
game backend as a service built on Cloudflare Workers.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
- Node.js 18+
|
|
9
|
+
- A Globio account — [console.globio.stanlink.online](https://console.globio.stanlink.online)
|
|
5
10
|
|
|
6
11
|
## Install
|
|
7
12
|
|
|
@@ -16,81 +21,280 @@ npm install -g @globio/cli
|
|
|
16
21
|
## Quick Start
|
|
17
22
|
|
|
18
23
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
# Log in to your Globio account
|
|
25
|
+
globio login
|
|
26
|
+
|
|
27
|
+
# Initialize a new project
|
|
28
|
+
globio init
|
|
29
|
+
|
|
30
|
+
# Or non-interactively
|
|
31
|
+
globio init --name "My Game" --org org_xxx --json
|
|
21
32
|
```
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Authentication
|
|
24
37
|
|
|
38
|
+
Globio CLI authenticates at account level using a Personal
|
|
39
|
+
Access Token (PAT). Two login methods are supported.
|
|
40
|
+
|
|
41
|
+
**Browser flow** — opens the Globio console for one-click approval:
|
|
25
42
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
--from ./serviceAccountKey.json \
|
|
29
|
-
--all
|
|
43
|
+
globio login
|
|
44
|
+
```
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
--bucket gs://my-game.appspot.com \
|
|
35
|
-
--all
|
|
46
|
+
**Token flow** — paste a PAT from console settings:
|
|
47
|
+
```bash
|
|
48
|
+
globio login --token glo_pat_xxxxx
|
|
36
49
|
```
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
**Named profiles** — manage multiple accounts:
|
|
52
|
+
```bash
|
|
53
|
+
globio login --profile work
|
|
54
|
+
globio login --profile personal
|
|
55
|
+
globio use work
|
|
56
|
+
```
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
Credentials are stored in `~/.globio/profiles/`
|
|
59
|
+
|
|
60
|
+
---
|
|
43
61
|
|
|
44
62
|
## Commands
|
|
45
63
|
|
|
46
64
|
### Auth
|
|
65
|
+
|
|
47
66
|
```bash
|
|
48
|
-
globio login
|
|
67
|
+
globio login # browser or token flow
|
|
68
|
+
globio login --token <pat> # non-interactive
|
|
69
|
+
globio login --profile <name> # named profile
|
|
49
70
|
globio logout
|
|
50
|
-
globio
|
|
71
|
+
globio logout --profile <name>
|
|
72
|
+
globio whoami
|
|
73
|
+
globio whoami --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Profiles
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
globio profiles list
|
|
80
|
+
globio profiles list --json
|
|
81
|
+
globio use <profile> # switch active profile
|
|
51
82
|
```
|
|
52
83
|
|
|
53
84
|
### Projects
|
|
85
|
+
|
|
54
86
|
```bash
|
|
55
87
|
globio projects list
|
|
88
|
+
globio projects list --json
|
|
56
89
|
globio projects use <projectId>
|
|
90
|
+
globio projects create # interactive
|
|
91
|
+
globio projects create \
|
|
92
|
+
--name "My Game" \
|
|
93
|
+
--org <orgId> \
|
|
94
|
+
--json # non-interactive
|
|
57
95
|
```
|
|
58
96
|
|
|
59
97
|
### Services
|
|
98
|
+
|
|
60
99
|
```bash
|
|
61
100
|
globio services list
|
|
101
|
+
globio services list --json
|
|
62
102
|
```
|
|
63
103
|
|
|
64
|
-
### Functions (GlobalCode)
|
|
104
|
+
### Edge Functions (GlobalCode)
|
|
105
|
+
|
|
65
106
|
```bash
|
|
66
107
|
globio functions list
|
|
67
|
-
globio functions
|
|
68
|
-
globio functions
|
|
108
|
+
globio functions list --json
|
|
109
|
+
globio functions create <slug> # scaffold locally
|
|
110
|
+
globio functions deploy <slug> # deploy to Globio
|
|
69
111
|
globio functions invoke <slug> --input '{"key":"value"}'
|
|
112
|
+
globio functions invoke <slug> --input '{"key":"value"}' --json
|
|
70
113
|
globio functions logs <slug>
|
|
114
|
+
globio functions logs <slug> --json
|
|
115
|
+
globio functions watch <slug> # live log streaming
|
|
71
116
|
globio functions enable <slug>
|
|
72
117
|
globio functions disable <slug>
|
|
73
118
|
globio functions delete <slug>
|
|
74
119
|
```
|
|
75
120
|
|
|
121
|
+
### GC Hooks
|
|
122
|
+
|
|
123
|
+
GC Hooks fire automatically when events occur in your
|
|
124
|
+
Globio project. They cannot be invoked manually.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
globio hooks list
|
|
128
|
+
globio hooks list --json
|
|
129
|
+
globio hooks create <slug> # scaffold locally
|
|
130
|
+
globio hooks deploy <slug> \
|
|
131
|
+
--trigger id.onSignup # deploy with trigger
|
|
132
|
+
globio hooks logs <slug>
|
|
133
|
+
globio hooks watch <slug> # live log streaming
|
|
134
|
+
globio hooks enable <slug>
|
|
135
|
+
globio hooks disable <slug>
|
|
136
|
+
globio hooks delete <slug>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Available hook triggers:**
|
|
140
|
+
|
|
141
|
+
| Trigger | Fires when |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `id.onSignup` | New user registers |
|
|
144
|
+
| `id.onSignin` | User signs in |
|
|
145
|
+
| `id.onSignout` | User signs out |
|
|
146
|
+
| `id.onPasswordReset` | Password reset completed |
|
|
147
|
+
| `doc.onCreate` | Document created |
|
|
148
|
+
| `doc.onUpdate` | Document updated |
|
|
149
|
+
| `doc.onDelete` | Document deleted |
|
|
150
|
+
| `mart.onPurchase` | In-game currency purchase |
|
|
151
|
+
| `mart.onPayment` | Fiat payment completed |
|
|
152
|
+
| `sync.onRoomCreate` | Game room created |
|
|
153
|
+
| `sync.onRoomClose` | Game room closed |
|
|
154
|
+
| `sync.onPlayerJoin` | Player joins a room |
|
|
155
|
+
| `sync.onPlayerLeave` | Player leaves a room |
|
|
156
|
+
| `vault.onUpload` | File uploaded |
|
|
157
|
+
| `vault.onDelete` | File deleted |
|
|
158
|
+
| `signal.onDeliver` | Notification delivered |
|
|
159
|
+
|
|
160
|
+
**Example hook:**
|
|
161
|
+
```javascript
|
|
162
|
+
// init-player.hook.js
|
|
163
|
+
async function handler({ userId, email }, globio) {
|
|
164
|
+
await globio.doc.set('players', userId, {
|
|
165
|
+
level: 1, xp: 0, coins: 100
|
|
166
|
+
});
|
|
167
|
+
await globio.signal.sendToUser(userId, {
|
|
168
|
+
title: 'Welcome!',
|
|
169
|
+
body: 'Your adventure begins.',
|
|
170
|
+
priority: 'high'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
globio hooks deploy init-player --trigger id.onSignup
|
|
177
|
+
```
|
|
178
|
+
|
|
76
179
|
### Migrate from Firebase
|
|
180
|
+
|
|
181
|
+
Migrate Firestore collections and Firebase Storage
|
|
182
|
+
to Globio in one command. Non-destructive — your
|
|
183
|
+
Firebase data stays intact until you delete it manually.
|
|
184
|
+
|
|
185
|
+
GlobalDoc indexes are created automatically for every
|
|
186
|
+
field during migration. Queries work immediately after.
|
|
187
|
+
|
|
77
188
|
```bash
|
|
189
|
+
# Migrate a single Firestore collection
|
|
78
190
|
globio migrate firestore \
|
|
79
191
|
--from ./serviceAccountKey.json \
|
|
80
192
|
--collection players
|
|
81
193
|
|
|
194
|
+
# Migrate all Firestore collections
|
|
82
195
|
globio migrate firestore \
|
|
83
196
|
--from ./serviceAccountKey.json \
|
|
84
197
|
--all
|
|
85
198
|
|
|
199
|
+
# Migrate Firebase Storage
|
|
86
200
|
globio migrate firebase-storage \
|
|
87
201
|
--from ./serviceAccountKey.json \
|
|
88
202
|
--bucket gs://my-game.appspot.com \
|
|
89
203
|
--all
|
|
204
|
+
|
|
205
|
+
# Migrate a specific folder
|
|
206
|
+
globio migrate firebase-storage \
|
|
207
|
+
--from ./serviceAccountKey.json \
|
|
208
|
+
--bucket gs://my-game.appspot.com \
|
|
209
|
+
--folder /avatars
|
|
90
210
|
```
|
|
91
211
|
|
|
92
|
-
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## JSON Output and CI/CD
|
|
215
|
+
|
|
216
|
+
Every command supports `--json` for machine-readable output.
|
|
217
|
+
Use this in CI/CD pipelines, scripts, and AI agents.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
globio whoami --json
|
|
221
|
+
globio projects list --json
|
|
222
|
+
globio functions list --json
|
|
223
|
+
globio functions invoke <slug> --input '{}' --json
|
|
224
|
+
globio hooks list --json
|
|
225
|
+
globio services list --json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Combined with non-interactive flags for full automation:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Full CI/CD setup — no prompts
|
|
232
|
+
globio login --token $GLOBIO_PAT --profile ci --json
|
|
233
|
+
globio projects create \
|
|
234
|
+
--name "My Game" \
|
|
235
|
+
--org $ORG_ID \
|
|
236
|
+
--json
|
|
237
|
+
globio functions deploy my-function --json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Live Log Streaming
|
|
243
|
+
|
|
244
|
+
Stream real-time function and hook execution logs
|
|
245
|
+
to your terminal — including console.log output,
|
|
246
|
+
inputs, results, and errors.
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
globio functions watch matchmaking
|
|
250
|
+
globio hooks watch init-player
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Example output:
|
|
254
|
+
```
|
|
255
|
+
⇒⇒ globio 1.0.0
|
|
256
|
+
──────────────────────────────────────────
|
|
257
|
+
watching matchmaking · press Ctrl+C to stop
|
|
258
|
+
|
|
259
|
+
● connected — waiting for invocations...
|
|
260
|
+
|
|
261
|
+
✓ 2026-03-15 06:12:01 [http] 3ms
|
|
262
|
+
input {"userId":"player_001","rating":1450}
|
|
263
|
+
log Querying players with rating 1450
|
|
264
|
+
log Found 3 candidates
|
|
265
|
+
result {"matched":true,"roomId":"room_abc"}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## globio.config.ts
|
|
271
|
+
|
|
272
|
+
Running `globio init` creates a `globio.config.ts` in
|
|
273
|
+
your project root with your project already configured:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { Globio } from '@globio/sdk';
|
|
277
|
+
|
|
278
|
+
export const globio = new Globio({
|
|
279
|
+
apiKey: process.env.GLOBIO_API_KEY!,
|
|
280
|
+
projectId: 'proj_xxxxx',
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Import it anywhere in your project:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { globio } from './globio.config';
|
|
288
|
+
|
|
289
|
+
const player = await globio.doc.get('players', userId);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Links
|
|
93
295
|
|
|
94
|
-
- [SDK](https://npmjs.com/package/@globio/sdk)
|
|
296
|
+
- [SDK](https://npmjs.com/package/@globio/sdk) — `@globio/sdk`
|
|
95
297
|
- [Console](https://console.globio.stanlink.online)
|
|
96
|
-
- [Docs](https://globio.
|
|
298
|
+
- [Docs](https://docs.globio.stanl.ink)
|
|
299
|
+
- [Discord](https://discord.gg/bNDvAsVkMY)
|
|
300
|
+
- [GitHub](https://github.com/Globio-Technologies/globio-cli)
|
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(version12, subtitle) {
|
|
210
210
|
const lines = [
|
|
211
211
|
"",
|
|
212
|
-
orange(" \u21D2\u21D2") + reset + " globio " + dim(
|
|
212
|
+
orange(" \u21D2\u21D2") + reset + " globio " + dim(version12),
|
|
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) {
|
|
@@ -234,14 +234,14 @@ var globioGradient = gradientString(
|
|
|
234
234
|
"#ffba08",
|
|
235
235
|
"#ffd000"
|
|
236
236
|
);
|
|
237
|
-
function printBanner(
|
|
237
|
+
function printBanner(version12) {
|
|
238
238
|
const art = figlet.textSync("Globio", {
|
|
239
239
|
font: "ANSI Shadow",
|
|
240
240
|
horizontalLayout: "default"
|
|
241
241
|
});
|
|
242
242
|
console.log(globioGradient.multiline(art));
|
|
243
243
|
console.log(
|
|
244
|
-
globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" +
|
|
244
|
+
globioGradient(" \u21D2\u21D2") + " Game Backend as a Service \x1B[2mv" + version12 + "\x1B[0m"
|
|
245
245
|
);
|
|
246
246
|
console.log("");
|
|
247
247
|
}
|
|
@@ -643,8 +643,8 @@ async function createIndex(collection, field, fieldType = "string", profile) {
|
|
|
643
643
|
// src/lib/firebase.ts
|
|
644
644
|
async function initFirebase(serviceAccountPath) {
|
|
645
645
|
const admin = await import("firebase-admin");
|
|
646
|
-
const { readFileSync:
|
|
647
|
-
const serviceAccount = JSON.parse(
|
|
646
|
+
const { readFileSync: readFileSync6 } = await import("fs");
|
|
647
|
+
const serviceAccount = JSON.parse(readFileSync6(serviceAccountPath, "utf-8"));
|
|
648
648
|
if (!admin.default.apps.length) {
|
|
649
649
|
admin.default.initializeApp({
|
|
650
650
|
credential: admin.default.credential.cert(serviceAccount),
|
|
@@ -1566,9 +1566,235 @@ async function functionsToggle(slug, active, options = {}) {
|
|
|
1566
1566
|
spinner2?.succeed(`${slug} is now ${active ? "active" : "inactive"}`);
|
|
1567
1567
|
}
|
|
1568
1568
|
|
|
1569
|
+
// src/commands/hooks.ts
|
|
1570
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1571
|
+
var version9 = getCliVersion();
|
|
1572
|
+
function parseJsonField2(value) {
|
|
1573
|
+
if (!value) return null;
|
|
1574
|
+
try {
|
|
1575
|
+
return JSON.parse(value);
|
|
1576
|
+
} catch {
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
var HOOK_TRIGGERS = [
|
|
1581
|
+
"id.onSignup",
|
|
1582
|
+
"id.onSignin",
|
|
1583
|
+
"id.onSignout",
|
|
1584
|
+
"id.onPasswordReset",
|
|
1585
|
+
"doc.onCreate",
|
|
1586
|
+
"doc.onUpdate",
|
|
1587
|
+
"doc.onDelete",
|
|
1588
|
+
"mart.onPurchase",
|
|
1589
|
+
"mart.onPayment",
|
|
1590
|
+
"sync.onRoomCreate",
|
|
1591
|
+
"sync.onRoomClose",
|
|
1592
|
+
"sync.onPlayerJoin",
|
|
1593
|
+
"sync.onPlayerLeave",
|
|
1594
|
+
"vault.onUpload",
|
|
1595
|
+
"vault.onDelete",
|
|
1596
|
+
"signal.onDeliver"
|
|
1597
|
+
];
|
|
1598
|
+
async function hooksList(options = {}) {
|
|
1599
|
+
const client = getClient(options.profile);
|
|
1600
|
+
const result = await client.code.listHooks();
|
|
1601
|
+
if (options.json) {
|
|
1602
|
+
jsonOutput(
|
|
1603
|
+
result.success ? (result.data ?? []).map((hook) => ({
|
|
1604
|
+
slug: hook.slug,
|
|
1605
|
+
type: hook.type,
|
|
1606
|
+
trigger_event: hook.trigger_event,
|
|
1607
|
+
active: hook.active
|
|
1608
|
+
})) : []
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
if (!result.success || !result.data?.length) {
|
|
1612
|
+
console.log(header(version9));
|
|
1613
|
+
console.log(" " + muted("No hooks found.") + "\n");
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
const rows = result.data.map((fn) => [
|
|
1617
|
+
gold(fn.slug),
|
|
1618
|
+
gold(fn.trigger_event ?? "\u2014"),
|
|
1619
|
+
fn.active ? green("active") : inactive("inactive")
|
|
1620
|
+
]);
|
|
1621
|
+
console.log(header(version9));
|
|
1622
|
+
console.log(
|
|
1623
|
+
renderTable({
|
|
1624
|
+
columns: [
|
|
1625
|
+
{ header: "Hook", width: 24 },
|
|
1626
|
+
{ header: "Trigger", width: 24 },
|
|
1627
|
+
{ header: "Status", width: 10 }
|
|
1628
|
+
],
|
|
1629
|
+
rows
|
|
1630
|
+
})
|
|
1631
|
+
);
|
|
1632
|
+
console.log("");
|
|
1633
|
+
}
|
|
1634
|
+
async function hooksCreate(slug, options = {}) {
|
|
1635
|
+
const filename = `${slug}.hook.js`;
|
|
1636
|
+
if (existsSync4(filename)) {
|
|
1637
|
+
if (options.json) {
|
|
1638
|
+
jsonOutput({ success: false, file: filename, error: "File already exists" });
|
|
1639
|
+
}
|
|
1640
|
+
console.log(gold(filename) + reset + " already exists.");
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const template = `/**
|
|
1644
|
+
* GC Hook: ${slug}
|
|
1645
|
+
* This hook fires automatically when its trigger event occurs.
|
|
1646
|
+
* You cannot invoke it manually.
|
|
1647
|
+
*
|
|
1648
|
+
* The handler receives the event payload and the injected
|
|
1649
|
+
* globio SDK \u2014 use it to orchestrate any Globio service.
|
|
1650
|
+
*/
|
|
1651
|
+
async function handler(payload, globio) {
|
|
1652
|
+
// payload: event data from the trigger
|
|
1653
|
+
// globio: injected SDK \u2014 access all Globio services
|
|
1654
|
+
|
|
1655
|
+
// Example for id.onSignup:
|
|
1656
|
+
// const { userId, email } = payload;
|
|
1657
|
+
// await globio.doc.set('players', userId, {
|
|
1658
|
+
// level: 1, xp: 0, coins: 100
|
|
1659
|
+
// });
|
|
1660
|
+
}
|
|
1661
|
+
`;
|
|
1662
|
+
writeFileSync4(filename, template);
|
|
1663
|
+
if (options.json) {
|
|
1664
|
+
jsonOutput({ success: true, file: filename });
|
|
1665
|
+
}
|
|
1666
|
+
console.log(green("\u2713") + reset + " Created " + gold(filename) + reset);
|
|
1667
|
+
console.log(muted(" Deploy with: globio hooks deploy " + slug));
|
|
1668
|
+
}
|
|
1669
|
+
async function hooksDeploy(slug, options) {
|
|
1670
|
+
const filename = options.file ?? `${slug}.hook.js`;
|
|
1671
|
+
if (!existsSync4(filename)) {
|
|
1672
|
+
if (options.json) {
|
|
1673
|
+
jsonOutput({ success: false, error: `File not found: ${filename}` });
|
|
1674
|
+
}
|
|
1675
|
+
console.log(
|
|
1676
|
+
failure("File not found: " + filename) + reset + " Run: globio hooks create " + slug
|
|
1677
|
+
);
|
|
1678
|
+
process.exit(1);
|
|
1679
|
+
}
|
|
1680
|
+
if (!options.trigger) {
|
|
1681
|
+
if (options.json) {
|
|
1682
|
+
jsonOutput({ success: false, error: "--trigger required for hooks" });
|
|
1683
|
+
}
|
|
1684
|
+
console.log(
|
|
1685
|
+
failure("--trigger required for hooks.") + reset + "\n\n Available triggers:\n" + HOOK_TRIGGERS.map((trigger) => " " + gold(trigger) + reset).join("\n")
|
|
1686
|
+
);
|
|
1687
|
+
process.exit(1);
|
|
1688
|
+
}
|
|
1689
|
+
const code = readFileSync5(filename, "utf-8");
|
|
1690
|
+
const client = getClient(options.profile);
|
|
1691
|
+
const existing = await client.code.getFunction(slug).catch(() => null);
|
|
1692
|
+
let result;
|
|
1693
|
+
if (existing?.success) {
|
|
1694
|
+
result = await client.code.updateHook(slug, {
|
|
1695
|
+
code,
|
|
1696
|
+
trigger: options.trigger
|
|
1697
|
+
});
|
|
1698
|
+
if (options.json) {
|
|
1699
|
+
jsonOutput({ success: result.success, slug, action: "updated" });
|
|
1700
|
+
}
|
|
1701
|
+
if (!result.success) {
|
|
1702
|
+
console.log(failure("Deploy failed"));
|
|
1703
|
+
process.exit(1);
|
|
1704
|
+
}
|
|
1705
|
+
console.log(green("\u2713") + reset + " Updated hook " + gold(slug) + reset);
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
result = await client.code.createHook({
|
|
1709
|
+
name: options.name ?? slug,
|
|
1710
|
+
slug,
|
|
1711
|
+
trigger: options.trigger,
|
|
1712
|
+
code
|
|
1713
|
+
});
|
|
1714
|
+
if (options.json) {
|
|
1715
|
+
jsonOutput({ success: result.success, slug, action: "created" });
|
|
1716
|
+
}
|
|
1717
|
+
if (!result.success) {
|
|
1718
|
+
console.log(failure("Deploy failed"));
|
|
1719
|
+
process.exit(1);
|
|
1720
|
+
}
|
|
1721
|
+
console.log(green("\u2713") + reset + " Deployed hook " + gold(slug) + reset);
|
|
1722
|
+
}
|
|
1723
|
+
async function hooksLogs(slug, options = {}) {
|
|
1724
|
+
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
1725
|
+
const client = getClient(options.profile);
|
|
1726
|
+
const result = await client.code.getHookInvocations(slug, limit);
|
|
1727
|
+
if (options.json) {
|
|
1728
|
+
jsonOutput(
|
|
1729
|
+
result.success ? result.data.map((invocation) => ({
|
|
1730
|
+
id: invocation.id,
|
|
1731
|
+
trigger_type: invocation.trigger_type,
|
|
1732
|
+
duration_ms: invocation.duration_ms,
|
|
1733
|
+
success: invocation.success,
|
|
1734
|
+
invoked_at: invocation.invoked_at,
|
|
1735
|
+
logs: parseJsonField2(invocation.logs) ?? [],
|
|
1736
|
+
error_message: invocation.error_message ?? null,
|
|
1737
|
+
input: parseJsonField2(invocation.input),
|
|
1738
|
+
result: parseJsonField2(invocation.result)
|
|
1739
|
+
})) : []
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
if (!result.success || !result.data?.length) {
|
|
1743
|
+
console.log(header(version9));
|
|
1744
|
+
console.log(" " + muted("No invocations yet.") + "\n");
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const rows = result.data.map((inv) => {
|
|
1748
|
+
const date = new Date(inv.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
1749
|
+
return [
|
|
1750
|
+
muted(date),
|
|
1751
|
+
muted(inv.duration_ms + "ms"),
|
|
1752
|
+
inv.success ? green("success") : failure("failed")
|
|
1753
|
+
];
|
|
1754
|
+
});
|
|
1755
|
+
console.log(header(version9));
|
|
1756
|
+
console.log(
|
|
1757
|
+
renderTable({
|
|
1758
|
+
columns: [
|
|
1759
|
+
{ header: "Time", width: 21 },
|
|
1760
|
+
{ header: "Duration", width: 10 },
|
|
1761
|
+
{ header: "Status", width: 10 }
|
|
1762
|
+
],
|
|
1763
|
+
rows
|
|
1764
|
+
})
|
|
1765
|
+
);
|
|
1766
|
+
console.log("");
|
|
1767
|
+
}
|
|
1768
|
+
async function hooksToggle(slug, active, options = {}) {
|
|
1769
|
+
const client = getClient(options.profile);
|
|
1770
|
+
const result = await client.code.toggleHook(slug, active);
|
|
1771
|
+
if (options.json) {
|
|
1772
|
+
jsonOutput({ success: result.success, slug, active });
|
|
1773
|
+
}
|
|
1774
|
+
if (!result.success) {
|
|
1775
|
+
console.log(failure("Toggle failed"));
|
|
1776
|
+
process.exit(1);
|
|
1777
|
+
}
|
|
1778
|
+
console.log(
|
|
1779
|
+
green("\u2713") + reset + " " + gold(slug) + reset + " is now " + (active ? green("active") : inactive("inactive")) + reset
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
async function hooksDelete(slug, options = {}) {
|
|
1783
|
+
const client = getClient(options.profile);
|
|
1784
|
+
const result = await client.code.deleteHook(slug);
|
|
1785
|
+
if (options.json) {
|
|
1786
|
+
jsonOutput({ success: result.success, slug });
|
|
1787
|
+
}
|
|
1788
|
+
if (!result.success) {
|
|
1789
|
+
console.log(failure("Delete failed"));
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
console.log(green("\u2713") + reset + " Deleted hook " + gold(slug) + reset);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1569
1795
|
// src/commands/watch.ts
|
|
1570
1796
|
var BASE_URL2 = "https://api.globio.stanlink.online";
|
|
1571
|
-
var
|
|
1797
|
+
var version10 = getCliVersion();
|
|
1572
1798
|
async function functionsWatch(slug, options = {}) {
|
|
1573
1799
|
const profileName = options.profile ?? config.getActiveProfile();
|
|
1574
1800
|
const profile = config.getProfile(profileName ?? "default");
|
|
@@ -1578,7 +1804,7 @@ async function functionsWatch(slug, options = {}) {
|
|
|
1578
1804
|
);
|
|
1579
1805
|
process.exit(1);
|
|
1580
1806
|
}
|
|
1581
|
-
console.log(header(
|
|
1807
|
+
console.log(header(version10));
|
|
1582
1808
|
console.log(
|
|
1583
1809
|
" " + orange("watching") + reset + " " + slug + dim(" \xB7 press Ctrl+C to stop") + "\n"
|
|
1584
1810
|
);
|
|
@@ -1674,10 +1900,10 @@ function renderEvent(event) {
|
|
|
1674
1900
|
}
|
|
1675
1901
|
|
|
1676
1902
|
// src/index.ts
|
|
1677
|
-
var
|
|
1903
|
+
var version11 = getCliVersion();
|
|
1678
1904
|
var program = new Command();
|
|
1679
|
-
program.name("globio").description("The official Globio CLI").version(
|
|
1680
|
-
printBanner(
|
|
1905
|
+
program.name("globio").description("The official Globio CLI").version(version11).addHelpText("beforeAll", () => {
|
|
1906
|
+
printBanner(version11);
|
|
1681
1907
|
return "";
|
|
1682
1908
|
}).addHelpText(
|
|
1683
1909
|
"after",
|
|
@@ -1689,6 +1915,8 @@ Examples:
|
|
|
1689
1915
|
$ globio projects list
|
|
1690
1916
|
$ globio projects use proj_abc123
|
|
1691
1917
|
$ globio functions deploy my-function
|
|
1918
|
+
$ globio hooks deploy on-signup --trigger id.onSignup
|
|
1919
|
+
$ globio hooks list
|
|
1692
1920
|
$ globio migrate firestore --from ./key.json --all
|
|
1693
1921
|
|
|
1694
1922
|
Credentials are stored in ~/.globio/profiles/
|
|
@@ -1710,7 +1938,7 @@ projects.command("list").description("List projects").option("--profile <name>",
|
|
|
1710
1938
|
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
1939
|
projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(projectsUse);
|
|
1712
1940
|
program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(servicesList);
|
|
1713
|
-
var functions = program.command("functions").alias("fn").description("Manage
|
|
1941
|
+
var functions = program.command("functions").alias("fn").description("Manage edge functions");
|
|
1714
1942
|
functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsList);
|
|
1715
1943
|
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
1944
|
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);
|
|
@@ -1720,6 +1948,15 @@ functions.command("watch <slug>").description("Stream live function execution lo
|
|
|
1720
1948
|
functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").option("--json", "Output as JSON").action(functionsDelete);
|
|
1721
1949
|
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
1950
|
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));
|
|
1951
|
+
var hooks = program.command("hooks").description("Manage GC Hooks").action(hooksList);
|
|
1952
|
+
hooks.command("list").description("List all hooks").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action(hooksList);
|
|
1953
|
+
hooks.command("create <slug>").description("Scaffold a new hook file locally").option("--json", "Output as JSON").action(hooksCreate);
|
|
1954
|
+
hooks.command("deploy <slug>").description("Deploy a hook").option("-f, --file <path>", "Path to hook file").option("-n, --name <name>", "Display name").option("-t, --trigger <event>", "Hook trigger event (e.g. id.onSignup)").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action(hooksDeploy);
|
|
1955
|
+
hooks.command("logs <slug>").description("Show hook invocation history").option("-l, --limit <n>", "Number of entries", "20").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action(hooksLogs);
|
|
1956
|
+
hooks.command("watch <slug>").description("Stream live hook execution logs").option("-p, --profile <name>", "Profile name").action((slug, opts) => functionsWatch(slug, opts));
|
|
1957
|
+
hooks.command("enable <slug>").description("Enable a hook").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action((slug, opts) => hooksToggle(slug, true, opts));
|
|
1958
|
+
hooks.command("disable <slug>").description("Disable a hook").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action((slug, opts) => hooksToggle(slug, false, opts));
|
|
1959
|
+
hooks.command("delete <slug>").description("Delete a hook").option("-p, --profile <name>", "Profile name").option("--json", "Output as JSON").action(hooksDelete);
|
|
1723
1960
|
var migrate = program.command("migrate").description("Migrate from Firebase to Globio");
|
|
1724
1961
|
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
1962
|
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);
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globio/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "The official CLI for Globio — game backend as a service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@clack/prompts": "^0.9.0",
|
|
17
|
-
"@globio/sdk": "^1.
|
|
17
|
+
"@globio/sdk": "^1.1.0",
|
|
18
18
|
"chalk": "^5.3.0",
|
|
19
19
|
"cli-progress": "^3.12.0",
|
|
20
20
|
"commander": "^12.0.0",
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { getClient } from '../lib/sdk.js';
|
|
3
|
+
import {
|
|
4
|
+
failure,
|
|
5
|
+
getCliVersion,
|
|
6
|
+
gold,
|
|
7
|
+
green,
|
|
8
|
+
header,
|
|
9
|
+
inactive,
|
|
10
|
+
jsonOutput,
|
|
11
|
+
muted,
|
|
12
|
+
renderTable,
|
|
13
|
+
reset,
|
|
14
|
+
} from '../lib/banner.js';
|
|
15
|
+
import type { CodeInvocation } from '@globio/sdk';
|
|
16
|
+
|
|
17
|
+
const version = getCliVersion();
|
|
18
|
+
|
|
19
|
+
function parseJsonField<T>(value: string | null | undefined): T | null {
|
|
20
|
+
if (!value) return null;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value) as T;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const HOOK_TRIGGERS = [
|
|
29
|
+
'id.onSignup',
|
|
30
|
+
'id.onSignin',
|
|
31
|
+
'id.onSignout',
|
|
32
|
+
'id.onPasswordReset',
|
|
33
|
+
'doc.onCreate',
|
|
34
|
+
'doc.onUpdate',
|
|
35
|
+
'doc.onDelete',
|
|
36
|
+
'mart.onPurchase',
|
|
37
|
+
'mart.onPayment',
|
|
38
|
+
'sync.onRoomCreate',
|
|
39
|
+
'sync.onRoomClose',
|
|
40
|
+
'sync.onPlayerJoin',
|
|
41
|
+
'sync.onPlayerLeave',
|
|
42
|
+
'vault.onUpload',
|
|
43
|
+
'vault.onDelete',
|
|
44
|
+
'signal.onDeliver',
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export async function hooksList(
|
|
48
|
+
options: { profile?: string; json?: boolean } = {}
|
|
49
|
+
) {
|
|
50
|
+
const client = getClient(options.profile);
|
|
51
|
+
const result = await client.code.listHooks();
|
|
52
|
+
|
|
53
|
+
if (options.json) {
|
|
54
|
+
jsonOutput(
|
|
55
|
+
result.success
|
|
56
|
+
? (result.data ?? []).map((hook) => ({
|
|
57
|
+
slug: hook.slug,
|
|
58
|
+
type: hook.type,
|
|
59
|
+
trigger_event: hook.trigger_event,
|
|
60
|
+
active: hook.active,
|
|
61
|
+
}))
|
|
62
|
+
: []
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!result.success || !result.data?.length) {
|
|
67
|
+
console.log(header(version));
|
|
68
|
+
console.log(' ' + muted('No hooks found.') + '\n');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const rows = result.data.map((fn) => [
|
|
73
|
+
gold(fn.slug),
|
|
74
|
+
gold(fn.trigger_event ?? '—'),
|
|
75
|
+
fn.active ? green('active') : inactive('inactive'),
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
console.log(header(version));
|
|
79
|
+
console.log(
|
|
80
|
+
renderTable({
|
|
81
|
+
columns: [
|
|
82
|
+
{ header: 'Hook', width: 24 },
|
|
83
|
+
{ header: 'Trigger', width: 24 },
|
|
84
|
+
{ header: 'Status', width: 10 },
|
|
85
|
+
],
|
|
86
|
+
rows,
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function hooksCreate(
|
|
93
|
+
slug: string,
|
|
94
|
+
options: { json?: boolean } = {}
|
|
95
|
+
) {
|
|
96
|
+
const filename = `${slug}.hook.js`;
|
|
97
|
+
if (existsSync(filename)) {
|
|
98
|
+
if (options.json) {
|
|
99
|
+
jsonOutput({ success: false, file: filename, error: 'File already exists' });
|
|
100
|
+
}
|
|
101
|
+
console.log(gold(filename) + reset + ' already exists.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const template = `/**
|
|
106
|
+
* GC Hook: ${slug}
|
|
107
|
+
* This hook fires automatically when its trigger event occurs.
|
|
108
|
+
* You cannot invoke it manually.
|
|
109
|
+
*
|
|
110
|
+
* The handler receives the event payload and the injected
|
|
111
|
+
* globio SDK — use it to orchestrate any Globio service.
|
|
112
|
+
*/
|
|
113
|
+
async function handler(payload, globio) {
|
|
114
|
+
// payload: event data from the trigger
|
|
115
|
+
// globio: injected SDK — access all Globio services
|
|
116
|
+
|
|
117
|
+
// Example for id.onSignup:
|
|
118
|
+
// const { userId, email } = payload;
|
|
119
|
+
// await globio.doc.set('players', userId, {
|
|
120
|
+
// level: 1, xp: 0, coins: 100
|
|
121
|
+
// });
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
writeFileSync(filename, template);
|
|
126
|
+
|
|
127
|
+
if (options.json) {
|
|
128
|
+
jsonOutput({ success: true, file: filename });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(green('✓') + reset + ' Created ' + gold(filename) + reset);
|
|
132
|
+
console.log(muted(' Deploy with: globio hooks deploy ' + slug));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function hooksDeploy(
|
|
136
|
+
slug: string,
|
|
137
|
+
options: {
|
|
138
|
+
file?: string;
|
|
139
|
+
name?: string;
|
|
140
|
+
trigger?: string;
|
|
141
|
+
profile?: string;
|
|
142
|
+
json?: boolean;
|
|
143
|
+
}
|
|
144
|
+
) {
|
|
145
|
+
const filename = options.file ?? `${slug}.hook.js`;
|
|
146
|
+
if (!existsSync(filename)) {
|
|
147
|
+
if (options.json) {
|
|
148
|
+
jsonOutput({ success: false, error: `File not found: ${filename}` });
|
|
149
|
+
}
|
|
150
|
+
console.log(
|
|
151
|
+
failure('File not found: ' + filename) +
|
|
152
|
+
reset +
|
|
153
|
+
' Run: globio hooks create ' +
|
|
154
|
+
slug
|
|
155
|
+
);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!options.trigger) {
|
|
160
|
+
if (options.json) {
|
|
161
|
+
jsonOutput({ success: false, error: '--trigger required for hooks' });
|
|
162
|
+
}
|
|
163
|
+
console.log(
|
|
164
|
+
failure('--trigger required for hooks.') +
|
|
165
|
+
reset +
|
|
166
|
+
'\n\n Available triggers:\n' +
|
|
167
|
+
HOOK_TRIGGERS.map((trigger) => ' ' + gold(trigger) + reset).join('\n')
|
|
168
|
+
);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const code = readFileSync(filename, 'utf-8');
|
|
173
|
+
const client = getClient(options.profile);
|
|
174
|
+
const existing = await client.code.getFunction(slug).catch(() => null);
|
|
175
|
+
|
|
176
|
+
let result;
|
|
177
|
+
if (existing?.success) {
|
|
178
|
+
result = await client.code.updateHook(slug, {
|
|
179
|
+
code,
|
|
180
|
+
trigger: options.trigger as (typeof HOOK_TRIGGERS)[number],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (options.json) {
|
|
184
|
+
jsonOutput({ success: result.success, slug, action: 'updated' });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!result.success) {
|
|
188
|
+
console.log(failure('Deploy failed'));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log(green('✓') + reset + ' Updated hook ' + gold(slug) + reset);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
result = await client.code.createHook({
|
|
197
|
+
name: options.name ?? slug,
|
|
198
|
+
slug,
|
|
199
|
+
trigger: options.trigger as (typeof HOOK_TRIGGERS)[number],
|
|
200
|
+
code,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (options.json) {
|
|
204
|
+
jsonOutput({ success: result.success, slug, action: 'created' });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!result.success) {
|
|
208
|
+
console.log(failure('Deploy failed'));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(green('✓') + reset + ' Deployed hook ' + gold(slug) + reset);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function hooksLogs(
|
|
216
|
+
slug: string,
|
|
217
|
+
options: { limit?: string; profile?: string; json?: boolean } = {}
|
|
218
|
+
) {
|
|
219
|
+
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
220
|
+
const client = getClient(options.profile);
|
|
221
|
+
const result = await client.code.getHookInvocations(slug, limit);
|
|
222
|
+
|
|
223
|
+
if (options.json) {
|
|
224
|
+
jsonOutput(
|
|
225
|
+
result.success
|
|
226
|
+
? (result.data as Array<CodeInvocation & {
|
|
227
|
+
logs?: string | null;
|
|
228
|
+
error_message?: string | null;
|
|
229
|
+
input?: string | null;
|
|
230
|
+
result?: string | null;
|
|
231
|
+
}>).map((invocation) => ({
|
|
232
|
+
id: invocation.id,
|
|
233
|
+
trigger_type: invocation.trigger_type,
|
|
234
|
+
duration_ms: invocation.duration_ms,
|
|
235
|
+
success: invocation.success,
|
|
236
|
+
invoked_at: invocation.invoked_at,
|
|
237
|
+
logs: parseJsonField<string[]>(invocation.logs) ?? [],
|
|
238
|
+
error_message: invocation.error_message ?? null,
|
|
239
|
+
input: parseJsonField<Record<string, unknown>>(invocation.input),
|
|
240
|
+
result: parseJsonField<unknown>(invocation.result),
|
|
241
|
+
}))
|
|
242
|
+
: []
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!result.success || !result.data?.length) {
|
|
247
|
+
console.log(header(version));
|
|
248
|
+
console.log(' ' + muted('No invocations yet.') + '\n');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const rows = result.data.map((inv) => {
|
|
253
|
+
const date = new Date(inv.invoked_at * 1000)
|
|
254
|
+
.toISOString()
|
|
255
|
+
.replace('T', ' ')
|
|
256
|
+
.slice(0, 19);
|
|
257
|
+
return [
|
|
258
|
+
muted(date),
|
|
259
|
+
muted(inv.duration_ms + 'ms'),
|
|
260
|
+
inv.success ? green('success') : failure('failed'),
|
|
261
|
+
];
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
console.log(header(version));
|
|
265
|
+
console.log(
|
|
266
|
+
renderTable({
|
|
267
|
+
columns: [
|
|
268
|
+
{ header: 'Time', width: 21 },
|
|
269
|
+
{ header: 'Duration', width: 10 },
|
|
270
|
+
{ header: 'Status', width: 10 },
|
|
271
|
+
],
|
|
272
|
+
rows,
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
console.log('');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function hooksToggle(
|
|
279
|
+
slug: string,
|
|
280
|
+
active: boolean,
|
|
281
|
+
options: { profile?: string; json?: boolean } = {}
|
|
282
|
+
) {
|
|
283
|
+
const client = getClient(options.profile);
|
|
284
|
+
const result = await client.code.toggleHook(slug, active);
|
|
285
|
+
|
|
286
|
+
if (options.json) {
|
|
287
|
+
jsonOutput({ success: result.success, slug, active });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!result.success) {
|
|
291
|
+
console.log(failure('Toggle failed'));
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log(
|
|
296
|
+
green('✓') +
|
|
297
|
+
reset +
|
|
298
|
+
' ' +
|
|
299
|
+
gold(slug) +
|
|
300
|
+
reset +
|
|
301
|
+
' is now ' +
|
|
302
|
+
(active ? green('active') : inactive('inactive')) +
|
|
303
|
+
reset
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function hooksDelete(
|
|
308
|
+
slug: string,
|
|
309
|
+
options: { profile?: string; json?: boolean } = {}
|
|
310
|
+
) {
|
|
311
|
+
const client = getClient(options.profile);
|
|
312
|
+
const result = await client.code.deleteHook(slug);
|
|
313
|
+
|
|
314
|
+
if (options.json) {
|
|
315
|
+
jsonOutput({ success: result.success, slug });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!result.success) {
|
|
319
|
+
console.log(failure('Delete failed'));
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log(green('✓') + reset + ' Deleted hook ' + gold(slug) + reset);
|
|
324
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,14 @@ import {
|
|
|
17
17
|
functionsDelete,
|
|
18
18
|
functionsToggle,
|
|
19
19
|
} from './commands/functions.js';
|
|
20
|
+
import {
|
|
21
|
+
hooksList,
|
|
22
|
+
hooksCreate,
|
|
23
|
+
hooksDeploy,
|
|
24
|
+
hooksLogs,
|
|
25
|
+
hooksToggle,
|
|
26
|
+
hooksDelete,
|
|
27
|
+
} from './commands/hooks.js';
|
|
20
28
|
import { functionsWatch } from './commands/watch.js';
|
|
21
29
|
import {
|
|
22
30
|
migrateFirestore,
|
|
@@ -46,6 +54,8 @@ Examples:
|
|
|
46
54
|
$ globio projects list
|
|
47
55
|
$ globio projects use proj_abc123
|
|
48
56
|
$ globio functions deploy my-function
|
|
57
|
+
$ globio hooks deploy on-signup --trigger id.onSignup
|
|
58
|
+
$ globio hooks list
|
|
49
59
|
$ globio migrate firestore --from ./key.json --all
|
|
50
60
|
|
|
51
61
|
Credentials are stored in ~/.globio/profiles/
|
|
@@ -110,7 +120,7 @@ program.command('services').description('List available Globio services').option
|
|
|
110
120
|
const functions = program
|
|
111
121
|
.command('functions')
|
|
112
122
|
.alias('fn')
|
|
113
|
-
.description('Manage
|
|
123
|
+
.description('Manage edge functions');
|
|
114
124
|
|
|
115
125
|
functions.command('list').description('List all functions').option('--profile <name>', 'Use a specific profile').option('--json', 'Output as JSON').action(functionsList);
|
|
116
126
|
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);
|
|
@@ -155,6 +165,69 @@ functions
|
|
|
155
165
|
.option('--json', 'Output as JSON')
|
|
156
166
|
.action((slug, options) => functionsToggle(slug, false, options));
|
|
157
167
|
|
|
168
|
+
const hooks = program
|
|
169
|
+
.command('hooks')
|
|
170
|
+
.description('Manage GC Hooks')
|
|
171
|
+
.action(hooksList);
|
|
172
|
+
|
|
173
|
+
hooks
|
|
174
|
+
.command('list')
|
|
175
|
+
.description('List all hooks')
|
|
176
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
177
|
+
.option('--json', 'Output as JSON')
|
|
178
|
+
.action(hooksList);
|
|
179
|
+
|
|
180
|
+
hooks
|
|
181
|
+
.command('create <slug>')
|
|
182
|
+
.description('Scaffold a new hook file locally')
|
|
183
|
+
.option('--json', 'Output as JSON')
|
|
184
|
+
.action(hooksCreate);
|
|
185
|
+
|
|
186
|
+
hooks
|
|
187
|
+
.command('deploy <slug>')
|
|
188
|
+
.description('Deploy a hook')
|
|
189
|
+
.option('-f, --file <path>', 'Path to hook file')
|
|
190
|
+
.option('-n, --name <name>', 'Display name')
|
|
191
|
+
.option('-t, --trigger <event>', 'Hook trigger event (e.g. id.onSignup)')
|
|
192
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
193
|
+
.option('--json', 'Output as JSON')
|
|
194
|
+
.action(hooksDeploy);
|
|
195
|
+
|
|
196
|
+
hooks
|
|
197
|
+
.command('logs <slug>')
|
|
198
|
+
.description('Show hook invocation history')
|
|
199
|
+
.option('-l, --limit <n>', 'Number of entries', '20')
|
|
200
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
201
|
+
.option('--json', 'Output as JSON')
|
|
202
|
+
.action(hooksLogs);
|
|
203
|
+
|
|
204
|
+
hooks
|
|
205
|
+
.command('watch <slug>')
|
|
206
|
+
.description('Stream live hook execution logs')
|
|
207
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
208
|
+
.action((slug, opts) => functionsWatch(slug, opts));
|
|
209
|
+
|
|
210
|
+
hooks
|
|
211
|
+
.command('enable <slug>')
|
|
212
|
+
.description('Enable a hook')
|
|
213
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
214
|
+
.option('--json', 'Output as JSON')
|
|
215
|
+
.action((slug, opts) => hooksToggle(slug, true, opts));
|
|
216
|
+
|
|
217
|
+
hooks
|
|
218
|
+
.command('disable <slug>')
|
|
219
|
+
.description('Disable a hook')
|
|
220
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
221
|
+
.option('--json', 'Output as JSON')
|
|
222
|
+
.action((slug, opts) => hooksToggle(slug, false, opts));
|
|
223
|
+
|
|
224
|
+
hooks
|
|
225
|
+
.command('delete <slug>')
|
|
226
|
+
.description('Delete a hook')
|
|
227
|
+
.option('-p, --profile <name>', 'Profile name')
|
|
228
|
+
.option('--json', 'Output as JSON')
|
|
229
|
+
.action(hooksDelete);
|
|
230
|
+
|
|
158
231
|
const migrate = program
|
|
159
232
|
.command('migrate')
|
|
160
233
|
.description('Migrate from Firebase to Globio');
|