@globio/cli 0.2.0 → 0.2.2
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 +453 -132
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/auth/login.ts +86 -27
- package/src/auth/whoami.ts +27 -2
- package/src/commands/functions.ts +111 -20
- package/src/commands/init.ts +82 -13
- package/src/commands/migrate.ts +105 -54
- package/src/commands/profiles.ts +16 -1
- package/src/commands/projects.ts +141 -24
- package/src/commands/services.ts +11 -1
- package/src/index.ts +46 -13
- package/src/lib/table.ts +5 -0
package/src/commands/migrate.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getCliVersion,
|
|
6
6
|
gold,
|
|
7
7
|
green,
|
|
8
|
+
jsonOutput,
|
|
8
9
|
muted,
|
|
9
10
|
orange,
|
|
10
11
|
printBanner,
|
|
@@ -21,6 +22,7 @@ interface MigrateFirestoreOptions {
|
|
|
21
22
|
collection?: string;
|
|
22
23
|
all?: boolean;
|
|
23
24
|
profile?: string;
|
|
25
|
+
json?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
interface MigrateStorageOptions {
|
|
@@ -29,15 +31,36 @@ interface MigrateStorageOptions {
|
|
|
29
31
|
folder?: string;
|
|
30
32
|
all?: boolean;
|
|
31
33
|
profile?: string;
|
|
34
|
+
json?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface FirestoreCollectionResult {
|
|
38
|
+
name: string;
|
|
39
|
+
migrated: number;
|
|
40
|
+
failed: number;
|
|
41
|
+
failed_ids: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface FirestoreMigrationSummary {
|
|
45
|
+
collections: FirestoreCollectionResult[];
|
|
46
|
+
total_migrated: number;
|
|
47
|
+
total_failed: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface StorageMigrationSummary {
|
|
51
|
+
migrated: number;
|
|
52
|
+
failed: number;
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
function resolveProfileName(profile?: string) {
|
|
35
56
|
return profile ?? config.getActiveProfile() ?? 'default';
|
|
36
57
|
}
|
|
37
58
|
|
|
38
|
-
export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
export async function migrateFirestore(options: MigrateFirestoreOptions): Promise<void> {
|
|
60
|
+
if (!options.json) {
|
|
61
|
+
printBanner(version);
|
|
62
|
+
p.intro(gold('⇒⇒') + ' Firebase → Globio Migration');
|
|
63
|
+
}
|
|
41
64
|
|
|
42
65
|
const { firestore } = await initFirebase(options.from);
|
|
43
66
|
const profileName = resolveProfileName(options.profile);
|
|
@@ -47,32 +70,34 @@ export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
|
47
70
|
if (options.all) {
|
|
48
71
|
const snapshot = await firestore.listCollections();
|
|
49
72
|
collections = snapshot.map((collection) => collection.id);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`Found ${collections.length} collections: ${collections.join(', ')}`
|
|
53
|
-
)
|
|
54
|
-
|
|
73
|
+
if (!options.json) {
|
|
74
|
+
console.log(
|
|
75
|
+
green(`Found ${collections.length} collections: ${collections.join(', ')}`)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
55
78
|
} else if (options.collection) {
|
|
56
79
|
collections = [options.collection];
|
|
57
80
|
} else {
|
|
81
|
+
if (options.json) {
|
|
82
|
+
jsonOutput({ success: false, error: 'Specify --collection <name> or --all' });
|
|
83
|
+
}
|
|
58
84
|
console.log(failure('Specify --collection <name> or --all'));
|
|
59
85
|
process.exit(1);
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
const results: Record<
|
|
63
|
-
string,
|
|
64
|
-
{ success: number; failed: number; failedIds: string[] }
|
|
65
|
-
> = {};
|
|
88
|
+
const results: Record<string, { success: number; failed: number; failedIds: string[] }> = {};
|
|
66
89
|
|
|
67
90
|
for (const collectionId of collections) {
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
if (!options.json) {
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(' ' + orange(collectionId));
|
|
94
|
+
}
|
|
70
95
|
|
|
71
96
|
const countSnap = await firestore.collection(collectionId).count().get();
|
|
72
97
|
const total = countSnap.data().count;
|
|
73
98
|
|
|
74
|
-
const bar = createProgressBar(collectionId);
|
|
75
|
-
bar
|
|
99
|
+
const bar = options.json ? null : createProgressBar(collectionId);
|
|
100
|
+
bar?.start(total, 0);
|
|
76
101
|
|
|
77
102
|
results[collectionId] = {
|
|
78
103
|
success: 0,
|
|
@@ -87,7 +112,6 @@ export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
|
87
112
|
|
|
88
113
|
while (processed < total) {
|
|
89
114
|
let query = firestore.collection(collectionId).limit(100);
|
|
90
|
-
|
|
91
115
|
if (lastDoc) {
|
|
92
116
|
query = query.startAfter(lastDoc as never);
|
|
93
117
|
}
|
|
@@ -119,35 +143,49 @@ export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
|
119
143
|
results[collectionId].failed++;
|
|
120
144
|
results[collectionId].failedIds.push(doc.id);
|
|
121
145
|
}
|
|
146
|
+
|
|
122
147
|
processed++;
|
|
123
|
-
bar
|
|
148
|
+
bar?.update(processed);
|
|
124
149
|
}
|
|
125
150
|
|
|
126
151
|
lastDoc = snapshot.docs[snapshot.docs.length - 1] ?? null;
|
|
127
152
|
}
|
|
128
153
|
|
|
129
|
-
bar
|
|
154
|
+
bar?.stop();
|
|
130
155
|
|
|
131
|
-
|
|
132
|
-
green(` ✓ ${results[collectionId].success} documents migrated`)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
);
|
|
156
|
+
if (!options.json) {
|
|
157
|
+
console.log(green(` ✓ ${results[collectionId].success} documents migrated`));
|
|
158
|
+
if (indexFieldCount > 0) {
|
|
159
|
+
console.log(muted(` Indexes created for ${indexFieldCount} fields`));
|
|
160
|
+
}
|
|
161
|
+
if (results[collectionId].failed > 0) {
|
|
162
|
+
console.log(failure(` ✗ ${results[collectionId].failed} failed`) + '\x1b[0m');
|
|
163
|
+
console.log(
|
|
164
|
+
muted(
|
|
165
|
+
' Failed IDs: ' +
|
|
166
|
+
results[collectionId].failedIds.slice(0, 10).join(', ') +
|
|
167
|
+
(results[collectionId].failedIds.length > 10 ? '...' : '')
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
148
171
|
}
|
|
149
172
|
}
|
|
150
173
|
|
|
174
|
+
const summary: FirestoreMigrationSummary = {
|
|
175
|
+
collections: collections.map((name) => ({
|
|
176
|
+
name,
|
|
177
|
+
migrated: results[name]?.success ?? 0,
|
|
178
|
+
failed: results[name]?.failed ?? 0,
|
|
179
|
+
failed_ids: results[name]?.failedIds ?? [],
|
|
180
|
+
})),
|
|
181
|
+
total_migrated: collections.reduce((sum, name) => sum + (results[name]?.success ?? 0), 0),
|
|
182
|
+
total_failed: collections.reduce((sum, name) => sum + (results[name]?.failed ?? 0), 0),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (options.json) {
|
|
186
|
+
jsonOutput(summary);
|
|
187
|
+
}
|
|
188
|
+
|
|
151
189
|
console.log('');
|
|
152
190
|
p.outro(
|
|
153
191
|
orange('✓') +
|
|
@@ -158,11 +196,14 @@ export async function migrateFirestore(options: MigrateFirestoreOptions) {
|
|
|
158
196
|
' ' +
|
|
159
197
|
muted('Delete it manually when ready.')
|
|
160
198
|
);
|
|
199
|
+
|
|
161
200
|
}
|
|
162
201
|
|
|
163
|
-
export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
164
|
-
|
|
165
|
-
|
|
202
|
+
export async function migrateFirebaseStorage(options: MigrateStorageOptions): Promise<void> {
|
|
203
|
+
if (!options.json) {
|
|
204
|
+
printBanner(version);
|
|
205
|
+
p.intro(gold('⇒⇒') + ' Firebase → Globio Migration');
|
|
206
|
+
}
|
|
166
207
|
|
|
167
208
|
const { storage } = await initFirebase(options.from);
|
|
168
209
|
const profileName = resolveProfileName(options.profile);
|
|
@@ -178,10 +219,12 @@ export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
|
178
219
|
|
|
179
220
|
const [files] = await bucket.getFiles(prefix ? { prefix } : {});
|
|
180
221
|
|
|
181
|
-
|
|
222
|
+
if (!options.json) {
|
|
223
|
+
console.log(green(`Found ${files.length} files to migrate`));
|
|
224
|
+
}
|
|
182
225
|
|
|
183
|
-
const bar = createProgressBar('Storage');
|
|
184
|
-
bar
|
|
226
|
+
const bar = options.json ? null : createProgressBar('Storage');
|
|
227
|
+
bar?.start(files.length, 0);
|
|
185
228
|
|
|
186
229
|
let success = 0;
|
|
187
230
|
let failed = 0;
|
|
@@ -198,16 +241,13 @@ export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
|
198
241
|
);
|
|
199
242
|
formData.append('path', file.name);
|
|
200
243
|
|
|
201
|
-
const res = await fetch(
|
|
202
|
-
'
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
body: formData,
|
|
209
|
-
}
|
|
210
|
-
);
|
|
244
|
+
const res = await fetch('https://api.globio.stanlink.online/vault/files', {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
headers: {
|
|
247
|
+
'X-Globio-Key': profile.project_api_key,
|
|
248
|
+
},
|
|
249
|
+
body: formData,
|
|
250
|
+
});
|
|
211
251
|
|
|
212
252
|
if (!res.ok) {
|
|
213
253
|
throw new Error(`Upload failed: ${res.status}`);
|
|
@@ -217,10 +257,20 @@ export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
|
217
257
|
} catch {
|
|
218
258
|
failed++;
|
|
219
259
|
}
|
|
220
|
-
|
|
260
|
+
|
|
261
|
+
bar?.increment();
|
|
221
262
|
}
|
|
222
263
|
|
|
223
|
-
bar
|
|
264
|
+
bar?.stop();
|
|
265
|
+
|
|
266
|
+
const summary: StorageMigrationSummary = {
|
|
267
|
+
migrated: success,
|
|
268
|
+
failed,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (options.json) {
|
|
272
|
+
jsonOutput(summary);
|
|
273
|
+
}
|
|
224
274
|
|
|
225
275
|
console.log('');
|
|
226
276
|
console.log(green(` ✓ ${success} files migrated`));
|
|
@@ -237,4 +287,5 @@ export async function migrateFirebaseStorage(options: MigrateStorageOptions) {
|
|
|
237
287
|
' ' +
|
|
238
288
|
muted('Delete it manually when ready.')
|
|
239
289
|
);
|
|
290
|
+
|
|
240
291
|
}
|
package/src/commands/profiles.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
green,
|
|
4
4
|
header,
|
|
5
5
|
inactive,
|
|
6
|
+
jsonOutput,
|
|
6
7
|
muted,
|
|
7
8
|
orange,
|
|
8
9
|
renderTable,
|
|
@@ -13,9 +14,23 @@ import { config } from '../lib/config.js';
|
|
|
13
14
|
|
|
14
15
|
const version = getCliVersion();
|
|
15
16
|
|
|
16
|
-
export async function profilesList() {
|
|
17
|
+
export async function profilesList(options: { json?: boolean } = {}) {
|
|
17
18
|
const profiles = config.listProfiles();
|
|
18
19
|
const active = config.getActiveProfile();
|
|
20
|
+
const wantsJson = options.json ?? process.argv.includes('--json');
|
|
21
|
+
|
|
22
|
+
if (wantsJson) {
|
|
23
|
+
jsonOutput(
|
|
24
|
+
profiles.map((name) => {
|
|
25
|
+
const profile = config.getProfile(name);
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
email: profile?.account_email ?? null,
|
|
29
|
+
active: name === active,
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
19
34
|
|
|
20
35
|
if (!profiles.length) {
|
|
21
36
|
console.log(
|
package/src/commands/projects.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
green,
|
|
15
15
|
header,
|
|
16
16
|
inactive,
|
|
17
|
+
jsonOutput,
|
|
17
18
|
muted,
|
|
18
19
|
orange,
|
|
19
20
|
renderTable,
|
|
@@ -54,13 +55,65 @@ async function createServerKey(projectId: string, profileName: string) {
|
|
|
54
55
|
return created.token;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
function buildSlug(value: string) {
|
|
59
|
+
return slugify(value).slice(0, 30);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function createProjectRecord(
|
|
63
|
+
input: {
|
|
64
|
+
profileName: string;
|
|
65
|
+
orgId: string;
|
|
66
|
+
orgName?: string;
|
|
67
|
+
name: string;
|
|
68
|
+
slug?: string;
|
|
69
|
+
environment?: string;
|
|
70
|
+
}
|
|
71
|
+
) {
|
|
72
|
+
const result = await manageRequest<{
|
|
73
|
+
project: { id: string; name: string; slug: string; environment: string; active: boolean };
|
|
74
|
+
keys: { client: string; server: string };
|
|
75
|
+
}>('/projects', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: {
|
|
78
|
+
org_id: input.orgId,
|
|
79
|
+
name: input.name,
|
|
80
|
+
slug: input.slug ?? buildSlug(input.name),
|
|
81
|
+
environment: input.environment ?? 'development',
|
|
82
|
+
},
|
|
83
|
+
profileName: input.profileName,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
config.setProfile(input.profileName, {
|
|
87
|
+
active_project_id: result.project.id,
|
|
88
|
+
active_project_name: result.project.name,
|
|
89
|
+
org_name: input.orgName,
|
|
90
|
+
project_api_key: result.keys.server,
|
|
91
|
+
});
|
|
92
|
+
config.setActiveProfile(input.profileName);
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function projectsList(options: { profile?: string; json?: boolean } = {}) {
|
|
58
98
|
const profileName = resolveProfileName(options.profile);
|
|
59
99
|
config.requireAuth(profileName);
|
|
60
100
|
|
|
61
101
|
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
62
102
|
const activeProjectId = config.getProfile(profileName)?.active_project_id;
|
|
63
103
|
|
|
104
|
+
if (options.json) {
|
|
105
|
+
jsonOutput(
|
|
106
|
+
projects.map((project) => ({
|
|
107
|
+
id: project.id,
|
|
108
|
+
name: project.name,
|
|
109
|
+
org_id: project.org_id,
|
|
110
|
+
org_name: project.org_name,
|
|
111
|
+
environment: project.environment,
|
|
112
|
+
active: project.id === activeProjectId,
|
|
113
|
+
}))
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
64
117
|
if (!projects.length) {
|
|
65
118
|
console.log(header(version) + ' ' + muted('No projects found.') + '\n');
|
|
66
119
|
return;
|
|
@@ -72,7 +125,15 @@ export async function projectsList(options: { profile?: string } = {}) {
|
|
|
72
125
|
: white(project.name),
|
|
73
126
|
muted(project.id),
|
|
74
127
|
muted(project.org_name || project.org_id),
|
|
75
|
-
inactive(
|
|
128
|
+
inactive(
|
|
129
|
+
project.environment === 'development'
|
|
130
|
+
? 'dev'
|
|
131
|
+
: project.environment === 'production'
|
|
132
|
+
? 'prod'
|
|
133
|
+
: project.environment === 'staging'
|
|
134
|
+
? 'stg'
|
|
135
|
+
: project.environment ?? 'dev'
|
|
136
|
+
),
|
|
76
137
|
]);
|
|
77
138
|
|
|
78
139
|
console.log(header(version));
|
|
@@ -92,13 +153,19 @@ export async function projectsList(options: { profile?: string } = {}) {
|
|
|
92
153
|
);
|
|
93
154
|
}
|
|
94
155
|
|
|
95
|
-
export async function projectsUse(
|
|
156
|
+
export async function projectsUse(
|
|
157
|
+
projectId: string,
|
|
158
|
+
options: { profile?: string; json?: boolean } = {}
|
|
159
|
+
) {
|
|
96
160
|
const profileName = resolveProfileName(options.profile);
|
|
97
161
|
config.requireAuth(profileName);
|
|
98
162
|
|
|
99
163
|
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
100
164
|
const project = projects.find((item) => item.id === projectId);
|
|
101
165
|
if (!project) {
|
|
166
|
+
if (options.json) {
|
|
167
|
+
jsonOutput({ success: false, error: `Project not found: ${projectId}` });
|
|
168
|
+
}
|
|
102
169
|
console.log(failure(`Project not found: ${projectId}`));
|
|
103
170
|
process.exit(1);
|
|
104
171
|
}
|
|
@@ -114,10 +181,26 @@ export async function projectsUse(projectId: string, options: { profile?: string
|
|
|
114
181
|
});
|
|
115
182
|
config.setActiveProfile(profileName);
|
|
116
183
|
|
|
184
|
+
if (options.json) {
|
|
185
|
+
jsonOutput({
|
|
186
|
+
success: true,
|
|
187
|
+
project_id: project.id,
|
|
188
|
+
project_name: project.name,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
117
192
|
console.log(green('Active project: ') + `${project.name} (${project.id})`);
|
|
118
193
|
}
|
|
119
194
|
|
|
120
|
-
export async function projectsCreate(
|
|
195
|
+
export async function projectsCreate(
|
|
196
|
+
options: {
|
|
197
|
+
profile?: string;
|
|
198
|
+
name?: string;
|
|
199
|
+
org?: string;
|
|
200
|
+
env?: string;
|
|
201
|
+
json?: boolean;
|
|
202
|
+
} = {}
|
|
203
|
+
) {
|
|
121
204
|
const profileName = resolveProfileName(options.profile);
|
|
122
205
|
config.requireAuth(profileName);
|
|
123
206
|
|
|
@@ -127,6 +210,51 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
127
210
|
process.exit(1);
|
|
128
211
|
}
|
|
129
212
|
|
|
213
|
+
const isNonInteractive = Boolean(options.name && options.org);
|
|
214
|
+
|
|
215
|
+
if (options.json && !isNonInteractive) {
|
|
216
|
+
jsonOutput({
|
|
217
|
+
success: false,
|
|
218
|
+
error: 'projects create --json requires --name <name> and --org <orgId>',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (isNonInteractive) {
|
|
223
|
+
const org = orgs.find((item) => item.id === options.org);
|
|
224
|
+
if (!org) {
|
|
225
|
+
console.log(failure(`Organization not found: ${options.org}`));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = await createProjectRecord({
|
|
230
|
+
profileName,
|
|
231
|
+
orgId: org.id,
|
|
232
|
+
orgName: org.name,
|
|
233
|
+
name: options.name as string,
|
|
234
|
+
environment: options.env ?? 'development',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (options.json) {
|
|
238
|
+
jsonOutput({
|
|
239
|
+
success: true,
|
|
240
|
+
project_id: result.project.id,
|
|
241
|
+
project_name: result.project.name,
|
|
242
|
+
org_id: org.id,
|
|
243
|
+
environment: result.project.environment,
|
|
244
|
+
client_key: result.keys.client,
|
|
245
|
+
server_key: result.keys.server,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log(green('Project created successfully.'));
|
|
251
|
+
console.log(orange('Project: ') + reset + `${result.project.name} (${result.project.id})`);
|
|
252
|
+
console.log(orange('Client key: ') + reset + result.keys.client);
|
|
253
|
+
console.log(orange('Server key: ') + reset + result.keys.server);
|
|
254
|
+
console.log('');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
130
258
|
const orgId = await p.select({
|
|
131
259
|
message: 'Select an organization',
|
|
132
260
|
options: orgs.map((org) => ({
|
|
@@ -151,7 +279,7 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
151
279
|
slug: ({ results }) =>
|
|
152
280
|
p.text({
|
|
153
281
|
message: 'Project slug',
|
|
154
|
-
initialValue:
|
|
282
|
+
initialValue: buildSlug(String(results.name ?? '')),
|
|
155
283
|
validate: (value) => (!value ? 'Project slug is required' : undefined),
|
|
156
284
|
}),
|
|
157
285
|
environment: () =>
|
|
@@ -172,28 +300,15 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
172
300
|
}
|
|
173
301
|
);
|
|
174
302
|
|
|
175
|
-
const result = await
|
|
176
|
-
project: { id: string; name: string; slug: string; environment: string; active: boolean };
|
|
177
|
-
keys: { client: string; server: string };
|
|
178
|
-
}>('/projects', {
|
|
179
|
-
method: 'POST',
|
|
180
|
-
body: {
|
|
181
|
-
org_id: orgId,
|
|
182
|
-
name: values.name,
|
|
183
|
-
slug: values.slug,
|
|
184
|
-
environment: values.environment,
|
|
185
|
-
},
|
|
303
|
+
const result = await createProjectRecord({
|
|
186
304
|
profileName,
|
|
305
|
+
orgId,
|
|
306
|
+
orgName: orgs.find((org) => org.id === orgId)?.name,
|
|
307
|
+
name: String(values.name),
|
|
308
|
+
slug: String(values.slug),
|
|
309
|
+
environment: String(values.environment),
|
|
187
310
|
});
|
|
188
311
|
|
|
189
|
-
config.setProfile(profileName, {
|
|
190
|
-
active_project_id: result.project.id,
|
|
191
|
-
active_project_name: result.project.name,
|
|
192
|
-
org_name: orgs.find((org) => org.id === orgId)?.name,
|
|
193
|
-
project_api_key: result.keys.server,
|
|
194
|
-
});
|
|
195
|
-
config.setActiveProfile(profileName);
|
|
196
|
-
|
|
197
312
|
console.log('');
|
|
198
313
|
console.log(green('Project created successfully.'));
|
|
199
314
|
console.log(orange('Project: ') + reset + `${result.project.name} (${result.project.id})`);
|
|
@@ -201,3 +316,5 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
201
316
|
console.log(orange('Server key: ') + reset + result.keys.server);
|
|
202
317
|
console.log('');
|
|
203
318
|
}
|
|
319
|
+
|
|
320
|
+
export { createProjectRecord, buildSlug };
|
package/src/commands/services.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
green,
|
|
7
7
|
header,
|
|
8
8
|
inactive,
|
|
9
|
+
jsonOutput,
|
|
9
10
|
muted,
|
|
10
11
|
orange,
|
|
11
12
|
renderTable,
|
|
@@ -26,7 +27,7 @@ const SERVICE_DESCRIPTIONS: Record<string, string> = {
|
|
|
26
27
|
code: 'Edge functions and GC Hooks',
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
export async function servicesList(options: { profile?: string } = {}) {
|
|
30
|
+
export async function servicesList(options: { profile?: string; json?: boolean } = {}) {
|
|
30
31
|
const profileName = options.profile ?? config.getActiveProfile() ?? 'default';
|
|
31
32
|
const profile = config.getProfile(profileName);
|
|
32
33
|
let serviceStatuses: ManageProjectServices = {};
|
|
@@ -42,6 +43,15 @@ export async function servicesList(options: { profile?: string } = {}) {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
if (options.json) {
|
|
47
|
+
jsonOutput(
|
|
48
|
+
Object.keys(SERVICE_DESCRIPTIONS).map((service) => ({
|
|
49
|
+
service,
|
|
50
|
+
enabled: serviceStatuses[service] ?? false,
|
|
51
|
+
}))
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
const rows = Object.entries(SERVICE_DESCRIPTIONS).map(([slug, desc]) => {
|
|
46
56
|
const enabled = serviceStatuses[slug] ?? null;
|
|
47
57
|
return [
|