@jacebenson/jsn 0.0.10 → 1.0.2
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 +7 -49
- package/bin/jsn.js +57 -2
- package/package.json +28 -32
- package/scripts/install.sh +227 -0
- package/scripts/npm-install.js +235 -0
- package/scripts/pre-commit-check.sh +61 -0
- package/src/app.js +0 -157
- package/src/auth.js +0 -283
- package/src/cli.js +0 -144
- package/src/commands/_ticket.js +0 -256
- package/src/commands/auth.js +0 -62
- package/src/commands/changes.js +0 -7
- package/src/commands/dev/_generic.js +0 -223
- package/src/commands/dev/_simple.js +0 -89
- package/src/commands/dev/eval.js +0 -17
- package/src/commands/dev/flows.js +0 -528
- package/src/commands/dev/forms.js +0 -313
- package/src/commands/dev/lists.js +0 -233
- package/src/commands/dev/logs.js +0 -51
- package/src/commands/dev/rest.js +0 -64
- package/src/commands/dev/scopes.js +0 -96
- package/src/commands/dev/updatesets.js +0 -97
- package/src/commands/dev.js +0 -53
- package/src/commands/groupmembers.js +0 -39
- package/src/commands/grouproles.js +0 -39
- package/src/commands/groups.js +0 -57
- package/src/commands/incidents.js +0 -7
- package/src/commands/profiles.js +0 -79
- package/src/commands/records.js +0 -137
- package/src/commands/requests.js +0 -7
- package/src/commands/setup.js +0 -39
- package/src/commands/tasks.js +0 -7
- package/src/commands/tickets.js +0 -121
- package/src/commands/users.js +0 -57
- package/src/commands/version.js +0 -25
- package/src/config.js +0 -154
- package/src/context.js +0 -62
- package/src/errors.js +0 -101
- package/src/helpers.js +0 -60
- package/src/output.js +0 -410
- package/src/sdk.js +0 -357
package/src/sdk.js
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
// ServiceNow REST API client
|
|
2
|
-
|
|
3
|
-
import { errAuth, errAPI, errNetwork } from './errors.js';
|
|
4
|
-
import { getStringField } from './helpers.js';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_TIMEOUT = 30000;
|
|
7
|
-
|
|
8
|
-
export class SDKClient {
|
|
9
|
-
constructor(baseURL, authProvider, opts = {}) {
|
|
10
|
-
this.baseURL = baseURL.replace(/\/$/, '');
|
|
11
|
-
this.authProvider = authProvider;
|
|
12
|
-
this.timeout = opts.timeout || DEFAULT_TIMEOUT;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async _setAuth(req) {
|
|
16
|
-
if (!this.authProvider) {
|
|
17
|
-
throw errAuth('No authentication configured');
|
|
18
|
-
}
|
|
19
|
-
const creds = await this.authProvider.getCredentials();
|
|
20
|
-
if (!creds) {
|
|
21
|
-
throw errAuth('No valid credentials');
|
|
22
|
-
}
|
|
23
|
-
switch (creds.auth_method) {
|
|
24
|
-
case 'basic':
|
|
25
|
-
req.headers.set('Authorization', 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`).toString('base64'));
|
|
26
|
-
break;
|
|
27
|
-
case 'token':
|
|
28
|
-
case 'oauth':
|
|
29
|
-
req.headers.set('Authorization', `Bearer ${creds.access_token}`);
|
|
30
|
-
break;
|
|
31
|
-
default:
|
|
32
|
-
if (creds.username && creds.password) {
|
|
33
|
-
req.headers.set('Authorization', 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`).toString('base64'));
|
|
34
|
-
} else if (creds.access_token) {
|
|
35
|
-
req.headers.set('Authorization', `Bearer ${creds.access_token}`);
|
|
36
|
-
} else {
|
|
37
|
-
throw errAuth('No valid credentials');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async request(endpoint, opts = {}) {
|
|
43
|
-
const controller = new AbortController();
|
|
44
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const req = new Request(endpoint, {
|
|
48
|
-
...opts,
|
|
49
|
-
signal: controller.signal,
|
|
50
|
-
});
|
|
51
|
-
req.headers.set('Accept', 'application/json');
|
|
52
|
-
if (opts.body && typeof opts.body === 'string') {
|
|
53
|
-
req.headers.set('Content-Type', 'application/json');
|
|
54
|
-
}
|
|
55
|
-
await this._setAuth(req);
|
|
56
|
-
|
|
57
|
-
const resp = await fetch(req);
|
|
58
|
-
const body = await resp.text();
|
|
59
|
-
|
|
60
|
-
if (!resp.ok) {
|
|
61
|
-
throw errAPI(resp.status, body || resp.statusText);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (resp.status === 204 || body === '') {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return JSON.parse(body);
|
|
69
|
-
} catch (err) {
|
|
70
|
-
if (err.name === 'AbortError') {
|
|
71
|
-
throw errNetwork(new Error('Request timed out'));
|
|
72
|
-
}
|
|
73
|
-
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT') {
|
|
74
|
-
throw errNetwork(err);
|
|
75
|
-
}
|
|
76
|
-
throw err;
|
|
77
|
-
} finally {
|
|
78
|
-
clearTimeout(timer);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async list(table, params = {}) {
|
|
83
|
-
const query = new URLSearchParams(params).toString();
|
|
84
|
-
const endpoint = `${this.baseURL}/api/now/table/${table}${query ? '?' + query : ''}`;
|
|
85
|
-
const result = await this.request(endpoint, { method: 'GET' });
|
|
86
|
-
return result?.result || [];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async get(table, sysID) {
|
|
90
|
-
const endpoint = `${this.baseURL}/api/now/table/${table}/${sysID}`;
|
|
91
|
-
const result = await this.request(endpoint, { method: 'GET' });
|
|
92
|
-
return result?.result || null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async create(table, data) {
|
|
96
|
-
const endpoint = `${this.baseURL}/api/now/table/${table}`;
|
|
97
|
-
const result = await this.request(endpoint, {
|
|
98
|
-
method: 'POST',
|
|
99
|
-
body: JSON.stringify(data),
|
|
100
|
-
});
|
|
101
|
-
return result?.result || null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async update(table, sysID, data) {
|
|
105
|
-
const endpoint = `${this.baseURL}/api/now/table/${table}/${sysID}`;
|
|
106
|
-
const result = await this.request(endpoint, {
|
|
107
|
-
method: 'PUT',
|
|
108
|
-
body: JSON.stringify(data),
|
|
109
|
-
});
|
|
110
|
-
return result?.result || null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async delete(table, sysID) {
|
|
114
|
-
const endpoint = `${this.baseURL}/api/now/table/${table}/${sysID}`;
|
|
115
|
-
await this.request(endpoint, { method: 'DELETE' });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async inspectFlow(identifier) {
|
|
119
|
-
const isSysID = identifier.length === 32 && /^[0-9a-fA-F]+$/.test(identifier);
|
|
120
|
-
|
|
121
|
-
// 1) Resolve flow
|
|
122
|
-
const flowQuery = new URLSearchParams();
|
|
123
|
-
flowQuery.set('sysparm_display_value', 'all');
|
|
124
|
-
flowQuery.set('sysparm_limit', '1');
|
|
125
|
-
flowQuery.set('sysparm_query', isSysID ? `sys_id=${identifier}` : `name=${identifier}`);
|
|
126
|
-
flowQuery.set('sysparm_fields', 'sys_id,name,active,version,type');
|
|
127
|
-
|
|
128
|
-
const flowRecords = await this.list('sys_hub_flow', flowQuery);
|
|
129
|
-
if (!flowRecords || flowRecords.length === 0) {
|
|
130
|
-
const err = new Error(`flow not found: ${identifier}`);
|
|
131
|
-
err.code = 'not_found';
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const flow = flowRecords[0];
|
|
136
|
-
const flowSysID = getStringField(flow, 'sys_id');
|
|
137
|
-
let flowVersion = getStringField(flow, 'version');
|
|
138
|
-
|
|
139
|
-
const inspection = {
|
|
140
|
-
flow: {
|
|
141
|
-
name: getStringField(flow, 'name'),
|
|
142
|
-
active: getBoolField(flow, 'active'),
|
|
143
|
-
version: flowVersion,
|
|
144
|
-
type: getStringField(flow, 'type'),
|
|
145
|
-
sysID: flowSysID,
|
|
146
|
-
},
|
|
147
|
-
version: {},
|
|
148
|
-
payload: {},
|
|
149
|
-
triggerInstances: [],
|
|
150
|
-
actionInstances: [],
|
|
151
|
-
flowLogicInstances: [],
|
|
152
|
-
subFlowInstances: [],
|
|
153
|
-
flowInputs: [],
|
|
154
|
-
flowOutputs: [],
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// 2) Fetch latest flow version
|
|
158
|
-
const versionQuery = new URLSearchParams();
|
|
159
|
-
versionQuery.set('sysparm_display_value', 'all');
|
|
160
|
-
versionQuery.set('sysparm_limit', '1');
|
|
161
|
-
versionQuery.set('sysparm_query', `flow=${flowSysID}^ORDERBYDESCsys_updated_on`);
|
|
162
|
-
versionQuery.set('sysparm_fields', 'sys_id,flow,version,payload,sys_updated_on');
|
|
163
|
-
|
|
164
|
-
const versionRecords = await this.list('sys_hub_flow_version', versionQuery);
|
|
165
|
-
if (versionRecords && versionRecords.length > 0) {
|
|
166
|
-
inspection.version = versionRecords[0];
|
|
167
|
-
if (!inspection.flow.version) {
|
|
168
|
-
inspection.flow.version = getStringField(versionRecords[0], 'version');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const payload = getStringField(versionRecords[0], 'payload');
|
|
172
|
-
if (payload) {
|
|
173
|
-
try {
|
|
174
|
-
const payloadData = JSON.parse(payload);
|
|
175
|
-
inspection.payload = payloadData;
|
|
176
|
-
extractPayloadData(inspection, payloadData);
|
|
177
|
-
} catch {
|
|
178
|
-
// ignore parse error
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 3) Fetch trigger instances
|
|
184
|
-
const triggerQuery = new URLSearchParams();
|
|
185
|
-
triggerQuery.set('sysparm_display_value', 'all');
|
|
186
|
-
triggerQuery.set('sysparm_query', `flow=${flowSysID}`);
|
|
187
|
-
triggerQuery.set('sysparm_fields', 'sys_id,name,trigger_type,display_text,active,trigger_definition');
|
|
188
|
-
triggerQuery.set('sysparm_limit', '20');
|
|
189
|
-
const triggerRecords = await this.list('sys_hub_trigger_instance', triggerQuery);
|
|
190
|
-
if (triggerRecords) {
|
|
191
|
-
inspection.triggerInstances = triggerRecords;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 4) Fallbacks when payload did not provide structure arrays
|
|
195
|
-
if (!inspection.actionInstances || inspection.actionInstances.length === 0) {
|
|
196
|
-
const actionQuery = new URLSearchParams();
|
|
197
|
-
actionQuery.set('sysparm_display_value', 'all');
|
|
198
|
-
actionQuery.set('sysparm_query', `flow=${flowSysID}^ORDERBYorder`);
|
|
199
|
-
actionQuery.set('sysparm_fields', 'sys_id,order,name,display_text,comment,action_type');
|
|
200
|
-
actionQuery.set('sysparm_limit', '200');
|
|
201
|
-
const records = await this.list('sys_hub_action_instance', actionQuery);
|
|
202
|
-
if (records) inspection.actionInstances = records;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (!inspection.flowLogicInstances || inspection.flowLogicInstances.length === 0) {
|
|
206
|
-
const logicTables = ['sys_hub_flow_logic', 'sys_hub_flow_logic_instance_v2'];
|
|
207
|
-
for (const table of logicTables) {
|
|
208
|
-
const logicQuery = new URLSearchParams();
|
|
209
|
-
logicQuery.set('sysparm_display_value', 'all');
|
|
210
|
-
logicQuery.set('sysparm_query', `flow=${flowSysID}^ORDERBYorder`);
|
|
211
|
-
logicQuery.set('sysparm_fields', 'sys_id,order,name,display_text,comment,parent_ui_id,logic_definition');
|
|
212
|
-
logicQuery.set('sysparm_limit', '200');
|
|
213
|
-
const records = await this.list(table, logicQuery);
|
|
214
|
-
if (records) inspection.flowLogicInstances.push(...records);
|
|
215
|
-
}
|
|
216
|
-
inspection.flowLogicInstances.sort((a, b) => parseOrderField(a) - parseOrderField(b));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (!inspection.subFlowInstances || inspection.subFlowInstances.length === 0) {
|
|
220
|
-
const subflowQuery = new URLSearchParams();
|
|
221
|
-
subflowQuery.set('sysparm_display_value', 'all');
|
|
222
|
-
subflowQuery.set('sysparm_query', `flow=${flowSysID}^ORDERBYorder`);
|
|
223
|
-
subflowQuery.set('sysparm_fields', 'sys_id,order,subflow,name,display_text,comment,parent_ui_id');
|
|
224
|
-
subflowQuery.set('sysparm_limit', '200');
|
|
225
|
-
const records = await this.list('sys_hub_sub_flow_instance', subflowQuery);
|
|
226
|
-
if (records) inspection.subFlowInstances = records;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Inputs/Outputs fallback
|
|
230
|
-
if (!inspection.flowInputs || inspection.flowInputs.length === 0) {
|
|
231
|
-
const inputsQuery = new URLSearchParams();
|
|
232
|
-
inputsQuery.set('sysparm_display_value', 'all');
|
|
233
|
-
inputsQuery.set('sysparm_query', `model=${flowSysID}^ORDERBYorder`);
|
|
234
|
-
inputsQuery.set('sysparm_fields', 'sys_id,name,label,type,mandatory,order');
|
|
235
|
-
inputsQuery.set('sysparm_limit', '200');
|
|
236
|
-
const records = await this.list('sys_hub_flow_input', inputsQuery);
|
|
237
|
-
if (records) inspection.flowInputs = records;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!inspection.flowOutputs || inspection.flowOutputs.length === 0) {
|
|
241
|
-
const outputsQuery = new URLSearchParams();
|
|
242
|
-
outputsQuery.set('sysparm_display_value', 'all');
|
|
243
|
-
outputsQuery.set('sysparm_query', `model=${flowSysID}^ORDERBYorder`);
|
|
244
|
-
outputsQuery.set('sysparm_fields', 'sys_id,name,label,type,order');
|
|
245
|
-
outputsQuery.set('sysparm_limit', '200');
|
|
246
|
-
const records = await this.list('sys_hub_flow_output', outputsQuery);
|
|
247
|
-
if (records) inspection.flowOutputs = records;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return inspection;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async aggregateCount(table, queryStr) {
|
|
254
|
-
const params = new URLSearchParams();
|
|
255
|
-
params.set('sysparm_count', 'true');
|
|
256
|
-
if (queryStr) params.set('sysparm_query', queryStr);
|
|
257
|
-
const endpoint = `${this.baseURL}/api/now/stats/${table}?${params.toString()}`;
|
|
258
|
-
const result = await this.request(endpoint, { method: 'GET' });
|
|
259
|
-
const stats = result?.result?.stats;
|
|
260
|
-
if (!stats) return 0;
|
|
261
|
-
|
|
262
|
-
let statsMap = stats;
|
|
263
|
-
if (typeof stats === 'string') {
|
|
264
|
-
try { statsMap = JSON.parse(stats); } catch { return 0; }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (statsMap.count != null) {
|
|
268
|
-
const v = statsMap.count;
|
|
269
|
-
if (typeof v === 'number') return v;
|
|
270
|
-
if (typeof v === 'string') {
|
|
271
|
-
const n = parseInt(v, 10);
|
|
272
|
-
return isNaN(n) ? 0 : n;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
for (const value of Object.values(statsMap)) {
|
|
277
|
-
if (value && typeof value === 'object' && value.count != null) {
|
|
278
|
-
const v = value.count;
|
|
279
|
-
if (typeof v === 'number') return v;
|
|
280
|
-
if (typeof v === 'string') {
|
|
281
|
-
const n = parseInt(v, 10);
|
|
282
|
-
return isNaN(n) ? 0 : n;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return 0;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function getBoolField(record, field) {
|
|
292
|
-
const val = record?.[field];
|
|
293
|
-
if (val == null) return false;
|
|
294
|
-
if (typeof val === 'boolean') return val;
|
|
295
|
-
if (typeof val === 'string') return val === 'true' || val === '1';
|
|
296
|
-
if (typeof val === 'object') {
|
|
297
|
-
const dv = val.display_value;
|
|
298
|
-
if (dv != null) return String(dv) === 'true' || String(dv) === '1';
|
|
299
|
-
const v = val.value;
|
|
300
|
-
if (v != null) return String(v) === 'true' || String(v) === '1';
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function extractPayloadData(inspection, payload) {
|
|
306
|
-
const actions = toMapSlice(payload.actionInstances);
|
|
307
|
-
if (actions) inspection.actionInstances = actions;
|
|
308
|
-
|
|
309
|
-
const logic = toMapSlice(payload.flowLogicInstances);
|
|
310
|
-
if (logic) inspection.flowLogicInstances = logic;
|
|
311
|
-
|
|
312
|
-
const subflows = toMapSlice(payload.subFlowInstances);
|
|
313
|
-
if (subflows) inspection.subFlowInstances = subflows;
|
|
314
|
-
|
|
315
|
-
const inputs = toMapSlice(payload.inputs);
|
|
316
|
-
if (inputs) inspection.flowInputs = inputs;
|
|
317
|
-
|
|
318
|
-
const outputs = toMapSlice(payload.outputs);
|
|
319
|
-
if (outputs) inspection.flowOutputs = outputs;
|
|
320
|
-
|
|
321
|
-
const triggerInstances = payload.triggerInstances;
|
|
322
|
-
if (!Array.isArray(triggerInstances) || triggerInstances.length === 0) return;
|
|
323
|
-
|
|
324
|
-
const first = triggerInstances[0];
|
|
325
|
-
if (!first || typeof first !== 'object') return;
|
|
326
|
-
|
|
327
|
-
if (!inspection.version || typeof inspection.version !== 'object') {
|
|
328
|
-
inspection.version = {};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const triggerName = getStringField(first, 'name');
|
|
332
|
-
if (triggerName) inspection.version.trigger_name = triggerName;
|
|
333
|
-
|
|
334
|
-
const triggerType = getStringField(first, 'type');
|
|
335
|
-
if (triggerType) inspection.version.trigger_type = triggerType;
|
|
336
|
-
|
|
337
|
-
if (Array.isArray(first.inputs)) {
|
|
338
|
-
for (const input of first.inputs) {
|
|
339
|
-
if (!input || typeof input !== 'object') continue;
|
|
340
|
-
const name = getStringField(input, 'name');
|
|
341
|
-
const value = getStringField(input, 'value');
|
|
342
|
-
if (name === 'table' && value) inspection.version.trigger_table = value;
|
|
343
|
-
if (name === 'time' && value) inspection.version.trigger_time = value;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function toMapSlice(v) {
|
|
349
|
-
if (!Array.isArray(v)) return null;
|
|
350
|
-
return v.filter(item => item && typeof item === 'object');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function parseOrderField(record) {
|
|
354
|
-
const order = getStringField(record, 'order');
|
|
355
|
-
const n = parseInt(order, 10);
|
|
356
|
-
return isNaN(n) ? 0 : n;
|
|
357
|
-
}
|