@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.
Files changed (56) hide show
  1. package/README.md +61 -11
  2. package/bin/cli-router.js +18 -4
  3. package/bin/cli.js +40 -15
  4. package/dist/cli/src/auth/index.js +128 -39
  5. package/dist/cli/src/deploy/index.js +11 -6
  6. package/dist/cli/src/info/index.js +3 -3
  7. package/dist/cli/src/list/index.js +3 -3
  8. package/dist/cli/src/login/index.js +11 -8
  9. package/dist/cli/src/logout/index.js +29 -8
  10. package/dist/cli/src/whoami/index.js +31 -10
  11. package/dist/cli/tsconfig.build.tsbuildinfo +1 -1
  12. package/dist/types/src/auth/index.d.ts +84 -0
  13. package/dist/types/src/auth/index.d.ts.map +1 -0
  14. package/dist/{cli → types}/src/deploy/index.d.ts +6 -0
  15. package/dist/{cli → types}/src/deploy/index.d.ts.map +1 -1
  16. package/dist/{cli → types}/src/info/index.d.ts.map +1 -1
  17. package/dist/{cli → types}/src/list/index.d.ts.map +1 -1
  18. package/dist/{cli → types}/src/login/index.d.ts +3 -1
  19. package/dist/types/src/login/index.d.ts.map +1 -0
  20. package/dist/types/src/logout/index.d.ts +12 -0
  21. package/dist/types/src/logout/index.d.ts.map +1 -0
  22. package/dist/types/src/whoami/index.d.ts +12 -0
  23. package/dist/types/src/whoami/index.d.ts.map +1 -0
  24. package/package.json +26 -26
  25. package/dist/assets/main-B8xiNI5_.js +0 -1
  26. package/dist/assets/main-Bf2N9gBQ.js +0 -1
  27. package/dist/assets/main-Bo8Yyr87.js +0 -1
  28. package/dist/assets/main-C3hW0d-b.css +0 -1
  29. package/dist/assets/main-C6ZAJydH.js +0 -1
  30. package/dist/assets/main-CCYNXtGp.css +0 -1
  31. package/dist/assets/main-COSZCfRq.css +0 -1
  32. package/dist/assets/main-CnpUG-52.js +0 -1
  33. package/dist/assets/main-D0353jp3.js +0 -1
  34. package/dist/assets/main-D7UD6CXw.css +0 -1
  35. package/dist/assets/main-DC3n4QV-.css +0 -1
  36. package/dist/assets/main-DWrNgA-L.js +0 -1
  37. package/dist/assets/main-HbFBqUMx.js +0 -1
  38. package/dist/assets/main-jQYqLcLk.js +0 -1
  39. package/dist/cli/src/auth/index.d.ts +0 -51
  40. package/dist/cli/src/auth/index.d.ts.map +0 -1
  41. package/dist/cli/src/login/index.d.ts.map +0 -1
  42. package/dist/cli/src/logout/index.d.ts +0 -5
  43. package/dist/cli/src/logout/index.d.ts.map +0 -1
  44. package/dist/cli/src/monorepo/index.d.ts +0 -37
  45. package/dist/cli/src/monorepo/index.d.ts.map +0 -1
  46. package/dist/cli/src/monorepo/index.js +0 -94
  47. package/dist/cli/src/whoami/index.d.ts +0 -5
  48. package/dist/cli/src/whoami/index.d.ts.map +0 -1
  49. /package/dist/{cli → types}/src/dev/index.d.ts +0 -0
  50. /package/dist/{cli → types}/src/dev/index.d.ts.map +0 -0
  51. /package/dist/{cli → types}/src/info/index.d.ts +0 -0
  52. /package/dist/{cli → types}/src/list/index.d.ts +0 -0
  53. /package/dist/{cli → types}/src/simulator/index.d.ts +0 -0
  54. /package/dist/{cli → types}/src/simulator/index.d.ts.map +0 -0
  55. /package/dist/{cli → types}/src/utils/fs.d.ts +0 -0
  56. /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
- Removes stored credentials from `~/.juuno/credentials.json`.
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
- Shows current authentication status.
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
- # API endpoint (defaults to production)
302
- export JUUNO_API_URL=https://api.juuno.co
347
+ # Profile to use (default: "default")
348
+ export JUUNO_PROFILE=stage
303
349
 
304
- # For staging
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 from `~/.juuno/credentials.json` (from `juuno-cli login`)
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({ email: authOptions.email, password: authOptions.password });
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
- await logout();
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
- await whoami();
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 ~/.juuno/credentials.json
42
- Subsequent commands will use the saved token automatically.
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> Build directory to deploy (default: ./dist)
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
- JUUNO_API_URL API URL (default: https://api.juuno.co)
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
- * Gets the credentials file path.
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
- const configDir = join(homedir(), '.juuno');
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
- * Saves authentication token to local credentials file.
22
+ * Reads and parses the credentials file. Migrates legacy single-profile
23
+ * files to the new profiles format transparently.
23
24
  */
24
- export function saveToken(token) {
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(credentials, null, 2), {
60
+ writeFileSync(credentialsPath, JSON.stringify(file, null, 2), {
33
61
  mode: 0o600,
34
62
  });
63
+ cachedFile = file;
35
64
  }
36
65
  /**
37
- * Loads authentication token from local credentials file.
66
+ * Resolves the profile name from options, environment, or default.
38
67
  */
39
- export function loadToken() {
40
- const credentialsPath = getCredentialsPath();
41
- if (!existsSync(credentialsPath)) {
42
- return null;
68
+ export function resolveProfile(options = {}) {
69
+ if (options.profile) {
70
+ return options.profile;
43
71
  }
44
- try {
45
- const fileContent = readFileSync(credentialsPath, 'utf-8');
46
- const credentials = JSON.parse(fileContent);
47
- return credentials.token || null;
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
- catch (error) {
50
- return null;
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 stored credentials file.
133
+ * Removes all profiles by deleting the credentials file.
55
134
  */
56
- export function removeToken() {
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 if user is currently logged in.
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
- * Gets the API URL from environment or uses default.
149
+ * Resolves the API URL with precedence: options override, env var, profile, default.
70
150
  */
71
- export function getApiUrl() {
72
- return process.env.JUUNO_API_URL || 'https://api.juuno.co';
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 credentials.
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 storedToken = loadToken();
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
- throw new Error('Not logged in. Please run "juuno-cli login" first or provide credentials via --email and --password');
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
- const apiUrl = getApiUrl();
10
- console.log(` API: ${apiUrl}`);
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(' ✓ Token saved to ~/.juuno/credentials.json');
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
- console.log('💡 You can now use deploy, list, and info commands without re-authenticating.');
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
  }