@juuno-sdk/cli 1.0.10 → 1.0.12
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 +61 -11
- package/bin/cli-router.js +18 -4
- package/bin/cli.js +40 -15
- package/dist/cli/src/auth/index.js +128 -39
- package/dist/cli/src/deploy/index.js +11 -6
- package/dist/cli/src/info/index.js +3 -3
- package/dist/cli/src/list/index.js +3 -3
- package/dist/cli/src/login/index.js +11 -8
- package/dist/cli/src/logout/index.js +29 -8
- package/dist/cli/src/whoami/index.js +31 -10
- package/dist/cli/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/auth/index.d.ts +84 -0
- package/dist/types/src/auth/index.d.ts.map +1 -0
- package/dist/{cli → types}/src/deploy/index.d.ts +6 -0
- package/dist/{cli → types}/src/deploy/index.d.ts.map +1 -1
- package/dist/{cli → types}/src/info/index.d.ts.map +1 -1
- package/dist/{cli → types}/src/list/index.d.ts.map +1 -1
- package/dist/{cli → types}/src/login/index.d.ts +3 -1
- package/dist/types/src/login/index.d.ts.map +1 -0
- package/dist/types/src/logout/index.d.ts +12 -0
- package/dist/types/src/logout/index.d.ts.map +1 -0
- package/dist/types/src/whoami/index.d.ts +12 -0
- package/dist/types/src/whoami/index.d.ts.map +1 -0
- package/package.json +26 -26
- package/dist/assets/main-B8xiNI5_.js +0 -1
- package/dist/assets/main-Bf2N9gBQ.js +0 -1
- package/dist/assets/main-Bo8Yyr87.js +0 -1
- package/dist/assets/main-C3hW0d-b.css +0 -1
- package/dist/assets/main-C6ZAJydH.js +0 -1
- package/dist/assets/main-CCYNXtGp.css +0 -1
- package/dist/assets/main-COSZCfRq.css +0 -1
- package/dist/assets/main-CnpUG-52.js +0 -1
- package/dist/assets/main-D0353jp3.js +0 -1
- package/dist/assets/main-D7UD6CXw.css +0 -1
- package/dist/assets/main-DC3n4QV-.css +0 -1
- package/dist/assets/main-DWrNgA-L.js +0 -1
- package/dist/assets/main-HbFBqUMx.js +0 -1
- package/dist/assets/main-jQYqLcLk.js +0 -1
- package/dist/cli/src/auth/index.d.ts +0 -51
- package/dist/cli/src/auth/index.d.ts.map +0 -1
- package/dist/cli/src/login/index.d.ts.map +0 -1
- package/dist/cli/src/logout/index.d.ts +0 -5
- package/dist/cli/src/logout/index.d.ts.map +0 -1
- package/dist/cli/src/monorepo/index.d.ts +0 -37
- package/dist/cli/src/monorepo/index.d.ts.map +0 -1
- package/dist/cli/src/monorepo/index.js +0 -94
- package/dist/cli/src/whoami/index.d.ts +0 -5
- package/dist/cli/src/whoami/index.d.ts.map +0 -1
- /package/dist/{cli → types}/src/dev/index.d.ts +0 -0
- /package/dist/{cli → types}/src/dev/index.d.ts.map +0 -0
- /package/dist/{cli → types}/src/info/index.d.ts +0 -0
- /package/dist/{cli → types}/src/list/index.d.ts +0 -0
- /package/dist/{cli → types}/src/simulator/index.d.ts +0 -0
- /package/dist/{cli → types}/src/simulator/index.d.ts.map +0 -0
- /package/dist/{cli → types}/src/utils/fs.d.ts +0 -0
- /package/dist/{cli → types}/src/utils/fs.d.ts.map +0 -0
package/README.md
CHANGED
|
@@ -75,6 +75,39 @@ npx juuno-cli whoami
|
|
|
75
75
|
npx juuno-cli logout
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Profiles
|
|
79
|
+
|
|
80
|
+
The CLI supports multiple named profiles (like AWS CLI), each with its own
|
|
81
|
+
token and API URL stored in `~/.juuno/credentials.json`. Use profiles to switch
|
|
82
|
+
between production, staging, or multiple accounts without re-logging in.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Log in to the default (production) profile
|
|
86
|
+
npx juuno-cli login
|
|
87
|
+
|
|
88
|
+
# Log in to a "stage" profile pointing at staging
|
|
89
|
+
npx juuno-cli login --profile stage --api-url https://api.stage.juuno.co
|
|
90
|
+
|
|
91
|
+
# Use a profile for a single command
|
|
92
|
+
npx juuno-cli list --profile stage
|
|
93
|
+
npx juuno-cli deploy --profile stage
|
|
94
|
+
|
|
95
|
+
# Or set it for the whole shell
|
|
96
|
+
export JUUNO_PROFILE=stage
|
|
97
|
+
npx juuno-cli list
|
|
98
|
+
|
|
99
|
+
# Inspect all profiles
|
|
100
|
+
npx juuno-cli whoami --all
|
|
101
|
+
|
|
102
|
+
# Log out of a single profile, or all of them
|
|
103
|
+
npx juuno-cli logout --profile stage
|
|
104
|
+
npx juuno-cli logout --all
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The API URL given at login time is stored with the profile, so `--profile
|
|
108
|
+
stage` alone is enough on subsequent commands. Override it on any single
|
|
109
|
+
command with `JUUNO_API_URL`.
|
|
110
|
+
|
|
78
111
|
### Deployment
|
|
79
112
|
|
|
80
113
|
Deploy your app to Juuno.
|
|
@@ -232,28 +265,38 @@ Options:
|
|
|
232
265
|
juuno-cli login [options]
|
|
233
266
|
|
|
234
267
|
Options:
|
|
268
|
+
--profile <name> Profile to save credentials under (default: "default")
|
|
269
|
+
--api-url <url> API URL to authenticate against and store on the profile
|
|
270
|
+
(default: profile's URL or https://api.juuno.co)
|
|
235
271
|
--email <email> Your Juuno email (will prompt if not provided)
|
|
236
272
|
--password <pwd> Your Juuno password (will prompt if not provided)
|
|
237
273
|
--help Show help
|
|
238
274
|
```
|
|
239
275
|
|
|
240
|
-
Saves credentials to `~/.juuno/credentials.json` for automatic reuse.
|
|
276
|
+
Saves credentials to `~/.juuno/credentials.json` for automatic reuse. Multiple
|
|
277
|
+
profiles can coexist in the same file.
|
|
241
278
|
|
|
242
279
|
### Logout Command
|
|
243
280
|
|
|
244
281
|
```bash
|
|
245
|
-
juuno-cli logout
|
|
246
|
-
```
|
|
282
|
+
juuno-cli logout [options]
|
|
247
283
|
|
|
248
|
-
|
|
284
|
+
Options:
|
|
285
|
+
--profile <name> Profile to log out of (default: "default")
|
|
286
|
+
--all Remove every profile
|
|
287
|
+
--help Show help
|
|
288
|
+
```
|
|
249
289
|
|
|
250
290
|
### Whoami Command
|
|
251
291
|
|
|
252
292
|
```bash
|
|
253
|
-
juuno-cli whoami
|
|
254
|
-
```
|
|
293
|
+
juuno-cli whoami [options]
|
|
255
294
|
|
|
256
|
-
|
|
295
|
+
Options:
|
|
296
|
+
--profile <name> Profile to inspect (default: "default")
|
|
297
|
+
--all List every configured profile
|
|
298
|
+
--help Show help
|
|
299
|
+
```
|
|
257
300
|
|
|
258
301
|
### Deploy Command
|
|
259
302
|
|
|
@@ -262,6 +305,7 @@ juuno-cli deploy [options]
|
|
|
262
305
|
|
|
263
306
|
Options:
|
|
264
307
|
--build-dir <dir> Build directory to deploy (default: ./dist)
|
|
308
|
+
--profile <name> Use credentials from the given profile
|
|
265
309
|
--token <token> Override with specific token (for CI/CD)
|
|
266
310
|
--email <email> Login with email instead of using saved credentials
|
|
267
311
|
--password <pwd> Use with --email
|
|
@@ -274,6 +318,7 @@ Options:
|
|
|
274
318
|
juuno-cli list [options]
|
|
275
319
|
|
|
276
320
|
Options:
|
|
321
|
+
--profile <name> Use credentials from the given profile
|
|
277
322
|
--token <token> Override with specific token (for CI/CD)
|
|
278
323
|
--email <email> Login with email instead of using saved credentials
|
|
279
324
|
--password <pwd> Use with --email
|
|
@@ -289,6 +334,7 @@ Arguments:
|
|
|
289
334
|
<app-id> The app ID to get information about
|
|
290
335
|
|
|
291
336
|
Options:
|
|
337
|
+
--profile <name> Use credentials from the given profile
|
|
292
338
|
--token <token> Override with specific token (for CI/CD)
|
|
293
339
|
--email <email> Login with email instead of using saved credentials
|
|
294
340
|
--password <pwd> Use with --email
|
|
@@ -298,16 +344,19 @@ Options:
|
|
|
298
344
|
## Environment Variables
|
|
299
345
|
|
|
300
346
|
```bash
|
|
301
|
-
#
|
|
302
|
-
export
|
|
347
|
+
# Profile to use (default: "default")
|
|
348
|
+
export JUUNO_PROFILE=stage
|
|
303
349
|
|
|
304
|
-
#
|
|
350
|
+
# API endpoint override (default: profile's URL or https://api.juuno.co)
|
|
305
351
|
export JUUNO_API_URL=https://api.stage.juuno.co
|
|
306
352
|
|
|
307
353
|
# Authentication token (for CI/CD, overrides saved credentials)
|
|
308
354
|
export JUUNO_API_TOKEN=your-auth-token
|
|
309
355
|
```
|
|
310
356
|
|
|
357
|
+
Precedence for API URL: `JUUNO_API_URL` > profile's stored `api_url` > default.
|
|
358
|
+
Precedence for profile: `--profile` flag > `JUUNO_PROFILE` > `"default"`.
|
|
359
|
+
|
|
311
360
|
## Deployment Workflow
|
|
312
361
|
|
|
313
362
|
### Quick Start
|
|
@@ -381,7 +430,8 @@ The CLI checks for credentials in this order:
|
|
|
381
430
|
|
|
382
431
|
1. `--token` flag (explicit override)
|
|
383
432
|
2. `JUUNO_API_TOKEN` environment variable (CI/CD)
|
|
384
|
-
3. Saved credentials
|
|
433
|
+
3. Saved credentials for the active profile in `~/.juuno/credentials.json`
|
|
434
|
+
(profile resolution: `--profile` > `JUUNO_PROFILE` > `"default"`)
|
|
385
435
|
4. `--email` and `--password` flags (one-off login)
|
|
386
436
|
5. Error: "Not logged in"
|
|
387
437
|
|
package/bin/cli-router.js
CHANGED
|
@@ -10,11 +10,15 @@ export function parseAuthOptions(args) {
|
|
|
10
10
|
const emailIndex = args.indexOf('--email');
|
|
11
11
|
const passwordIndex = args.indexOf('--password');
|
|
12
12
|
const tokenIndex = args.indexOf('--token');
|
|
13
|
+
const profileIndex = args.indexOf('--profile');
|
|
14
|
+
const apiUrlIndex = args.indexOf('--api-url');
|
|
13
15
|
|
|
14
16
|
return {
|
|
15
17
|
email: emailIndex !== -1 ? args[emailIndex + 1] : undefined,
|
|
16
18
|
password: passwordIndex !== -1 ? args[passwordIndex + 1] : undefined,
|
|
17
19
|
token: tokenIndex !== -1 ? args[tokenIndex + 1] : undefined,
|
|
20
|
+
profile: profileIndex !== -1 ? args[profileIndex + 1] : undefined,
|
|
21
|
+
apiUrl: apiUrlIndex !== -1 ? args[apiUrlIndex + 1] : undefined,
|
|
18
22
|
};
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -32,21 +36,30 @@ export async function runCommand(command, args, importPrefix) {
|
|
|
32
36
|
if (command === 'login') {
|
|
33
37
|
const { login } = await import(`${importPrefix}login/index.js`);
|
|
34
38
|
const authOptions = parseAuthOptions(args);
|
|
35
|
-
await login({
|
|
39
|
+
await login({
|
|
40
|
+
email: authOptions.email,
|
|
41
|
+
password: authOptions.password,
|
|
42
|
+
profile: authOptions.profile,
|
|
43
|
+
apiUrl: authOptions.apiUrl,
|
|
44
|
+
});
|
|
36
45
|
return 0;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
// Handle logout command.
|
|
40
49
|
if (command === 'logout') {
|
|
41
50
|
const { logout } = await import(`${importPrefix}logout/index.js`);
|
|
42
|
-
|
|
51
|
+
const authOptions = parseAuthOptions(args);
|
|
52
|
+
const all = args.includes('--all');
|
|
53
|
+
await logout({ profile: authOptions.profile, all });
|
|
43
54
|
return 0;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
// Handle whoami command.
|
|
47
58
|
if (command === 'whoami') {
|
|
48
59
|
const { whoami } = await import(`${importPrefix}whoami/index.js`);
|
|
49
|
-
|
|
60
|
+
const authOptions = parseAuthOptions(args);
|
|
61
|
+
const all = args.includes('--all');
|
|
62
|
+
await whoami({ profile: authOptions.profile, all });
|
|
50
63
|
return 0;
|
|
51
64
|
}
|
|
52
65
|
|
|
@@ -56,8 +69,9 @@ export async function runCommand(command, args, importPrefix) {
|
|
|
56
69
|
const buildDirIndex = args.indexOf('--build-dir');
|
|
57
70
|
const buildDir =
|
|
58
71
|
buildDirIndex !== -1 ? args[buildDirIndex + 1] : './dist';
|
|
72
|
+
const skipIfDeployed = args.includes('--skip-if-deployed');
|
|
59
73
|
const authOptions = parseAuthOptions(args);
|
|
60
|
-
await deployApp({ buildDir, ...authOptions });
|
|
74
|
+
await deployApp({ buildDir, skipIfDeployed, ...authOptions });
|
|
61
75
|
return 0;
|
|
62
76
|
}
|
|
63
77
|
|
package/bin/cli.js
CHANGED
|
@@ -36,16 +36,25 @@ Commands:
|
|
|
36
36
|
info <app-id> Show detailed information about an app
|
|
37
37
|
help Show this help message
|
|
38
38
|
|
|
39
|
+
Profiles:
|
|
40
|
+
The CLI supports multiple named profiles (like AWS CLI), each storing its own
|
|
41
|
+
token and API URL in ~/.juuno/credentials.json. Use profiles to switch between
|
|
42
|
+
environments (e.g. production, staging) without re-logging in.
|
|
43
|
+
|
|
44
|
+
--profile <name> Target a specific profile (default: "default")
|
|
45
|
+
JUUNO_PROFILE=<name> Same, via environment variable
|
|
46
|
+
|
|
39
47
|
Authentication Commands:
|
|
40
|
-
login [--email <email>] [--password <pwd>]
|
|
41
|
-
Authenticate and save credentials to
|
|
42
|
-
|
|
48
|
+
login [--profile <name>] [--api-url <url>] [--email <email>] [--password <pwd>]
|
|
49
|
+
Authenticate and save credentials to the given profile. The API URL used
|
|
50
|
+
at login time is stored with the profile, so --profile <name> alone is
|
|
51
|
+
enough on subsequent commands.
|
|
43
52
|
|
|
44
|
-
logout
|
|
45
|
-
Remove stored credentials.
|
|
53
|
+
logout [--profile <name>] [--all]
|
|
54
|
+
Remove stored credentials for a profile. --all removes every profile.
|
|
46
55
|
|
|
47
|
-
whoami
|
|
48
|
-
Show current authentication status.
|
|
56
|
+
whoami [--profile <name>] [--all]
|
|
57
|
+
Show current authentication status. --all lists every configured profile.
|
|
49
58
|
|
|
50
59
|
Dev Command (Unified Workflow):
|
|
51
60
|
juuno-cli dev
|
|
@@ -61,23 +70,45 @@ Simulator Options:
|
|
|
61
70
|
--serve-port <number> Port for Vite dev server in auto mode (default: 3000)
|
|
62
71
|
|
|
63
72
|
Deploy Options:
|
|
64
|
-
--build-dir <dir>
|
|
73
|
+
--build-dir <dir> Build directory to deploy (default: ./dist)
|
|
74
|
+
--skip-if-deployed Exit 0 with a notice if the manifest version is
|
|
75
|
+
already live, instead of erroring. Intended for CI.
|
|
65
76
|
|
|
66
77
|
Authentication Options (for deploy, list, info):
|
|
67
78
|
By default, these commands use the token saved by 'juuno-cli login'.
|
|
68
79
|
You can override with:
|
|
80
|
+
--profile <name> Use credentials from a named profile
|
|
69
81
|
--token <token> Use specific token (for CI/CD)
|
|
70
82
|
--email <email> Login with email/password (skips saved token)
|
|
71
83
|
--password <pwd> Use with --email
|
|
72
84
|
|
|
73
85
|
Environment Variables:
|
|
74
|
-
|
|
86
|
+
JUUNO_PROFILE Profile to use (default: "default")
|
|
87
|
+
JUUNO_API_URL API URL override (default: profile's URL or https://api.juuno.co)
|
|
75
88
|
JUUNO_API_TOKEN Authentication token (for CI/CD, skips saved credentials)
|
|
76
89
|
|
|
77
90
|
Examples:
|
|
78
91
|
# Authenticate (one-time setup)
|
|
79
92
|
juuno-cli login
|
|
80
93
|
|
|
94
|
+
# Log in to a staging profile
|
|
95
|
+
juuno-cli login --profile stage --api-url https://api.stage.juuno.co
|
|
96
|
+
|
|
97
|
+
# Use a specific profile for any command
|
|
98
|
+
juuno-cli list --profile stage
|
|
99
|
+
juuno-cli deploy --profile stage
|
|
100
|
+
|
|
101
|
+
# Or set it for the whole shell
|
|
102
|
+
export JUUNO_PROFILE=stage
|
|
103
|
+
juuno-cli list
|
|
104
|
+
|
|
105
|
+
# Inspect all profiles
|
|
106
|
+
juuno-cli whoami --all
|
|
107
|
+
|
|
108
|
+
# Log out of a single profile, or all of them
|
|
109
|
+
juuno-cli logout --profile stage
|
|
110
|
+
juuno-cli logout --all
|
|
111
|
+
|
|
81
112
|
# Start unified dev environment (recommended)
|
|
82
113
|
cd my-juuno-app
|
|
83
114
|
juuno-cli dev
|
|
@@ -94,12 +125,6 @@ Examples:
|
|
|
94
125
|
|
|
95
126
|
# Show app info (uses saved credentials)
|
|
96
127
|
juuno-cli info app_abc123
|
|
97
|
-
|
|
98
|
-
# Check authentication status
|
|
99
|
-
juuno-cli whoami
|
|
100
|
-
|
|
101
|
-
# Logout
|
|
102
|
-
juuno-cli logout
|
|
103
128
|
`);
|
|
104
129
|
process.exit(0);
|
|
105
130
|
}
|
|
@@ -2,16 +2,16 @@ import inquirer from 'inquirer';
|
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, } from 'fs';
|
|
5
|
+
export const DEFAULT_PROFILE = 'default';
|
|
6
|
+
const DEFAULT_API_URL = 'https://api.juuno.co';
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* In-memory cache of the parsed credentials file, invalidated on write.
|
|
9
|
+
* Prevents repeated disk reads across multiple auth helpers in a single command.
|
|
7
10
|
*/
|
|
11
|
+
let cachedFile = null;
|
|
8
12
|
function getCredentialsPath() {
|
|
9
|
-
|
|
10
|
-
return join(configDir, 'credentials.json');
|
|
13
|
+
return join(homedir(), '.juuno', 'credentials.json');
|
|
11
14
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Ensures the credentials directory exists.
|
|
14
|
-
*/
|
|
15
15
|
function ensureCredentialsDir() {
|
|
16
16
|
const configDir = join(homedir(), '.juuno');
|
|
17
17
|
if (!existsSync(configDir)) {
|
|
@@ -19,60 +19,147 @@ function ensureCredentialsDir() {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Reads and parses the credentials file. Migrates legacy single-profile
|
|
23
|
+
* files to the new profiles format transparently.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
function readCredentialsFile() {
|
|
26
|
+
if (cachedFile) {
|
|
27
|
+
return cachedFile;
|
|
28
|
+
}
|
|
29
|
+
const credentialsPath = getCredentialsPath();
|
|
30
|
+
if (!existsSync(credentialsPath)) {
|
|
31
|
+
cachedFile = { profiles: {} };
|
|
32
|
+
return cachedFile;
|
|
33
|
+
}
|
|
34
|
+
const fileContent = readFileSync(credentialsPath, 'utf-8');
|
|
35
|
+
const parsed = JSON.parse(fileContent);
|
|
36
|
+
if (parsed && typeof parsed === 'object' && 'profiles' in parsed) {
|
|
37
|
+
cachedFile = parsed;
|
|
38
|
+
return cachedFile;
|
|
39
|
+
}
|
|
40
|
+
// Legacy format: { token, api_url, created_at }. Migrate to default profile.
|
|
41
|
+
if (parsed && typeof parsed === 'object' && 'token' in parsed) {
|
|
42
|
+
const legacy = parsed;
|
|
43
|
+
cachedFile = {
|
|
44
|
+
profiles: {
|
|
45
|
+
[DEFAULT_PROFILE]: {
|
|
46
|
+
token: legacy.token,
|
|
47
|
+
api_url: legacy.api_url || DEFAULT_API_URL,
|
|
48
|
+
created_at: legacy.created_at || new Date().toISOString(),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return cachedFile;
|
|
53
|
+
}
|
|
54
|
+
cachedFile = { profiles: {} };
|
|
55
|
+
return cachedFile;
|
|
56
|
+
}
|
|
57
|
+
function writeCredentialsFile(file) {
|
|
25
58
|
ensureCredentialsDir();
|
|
26
|
-
const credentials = {
|
|
27
|
-
token,
|
|
28
|
-
api_url: getApiUrl(),
|
|
29
|
-
created_at: new Date().toISOString(),
|
|
30
|
-
};
|
|
31
59
|
const credentialsPath = getCredentialsPath();
|
|
32
|
-
writeFileSync(credentialsPath, JSON.stringify(
|
|
60
|
+
writeFileSync(credentialsPath, JSON.stringify(file, null, 2), {
|
|
33
61
|
mode: 0o600,
|
|
34
62
|
});
|
|
63
|
+
cachedFile = file;
|
|
35
64
|
}
|
|
36
65
|
/**
|
|
37
|
-
*
|
|
66
|
+
* Resolves the profile name from options, environment, or default.
|
|
38
67
|
*/
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return null;
|
|
68
|
+
export function resolveProfile(options = {}) {
|
|
69
|
+
if (options.profile) {
|
|
70
|
+
return options.profile;
|
|
43
71
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
return process.env.JUUNO_PROFILE || DEFAULT_PROFILE;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Formats a "--profile <name>" CLI flag suffix for help/error text, or an
|
|
76
|
+
* empty string when the profile is the default.
|
|
77
|
+
*/
|
|
78
|
+
export function formatProfileFlag(profile) {
|
|
79
|
+
return profile === DEFAULT_PROFILE ? '' : ` --profile ${profile}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Lists all saved profile names.
|
|
83
|
+
*/
|
|
84
|
+
export function listProfiles() {
|
|
85
|
+
return Object.keys(readCredentialsFile().profiles).sort();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the stored credentials for a profile, or null if missing.
|
|
89
|
+
*/
|
|
90
|
+
export function getProfile(profile) {
|
|
91
|
+
return readCredentialsFile().profiles[profile] || null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Saves authentication token to the given profile.
|
|
95
|
+
*/
|
|
96
|
+
export function saveToken(token, profile, apiUrl) {
|
|
97
|
+
const file = readCredentialsFile();
|
|
98
|
+
file.profiles[profile] = {
|
|
99
|
+
token,
|
|
100
|
+
api_url: apiUrl,
|
|
101
|
+
created_at: new Date().toISOString(),
|
|
102
|
+
};
|
|
103
|
+
writeCredentialsFile(file);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Loads authentication token for the given profile.
|
|
107
|
+
*/
|
|
108
|
+
export function loadToken(profile) {
|
|
109
|
+
return getProfile(profile)?.token || null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Removes a single profile from the credentials file.
|
|
113
|
+
* Deletes the file if no profiles remain.
|
|
114
|
+
*/
|
|
115
|
+
export function removeProfile(profile) {
|
|
116
|
+
const file = readCredentialsFile();
|
|
117
|
+
if (!(profile in file.profiles)) {
|
|
118
|
+
return false;
|
|
48
119
|
}
|
|
49
|
-
|
|
50
|
-
|
|
120
|
+
delete file.profiles[profile];
|
|
121
|
+
if (Object.keys(file.profiles).length === 0) {
|
|
122
|
+
const credentialsPath = getCredentialsPath();
|
|
123
|
+
if (existsSync(credentialsPath)) {
|
|
124
|
+
unlinkSync(credentialsPath);
|
|
125
|
+
}
|
|
126
|
+
cachedFile = { profiles: {} };
|
|
127
|
+
return true;
|
|
51
128
|
}
|
|
129
|
+
writeCredentialsFile(file);
|
|
130
|
+
return true;
|
|
52
131
|
}
|
|
53
132
|
/**
|
|
54
|
-
* Removes
|
|
133
|
+
* Removes all profiles by deleting the credentials file.
|
|
55
134
|
*/
|
|
56
|
-
export function
|
|
135
|
+
export function removeAllProfiles() {
|
|
57
136
|
const credentialsPath = getCredentialsPath();
|
|
58
137
|
if (existsSync(credentialsPath)) {
|
|
59
138
|
unlinkSync(credentialsPath);
|
|
60
139
|
}
|
|
140
|
+
cachedFile = { profiles: {} };
|
|
61
141
|
}
|
|
62
142
|
/**
|
|
63
|
-
* Checks
|
|
143
|
+
* Checks whether a profile has stored credentials.
|
|
64
144
|
*/
|
|
65
|
-
export function isLoggedIn() {
|
|
66
|
-
return loadToken() !== null;
|
|
145
|
+
export function isLoggedIn(profile) {
|
|
146
|
+
return loadToken(profile) !== null;
|
|
67
147
|
}
|
|
68
148
|
/**
|
|
69
|
-
*
|
|
149
|
+
* Resolves the API URL with precedence: options override, env var, profile, default.
|
|
70
150
|
*/
|
|
71
|
-
export function getApiUrl() {
|
|
72
|
-
|
|
151
|
+
export function getApiUrl(options = {}) {
|
|
152
|
+
if (options.apiUrl) {
|
|
153
|
+
return options.apiUrl;
|
|
154
|
+
}
|
|
155
|
+
if (process.env.JUUNO_API_URL) {
|
|
156
|
+
return process.env.JUUNO_API_URL;
|
|
157
|
+
}
|
|
158
|
+
const profile = getProfile(resolveProfile(options));
|
|
159
|
+
return profile?.api_url || DEFAULT_API_URL;
|
|
73
160
|
}
|
|
74
161
|
/**
|
|
75
|
-
* Gets the authentication token from options, environment, or stored
|
|
162
|
+
* Gets the authentication token from options, environment, or stored profile.
|
|
76
163
|
*/
|
|
77
164
|
export async function getAuthToken(options) {
|
|
78
165
|
if (options.token) {
|
|
@@ -82,15 +169,18 @@ export async function getAuthToken(options) {
|
|
|
82
169
|
if (envToken) {
|
|
83
170
|
return envToken;
|
|
84
171
|
}
|
|
85
|
-
const
|
|
172
|
+
const profile = resolveProfile(options);
|
|
173
|
+
const storedToken = loadToken(profile);
|
|
86
174
|
if (storedToken) {
|
|
87
175
|
return storedToken;
|
|
88
176
|
}
|
|
89
177
|
if (options.email || options.password) {
|
|
90
178
|
const credentials = await getCredentials(options);
|
|
91
|
-
return await loginWithCredentials(credentials);
|
|
179
|
+
return await loginWithCredentials(credentials, getApiUrl(options));
|
|
92
180
|
}
|
|
93
|
-
|
|
181
|
+
const profileFlag = formatProfileFlag(profile);
|
|
182
|
+
const suffix = profile === DEFAULT_PROFILE ? '' : ` for profile "${profile}"`;
|
|
183
|
+
throw new Error(`Not logged in${suffix}. Please run "juuno-cli login${profileFlag}" first or provide credentials via --email and --password`);
|
|
94
184
|
}
|
|
95
185
|
/**
|
|
96
186
|
* Gets credentials from options or prompts the user.
|
|
@@ -123,8 +213,7 @@ async function getCredentials(options) {
|
|
|
123
213
|
/**
|
|
124
214
|
* Authenticates with the developer API using email and password.
|
|
125
215
|
*/
|
|
126
|
-
export async function loginWithCredentials(credentials) {
|
|
127
|
-
const apiUrl = getApiUrl();
|
|
216
|
+
export async function loginWithCredentials(credentials, apiUrl) {
|
|
128
217
|
const loginUrl = `${apiUrl}/api/v1/developer/auth/login`;
|
|
129
218
|
try {
|
|
130
219
|
const response = await fetch(loginUrl, {
|
|
@@ -6,8 +6,7 @@ import { getAuthToken, getApiUrl } from '../auth/index.js';
|
|
|
6
6
|
* Fetches the currently deployed version of an app.
|
|
7
7
|
* Returns null if the app doesn't exist yet.
|
|
8
8
|
*/
|
|
9
|
-
async function fetchDeployedVersion(token, appId) {
|
|
10
|
-
const apiUrl = getApiUrl();
|
|
9
|
+
async function fetchDeployedVersion(token, appId, apiUrl) {
|
|
11
10
|
const infoUrl = `${apiUrl}/api/v1/developer/apps/${appId}`;
|
|
12
11
|
try {
|
|
13
12
|
const response = await fetch(infoUrl, {
|
|
@@ -78,8 +77,7 @@ async function createZipBundle(buildDir) {
|
|
|
78
77
|
/**
|
|
79
78
|
* Uploads the app bundle to the developer API.
|
|
80
79
|
*/
|
|
81
|
-
async function uploadBundle(token, zipBuffer) {
|
|
82
|
-
const apiUrl = getApiUrl();
|
|
80
|
+
async function uploadBundle(token, zipBuffer, apiUrl) {
|
|
83
81
|
const uploadUrl = `${apiUrl}/api/v1/developer/apps/upload`;
|
|
84
82
|
const formData = new FormData();
|
|
85
83
|
const blob = new Blob([zipBuffer], { type: 'application/zip' });
|
|
@@ -137,6 +135,7 @@ export async function deployApp(options) {
|
|
|
137
135
|
console.log(` Version: ${manifest.version}`);
|
|
138
136
|
console.log(` ID: ${manifest.id}`);
|
|
139
137
|
console.log('');
|
|
138
|
+
const apiUrl = getApiUrl(options);
|
|
140
139
|
console.log('🔑 Authenticating...');
|
|
141
140
|
let token;
|
|
142
141
|
try {
|
|
@@ -150,8 +149,14 @@ export async function deployApp(options) {
|
|
|
150
149
|
}
|
|
151
150
|
// Check if this version is already deployed.
|
|
152
151
|
console.log('🔍 Checking deployed version...');
|
|
153
|
-
const deployedVersion = await fetchDeployedVersion(token, manifest.id);
|
|
152
|
+
const deployedVersion = await fetchDeployedVersion(token, manifest.id, apiUrl);
|
|
154
153
|
if (deployedVersion !== null && deployedVersion === manifest.version) {
|
|
154
|
+
if (options.skipIfDeployed) {
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log(`ℹ️ ${manifest.name}@${manifest.version} already deployed, skipping.`);
|
|
157
|
+
console.log('');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
155
160
|
console.error('');
|
|
156
161
|
console.error(`❌ Version ${manifest.version} is already deployed for '${manifest.name}'`);
|
|
157
162
|
console.error('');
|
|
@@ -180,7 +185,7 @@ export async function deployApp(options) {
|
|
|
180
185
|
console.log('☁️ Uploading to Juuno...');
|
|
181
186
|
let result;
|
|
182
187
|
try {
|
|
183
|
-
result = await uploadBundle(token, zipBuffer);
|
|
188
|
+
result = await uploadBundle(token, zipBuffer, apiUrl);
|
|
184
189
|
console.log(' ✓ Upload complete');
|
|
185
190
|
console.log('');
|
|
186
191
|
}
|
|
@@ -2,8 +2,7 @@ import { getAuthToken, getApiUrl } from '../auth/index.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Fetches detailed information about a specific app.
|
|
4
4
|
*/
|
|
5
|
-
async function fetchAppInfo(token, appId) {
|
|
6
|
-
const apiUrl = getApiUrl();
|
|
5
|
+
async function fetchAppInfo(token, appId, apiUrl) {
|
|
7
6
|
const infoUrl = `${apiUrl}/api/v1/developer/apps/${appId}`;
|
|
8
7
|
try {
|
|
9
8
|
const response = await fetch(infoUrl, {
|
|
@@ -85,6 +84,7 @@ export async function getAppInfo(options) {
|
|
|
85
84
|
console.error(' Usage: juuno-cli info <app-id>');
|
|
86
85
|
process.exit(1);
|
|
87
86
|
}
|
|
87
|
+
const apiUrl = getApiUrl(options);
|
|
88
88
|
console.log('🔑 Authenticating...');
|
|
89
89
|
let token;
|
|
90
90
|
try {
|
|
@@ -99,7 +99,7 @@ export async function getAppInfo(options) {
|
|
|
99
99
|
console.log(`📡 Fetching app info for: ${options.appId}`);
|
|
100
100
|
let appInfo;
|
|
101
101
|
try {
|
|
102
|
-
appInfo = await fetchAppInfo(token, options.appId);
|
|
102
|
+
appInfo = await fetchAppInfo(token, options.appId, apiUrl);
|
|
103
103
|
console.log(' ✓ Info retrieved');
|
|
104
104
|
console.log('');
|
|
105
105
|
}
|
|
@@ -2,8 +2,7 @@ import { getAuthToken, getApiUrl } from '../auth/index.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Fetches the list of developer apps from the API.
|
|
4
4
|
*/
|
|
5
|
-
async function fetchAppList(token) {
|
|
6
|
-
const apiUrl = getApiUrl();
|
|
5
|
+
async function fetchAppList(token, apiUrl) {
|
|
7
6
|
const listUrl = `${apiUrl}/api/v1/developer/apps`;
|
|
8
7
|
try {
|
|
9
8
|
const response = await fetch(listUrl, {
|
|
@@ -76,6 +75,7 @@ function displayAppList(apps) {
|
|
|
76
75
|
export async function listApps(options) {
|
|
77
76
|
console.log('📦 Juuno CLI - List Apps');
|
|
78
77
|
console.log('');
|
|
78
|
+
const apiUrl = getApiUrl(options);
|
|
79
79
|
console.log('🔑 Authenticating...');
|
|
80
80
|
let token;
|
|
81
81
|
try {
|
|
@@ -90,7 +90,7 @@ export async function listApps(options) {
|
|
|
90
90
|
console.log('📡 Fetching apps...');
|
|
91
91
|
let apps;
|
|
92
92
|
try {
|
|
93
|
-
apps = await fetchAppList(token);
|
|
93
|
+
apps = await fetchAppList(token, apiUrl);
|
|
94
94
|
console.log(' ✓ Apps retrieved');
|
|
95
95
|
console.log('');
|
|
96
96
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { loginWithCredentials, saveToken, getApiUrl, } from '../auth/index.js';
|
|
1
|
+
import { loginWithCredentials, saveToken, getApiUrl, resolveProfile, formatProfileFlag, } from '../auth/index.js';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
/**
|
|
4
|
-
* Logs in to Juuno and saves authentication token.
|
|
4
|
+
* Logs in to Juuno and saves authentication token for the target profile.
|
|
5
5
|
*/
|
|
6
6
|
export async function login(options) {
|
|
7
|
+
const profile = resolveProfile(options);
|
|
8
|
+
const apiUrl = getApiUrl(options);
|
|
7
9
|
console.log('📦 Juuno CLI - Login');
|
|
8
10
|
console.log('');
|
|
9
|
-
|
|
10
|
-
console.log(` API:
|
|
11
|
+
console.log(` Profile: ${profile}`);
|
|
12
|
+
console.log(` API: ${apiUrl}`);
|
|
11
13
|
console.log('');
|
|
12
14
|
let email = options.email;
|
|
13
15
|
let pwd = options.password;
|
|
@@ -39,15 +41,15 @@ export async function login(options) {
|
|
|
39
41
|
console.log('🔑 Authenticating...');
|
|
40
42
|
let token;
|
|
41
43
|
try {
|
|
42
|
-
token = await loginWithCredentials(credentials);
|
|
44
|
+
token = await loginWithCredentials(credentials, apiUrl);
|
|
43
45
|
}
|
|
44
46
|
catch (error) {
|
|
45
47
|
console.error(`❌ ${error instanceof Error ? error.message : String(error)}`);
|
|
46
48
|
process.exit(1);
|
|
47
49
|
}
|
|
48
50
|
try {
|
|
49
|
-
saveToken(token);
|
|
50
|
-
console.log(
|
|
51
|
+
saveToken(token, profile, apiUrl);
|
|
52
|
+
console.log(` ✓ Token saved to ~/.juuno/credentials.json (profile: ${profile})`);
|
|
51
53
|
console.log('');
|
|
52
54
|
}
|
|
53
55
|
catch (error) {
|
|
@@ -56,6 +58,7 @@ export async function login(options) {
|
|
|
56
58
|
}
|
|
57
59
|
console.log('✅ Login successful!');
|
|
58
60
|
console.log('');
|
|
59
|
-
|
|
61
|
+
const profileFlag = formatProfileFlag(profile);
|
|
62
|
+
console.log(`💡 You can now use deploy, list, and info commands${profileFlag ? ` with${profileFlag}` : ''} without re-authenticating.`);
|
|
60
63
|
console.log('');
|
|
61
64
|
}
|