@globio/cli 0.1.3 → 0.1.5

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/src/index.ts CHANGED
@@ -2,9 +2,11 @@
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
8
  import { projectsCreate, projectsList, projectsUse } from './commands/projects.js';
9
+ import { profilesList } from './commands/profiles.js';
8
10
  import { servicesList } from './commands/services.js';
9
11
  import {
10
12
  functionsList,
@@ -32,51 +34,89 @@ program
32
34
  .addHelpText('beforeAll', () => {
33
35
  printBanner(version);
34
36
  return '';
35
- });
37
+ })
38
+ .addHelpText(
39
+ 'after',
40
+ `
41
+ Examples:
42
+ $ globio login
43
+ $ globio login --profile work
44
+ $ globio use work
45
+ $ globio projects list
46
+ $ globio projects use proj_abc123
47
+ $ globio functions deploy my-function
48
+ $ globio migrate firestore --from ./key.json --all
49
+
50
+ Credentials are stored in ~/.globio/profiles/
51
+ `
52
+ );
36
53
 
37
54
  program
38
55
  .command('login')
39
56
  .description('Log in to your Globio account')
57
+ .option('-p, --profile <name>', 'Profile name', 'default')
40
58
  .option('--token', 'Use a personal access token')
41
59
  .action(login);
42
- program.command('logout').description('Log out').action(logout);
43
- program.command('whoami').description('Show current account and project').action(whoami);
60
+ program.command('logout').description('Log out').option('--profile <name>', 'Use a specific profile').action(logout);
61
+ program.command('whoami').description('Show current account and project').option('--profile <name>', 'Use a specific profile').action(whoami);
62
+ program.command('use <profile>').description('Switch active profile').action(useProfile);
63
+
64
+ program.command('init').description('Initialize a Globio project').option('--profile <name>', 'Use a specific profile').action(init);
65
+
66
+ const profiles = program
67
+ .command('profiles')
68
+ .description('Manage login profiles')
69
+ .action(profilesList);
44
70
 
45
- program.command('init').description('Initialize a Globio project').action(init);
71
+ profiles
72
+ .command('list')
73
+ .description('List all profiles')
74
+ .action(profilesList);
46
75
 
47
76
  const projects = program.command('projects').description('Manage projects');
48
- projects.command('list').description('List projects').action(projectsList);
49
- projects.command('create').description('Create a project').action(projectsCreate);
50
- projects.command('use <projectId>').description('Set active project').action(projectsUse);
77
+ projects.command('list').description('List projects').option('--profile <name>', 'Use a specific profile').action(projectsList);
78
+ projects.command('create').description('Create a project').option('--profile <name>', 'Use a specific profile').action(projectsCreate);
79
+ projects.command('use <projectId>').description('Set active project').option('--profile <name>', 'Use a specific profile').action(projectsUse);
51
80
 
52
- program.command('services').description('List available Globio services').action(servicesList);
81
+ program.command('services').description('List available Globio services').option('--profile <name>', 'Use a specific profile').action(servicesList);
53
82
 
54
83
  const functions = program
55
84
  .command('functions')
56
85
  .alias('fn')
57
86
  .description('Manage GlobalCode edge functions');
58
87
 
59
- functions.command('list').description('List all functions').action(functionsList);
60
- functions.command('create <slug>').description('Scaffold a new function file locally').action(functionsCreate);
88
+ functions.command('list').description('List all functions').option('--profile <name>', 'Use a specific profile').action(functionsList);
89
+ functions.command('create <slug>').description('Scaffold a new function file locally').option('--profile <name>', 'Use a specific profile').action(functionsCreate);
61
90
  functions
62
91
  .command('deploy <slug>')
63
92
  .description('Deploy a function to GlobalCode')
64
93
  .option('-f, --file <path>', 'Path to function file')
65
94
  .option('-n, --name <name>', 'Display name')
95
+ .option('--profile <name>', 'Use a specific profile')
66
96
  .action(functionsDeploy);
67
97
  functions
68
98
  .command('invoke <slug>')
69
99
  .description('Invoke a function')
70
100
  .option('-i, --input <json>', 'JSON input payload')
101
+ .option('--profile <name>', 'Use a specific profile')
71
102
  .action(functionsInvoke);
72
103
  functions
73
104
  .command('logs <slug>')
74
105
  .description('Show invocation history')
75
106
  .option('-l, --limit <n>', 'Number of entries', '20')
107
+ .option('--profile <name>', 'Use a specific profile')
76
108
  .action(functionsLogs);
77
- functions.command('delete <slug>').description('Delete a function').action(functionsDelete);
78
- functions.command('enable <slug>').description('Enable a function').action((slug) => functionsToggle(slug, true));
79
- functions.command('disable <slug>').description('Disable a function').action((slug) => functionsToggle(slug, false));
109
+ functions.command('delete <slug>').description('Delete a function').option('--profile <name>', 'Use a specific profile').action(functionsDelete);
110
+ functions
111
+ .command('enable <slug>')
112
+ .description('Enable a function')
113
+ .option('--profile <name>', 'Use a specific profile')
114
+ .action((slug, options) => functionsToggle(slug, true, options));
115
+ functions
116
+ .command('disable <slug>')
117
+ .description('Disable a function')
118
+ .option('--profile <name>', 'Use a specific profile')
119
+ .action((slug, options) => functionsToggle(slug, false, options));
80
120
 
81
121
  const migrate = program
82
122
  .command('migrate')
@@ -88,6 +128,7 @@ migrate
88
128
  .requiredOption('--from <path>', 'Path to Firebase service account JSON')
89
129
  .option('--collection <name>', 'Migrate a specific collection')
90
130
  .option('--all', 'Migrate all collections')
131
+ .option('--profile <name>', 'Use a specific profile')
91
132
  .action(migrateFirestore);
92
133
 
93
134
  migrate
@@ -97,6 +138,7 @@ migrate
97
138
  .requiredOption('--bucket <name>', 'Firebase Storage bucket')
98
139
  .option('--folder <path>', 'Migrate a specific folder')
99
140
  .option('--all', 'Migrate all files')
141
+ .option('--profile <name>', 'Use a specific profile')
100
142
  .action(migrateFirebaseStorage);
101
143
 
102
144
  async function main() {
package/src/lib/config.ts CHANGED
@@ -1,91 +1,139 @@
1
1
  import chalk from 'chalk';
2
- import Conf from 'conf';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
3
5
 
4
- interface GlobioConfig {
5
- pat?: string;
6
- accountEmail?: string;
7
- accountName?: string;
8
- projectId?: string;
9
- projectName?: string;
10
- projectApiKeys?: Record<string, string>;
11
- projectNames?: Record<string, string>;
6
+ interface GlobalConfig {
7
+ active_profile: string;
12
8
  }
13
9
 
14
- const store = new Conf<GlobioConfig>({
15
- projectName: 'globio',
16
- defaults: {},
17
- });
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
+ }
18
72
 
19
73
  export const config = {
20
- get: (): GlobioConfig => store.store,
21
- set: (values: Partial<GlobioConfig>) => {
22
- Object.entries(values).forEach(([key, value]) => {
23
- if (value !== undefined) {
24
- store.set(key as keyof GlobioConfig, value);
25
- }
26
- });
74
+ getBaseDir: () => baseDir,
75
+ getProfilesDir: () => profilesDir,
76
+ getActiveProfile: (): string => readGlobalConfig().active_profile,
77
+ setActiveProfile: (name: string): void => {
78
+ writeGlobalConfig({ active_profile: name });
27
79
  },
28
- clear: () => store.clear(),
29
- getPat: () => store.get('pat'),
30
- requirePat: () => {
31
- const pat = store.get('pat');
32
- if (!pat) {
33
- console.error(chalk.red('Not logged in. Run: npx @globio/cli login'));
34
- process.exit(1);
35
- }
36
- return pat;
80
+ getProfile: (name?: string): ProfileData | null => {
81
+ const profileName = name ?? config.getActiveProfile();
82
+ if (!profileName) return null;
83
+ return readProfile(profileName);
37
84
  },
38
- requireProject: () => {
39
- const projectId = store.get('projectId');
40
- if (!projectId) {
41
- console.error(
42
- chalk.red('No active project. Run: npx @globio/cli projects use <projectId>')
43
- );
44
- process.exit(1);
45
- }
46
- return projectId;
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);
47
97
  },
48
- setProjectAuth: (projectId: string, apiKey: string, projectName?: string) => {
49
- const projectApiKeys = store.get('projectApiKeys') ?? {};
50
- const projectNames = store.get('projectNames') ?? {};
51
- projectApiKeys[projectId] = apiKey;
52
- if (projectName) {
53
- projectNames[projectId] = projectName;
54
- }
55
-
56
- store.set('projectApiKeys', projectApiKeys);
57
- store.set('projectNames', projectNames);
58
- store.set('projectId', projectId);
59
- if (projectName) {
60
- store.set('projectName', projectName);
98
+ deleteProfile: (name: string): void => {
99
+ const file = profilePath(name);
100
+ if (existsSync(file)) {
101
+ rmSync(file);
61
102
  }
62
103
  },
63
- getProjectApiKey: (projectId: string) => {
64
- const projectApiKeys = store.get('projectApiKeys') ?? {};
65
- return projectApiKeys[projectId];
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();
66
110
  },
67
- requireProjectApiKey: () => {
68
- const projectId = store.get('projectId');
69
- if (!projectId) {
70
- console.error(
71
- chalk.red('No active project. Run: npx @globio/cli projects use <projectId>')
72
- );
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) {
120
+ console.error(chalk.red('Not logged in. Run: npx @globio/cli login'));
73
121
  process.exit(1);
74
122
  }
75
-
76
- const projectApiKeys = store.get('projectApiKeys') ?? {};
77
- const apiKey = projectApiKeys[projectId];
78
- if (!apiKey) {
123
+ return { pat: profile.pat, profileName: resolvedProfile };
124
+ },
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) {
79
129
  console.error(
80
- chalk.red(
81
- 'No project API key stored for the active project. Run: npx @globio/cli projects use <projectId>'
82
- )
130
+ chalk.red('No active project. Run: npx @globio/cli projects use <projectId>')
83
131
  );
84
132
  process.exit(1);
85
133
  }
86
-
87
- return apiKey;
134
+ return {
135
+ projectId: profile.active_project_id,
136
+ projectName: profile.active_project_name ?? 'unnamed',
137
+ };
88
138
  },
89
139
  };
90
-
91
- export type { GlobioConfig };
package/src/lib/manage.ts CHANGED
@@ -7,6 +7,7 @@ interface ManageRequestOptions {
7
7
  method?: string;
8
8
  body?: unknown;
9
9
  token?: string;
10
+ profileName?: string;
10
11
  }
11
12
 
12
13
  export interface ManageAccount {
@@ -45,9 +46,9 @@ export interface ManageProjectKey {
45
46
  token?: string;
46
47
  }
47
48
 
48
- function getAuthToken(explicitToken?: string): string | undefined {
49
+ function getAuthToken(explicitToken?: string, profileName?: string): string | undefined {
49
50
  if (explicitToken) return explicitToken;
50
- return config.getPat();
51
+ return config.getProfile(profileName)?.pat;
51
52
  }
52
53
 
53
54
  export async function manageRequest<T>(
@@ -59,7 +60,7 @@ export async function manageRequest<T>(
59
60
  headers.set('Content-Type', 'application/json');
60
61
  }
61
62
 
62
- const token = getAuthToken(options.token);
63
+ const token = getAuthToken(options.token, options.profileName);
63
64
  if (token) {
64
65
  headers.set('Authorization', `Bearer ${token}`);
65
66
  }
package/src/lib/sdk.ts CHANGED
@@ -1,8 +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 apiKey = config.requireProjectApiKey();
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;
6
10
  return new Globio({ apiKey });
7
11
  }
8
12