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