@juuno-sdk/cli 1.0.12 → 2.0.0
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 +5 -3
- package/bin/cli-router.js +54 -72
- package/bin/cli.js +2 -3
- package/dist/assets/main-GRbDNSIa.css +1 -0
- package/dist/assets/main-TDlmPBlt.js +1 -0
- package/dist/assets/main-rFvAfxK4.js +1 -0
- package/dist/cli/src/auth/index.js +47 -34
- package/dist/cli/src/deploy/index.js +209 -115
- package/dist/cli/src/dev/index.js +46 -40
- package/dist/cli/src/info/index.js +12 -36
- package/dist/cli/src/list/index.js +12 -36
- package/dist/cli/src/login/index.js +37 -42
- package/dist/cli/src/logout/index.js +34 -33
- package/dist/cli/src/simulator/index.js +54 -39
- package/dist/cli/src/utils/api.js +45 -0
- package/dist/cli/src/utils/error.js +9 -0
- package/dist/cli/src/utils/is-record.js +11 -0
- package/dist/cli/src/whoami/index.js +37 -33
- package/dist/cli/tsconfig.build.tsbuildinfo +1 -1
- package/dist/index.html +5 -2
- package/dist/types/src/auth/index.d.ts +17 -0
- package/dist/types/src/auth/index.d.ts.map +1 -1
- package/dist/types/src/deploy/index.d.ts +12 -0
- package/dist/types/src/deploy/index.d.ts.map +1 -1
- package/dist/types/src/dev/index.d.ts.map +1 -1
- package/dist/types/src/info/index.d.ts.map +1 -1
- package/dist/types/src/list/index.d.ts.map +1 -1
- package/dist/types/src/login/index.d.ts.map +1 -1
- package/dist/types/src/logout/index.d.ts.map +1 -1
- package/dist/types/src/simulator/index.d.ts.map +1 -1
- package/dist/types/src/utils/api.d.ts +27 -0
- package/dist/types/src/utils/api.d.ts.map +1 -0
- package/dist/types/src/utils/error.d.ts +8 -0
- package/dist/types/src/utils/error.d.ts.map +1 -0
- package/dist/types/src/utils/is-record.d.ts +10 -0
- package/dist/types/src/utils/is-record.d.ts.map +1 -0
- package/dist/types/src/whoami/index.d.ts.map +1 -1
- package/package.json +28 -26
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import { loginWithCredentials, saveToken, getApiUrl, resolveProfile, formatProfileFlag, } from '../auth/index.js';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
4
|
+
async function gatherCredentials(options) {
|
|
5
|
+
if (options.email && options.password) {
|
|
6
|
+
return { email: options.email, password: options.password };
|
|
7
|
+
}
|
|
8
|
+
const answers = await inquirer.prompt([
|
|
9
|
+
{
|
|
10
|
+
type: 'input',
|
|
11
|
+
name: 'email',
|
|
12
|
+
message: 'Email:',
|
|
13
|
+
when: !options.email,
|
|
14
|
+
validate: (input) => input.length > 0 || 'Email is required',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: 'password',
|
|
18
|
+
name: 'password',
|
|
19
|
+
message: 'Password:',
|
|
20
|
+
when: !options.password,
|
|
21
|
+
mask: '*',
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
return {
|
|
25
|
+
email: options.email || answers.email,
|
|
26
|
+
password: options.password || answers.password,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
3
29
|
/**
|
|
4
30
|
* Logs in to Juuno and saves authentication token for the target profile.
|
|
5
31
|
*/
|
|
@@ -11,54 +37,23 @@ export async function login(options) {
|
|
|
11
37
|
console.log(` Profile: ${profile}`);
|
|
12
38
|
console.log(` API: ${apiUrl}`);
|
|
13
39
|
console.log('');
|
|
14
|
-
let email = options.email;
|
|
15
|
-
let pwd = options.password;
|
|
16
|
-
if (!email || !pwd) {
|
|
17
|
-
const answers = await inquirer.prompt([
|
|
18
|
-
{
|
|
19
|
-
type: 'input',
|
|
20
|
-
name: 'email',
|
|
21
|
-
message: 'Email:',
|
|
22
|
-
when: !options.email,
|
|
23
|
-
validate: (input) => input.length > 0 || 'Email is required',
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
type: 'password',
|
|
27
|
-
name: 'password',
|
|
28
|
-
message: 'Password:',
|
|
29
|
-
when: !options.password,
|
|
30
|
-
mask: '*',
|
|
31
|
-
},
|
|
32
|
-
]);
|
|
33
|
-
email = email || answers.email;
|
|
34
|
-
pwd = pwd || answers.password;
|
|
35
|
-
}
|
|
36
|
-
const credentials = {
|
|
37
|
-
email: email,
|
|
38
|
-
password: pwd,
|
|
39
|
-
};
|
|
40
|
-
console.log('');
|
|
41
|
-
console.log('🔑 Authenticating...');
|
|
42
|
-
let token;
|
|
43
|
-
try {
|
|
44
|
-
token = await loginWithCredentials(credentials, apiUrl);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
console.error(`❌ ${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
40
|
try {
|
|
41
|
+
const credentials = await gatherCredentials(options);
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('🔑 Authenticating...');
|
|
44
|
+
const token = await loginWithCredentials(credentials, apiUrl);
|
|
51
45
|
saveToken(token, profile, apiUrl);
|
|
52
46
|
console.log(` ✓ Token saved to ~/.juuno/credentials.json (profile: ${profile})`);
|
|
53
47
|
console.log('');
|
|
48
|
+
const profileFlag = formatProfileFlag(profile);
|
|
49
|
+
const flagSuffix = profileFlag ? ` with${profileFlag}` : '';
|
|
50
|
+
console.log('✅ Login successful!');
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(`💡 You can now use deploy, list, and info commands${flagSuffix} without re-authenticating.`);
|
|
53
|
+
console.log('');
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
|
-
console.error(`❌
|
|
56
|
+
console.error(`❌ ${getErrorMessage(error)}`);
|
|
57
57
|
process.exit(1);
|
|
58
58
|
}
|
|
59
|
-
console.log('✅ Login successful!');
|
|
60
|
-
console.log('');
|
|
61
|
-
const profileFlag = formatProfileFlag(profile);
|
|
62
|
-
console.log(`💡 You can now use deploy, list, and info commands${profileFlag ? ` with${profileFlag}` : ''} without re-authenticating.`);
|
|
63
|
-
console.log('');
|
|
64
59
|
}
|
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
import { removeProfile, removeAllProfiles, listProfiles, resolveProfile, isLoggedIn, formatProfileFlag, } from '../auth/index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (options.all) {
|
|
9
|
-
const profiles = listProfiles();
|
|
10
|
-
if (profiles.length === 0) {
|
|
11
|
-
console.log(' No profiles are currently logged in.');
|
|
12
|
-
console.log('');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
removeAllProfiles();
|
|
17
|
-
console.log(` ✓ Removed ${profiles.length} profile${profiles.length === 1 ? '' : 's'}: ${profiles.join(', ')}`);
|
|
18
|
-
console.log('');
|
|
19
|
-
console.log('✅ All profiles logged out.');
|
|
20
|
-
console.log('');
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
console.error(`❌ Failed to remove credentials: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
2
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
3
|
+
function logoutAll() {
|
|
4
|
+
const profiles = listProfiles();
|
|
5
|
+
if (profiles.length === 0) {
|
|
6
|
+
console.log(' No profiles are currently logged in.');
|
|
7
|
+
console.log('');
|
|
26
8
|
return;
|
|
27
9
|
}
|
|
28
|
-
|
|
10
|
+
removeAllProfiles();
|
|
11
|
+
const plural = profiles.length === 1 ? '' : 's';
|
|
12
|
+
console.log(` ✓ Removed ${profiles.length} profile${plural}: ${profiles.join(', ')}`);
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log('✅ All profiles logged out.');
|
|
15
|
+
console.log('');
|
|
16
|
+
}
|
|
17
|
+
function logoutOne(profile) {
|
|
29
18
|
if (!isLoggedIn(profile)) {
|
|
30
19
|
console.log(` Profile "${profile}" is not currently logged in.`);
|
|
31
20
|
console.log('');
|
|
32
21
|
return;
|
|
33
22
|
}
|
|
23
|
+
removeProfile(profile);
|
|
24
|
+
console.log(` ✓ Credentials removed for profile "${profile}"`);
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log('✅ Logged out successfully!');
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(`💡 Run "juuno-cli login${formatProfileFlag(profile)}" to authenticate again.`);
|
|
29
|
+
console.log('');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Logs out of Juuno by removing stored credentials for the target profile.
|
|
33
|
+
*/
|
|
34
|
+
export async function logout(options = {}) {
|
|
35
|
+
console.log('📦 Juuno CLI - Logout');
|
|
36
|
+
console.log('');
|
|
34
37
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(`💡 Run "juuno-cli login${formatProfileFlag(profile)}" to authenticate again.`);
|
|
41
|
-
console.log('');
|
|
38
|
+
if (options.all) {
|
|
39
|
+
logoutAll();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
logoutOne(resolveProfile(options));
|
|
42
43
|
}
|
|
43
44
|
catch (error) {
|
|
44
|
-
console.error(`❌ Failed to remove credentials: ${
|
|
45
|
+
console.error(`❌ Failed to remove credentials: ${getErrorMessage(error)}`);
|
|
45
46
|
process.exit(1);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -45,23 +45,40 @@ async function fetchManifest(appUrl) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Static CDN map for the import-map slots in index.html. Adding a new
|
|
49
|
+
* dependency is one entry; the runtime substitution below iterates this
|
|
50
|
+
* record so the body never grows.
|
|
49
51
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
const IMPORT_MAP_CDN = {
|
|
53
|
+
vue: 'https://cdn.jsdelivr.net/npm/vue@3.5.22/dist/vue.esm-browser.prod.js',
|
|
54
|
+
'@juuno-sdk/app-sdk': 'https://cdn.jsdelivr.net/npm/@juuno-sdk/app-sdk@1.0.2/dist/index.js',
|
|
55
|
+
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@11.4.1/index.mjs',
|
|
56
|
+
'@tiptap/vue-3': 'https://cdn.jsdelivr.net/npm/@tiptap/vue-3@2.10.5/dist/index.js',
|
|
57
|
+
'@tiptap/starter-kit': 'https://cdn.jsdelivr.net/npm/@tiptap/starter-kit@2.10.5/dist/index.js',
|
|
58
|
+
'@tiptap/extension-underline': 'https://cdn.jsdelivr.net/npm/@tiptap/extension-underline@2.10.5/dist/index.js',
|
|
59
|
+
'lodash-es': 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js',
|
|
60
|
+
'date-fns': 'https://cdn.jsdelivr.net/npm/date-fns@4.1.0/index.mjs',
|
|
61
|
+
'url-parse': 'https://cdn.jsdelivr.net/npm/url-parse@1.5.10/dist/url-parse.min.js',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Apply the import-map CDN substitutions and the external-app entrypoints
|
|
65
|
+
* to a copy of index.html. Pure over the inputs.
|
|
66
|
+
*/
|
|
67
|
+
function applyImportMap(indexHtml, appUrl, playerPath, configPath) {
|
|
68
|
+
let result = indexHtml;
|
|
69
|
+
for (const [key, url] of Object.entries(IMPORT_MAP_CDN)) {
|
|
70
|
+
result = result.replace(`"${key}": ""`, `"${key}": "${url}"`);
|
|
71
|
+
}
|
|
72
|
+
result = result.replace('"external-app/player": ""', `"external-app/player": "${appUrl}${playerPath}"`);
|
|
73
|
+
result = result.replace('"external-app/config": ""', `"external-app/config": "${appUrl}${configPath}"`);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function ensureValidDistDir(distPath) {
|
|
59
77
|
if (!existsSync(distPath)) {
|
|
60
78
|
console.error(`Error: Build directory does not exist: ${distPath}`);
|
|
61
79
|
console.error('Please run "pnpm build" or "pnpm build:simulator" first.');
|
|
62
80
|
process.exit(1);
|
|
63
81
|
}
|
|
64
|
-
// Validate that dist path is actually a directory.
|
|
65
82
|
try {
|
|
66
83
|
const stats = statSync(distPath);
|
|
67
84
|
if (!stats.isDirectory()) {
|
|
@@ -74,44 +91,42 @@ export async function launchSimulatorWithOptions(options) {
|
|
|
74
91
|
console.error(`Details: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
92
|
process.exit(1);
|
|
76
93
|
}
|
|
94
|
+
}
|
|
95
|
+
function ensureIndexHtmlPresent(indexPath) {
|
|
96
|
+
if (existsSync(indexPath)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.error(`Error: index.html not found in build directory after copy`);
|
|
100
|
+
console.error(`Expected location: ${indexPath}`);
|
|
101
|
+
console.error('The build directory may be incomplete or corrupted.');
|
|
102
|
+
console.error('Please run "pnpm build" to rebuild the simulator.');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Launch the app simulator with full options object.
|
|
107
|
+
*/
|
|
108
|
+
export async function launchSimulatorWithOptions(options) {
|
|
109
|
+
const { appUrl, port, distPath } = options;
|
|
110
|
+
// Fetch manifest to get actual resource paths.
|
|
111
|
+
const manifest = await fetchManifest(appUrl);
|
|
112
|
+
const playerPath = manifest?.resources?.player?.find((p) => p.endsWith('.js')) || '/player.js';
|
|
113
|
+
const configPath = manifest?.resources?.config?.find((p) => p.endsWith('.js')) || '/config.js';
|
|
114
|
+
ensureValidDistDir(distPath);
|
|
77
115
|
// Create a temporary directory for the modified dist.
|
|
78
116
|
const tempDir = join(tmpdir(), `juuno-simulator-${Date.now()}`);
|
|
79
117
|
mkdirSync(tempDir, { recursive: true });
|
|
80
|
-
// Copy all files from dist to temp directory.
|
|
81
118
|
copyDir(distPath, tempDir);
|
|
82
|
-
// Inject the app URL and import map CDN URLs into index.html.
|
|
83
119
|
const indexPath = join(tempDir, 'index.html');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.error('The build directory may be incomplete or corrupted.');
|
|
89
|
-
console.error('Please run "pnpm build" to rebuild the simulator.');
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
let indexHtml = readFileSync(indexPath, 'utf-8');
|
|
93
|
-
// Fill in the import map with CDN URLs for vue and @juuno-sdk/app-sdk.
|
|
94
|
-
indexHtml = indexHtml.replace('"vue": ""', '"vue": "https://cdn.jsdelivr.net/npm/vue@3.5.22/dist/vue.esm-browser.prod.js"');
|
|
95
|
-
indexHtml = indexHtml.replace('"@juuno-sdk/app-sdk": ""', '"@juuno-sdk/app-sdk": "https://cdn.jsdelivr.net/npm/@juuno-sdk/app-sdk@1.0.2/dist/index.js"');
|
|
96
|
-
// Fill in CDN URLs for externalized dependencies from the SDK.
|
|
97
|
-
indexHtml = indexHtml.replace('"@vueuse/core": ""', '"@vueuse/core": "https://cdn.jsdelivr.net/npm/@vueuse/core@11.4.1/index.mjs"');
|
|
98
|
-
indexHtml = indexHtml.replace('"@tiptap/vue-3": ""', '"@tiptap/vue-3": "https://cdn.jsdelivr.net/npm/@tiptap/vue-3@2.10.5/dist/index.js"');
|
|
99
|
-
indexHtml = indexHtml.replace('"@tiptap/starter-kit": ""', '"@tiptap/starter-kit": "https://cdn.jsdelivr.net/npm/@tiptap/starter-kit@2.10.5/dist/index.js"');
|
|
100
|
-
indexHtml = indexHtml.replace('"@tiptap/extension-underline": ""', '"@tiptap/extension-underline": "https://cdn.jsdelivr.net/npm/@tiptap/extension-underline@2.10.5/dist/index.js"');
|
|
101
|
-
indexHtml = indexHtml.replace('"lodash-es": ""', '"lodash-es": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js"');
|
|
102
|
-
indexHtml = indexHtml.replace('"date-fns": ""', '"date-fns": "https://cdn.jsdelivr.net/npm/date-fns@4.1.0/index.mjs"');
|
|
103
|
-
indexHtml = indexHtml.replace('"url-parse": ""', '"url-parse": "https://cdn.jsdelivr.net/npm/url-parse@1.5.10/dist/url-parse.min.js"');
|
|
104
|
-
// Fill in the import map with external app entrypoints (from manifest or defaults).
|
|
105
|
-
indexHtml = indexHtml.replace('"external-app/player": ""', `"external-app/player": "${appUrl}${playerPath}"`);
|
|
106
|
-
indexHtml = indexHtml.replace('"external-app/config": ""', `"external-app/config": "${appUrl}${configPath}"`);
|
|
107
|
-
// Inject the app URL as a global variable.
|
|
108
|
-
// Use JSON.stringify for safe injection into JavaScript.
|
|
120
|
+
ensureIndexHtmlPresent(indexPath);
|
|
121
|
+
const indexHtmlSource = readFileSync(indexPath, 'utf-8');
|
|
122
|
+
const withImportMap = applyImportMap(indexHtmlSource, appUrl, playerPath, configPath);
|
|
123
|
+
// Inject the app URL as a global variable. JSON.stringify for safe escaping.
|
|
109
124
|
const appUrlScript = `
|
|
110
125
|
<script>
|
|
111
126
|
window.__JUUNO_EXTERNAL_APP_URL__ = ${JSON.stringify(appUrl)};
|
|
112
127
|
</script>
|
|
113
128
|
`;
|
|
114
|
-
indexHtml =
|
|
129
|
+
const indexHtml = withImportMap.replace('</head>', `${appUrlScript}</head>`);
|
|
115
130
|
writeFileSync(indexPath, indexHtml);
|
|
116
131
|
console.log(`
|
|
117
132
|
🚀 Juuno App Simulator
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getErrorMessage } from './error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch JSON from a developer-API endpoint with bearer-token auth, throwing
|
|
4
|
+
* a useful error on non-2xx responses (status, statusText, body). Used by
|
|
5
|
+
* the info, list, and deploy commands.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchAuthorisedJson(url, token) {
|
|
8
|
+
const response = await fetch(url, {
|
|
9
|
+
method: 'GET',
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${token}`,
|
|
12
|
+
Accept: 'application/json',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const body = await response.text();
|
|
17
|
+
throw new Error(`${response.status} ${response.statusText}\n${body}`);
|
|
18
|
+
}
|
|
19
|
+
return (await response.json());
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a thrown error with a contextual prefix. Preserves the original
|
|
23
|
+
* message and lets callers throw a single high-level error with the
|
|
24
|
+
* subsystem context, instead of stringifying details inline at every site.
|
|
25
|
+
*/
|
|
26
|
+
export function wrapError(prefix, error) {
|
|
27
|
+
return new Error(`${prefix}: ${getErrorMessage(error)}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate a developer-API envelope and return the unwrapped data, or
|
|
31
|
+
* throw a specific error for each failure mode (server-reported failure
|
|
32
|
+
* with or without a message; success with missing data).
|
|
33
|
+
*/
|
|
34
|
+
export function assertSuccessEnvelope(envelope) {
|
|
35
|
+
if (!envelope.success) {
|
|
36
|
+
if (envelope.message) {
|
|
37
|
+
throw new Error(envelope.message);
|
|
38
|
+
}
|
|
39
|
+
throw new Error('Server returned success=false');
|
|
40
|
+
}
|
|
41
|
+
if (envelope.data === undefined) {
|
|
42
|
+
throw new Error('Server response is missing the data field');
|
|
43
|
+
}
|
|
44
|
+
return envelope.data;
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull a user-facing string out of an unknown thrown value. Preserves the
|
|
3
|
+
* original Error.message when present; falls back to String() coercion for
|
|
4
|
+
* anything else (which gives e.g. "[object Object]" for plain objects but
|
|
5
|
+
* is still better than crashing the catch site).
|
|
6
|
+
*/
|
|
7
|
+
export function getErrorMessage(error) {
|
|
8
|
+
return error instanceof Error ? error.message : String(error);
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True for plain object-shaped values (excludes null and arrays). Use as
|
|
3
|
+
* the entry guard before reading properties on an `unknown` from JSON,
|
|
4
|
+
* dynamic imports, or other untyped boundaries.
|
|
5
|
+
*
|
|
6
|
+
* A copy of `@juuno/utils`'s `isRecord` on purpose: this package publishes
|
|
7
|
+
* standalone and cannot depend on a workspace package.
|
|
8
|
+
*/
|
|
9
|
+
export function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
@@ -1,45 +1,49 @@
|
|
|
1
1
|
import { isLoggedIn, getApiUrl, resolveProfile, listProfiles, getProfile, formatProfileFlag, } from '../auth/index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (options.all) {
|
|
9
|
-
const profiles = listProfiles();
|
|
10
|
-
if (profiles.length === 0) {
|
|
11
|
-
console.log('❌ No profiles configured');
|
|
12
|
-
console.log('');
|
|
13
|
-
console.log('💡 Run "juuno-cli login" to authenticate.');
|
|
14
|
-
console.log('');
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
console.log(`✅ ${profiles.length} profile${profiles.length === 1 ? '' : 's'} configured`);
|
|
2
|
+
function printAllProfiles() {
|
|
3
|
+
const profiles = listProfiles();
|
|
4
|
+
if (profiles.length === 0) {
|
|
5
|
+
console.log('❌ No profiles configured');
|
|
6
|
+
console.log('');
|
|
7
|
+
console.log('💡 Run "juuno-cli login" to authenticate.');
|
|
18
8
|
console.log('');
|
|
19
|
-
for (const name of profiles) {
|
|
20
|
-
const creds = getProfile(name);
|
|
21
|
-
console.log(` ${name}`);
|
|
22
|
-
console.log(` API: ${creds?.api_url || 'unknown'}`);
|
|
23
|
-
console.log(` Created: ${creds?.created_at || 'unknown'}`);
|
|
24
|
-
console.log('');
|
|
25
|
-
}
|
|
26
9
|
return;
|
|
27
10
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log(`
|
|
34
|
-
console.log(`
|
|
35
|
-
console.log('');
|
|
36
|
-
console.log(`💡 Run "juuno-cli logout${formatProfileFlag(profile)}" to remove stored credentials.`);
|
|
11
|
+
const plural = profiles.length === 1 ? '' : 's';
|
|
12
|
+
console.log(`✅ ${profiles.length} profile${plural} configured`);
|
|
13
|
+
console.log('');
|
|
14
|
+
for (const name of profiles) {
|
|
15
|
+
const creds = getProfile(name);
|
|
16
|
+
console.log(` ${name}`);
|
|
17
|
+
console.log(` API: ${creds?.api_url || 'unknown'}`);
|
|
18
|
+
console.log(` Created: ${creds?.created_at || 'unknown'}`);
|
|
37
19
|
console.log('');
|
|
38
20
|
}
|
|
39
|
-
|
|
21
|
+
}
|
|
22
|
+
function printSingleProfile(profile, apiUrl) {
|
|
23
|
+
if (!isLoggedIn(profile)) {
|
|
40
24
|
console.log(`❌ Not logged in (profile: ${profile})`);
|
|
41
25
|
console.log('');
|
|
42
26
|
console.log(`💡 Run "juuno-cli login${formatProfileFlag(profile)}" to authenticate.`);
|
|
43
27
|
console.log('');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log(`✅ Logged in (profile: ${profile})`);
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(` API: ${apiUrl}`);
|
|
33
|
+
console.log(` Credentials: ~/.juuno/credentials.json`);
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(`💡 Run "juuno-cli logout${formatProfileFlag(profile)}" to remove stored credentials.`);
|
|
36
|
+
console.log('');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Shows current authentication status for a profile or all profiles.
|
|
40
|
+
*/
|
|
41
|
+
export async function whoami(options = {}) {
|
|
42
|
+
console.log('📦 Juuno CLI - Authentication Status');
|
|
43
|
+
console.log('');
|
|
44
|
+
if (options.all) {
|
|
45
|
+
printAllProfiles();
|
|
46
|
+
return;
|
|
44
47
|
}
|
|
48
|
+
printSingleProfile(resolveProfile(options), getApiUrl(options));
|
|
45
49
|
}
|