@globio/cli 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +59 -0
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/index.js +643 -1
- package/jsr.json +6 -0
- package/package.json +30 -2
- package/src/auth/login.ts +72 -0
- package/src/auth/logout.ts +8 -0
- package/src/auth/whoami.ts +15 -0
- package/src/commands/functions.ts +190 -0
- package/src/commands/init.ts +75 -0
- package/src/commands/migrate.ts +199 -0
- package/src/commands/projects.ts +16 -0
- package/src/commands/services.ts +27 -0
- package/src/index.ts +102 -0
- package/src/lib/banner.ts +51 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/firebase.ts +19 -0
- package/src/lib/progress.ts +17 -0
- package/src/lib/sdk.ts +13 -0
- package/src/prompts/init.ts +38 -0
- package/src/prompts/migrate.ts +8 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { config } from '../lib/config.js';
|
|
4
|
+
import { getCliVersion, muted, orange, printBanner } from '../lib/banner.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BASE_URL = 'https://api.globio.stanlink.online';
|
|
7
|
+
const version = getCliVersion();
|
|
8
|
+
|
|
9
|
+
export async function login() {
|
|
10
|
+
printBanner(version);
|
|
11
|
+
|
|
12
|
+
const values = await p.group(
|
|
13
|
+
{
|
|
14
|
+
apiKey: () =>
|
|
15
|
+
p.text({
|
|
16
|
+
message: 'Paste your Globio API key',
|
|
17
|
+
placeholder: 'gk_live_...',
|
|
18
|
+
validate: (value) => (!value ? 'API key is required' : undefined),
|
|
19
|
+
}),
|
|
20
|
+
projectId: () =>
|
|
21
|
+
p.text({
|
|
22
|
+
message: 'Paste your Project ID',
|
|
23
|
+
placeholder: 'proj_...',
|
|
24
|
+
validate: (value) => (!value ? 'Project ID is required' : undefined),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
onCancel: () => {
|
|
29
|
+
p.cancel('Login cancelled.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const spinner = p.spinner();
|
|
36
|
+
spinner.start('Validating credentials...');
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(`${DEFAULT_BASE_URL}/id/health`, {
|
|
40
|
+
headers: {
|
|
41
|
+
'X-Globio-Key': values.apiKey as string,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
spinner.stop('Validation failed.');
|
|
47
|
+
p.outro(chalk.red('Invalid API key or project ID.'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
config.set({
|
|
52
|
+
apiKey: values.apiKey as string,
|
|
53
|
+
projectId: values.projectId as string,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
spinner.stop('Credentials validated.');
|
|
57
|
+
p.outro(
|
|
58
|
+
' Logged in.\n\n' +
|
|
59
|
+
' ' +
|
|
60
|
+
muted('API Key: ') +
|
|
61
|
+
orange(values.apiKey as string) +
|
|
62
|
+
'\n' +
|
|
63
|
+
' ' +
|
|
64
|
+
muted('Project: ') +
|
|
65
|
+
orange(values.projectId as string)
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
spinner.stop('');
|
|
69
|
+
p.outro(chalk.red('Could not connect to Globio. Check your credentials.'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { config } from '../lib/config.js';
|
|
3
|
+
|
|
4
|
+
export async function whoami() {
|
|
5
|
+
const cfg = config.get();
|
|
6
|
+
if (!cfg.apiKey) {
|
|
7
|
+
console.log(chalk.red('Not logged in.'));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(chalk.cyan('API Key: ') + cfg.apiKey);
|
|
13
|
+
console.log(chalk.cyan('Project: ') + (cfg.projectId ?? 'none'));
|
|
14
|
+
console.log('');
|
|
15
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import type { CodeFunction, CodeInvocation } from '@globio/sdk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
+
import { gold, muted, orange } from '../lib/banner.js';
|
|
6
|
+
import { getClient } from '../lib/sdk.js';
|
|
7
|
+
|
|
8
|
+
export async function functionsList() {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const spinner = ora('Fetching functions...').start();
|
|
11
|
+
const result = await client.code.listFunctions();
|
|
12
|
+
spinner.stop();
|
|
13
|
+
|
|
14
|
+
if (!result.success || !result.data.length) {
|
|
15
|
+
console.log(chalk.gray('No functions found.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log('');
|
|
20
|
+
result.data.forEach((fn: CodeFunction) => {
|
|
21
|
+
const status = fn.active ? '\x1b[32m●\x1b[0m' : '\x1b[2m○\x1b[0m';
|
|
22
|
+
const type =
|
|
23
|
+
fn.type === 'hook' ? gold('[hook]') : orange('[function]');
|
|
24
|
+
console.log(' ' + status + ' ' + type + ' ' + fn.slug);
|
|
25
|
+
if (fn.type === 'hook' && fn.trigger_event) {
|
|
26
|
+
console.log(muted(' trigger: ' + fn.trigger_event));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
console.log('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function functionsCreate(slug: string) {
|
|
33
|
+
const filename = `${slug}.js`;
|
|
34
|
+
if (existsSync(filename)) {
|
|
35
|
+
console.log(chalk.yellow(`${filename} already exists.`));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const template = `/**
|
|
40
|
+
* Globio Edge Function: ${slug}
|
|
41
|
+
* Invoke: npx @globio/cli functions invoke ${slug} --input '{"key":"value"}'
|
|
42
|
+
*/
|
|
43
|
+
async function handler(input, globio) {
|
|
44
|
+
// input: the payload from the caller
|
|
45
|
+
// globio: injected SDK — access all Globio services
|
|
46
|
+
// Example: const player = await globio.doc.get('players', input.userId);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
received: input,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
writeFileSync(filename, template);
|
|
55
|
+
console.log(chalk.green(`Created ${filename}`));
|
|
56
|
+
console.log(
|
|
57
|
+
chalk.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function functionsDeploy(
|
|
62
|
+
slug: string,
|
|
63
|
+
options: { file?: string; name?: string }
|
|
64
|
+
) {
|
|
65
|
+
const filename = options.file ?? `${slug}.js`;
|
|
66
|
+
if (!existsSync(filename)) {
|
|
67
|
+
console.log(
|
|
68
|
+
chalk.red(
|
|
69
|
+
`File not found: ${filename}. Create it with: npx @globio/cli functions create ${slug}`
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const code = readFileSync(filename, 'utf-8');
|
|
76
|
+
const client = getClient();
|
|
77
|
+
const spinner = ora(`Deploying ${slug}...`).start();
|
|
78
|
+
const existing = await client.code.getFunction(slug);
|
|
79
|
+
|
|
80
|
+
let result;
|
|
81
|
+
if (existing.success) {
|
|
82
|
+
result = await client.code.updateFunction(slug, {
|
|
83
|
+
code,
|
|
84
|
+
name: options.name ?? slug,
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
result = await client.code.createFunction({
|
|
88
|
+
name: options.name ?? slug,
|
|
89
|
+
slug,
|
|
90
|
+
type: 'function',
|
|
91
|
+
code,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
spinner.fail('Deploy failed');
|
|
97
|
+
console.error(result.error.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
spinner.succeed(existing.success ? `Updated ${slug}` : `Deployed ${slug}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function functionsInvoke(
|
|
105
|
+
slug: string,
|
|
106
|
+
options: { input?: string }
|
|
107
|
+
) {
|
|
108
|
+
let input: Record<string, unknown> = {};
|
|
109
|
+
if (options.input) {
|
|
110
|
+
try {
|
|
111
|
+
input = JSON.parse(options.input) as Record<string, unknown>;
|
|
112
|
+
} catch {
|
|
113
|
+
console.error(chalk.red('--input must be valid JSON'));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const client = getClient();
|
|
119
|
+
const spinner = ora(`Invoking ${slug}...`).start();
|
|
120
|
+
const result = await client.code.invoke(slug, input);
|
|
121
|
+
spinner.stop();
|
|
122
|
+
|
|
123
|
+
if (!result.success) {
|
|
124
|
+
console.log(chalk.red('Invocation failed'));
|
|
125
|
+
console.error(result.error.message);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log(orange('Result:'));
|
|
131
|
+
console.log(JSON.stringify(result.data.result, null, 2));
|
|
132
|
+
console.log(muted(`\nDuration: ${result.data.duration_ms}ms`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function functionsLogs(
|
|
136
|
+
slug: string,
|
|
137
|
+
options: { limit?: string }
|
|
138
|
+
) {
|
|
139
|
+
const limit = options.limit ? parseInt(options.limit, 10) : 20;
|
|
140
|
+
const client = getClient();
|
|
141
|
+
const spinner = ora('Fetching invocations...').start();
|
|
142
|
+
const result = await client.code.getInvocations(slug, limit);
|
|
143
|
+
spinner.stop();
|
|
144
|
+
|
|
145
|
+
if (!result.success || !result.data.length) {
|
|
146
|
+
console.log(chalk.gray('No invocations yet.'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
result.data.forEach((inv: CodeInvocation) => {
|
|
152
|
+
const status = inv.success
|
|
153
|
+
? '\x1b[38;2;244;140;6m✓\x1b[0m'
|
|
154
|
+
: '\x1b[31m✗\x1b[0m';
|
|
155
|
+
const date = new Date(inv.invoked_at * 1000)
|
|
156
|
+
.toISOString()
|
|
157
|
+
.replace('T', ' ')
|
|
158
|
+
.slice(0, 19);
|
|
159
|
+
console.log(
|
|
160
|
+
` ${status} ${chalk.gray(date)} ${inv.duration_ms}ms ${chalk.gray(`[${inv.trigger_type}]`)}`
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
console.log('');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function functionsDelete(slug: string) {
|
|
167
|
+
const client = getClient();
|
|
168
|
+
const spinner = ora(`Deleting ${slug}...`).start();
|
|
169
|
+
const result = await client.code.deleteFunction(slug);
|
|
170
|
+
if (!result.success) {
|
|
171
|
+
spinner.fail(`Delete failed for ${slug}`);
|
|
172
|
+
console.error(result.error.message);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
spinner.succeed(`Deleted ${slug}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function functionsToggle(slug: string, active: boolean) {
|
|
179
|
+
const client = getClient();
|
|
180
|
+
const spinner = ora(
|
|
181
|
+
`${active ? 'Enabling' : 'Disabling'} ${slug}...`
|
|
182
|
+
).start();
|
|
183
|
+
const result = await client.code.toggleFunction(slug, active);
|
|
184
|
+
if (!result.success) {
|
|
185
|
+
spinner.fail(`Toggle failed for ${slug}`);
|
|
186
|
+
console.error(result.error.message);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
spinner.succeed(`${slug} is now ${active ? 'active' : 'inactive'}`);
|
|
190
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { config } from '../lib/config.js';
|
|
4
|
+
import {
|
|
5
|
+
getCliVersion,
|
|
6
|
+
muted,
|
|
7
|
+
orange,
|
|
8
|
+
printSuccess,
|
|
9
|
+
printBanner,
|
|
10
|
+
} from '../lib/banner.js';
|
|
11
|
+
import { promptInit } from '../prompts/init.js';
|
|
12
|
+
import { migrateFirestore, migrateFirebaseStorage } from './migrate.js';
|
|
13
|
+
|
|
14
|
+
const version = getCliVersion();
|
|
15
|
+
|
|
16
|
+
export async function init() {
|
|
17
|
+
printBanner(version);
|
|
18
|
+
p.intro(orange('⇒⇒') + ' Initialize your Globio project');
|
|
19
|
+
|
|
20
|
+
const values = await promptInit();
|
|
21
|
+
|
|
22
|
+
config.set({
|
|
23
|
+
apiKey: values.apiKey as string,
|
|
24
|
+
projectId: values.projectId as string,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!existsSync('globio.config.ts')) {
|
|
28
|
+
writeFileSync(
|
|
29
|
+
'globio.config.ts',
|
|
30
|
+
`import { GlobioClient } from '@globio/sdk';
|
|
31
|
+
|
|
32
|
+
export const globio = new GlobioClient({
|
|
33
|
+
apiKey: process.env.GLOBIO_API_KEY!,
|
|
34
|
+
});
|
|
35
|
+
`
|
|
36
|
+
);
|
|
37
|
+
printSuccess('Created globio.config.ts');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!existsSync('.env')) {
|
|
41
|
+
writeFileSync('.env', `GLOBIO_API_KEY=${values.apiKey}\n`);
|
|
42
|
+
printSuccess('Created .env');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (values.migrateFromFirebase && values.serviceAccountPath) {
|
|
46
|
+
console.log('');
|
|
47
|
+
printSuccess('Starting Firebase migration...');
|
|
48
|
+
|
|
49
|
+
await migrateFirestore({
|
|
50
|
+
from: values.serviceAccountPath as string,
|
|
51
|
+
all: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const serviceAccount = JSON.parse(
|
|
55
|
+
readFileSync(values.serviceAccountPath as string, 'utf-8')
|
|
56
|
+
) as { project_id: string };
|
|
57
|
+
|
|
58
|
+
await migrateFirebaseStorage({
|
|
59
|
+
from: values.serviceAccountPath as string,
|
|
60
|
+
bucket: `${serviceAccount.project_id}.appspot.com`,
|
|
61
|
+
all: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('');
|
|
66
|
+
p.outro(
|
|
67
|
+
orange('⇒⇒') +
|
|
68
|
+
' Your project is ready.\n\n' +
|
|
69
|
+
' ' +
|
|
70
|
+
muted('Next steps:') +
|
|
71
|
+
'\n\n' +
|
|
72
|
+
' npm install @globio/sdk\n' +
|
|
73
|
+
' npx @globio/cli functions create my-first-function'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { basename } from 'path';
|
|
4
|
+
import {
|
|
5
|
+
getCliVersion,
|
|
6
|
+
gold,
|
|
7
|
+
muted,
|
|
8
|
+
orange,
|
|
9
|
+
printBanner,
|
|
10
|
+
} from '../lib/banner.js';
|
|
11
|
+
import { initFirebase } from '../lib/firebase.js';
|
|
12
|
+
import { createProgressBar } from '../lib/progress.js';
|
|
13
|
+
import { getClient } from '../lib/sdk.js';
|
|
14
|
+
|
|
15
|
+
const version = getCliVersion();
|
|
16
|
+
|
|
17
|
+
interface MigrateFirestoreOptions {
|
|
18
|
+
from: string;
|
|
19
|
+
collection?: string;
|
|
20
|
+
all?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface MigrateStorageOptions {
|
|
24
|
+
from: string;
|
|
25
|
+
bucket: string;
|
|
26
|
+
folder?: string;
|
|
27
|
+
all?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
31
|
+
printBanner(version);
|
|
32
|
+
p.intro(gold('⇒⇒') + ' Firebase → Globio Migration');
|
|
33
|
+
|
|
34
|
+
const { firestore } = await initFirebase(options.from);
|
|
35
|
+
const client = getClient();
|
|
36
|
+
|
|
37
|
+
let collections: string[] = [];
|
|
38
|
+
|
|
39
|
+
if (options.all) {
|
|
40
|
+
const snapshot = await firestore.listCollections();
|
|
41
|
+
collections = snapshot.map((collection) => collection.id);
|
|
42
|
+
console.log(
|
|
43
|
+
chalk.cyan(
|
|
44
|
+
`Found ${collections.length} collections: ${collections.join(', ')}`
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
} else if (options.collection) {
|
|
48
|
+
collections = [options.collection];
|
|
49
|
+
} else {
|
|
50
|
+
console.log(chalk.red('Specify --collection <name> or --all'));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const results: Record<
|
|
55
|
+
string,
|
|
56
|
+
{ success: number; failed: number; failedIds: string[] }
|
|
57
|
+
> = {};
|
|
58
|
+
|
|
59
|
+
for (const collectionId of collections) {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(' ' + orange(collectionId));
|
|
62
|
+
|
|
63
|
+
const countSnap = await firestore.collection(collectionId).count().get();
|
|
64
|
+
const total = countSnap.data().count;
|
|
65
|
+
|
|
66
|
+
const bar = createProgressBar(collectionId);
|
|
67
|
+
bar.start(total, 0);
|
|
68
|
+
|
|
69
|
+
results[collectionId] = {
|
|
70
|
+
success: 0,
|
|
71
|
+
failed: 0,
|
|
72
|
+
failedIds: [],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let lastDoc: unknown = null;
|
|
76
|
+
let processed = 0;
|
|
77
|
+
|
|
78
|
+
while (processed < total) {
|
|
79
|
+
let query = firestore.collection(collectionId).limit(100);
|
|
80
|
+
|
|
81
|
+
if (lastDoc) {
|
|
82
|
+
query = query.startAfter(lastDoc as never);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const snapshot = await query.get();
|
|
86
|
+
if (snapshot.empty) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const doc of snapshot.docs) {
|
|
91
|
+
try {
|
|
92
|
+
const result = await client.doc.set(collectionId, doc.id, doc.data());
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
throw new Error(result.error.message);
|
|
95
|
+
}
|
|
96
|
+
results[collectionId].success++;
|
|
97
|
+
} catch {
|
|
98
|
+
results[collectionId].failed++;
|
|
99
|
+
results[collectionId].failedIds.push(doc.id);
|
|
100
|
+
}
|
|
101
|
+
processed++;
|
|
102
|
+
bar.update(processed);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
lastDoc = snapshot.docs[snapshot.docs.length - 1] ?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
bar.stop();
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
chalk.green(` ✓ ${results[collectionId].success} documents migrated`)
|
|
112
|
+
);
|
|
113
|
+
if (results[collectionId].failed > 0) {
|
|
114
|
+
console.log(chalk.red(` ✗ ${results[collectionId].failed} failed`));
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.gray(
|
|
117
|
+
' Failed IDs: ' +
|
|
118
|
+
results[collectionId].failedIds.slice(0, 10).join(', ') +
|
|
119
|
+
(results[collectionId].failedIds.length > 10 ? '...' : '')
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('');
|
|
126
|
+
p.outro(
|
|
127
|
+
orange('✓') +
|
|
128
|
+
' Migration complete.\n\n' +
|
|
129
|
+
' ' +
|
|
130
|
+
muted('Your Firebase data is intact.') +
|
|
131
|
+
'\n' +
|
|
132
|
+
' ' +
|
|
133
|
+
muted('Delete it manually when ready.')
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
138
|
+
printBanner(version);
|
|
139
|
+
p.intro(gold('⇒⇒') + ' Firebase → Globio Migration');
|
|
140
|
+
|
|
141
|
+
const { storage } = await initFirebase(options.from);
|
|
142
|
+
const client = getClient();
|
|
143
|
+
|
|
144
|
+
const bucketName = options.bucket.replace(/^gs:\/\//, '');
|
|
145
|
+
const bucket = storage.bucket(bucketName);
|
|
146
|
+
const prefix = options.folder ? options.folder.replace(/^\//, '') : '';
|
|
147
|
+
|
|
148
|
+
const [files] = await bucket.getFiles(prefix ? { prefix } : {});
|
|
149
|
+
|
|
150
|
+
console.log(chalk.cyan(`Found ${files.length} files to migrate`));
|
|
151
|
+
|
|
152
|
+
const bar = createProgressBar('Storage');
|
|
153
|
+
bar.start(files.length, 0);
|
|
154
|
+
|
|
155
|
+
let success = 0;
|
|
156
|
+
let failed = 0;
|
|
157
|
+
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
try {
|
|
160
|
+
const [buffer] = await file.download();
|
|
161
|
+
const uploadFile = new File(
|
|
162
|
+
[new Uint8Array(buffer)],
|
|
163
|
+
basename(file.name) || file.name
|
|
164
|
+
);
|
|
165
|
+
const result = await client.vault.uploadFile(uploadFile, {
|
|
166
|
+
metadata: {
|
|
167
|
+
original_path: file.name,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
throw new Error(result.error.message);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
success++;
|
|
176
|
+
} catch {
|
|
177
|
+
failed++;
|
|
178
|
+
}
|
|
179
|
+
bar.increment();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
bar.stop();
|
|
183
|
+
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(chalk.green(` ✓ ${success} files migrated`));
|
|
186
|
+
if (failed > 0) {
|
|
187
|
+
console.log(chalk.red(` ✗ ${failed} failed`));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
p.outro(
|
|
191
|
+
orange('✓') +
|
|
192
|
+
' Migration complete.\n\n' +
|
|
193
|
+
' ' +
|
|
194
|
+
muted('Your Firebase data is intact.') +
|
|
195
|
+
'\n' +
|
|
196
|
+
' ' +
|
|
197
|
+
muted('Delete it manually when ready.')
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { config } from '../lib/config.js';
|
|
3
|
+
|
|
4
|
+
export async function projectsList() {
|
|
5
|
+
const cfg = config.get();
|
|
6
|
+
console.log('');
|
|
7
|
+
console.log(
|
|
8
|
+
chalk.cyan('Active project: ') + (cfg.projectId ?? chalk.gray('none'))
|
|
9
|
+
);
|
|
10
|
+
console.log('');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function projectsUse(projectId: string) {
|
|
14
|
+
config.set({ projectId });
|
|
15
|
+
console.log(chalk.green('Active project set to: ') + chalk.cyan(projectId));
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
const ALL_SERVICES = [
|
|
4
|
+
'id',
|
|
5
|
+
'doc',
|
|
6
|
+
'vault',
|
|
7
|
+
'pulse',
|
|
8
|
+
'scope',
|
|
9
|
+
'sync',
|
|
10
|
+
'signal',
|
|
11
|
+
'mart',
|
|
12
|
+
'brain',
|
|
13
|
+
'code',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export async function servicesList() {
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(chalk.cyan('Available Globio services:'));
|
|
19
|
+
ALL_SERVICES.forEach((service) => {
|
|
20
|
+
console.log(' ' + chalk.white(service));
|
|
21
|
+
});
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(
|
|
24
|
+
chalk.gray('Manage service access via console.globio.stanlink.online')
|
|
25
|
+
);
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|