@globio/cli 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.js +304 -145
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/auth/login.ts +9 -10
- package/src/auth/logout.ts +5 -5
- package/src/auth/whoami.ts +58 -19
- package/src/commands/functions.ts +60 -34
- package/src/commands/init.ts +3 -2
- package/src/commands/migrate.ts +32 -10
- package/src/commands/profiles.ts +39 -12
- package/src/commands/projects.ts +51 -29
- package/src/commands/services.ts +62 -22
- package/src/lib/api.ts +21 -0
- package/src/lib/banner.ts +1 -6
- package/src/lib/config.ts +2 -0
- package/src/lib/manage.ts +4 -0
- package/src/lib/table.ts +97 -0
package/src/commands/projects.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
2
|
import { config } from '../lib/config.js';
|
|
4
3
|
import {
|
|
5
4
|
manageRequest,
|
|
6
5
|
type ManageOrg,
|
|
7
6
|
type ManageProject,
|
|
8
7
|
type ManageProjectKey,
|
|
8
|
+
type ManageProjectServices,
|
|
9
9
|
} from '../lib/manage.js';
|
|
10
|
+
import {
|
|
11
|
+
footer,
|
|
12
|
+
getCliVersion,
|
|
13
|
+
gold,
|
|
14
|
+
green,
|
|
15
|
+
header,
|
|
16
|
+
inactive,
|
|
17
|
+
muted,
|
|
18
|
+
orange,
|
|
19
|
+
renderTable,
|
|
20
|
+
reset,
|
|
21
|
+
white,
|
|
22
|
+
failure,
|
|
23
|
+
} from '../lib/banner.js';
|
|
24
|
+
|
|
25
|
+
const version = getCliVersion();
|
|
10
26
|
|
|
11
27
|
function slugify(value: string) {
|
|
12
28
|
return value
|
|
@@ -44,30 +60,36 @@ export async function projectsList(options: { profile?: string } = {}) {
|
|
|
44
60
|
|
|
45
61
|
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
46
62
|
const activeProjectId = config.getProfile(profileName)?.active_project_id;
|
|
47
|
-
const grouped = new Map<string, ManageProject[]>();
|
|
48
|
-
|
|
49
|
-
for (const project of projects) {
|
|
50
|
-
const list = grouped.get(project.org_name) ?? [];
|
|
51
|
-
list.push(project);
|
|
52
|
-
grouped.set(project.org_name, list);
|
|
53
|
-
}
|
|
54
63
|
|
|
55
|
-
console.log('');
|
|
56
64
|
if (!projects.length) {
|
|
57
|
-
console.log(
|
|
58
|
-
console.log('');
|
|
65
|
+
console.log(header(version) + ' ' + muted('No projects found.') + '\n');
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const rows = projects.map((project) => [
|
|
70
|
+
activeProjectId === project.id
|
|
71
|
+
? gold(project.name) + reset + ' ' + orange('●') + reset
|
|
72
|
+
: white(project.name),
|
|
73
|
+
muted(project.id),
|
|
74
|
+
muted(project.org_name || project.org_id),
|
|
75
|
+
inactive(project.environment?.slice(0, 4) ?? 'dev'),
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
console.log(header(version));
|
|
79
|
+
console.log(
|
|
80
|
+
renderTable({
|
|
81
|
+
columns: [
|
|
82
|
+
{ header: 'Project', width: 24 },
|
|
83
|
+
{ header: 'ID', width: 26 },
|
|
84
|
+
{ header: 'Org', width: 16 },
|
|
85
|
+
{ header: 'Env', width: 6 },
|
|
86
|
+
],
|
|
87
|
+
rows,
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
console.log(
|
|
91
|
+
footer('● active project · run globio projects use <id> to switch')
|
|
92
|
+
);
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
export async function projectsUse(projectId: string, options: { profile?: string } = {}) {
|
|
@@ -77,7 +99,7 @@ export async function projectsUse(projectId: string, options: { profile?: string
|
|
|
77
99
|
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
78
100
|
const project = projects.find((item) => item.id === projectId);
|
|
79
101
|
if (!project) {
|
|
80
|
-
console.log(
|
|
102
|
+
console.log(failure(`Project not found: ${projectId}`));
|
|
81
103
|
process.exit(1);
|
|
82
104
|
}
|
|
83
105
|
|
|
@@ -87,13 +109,12 @@ export async function projectsUse(projectId: string, options: { profile?: string
|
|
|
87
109
|
config.setProfile(profileName, {
|
|
88
110
|
active_project_id: project.id,
|
|
89
111
|
active_project_name: project.name,
|
|
112
|
+
org_name: project.org_name,
|
|
90
113
|
project_api_key: apiKey,
|
|
91
114
|
});
|
|
92
115
|
config.setActiveProfile(profileName);
|
|
93
116
|
|
|
94
|
-
console.log(
|
|
95
|
-
chalk.green('Active project set to: ') + chalk.cyan(`${project.name} (${project.id})`)
|
|
96
|
-
);
|
|
117
|
+
console.log(green('Active project: ') + `${project.name} (${project.id})`);
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
export async function projectsCreate(options: { profile?: string } = {}) {
|
|
@@ -102,7 +123,7 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
102
123
|
|
|
103
124
|
const orgs = await manageRequest<ManageOrg[]>('/orgs', { profileName });
|
|
104
125
|
if (!orgs.length) {
|
|
105
|
-
console.log(
|
|
126
|
+
console.log(failure('No organizations found. Create one in the console first.'));
|
|
106
127
|
process.exit(1);
|
|
107
128
|
}
|
|
108
129
|
|
|
@@ -168,14 +189,15 @@ export async function projectsCreate(options: { profile?: string } = {}) {
|
|
|
168
189
|
config.setProfile(profileName, {
|
|
169
190
|
active_project_id: result.project.id,
|
|
170
191
|
active_project_name: result.project.name,
|
|
192
|
+
org_name: orgs.find((org) => org.id === orgId)?.name,
|
|
171
193
|
project_api_key: result.keys.server,
|
|
172
194
|
});
|
|
173
195
|
config.setActiveProfile(profileName);
|
|
174
196
|
|
|
175
197
|
console.log('');
|
|
176
|
-
console.log(
|
|
177
|
-
console.log(
|
|
178
|
-
console.log(
|
|
179
|
-
console.log(
|
|
198
|
+
console.log(green('Project created successfully.'));
|
|
199
|
+
console.log(orange('Project: ') + reset + `${result.project.name} (${result.project.id})`);
|
|
200
|
+
console.log(orange('Client key: ') + reset + result.keys.client);
|
|
201
|
+
console.log(orange('Server key: ') + reset + result.keys.server);
|
|
180
202
|
console.log('');
|
|
181
203
|
}
|
package/src/commands/services.ts
CHANGED
|
@@ -1,30 +1,70 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import { config } from '../lib/config.js';
|
|
2
|
+
import { manageRequest, type ManageProjectServices } from '../lib/manage.js';
|
|
3
|
+
import {
|
|
4
|
+
footer,
|
|
5
|
+
getCliVersion,
|
|
6
|
+
green,
|
|
7
|
+
header,
|
|
8
|
+
inactive,
|
|
9
|
+
muted,
|
|
10
|
+
orange,
|
|
11
|
+
renderTable,
|
|
12
|
+
} from '../lib/banner.js';
|
|
3
13
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
|
|
14
|
+
const version = getCliVersion();
|
|
15
|
+
|
|
16
|
+
const SERVICE_DESCRIPTIONS: Record<string, string> = {
|
|
17
|
+
id: 'Authentication and user management',
|
|
18
|
+
doc: 'Document database',
|
|
19
|
+
vault: 'File storage',
|
|
20
|
+
pulse: 'Feature flags and remote config',
|
|
21
|
+
scope: 'Analytics and event tracking',
|
|
22
|
+
sync: 'Real-time multiplayer rooms',
|
|
23
|
+
signal: 'Push notifications',
|
|
24
|
+
mart: 'Game economy and payments',
|
|
25
|
+
brain: 'AI agents and LLM routing',
|
|
26
|
+
code: 'Edge functions and GC Hooks',
|
|
27
|
+
};
|
|
16
28
|
|
|
17
29
|
export async function servicesList(options: { profile?: string } = {}) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
const profileName = options.profile ?? config.getActiveProfile() ?? 'default';
|
|
31
|
+
const profile = config.getProfile(profileName);
|
|
32
|
+
let serviceStatuses: ManageProjectServices = {};
|
|
33
|
+
|
|
34
|
+
if (profile?.active_project_id) {
|
|
35
|
+
try {
|
|
36
|
+
serviceStatuses = await manageRequest<ManageProjectServices>(
|
|
37
|
+
`/projects/${profile.active_project_id}/services`,
|
|
38
|
+
{ profileName }
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
serviceStatuses = {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rows = Object.entries(SERVICE_DESCRIPTIONS).map(([slug, desc]) => {
|
|
46
|
+
const enabled = serviceStatuses[slug] ?? null;
|
|
47
|
+
return [
|
|
48
|
+
orange(slug),
|
|
49
|
+
muted(desc),
|
|
50
|
+
enabled === true
|
|
51
|
+
? green('enabled')
|
|
52
|
+
: enabled === false
|
|
53
|
+
? inactive('disabled')
|
|
54
|
+
: inactive('—'),
|
|
55
|
+
];
|
|
24
56
|
});
|
|
25
|
-
|
|
57
|
+
|
|
58
|
+
console.log(header(version));
|
|
26
59
|
console.log(
|
|
27
|
-
|
|
60
|
+
renderTable({
|
|
61
|
+
columns: [
|
|
62
|
+
{ header: 'Service', width: 10 },
|
|
63
|
+
{ header: 'Description', width: 42 },
|
|
64
|
+
{ header: 'Status', width: 10 },
|
|
65
|
+
],
|
|
66
|
+
rows,
|
|
67
|
+
})
|
|
28
68
|
);
|
|
29
|
-
console.log('');
|
|
69
|
+
console.log(footer('Manage services at console.globio.stanlink.online'));
|
|
30
70
|
}
|
package/src/lib/api.ts
CHANGED
|
@@ -50,3 +50,24 @@ export async function docSet(
|
|
|
50
50
|
profile,
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
export async function createIndex(
|
|
55
|
+
collection: string,
|
|
56
|
+
field: string,
|
|
57
|
+
fieldType: 'string' | 'number' | 'boolean' = 'string',
|
|
58
|
+
profile?: string
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
void fieldType;
|
|
61
|
+
try {
|
|
62
|
+
await apiCall(`/doc/${collection}/indexes`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: {
|
|
65
|
+
field_path: field,
|
|
66
|
+
index_type: 'asc',
|
|
67
|
+
},
|
|
68
|
+
profile,
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
// Index may already exist. Ignore duplicate-style failures.
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/lib/banner.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import figlet from 'figlet';
|
|
3
3
|
import gradientString from 'gradient-string';
|
|
4
|
+
export * from './table.js';
|
|
4
5
|
|
|
5
6
|
const globioGradient = gradientString(
|
|
6
7
|
'#e85d04',
|
|
@@ -39,12 +40,6 @@ export function printInfo(message: string) {
|
|
|
39
40
|
console.log('\x1b[2m›\x1b[0m ' + message);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export const orange = (s: string) => '\x1b[38;2;244;140;6m' + s + '\x1b[0m';
|
|
43
|
-
|
|
44
|
-
export const gold = (s: string) => '\x1b[38;2;255;208;0m' + s + '\x1b[0m';
|
|
45
|
-
|
|
46
|
-
export const muted = (s: string) => '\x1b[2m' + s + '\x1b[0m';
|
|
47
|
-
|
|
48
43
|
export function getCliVersion() {
|
|
49
44
|
const file = readFileSync(new URL('../package.json', import.meta.url), 'utf8');
|
|
50
45
|
return (JSON.parse(file) as { version: string }).version;
|
package/src/lib/config.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface ProfileData {
|
|
|
11
11
|
pat: string;
|
|
12
12
|
account_email: string;
|
|
13
13
|
account_name: string;
|
|
14
|
+
org_name?: string;
|
|
14
15
|
active_project_id?: string;
|
|
15
16
|
active_project_name?: string;
|
|
16
17
|
project_api_key?: string;
|
|
@@ -88,6 +89,7 @@ export const config = {
|
|
|
88
89
|
pat: data.pat ?? existing?.pat ?? '',
|
|
89
90
|
account_email: data.account_email ?? existing?.account_email ?? '',
|
|
90
91
|
account_name: data.account_name ?? existing?.account_name ?? '',
|
|
92
|
+
org_name: data.org_name ?? existing?.org_name,
|
|
91
93
|
active_project_id: data.active_project_id ?? existing?.active_project_id,
|
|
92
94
|
active_project_name: data.active_project_name ?? existing?.active_project_name,
|
|
93
95
|
project_api_key: data.project_api_key ?? existing?.project_api_key,
|
package/src/lib/manage.ts
CHANGED
package/src/lib/table.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export interface Column {
|
|
2
|
+
header: string;
|
|
3
|
+
width: number;
|
|
4
|
+
color?: (val: string) => string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TableOptions {
|
|
8
|
+
columns: Column[];
|
|
9
|
+
rows: string[][];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
13
|
+
|
|
14
|
+
export const orange = (s: string) => '\x1b[38;2;244;140;6m' + s;
|
|
15
|
+
export const gold = (s: string) => '\x1b[38;2;255;208;0m' + s;
|
|
16
|
+
export const dim = (s: string) => '\x1b[2m' + s + '\x1b[0m';
|
|
17
|
+
export const white = (s: string) => '\x1b[97m' + s;
|
|
18
|
+
export const green = (s: string) => '\x1b[38;2;34;197;94m' + s;
|
|
19
|
+
export const muted = (s: string) => '\x1b[38;2;85;85;85m' + s;
|
|
20
|
+
export const inactive = (s: string) => '\x1b[38;2;68;68;68m' + s;
|
|
21
|
+
export const failure = (s: string) => '\x1b[38;2;232;93;4m' + s;
|
|
22
|
+
export const reset = '\x1b[0m';
|
|
23
|
+
|
|
24
|
+
function stripAnsi(value: string): string {
|
|
25
|
+
return value.replace(ANSI_PATTERN, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function fitCell(value: string, width: number): string {
|
|
29
|
+
const plain = stripAnsi(value);
|
|
30
|
+
if (plain.length <= width) {
|
|
31
|
+
return value + ' '.repeat(width - plain.length);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const truncated = plain.slice(0, width);
|
|
35
|
+
return truncated;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function renderTable(options: TableOptions): string {
|
|
39
|
+
const { columns, rows } = options;
|
|
40
|
+
const lines: string[] = [];
|
|
41
|
+
|
|
42
|
+
lines.push(
|
|
43
|
+
' ┌' + columns.map((c) => '─'.repeat(c.width + 2)).join('┬') + '┐'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
lines.push(
|
|
47
|
+
' │' +
|
|
48
|
+
columns
|
|
49
|
+
.map((c) => ' ' + dim(c.header.padEnd(c.width)) + ' │')
|
|
50
|
+
.join('')
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
lines.push(
|
|
54
|
+
' ├' + columns.map((c) => '─'.repeat(c.width + 2)).join('┼') + '┤'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
lines.push(
|
|
59
|
+
' │' +
|
|
60
|
+
columns
|
|
61
|
+
.map((c, i) => {
|
|
62
|
+
const raw = row[i] ?? '';
|
|
63
|
+
const fitted = fitCell(raw, c.width);
|
|
64
|
+
const colored = c.color
|
|
65
|
+
? fitCell(c.color(stripAnsi(raw)), c.width)
|
|
66
|
+
: fitted;
|
|
67
|
+
return ' ' + colored + ' ' + reset + '│';
|
|
68
|
+
})
|
|
69
|
+
.join('')
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lines.push(
|
|
74
|
+
' └' + columns.map((c) => '─'.repeat(c.width + 2)).join('┴') + '┘'
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return lines.join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function header(version: string, subtitle?: string): string {
|
|
81
|
+
const lines = [
|
|
82
|
+
'',
|
|
83
|
+
orange(' ⇒⇒') + reset + ' globio ' + dim(version),
|
|
84
|
+
dim(' ──────────────────────────────────────────'),
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
if (subtitle) {
|
|
88
|
+
lines.push(' ' + subtitle);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lines.push('');
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function footer(text: string): string {
|
|
96
|
+
return '\n' + dim(' ' + text) + '\n';
|
|
97
|
+
}
|