@jacebenson/jsn 4.3.9-dev.63 → 4.3.9-dev.64
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/package.json +1 -1
- package/src/auth.js +81 -38
- package/src/browser-auth.js +162 -0
- package/src/browser-launch.js +148 -0
- package/src/capabilities.js +10 -0
- package/src/cli.js +2 -2
- package/src/commands/auth.js +763 -650
- package/src/commands/setup.js +25 -20
- package/src/errors.js +8 -0
- package/src/prompts.js +37 -0
package/src/commands/setup.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { createPrompt, useKeypress, usePagination, useState, isDownKey, isEnterKey, isUpKey } from '@inquirer/core';
|
|
2
|
-
import { loginWizard, modifyProfile,
|
|
3
|
-
import { setActiveProfile } from '../config.js';
|
|
2
|
+
import { loginWizard, loginProfile, showAuthStatus, switchProfile, modifyProfile, logoutProfile, removeConnection } from './auth.js';
|
|
4
3
|
import { errUsage } from '../errors.js';
|
|
5
4
|
import { declareCapabilities } from '../capabilities.js';
|
|
6
5
|
|
|
7
6
|
// Setup manages local profile config — runs without a configured instance.
|
|
8
7
|
// (Not in the daily-check skip-list: legacy behavior checks for updates here.)
|
|
9
|
-
declareCapabilities('setup', { noInstance: true });
|
|
8
|
+
declareCapabilities('setup', { noInstance: true, skipContextHeader: true });
|
|
10
9
|
|
|
11
10
|
const HUB_CHOICES = [
|
|
12
|
-
{ name: 'Add a
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
11
|
+
{ name: 'Add a connection', value: 'add' },
|
|
12
|
+
{ name: 'Sign in again', value: 'login' },
|
|
13
|
+
{ name: 'Check connections', value: 'status' },
|
|
14
|
+
{ name: 'Choose the default profile', value: 'switch' },
|
|
15
|
+
{ name: 'Change settings', value: 'modify' },
|
|
16
|
+
{ name: 'Sign out (keep the profile)', value: 'logout' },
|
|
17
|
+
{ name: 'Remove a connection', value: 'remove' },
|
|
16
18
|
];
|
|
17
19
|
|
|
18
20
|
/** The setup hub prompt; q and Escape resolve with no selected action. */
|
|
@@ -54,41 +56,44 @@ export async function authHubMenu({ promptFn = authHubPrompt, input, output } =
|
|
|
54
56
|
*/
|
|
55
57
|
export async function dispatchSetupAction(app, argv, action) {
|
|
56
58
|
if (action === 'add') {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
return loginWizard(app, argv);
|
|
60
|
+
} else if (action === 'login') {
|
|
61
|
+
return loginProfile(argv, app);
|
|
62
|
+
} else if (action === 'status') {
|
|
63
|
+
return showAuthStatus(argv, app);
|
|
59
64
|
} else if (action === 'switch') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
return switchProfile(argv, app);
|
|
66
|
+
} else if (action === 'logout') {
|
|
67
|
+
return logoutProfile(argv, app);
|
|
63
68
|
} else if (action === 'remove') {
|
|
64
|
-
|
|
65
|
-
await removeProfile(app, name);
|
|
69
|
+
return removeConnection(argv, app);
|
|
66
70
|
} else if (action === 'modify') {
|
|
67
|
-
|
|
71
|
+
return modifyProfile(app, argv);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// `jsn setup` is the interactive front door for managing instances.
|
|
72
76
|
// The granular, scriptable surface lives under `jsn auth` — this command
|
|
73
|
-
// dispatches to the
|
|
74
|
-
// removeProfile, modifyProfile) so the two surfaces can never drift.
|
|
77
|
+
// collects choices and dispatches to the same auth operations.
|
|
75
78
|
export function setupCmd(wrap) {
|
|
76
79
|
return {
|
|
77
80
|
command: 'setup',
|
|
78
81
|
describe: 'Set up and manage your ServiceNow instances (interactive)',
|
|
79
82
|
handler: wrap(async (argv, app) => {
|
|
80
|
-
|
|
83
|
+
const { canUsePicker } = await import('../helpers.js');
|
|
84
|
+
if (!app.isInteractive() || !canUsePicker(app, argv)) {
|
|
81
85
|
throw errUsage('"jsn setup" is interactive. Use the specific commands:\n jsn auth login <instance>\n jsn auth refresh\n jsn auth status');
|
|
82
86
|
}
|
|
83
87
|
const profileNames = Object.keys(app.config.profiles || {});
|
|
84
88
|
if (profileNames.length === 0) {
|
|
85
89
|
const result = await loginWizard(app, argv);
|
|
86
|
-
|
|
90
|
+
await showAuthStatus({ ...argv, profile: result.profileName }, app);
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
const action = await authHubMenu();
|
|
90
94
|
if (action === undefined) return;
|
|
91
|
-
await dispatchSetupAction(app, argv, action);
|
|
95
|
+
const result = await dispatchSetupAction(app, argv, action);
|
|
96
|
+
if (action !== 'status') await showAuthStatus({ ...argv, profile: result?.profile || result?.profileName }, app);
|
|
92
97
|
}),
|
|
93
98
|
};
|
|
94
99
|
}
|
package/src/errors.js
CHANGED
|
@@ -219,8 +219,16 @@ function resolveJsonMode(app, argv) {
|
|
|
219
219
|
* text is null when the error was rendered through app.err (envelope).
|
|
220
220
|
*/
|
|
221
221
|
export function renderAppError(err, { app = null, argv = null, identifier = '' } = {}) {
|
|
222
|
+
if (err.name === 'PromptCancelled') return { stream: 'stderr', text: 'Cancelled.\n', exitCode: 0 };
|
|
222
223
|
const code = err.code || 'unknown';
|
|
223
224
|
|
|
225
|
+
if (code === CodeAuth) {
|
|
226
|
+
if (resolveJsonMode(app, argv) && app) return { stream: 'stdout', text: null, exitCode: 1, appErr: true };
|
|
227
|
+
let text = `Error (auth): ${err.message}\n`;
|
|
228
|
+
if (err.hint) text += `\nHint: ${err.hint}\n`;
|
|
229
|
+
return { stream: 'stderr', text, exitCode: 1 };
|
|
230
|
+
}
|
|
231
|
+
|
|
224
232
|
if (code === CodeConfirmationRequired) {
|
|
225
233
|
// Structured error for AI agents / piped consumers: the JSON envelope
|
|
226
234
|
// goes to stdout (where tooling reads) so the agent can surface the
|
package/src/prompts.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as prompts from '@inquirer/prompts';
|
|
2
|
+
|
|
3
|
+
export class PromptCancelled extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super('Cancelled.');
|
|
6
|
+
this.name = 'PromptCancelled';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Add Escape cancellation to an Inquirer prompt without changing its answers. */
|
|
11
|
+
export function cancellablePrompt(prompt) {
|
|
12
|
+
return async (config, context = {}) => {
|
|
13
|
+
const input = context.input || process.stdin;
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const onKey = (_value, key) => {
|
|
16
|
+
if (key?.name === 'escape') controller.abort(new PromptCancelled());
|
|
17
|
+
};
|
|
18
|
+
input.on('keypress', onKey);
|
|
19
|
+
try {
|
|
20
|
+
return await prompt({ ...config, message: `${config.message} (Esc to cancel)` }, {
|
|
21
|
+
...context,
|
|
22
|
+
signal: context.signal ? AbortSignal.any([context.signal, controller.signal]) : controller.signal,
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
26
|
+
throw error;
|
|
27
|
+
} finally {
|
|
28
|
+
input.removeListener('keypress', onKey);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const input = cancellablePrompt(prompts.input);
|
|
34
|
+
export const password = cancellablePrompt(prompts.password);
|
|
35
|
+
export const select = cancellablePrompt(prompts.select);
|
|
36
|
+
export const search = cancellablePrompt(prompts.search);
|
|
37
|
+
export const confirm = cancellablePrompt(prompts.confirm);
|