@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/src/commands/projects.ts
CHANGED
|
@@ -1,16 +1,181 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import { config } from '../lib/config.js';
|
|
4
|
+
import {
|
|
5
|
+
manageRequest,
|
|
6
|
+
type ManageOrg,
|
|
7
|
+
type ManageProject,
|
|
8
|
+
type ManageProjectKey,
|
|
9
|
+
} from '../lib/manage.js';
|
|
10
|
+
|
|
11
|
+
function slugify(value: string) {
|
|
12
|
+
return value
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/[^a-z0-9\\s-]/g, '')
|
|
16
|
+
.replace(/\\s+/g, '-')
|
|
17
|
+
.replace(/-+/g, '-');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveProfileName(profileName?: string) {
|
|
21
|
+
return profileName ?? config.getActiveProfile() ?? 'default';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function createServerKey(projectId: string, profileName: string) {
|
|
25
|
+
const created = await manageRequest<ManageProjectKey>(`/projects/${projectId}/keys`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: {
|
|
28
|
+
name: 'CLI server key',
|
|
29
|
+
scope: 'server',
|
|
30
|
+
},
|
|
31
|
+
profileName,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!created.token) {
|
|
35
|
+
throw new Error('Management API did not return a project API key');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return created.token;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function projectsList(options: { profile?: string } = {}) {
|
|
42
|
+
const profileName = resolveProfileName(options.profile);
|
|
43
|
+
config.requireAuth(profileName);
|
|
44
|
+
|
|
45
|
+
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
46
|
+
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
|
+
}
|
|
3
54
|
|
|
4
|
-
export async function projectsList() {
|
|
5
|
-
const cfg = config.get();
|
|
6
55
|
console.log('');
|
|
56
|
+
if (!projects.length) {
|
|
57
|
+
console.log(chalk.gray('No projects found.'));
|
|
58
|
+
console.log('');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const [orgName, orgProjects] of grouped.entries()) {
|
|
63
|
+
console.log(chalk.cyan(`org: ${orgName}`));
|
|
64
|
+
for (const project of orgProjects) {
|
|
65
|
+
const marker = project.id === activeProjectId ? chalk.green('●') : chalk.gray('○');
|
|
66
|
+
const active = project.id === activeProjectId ? chalk.green(' (active)') : '';
|
|
67
|
+
console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk.gray(project.id)}${active}`);
|
|
68
|
+
}
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function projectsUse(projectId: string, options: { profile?: string } = {}) {
|
|
74
|
+
const profileName = resolveProfileName(options.profile);
|
|
75
|
+
config.requireAuth(profileName);
|
|
76
|
+
|
|
77
|
+
const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
|
|
78
|
+
const project = projects.find((item) => item.id === projectId);
|
|
79
|
+
if (!project) {
|
|
80
|
+
console.log(chalk.red(`Project not found: ${projectId}`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await manageRequest<ManageProjectKey[]>(`/projects/${projectId}/keys`, { profileName });
|
|
85
|
+
const apiKey = await createServerKey(projectId, profileName);
|
|
86
|
+
|
|
87
|
+
config.setProfile(profileName, {
|
|
88
|
+
active_project_id: project.id,
|
|
89
|
+
active_project_name: project.name,
|
|
90
|
+
project_api_key: apiKey,
|
|
91
|
+
});
|
|
92
|
+
config.setActiveProfile(profileName);
|
|
93
|
+
|
|
7
94
|
console.log(
|
|
8
|
-
chalk.
|
|
95
|
+
chalk.green('Active project set to: ') + chalk.cyan(`${project.name} (${project.id})`)
|
|
9
96
|
);
|
|
10
|
-
console.log('');
|
|
11
97
|
}
|
|
12
98
|
|
|
13
|
-
export async function
|
|
14
|
-
|
|
15
|
-
|
|
99
|
+
export async function projectsCreate(options: { profile?: string } = {}) {
|
|
100
|
+
const profileName = resolveProfileName(options.profile);
|
|
101
|
+
config.requireAuth(profileName);
|
|
102
|
+
|
|
103
|
+
const orgs = await manageRequest<ManageOrg[]>('/orgs', { profileName });
|
|
104
|
+
if (!orgs.length) {
|
|
105
|
+
console.log(chalk.red('No organizations found. Create one in the console first.'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const orgId = await p.select({
|
|
110
|
+
message: 'Select an organization',
|
|
111
|
+
options: orgs.map((org) => ({
|
|
112
|
+
value: org.id,
|
|
113
|
+
label: org.name,
|
|
114
|
+
hint: org.role,
|
|
115
|
+
})),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (p.isCancel(orgId)) {
|
|
119
|
+
p.cancel('Project creation cancelled.');
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const values = await p.group(
|
|
124
|
+
{
|
|
125
|
+
name: () =>
|
|
126
|
+
p.text({
|
|
127
|
+
message: 'Project name',
|
|
128
|
+
validate: (value) => (!value ? 'Project name is required' : undefined),
|
|
129
|
+
}),
|
|
130
|
+
slug: ({ results }) =>
|
|
131
|
+
p.text({
|
|
132
|
+
message: 'Project slug',
|
|
133
|
+
initialValue: slugify(String(results.name ?? '')),
|
|
134
|
+
validate: (value) => (!value ? 'Project slug is required' : undefined),
|
|
135
|
+
}),
|
|
136
|
+
environment: () =>
|
|
137
|
+
p.select({
|
|
138
|
+
message: 'Environment',
|
|
139
|
+
options: [
|
|
140
|
+
{ value: 'development', label: 'development' },
|
|
141
|
+
{ value: 'staging', label: 'staging' },
|
|
142
|
+
{ value: 'production', label: 'production' },
|
|
143
|
+
],
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
onCancel: () => {
|
|
148
|
+
p.cancel('Project creation cancelled.');
|
|
149
|
+
process.exit(0);
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const result = await manageRequest<{
|
|
155
|
+
project: { id: string; name: string; slug: string; environment: string; active: boolean };
|
|
156
|
+
keys: { client: string; server: string };
|
|
157
|
+
}>('/projects', {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
body: {
|
|
160
|
+
org_id: orgId,
|
|
161
|
+
name: values.name,
|
|
162
|
+
slug: values.slug,
|
|
163
|
+
environment: values.environment,
|
|
164
|
+
},
|
|
165
|
+
profileName,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
config.setProfile(profileName, {
|
|
169
|
+
active_project_id: result.project.id,
|
|
170
|
+
active_project_name: result.project.name,
|
|
171
|
+
project_api_key: result.keys.server,
|
|
172
|
+
});
|
|
173
|
+
config.setActiveProfile(profileName);
|
|
174
|
+
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(chalk.green('Project created successfully.'));
|
|
177
|
+
console.log(chalk.cyan('Project: ') + `${result.project.name} (${result.project.id})`);
|
|
178
|
+
console.log(chalk.cyan('Client key: ') + result.keys.client);
|
|
179
|
+
console.log(chalk.cyan('Server key: ') + result.keys.server);
|
|
180
|
+
console.log('');
|
|
16
181
|
}
|
package/src/commands/services.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { config } from '../lib/config.js';
|
|
2
3
|
|
|
3
4
|
const ALL_SERVICES = [
|
|
4
5
|
'id',
|
|
@@ -13,7 +14,9 @@ const ALL_SERVICES = [
|
|
|
13
14
|
'code',
|
|
14
15
|
];
|
|
15
16
|
|
|
16
|
-
export async function servicesList() {
|
|
17
|
+
export async function servicesList(options: { profile?: string } = {}) {
|
|
18
|
+
void options.profile;
|
|
19
|
+
void config;
|
|
17
20
|
console.log('');
|
|
18
21
|
console.log(chalk.cyan('Available Globio services:'));
|
|
19
22
|
ALL_SERVICES.forEach((service) => {
|
package/src/index.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { login } from './auth/login.js';
|
|
4
4
|
import { logout } from './auth/logout.js';
|
|
5
|
+
import { useProfile } from './auth/useProfile.js';
|
|
5
6
|
import { whoami } from './auth/whoami.js';
|
|
6
7
|
import { init } from './commands/init.js';
|
|
7
|
-
import { projectsList, projectsUse } from './commands/projects.js';
|
|
8
|
+
import { projectsCreate, projectsList, projectsUse } from './commands/projects.js';
|
|
8
9
|
import { servicesList } from './commands/services.js';
|
|
9
10
|
import {
|
|
10
11
|
functionsList,
|
|
@@ -32,46 +33,79 @@ program
|
|
|
32
33
|
.addHelpText('beforeAll', () => {
|
|
33
34
|
printBanner(version);
|
|
34
35
|
return '';
|
|
35
|
-
})
|
|
36
|
+
})
|
|
37
|
+
.addHelpText(
|
|
38
|
+
'after',
|
|
39
|
+
`
|
|
40
|
+
Examples:
|
|
41
|
+
$ globio login
|
|
42
|
+
$ globio login --profile work
|
|
43
|
+
$ globio use work
|
|
44
|
+
$ globio projects list
|
|
45
|
+
$ globio projects use proj_abc123
|
|
46
|
+
$ globio functions deploy my-function
|
|
47
|
+
$ globio migrate firestore --from ./key.json --all
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
Credentials are stored in ~/.globio/profiles/
|
|
50
|
+
`
|
|
51
|
+
);
|
|
40
52
|
|
|
41
|
-
program
|
|
53
|
+
program
|
|
54
|
+
.command('login')
|
|
55
|
+
.description('Log in to your Globio account')
|
|
56
|
+
.option('-p, --profile <name>', 'Profile name', 'default')
|
|
57
|
+
.option('--token', 'Use a personal access token')
|
|
58
|
+
.action(login);
|
|
59
|
+
program.command('logout').description('Log out').option('--profile <name>', 'Use a specific profile').action(logout);
|
|
60
|
+
program.command('whoami').description('Show current account and project').option('--profile <name>', 'Use a specific profile').action(whoami);
|
|
61
|
+
program.command('use <profile>').description('Switch active profile').action(useProfile);
|
|
62
|
+
|
|
63
|
+
program.command('init').description('Initialize a Globio project').option('--profile <name>', 'Use a specific profile').action(init);
|
|
42
64
|
|
|
43
65
|
const projects = program.command('projects').description('Manage projects');
|
|
44
|
-
projects.command('list').description('List projects').action(projectsList);
|
|
45
|
-
projects.command('
|
|
66
|
+
projects.command('list').description('List projects').option('--profile <name>', 'Use a specific profile').action(projectsList);
|
|
67
|
+
projects.command('create').description('Create a project').option('--profile <name>', 'Use a specific profile').action(projectsCreate);
|
|
68
|
+
projects.command('use <projectId>').description('Set active project').option('--profile <name>', 'Use a specific profile').action(projectsUse);
|
|
46
69
|
|
|
47
|
-
program.command('services').description('List available Globio services').action(servicesList);
|
|
70
|
+
program.command('services').description('List available Globio services').option('--profile <name>', 'Use a specific profile').action(servicesList);
|
|
48
71
|
|
|
49
72
|
const functions = program
|
|
50
73
|
.command('functions')
|
|
51
74
|
.alias('fn')
|
|
52
75
|
.description('Manage GlobalCode edge functions');
|
|
53
76
|
|
|
54
|
-
functions.command('list').description('List all functions').action(functionsList);
|
|
55
|
-
functions.command('create <slug>').description('Scaffold a new function file locally').action(functionsCreate);
|
|
77
|
+
functions.command('list').description('List all functions').option('--profile <name>', 'Use a specific profile').action(functionsList);
|
|
78
|
+
functions.command('create <slug>').description('Scaffold a new function file locally').option('--profile <name>', 'Use a specific profile').action(functionsCreate);
|
|
56
79
|
functions
|
|
57
80
|
.command('deploy <slug>')
|
|
58
81
|
.description('Deploy a function to GlobalCode')
|
|
59
82
|
.option('-f, --file <path>', 'Path to function file')
|
|
60
83
|
.option('-n, --name <name>', 'Display name')
|
|
84
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
61
85
|
.action(functionsDeploy);
|
|
62
86
|
functions
|
|
63
87
|
.command('invoke <slug>')
|
|
64
88
|
.description('Invoke a function')
|
|
65
89
|
.option('-i, --input <json>', 'JSON input payload')
|
|
90
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
66
91
|
.action(functionsInvoke);
|
|
67
92
|
functions
|
|
68
93
|
.command('logs <slug>')
|
|
69
94
|
.description('Show invocation history')
|
|
70
95
|
.option('-l, --limit <n>', 'Number of entries', '20')
|
|
96
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
71
97
|
.action(functionsLogs);
|
|
72
|
-
functions.command('delete <slug>').description('Delete a function').action(functionsDelete);
|
|
73
|
-
functions
|
|
74
|
-
|
|
98
|
+
functions.command('delete <slug>').description('Delete a function').option('--profile <name>', 'Use a specific profile').action(functionsDelete);
|
|
99
|
+
functions
|
|
100
|
+
.command('enable <slug>')
|
|
101
|
+
.description('Enable a function')
|
|
102
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
103
|
+
.action((slug, options) => functionsToggle(slug, true, options));
|
|
104
|
+
functions
|
|
105
|
+
.command('disable <slug>')
|
|
106
|
+
.description('Disable a function')
|
|
107
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
108
|
+
.action((slug, options) => functionsToggle(slug, false, options));
|
|
75
109
|
|
|
76
110
|
const migrate = program
|
|
77
111
|
.command('migrate')
|
|
@@ -83,6 +117,7 @@ migrate
|
|
|
83
117
|
.requiredOption('--from <path>', 'Path to Firebase service account JSON')
|
|
84
118
|
.option('--collection <name>', 'Migrate a specific collection')
|
|
85
119
|
.option('--all', 'Migrate all collections')
|
|
120
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
86
121
|
.action(migrateFirestore);
|
|
87
122
|
|
|
88
123
|
migrate
|
|
@@ -92,10 +127,18 @@ migrate
|
|
|
92
127
|
.requiredOption('--bucket <name>', 'Firebase Storage bucket')
|
|
93
128
|
.option('--folder <path>', 'Migrate a specific folder')
|
|
94
129
|
.option('--all', 'Migrate all files')
|
|
130
|
+
.option('--profile <name>', 'Use a specific profile')
|
|
95
131
|
.action(migrateFirebaseStorage);
|
|
96
132
|
|
|
97
|
-
|
|
98
|
-
|
|
133
|
+
async function main() {
|
|
134
|
+
if (process.argv.length <= 2) {
|
|
135
|
+
program.help();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await program.parseAsync();
|
|
99
139
|
}
|
|
100
140
|
|
|
101
|
-
|
|
141
|
+
main().catch((error) => {
|
|
142
|
+
console.error(error instanceof Error ? error.message : error);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
package/src/lib/config.ts
CHANGED
|
@@ -1,47 +1,139 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
3
5
|
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
projectId?: string;
|
|
7
|
-
projectName?: string;
|
|
8
|
-
email?: string;
|
|
6
|
+
interface GlobalConfig {
|
|
7
|
+
active_profile: string;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export interface ProfileData {
|
|
11
|
+
pat: string;
|
|
12
|
+
account_email: string;
|
|
13
|
+
account_name: string;
|
|
14
|
+
active_project_id?: string;
|
|
15
|
+
active_project_name?: string;
|
|
16
|
+
project_api_key?: string;
|
|
17
|
+
created_at: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const baseDir = path.join(os.homedir(), '.globio');
|
|
21
|
+
const profilesDir = path.join(baseDir, 'profiles');
|
|
22
|
+
const configPath = path.join(baseDir, 'config.json');
|
|
23
|
+
|
|
24
|
+
function ensureBaseDir() {
|
|
25
|
+
mkdirSync(baseDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureProfilesDir() {
|
|
29
|
+
ensureBaseDir();
|
|
30
|
+
mkdirSync(profilesDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readGlobalConfig(): GlobalConfig {
|
|
34
|
+
if (!existsSync(configPath)) {
|
|
35
|
+
return { active_profile: 'default' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf-8')) as Partial<GlobalConfig>;
|
|
40
|
+
return {
|
|
41
|
+
active_profile: raw.active_profile ?? 'default',
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
return { active_profile: 'default' };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeGlobalConfig(data: GlobalConfig) {
|
|
49
|
+
ensureBaseDir();
|
|
50
|
+
writeFileSync(configPath, JSON.stringify(data, null, 2) + '\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function profilePath(name: string) {
|
|
54
|
+
return path.join(profilesDir, `${name}.json`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readProfile(name: string): ProfileData | null {
|
|
58
|
+
const file = profilePath(name);
|
|
59
|
+
if (!existsSync(file)) return null;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(readFileSync(file, 'utf-8')) as ProfileData;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeProfile(name: string, data: ProfileData) {
|
|
69
|
+
ensureProfilesDir();
|
|
70
|
+
writeFileSync(profilePath(name), JSON.stringify(data, null, 2) + '\n');
|
|
71
|
+
}
|
|
15
72
|
|
|
16
73
|
export const config = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
74
|
+
getBaseDir: () => baseDir,
|
|
75
|
+
getProfilesDir: () => profilesDir,
|
|
76
|
+
getActiveProfile: (): string => readGlobalConfig().active_profile,
|
|
77
|
+
setActiveProfile: (name: string): void => {
|
|
78
|
+
writeGlobalConfig({ active_profile: name });
|
|
79
|
+
},
|
|
80
|
+
getProfile: (name?: string): ProfileData | null => {
|
|
81
|
+
const profileName = name ?? config.getActiveProfile();
|
|
82
|
+
if (!profileName) return null;
|
|
83
|
+
return readProfile(profileName);
|
|
84
|
+
},
|
|
85
|
+
setProfile: (name: string, data: Partial<ProfileData>): void => {
|
|
86
|
+
const existing = readProfile(name);
|
|
87
|
+
const next: ProfileData = {
|
|
88
|
+
pat: data.pat ?? existing?.pat ?? '',
|
|
89
|
+
account_email: data.account_email ?? existing?.account_email ?? '',
|
|
90
|
+
account_name: data.account_name ?? existing?.account_name ?? '',
|
|
91
|
+
active_project_id: data.active_project_id ?? existing?.active_project_id,
|
|
92
|
+
active_project_name: data.active_project_name ?? existing?.active_project_name,
|
|
93
|
+
project_api_key: data.project_api_key ?? existing?.project_api_key,
|
|
94
|
+
created_at: data.created_at ?? existing?.created_at ?? Date.now(),
|
|
95
|
+
};
|
|
96
|
+
writeProfile(name, next);
|
|
97
|
+
},
|
|
98
|
+
deleteProfile: (name: string): void => {
|
|
99
|
+
const file = profilePath(name);
|
|
100
|
+
if (existsSync(file)) {
|
|
101
|
+
rmSync(file);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
listProfiles: (): string[] => {
|
|
105
|
+
if (!existsSync(profilesDir)) return [];
|
|
106
|
+
return readdirSync(profilesDir)
|
|
107
|
+
.filter((file) => file.endsWith('.json'))
|
|
108
|
+
.map((file) => file.replace(/\.json$/, ''))
|
|
109
|
+
.sort();
|
|
110
|
+
},
|
|
111
|
+
getActiveProfileData: (): ProfileData | null => {
|
|
112
|
+
const active = config.getActiveProfile();
|
|
113
|
+
if (!active) return null;
|
|
114
|
+
return config.getProfile(active);
|
|
115
|
+
},
|
|
116
|
+
requireAuth: (profileName?: string): { pat: string; profileName: string } => {
|
|
117
|
+
const resolvedProfile = profileName ?? config.getActiveProfile() ?? 'default';
|
|
118
|
+
const profile = config.getProfile(resolvedProfile);
|
|
119
|
+
if (!profile?.pat) {
|
|
30
120
|
console.error(chalk.red('Not logged in. Run: npx @globio/cli login'));
|
|
31
121
|
process.exit(1);
|
|
32
122
|
}
|
|
33
|
-
return
|
|
123
|
+
return { pat: profile.pat, profileName: resolvedProfile };
|
|
34
124
|
},
|
|
35
|
-
requireProject: () => {
|
|
36
|
-
const
|
|
37
|
-
|
|
125
|
+
requireProject: (profileName?: string): { projectId: string; projectName: string } => {
|
|
126
|
+
const resolvedProfile = profileName ?? config.getActiveProfile() ?? 'default';
|
|
127
|
+
const profile = config.getProfile(resolvedProfile);
|
|
128
|
+
if (!profile?.active_project_id) {
|
|
38
129
|
console.error(
|
|
39
130
|
chalk.red('No active project. Run: npx @globio/cli projects use <projectId>')
|
|
40
131
|
);
|
|
41
132
|
process.exit(1);
|
|
42
133
|
}
|
|
43
|
-
return
|
|
134
|
+
return {
|
|
135
|
+
projectId: profile.active_project_id,
|
|
136
|
+
projectName: profile.active_project_name ?? 'unnamed',
|
|
137
|
+
};
|
|
44
138
|
},
|
|
45
139
|
};
|
|
46
|
-
|
|
47
|
-
export type { GlobioConfig };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { config } from './config.js';
|
|
2
|
+
|
|
3
|
+
const API_BASE_URL = 'https://api.globio.stanlink.online';
|
|
4
|
+
const CONSOLE_BASE_URL = 'https://console.globio.stanlink.online';
|
|
5
|
+
|
|
6
|
+
interface ManageRequestOptions {
|
|
7
|
+
method?: string;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
token?: string;
|
|
10
|
+
profileName?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ManageAccount {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
display_name: string | null;
|
|
17
|
+
avatar_url: string | null;
|
|
18
|
+
created_at: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ManageOrg {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
role: 'owner' | 'admin' | 'developer' | 'viewer';
|
|
26
|
+
created_at: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ManageProject {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
slug: string;
|
|
33
|
+
org_id: string;
|
|
34
|
+
org_name: string;
|
|
35
|
+
environment: string;
|
|
36
|
+
active: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ManageProjectKey {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
key_prefix: string;
|
|
43
|
+
scope: string;
|
|
44
|
+
created_at: number;
|
|
45
|
+
last_used_at: number | null;
|
|
46
|
+
token?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getAuthToken(explicitToken?: string, profileName?: string): string | undefined {
|
|
50
|
+
if (explicitToken) return explicitToken;
|
|
51
|
+
return config.getProfile(profileName)?.pat;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function manageRequest<T>(
|
|
55
|
+
path: string,
|
|
56
|
+
options: ManageRequestOptions = {}
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
const headers = new Headers();
|
|
59
|
+
if (!(options.body instanceof FormData)) {
|
|
60
|
+
headers.set('Content-Type', 'application/json');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const token = getAuthToken(options.token, options.profileName);
|
|
64
|
+
if (token) {
|
|
65
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const response = await fetch(`${API_BASE_URL}/manage${path}`, {
|
|
69
|
+
method: options.method ?? 'GET',
|
|
70
|
+
headers,
|
|
71
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const payload = await response.json().catch(() => ({}));
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(payload.error || payload.message || 'Management request failed');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (payload.data ?? payload) as T;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getConsoleCliAuthUrl(state: string): string {
|
|
83
|
+
return `${CONSOLE_BASE_URL}/cli-auth?state=${encodeURIComponent(state)}`;
|
|
84
|
+
}
|
package/src/lib/sdk.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Globio } from '@globio/sdk';
|
|
2
2
|
import { config } from './config.js';
|
|
3
3
|
|
|
4
|
-
export function getClient(): Globio {
|
|
5
|
-
const
|
|
6
|
-
config.requireProject();
|
|
4
|
+
export function getClient(profileName?: string): Globio {
|
|
5
|
+
const { pat } = config.requireAuth(profileName);
|
|
6
|
+
const { projectId } = config.requireProject(profileName);
|
|
7
|
+
const profile = config.getProfile(profileName);
|
|
8
|
+
const apiKey = profile?.project_api_key ?? pat;
|
|
9
|
+
void projectId;
|
|
7
10
|
return new Globio({ apiKey });
|
|
8
11
|
}
|
|
9
12
|
|
package/src/prompts/init.ts
CHANGED
|
@@ -3,18 +3,6 @@ import * as p from '@clack/prompts';
|
|
|
3
3
|
export async function promptInit() {
|
|
4
4
|
return p.group(
|
|
5
5
|
{
|
|
6
|
-
apiKey: () =>
|
|
7
|
-
p.text({
|
|
8
|
-
message: 'Globio API key',
|
|
9
|
-
placeholder: 'gk_live_...',
|
|
10
|
-
validate: (value) => (!value ? 'Required' : undefined),
|
|
11
|
-
}),
|
|
12
|
-
projectId: () =>
|
|
13
|
-
p.text({
|
|
14
|
-
message: 'Project ID',
|
|
15
|
-
placeholder: 'proj_...',
|
|
16
|
-
validate: (value) => (!value ? 'Required' : undefined),
|
|
17
|
-
}),
|
|
18
6
|
migrateFromFirebase: () =>
|
|
19
7
|
p.confirm({
|
|
20
8
|
message: 'Migrating from Firebase?',
|