@jacebenson/jsn 3.1.3 → 3.1.5
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 +2 -47
- package/src/cli.js +5 -7
- package/src/commands/catalog.js +97 -70
- package/src/commands/dev/_generic.js +1 -0
- package/src/commands/dev/_simple.js +11 -13
- package/src/commands/dev/aliases.js +30 -0
- package/src/commands/dev/b4rules.js +1 -1
- package/src/commands/dev/columns.js +27 -0
- package/src/commands/dev/import.js +27 -0
- package/src/commands/dev/lists.js +93 -210
- package/src/commands/dev/restmessage.js +32 -0
- package/src/commands/dev/soapmessages.js +30 -0
- package/src/commands/dev/uipolicies.js +28 -0
- package/src/commands/dev.js +1 -2
- package/src/commands/groups.js +23 -1
- package/src/commands/users.js +8 -1
- package/src/help.js +0 -4
- package/src/output.js +18 -3
- package/src/paginated-search.js +1 -17
- package/src/sdk.js +0 -4
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import crypto from 'node:crypto';
|
|
10
|
-
import readline from 'node:readline';
|
|
11
10
|
import { execSync } from 'node:child_process';
|
|
11
|
+
import { password as passwordPrompt } from '@inquirer/prompts';
|
|
12
12
|
import { globalConfigDir, normalizeInstanceURL } from './config.js';
|
|
13
13
|
import { errAuth } from './errors.js';
|
|
14
14
|
|
|
@@ -248,52 +248,7 @@ function deleteCredentials(instance, username) {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
function askHidden(promptText) {
|
|
251
|
-
return
|
|
252
|
-
const rl = readline.createInterface({
|
|
253
|
-
input: process.stdin,
|
|
254
|
-
output: process.stdout,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const stdin = process.stdin;
|
|
258
|
-
const stdout = process.stdout;
|
|
259
|
-
|
|
260
|
-
if (!stdin.isTTY) {
|
|
261
|
-
rl.question(promptText, (answer) => {
|
|
262
|
-
rl.close();
|
|
263
|
-
resolve(answer.trim());
|
|
264
|
-
});
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
stdout.write(promptText);
|
|
269
|
-
|
|
270
|
-
stdin.setRawMode(true);
|
|
271
|
-
stdin.resume();
|
|
272
|
-
stdin.setEncoding('utf-8');
|
|
273
|
-
|
|
274
|
-
let input = '';
|
|
275
|
-
const onData = (key) => {
|
|
276
|
-
if (key === '\r' || key === '\n') {
|
|
277
|
-
stdin.removeListener('data', onData);
|
|
278
|
-
stdin.setRawMode(false);
|
|
279
|
-
stdin.pause();
|
|
280
|
-
stdout.write('\n');
|
|
281
|
-
rl.close();
|
|
282
|
-
resolve(input);
|
|
283
|
-
} else if (key === '\u0003') {
|
|
284
|
-
process.exit();
|
|
285
|
-
} else if (key === '\u007f') {
|
|
286
|
-
if (input.length > 0) {
|
|
287
|
-
input = input.slice(0, -1);
|
|
288
|
-
stdout.write('\b \b');
|
|
289
|
-
}
|
|
290
|
-
} else {
|
|
291
|
-
input += key;
|
|
292
|
-
stdout.write('*');
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
stdin.on('data', onData);
|
|
296
|
-
});
|
|
251
|
+
return passwordPrompt({ message: promptText });
|
|
297
252
|
}
|
|
298
253
|
|
|
299
254
|
export class AuthManager {
|
package/src/cli.js
CHANGED
|
@@ -35,12 +35,13 @@ import {
|
|
|
35
35
|
spPagesCmd, spWidgetsCmd, uiPagesCmd, appMenuCmd,
|
|
36
36
|
aclsCmd, rolesCmd, propertiesCmd,
|
|
37
37
|
relationshipsCmd, appmodulesCmd, listcontrolsCmd, viewsCmd,
|
|
38
|
-
privilegesCmd,
|
|
38
|
+
privilegesCmd, uxscriptsCmd, aliasesCmd,
|
|
39
39
|
catalogscriptsCmd, cataloguipoliciesCmd,
|
|
40
|
+
uipoliciesCmd,
|
|
40
41
|
scriptactionsCmd, scheduledjobsCmd, asyncrulesCmd, triggersCmd,
|
|
41
|
-
|
|
42
|
+
decisiontablesCmd, assignmentsCmd,
|
|
42
43
|
emailCmd,
|
|
43
|
-
restmessageCmd,
|
|
44
|
+
restmessageCmd, soapmessagesCmd,
|
|
44
45
|
uimacrosCmd, uxlistsCmd, uxapplicabilityCmd,
|
|
45
46
|
} from './commands/dev/_simple.js';
|
|
46
47
|
import { flowsCmd } from './commands/dev/flows.js';
|
|
@@ -222,7 +223,6 @@ export function buildCLI() {
|
|
|
222
223
|
.command(triggersCmd(wrap))
|
|
223
224
|
// AUTOMATION — In Memory
|
|
224
225
|
.command(rulesCmd(wrap))
|
|
225
|
-
.command(workflowsCmd(wrap))
|
|
226
226
|
.command(decisiontablesCmd(wrap))
|
|
227
227
|
.command(assignmentsCmd(wrap))
|
|
228
228
|
// AUTOMATION — Inbound
|
|
@@ -230,15 +230,12 @@ export function buildCLI() {
|
|
|
230
230
|
.command(emailCmd(wrap))
|
|
231
231
|
// AUTOMATION — Outbound
|
|
232
232
|
.command(restmessageCmd(wrap))
|
|
233
|
-
.command(restmethodsCmd(wrap))
|
|
234
233
|
.command(soapmessagesCmd(wrap))
|
|
235
|
-
.command(soapfunctionsCmd(wrap))
|
|
236
234
|
// ACCESS
|
|
237
235
|
.command(aclsCmd(wrap))
|
|
238
236
|
.command(b4rulesCmd(wrap))
|
|
239
237
|
.command(rolesCmd(wrap))
|
|
240
238
|
.command(privilegesCmd(wrap))
|
|
241
|
-
.command(securitytypesCmd(wrap))
|
|
242
239
|
.command(aliasesCmd(wrap))
|
|
243
240
|
.command(groupsCmd(wrap))
|
|
244
241
|
.command(groupMembersCmd(wrap))
|
|
@@ -252,6 +249,7 @@ export function buildCLI() {
|
|
|
252
249
|
.command(uiActionsCmd(wrap))
|
|
253
250
|
.command(viewsCmd(wrap))
|
|
254
251
|
.command(catalogscriptsCmd(wrap))
|
|
252
|
+
.command(uipoliciesCmd(wrap))
|
|
255
253
|
.command(cataloguipoliciesCmd(wrap))
|
|
256
254
|
// USER EXPERIENCE — Core UI
|
|
257
255
|
.command(uiPagesCmd(wrap))
|
package/src/commands/catalog.js
CHANGED
|
@@ -14,9 +14,34 @@ export function catalogCmd(wrap) {
|
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('name', { alias: 'n', type: 'string', demandOption: true })
|
|
16
16
|
.option('short-description', { alias: 'd', type: 'string' })
|
|
17
|
-
.option('category', { alias: 'c', type: 'string' })
|
|
17
|
+
.option('category', { alias: 'c', type: 'string' })
|
|
18
|
+
.option('variable', { alias: 'v', type: 'array', describe: 'Variable: "name:type:label"' })
|
|
19
|
+
.option('variables', { type: 'string', describe: 'JSON for submit_produce: {\"key\":\"value\"}' }),
|
|
18
20
|
handler: wrap(async (argv, app) => {
|
|
19
21
|
app.requireInstance();
|
|
22
|
+
|
|
23
|
+
// submit_produce path
|
|
24
|
+
if (argv.variables) {
|
|
25
|
+
let varsObj;
|
|
26
|
+
try { varsObj = JSON.parse(argv.variables); } catch { throw new Error('--variables must be valid JSON'); }
|
|
27
|
+
let itemSysID = argv.name;
|
|
28
|
+
if (!/^[a-f0-9]{32}$/i.test(itemSysID)) {
|
|
29
|
+
const sp = new URLSearchParams();
|
|
30
|
+
sp.set('sysparm_query', `name=${itemSysID}`);
|
|
31
|
+
sp.set('sysparm_limit', '1');
|
|
32
|
+
sp.set('sysparm_fields', 'sys_id');
|
|
33
|
+
const items = await app.sdk.list('sc_cat_item', sp);
|
|
34
|
+
if (items.length === 0) throw new Error(`Not found: ${itemSysID}`);
|
|
35
|
+
itemSysID = items[0].sys_id?.value || items[0].sys_id;
|
|
36
|
+
}
|
|
37
|
+
const endpoint = `${app.sdk.baseURL}/api/sn_sc/servicecatalog/items/${itemSysID}/submit_produce`;
|
|
38
|
+
const result = await app.sdk.request(endpoint, { method: 'POST', body: JSON.stringify({ variables: varsObj }) });
|
|
39
|
+
const reqID = result?.result?.sys_id || result?.result?.number || '';
|
|
40
|
+
app.ok({ requested_item: reqID, item_id: itemSysID, variables: varsObj },
|
|
41
|
+
{ summary: `Request: ${result?.result?.number || reqID}` });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
let categoryID = '';
|
|
21
46
|
if (argv.category) {
|
|
22
47
|
const cp = new URLSearchParams();
|
|
@@ -34,7 +59,31 @@ export function catalogCmd(wrap) {
|
|
|
34
59
|
active: true,
|
|
35
60
|
type: 'item',
|
|
36
61
|
});
|
|
37
|
-
|
|
62
|
+
const itemID = item.sys_id?.value || item.sys_id;
|
|
63
|
+
|
|
64
|
+
// Create variables if --variable flag(s) provided
|
|
65
|
+
let varCount = 0;
|
|
66
|
+
if (argv.variable) {
|
|
67
|
+
for (const v of argv.variable) {
|
|
68
|
+
const parts = String(v).split(':');
|
|
69
|
+
if (parts.length >= 2) {
|
|
70
|
+
const typeName = parts.length >= 2 ? parts[parts.length - 1] : '';
|
|
71
|
+
const label = parts.length >= 3 ? parts[parts.length - 1] : parts[0];
|
|
72
|
+
const sysName = parts[0].replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
|
|
73
|
+
try {
|
|
74
|
+
const typeMap = { string: '6', multilinetext: '2', select: '3', date: '4', datetime: '5', reference: '8', checkbox: '12', email: '11' };
|
|
75
|
+
await app.sdk.create('item_option_new', {
|
|
76
|
+
name: sysName, question_text: label, type: typeMap[typeName] || '6',
|
|
77
|
+
order: (varCount + 1) * 100, cat_item: itemID, mandatory: true, active: true,
|
|
78
|
+
});
|
|
79
|
+
varCount++;
|
|
80
|
+
} catch { /* non-fatal */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
app.ok({ sys_id: itemID, name: argv.name, variables_created: varCount },
|
|
86
|
+
{ summary: `Created: ${argv.name}${varCount ? ` (${varCount} variable(s))` : ''}` });
|
|
38
87
|
}),
|
|
39
88
|
})
|
|
40
89
|
.command({
|
|
@@ -43,10 +92,11 @@ export function catalogCmd(wrap) {
|
|
|
43
92
|
describe: 'List catalog items',
|
|
44
93
|
builder: (y) => y
|
|
45
94
|
.option('limit', { alias: 'l', type: 'number', default: 50 })
|
|
46
|
-
.option('category', { type: 'string' })
|
|
95
|
+
.option('category', { type: 'string' })
|
|
96
|
+
.option('query', { type: 'string', describe: 'Encoded query (e.g. active=true)' }),
|
|
47
97
|
handler: wrap(async (argv, app) => {
|
|
48
98
|
app.requireInstance();
|
|
49
|
-
const query = argv.category ? `category.titleLIKE${argv.category}` : '';
|
|
99
|
+
const query = argv.query || (argv.category ? `category.titleLIKE${argv.category}` : '');
|
|
50
100
|
|
|
51
101
|
const picked = await interactiveList({
|
|
52
102
|
app,
|
|
@@ -69,20 +119,27 @@ export function catalogCmd(wrap) {
|
|
|
69
119
|
return;
|
|
70
120
|
}
|
|
71
121
|
|
|
72
|
-
// Fallback — non-TTY
|
|
122
|
+
// Fallback — non-TTY / --json / --styled
|
|
73
123
|
const p = new URLSearchParams();
|
|
74
124
|
p.set('sysparm_query', query ? `${query}^ORDERBYname` : 'ORDERBYname');
|
|
75
125
|
p.set('sysparm_limit', String(argv.limit));
|
|
76
126
|
p.set('sysparm_display_value', 'all');
|
|
77
127
|
p.set('sysparm_fields', 'name,short_description,category,active');
|
|
78
128
|
const records = await app.sdk.list('sc_cat_item', p);
|
|
129
|
+
// Map reference fields to strings for styled/table renderer
|
|
130
|
+
const safe = records.map(r => ({
|
|
131
|
+
name: getStringField(r, 'name'),
|
|
132
|
+
short_description: getStringField(r, 'short_description'),
|
|
133
|
+
category: getStringField(r, 'category'),
|
|
134
|
+
active: getStringField(r, 'active'),
|
|
135
|
+
}));
|
|
79
136
|
app.ok({
|
|
80
137
|
table: 'sc_cat_item',
|
|
81
|
-
count:
|
|
138
|
+
count: safe.length,
|
|
82
139
|
columns: ['name', 'short_description', 'category', 'active'],
|
|
83
|
-
records,
|
|
140
|
+
records: safe,
|
|
84
141
|
context: { instance_url: app.getEffectiveInstance() },
|
|
85
|
-
}, { summary: `${
|
|
142
|
+
}, { summary: `${safe.length} catalog item(s)` });
|
|
86
143
|
}),
|
|
87
144
|
})
|
|
88
145
|
.command({
|
|
@@ -101,7 +158,6 @@ export function catalogCmd(wrap) {
|
|
|
101
158
|
if (results.length === 0) throw new Error(`Not found: ${id}`);
|
|
102
159
|
id = results[0].sys_id?.value || results[0].sys_id;
|
|
103
160
|
}
|
|
104
|
-
const item = await app.sdk.get('sc_cat_item', id);
|
|
105
161
|
await showItem(app, id);
|
|
106
162
|
}),
|
|
107
163
|
});
|
|
@@ -120,7 +176,6 @@ export function catalogCmd(wrap) {
|
|
|
120
176
|
async function showItem(app, sysID) {
|
|
121
177
|
const item = await app.sdk.get('sc_cat_item', sysID);
|
|
122
178
|
|
|
123
|
-
// Fetch variables
|
|
124
179
|
const vp = new URLSearchParams();
|
|
125
180
|
vp.set('sysparm_query', `cat_item=${sysID}^active=true`);
|
|
126
181
|
vp.set('sysparm_limit', '100');
|
|
@@ -145,68 +200,40 @@ async function showItem(app, sysID) {
|
|
|
145
200
|
standalone.push(entry);
|
|
146
201
|
}
|
|
147
202
|
}
|
|
148
|
-
|
|
149
203
|
const totalVars = standalone.length + Array.from(setVars.values()).reduce((a, s) => a + s.variables.length, 0);
|
|
150
204
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const flowID = item.flow_designer_flow?.value || '';
|
|
161
|
-
const wfName = getStringField(item, 'workflow');
|
|
162
|
-
const wfID = item.workflow?.value || '';
|
|
163
|
-
const planName = getStringField(item, 'delivery_plan') || getStringField(item, 'execution_plan');
|
|
164
|
-
|
|
165
|
-
if (flowID) {
|
|
166
|
-
if (flowName === flowID) {
|
|
167
|
-
try {
|
|
168
|
-
const flowRec = await app.sdk.get('sys_hub_flow', flowID);
|
|
169
|
-
lines.push(` Flow: ${getStringField(flowRec, 'name') || flowID}`);
|
|
170
|
-
} catch {
|
|
171
|
-
lines.push(` Flow: ${flowName}`);
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
lines.push(` Flow: ${flowName}`);
|
|
175
|
-
}
|
|
176
|
-
lines.push(` → jsn flows show ${flowID}`);
|
|
177
|
-
} else if (wfName) {
|
|
178
|
-
lines.push(` Workflow: ${wfName}`);
|
|
179
|
-
if (wfID) lines.push(` → jsn workflows show ${wfID}`);
|
|
180
|
-
} else if (planName) {
|
|
181
|
-
const planID = item.delivery_plan?.value || item.execution_plan?.value || '';
|
|
182
|
-
if (planName === planID && planID) {
|
|
183
|
-
try {
|
|
184
|
-
const planRec = await app.sdk.get('delivery_plan', planID);
|
|
185
|
-
const realName = getStringField(planRec, 'name') || getStringField(planRec, 'short_description') || planID;
|
|
186
|
-
lines.push(` Execution Plan: ${realName}`);
|
|
187
|
-
lines.push(` → jsn records get --table delivery_plan --sys-id ${planID}`);
|
|
188
|
-
} catch {
|
|
189
|
-
lines.push(` Execution Plan: ${planName}`);
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
lines.push(` Execution Plan: ${planName}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (totalVars > 0) {
|
|
197
|
-
lines.push(` Variables (${totalVars}):`);
|
|
198
|
-
for (const v of standalone) {
|
|
199
|
-
const flags = String(v.mandatory) === 'true' ? ' [required]' : '';
|
|
200
|
-
lines.push(` ${v.question_text || v.name} ${v.type || ''}${flags}`);
|
|
201
|
-
}
|
|
202
|
-
for (const [_, sd] of setVars) {
|
|
203
|
-
lines.push(` [${sd.name}]`);
|
|
204
|
-
for (const v of sd.variables) {
|
|
205
|
-
const flags = String(v.mandatory) === 'true' ? ' [required]' : '';
|
|
206
|
-
lines.push(` ${v.question_text || v.name} ${v.type || ''}${flags}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
205
|
+
// Resolve flow name
|
|
206
|
+
let flowName = '', flowCmd = '';
|
|
207
|
+
const flowVal = item.flow_designer_flow;
|
|
208
|
+
if (flowVal?.value) {
|
|
209
|
+
const fn = getStringField(item, 'flow_designer_flow');
|
|
210
|
+
if (fn === flowVal.value) {
|
|
211
|
+
try { const fr = await app.sdk.get('sys_hub_flow', flowVal.value); flowName = getStringField(fr, 'name') || ''; } catch { flowName = fn; }
|
|
212
|
+
} else { flowName = fn; }
|
|
213
|
+
flowCmd = `jsn flows show ${flowVal.value}`;
|
|
209
214
|
}
|
|
215
|
+
const wfName = getStringField(item, 'workflow') || '';
|
|
216
|
+
const wfID = item.workflow?.value || '';
|
|
217
|
+
const planName = getStringField(item, 'delivery_plan') || getStringField(item, 'execution_plan') || '';
|
|
218
|
+
const planID = item.delivery_plan?.value || item.execution_plan?.value || '';
|
|
210
219
|
|
|
211
|
-
|
|
220
|
+
app.ok({
|
|
221
|
+
name: getStringField(item, 'name'),
|
|
222
|
+
short_description: getStringField(item, 'short_description'),
|
|
223
|
+
category: getStringField(item, 'category'),
|
|
224
|
+
active: getStringField(item, 'active'),
|
|
225
|
+
url: `${app.getEffectiveInstance()}/sc_cat_item.do?sys_id=${sysID}`,
|
|
226
|
+
flow: flowName || undefined,
|
|
227
|
+
flow_cmd: flowCmd || undefined,
|
|
228
|
+
workflow: wfName || undefined,
|
|
229
|
+
workflow_cmd: (wfName && wfID) ? `jsn workflows show ${wfID}` : undefined,
|
|
230
|
+
execution_plan: planName || undefined,
|
|
231
|
+
variables: totalVars > 0 ? {
|
|
232
|
+
count: totalVars,
|
|
233
|
+
standalone: standalone.map(v => ({ label: v.question_text || v.name, type: v.type, mandatory: String(v.mandatory) === 'true' })),
|
|
234
|
+
sets: Array.from(setVars.values()).map(s => ({ name: s.name, variables: s.variables.map(v => ({ label: v.question_text || v.name, type: v.type, mandatory: String(v.mandatory) === 'true' })) })),
|
|
235
|
+
} : undefined,
|
|
236
|
+
}, {
|
|
237
|
+
summary: `${getStringField(item, 'name')} — ${totalVars} variable(s)`,
|
|
238
|
+
});
|
|
212
239
|
}
|
|
@@ -81,6 +81,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
81
81
|
const readOnly = opts.readOnly || false;
|
|
82
82
|
const scopeValidation = opts.scopeValidation || false;
|
|
83
83
|
const extraQuery = opts.extraQuery || '';
|
|
84
|
+
const formatLabel = opts.formatLabel || null;
|
|
84
85
|
const showSummary = opts.showSummary || ((record, id) => `${singular.charAt(0).toUpperCase() + singular.slice(1)}: ${getStringField(record, 'name') || id}`);
|
|
85
86
|
const showBreadcrumbs = opts.showBreadcrumbs || ((record, id) => {
|
|
86
87
|
const crumbs = [
|
|
@@ -73,10 +73,10 @@ export const tablesCmd = (wrap) => buildDevCmd('tables', 'sys_db_object', ['tabl
|
|
|
73
73
|
},
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
export
|
|
76
|
+
export { columnsCmd } from './columns.js';
|
|
77
77
|
|
|
78
78
|
// Read-only commands (Go only has list/show)
|
|
79
|
-
export
|
|
79
|
+
export { importCmd } from './import.js';
|
|
80
80
|
export const spPagesCmd = (wrap) => buildDevCmd('sppages', 'sp_page', ['sp-pages', 'pages'], ['id', 'title', 'sys_scope'], wrap, { singular: 'Service Portal page', readOnly: true });
|
|
81
81
|
export const spWidgetsCmd = (wrap) => buildDevCmd('spwidgets', 'sp_widget', ['sp-widget', 'widgets'], ['id', 'name', 'sys_scope'], wrap, { singular: 'Service Portal widget', readOnly: true });
|
|
82
82
|
export const uiPagesCmd = (wrap) => buildDevCmd('uipages', 'sys_ui_page', ['ui-page', 'pages'], ['name', 'sys_scope'], wrap, { singular: 'UI page', scopeValidation: true });
|
|
@@ -92,31 +92,28 @@ export const relationshipsCmd = (wrap) => buildDevCmd('relationships', 'sys_rela
|
|
|
92
92
|
export const appmodulesCmd = (wrap) => buildDevCmd('appmodules', 'sys_app_module', ['appmodule', 'am'], ['name', 'active', 'sys_scope'], wrap, { singular: 'application module', scopeValidation: true });
|
|
93
93
|
export const listcontrolsCmd = (wrap) => buildDevCmd('listcontrols', 'sys_ui_list_control', ['listcontrol', 'lc'], ['name', 'active', 'sys_scope'], wrap, { singular: 'list control', scopeValidation: true });
|
|
94
94
|
export const viewsCmd = (wrap) => buildDevCmd('views', 'sys_ui_view', ['view', 'vw'], ['name', 'title', 'sys_scope'], wrap, { singular: 'view', scopeValidation: true });
|
|
95
|
-
export const privilegesCmd = (wrap) => buildDevCmd('privileges', 'sys_scope_privilege', ['privilege', 'priv'], ['
|
|
96
|
-
export const securitytypesCmd = (wrap) => buildDevCmd('securitytypes', 'sys_security_type', ['securitytype', 'st'], ['name', 'active', 'sys_scope'], wrap, { singular: 'security type', scopeValidation: true });
|
|
95
|
+
export const privilegesCmd = (wrap) => buildDevCmd('privileges', 'sys_scope_privilege', ['privilege', 'priv'], ['source_scope', 'target_scope', 'target_name', 'operation', 'status'], wrap, { singular: 'privilege', scopeValidation: true });
|
|
97
96
|
export const uxscriptsCmd = (wrap) => buildDevCmd('uxscripts', 'sys_ux_lib_source_script', ['uxscript', 'ux'], ['name', 'active', 'sys_scope'], wrap, { singular: 'UX script', scopeValidation: true });
|
|
98
|
-
export
|
|
97
|
+
export { aliasesCmd } from './aliases.js';
|
|
99
98
|
export const catalogscriptsCmd = (wrap) => buildDevCmd('catalogscripts', 'catalog_script_client', ['catalogscript', 'cs'], ['name', 'table', 'active', 'sys_scope'], wrap, { singular: 'catalog script', scopeValidation: true });
|
|
100
99
|
|
|
101
100
|
// ── Automation: Async ──
|
|
102
101
|
export const scriptactionsCmd = (wrap) => buildDevCmd('scriptactions', 'sysevent_script_action', ['scriptaction'], ['name', 'active', 'sys_scope'], wrap, { singular: 'script action', scopeValidation: true });
|
|
103
102
|
export const scheduledjobsCmd = (wrap) => buildDevCmd('scheduledjobs', 'sysauto_script', ['scheduledjob'], ['name', 'active', 'run_type', 'sys_scope'], wrap, { singular: 'scheduled job', scopeValidation: true });
|
|
104
|
-
export const asyncrulesCmd = (wrap) => buildDevCmd('asyncrules', 'sys_script', ['asyncrule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'async business rule', scopeValidation: true, extraQuery: '
|
|
103
|
+
export const asyncrulesCmd = (wrap) => buildDevCmd('asyncrules', 'sys_script', ['asyncrule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'async business rule', scopeValidation: true, extraQuery: 'when=async' });
|
|
105
104
|
export const triggersCmd = (wrap) => buildDevCmd('triggers', 'sysevent_register', ['trigger'], ['name', 'event_name', 'active', 'sys_scope'], wrap, { singular: 'event trigger', scopeValidation: true });
|
|
106
105
|
|
|
107
106
|
// ── Automation: In Memory ──
|
|
108
|
-
export const
|
|
109
|
-
export const decisiontablesCmd = (wrap) => buildDevCmd('decisiontables', 'sys_decision', ['decisiontable'], ['name', 'active', 'sys_scope'], wrap, { singular: 'decision table', scopeValidation: true, readOnly: true });
|
|
107
|
+
export const decisiontablesCmd = (wrap) => buildDevCmd('decisiontables', 'sys_ws_definition', ['decisiontable'], ['name', 'active', 'sys_scope'], wrap, { singular: 'decision table', scopeValidation: true, readOnly: true });
|
|
110
108
|
export const assignmentsCmd = (wrap) => buildDevCmd('assignments', 'sysrule_assignment', ['assignment'], ['name', 'active', 'sys_scope'], wrap, { singular: 'assignment rule', scopeValidation: true, readOnly: true });
|
|
111
109
|
|
|
112
110
|
// ── Automation: Inbound ──
|
|
113
|
-
export const emailCmd = (wrap) => buildDevCmd('email', '
|
|
111
|
+
export const emailCmd = (wrap) => buildDevCmd('email', 'sysevent_in_email_action', ['emails'], ['name', 'active', 'type', 'sys_scope'], wrap, { singular: 'inbound email action', scopeValidation: true });
|
|
114
112
|
|
|
115
113
|
// ── Automation: Outbound ──
|
|
116
|
-
export
|
|
114
|
+
export { restmessageCmd } from './restmessage.js';
|
|
117
115
|
export const restmethodsCmd = (wrap) => buildDevCmd('restmethods', 'sys_rest_message_fn', ['restmethod'], ['name', 'http_method', 'active', 'sys_scope'], wrap, { singular: 'REST method', scopeValidation: true });
|
|
118
|
-
export
|
|
119
|
-
export const soapfunctionsCmd = (wrap) => buildDevCmd('soapfunctions', 'sys_soap_message_function', ['soapfunc'], ['name', 'active', 'sys_scope'], wrap, { singular: 'SOAP function', scopeValidation: true });
|
|
116
|
+
export { soapmessagesCmd } from './soapmessages.js';
|
|
120
117
|
|
|
121
118
|
// ── UX: Core UI ──
|
|
122
119
|
export const uimacrosCmd = (wrap) => buildDevCmd('uimacros', 'sys_ui_macro', ['uimacro'], ['name', 'active', 'sys_scope'], wrap, { singular: 'UI macro', scopeValidation: true });
|
|
@@ -124,4 +121,5 @@ export const uimacrosCmd = (wrap) => buildDevCmd('uimacros', 'sys_ui_macro', ['u
|
|
|
124
121
|
// ── UX: Workspaces ──
|
|
125
122
|
export const uxlistsCmd = (wrap) => buildDevCmd('uxlists', 'sys_ux_list', ['uxlist'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace list', scopeValidation: true, readOnly: true });
|
|
126
123
|
export const uxapplicabilityCmd = (wrap) => buildDevCmd('uxapplicability', 'sys_ux_applicability_m2m', ['uxapp'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace applicability', scopeValidation: true, readOnly: true });
|
|
127
|
-
export
|
|
124
|
+
export { uipoliciesCmd } from './uipolicies.js';
|
|
125
|
+
export const cataloguipoliciesCmd = (wrap) => buildDevCmd('cataloguipolicies', 'catalog_ui_policy', ['cataloguipolicy', 'cup'], ['short_description', 'catalog_item', 'variable_set', 'active', 'sys_scope'], wrap, { singular: 'catalog UI policy', scopeValidation: true });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
import { getStringField } from '../../helpers.js';
|
|
3
|
+
|
|
4
|
+
export const aliasesCmd = (wrap) => buildDevCmd('aliases', 'sys_alias', ['alias', 'als'],
|
|
5
|
+
['name', 'table', 'sys_scope'], wrap, {
|
|
6
|
+
singular: 'alias',
|
|
7
|
+
scopeValidation: true,
|
|
8
|
+
onShow: async (record, app) => {
|
|
9
|
+
const sysID = getStringField(record, 'sys_id') || '';
|
|
10
|
+
if (!sysID) return;
|
|
11
|
+
|
|
12
|
+
const mp = new URLSearchParams();
|
|
13
|
+
mp.set('sysparm_query', `connection_alias=${sysID}^ORDERBYname`);
|
|
14
|
+
mp.set('sysparm_limit', '100');
|
|
15
|
+
mp.set('sysparm_display_value', 'all');
|
|
16
|
+
mp.set('sysparm_fields', 'name,active,credential,sys_id');
|
|
17
|
+
const conns = await app.sdk.list('sys_connection', mp);
|
|
18
|
+
|
|
19
|
+
if (conns.length > 0) {
|
|
20
|
+
console.log(` Connections (${conns.length}):`);
|
|
21
|
+
for (const c of conns) {
|
|
22
|
+
const name = getStringField(c, 'name') || '';
|
|
23
|
+
const cred = getStringField(c, 'credential') || '';
|
|
24
|
+
const cid = getStringField(c, 'sys_id') || '';
|
|
25
|
+
console.log(` ${name}${cred ? ` → ${cred}` : ''}`);
|
|
26
|
+
console.log(` → jsn records get --table sys_connection --sys-id ${cid}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -2,4 +2,4 @@ import { buildDevCmd } from './_generic.js';
|
|
|
2
2
|
|
|
3
3
|
// Before-query business rules — applies filters to GlideRecord queries before they execute.
|
|
4
4
|
// These are sys_script records with order < 100.
|
|
5
|
-
export const b4rulesCmd = (wrap) => buildDevCmd('b4rules', 'sys_script', ['b4rule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'before-query business rule', scopeValidation: true, extraQuery: '
|
|
5
|
+
export const b4rulesCmd = (wrap) => buildDevCmd('b4rules', 'sys_script', ['b4rule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'before-query business rule', scopeValidation: true, extraQuery: 'when=before' });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
|
|
3
|
+
export const columnsCmd = (wrap) => {
|
|
4
|
+
const base = buildDevCmd('columns', 'sys_dictionary', ['column', 'col'],
|
|
5
|
+
['element', 'column_label', 'internal_type', 'mandatory', 'max_length', 'active'],
|
|
6
|
+
wrap, { singular: 'column', scopeValidation: true });
|
|
7
|
+
|
|
8
|
+
// Add --table filter
|
|
9
|
+
const origBuilder = base.builder;
|
|
10
|
+
base.builder = (yargs) => {
|
|
11
|
+
const built = origBuilder(yargs);
|
|
12
|
+
// Patch the 'list' subcommand builder to add --table
|
|
13
|
+
const listCmd = built._commands?.find(c => c.name === 'list' || c._aliases?.includes('ls'));
|
|
14
|
+
if (listCmd) {
|
|
15
|
+
const origListBuilder = listCmd.builder;
|
|
16
|
+
listCmd.builder = (y) => (origListBuilder ? origListBuilder(y).option('table', { type: 'string', describe: 'Filter by table (e.g. incident)' }) : y.option('table', { type: 'string' }));
|
|
17
|
+
const origHandler = listCmd.handler;
|
|
18
|
+
listCmd.handler = async (argv) => {
|
|
19
|
+
if (argv.table) argv.query = argv.query ? `${argv.query}^name=${argv.table}` : `name=${argv.table}`;
|
|
20
|
+
return origHandler(argv);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return built;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return base;
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
import { getStringField } from '../../helpers.js';
|
|
3
|
+
|
|
4
|
+
export const importCmd = (wrap) => buildDevCmd('import', 'sys_import_set', ['imports', 'imp'],
|
|
5
|
+
['name', 'sys_target_table', 'state', 'sys_created_on'], wrap, {
|
|
6
|
+
singular: 'import set',
|
|
7
|
+
readOnly: true,
|
|
8
|
+
formatLabel: (r) => {
|
|
9
|
+
const name = getStringField(r, 'name') || '';
|
|
10
|
+
const table = getStringField(r, 'sys_target_table') || '';
|
|
11
|
+
return table ? `${name} [${table}]` : name;
|
|
12
|
+
},
|
|
13
|
+
onShow: async (record, app) => {
|
|
14
|
+
const sysID = getStringField(record, 'sys_id') || '';
|
|
15
|
+
if (!sysID) return;
|
|
16
|
+
try {
|
|
17
|
+
const rp = new URLSearchParams();
|
|
18
|
+
rp.set('sysparm_query', `sys_import_set=${sysID}`);
|
|
19
|
+
rp.set('sysparm_limit', '0'); rp.set('sysparm_fields', 'sys_id');
|
|
20
|
+
const rows = await app.sdk.list('sys_import_set_row', rp);
|
|
21
|
+
console.log(` Import rows: ${rows.length}`);
|
|
22
|
+
console.log(` → jsn records list --table sys_import_set_row --query "sys_import_set=${sysID}" --limit 200`);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.log(` Import rows: (unavailable)`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -1,99 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getStringField } from '../../helpers.js';
|
|
3
|
-
|
|
4
|
-
// ─── Helpers ───
|
|
5
|
-
|
|
6
|
-
function getDisplayValue(record, key) {
|
|
7
|
-
if (!record || typeof record !== 'object') return '';
|
|
8
|
-
const val = record[key];
|
|
9
|
-
if (val == null) return '';
|
|
10
|
-
if (typeof val === 'string') return val;
|
|
11
|
-
if (typeof val === 'object') {
|
|
12
|
-
if (val.display_value != null && val.display_value !== '') return String(val.display_value);
|
|
13
|
-
if (val.value != null) return String(val.value);
|
|
14
|
-
}
|
|
15
|
-
return String(val);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getIntValue(record, key) {
|
|
19
|
-
if (!record || typeof record !== 'object') return 0;
|
|
20
|
-
const val = record[key];
|
|
21
|
-
if (val == null) return 0;
|
|
22
|
-
if (typeof val === 'number') return Math.floor(val);
|
|
23
|
-
if (typeof val === 'string') {
|
|
24
|
-
const n = parseInt(val, 10);
|
|
25
|
-
return isNaN(n) ? 0 : n;
|
|
26
|
-
}
|
|
27
|
-
if (typeof val === 'object' && val.value != null) {
|
|
28
|
-
const n = parseInt(val.value, 10);
|
|
29
|
-
return isNaN(n) ? 0 : n;
|
|
30
|
-
}
|
|
31
|
-
return 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ─── Commands ───
|
|
1
|
+
import { getStringField, interactiveList } from '../../helpers.js';
|
|
35
2
|
|
|
36
3
|
export function listsCmd(wrap) {
|
|
37
4
|
return {
|
|
38
5
|
command: 'lists [subcommand]',
|
|
39
6
|
aliases: ['list-layout', 'l'],
|
|
40
|
-
describe: '
|
|
7
|
+
describe: 'List views for a table',
|
|
41
8
|
builder: (yargs) => {
|
|
42
9
|
return yargs
|
|
43
10
|
.command({
|
|
44
|
-
command: 'list
|
|
11
|
+
command: 'list [table]',
|
|
45
12
|
aliases: ['ls'],
|
|
46
|
-
describe: 'List
|
|
13
|
+
describe: 'List views for a table (or pick from all tables)',
|
|
47
14
|
builder: (y) => y
|
|
48
|
-
.positional('table', {
|
|
49
|
-
|
|
50
|
-
type: 'string',
|
|
51
|
-
})
|
|
52
|
-
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
15
|
+
.positional('table', { type: 'string', describe: 'Table name (e.g. incident)' })
|
|
16
|
+
.option('limit', { alias: 'l', type: 'number', default: 50 }),
|
|
53
17
|
handler: wrap(async (argv, app) => {
|
|
54
18
|
app.requireInstance();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for (const record of records) {
|
|
70
|
-
const view = getDisplayValue(record, 'view');
|
|
71
|
-
if (view && !viewMap.has(view)) {
|
|
72
|
-
viewMap.set(view, true);
|
|
73
|
-
}
|
|
19
|
+
let table = argv.table;
|
|
20
|
+
let viewSysID = null;
|
|
21
|
+
|
|
22
|
+
// If no table, pick one interactively
|
|
23
|
+
if (!table) {
|
|
24
|
+
const tpicked = await interactiveList({
|
|
25
|
+
app, table: 'sys_ui_list', singular: 'table',
|
|
26
|
+
columns: ['name'],
|
|
27
|
+
limit: argv.limit,
|
|
28
|
+
labelField: 'name',
|
|
29
|
+
groupBy: 'name',
|
|
30
|
+
});
|
|
31
|
+
if (!tpicked) return;
|
|
32
|
+
table = getStringField(tpicked, 'name');
|
|
74
33
|
}
|
|
75
34
|
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
35
|
+
// Show list views for table
|
|
36
|
+
const picked = await interactiveList({
|
|
37
|
+
app, table: 'sys_ui_list', singular: 'list',
|
|
38
|
+
columns: ['name', 'view'],
|
|
39
|
+
limit: argv.limit,
|
|
40
|
+
query: table ? `name=${table}` : '',
|
|
41
|
+
labelField: 'view',
|
|
42
|
+
formatLabel: (r) => `${getStringField(r, 'view')}`,
|
|
43
|
+
});
|
|
80
44
|
|
|
81
|
-
|
|
45
|
+
if (picked) {
|
|
46
|
+
const sysID = getStringField(picked, 'sys_id') || '';
|
|
47
|
+
viewSysID = getStringField(picked, 'view') || '';
|
|
48
|
+
await showList(app, table, viewSysID, sysID);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
82
51
|
|
|
52
|
+
// Fallback non-TTY
|
|
53
|
+
const p = new URLSearchParams();
|
|
54
|
+
p.set('sysparm_limit', String(argv.limit));
|
|
55
|
+
p.set('sysparm_display_value', 'all');
|
|
56
|
+
p.set('sysparm_fields', 'name,view');
|
|
57
|
+
p.set('sysparm_query', table ? `name=${table}^ORDERBYview` : 'ORDERBYview');
|
|
58
|
+
const records = await app.sdk.list('sys_ui_list', p);
|
|
83
59
|
app.ok({
|
|
84
|
-
table,
|
|
85
|
-
count:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
instance_url: app.getEffectiveInstance(),
|
|
91
|
-
}, {
|
|
92
|
-
summary: `${views.length} list views for ${table}`,
|
|
93
|
-
breadcrumbs: [
|
|
94
|
-
{ action: 'show', cmd: `jsn lists show ${table} --view "Default view"`, description: 'Show Default view columns' },
|
|
95
|
-
],
|
|
96
|
-
});
|
|
60
|
+
table: 'sys_ui_list',
|
|
61
|
+
count: records.length,
|
|
62
|
+
columns: ['name', 'view'],
|
|
63
|
+
records: records.map(r => ({ name: getStringField(r, 'name'), view: getStringField(r, 'view') })),
|
|
64
|
+
context: { instance_url: app.getEffectiveInstance() },
|
|
65
|
+
}, { summary: `${records.length} list view(s)${table ? ` for ${table}` : ''}` });
|
|
97
66
|
}),
|
|
98
67
|
})
|
|
99
68
|
.command({
|
|
@@ -101,147 +70,61 @@ export function listsCmd(wrap) {
|
|
|
101
70
|
aliases: ['get'],
|
|
102
71
|
describe: 'Show list layout',
|
|
103
72
|
builder: (y) => y
|
|
104
|
-
.option('view', { type: 'string', default: 'Default view'
|
|
73
|
+
.option('view', { type: 'string', default: 'Default view' }),
|
|
105
74
|
handler: wrap(async (argv, app) => {
|
|
106
75
|
app.requireInstance();
|
|
107
76
|
const table = argv.table;
|
|
108
|
-
const viewName = argv.view;
|
|
109
77
|
|
|
110
|
-
//
|
|
78
|
+
// Resolve view sys_id
|
|
111
79
|
let viewSysID = '';
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
viewParams.set('sysparm_query', `title=${viewName}`);
|
|
127
|
-
try {
|
|
128
|
-
const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
|
|
129
|
-
if (viewRecords.length > 0) {
|
|
130
|
-
viewSysID = getStringField(viewRecords[0], 'sys_id');
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
// ignore
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!viewSysID) {
|
|
138
|
-
viewSysID = viewName;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Fetch list layouts
|
|
142
|
-
const params = new URLSearchParams();
|
|
143
|
-
params.set('sysparm_limit', '10');
|
|
144
|
-
params.set('sysparm_fields', 'sys_id,name,view,parent,active,sys_created_on,sys_updated_on');
|
|
145
|
-
params.set('sysparm_display_value', 'all');
|
|
146
|
-
|
|
147
|
-
const parts = [];
|
|
148
|
-
if (table) parts.push(`name=${table}`);
|
|
149
|
-
if (viewSysID) parts.push(`view=${viewSysID}`);
|
|
150
|
-
const sysparmQuery = parts.length > 0 ? `${parts.join('^')}^ORDERBYsys_created_on` : 'ORDERBYsys_created_on';
|
|
151
|
-
params.set('sysparm_query', sysparmQuery);
|
|
152
|
-
|
|
153
|
-
const records = await app.sdk.list('sys_ui_list', params);
|
|
154
|
-
if (records.length === 0) {
|
|
155
|
-
throw new Error(`no list layout found for ${table} with view "${viewName}"`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const mainLayout = records[0];
|
|
159
|
-
const layoutSysID = getStringField(mainLayout, 'sys_id');
|
|
160
|
-
|
|
161
|
-
// Fetch elements (columns) for the list
|
|
162
|
-
const elemParams = new URLSearchParams();
|
|
163
|
-
elemParams.set('sysparm_limit', '100');
|
|
164
|
-
elemParams.set('sysparm_fields', 'sys_id,list_id,element,position,type');
|
|
165
|
-
elemParams.set('sysparm_display_value', 'all');
|
|
166
|
-
elemParams.set('sysparm_query', `list_id=${layoutSysID}^ORDERBYposition`);
|
|
167
|
-
|
|
168
|
-
const elemRecords = await app.sdk.list('sys_ui_list_element', elemParams);
|
|
169
|
-
|
|
170
|
-
const elements = elemRecords.map(record => ({
|
|
171
|
-
sys_id: getStringField(record, 'sys_id'),
|
|
172
|
-
listID: getStringField(record, 'list_id'),
|
|
173
|
-
element: getDisplayValue(record, 'element'),
|
|
174
|
-
position: getIntValue(record, 'position'),
|
|
175
|
-
type: getDisplayValue(record, 'type'),
|
|
176
|
-
}));
|
|
177
|
-
|
|
178
|
-
elements.sort((a, b) => a.position - b.position);
|
|
179
|
-
|
|
180
|
-
// Build formatted output
|
|
181
|
-
const lines = [];
|
|
182
|
-
lines.push('');
|
|
183
|
-
lines.push(chalk.bold(chalk.hex('#e8a217')(`${table} (${viewName})`)));
|
|
184
|
-
lines.push('');
|
|
185
|
-
|
|
186
|
-
lines.push(chalk.bold(chalk.hex('#666666')('─ List Columns ─')));
|
|
187
|
-
if (elements.length === 0) {
|
|
188
|
-
lines.push(chalk.hex('#888888')(' (no columns defined)'));
|
|
189
|
-
} else {
|
|
190
|
-
for (let i = 0; i < elements.length; i++) {
|
|
191
|
-
const elem = elements[i];
|
|
192
|
-
lines.push(` ${chalk.hex('#666666')(`${String(i + 1).padStart(2)}.`)} ${chalk.hex('#cccccc')(elem.element)}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
lines.push('');
|
|
196
|
-
|
|
197
|
-
lines.push('─────');
|
|
198
|
-
lines.push('');
|
|
199
|
-
lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
|
|
200
|
-
lines.push(` ${`jsn lists list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
|
|
201
|
-
lines.push(` ${`jsn forms show ${table} --view "${viewName}"`.padEnd(50)} ${chalk.hex('#888888')('Show form layout')}`);
|
|
202
|
-
lines.push('');
|
|
203
|
-
|
|
204
|
-
const formatted = lines.join('\n');
|
|
205
|
-
|
|
206
|
-
// Build structured data for JSON/quiet mode
|
|
207
|
-
const columnsData = elements.map(elem => {
|
|
208
|
-
const col = {
|
|
209
|
-
element: elem.element,
|
|
210
|
-
position: elem.position,
|
|
211
|
-
};
|
|
212
|
-
if (elem.type) {
|
|
213
|
-
col.type = elem.type;
|
|
214
|
-
}
|
|
215
|
-
return col;
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
app.ok({
|
|
219
|
-
table,
|
|
220
|
-
view: viewName,
|
|
221
|
-
columns: columnsData,
|
|
222
|
-
_formatted: formatted,
|
|
223
|
-
_context: {
|
|
224
|
-
instance_url: app.getEffectiveInstance(),
|
|
225
|
-
table: 'sys_ui_list',
|
|
226
|
-
},
|
|
227
|
-
}, {
|
|
228
|
-
summary: `List: ${table} (${viewName}) - ${elements.length} columns`,
|
|
229
|
-
breadcrumbs: [
|
|
230
|
-
{ action: 'list', cmd: `jsn lists list ${table}`, description: 'List all views' },
|
|
231
|
-
{ action: 'form', cmd: `jsn forms show ${table} --view "${viewName}"`, description: 'Show form layout' },
|
|
232
|
-
],
|
|
233
|
-
});
|
|
80
|
+
const vp = new URLSearchParams();
|
|
81
|
+
vp.set('sysparm_limit', '1'); vp.set('sysparm_fields', 'sys_id');
|
|
82
|
+
vp.set('sysparm_query', `name=${argv.view}^name=${table}`);
|
|
83
|
+
const vr = await app.sdk.list('sys_ui_view', vp);
|
|
84
|
+
viewSysID = vr.length > 0 ? getStringField(vr[0], 'sys_id') : '';
|
|
85
|
+
|
|
86
|
+
// Find list layout
|
|
87
|
+
const lp = new URLSearchParams();
|
|
88
|
+
lp.set('sysparm_limit', '1'); lp.set('sysparm_display_value', 'all');
|
|
89
|
+
lp.set('sysparm_fields', 'sys_id,name,view');
|
|
90
|
+
lp.set('sysparm_query', `name=${table}^view=${viewSysID || argv.view}`);
|
|
91
|
+
const layouts = await app.sdk.list('sys_ui_list', lp);
|
|
92
|
+
if (layouts.length === 0) throw new Error(`No list layout for ${table} view "${argv.view}"`);
|
|
93
|
+
await showList(app, table, getStringField(layouts[0], 'view') || argv.view, getStringField(layouts[0], 'sys_id'));
|
|
234
94
|
}),
|
|
235
95
|
});
|
|
236
96
|
},
|
|
237
97
|
handler: () => {
|
|
238
|
-
console.log('Manage list layouts
|
|
239
|
-
console.log('');
|
|
240
|
-
console.log('Available subcommands:');
|
|
241
|
-
console.log(' list <table> List list views for a table');
|
|
242
|
-
console.log(' show <table> Show list layout for a table');
|
|
98
|
+
console.log('Manage list layouts.');
|
|
243
99
|
console.log('');
|
|
244
|
-
console.log('
|
|
100
|
+
console.log('Commands:');
|
|
101
|
+
console.log(' list [table] List views (pick table if omitted)');
|
|
102
|
+
console.log(' show <table> Show list layout columns');
|
|
245
103
|
},
|
|
246
104
|
};
|
|
247
105
|
}
|
|
106
|
+
|
|
107
|
+
async function showList(app, table, viewName, layoutSysID) {
|
|
108
|
+
const ep = new URLSearchParams();
|
|
109
|
+
ep.set('sysparm_limit', '100'); ep.set('sysparm_display_value', 'all');
|
|
110
|
+
ep.set('sysparm_fields', 'element,position,type');
|
|
111
|
+
ep.set('sysparm_query', `list_id=${layoutSysID}^ORDERBYposition`);
|
|
112
|
+
const elements = await app.sdk.list('sys_ui_list_element', ep);
|
|
113
|
+
|
|
114
|
+
elements.sort((a, b) => {
|
|
115
|
+
const pa = parseInt(getStringField(a, 'position') || '0', 10) || 0;
|
|
116
|
+
const pb = parseInt(getStringField(b, 'position') || '0', 10) || 0;
|
|
117
|
+
return pa - pb;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
app.ok({
|
|
121
|
+
table,
|
|
122
|
+
view: viewName,
|
|
123
|
+
columns: elements.map(e => ({
|
|
124
|
+
element: getStringField(e, 'element'),
|
|
125
|
+
position: parseInt(getStringField(e, 'position') || '0', 10) || 0,
|
|
126
|
+
type: getStringField(e, 'type') || undefined,
|
|
127
|
+
})),
|
|
128
|
+
_context: { instance_url: app.getEffectiveInstance(), table: 'sys_ui_list' },
|
|
129
|
+
}, { summary: `${table} (${viewName}) — ${elements.length} columns` });
|
|
130
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
import { getStringField } from '../../helpers.js';
|
|
3
|
+
|
|
4
|
+
export const restmessageCmd = (wrap) => buildDevCmd('restmessage', 'sys_rest_message', ['rm', 'restmsg'],
|
|
5
|
+
['name', 'endpoint', 'active', 'sys_scope'], wrap, {
|
|
6
|
+
singular: 'REST message',
|
|
7
|
+
scopeValidation: true,
|
|
8
|
+
onShow: async (record, app) => {
|
|
9
|
+
const sysID = getStringField(record, 'sys_id') || '';
|
|
10
|
+
if (!sysID) return;
|
|
11
|
+
|
|
12
|
+
// Fetch related methods
|
|
13
|
+
const mp = new URLSearchParams();
|
|
14
|
+
mp.set('sysparm_query', `rest_message=${sysID}^ORDERBYorder`);
|
|
15
|
+
mp.set('sysparm_limit', '100');
|
|
16
|
+
mp.set('sysparm_display_value', 'all');
|
|
17
|
+
mp.set('sysparm_fields', 'name,http_method,rest_endpoint,order,sys_id');
|
|
18
|
+
const methods = await app.sdk.list('sys_rest_message_fn', mp);
|
|
19
|
+
|
|
20
|
+
if (methods.length > 0) {
|
|
21
|
+
console.log(` Methods (${methods.length}):`);
|
|
22
|
+
for (const m of methods) {
|
|
23
|
+
const name = getStringField(m, 'name') || getStringField(m, 'http_method') || '';
|
|
24
|
+
const method = getStringField(m, 'http_method') || '';
|
|
25
|
+
const path = getStringField(m, 'rest_endpoint') || '';
|
|
26
|
+
const mid = getStringField(m, 'sys_id') || '';
|
|
27
|
+
console.log(` ${method} ${path} (${name})`);
|
|
28
|
+
console.log(` → jsn records get --table sys_rest_message_fn --sys-id ${mid}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
import { getStringField } from '../../helpers.js';
|
|
3
|
+
|
|
4
|
+
export const soapmessagesCmd = (wrap) => buildDevCmd('soapmessages', 'sys_soap_message', ['soapmsg'],
|
|
5
|
+
['name', 'active', 'sys_scope'], wrap, {
|
|
6
|
+
singular: 'SOAP message',
|
|
7
|
+
scopeValidation: true,
|
|
8
|
+
onShow: async (record, app) => {
|
|
9
|
+
const sysID = getStringField(record, 'sys_id') || '';
|
|
10
|
+
if (!sysID) return;
|
|
11
|
+
|
|
12
|
+
// Fetch related functions
|
|
13
|
+
const mp = new URLSearchParams();
|
|
14
|
+
mp.set('sysparm_query', `soap_message=${sysID}^ORDERBYorder`);
|
|
15
|
+
mp.set('sysparm_limit', '100');
|
|
16
|
+
mp.set('sysparm_display_value', 'all');
|
|
17
|
+
mp.set('sysparm_fields', 'name,order,sys_id');
|
|
18
|
+
const funcs = await app.sdk.list('sys_soap_message_function', mp);
|
|
19
|
+
|
|
20
|
+
if (funcs.length > 0) {
|
|
21
|
+
console.log(` Functions (${funcs.length}):`);
|
|
22
|
+
for (const f of funcs) {
|
|
23
|
+
const name = getStringField(f, 'name') || '';
|
|
24
|
+
const fid = getStringField(f, 'sys_id') || '';
|
|
25
|
+
console.log(` ${name}`);
|
|
26
|
+
console.log(` → jsn records get --table sys_soap_message_function --sys-id ${fid}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildDevCmd } from './_generic.js';
|
|
2
|
+
import { getStringField } from '../../helpers.js';
|
|
3
|
+
|
|
4
|
+
export const uipoliciesCmd = (wrap) => buildDevCmd('uipolicies', 'sys_ui_policy', ['uipolicy', 'up'],
|
|
5
|
+
['short_description', 'table', 'sys_class_name', 'active'], wrap, {
|
|
6
|
+
singular: 'UI policy',
|
|
7
|
+
scopeValidation: true,
|
|
8
|
+
onShow: async (record, app) => {
|
|
9
|
+
const sysID = getStringField(record, 'sys_id') || '';
|
|
10
|
+
if (!sysID) return;
|
|
11
|
+
const mp = new URLSearchParams();
|
|
12
|
+
mp.set('sysparm_query', `ui_policy=${sysID}^ORDERBYorder`);
|
|
13
|
+
mp.set('sysparm_limit', '100'); mp.set('sysparm_display_value', 'all');
|
|
14
|
+
mp.set('sysparm_fields', 'name,table,field_name,read_only,mandatory,visible,sys_id');
|
|
15
|
+
const actions = await app.sdk.list('sys_ui_policy_action', mp);
|
|
16
|
+
if (actions.length > 0) {
|
|
17
|
+
console.log(` Actions (${actions.length}):`);
|
|
18
|
+
for (const a of actions) {
|
|
19
|
+
const f = getStringField(a, 'field_name') || '(all fields)';
|
|
20
|
+
const flags = [];
|
|
21
|
+
if (getStringField(a, 'mandatory') === 'true') flags.push('mandatory');
|
|
22
|
+
if (getStringField(a, 'read_only') === 'true') flags.push('read-only');
|
|
23
|
+
if (getStringField(a, 'visible') === 'false') flags.push('hidden');
|
|
24
|
+
console.log(` ${f} ${flags.length ? `[${flags.join(', ')}]` : ''}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
package/src/commands/dev.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
spPagesCmd, spWidgetsCmd, uiPagesCmd, appMenuCmd,
|
|
8
8
|
aclsCmd, rolesCmd, propertiesCmd,
|
|
9
9
|
relationshipsCmd, appmodulesCmd, listcontrolsCmd, viewsCmd,
|
|
10
|
-
privilegesCmd,
|
|
10
|
+
privilegesCmd, uxscriptsCmd, aliasesCmd,
|
|
11
11
|
catalogscriptsCmd, cataloguipoliciesCmd,
|
|
12
12
|
} from './dev/_simple.js';
|
|
13
13
|
import { flowsCmd } from './dev/flows.js';
|
|
@@ -53,7 +53,6 @@ export function devCmd(wrap) {
|
|
|
53
53
|
.command(listcontrolsCmd(wrap))
|
|
54
54
|
.command(viewsCmd(wrap))
|
|
55
55
|
.command(privilegesCmd(wrap))
|
|
56
|
-
.command(securitytypesCmd(wrap))
|
|
57
56
|
.command(uxscriptsCmd(wrap))
|
|
58
57
|
.command(aliasesCmd(wrap))
|
|
59
58
|
.command(catalogscriptsCmd(wrap))
|
package/src/commands/groups.js
CHANGED
|
@@ -24,8 +24,30 @@ export function groupsCmd(wrap) {
|
|
|
24
24
|
formatLabel: r => `${getStringField(r, 'name')} ${getStringField(r, 'manager') ? '→ ' + getStringField(r, 'manager') : ''}`,
|
|
25
25
|
});
|
|
26
26
|
if (picked) {
|
|
27
|
+
const sysID = getStringField(picked, 'sys_id') || '';
|
|
28
|
+
// Fetch member + role counts for hints
|
|
29
|
+
let memberCount = 0, roleCount = 0;
|
|
30
|
+
try {
|
|
31
|
+
const cp = new URLSearchParams();
|
|
32
|
+
cp.set('sysparm_query', `group=${sysID}`); cp.set('sysparm_limit', '0'); cp.set('sysparm_fields', 'sys_id');
|
|
33
|
+
const members = await app.sdk.list('sys_user_grmember', cp);
|
|
34
|
+
memberCount = members.length;
|
|
35
|
+
} catch {}
|
|
36
|
+
try {
|
|
37
|
+
const rp = new URLSearchParams();
|
|
38
|
+
rp.set('sysparm_query', `group=${sysID}`); rp.set('sysparm_limit', '0'); rp.set('sysparm_fields', 'sys_id');
|
|
39
|
+
const roles = await app.sdk.list('sys_group_has_role', rp);
|
|
40
|
+
roleCount = roles.length;
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
27
43
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'sys_user_group' };
|
|
28
|
-
return app.ok(picked, {
|
|
44
|
+
return app.ok(picked, {
|
|
45
|
+
summary: `Group: ${getStringField(picked, 'name')}`,
|
|
46
|
+
breadcrumbs: [
|
|
47
|
+
memberCount > 0 ? { action: 'members', cmd: `jsn records list --table sys_user_grmember --query "group=${sysID}"`, description: `${memberCount} member(s)` } : null,
|
|
48
|
+
roleCount > 0 ? { action: 'roles', cmd: `jsn records list --table sys_group_has_role --query "group=${sysID}"`, description: `${roleCount} role(s)` } : null,
|
|
49
|
+
].filter(Boolean),
|
|
50
|
+
});
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
const params = new URLSearchParams();
|
package/src/commands/users.js
CHANGED
|
@@ -24,8 +24,15 @@ export function usersCmd(wrap) {
|
|
|
24
24
|
formatLabel: r => `${getStringField(r, 'user_name')} (${getStringField(r, 'name') || '-'})`,
|
|
25
25
|
});
|
|
26
26
|
if (picked) {
|
|
27
|
+
const sysID = getStringField(picked, 'sys_id') || '';
|
|
27
28
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'sys_user' };
|
|
28
|
-
return app.ok(picked, {
|
|
29
|
+
return app.ok(picked, {
|
|
30
|
+
summary: `User: ${getStringField(picked, 'user_name')}`,
|
|
31
|
+
breadcrumbs: [
|
|
32
|
+
{ action: 'preferences', cmd: `jsn records list --table sys_user_preference --query "user=${sysID}"`, description: 'User preferences' },
|
|
33
|
+
{ action: 'groups', cmd: `jsn records list --table sys_user_grmember --query "user=${sysID}"`, description: 'Group memberships' },
|
|
34
|
+
],
|
|
35
|
+
});
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
const params = new URLSearchParams();
|
package/src/help.js
CHANGED
|
@@ -36,7 +36,6 @@ const AUTOMATION_COMMANDS = [
|
|
|
36
36
|
|
|
37
37
|
{ divider: '── In Memory ──' },
|
|
38
38
|
{ name: 'rules', alias: 'br', desc: 'Business rules (create, update, delete)' },
|
|
39
|
-
{ name: 'workflows', desc: 'Workflow definitions' },
|
|
40
39
|
{ name: 'decisiontables', desc: 'Decision tables' },
|
|
41
40
|
{ name: 'assignments', desc: 'Assignment rules' },
|
|
42
41
|
|
|
@@ -46,9 +45,7 @@ const AUTOMATION_COMMANDS = [
|
|
|
46
45
|
|
|
47
46
|
{ divider: '── Outbound ──' },
|
|
48
47
|
{ name: 'restmessage', alias: 'rm', desc: 'REST Messages (sys_rest_message)' },
|
|
49
|
-
{ name: 'restmethods', desc: 'REST Message methods (sys_rest_message_fn)' },
|
|
50
48
|
{ name: 'soapmessages', desc: 'SOAP Messages' },
|
|
51
|
-
{ name: 'soapfunctions', desc: 'SOAP Message functions' },
|
|
52
49
|
];
|
|
53
50
|
|
|
54
51
|
const ACCESS_COMMANDS = [
|
|
@@ -56,7 +53,6 @@ const ACCESS_COMMANDS = [
|
|
|
56
53
|
{ name: 'b4rules', alias: 'b4r', desc: 'Before-query business rules (see also Automation)' },
|
|
57
54
|
{ name: 'roles', alias: 'role', desc: 'Roles' },
|
|
58
55
|
{ name: 'privileges', alias: 'priv', desc: 'Scope privileges' },
|
|
59
|
-
{ name: 'securitytypes', alias: 'st', desc: 'Custom security types' },
|
|
60
56
|
{ name: 'aliases', alias: 'als', desc: 'Connection & credential aliases' },
|
|
61
57
|
{ name: 'groups', alias: 'group', desc: 'Manage groups' },
|
|
62
58
|
{ name: 'groupmembers', alias: 'gm', desc: 'Group memberships' },
|
package/src/output.js
CHANGED
|
@@ -281,7 +281,7 @@ export class OutputWriter {
|
|
|
281
281
|
const width = colWidths[col] || 20;
|
|
282
282
|
|
|
283
283
|
if (j === 0 && instanceURL && table && row.sys_id) {
|
|
284
|
-
const url =
|
|
284
|
+
const url = getTableURL(instanceURL, table, row.sys_id);
|
|
285
285
|
val = hyperlink(val, url);
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -291,7 +291,7 @@ export class OutputWriter {
|
|
|
291
291
|
display = visible.slice(0, width - 3) + '...';
|
|
292
292
|
if (val !== visible) {
|
|
293
293
|
if (j === 0 && instanceURL && table && row.sys_id) {
|
|
294
|
-
const url =
|
|
294
|
+
const url = getTableURL(instanceURL, table, row.sys_id);
|
|
295
295
|
display = hyperlink(display, url);
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -417,7 +417,7 @@ export class OutputWriter {
|
|
|
417
417
|
const sysID = getDisplayValue(data.sys_id);
|
|
418
418
|
if (sysID) {
|
|
419
419
|
const urlTable = table.toLowerCase().replace(/\s+/g, '_');
|
|
420
|
-
const recordURL =
|
|
420
|
+
const recordURL = getTableURL(instanceURL, urlTable, sysID);
|
|
421
421
|
this.writer.write(`Link: ${recordURL}\n\n`);
|
|
422
422
|
}
|
|
423
423
|
}
|
|
@@ -453,6 +453,21 @@ function detectRecord(data) {
|
|
|
453
453
|
return { isRecord: hasSysID && (hasSysClass || tableName), tableName };
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
export function getTableURL(instance, table, sysID) {
|
|
457
|
+
const mapped = {
|
|
458
|
+
sys_decision: `now/workflow-studio/builder?table=sys_decision&sysId=${sysID}`,
|
|
459
|
+
sys_rest_message: `nav_to.do?uri=sys_rest_message.do?sys_id=${sysID}`,
|
|
460
|
+
sysevent_email_action: `nav_to.do?uri=sysevent_email_action.do?sys_id=${sysID}`,
|
|
461
|
+
sysevent_in_email_action: `nav_to.do?uri=sysevent_in_email_action.do?sys_id=${sysID}`,
|
|
462
|
+
soap_message: `nav_to.do?uri=soap_message.do?sys_id=${sysID}`,
|
|
463
|
+
sys_security_acl: `nav_to.do?uri=sys_security_acl.do?sys_id=${sysID}`,
|
|
464
|
+
sys_user_role: `nav_to.do?uri=sys_user_role.do?sys_id=${sysID}`,
|
|
465
|
+
sys_db_object: `nav_to.do?uri=sys_db_object.do?sys_id=${sysID}`,
|
|
466
|
+
};
|
|
467
|
+
const path = mapped[table] || `${table}.do?sys_id=${sysID}`;
|
|
468
|
+
return `${instance}/${path}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
456
471
|
export function getDisplayValue(val) {
|
|
457
472
|
if (val == null) return '';
|
|
458
473
|
if (typeof val === 'string') return val;
|
package/src/paginated-search.js
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { createPrompt, useState, useKeypress, usePagination, useEffect, useRef } from '@inquirer/core';
|
|
6
6
|
import { isEnterKey, isUpKey, isDownKey } from '@inquirer/core';
|
|
7
|
-
import { appendFileSync } from 'fs';
|
|
8
7
|
|
|
9
8
|
export const paginatedSearch = createPrompt((config, done) => {
|
|
10
9
|
const { message, pageSize = 10, totalCount = 0, source } = config;
|
|
11
10
|
|
|
12
|
-
// ── DEBUG ──
|
|
13
|
-
const DBG = '/home/jace/workspace/holly/sn-jsn-fork/debug.log';
|
|
14
|
-
const log = (msg) => appendFileSync(DBG, `${new Date().toISOString()} ${msg}\n`);
|
|
15
|
-
log(`START totalCount=${totalCount}`);
|
|
16
|
-
|
|
17
11
|
const [status, setStatus] = useState('loading');
|
|
18
12
|
const [searchTerm, setSearchTerm] = useState('');
|
|
19
13
|
const [choices, setChoices] = useState([]);
|
|
@@ -36,12 +30,10 @@ export const paginatedSearch = createPrompt((config, done) => {
|
|
|
36
30
|
useEffect(() => {
|
|
37
31
|
const controller = new AbortController();
|
|
38
32
|
setStatus('loading');
|
|
39
|
-
log(`EFFECT-INIT searchTerm="${searchTerm}"`);
|
|
40
33
|
|
|
41
34
|
const load = async () => {
|
|
42
35
|
try {
|
|
43
36
|
const items = await source(searchTerm || undefined, 0, { signal: controller.signal });
|
|
44
|
-
log(`INIT-LOAD got ${items.length} items`);
|
|
45
37
|
if (!controller.signal.aborted) {
|
|
46
38
|
choicesRef.current = items;
|
|
47
39
|
loadedRef.current = items.length;
|
|
@@ -52,7 +44,6 @@ export const paginatedSearch = createPrompt((config, done) => {
|
|
|
52
44
|
setStatus('done');
|
|
53
45
|
}
|
|
54
46
|
} catch (err) {
|
|
55
|
-
log(`INIT-LOAD error: ${err.message}`);
|
|
56
47
|
if (!controller.signal.aborted) {
|
|
57
48
|
setStatus('done');
|
|
58
49
|
setChoices([]);
|
|
@@ -66,16 +57,10 @@ export const paginatedSearch = createPrompt((config, done) => {
|
|
|
66
57
|
|
|
67
58
|
// Load more when scrolling past end
|
|
68
59
|
async function loadMore() {
|
|
69
|
-
|
|
70
|
-
if (loadedRef.current >= totalCountRef.current || searchModeRef.current) {
|
|
71
|
-
log(`loadMore SKIPPED`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
60
|
+
if (loadedRef.current >= totalCountRef.current || searchModeRef.current) return;
|
|
74
61
|
setStatus('loading');
|
|
75
|
-
log(`loadMore calling source offset=${loadedRef.current}`);
|
|
76
62
|
try {
|
|
77
63
|
const newItems = await source('', loadedRef.current, { signal: new AbortController().signal });
|
|
78
|
-
log(`loadMore got ${newItems.length} items`);
|
|
79
64
|
const updated = choicesRef.current.concat(newItems);
|
|
80
65
|
choicesRef.current = updated;
|
|
81
66
|
loadedRef.current = updated.length;
|
|
@@ -166,7 +151,6 @@ export const paginatedSearch = createPrompt((config, done) => {
|
|
|
166
151
|
activeRef.current++;
|
|
167
152
|
setActive(activeRef.current);
|
|
168
153
|
} else if (!searchModeRef.current && loadedRef.current < totalCountRef.current) {
|
|
169
|
-
log(`DOWN at end: active=${activeRef.current} len=${choicesRef.current.length} loaded=${loadedRef.current} total=${totalCountRef.current}`);
|
|
170
154
|
loadMore().then(() => {
|
|
171
155
|
activeRef.current++;
|
|
172
156
|
setActive(activeRef.current);
|
package/src/sdk.js
CHANGED
|
@@ -139,10 +139,6 @@ export class SDKClient {
|
|
|
139
139
|
async list(table, params = {}) {
|
|
140
140
|
const query = new URLSearchParams(params).toString();
|
|
141
141
|
const endpoint = `${this.baseURL}/api/now/table/${table}${query ? '?' + query : ''}`;
|
|
142
|
-
// Debug: log paginated queries
|
|
143
|
-
if (params.sysparm_offset) {
|
|
144
|
-
process.stderr.write(`[SDK list] offset=${params.sysparm_offset} limit=${params.sysparm_limit} table=${table}\n`);
|
|
145
|
-
}
|
|
146
142
|
const result = await this.request(endpoint, { method: 'GET' });
|
|
147
143
|
return result?.result || [];
|
|
148
144
|
}
|