@globio/cli 0.1.3 → 0.1.5
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 +437 -223
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/auth/login.ts +62 -16
- package/src/auth/logout.ts +26 -4
- package/src/auth/useProfile.ts +20 -0
- package/src/auth/whoami.ts +26 -8
- package/src/commands/functions.ts +28 -13
- package/src/commands/init.ts +21 -7
- package/src/commands/migrate.ts +9 -2
- package/src/commands/profiles.ts +26 -0
- package/src/commands/projects.ts +41 -14
- package/src/commands/services.ts +4 -1
- package/src/index.ts +55 -13
- package/src/lib/config.ts +119 -71
- package/src/lib/manage.ts +4 -3
- package/src/lib/sdk.ts +6 -2
package/dist/index.js
CHANGED
|
@@ -10,97 +10,134 @@ import chalk2 from "chalk";
|
|
|
10
10
|
|
|
11
11
|
// src/lib/config.ts
|
|
12
12
|
import chalk from "chalk";
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
14
|
+
import os from "os";
|
|
15
|
+
import path from "path";
|
|
16
|
+
var baseDir = path.join(os.homedir(), ".globio");
|
|
17
|
+
var profilesDir = path.join(baseDir, "profiles");
|
|
18
|
+
var configPath = path.join(baseDir, "config.json");
|
|
19
|
+
function ensureBaseDir() {
|
|
20
|
+
mkdirSync(baseDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
function ensureProfilesDir() {
|
|
23
|
+
ensureBaseDir();
|
|
24
|
+
mkdirSync(profilesDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
function readGlobalConfig() {
|
|
27
|
+
if (!existsSync(configPath)) {
|
|
28
|
+
return { active_profile: "default" };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
32
|
+
return {
|
|
33
|
+
active_profile: raw.active_profile ?? "default"
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return { active_profile: "default" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeGlobalConfig(data) {
|
|
40
|
+
ensureBaseDir();
|
|
41
|
+
writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n");
|
|
42
|
+
}
|
|
43
|
+
function profilePath(name) {
|
|
44
|
+
return path.join(profilesDir, `${name}.json`);
|
|
45
|
+
}
|
|
46
|
+
function readProfile(name) {
|
|
47
|
+
const file = profilePath(name);
|
|
48
|
+
if (!existsSync(file)) return null;
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function writeProfile(name, data) {
|
|
56
|
+
ensureProfilesDir();
|
|
57
|
+
writeFileSync(profilePath(name), JSON.stringify(data, null, 2) + "\n");
|
|
58
|
+
}
|
|
18
59
|
var config = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
});
|
|
60
|
+
getBaseDir: () => baseDir,
|
|
61
|
+
getProfilesDir: () => profilesDir,
|
|
62
|
+
getActiveProfile: () => readGlobalConfig().active_profile,
|
|
63
|
+
setActiveProfile: (name) => {
|
|
64
|
+
writeGlobalConfig({ active_profile: name });
|
|
26
65
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!pat) {
|
|
32
|
-
console.error(chalk.red("Not logged in. Run: npx @globio/cli login"));
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
return pat;
|
|
66
|
+
getProfile: (name) => {
|
|
67
|
+
const profileName = name ?? config.getActiveProfile();
|
|
68
|
+
if (!profileName) return null;
|
|
69
|
+
return readProfile(profileName);
|
|
36
70
|
},
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
setProfile: (name, data) => {
|
|
72
|
+
const existing = readProfile(name);
|
|
73
|
+
const next = {
|
|
74
|
+
pat: data.pat ?? existing?.pat ?? "",
|
|
75
|
+
account_email: data.account_email ?? existing?.account_email ?? "",
|
|
76
|
+
account_name: data.account_name ?? existing?.account_name ?? "",
|
|
77
|
+
active_project_id: data.active_project_id ?? existing?.active_project_id,
|
|
78
|
+
active_project_name: data.active_project_name ?? existing?.active_project_name,
|
|
79
|
+
project_api_key: data.project_api_key ?? existing?.project_api_key,
|
|
80
|
+
created_at: data.created_at ?? existing?.created_at ?? Date.now()
|
|
81
|
+
};
|
|
82
|
+
writeProfile(name, next);
|
|
46
83
|
},
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (projectName) {
|
|
52
|
-
projectNames[projectId] = projectName;
|
|
53
|
-
}
|
|
54
|
-
store.set("projectApiKeys", projectApiKeys);
|
|
55
|
-
store.set("projectNames", projectNames);
|
|
56
|
-
store.set("projectId", projectId);
|
|
57
|
-
if (projectName) {
|
|
58
|
-
store.set("projectName", projectName);
|
|
84
|
+
deleteProfile: (name) => {
|
|
85
|
+
const file = profilePath(name);
|
|
86
|
+
if (existsSync(file)) {
|
|
87
|
+
rmSync(file);
|
|
59
88
|
}
|
|
60
89
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
90
|
+
listProfiles: () => {
|
|
91
|
+
if (!existsSync(profilesDir)) return [];
|
|
92
|
+
return readdirSync(profilesDir).filter((file) => file.endsWith(".json")).map((file) => file.replace(/\.json$/, "")).sort();
|
|
64
93
|
},
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
getActiveProfileData: () => {
|
|
95
|
+
const active = config.getActiveProfile();
|
|
96
|
+
if (!active) return null;
|
|
97
|
+
return config.getProfile(active);
|
|
98
|
+
},
|
|
99
|
+
requireAuth: (profileName) => {
|
|
100
|
+
const resolvedProfile = profileName ?? config.getActiveProfile() ?? "default";
|
|
101
|
+
const profile = config.getProfile(resolvedProfile);
|
|
102
|
+
if (!profile?.pat) {
|
|
103
|
+
console.error(chalk.red("Not logged in. Run: npx @globio/cli login"));
|
|
71
104
|
process.exit(1);
|
|
72
105
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
return { pat: profile.pat, profileName: resolvedProfile };
|
|
107
|
+
},
|
|
108
|
+
requireProject: (profileName) => {
|
|
109
|
+
const resolvedProfile = profileName ?? config.getActiveProfile() ?? "default";
|
|
110
|
+
const profile = config.getProfile(resolvedProfile);
|
|
111
|
+
if (!profile?.active_project_id) {
|
|
76
112
|
console.error(
|
|
77
|
-
chalk.red(
|
|
78
|
-
"No project API key stored for the active project. Run: npx @globio/cli projects use <projectId>"
|
|
79
|
-
)
|
|
113
|
+
chalk.red("No active project. Run: npx @globio/cli projects use <projectId>")
|
|
80
114
|
);
|
|
81
115
|
process.exit(1);
|
|
82
116
|
}
|
|
83
|
-
return
|
|
117
|
+
return {
|
|
118
|
+
projectId: profile.active_project_id,
|
|
119
|
+
projectName: profile.active_project_name ?? "unnamed"
|
|
120
|
+
};
|
|
84
121
|
}
|
|
85
122
|
};
|
|
86
123
|
|
|
87
124
|
// src/lib/manage.ts
|
|
88
125
|
var API_BASE_URL = "https://api.globio.stanlink.online";
|
|
89
126
|
var CONSOLE_BASE_URL = "https://console.globio.stanlink.online";
|
|
90
|
-
function getAuthToken(explicitToken) {
|
|
127
|
+
function getAuthToken(explicitToken, profileName) {
|
|
91
128
|
if (explicitToken) return explicitToken;
|
|
92
|
-
return config.
|
|
129
|
+
return config.getProfile(profileName)?.pat;
|
|
93
130
|
}
|
|
94
|
-
async function manageRequest(
|
|
131
|
+
async function manageRequest(path2, options = {}) {
|
|
95
132
|
const headers = new Headers();
|
|
96
133
|
if (!(options.body instanceof FormData)) {
|
|
97
134
|
headers.set("Content-Type", "application/json");
|
|
98
135
|
}
|
|
99
|
-
const token = getAuthToken(options.token);
|
|
136
|
+
const token = getAuthToken(options.token, options.profileName);
|
|
100
137
|
if (token) {
|
|
101
138
|
headers.set("Authorization", `Bearer ${token}`);
|
|
102
139
|
}
|
|
103
|
-
const response = await fetch(`${API_BASE_URL}/manage${
|
|
140
|
+
const response = await fetch(`${API_BASE_URL}/manage${path2}`, {
|
|
104
141
|
method: options.method ?? "GET",
|
|
105
142
|
headers,
|
|
106
143
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
@@ -116,7 +153,7 @@ function getConsoleCliAuthUrl(state) {
|
|
|
116
153
|
}
|
|
117
154
|
|
|
118
155
|
// src/lib/banner.ts
|
|
119
|
-
import { readFileSync } from "fs";
|
|
156
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
120
157
|
import figlet from "figlet";
|
|
121
158
|
import gradientString from "gradient-string";
|
|
122
159
|
var globioGradient = gradientString(
|
|
@@ -144,7 +181,7 @@ var orange = (s) => "\x1B[38;2;244;140;6m" + s + "\x1B[0m";
|
|
|
144
181
|
var gold = (s) => "\x1B[38;2;255;208;0m" + s + "\x1B[0m";
|
|
145
182
|
var muted = (s) => "\x1B[2m" + s + "\x1B[0m";
|
|
146
183
|
function getCliVersion() {
|
|
147
|
-
const file =
|
|
184
|
+
const file = readFileSync2(new URL("../package.json", import.meta.url), "utf8");
|
|
148
185
|
return JSON.parse(file).version;
|
|
149
186
|
}
|
|
150
187
|
|
|
@@ -159,14 +196,23 @@ function sleep(ms) {
|
|
|
159
196
|
}
|
|
160
197
|
async function savePat(token) {
|
|
161
198
|
const account = await manageRequest("/account", { token });
|
|
162
|
-
config.set({
|
|
163
|
-
pat: token,
|
|
164
|
-
accountEmail: account.email,
|
|
165
|
-
accountName: account.display_name ?? account.email
|
|
166
|
-
});
|
|
167
199
|
return account;
|
|
168
200
|
}
|
|
169
|
-
|
|
201
|
+
function warnOnDuplicateAccount(accountEmail, targetProfileName) {
|
|
202
|
+
const allProfiles = config.listProfiles();
|
|
203
|
+
const duplicate = allProfiles.find((name) => {
|
|
204
|
+
const profile = config.getProfile(name);
|
|
205
|
+
return profile?.account_email === accountEmail && name !== targetProfileName;
|
|
206
|
+
});
|
|
207
|
+
if (!duplicate) return;
|
|
208
|
+
console.log("");
|
|
209
|
+
console.log(
|
|
210
|
+
chalk2.yellow(" \u26A0 ") + chalk2.white(accountEmail) + chalk2.gray(" is already logged in under profile ") + orange(`"${duplicate}"`) + chalk2.gray(".")
|
|
211
|
+
);
|
|
212
|
+
console.log("");
|
|
213
|
+
}
|
|
214
|
+
async function runTokenLogin(profileName) {
|
|
215
|
+
const hadProfiles = config.listProfiles().length > 0;
|
|
170
216
|
const token = await p.text({
|
|
171
217
|
message: "Paste your personal access token",
|
|
172
218
|
placeholder: "glo_pat_...",
|
|
@@ -184,17 +230,29 @@ async function runTokenLogin() {
|
|
|
184
230
|
spinner2.start("Validating personal access token...");
|
|
185
231
|
try {
|
|
186
232
|
const account = await savePat(token);
|
|
233
|
+
warnOnDuplicateAccount(account.email, profileName);
|
|
234
|
+
config.setProfile(profileName, {
|
|
235
|
+
pat: token,
|
|
236
|
+
account_email: account.email,
|
|
237
|
+
account_name: account.display_name ?? account.email,
|
|
238
|
+
created_at: Date.now()
|
|
239
|
+
});
|
|
240
|
+
if (profileName === "default" || !hadProfiles) {
|
|
241
|
+
config.setActiveProfile(profileName);
|
|
242
|
+
}
|
|
187
243
|
spinner2.stop("Token validated.");
|
|
188
|
-
p.outro(`Logged in as ${account.email}
|
|
244
|
+
p.outro(`Logged in as ${account.email}
|
|
245
|
+
Profile: ${profileName}`);
|
|
189
246
|
} catch (error) {
|
|
190
247
|
spinner2.stop("Validation failed.");
|
|
191
248
|
p.outro(chalk2.red(error instanceof Error ? error.message : "Could not validate token"));
|
|
192
249
|
process.exit(1);
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
|
-
async function runBrowserLogin() {
|
|
252
|
+
async function runBrowserLogin(profileName) {
|
|
196
253
|
const state = crypto.randomUUID();
|
|
197
254
|
const spinner2 = p.spinner();
|
|
255
|
+
const hadProfiles = config.listProfiles().length > 0;
|
|
198
256
|
await manageRequest("/cli-auth/request", {
|
|
199
257
|
method: "POST",
|
|
200
258
|
body: { state }
|
|
@@ -220,13 +278,19 @@ async function runBrowserLogin() {
|
|
|
220
278
|
method: "POST",
|
|
221
279
|
body: { code: status.code }
|
|
222
280
|
});
|
|
223
|
-
|
|
281
|
+
warnOnDuplicateAccount(exchange.account.email, profileName);
|
|
282
|
+
config.setProfile(profileName, {
|
|
224
283
|
pat: exchange.token,
|
|
225
|
-
|
|
226
|
-
|
|
284
|
+
account_email: exchange.account.email,
|
|
285
|
+
account_name: exchange.account.display_name ?? exchange.account.email,
|
|
286
|
+
created_at: Date.now()
|
|
227
287
|
});
|
|
288
|
+
if (profileName === "default" || !hadProfiles) {
|
|
289
|
+
config.setActiveProfile(profileName);
|
|
290
|
+
}
|
|
228
291
|
spinner2.stop("Browser approval received.");
|
|
229
|
-
p.outro(`Logged in as ${exchange.account.email}
|
|
292
|
+
p.outro(`Logged in as ${exchange.account.email}
|
|
293
|
+
Profile: ${profileName}`);
|
|
230
294
|
return;
|
|
231
295
|
}
|
|
232
296
|
} catch {
|
|
@@ -239,8 +303,20 @@ async function runBrowserLogin() {
|
|
|
239
303
|
}
|
|
240
304
|
async function login(options = {}) {
|
|
241
305
|
printBanner(version);
|
|
306
|
+
const profileName = options.profile ?? "default";
|
|
307
|
+
const existing = config.getProfile(profileName);
|
|
308
|
+
if (existing) {
|
|
309
|
+
const proceed = await p.confirm({
|
|
310
|
+
message: `Already logged in as ${existing.account_email} on profile "${profileName}". Replace?`,
|
|
311
|
+
initialValue: false
|
|
312
|
+
});
|
|
313
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
314
|
+
p.outro("Login cancelled.");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
242
318
|
if (options.token) {
|
|
243
|
-
await runTokenLogin();
|
|
319
|
+
await runTokenLogin(profileName);
|
|
244
320
|
return;
|
|
245
321
|
}
|
|
246
322
|
const choice = await p.select({
|
|
@@ -255,11 +331,11 @@ async function login(options = {}) {
|
|
|
255
331
|
process.exit(0);
|
|
256
332
|
}
|
|
257
333
|
if (choice === "token") {
|
|
258
|
-
await runTokenLogin();
|
|
334
|
+
await runTokenLogin(profileName);
|
|
259
335
|
return;
|
|
260
336
|
}
|
|
261
337
|
try {
|
|
262
|
-
await runBrowserLogin();
|
|
338
|
+
await runBrowserLogin(profileName);
|
|
263
339
|
} catch (error) {
|
|
264
340
|
p.outro(chalk2.red(error instanceof Error ? error.message : "Could not connect to Globio."));
|
|
265
341
|
process.exit(1);
|
|
@@ -267,51 +343,98 @@ async function login(options = {}) {
|
|
|
267
343
|
}
|
|
268
344
|
|
|
269
345
|
// src/auth/logout.ts
|
|
270
|
-
import * as p2 from "@clack/prompts";
|
|
271
346
|
import chalk3 from "chalk";
|
|
272
|
-
async function logout() {
|
|
273
|
-
config.
|
|
274
|
-
|
|
347
|
+
async function logout(options = {}) {
|
|
348
|
+
const activeProfile = config.getActiveProfile();
|
|
349
|
+
const profileName = options.profile ?? activeProfile;
|
|
350
|
+
const profile = profileName ? config.getProfile(profileName) : null;
|
|
351
|
+
if (!profileName || !profile) {
|
|
352
|
+
console.log(chalk3.yellow(`No active session on profile "${profileName || "default"}".`));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
config.deleteProfile(profileName);
|
|
356
|
+
if (profileName === activeProfile) {
|
|
357
|
+
const remaining = config.listProfiles();
|
|
358
|
+
if (remaining.length > 0) {
|
|
359
|
+
config.setActiveProfile(remaining[0]);
|
|
360
|
+
console.log(chalk3.green(`Logged out. Switched to profile: ${remaining[0]}`));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
config.setActiveProfile("");
|
|
364
|
+
console.log(chalk3.green("Logged out."));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.log(chalk3.green(`Logged out profile: ${profileName}`));
|
|
275
368
|
}
|
|
276
369
|
|
|
277
|
-
// src/auth/
|
|
370
|
+
// src/auth/useProfile.ts
|
|
278
371
|
import chalk4 from "chalk";
|
|
279
|
-
async function
|
|
280
|
-
const
|
|
281
|
-
if (!
|
|
282
|
-
console.log(
|
|
372
|
+
async function useProfile(profileName) {
|
|
373
|
+
const profile = config.getProfile(profileName);
|
|
374
|
+
if (!profile) {
|
|
375
|
+
console.log(
|
|
376
|
+
chalk4.red(
|
|
377
|
+
`Profile "${profileName}" not found. Run: globio login --profile ${profileName}`
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
config.setActiveProfile(profileName);
|
|
383
|
+
console.log(
|
|
384
|
+
chalk4.green("Switched to profile: ") + orange(profileName) + ` (${profile.account_email})`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/auth/whoami.ts
|
|
389
|
+
import chalk5 from "chalk";
|
|
390
|
+
async function whoami(options = {}) {
|
|
391
|
+
const profileName = options.profile ?? config.getActiveProfile() ?? "default";
|
|
392
|
+
const profile = config.getProfile(profileName);
|
|
393
|
+
if (!profile) {
|
|
394
|
+
console.log(chalk5.red("Not logged in. Run: globio login"));
|
|
283
395
|
return;
|
|
284
396
|
}
|
|
397
|
+
const allProfiles = config.listProfiles();
|
|
398
|
+
const activeProfile = config.getActiveProfile();
|
|
285
399
|
console.log("");
|
|
286
|
-
console.log(chalk4.cyan("Account: ") + (cfg.accountEmail ?? "unknown"));
|
|
287
|
-
console.log(chalk4.cyan("Name: ") + (cfg.accountName ?? "unknown"));
|
|
288
400
|
console.log(
|
|
289
|
-
|
|
401
|
+
muted("Profile: ") + orange(profileName) + (profileName === activeProfile ? muted(" (active)") : "")
|
|
290
402
|
);
|
|
403
|
+
console.log(muted("Account: ") + profile.account_email);
|
|
404
|
+
console.log(muted("Name: ") + (profile.account_name || "\u2014"));
|
|
405
|
+
console.log(
|
|
406
|
+
muted("Project: ") + (profile.active_project_id ? orange(profile.active_project_name || "unnamed") + muted(` (${profile.active_project_id})`) : chalk5.gray("none \u2014 run: globio projects use <id>"))
|
|
407
|
+
);
|
|
408
|
+
if (allProfiles.length > 1) {
|
|
409
|
+
console.log("");
|
|
410
|
+
console.log(
|
|
411
|
+
muted("Other profiles: ") + allProfiles.filter((name) => name !== profileName).join(", ")
|
|
412
|
+
);
|
|
413
|
+
}
|
|
291
414
|
console.log("");
|
|
292
415
|
}
|
|
293
416
|
|
|
294
417
|
// src/commands/init.ts
|
|
295
|
-
import * as
|
|
296
|
-
import { existsSync, readFileSync as
|
|
418
|
+
import * as p5 from "@clack/prompts";
|
|
419
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
297
420
|
|
|
298
421
|
// src/prompts/init.ts
|
|
299
|
-
import * as
|
|
422
|
+
import * as p2 from "@clack/prompts";
|
|
300
423
|
async function promptInit() {
|
|
301
|
-
return
|
|
424
|
+
return p2.group(
|
|
302
425
|
{
|
|
303
|
-
migrateFromFirebase: () =>
|
|
426
|
+
migrateFromFirebase: () => p2.confirm({
|
|
304
427
|
message: "Migrating from Firebase?",
|
|
305
428
|
initialValue: false
|
|
306
429
|
}),
|
|
307
|
-
serviceAccountPath: ({ results }) => results.migrateFromFirebase ?
|
|
430
|
+
serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p2.text({
|
|
308
431
|
message: "Path to Firebase service account JSON",
|
|
309
432
|
placeholder: "./serviceAccountKey.json"
|
|
310
433
|
}) : Promise.resolve(void 0)
|
|
311
434
|
},
|
|
312
435
|
{
|
|
313
436
|
onCancel: () => {
|
|
314
|
-
|
|
437
|
+
p2.cancel("Cancelled.");
|
|
315
438
|
process.exit(0);
|
|
316
439
|
}
|
|
317
440
|
}
|
|
@@ -319,15 +442,15 @@ async function promptInit() {
|
|
|
319
442
|
}
|
|
320
443
|
|
|
321
444
|
// src/commands/migrate.ts
|
|
322
|
-
import * as
|
|
323
|
-
import
|
|
445
|
+
import * as p3 from "@clack/prompts";
|
|
446
|
+
import chalk7 from "chalk";
|
|
324
447
|
import { basename } from "path";
|
|
325
448
|
|
|
326
449
|
// src/lib/firebase.ts
|
|
327
450
|
async function initFirebase(serviceAccountPath) {
|
|
328
451
|
const admin = await import("firebase-admin");
|
|
329
|
-
const { readFileSync:
|
|
330
|
-
const serviceAccount = JSON.parse(
|
|
452
|
+
const { readFileSync: readFileSync5 } = await import("fs");
|
|
453
|
+
const serviceAccount = JSON.parse(readFileSync5(serviceAccountPath, "utf-8"));
|
|
331
454
|
if (!admin.default.apps.length) {
|
|
332
455
|
admin.default.initializeApp({
|
|
333
456
|
credential: admin.default.credential.cert(serviceAccount),
|
|
@@ -342,12 +465,12 @@ async function initFirebase(serviceAccountPath) {
|
|
|
342
465
|
}
|
|
343
466
|
|
|
344
467
|
// src/lib/progress.ts
|
|
345
|
-
import
|
|
468
|
+
import chalk6 from "chalk";
|
|
346
469
|
import cliProgress from "cli-progress";
|
|
347
470
|
function createProgressBar(label) {
|
|
348
471
|
const bar = new cliProgress.SingleBar(
|
|
349
472
|
{
|
|
350
|
-
format:
|
|
473
|
+
format: chalk6.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
|
|
351
474
|
barCompleteChar: "\u2588",
|
|
352
475
|
barIncompleteChar: "\u2591",
|
|
353
476
|
hideCursor: true
|
|
@@ -359,31 +482,38 @@ function createProgressBar(label) {
|
|
|
359
482
|
|
|
360
483
|
// src/lib/sdk.ts
|
|
361
484
|
import { Globio } from "@globio/sdk";
|
|
362
|
-
function getClient() {
|
|
363
|
-
const
|
|
485
|
+
function getClient(profileName) {
|
|
486
|
+
const { pat } = config.requireAuth(profileName);
|
|
487
|
+
const { projectId } = config.requireProject(profileName);
|
|
488
|
+
const profile = config.getProfile(profileName);
|
|
489
|
+
const apiKey = profile?.project_api_key ?? pat;
|
|
490
|
+
void projectId;
|
|
364
491
|
return new Globio({ apiKey });
|
|
365
492
|
}
|
|
366
493
|
|
|
367
494
|
// src/commands/migrate.ts
|
|
368
495
|
var version2 = getCliVersion();
|
|
496
|
+
function resolveProfileName(profile) {
|
|
497
|
+
return profile ?? config.getActiveProfile() ?? "default";
|
|
498
|
+
}
|
|
369
499
|
async function migrateFirestore(options) {
|
|
370
500
|
printBanner(version2);
|
|
371
|
-
|
|
501
|
+
p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
372
502
|
const { firestore } = await initFirebase(options.from);
|
|
373
|
-
const client = getClient();
|
|
503
|
+
const client = getClient(resolveProfileName(options.profile));
|
|
374
504
|
let collections = [];
|
|
375
505
|
if (options.all) {
|
|
376
506
|
const snapshot = await firestore.listCollections();
|
|
377
507
|
collections = snapshot.map((collection) => collection.id);
|
|
378
508
|
console.log(
|
|
379
|
-
|
|
509
|
+
chalk7.cyan(
|
|
380
510
|
`Found ${collections.length} collections: ${collections.join(", ")}`
|
|
381
511
|
)
|
|
382
512
|
);
|
|
383
513
|
} else if (options.collection) {
|
|
384
514
|
collections = [options.collection];
|
|
385
515
|
} else {
|
|
386
|
-
console.log(
|
|
516
|
+
console.log(chalk7.red("Specify --collection <name> or --all"));
|
|
387
517
|
process.exit(1);
|
|
388
518
|
}
|
|
389
519
|
const results = {};
|
|
@@ -428,32 +558,32 @@ async function migrateFirestore(options) {
|
|
|
428
558
|
}
|
|
429
559
|
bar.stop();
|
|
430
560
|
console.log(
|
|
431
|
-
|
|
561
|
+
chalk7.green(` \u2713 ${results[collectionId].success} documents migrated`)
|
|
432
562
|
);
|
|
433
563
|
if (results[collectionId].failed > 0) {
|
|
434
|
-
console.log(
|
|
564
|
+
console.log(chalk7.red(` \u2717 ${results[collectionId].failed} failed`));
|
|
435
565
|
console.log(
|
|
436
|
-
|
|
566
|
+
chalk7.gray(
|
|
437
567
|
" Failed IDs: " + results[collectionId].failedIds.slice(0, 10).join(", ") + (results[collectionId].failedIds.length > 10 ? "..." : "")
|
|
438
568
|
)
|
|
439
569
|
);
|
|
440
570
|
}
|
|
441
571
|
}
|
|
442
572
|
console.log("");
|
|
443
|
-
|
|
573
|
+
p3.outro(
|
|
444
574
|
orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
|
|
445
575
|
);
|
|
446
576
|
}
|
|
447
577
|
async function migrateFirebaseStorage(options) {
|
|
448
578
|
printBanner(version2);
|
|
449
|
-
|
|
579
|
+
p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
|
|
450
580
|
const { storage } = await initFirebase(options.from);
|
|
451
|
-
const client = getClient();
|
|
581
|
+
const client = getClient(resolveProfileName(options.profile));
|
|
452
582
|
const bucketName = options.bucket.replace(/^gs:\/\//, "");
|
|
453
583
|
const bucket = storage.bucket(bucketName);
|
|
454
584
|
const prefix = options.folder ? options.folder.replace(/^\//, "") : "";
|
|
455
585
|
const [files] = await bucket.getFiles(prefix ? { prefix } : {});
|
|
456
|
-
console.log(
|
|
586
|
+
console.log(chalk7.cyan(`Found ${files.length} files to migrate`));
|
|
457
587
|
const bar = createProgressBar("Storage");
|
|
458
588
|
bar.start(files.length, 0);
|
|
459
589
|
let success = 0;
|
|
@@ -481,39 +611,43 @@ async function migrateFirebaseStorage(options) {
|
|
|
481
611
|
}
|
|
482
612
|
bar.stop();
|
|
483
613
|
console.log("");
|
|
484
|
-
console.log(
|
|
614
|
+
console.log(chalk7.green(` \u2713 ${success} files migrated`));
|
|
485
615
|
if (failed > 0) {
|
|
486
|
-
console.log(
|
|
616
|
+
console.log(chalk7.red(` \u2717 ${failed} failed`));
|
|
487
617
|
}
|
|
488
|
-
|
|
618
|
+
p3.outro(
|
|
489
619
|
orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
|
|
490
620
|
);
|
|
491
621
|
}
|
|
492
622
|
|
|
493
623
|
// src/commands/projects.ts
|
|
494
|
-
import * as
|
|
495
|
-
import
|
|
624
|
+
import * as p4 from "@clack/prompts";
|
|
625
|
+
import chalk8 from "chalk";
|
|
496
626
|
function slugify(value) {
|
|
497
627
|
return value.toLowerCase().trim().replace(/[^a-z0-9\\s-]/g, "").replace(/\\s+/g, "-").replace(/-+/g, "-");
|
|
498
628
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
629
|
+
function resolveProfileName2(profileName) {
|
|
630
|
+
return profileName ?? config.getActiveProfile() ?? "default";
|
|
631
|
+
}
|
|
632
|
+
async function createServerKey(projectId, profileName) {
|
|
502
633
|
const created = await manageRequest(`/projects/${projectId}/keys`, {
|
|
503
634
|
method: "POST",
|
|
504
635
|
body: {
|
|
505
636
|
name: "CLI server key",
|
|
506
637
|
scope: "server"
|
|
507
|
-
}
|
|
638
|
+
},
|
|
639
|
+
profileName
|
|
508
640
|
});
|
|
509
641
|
if (!created.token) {
|
|
510
642
|
throw new Error("Management API did not return a project API key");
|
|
511
643
|
}
|
|
512
644
|
return created.token;
|
|
513
645
|
}
|
|
514
|
-
async function projectsList() {
|
|
515
|
-
const
|
|
516
|
-
|
|
646
|
+
async function projectsList(options = {}) {
|
|
647
|
+
const profileName = resolveProfileName2(options.profile);
|
|
648
|
+
config.requireAuth(profileName);
|
|
649
|
+
const projects2 = await manageRequest("/projects", { profileName });
|
|
650
|
+
const activeProjectId = config.getProfile(profileName)?.active_project_id;
|
|
517
651
|
const grouped = /* @__PURE__ */ new Map();
|
|
518
652
|
for (const project of projects2) {
|
|
519
653
|
const list = grouped.get(project.org_name) ?? [];
|
|
@@ -522,38 +656,50 @@ async function projectsList() {
|
|
|
522
656
|
}
|
|
523
657
|
console.log("");
|
|
524
658
|
if (!projects2.length) {
|
|
525
|
-
console.log(
|
|
659
|
+
console.log(chalk8.gray("No projects found."));
|
|
526
660
|
console.log("");
|
|
527
661
|
return;
|
|
528
662
|
}
|
|
529
663
|
for (const [orgName, orgProjects] of grouped.entries()) {
|
|
530
|
-
console.log(
|
|
664
|
+
console.log(chalk8.cyan(`org: ${orgName}`));
|
|
531
665
|
for (const project of orgProjects) {
|
|
532
|
-
const marker = project.id === activeProjectId ?
|
|
533
|
-
const active = project.id === activeProjectId ?
|
|
534
|
-
console.log(` ${marker} ${project.slug.padEnd(22)} ${
|
|
666
|
+
const marker = project.id === activeProjectId ? chalk8.green("\u25CF") : chalk8.gray("\u25CB");
|
|
667
|
+
const active = project.id === activeProjectId ? chalk8.green(" (active)") : "";
|
|
668
|
+
console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk8.gray(project.id)}${active}`);
|
|
535
669
|
}
|
|
536
670
|
console.log("");
|
|
537
671
|
}
|
|
538
672
|
}
|
|
539
|
-
async function projectsUse(projectId) {
|
|
540
|
-
const
|
|
673
|
+
async function projectsUse(projectId, options = {}) {
|
|
674
|
+
const profileName = resolveProfileName2(options.profile);
|
|
675
|
+
config.requireAuth(profileName);
|
|
676
|
+
const projects2 = await manageRequest("/projects", { profileName });
|
|
541
677
|
const project = projects2.find((item) => item.id === projectId);
|
|
542
678
|
if (!project) {
|
|
543
|
-
console.log(
|
|
679
|
+
console.log(chalk8.red(`Project not found: ${projectId}`));
|
|
544
680
|
process.exit(1);
|
|
545
681
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
682
|
+
await manageRequest(`/projects/${projectId}/keys`, { profileName });
|
|
683
|
+
const apiKey = await createServerKey(projectId, profileName);
|
|
684
|
+
config.setProfile(profileName, {
|
|
685
|
+
active_project_id: project.id,
|
|
686
|
+
active_project_name: project.name,
|
|
687
|
+
project_api_key: apiKey
|
|
688
|
+
});
|
|
689
|
+
config.setActiveProfile(profileName);
|
|
690
|
+
console.log(
|
|
691
|
+
chalk8.green("Active project set to: ") + chalk8.cyan(`${project.name} (${project.id})`)
|
|
692
|
+
);
|
|
549
693
|
}
|
|
550
|
-
async function projectsCreate() {
|
|
551
|
-
const
|
|
694
|
+
async function projectsCreate(options = {}) {
|
|
695
|
+
const profileName = resolveProfileName2(options.profile);
|
|
696
|
+
config.requireAuth(profileName);
|
|
697
|
+
const orgs = await manageRequest("/orgs", { profileName });
|
|
552
698
|
if (!orgs.length) {
|
|
553
|
-
console.log(
|
|
699
|
+
console.log(chalk8.red("No organizations found. Create one in the console first."));
|
|
554
700
|
process.exit(1);
|
|
555
701
|
}
|
|
556
|
-
const orgId = await
|
|
702
|
+
const orgId = await p4.select({
|
|
557
703
|
message: "Select an organization",
|
|
558
704
|
options: orgs.map((org) => ({
|
|
559
705
|
value: org.id,
|
|
@@ -561,22 +707,22 @@ async function projectsCreate() {
|
|
|
561
707
|
hint: org.role
|
|
562
708
|
}))
|
|
563
709
|
});
|
|
564
|
-
if (
|
|
565
|
-
|
|
710
|
+
if (p4.isCancel(orgId)) {
|
|
711
|
+
p4.cancel("Project creation cancelled.");
|
|
566
712
|
process.exit(0);
|
|
567
713
|
}
|
|
568
|
-
const values = await
|
|
714
|
+
const values = await p4.group(
|
|
569
715
|
{
|
|
570
|
-
name: () =>
|
|
716
|
+
name: () => p4.text({
|
|
571
717
|
message: "Project name",
|
|
572
718
|
validate: (value) => !value ? "Project name is required" : void 0
|
|
573
719
|
}),
|
|
574
|
-
slug: ({ results }) =>
|
|
720
|
+
slug: ({ results }) => p4.text({
|
|
575
721
|
message: "Project slug",
|
|
576
722
|
initialValue: slugify(String(results.name ?? "")),
|
|
577
723
|
validate: (value) => !value ? "Project slug is required" : void 0
|
|
578
724
|
}),
|
|
579
|
-
environment: () =>
|
|
725
|
+
environment: () => p4.select({
|
|
580
726
|
message: "Environment",
|
|
581
727
|
options: [
|
|
582
728
|
{ value: "development", label: "development" },
|
|
@@ -587,7 +733,7 @@ async function projectsCreate() {
|
|
|
587
733
|
},
|
|
588
734
|
{
|
|
589
735
|
onCancel: () => {
|
|
590
|
-
|
|
736
|
+
p4.cancel("Project creation cancelled.");
|
|
591
737
|
process.exit(0);
|
|
592
738
|
}
|
|
593
739
|
}
|
|
@@ -599,33 +745,49 @@ async function projectsCreate() {
|
|
|
599
745
|
name: values.name,
|
|
600
746
|
slug: values.slug,
|
|
601
747
|
environment: values.environment
|
|
602
|
-
}
|
|
748
|
+
},
|
|
749
|
+
profileName
|
|
603
750
|
});
|
|
604
|
-
config.
|
|
751
|
+
config.setProfile(profileName, {
|
|
752
|
+
active_project_id: result.project.id,
|
|
753
|
+
active_project_name: result.project.name,
|
|
754
|
+
project_api_key: result.keys.server
|
|
755
|
+
});
|
|
756
|
+
config.setActiveProfile(profileName);
|
|
605
757
|
console.log("");
|
|
606
|
-
console.log(
|
|
607
|
-
console.log(
|
|
608
|
-
console.log(
|
|
609
|
-
console.log(
|
|
758
|
+
console.log(chalk8.green("Project created successfully."));
|
|
759
|
+
console.log(chalk8.cyan("Project: ") + `${result.project.name} (${result.project.id})`);
|
|
760
|
+
console.log(chalk8.cyan("Client key: ") + result.keys.client);
|
|
761
|
+
console.log(chalk8.cyan("Server key: ") + result.keys.server);
|
|
610
762
|
console.log("");
|
|
611
763
|
}
|
|
612
764
|
|
|
613
765
|
// src/commands/init.ts
|
|
614
766
|
var version3 = getCliVersion();
|
|
615
|
-
async function init() {
|
|
767
|
+
async function init(options = {}) {
|
|
616
768
|
printBanner(version3);
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
769
|
+
p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
|
|
770
|
+
const profileName = options.profile ?? config.getActiveProfile() ?? "default";
|
|
771
|
+
const profile = config.getProfile(profileName);
|
|
772
|
+
if (!profile) {
|
|
773
|
+
console.log("Run: npx @globio/cli login --profile " + profileName);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
if (!profile.active_project_id) {
|
|
777
|
+
await projectsCreate({ profile: profileName });
|
|
621
778
|
} else {
|
|
622
|
-
await projectsUse(
|
|
779
|
+
await projectsUse(profile.active_project_id, { profile: profileName });
|
|
623
780
|
}
|
|
624
781
|
const values = await promptInit();
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
782
|
+
const activeProfile = config.getProfile(profileName);
|
|
783
|
+
const activeProjectKey = activeProfile?.project_api_key;
|
|
784
|
+
const { projectId: activeProjectId } = config.requireProject(profileName);
|
|
785
|
+
if (!activeProjectKey) {
|
|
786
|
+
console.log("No project API key cached. Run: npx @globio/cli projects use " + activeProjectId);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
if (!existsSync2("globio.config.ts")) {
|
|
790
|
+
writeFileSync2(
|
|
629
791
|
"globio.config.ts",
|
|
630
792
|
`import { Globio } from '@globio/sdk';
|
|
631
793
|
|
|
@@ -636,8 +798,8 @@ export const globio = new Globio({
|
|
|
636
798
|
);
|
|
637
799
|
printSuccess("Created globio.config.ts");
|
|
638
800
|
}
|
|
639
|
-
if (!
|
|
640
|
-
|
|
801
|
+
if (!existsSync2(".env")) {
|
|
802
|
+
writeFileSync2(".env", `GLOBIO_API_KEY=${activeProjectKey}
|
|
641
803
|
`);
|
|
642
804
|
printSuccess("Created .env");
|
|
643
805
|
}
|
|
@@ -646,19 +808,21 @@ export const globio = new Globio({
|
|
|
646
808
|
printSuccess("Starting Firebase migration...");
|
|
647
809
|
await migrateFirestore({
|
|
648
810
|
from: values.serviceAccountPath,
|
|
649
|
-
all: true
|
|
811
|
+
all: true,
|
|
812
|
+
profile: profileName
|
|
650
813
|
});
|
|
651
814
|
const serviceAccount = JSON.parse(
|
|
652
|
-
|
|
815
|
+
readFileSync3(values.serviceAccountPath, "utf-8")
|
|
653
816
|
);
|
|
654
817
|
await migrateFirebaseStorage({
|
|
655
818
|
from: values.serviceAccountPath,
|
|
656
819
|
bucket: `${serviceAccount.project_id}.appspot.com`,
|
|
657
|
-
all: true
|
|
820
|
+
all: true,
|
|
821
|
+
profile: profileName
|
|
658
822
|
});
|
|
659
823
|
}
|
|
660
824
|
console.log("");
|
|
661
|
-
|
|
825
|
+
p5.outro(
|
|
662
826
|
orange("\u21D2\u21D2") + " Your project is ready.\n\n " + muted("Next steps:") + `
|
|
663
827
|
|
|
664
828
|
npm install @globio/sdk
|
|
@@ -667,8 +831,30 @@ export const globio = new Globio({
|
|
|
667
831
|
);
|
|
668
832
|
}
|
|
669
833
|
|
|
834
|
+
// src/commands/profiles.ts
|
|
835
|
+
import chalk9 from "chalk";
|
|
836
|
+
async function profilesList() {
|
|
837
|
+
const profiles2 = config.listProfiles();
|
|
838
|
+
const active = config.getActiveProfile();
|
|
839
|
+
if (!profiles2.length) {
|
|
840
|
+
console.log(chalk9.gray("No profiles found. Run: globio login"));
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
console.log("");
|
|
844
|
+
for (const name of profiles2) {
|
|
845
|
+
const data = config.getProfile(name);
|
|
846
|
+
const isActive = name === active;
|
|
847
|
+
const bullet = isActive ? orange("\u25CF") : chalk9.gray("\u25CB");
|
|
848
|
+
const label = isActive ? orange(name) : chalk9.white(name);
|
|
849
|
+
const email = data?.account_email ? muted(data.account_email) : chalk9.gray("unknown");
|
|
850
|
+
const tag = isActive ? muted(" (active)") : "";
|
|
851
|
+
console.log(` ${bullet} ${label} ${email}${tag}`);
|
|
852
|
+
}
|
|
853
|
+
console.log("");
|
|
854
|
+
}
|
|
855
|
+
|
|
670
856
|
// src/commands/services.ts
|
|
671
|
-
import
|
|
857
|
+
import chalk10 from "chalk";
|
|
672
858
|
var ALL_SERVICES = [
|
|
673
859
|
"id",
|
|
674
860
|
"doc",
|
|
@@ -681,30 +867,36 @@ var ALL_SERVICES = [
|
|
|
681
867
|
"brain",
|
|
682
868
|
"code"
|
|
683
869
|
];
|
|
684
|
-
async function servicesList() {
|
|
870
|
+
async function servicesList(options = {}) {
|
|
871
|
+
void options.profile;
|
|
872
|
+
void config;
|
|
685
873
|
console.log("");
|
|
686
|
-
console.log(
|
|
874
|
+
console.log(chalk10.cyan("Available Globio services:"));
|
|
687
875
|
ALL_SERVICES.forEach((service) => {
|
|
688
|
-
console.log(" " +
|
|
876
|
+
console.log(" " + chalk10.white(service));
|
|
689
877
|
});
|
|
690
878
|
console.log("");
|
|
691
879
|
console.log(
|
|
692
|
-
|
|
880
|
+
chalk10.gray("Manage service access via console.globio.stanlink.online")
|
|
693
881
|
);
|
|
694
882
|
console.log("");
|
|
695
883
|
}
|
|
696
884
|
|
|
697
885
|
// src/commands/functions.ts
|
|
698
|
-
import
|
|
886
|
+
import chalk11 from "chalk";
|
|
699
887
|
import ora from "ora";
|
|
700
|
-
import { existsSync as
|
|
701
|
-
|
|
702
|
-
|
|
888
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
889
|
+
function resolveProfileName3(profile) {
|
|
890
|
+
return profile ?? config.getActiveProfile() ?? "default";
|
|
891
|
+
}
|
|
892
|
+
async function functionsList(options = {}) {
|
|
893
|
+
const profileName = resolveProfileName3(options.profile);
|
|
894
|
+
const client = getClient(profileName);
|
|
703
895
|
const spinner2 = ora("Fetching functions...").start();
|
|
704
896
|
const result = await client.code.listFunctions();
|
|
705
897
|
spinner2.stop();
|
|
706
898
|
if (!result.success || !result.data.length) {
|
|
707
|
-
console.log(
|
|
899
|
+
console.log(chalk11.gray("No functions found."));
|
|
708
900
|
return;
|
|
709
901
|
}
|
|
710
902
|
console.log("");
|
|
@@ -718,10 +910,10 @@ async function functionsList() {
|
|
|
718
910
|
});
|
|
719
911
|
console.log("");
|
|
720
912
|
}
|
|
721
|
-
async function functionsCreate(slug) {
|
|
913
|
+
async function functionsCreate(slug, _options = {}) {
|
|
722
914
|
const filename = `${slug}.js`;
|
|
723
|
-
if (
|
|
724
|
-
console.log(
|
|
915
|
+
if (existsSync3(filename)) {
|
|
916
|
+
console.log(chalk11.yellow(`${filename} already exists.`));
|
|
725
917
|
return;
|
|
726
918
|
}
|
|
727
919
|
const template = `/**
|
|
@@ -739,24 +931,25 @@ async function handler(input, globio) {
|
|
|
739
931
|
};
|
|
740
932
|
}
|
|
741
933
|
`;
|
|
742
|
-
|
|
743
|
-
console.log(
|
|
934
|
+
writeFileSync3(filename, template);
|
|
935
|
+
console.log(chalk11.green(`Created ${filename}`));
|
|
744
936
|
console.log(
|
|
745
|
-
|
|
937
|
+
chalk11.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
|
|
746
938
|
);
|
|
747
939
|
}
|
|
748
940
|
async function functionsDeploy(slug, options) {
|
|
749
941
|
const filename = options.file ?? `${slug}.js`;
|
|
750
|
-
if (!
|
|
942
|
+
if (!existsSync3(filename)) {
|
|
751
943
|
console.log(
|
|
752
|
-
|
|
944
|
+
chalk11.red(
|
|
753
945
|
`File not found: ${filename}. Create it with: npx @globio/cli functions create ${slug}`
|
|
754
946
|
)
|
|
755
947
|
);
|
|
756
948
|
process.exit(1);
|
|
757
949
|
}
|
|
758
|
-
const code =
|
|
759
|
-
const
|
|
950
|
+
const code = readFileSync4(filename, "utf-8");
|
|
951
|
+
const profileName = resolveProfileName3(options.profile);
|
|
952
|
+
const client = getClient(profileName);
|
|
760
953
|
const spinner2 = ora(`Deploying ${slug}...`).start();
|
|
761
954
|
const existing = await client.code.getFunction(slug);
|
|
762
955
|
let result;
|
|
@@ -786,16 +979,17 @@ async function functionsInvoke(slug, options) {
|
|
|
786
979
|
try {
|
|
787
980
|
input = JSON.parse(options.input);
|
|
788
981
|
} catch {
|
|
789
|
-
console.error(
|
|
982
|
+
console.error(chalk11.red("--input must be valid JSON"));
|
|
790
983
|
process.exit(1);
|
|
791
984
|
}
|
|
792
985
|
}
|
|
793
|
-
const
|
|
986
|
+
const profileName = resolveProfileName3(options.profile);
|
|
987
|
+
const client = getClient(profileName);
|
|
794
988
|
const spinner2 = ora(`Invoking ${slug}...`).start();
|
|
795
989
|
const result = await client.code.invoke(slug, input);
|
|
796
990
|
spinner2.stop();
|
|
797
991
|
if (!result.success) {
|
|
798
|
-
console.log(
|
|
992
|
+
console.log(chalk11.red("Invocation failed"));
|
|
799
993
|
console.error(result.error.message);
|
|
800
994
|
return;
|
|
801
995
|
}
|
|
@@ -807,12 +1001,13 @@ Duration: ${result.data.duration_ms}ms`));
|
|
|
807
1001
|
}
|
|
808
1002
|
async function functionsLogs(slug, options) {
|
|
809
1003
|
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
810
|
-
const
|
|
1004
|
+
const profileName = resolveProfileName3(options.profile);
|
|
1005
|
+
const client = getClient(profileName);
|
|
811
1006
|
const spinner2 = ora("Fetching invocations...").start();
|
|
812
1007
|
const result = await client.code.getInvocations(slug, limit);
|
|
813
1008
|
spinner2.stop();
|
|
814
1009
|
if (!result.success || !result.data.length) {
|
|
815
|
-
console.log(
|
|
1010
|
+
console.log(chalk11.gray("No invocations yet."));
|
|
816
1011
|
return;
|
|
817
1012
|
}
|
|
818
1013
|
console.log("");
|
|
@@ -820,13 +1015,14 @@ async function functionsLogs(slug, options) {
|
|
|
820
1015
|
const status = inv.success ? "\x1B[38;2;244;140;6m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
|
|
821
1016
|
const date = new Date(inv.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
822
1017
|
console.log(
|
|
823
|
-
` ${status} ${
|
|
1018
|
+
` ${status} ${chalk11.gray(date)} ${inv.duration_ms}ms ${chalk11.gray(`[${inv.trigger_type}]`)}`
|
|
824
1019
|
);
|
|
825
1020
|
});
|
|
826
1021
|
console.log("");
|
|
827
1022
|
}
|
|
828
|
-
async function functionsDelete(slug) {
|
|
829
|
-
const
|
|
1023
|
+
async function functionsDelete(slug, options = {}) {
|
|
1024
|
+
const profileName = resolveProfileName3(options.profile);
|
|
1025
|
+
const client = getClient(profileName);
|
|
830
1026
|
const spinner2 = ora(`Deleting ${slug}...`).start();
|
|
831
1027
|
const result = await client.code.deleteFunction(slug);
|
|
832
1028
|
if (!result.success) {
|
|
@@ -836,8 +1032,9 @@ async function functionsDelete(slug) {
|
|
|
836
1032
|
}
|
|
837
1033
|
spinner2.succeed(`Deleted ${slug}`);
|
|
838
1034
|
}
|
|
839
|
-
async function functionsToggle(slug, active) {
|
|
840
|
-
const
|
|
1035
|
+
async function functionsToggle(slug, active, options = {}) {
|
|
1036
|
+
const profileName = resolveProfileName3(options.profile);
|
|
1037
|
+
const client = getClient(profileName);
|
|
841
1038
|
const spinner2 = ora(
|
|
842
1039
|
`${active ? "Enabling" : "Disabling"} ${slug}...`
|
|
843
1040
|
).start();
|
|
@@ -856,28 +1053,45 @@ var program = new Command();
|
|
|
856
1053
|
program.name("globio").description("The official Globio CLI").version(version4).addHelpText("beforeAll", () => {
|
|
857
1054
|
printBanner(version4);
|
|
858
1055
|
return "";
|
|
859
|
-
})
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1056
|
+
}).addHelpText(
|
|
1057
|
+
"after",
|
|
1058
|
+
`
|
|
1059
|
+
Examples:
|
|
1060
|
+
$ globio login
|
|
1061
|
+
$ globio login --profile work
|
|
1062
|
+
$ globio use work
|
|
1063
|
+
$ globio projects list
|
|
1064
|
+
$ globio projects use proj_abc123
|
|
1065
|
+
$ globio functions deploy my-function
|
|
1066
|
+
$ globio migrate firestore --from ./key.json --all
|
|
1067
|
+
|
|
1068
|
+
Credentials are stored in ~/.globio/profiles/
|
|
1069
|
+
`
|
|
1070
|
+
);
|
|
1071
|
+
program.command("login").description("Log in to your Globio account").option("-p, --profile <name>", "Profile name", "default").option("--token", "Use a personal access token").action(login);
|
|
1072
|
+
program.command("logout").description("Log out").option("--profile <name>", "Use a specific profile").action(logout);
|
|
1073
|
+
program.command("whoami").description("Show current account and project").option("--profile <name>", "Use a specific profile").action(whoami);
|
|
1074
|
+
program.command("use <profile>").description("Switch active profile").action(useProfile);
|
|
1075
|
+
program.command("init").description("Initialize a Globio project").option("--profile <name>", "Use a specific profile").action(init);
|
|
1076
|
+
var profiles = program.command("profiles").description("Manage login profiles").action(profilesList);
|
|
1077
|
+
profiles.command("list").description("List all profiles").action(profilesList);
|
|
864
1078
|
var projects = program.command("projects").description("Manage projects");
|
|
865
|
-
projects.command("list").description("List projects").action(projectsList);
|
|
866
|
-
projects.command("create").description("Create a project").action(projectsCreate);
|
|
867
|
-
projects.command("use <projectId>").description("Set active project").action(projectsUse);
|
|
868
|
-
program.command("services").description("List available Globio services").action(servicesList);
|
|
1079
|
+
projects.command("list").description("List projects").option("--profile <name>", "Use a specific profile").action(projectsList);
|
|
1080
|
+
projects.command("create").description("Create a project").option("--profile <name>", "Use a specific profile").action(projectsCreate);
|
|
1081
|
+
projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").action(projectsUse);
|
|
1082
|
+
program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").action(servicesList);
|
|
869
1083
|
var functions = program.command("functions").alias("fn").description("Manage GlobalCode edge functions");
|
|
870
|
-
functions.command("list").description("List all functions").action(functionsList);
|
|
871
|
-
functions.command("create <slug>").description("Scaffold a new function file locally").action(functionsCreate);
|
|
872
|
-
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);
|
|
873
|
-
functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").action(functionsInvoke);
|
|
874
|
-
functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").action(functionsLogs);
|
|
875
|
-
functions.command("delete <slug>").description("Delete a function").action(functionsDelete);
|
|
876
|
-
functions.command("enable <slug>").description("Enable a function").action((slug) => functionsToggle(slug, true));
|
|
877
|
-
functions.command("disable <slug>").description("Disable a function").action((slug) => functionsToggle(slug, false));
|
|
1084
|
+
functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").action(functionsList);
|
|
1085
|
+
functions.command("create <slug>").description("Scaffold a new function file locally").option("--profile <name>", "Use a specific profile").action(functionsCreate);
|
|
1086
|
+
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);
|
|
1087
|
+
functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").option("--profile <name>", "Use a specific profile").action(functionsInvoke);
|
|
1088
|
+
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);
|
|
1089
|
+
functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").action(functionsDelete);
|
|
1090
|
+
functions.command("enable <slug>").description("Enable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, true, options));
|
|
1091
|
+
functions.command("disable <slug>").description("Disable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, false, options));
|
|
878
1092
|
var migrate = program.command("migrate").description("Migrate from Firebase to Globio");
|
|
879
|
-
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);
|
|
880
|
-
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);
|
|
1093
|
+
migrate.command("firestore").description("Migrate Firestore collections to GlobalDoc").requiredOption("--from <path>", "Path to Firebase service account JSON").option("--collection <name>", "Migrate a specific collection").option("--all", "Migrate all collections").option("--profile <name>", "Use a specific profile").action(migrateFirestore);
|
|
1094
|
+
migrate.command("firebase-storage").description("Migrate Firebase Storage to GlobalVault").requiredOption("--from <path>", "Path to Firebase service account JSON").requiredOption("--bucket <name>", "Firebase Storage bucket").option("--folder <path>", "Migrate a specific folder").option("--all", "Migrate all files").option("--profile <name>", "Use a specific profile").action(migrateFirebaseStorage);
|
|
881
1095
|
async function main() {
|
|
882
1096
|
if (process.argv.length <= 2) {
|
|
883
1097
|
program.help();
|