@jacebenson/jsn 4.3.7-dev.31 → 4.3.7-dev.33
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/commands/midservers.js +95 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { input, password as passwordPrompt, select } from '@inquirer/prompts';
|
|
4
|
-
import { confirmDelete, formatRecordForDisplay, getStringField, interactiveList, canPrompt } from '../helpers.js';
|
|
4
|
+
import { confirmDelete, formatRecordForDisplay, getStringField, interactiveList, canPrompt, assertSafeExactMatch } from '../helpers.js';
|
|
5
5
|
import { FormatAuto } from '../output.js';
|
|
6
6
|
import { declareCapabilities } from '../capabilities.js';
|
|
7
7
|
import { resolveRecord, unwrapSysId } from '../resolve-record.js';
|
|
@@ -91,6 +91,85 @@ async function resolveMidServer(sdk, identifier) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async function showMidDiagnostics(app, argv, kind) {
|
|
95
|
+
app.requireInstance();
|
|
96
|
+
let mid;
|
|
97
|
+
if (argv.identifier) {
|
|
98
|
+
mid = await resolveMidServer(app.sdk, argv.identifier);
|
|
99
|
+
} else {
|
|
100
|
+
if (!canPrompt() || app.output.getFormat() !== FormatAuto) {
|
|
101
|
+
throw new Error('An identifier is required outside an interactive terminal');
|
|
102
|
+
}
|
|
103
|
+
mid = await interactiveList({
|
|
104
|
+
app, table: AGENT_TABLE, singular: 'MID Server', columns: LIST_FIELDS,
|
|
105
|
+
limit: 50, labelField: 'name', message: `Select a MID Server for ${kind}`,
|
|
106
|
+
formatLabel: record => `${getStringField(record, 'name')} [${getStringField(record, 'status')}]`,
|
|
107
|
+
});
|
|
108
|
+
if (!mid) return;
|
|
109
|
+
}
|
|
110
|
+
const name = getStringField(mid, 'name');
|
|
111
|
+
const sysId = unwrapSysId(mid);
|
|
112
|
+
assertSafeExactMatch(name);
|
|
113
|
+
assertSafeExactMatch(sysId);
|
|
114
|
+
const issues = kind === 'issues';
|
|
115
|
+
const table = issues ? 'ecc_agent_issue' : 'mid_server_event';
|
|
116
|
+
const columns = issues
|
|
117
|
+
? ['sys_id', 'state', 'source', 'message', 'last_detected']
|
|
118
|
+
: ['sys_id', 'event', 'source', 'message', 'sys_created_on'];
|
|
119
|
+
const query = issues
|
|
120
|
+
? `mid_server=${sysId}${argv.all ? '' : '^state!=resolved'}^ORDERBYDESClast_detected`
|
|
121
|
+
: `agent=${name}^ORDERBYDESCsys_created_on`;
|
|
122
|
+
const limit = argv.limit ?? 50;
|
|
123
|
+
const offset = argv.offset ?? 0;
|
|
124
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000 || !Number.isInteger(offset) || offset < 0) {
|
|
125
|
+
throw new Error('Use --limit between 1 and 1000 and a nonnegative --offset');
|
|
126
|
+
}
|
|
127
|
+
const records = await app.sdk.list(table, new URLSearchParams({
|
|
128
|
+
sysparm_query: query, sysparm_fields: columns.join(','), sysparm_display_value: 'all',
|
|
129
|
+
sysparm_limit: String(limit), sysparm_offset: String(offset),
|
|
130
|
+
}));
|
|
131
|
+
const instanceURL = app.getEffectiveInstance();
|
|
132
|
+
const url = new URL(`/${table}_list.do`, instanceURL);
|
|
133
|
+
url.searchParams.set('sysparm_query', query);
|
|
134
|
+
const rows = records.map(record => ({
|
|
135
|
+
...record,
|
|
136
|
+
url: new URL(`/${table}.do?sys_id=${encodeURIComponent(unwrapSysId(record))}`, instanceURL).href,
|
|
137
|
+
}));
|
|
138
|
+
const label = issues ? (argv.all ? 'issues' : 'unresolved issues') : 'lifecycle events';
|
|
139
|
+
const summary = rows.length
|
|
140
|
+
? `Showing ${rows.length} ${label} for MID Server '${name}' (offset ${offset})`
|
|
141
|
+
: `No ${label} returned for MID Server '${name}' (offset ${offset})`;
|
|
142
|
+
const lines = [summary, ''];
|
|
143
|
+
for (const record of rows) {
|
|
144
|
+
lines.push(`${getStringField(record, issues ? 'last_detected' : 'sys_created_on')} ${getStringField(record, issues ? 'state' : 'event')}`);
|
|
145
|
+
const source = getStringField(record, 'source');
|
|
146
|
+
const message = getStringField(record, 'message');
|
|
147
|
+
if (source) lines.push(` ${source}`);
|
|
148
|
+
if (message) lines.push(` ${message}`);
|
|
149
|
+
lines.push(` ${record.url}`, '');
|
|
150
|
+
}
|
|
151
|
+
lines.push(`View in ServiceNow: ${url.href}`);
|
|
152
|
+
app.ok({
|
|
153
|
+
mid_server: name, sys_id: sysId, table, count: rows.length, records: rows,
|
|
154
|
+
pagination: { limit, offset }, url: url.href, context: { instance_url: instanceURL },
|
|
155
|
+
_formatted: `${lines.join('\n')}\n`,
|
|
156
|
+
}, { summary });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function midDiagnosticsCommand(kind, wrap) {
|
|
160
|
+
return {
|
|
161
|
+
command: `${kind} [identifier]`,
|
|
162
|
+
describe: kind === 'issues' ? 'Show MID Server issues (unresolved by default)' : 'Show MID Server lifecycle history',
|
|
163
|
+
builder: b => {
|
|
164
|
+
b.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records (1-1000)' })
|
|
165
|
+
.option('offset', { type: 'number', default: 0, describe: 'Result offset' });
|
|
166
|
+
if (kind === 'issues') b.option('all', { type: 'boolean', default: false, describe: 'Include resolved issues' });
|
|
167
|
+
return b;
|
|
168
|
+
},
|
|
169
|
+
handler: wrap(async (argv, app) => showMidDiagnostics(app, argv, kind)),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
94
173
|
async function listAgents(app, argv) {
|
|
95
174
|
const columns = argv.columns
|
|
96
175
|
? [...new Set(argv.columns.split(',').map(column => column.trim()).filter(Boolean))]
|
|
@@ -244,6 +323,8 @@ export function midserversCmd(wrap) {
|
|
|
244
323
|
aliases: ['midserver', 'mid'],
|
|
245
324
|
describe: 'Manage ServiceNow MID Servers and run commands through them',
|
|
246
325
|
builder: (y) => y
|
|
326
|
+
.command(midDiagnosticsCommand('issues', wrap))
|
|
327
|
+
.command(midDiagnosticsCommand('events', wrap))
|
|
247
328
|
.command({
|
|
248
329
|
command: 'list',
|
|
249
330
|
aliases: ['ls'],
|
|
@@ -457,12 +538,21 @@ export function midserversCmd(wrap) {
|
|
|
457
538
|
const responseError = getStringField(response, 'error');
|
|
458
539
|
if (responseError) throw new Error(`MID Server command failed: ${responseError}`);
|
|
459
540
|
const result = parseCommandResult(queueValue(response.payload));
|
|
541
|
+
const responseSysId = unwrapSysId(response);
|
|
542
|
+
const responseURL = responseSysId
|
|
543
|
+
? new URL(`/ecc_queue.do?sys_id=${encodeURIComponent(responseSysId)}`, app.getEffectiveInstance()).href
|
|
544
|
+
: '';
|
|
545
|
+
const summary = `Executed command on MID Server '${getStringField(record, 'name')}'`;
|
|
546
|
+
const lines = [summary, '', result.stdout || '(no stdout)'];
|
|
547
|
+
if (result.stderr) lines.push('', 'stderr:', result.stderr);
|
|
548
|
+
if (responseURL) lines.push('', `ECC input: ${responseURL}`);
|
|
460
549
|
app.ok({
|
|
461
550
|
mid_server: getStringField(record, 'name'),
|
|
462
551
|
command,
|
|
463
552
|
output: { sys_id: outputSysId, ...output },
|
|
464
|
-
response: { ...response, ...result },
|
|
465
|
-
|
|
553
|
+
response: { ...response, ...result, url: responseURL },
|
|
554
|
+
_formatted: `${lines.join('\n')}\n`,
|
|
555
|
+
}, { summary });
|
|
466
556
|
}),
|
|
467
557
|
}),
|
|
468
558
|
handler: () => {
|
|
@@ -470,6 +560,8 @@ export function midserversCmd(wrap) {
|
|
|
470
560
|
console.log('');
|
|
471
561
|
console.log('Available subcommands:');
|
|
472
562
|
console.log(' list List MID Servers');
|
|
563
|
+
console.log(' issues [name_or_sys_id] Show unresolved issues (--all includes resolved)');
|
|
564
|
+
console.log(' events [name_or_sys_id] Show lifecycle history');
|
|
473
565
|
console.log(' show <name_or_sys_id> Show a MID Server');
|
|
474
566
|
console.log(' add --name <name> Create a MID Server record');
|
|
475
567
|
console.log(' remove <name_or_sys_id> Remove a MID Server record');
|