@jacebenson/jsn 3.0.6 → 3.1.1
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 +60 -285
- package/package.json +5 -2
- package/scripts/postinstall.mjs +80 -0
- package/scripts/pre-commit-check.sh +61 -0
- package/scripts/release.js +62 -0
- package/scripts/tag-reminder.js +30 -0
- package/skills/servicenow/SKILL.md +47 -92
- package/src/app.js +5 -5
- package/src/cli.js +268 -161
- package/src/commands/_ticket.js +15 -2
- package/src/commands/auth.js +10 -1
- package/src/commands/catalog.js +179 -292
- package/src/commands/dev/_generic.js +50 -62
- package/src/commands/dev/_simple.js +27 -0
- package/src/commands/dev/b4rules.js +5 -0
- package/src/commands/dev/eval.js +1 -1
- package/src/commands/dev/flows.js +31 -8
- package/src/commands/dev/forms.js +131 -253
- package/src/commands/dev/lists.js +16 -6
- package/src/commands/dev/logs.js +52 -9
- package/src/commands/dev/scopes.js +4 -3
- package/src/commands/dev/scrapi.js +13 -13
- package/src/commands/dev/updatesets.js +6 -5
- package/src/commands/groupmembers.js +10 -1
- package/src/commands/grouproles.js +10 -1
- package/src/commands/groups.js +12 -1
- package/src/commands/records.js +16 -4
- package/src/commands/skill.js +11 -1
- package/src/commands/tickets.js +12 -1
- package/src/commands/users.js +12 -1
- package/src/help.js +125 -35
- package/src/helpers.js +50 -34
- package/src/paginated-search.js +210 -0
- package/src/sdk.js +4 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getStringField } from '../../helpers.js';
|
|
3
|
-
|
|
4
|
-
// ─── Helpers ───
|
|
1
|
+
import { getStringField, interactiveList } from '../../helpers.js';
|
|
5
2
|
|
|
6
3
|
function getDisplayValue(record, key) {
|
|
7
4
|
if (!record || typeof record !== 'object') return '';
|
|
@@ -20,14 +17,8 @@ function getIntValue(record, key) {
|
|
|
20
17
|
const val = record[key];
|
|
21
18
|
if (val == null) return 0;
|
|
22
19
|
if (typeof val === 'number') return Math.floor(val);
|
|
23
|
-
if (typeof val === 'string') {
|
|
24
|
-
|
|
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
|
-
}
|
|
20
|
+
if (typeof val === 'string') { const n = parseInt(val, 10); return isNaN(n) ? 0 : n; }
|
|
21
|
+
if (typeof val === 'object' && val.value != null) { const n = parseInt(val.value, 10); return isNaN(n) ? 0 : n; }
|
|
31
22
|
return 0;
|
|
32
23
|
}
|
|
33
24
|
|
|
@@ -40,7 +31,87 @@ function getBoolValue(record, key) {
|
|
|
40
31
|
return false;
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
|
|
34
|
+
async function showForm(app, table, viewName) {
|
|
35
|
+
// 1) Look up the view
|
|
36
|
+
const viewParams = new URLSearchParams();
|
|
37
|
+
viewParams.set('sysparm_limit', '1');
|
|
38
|
+
viewParams.set('sysparm_fields', 'sys_id');
|
|
39
|
+
viewParams.set('sysparm_display_value', 'all');
|
|
40
|
+
viewParams.set('sysparm_query', `name=${viewName}`);
|
|
41
|
+
let viewSysID = '';
|
|
42
|
+
try { const vr = await app.sdk.list('sys_ui_view', viewParams); if (vr.length > 0) viewSysID = getStringField(vr[0], 'sys_id'); } catch { /* ignore */ }
|
|
43
|
+
if (!viewSysID) {
|
|
44
|
+
viewParams.set('sysparm_query', `title=${viewName}`);
|
|
45
|
+
try { const vr = await app.sdk.list('sys_ui_view', viewParams); if (vr.length > 0) viewSysID = getStringField(vr[0], 'sys_id'); } catch { /* ignore */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2) Get sections for this table + view
|
|
49
|
+
const secParams = new URLSearchParams();
|
|
50
|
+
secParams.set('sysparm_limit', '200');
|
|
51
|
+
secParams.set('sysparm_fields', 'sys_id,caption');
|
|
52
|
+
secParams.set('sysparm_display_value', 'all');
|
|
53
|
+
secParams.set('sysparm_query', `name=${table}^view=${viewName}^ORDERBYposition`);
|
|
54
|
+
if (viewSysID) {
|
|
55
|
+
secParams.set('sysparm_query', `name=${table}^view=${viewSysID}^ORDERBYposition`);
|
|
56
|
+
}
|
|
57
|
+
const sections = await app.sdk.list('sys_ui_section', secParams);
|
|
58
|
+
|
|
59
|
+
// 3) Get elements for each section
|
|
60
|
+
const sectionsOut = [];
|
|
61
|
+
for (const sec of sections) {
|
|
62
|
+
const secSysID = getStringField(sec, 'sys_id');
|
|
63
|
+
const sectionCaption = getDisplayValue(sec, 'caption') || '(unnamed)';
|
|
64
|
+
|
|
65
|
+
let elements = [];
|
|
66
|
+
if (secSysID) {
|
|
67
|
+
const elemParams = new URLSearchParams();
|
|
68
|
+
elemParams.set('sysparm_limit', '500');
|
|
69
|
+
elemParams.set('sysparm_fields', 'element,type,label,position');
|
|
70
|
+
elemParams.set('sysparm_display_value', 'all');
|
|
71
|
+
elemParams.set('sysparm_query', `sys_ui_section=${secSysID}^ORDERBYposition`);
|
|
72
|
+
try { elements = await app.sdk.list('sys_ui_element', elemParams); } catch { /* ignore */ }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
sectionsOut.push({
|
|
76
|
+
caption: sectionCaption,
|
|
77
|
+
position: getIntValue(sec, 'position'),
|
|
78
|
+
elements: elements
|
|
79
|
+
.map(e => ({
|
|
80
|
+
type: getDisplayValue(e, 'type'),
|
|
81
|
+
label: getDisplayValue(e, 'label'),
|
|
82
|
+
element: getDisplayValue(e, 'element'),
|
|
83
|
+
position: getIntValue(e, 'position'),
|
|
84
|
+
}))
|
|
85
|
+
.sort((a, b) => a.position - b.position),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
sectionsOut.sort((a, b) => a.position - b.position);
|
|
90
|
+
|
|
91
|
+
const totalElements = sectionsOut.reduce((sum, s) => sum + s.elements.length, 0);
|
|
92
|
+
|
|
93
|
+
// Build formatted output
|
|
94
|
+
const lines = [];
|
|
95
|
+
lines.push(`Form: ${table} → ${viewName}`);
|
|
96
|
+
if (viewSysID) lines.push(` View: ${app.getEffectiveInstance()}/nav_to.do?uri=sys_ui_view.do?sys_id=${viewSysID}`);
|
|
97
|
+
lines.push('');
|
|
98
|
+
|
|
99
|
+
for (const sec of sectionsOut) {
|
|
100
|
+
const secLabel = sec.caption || '(unnamed)';
|
|
101
|
+
lines.push(`── ${secLabel} ──`);
|
|
102
|
+
if (sec.elements.length === 0) {
|
|
103
|
+
lines.push(' (no elements)');
|
|
104
|
+
}
|
|
105
|
+
for (const el of sec.elements) {
|
|
106
|
+
const elLabel = el.label || el.element || '(unnamed)';
|
|
107
|
+
const typeStr = el.type ? ` (${el.type})` : '';
|
|
108
|
+
lines.push(` ${elLabel}${typeStr}`);
|
|
109
|
+
}
|
|
110
|
+
lines.push('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
114
|
+
}
|
|
44
115
|
|
|
45
116
|
export function formsCmd(wrap) {
|
|
46
117
|
return {
|
|
@@ -50,57 +121,52 @@ export function formsCmd(wrap) {
|
|
|
50
121
|
builder: (yargs) => {
|
|
51
122
|
return yargs
|
|
52
123
|
.command({
|
|
53
|
-
command: 'list
|
|
124
|
+
command: 'list [table]',
|
|
54
125
|
aliases: ['ls'],
|
|
55
|
-
describe: 'List form views
|
|
126
|
+
describe: 'List form views',
|
|
56
127
|
builder: (y) => y
|
|
57
|
-
.positional('table', {
|
|
58
|
-
describe: 'Table name (e.g. incident, change_request)',
|
|
59
|
-
type: 'string',
|
|
60
|
-
})
|
|
128
|
+
.positional('table', { describe: 'Table name (e.g. incident, change_request)', type: 'string' })
|
|
61
129
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
62
130
|
handler: wrap(async (argv, app) => {
|
|
63
|
-
|
|
64
|
-
const
|
|
131
|
+
app.requireInstance();
|
|
132
|
+
const table = argv.table || '';
|
|
133
|
+
const query = table ? `name=${table}` : '';
|
|
134
|
+
|
|
135
|
+
const picked = await interactiveList({
|
|
136
|
+
app, table: 'sys_ui_form', singular: 'form', columns: ['name', 'view'], limit: argv.limit, query, labelField: 'name',
|
|
137
|
+
formatLabel: r => {
|
|
138
|
+
const t = getStringField(r, 'name') || '';
|
|
139
|
+
const v = getStringField(r, 'view') || '';
|
|
140
|
+
return `${t} — ${v}`;
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (picked === undefined) return;
|
|
144
|
+
if (picked) {
|
|
145
|
+
const t = getStringField(picked, 'name') || '';
|
|
146
|
+
const v = getStringField(picked, 'view') || 'Default view';
|
|
147
|
+
// Show full form layout using the show logic
|
|
148
|
+
await showForm(app, t, v);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
65
151
|
|
|
152
|
+
// Fallback
|
|
66
153
|
const params = new URLSearchParams();
|
|
67
|
-
params.set('sysparm_limit', String(limit));
|
|
68
|
-
params.set('sysparm_fields', 'view');
|
|
69
|
-
params.set('sysparm_group_by', 'view');
|
|
154
|
+
params.set('sysparm_limit', String(argv.limit));
|
|
155
|
+
params.set('sysparm_fields', 'name,view,sys_id');
|
|
70
156
|
params.set('sysparm_display_value', 'all');
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const records = await app.sdk.list('sys_ui_section', params);
|
|
75
|
-
|
|
76
|
-
const viewMap = new Map();
|
|
77
|
-
for (const record of records) {
|
|
78
|
-
const view = getDisplayValue(record, 'view');
|
|
79
|
-
if (view && !viewMap.has(view)) {
|
|
80
|
-
viewMap.set(view, true);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const views = Array.from(viewMap.keys()).sort();
|
|
85
|
-
const defaultViews = views.filter(v => v === 'Default view');
|
|
86
|
-
const workspaceViews = views.filter(v => v.toLowerCase().includes('workspace'));
|
|
87
|
-
const otherViews = views.filter(v => !defaultViews.includes(v) && !workspaceViews.includes(v));
|
|
88
|
-
|
|
89
|
-
const recordsOut = views.map(v => ({ view: v, table }));
|
|
157
|
+
params.set('sysparm_query', (query || '') + '^ORDERBYname');
|
|
158
|
+
const records = await app.sdk.list('sys_ui_form', params);
|
|
90
159
|
|
|
160
|
+
const desc = table ? `for ${table}` : 'across all tables';
|
|
91
161
|
app.ok({
|
|
92
|
-
table,
|
|
93
|
-
count:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
records: recordsOut,
|
|
98
|
-
instance_url: app.getEffectiveInstance(),
|
|
162
|
+
table: table || 'sys_ui_form',
|
|
163
|
+
count: records.length,
|
|
164
|
+
columns: ['name', 'view'],
|
|
165
|
+
records,
|
|
166
|
+
context: { instance_url: app.getEffectiveInstance() },
|
|
99
167
|
}, {
|
|
100
|
-
summary: `${
|
|
101
|
-
breadcrumbs: [
|
|
102
|
-
{ action: 'show', cmd: `jsn dev forms show ${table} --view "Default view"`, description: 'Show Default view layout' },
|
|
103
|
-
],
|
|
168
|
+
summary: `${records.length} form(s) ${desc}`,
|
|
169
|
+
...(table ? { breadcrumbs: [{ action: 'show', cmd: `jsn forms show ${table} --view "Default view"`, description: 'Show Default view layout' }] } : {}),
|
|
104
170
|
});
|
|
105
171
|
}),
|
|
106
172
|
})
|
|
@@ -111,207 +177,19 @@ export function formsCmd(wrap) {
|
|
|
111
177
|
builder: (y) => y
|
|
112
178
|
.option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
|
|
113
179
|
handler: wrap(async (argv, app) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Look up view sys_id
|
|
118
|
-
let viewSysID = '';
|
|
119
|
-
const viewParams = new URLSearchParams();
|
|
120
|
-
viewParams.set('sysparm_limit', '1');
|
|
121
|
-
viewParams.set('sysparm_fields', 'sys_id');
|
|
122
|
-
viewParams.set('sysparm_query', `name=${viewName}`);
|
|
123
|
-
try {
|
|
124
|
-
const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
|
|
125
|
-
if (viewRecords.length > 0) {
|
|
126
|
-
viewSysID = getStringField(viewRecords[0], 'sys_id');
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
// ignore
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!viewSysID) {
|
|
133
|
-
viewParams.set('sysparm_query', `title=${viewName}`);
|
|
134
|
-
try {
|
|
135
|
-
const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
|
|
136
|
-
if (viewRecords.length > 0) {
|
|
137
|
-
viewSysID = getStringField(viewRecords[0], 'sys_id');
|
|
138
|
-
}
|
|
139
|
-
} catch {
|
|
140
|
-
// ignore
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!viewSysID) {
|
|
145
|
-
viewSysID = viewName;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Fetch sections
|
|
149
|
-
const params = new URLSearchParams();
|
|
150
|
-
params.set('sysparm_limit', '100');
|
|
151
|
-
params.set('sysparm_fields', 'sys_id,name,view,caption,header,order,active,sys_created_on,sys_updated_on');
|
|
152
|
-
params.set('sysparm_display_value', 'all');
|
|
153
|
-
|
|
154
|
-
const parts = [];
|
|
155
|
-
if (table) parts.push(`name=${table}`);
|
|
156
|
-
if (viewSysID) parts.push(`view=${viewSysID}`);
|
|
157
|
-
const sysparmQuery = parts.length > 0 ? `${parts.join('^')}^ORDERBYorder` : 'ORDERBYorder';
|
|
158
|
-
params.set('sysparm_query', sysparmQuery);
|
|
159
|
-
|
|
160
|
-
const records = await app.sdk.list('sys_ui_section', params);
|
|
161
|
-
if (records.length === 0) {
|
|
162
|
-
throw new Error(`no form sections found for ${table} with view "${viewName}"`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Parse sections
|
|
166
|
-
const sections = records.map(record => ({
|
|
167
|
-
sys_id: getStringField(record, 'sys_id'),
|
|
168
|
-
name: getDisplayValue(record, 'name'),
|
|
169
|
-
view: getDisplayValue(record, 'view'),
|
|
170
|
-
caption: getDisplayValue(record, 'caption'),
|
|
171
|
-
header: getDisplayValue(record, 'header'),
|
|
172
|
-
order: getIntValue(record, 'order'),
|
|
173
|
-
active: getBoolValue(record, 'active'),
|
|
174
|
-
createdOn: getDisplayValue(record, 'sys_created_on'),
|
|
175
|
-
updatedOn: getDisplayValue(record, 'sys_updated_on'),
|
|
176
|
-
}));
|
|
177
|
-
|
|
178
|
-
// Fetch elements for each section
|
|
179
|
-
const sectionElements = new Map();
|
|
180
|
-
for (const section of sections) {
|
|
181
|
-
const elemParams = new URLSearchParams();
|
|
182
|
-
elemParams.set('sysparm_limit', '100');
|
|
183
|
-
elemParams.set('sysparm_fields', 'sys_id,sys_ui_section,element,label,type,position,row,col,mandatory,read_only,visible');
|
|
184
|
-
elemParams.set('sysparm_display_value', 'all');
|
|
185
|
-
elemParams.set('sysparm_query', `sys_ui_section=${section.sys_id}^ORDERBYposition`);
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const elemRecords = await app.sdk.list('sys_ui_element', elemParams);
|
|
189
|
-
const elements = elemRecords.map(record => ({
|
|
190
|
-
sys_id: getStringField(record, 'sys_id'),
|
|
191
|
-
section: getStringField(record, 'sys_ui_section'),
|
|
192
|
-
name: getDisplayValue(record, 'element'),
|
|
193
|
-
label: getDisplayValue(record, 'label'),
|
|
194
|
-
elementType: getDisplayValue(record, 'type'),
|
|
195
|
-
position: getIntValue(record, 'position'),
|
|
196
|
-
row: getIntValue(record, 'row'),
|
|
197
|
-
column: getIntValue(record, 'col'),
|
|
198
|
-
mandatory: getBoolValue(record, 'mandatory'),
|
|
199
|
-
readOnly: getBoolValue(record, 'read_only'),
|
|
200
|
-
visible: getBoolValue(record, 'visible'),
|
|
201
|
-
}));
|
|
202
|
-
sectionElements.set(section.sys_id, elements);
|
|
203
|
-
} catch {
|
|
204
|
-
sectionElements.set(section.sys_id, []);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Build formatted output
|
|
209
|
-
const lines = [];
|
|
210
|
-
lines.push('');
|
|
211
|
-
lines.push(chalk.bold(chalk.hex('#e8a217')(`${table} (${viewName})`)));
|
|
212
|
-
lines.push('');
|
|
213
|
-
|
|
214
|
-
for (let i = 0; i < sections.length; i++) {
|
|
215
|
-
const section = sections[i];
|
|
216
|
-
const elements = sectionElements.get(section.sys_id) || [];
|
|
217
|
-
|
|
218
|
-
let sectionTitle = section.caption;
|
|
219
|
-
if (!sectionTitle && section.header && section.header !== 'false') {
|
|
220
|
-
sectionTitle = section.header;
|
|
221
|
-
}
|
|
222
|
-
if (!sectionTitle) {
|
|
223
|
-
sectionTitle = `Section ${i + 1}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
lines.push(chalk.bold(chalk.hex('#666666')(`─ ${sectionTitle} ─`)));
|
|
227
|
-
|
|
228
|
-
if (elements.length === 0) {
|
|
229
|
-
lines.push(chalk.hex('#888888')(' (no fields)'));
|
|
230
|
-
lines.push('');
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
elements.sort((a, b) => a.position - b.position);
|
|
235
|
-
|
|
236
|
-
for (const elem of elements) {
|
|
237
|
-
if (elem.elementType && elem.elementType !== 'field') {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
let displayName = elem.name;
|
|
242
|
-
if (!displayName) displayName = elem.label;
|
|
243
|
-
if (!displayName) displayName = elem.elementType;
|
|
244
|
-
|
|
245
|
-
const indicators = [];
|
|
246
|
-
if (elem.mandatory) indicators.push('*');
|
|
247
|
-
if (elem.readOnly) indicators.push('(RO)');
|
|
248
|
-
const indicatorStr = indicators.length > 0 ? ` ${indicators.join(' ')}` : '';
|
|
249
|
-
|
|
250
|
-
lines.push(` ${chalk.hex('#cccccc')(displayName)}${indicatorStr ? chalk.hex('#888888')(indicatorStr) : ''}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
lines.push('');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
lines.push('─────');
|
|
257
|
-
lines.push('');
|
|
258
|
-
lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
|
|
259
|
-
lines.push(` ${`jsn dev forms list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
|
|
260
|
-
lines.push(` ${`jsn dev columns --table ${table}`.padEnd(50)} ${chalk.hex('#888888')('View table columns')}`);
|
|
261
|
-
lines.push('');
|
|
262
|
-
|
|
263
|
-
const formatted = lines.join('\n');
|
|
264
|
-
|
|
265
|
-
// Build structured data for JSON/quiet mode
|
|
266
|
-
const sectionsData = sections.map(section => {
|
|
267
|
-
const elements = (sectionElements.get(section.sys_id) || [])
|
|
268
|
-
.filter(elem => !elem.elementType || elem.elementType === 'field')
|
|
269
|
-
.sort((a, b) => a.position - b.position)
|
|
270
|
-
.map(elem => ({
|
|
271
|
-
sys_id: elem.sys_id,
|
|
272
|
-
name: elem.name,
|
|
273
|
-
label: elem.label,
|
|
274
|
-
type: elem.elementType,
|
|
275
|
-
position: elem.position,
|
|
276
|
-
mandatory: elem.mandatory,
|
|
277
|
-
read_only: elem.readOnly,
|
|
278
|
-
}));
|
|
279
|
-
|
|
280
|
-
let sectionTitle = section.caption;
|
|
281
|
-
if (!sectionTitle && section.header && section.header !== 'false') {
|
|
282
|
-
sectionTitle = section.header;
|
|
283
|
-
}
|
|
284
|
-
if (!sectionTitle) {
|
|
285
|
-
sectionTitle = 'Section';
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
sys_id: section.sys_id,
|
|
290
|
-
caption: sectionTitle,
|
|
291
|
-
order: section.order,
|
|
292
|
-
elements,
|
|
293
|
-
};
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
app.ok({
|
|
297
|
-
table,
|
|
298
|
-
view: viewName,
|
|
299
|
-
sections: sectionsData,
|
|
300
|
-
_formatted: formatted,
|
|
301
|
-
_context: {
|
|
302
|
-
instance_url: app.getEffectiveInstance(),
|
|
303
|
-
table: 'sys_ui_section',
|
|
304
|
-
},
|
|
305
|
-
}, {
|
|
306
|
-
summary: `Form: ${table} (${viewName}) - ${sections.length} sections`,
|
|
307
|
-
breadcrumbs: [
|
|
308
|
-
{ action: 'list', cmd: `jsn dev forms list ${table}`, description: 'List all views' },
|
|
309
|
-
{ action: 'columns', cmd: `jsn dev columns --table ${table}`, description: 'View table columns' },
|
|
310
|
-
],
|
|
311
|
-
});
|
|
180
|
+
app.requireInstance();
|
|
181
|
+
await showForm(app, argv.table, argv.view);
|
|
312
182
|
}),
|
|
313
183
|
});
|
|
314
184
|
},
|
|
315
|
-
handler: () => {
|
|
185
|
+
handler: () => {
|
|
186
|
+
console.log('Manage UI form layouts.');
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log('Available subcommands:');
|
|
189
|
+
console.log(' list [table] List form views (shows table — view name)');
|
|
190
|
+
console.log(' show <table> Show form layout for a table');
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log('Run "jsn forms <command> --help" for details.');
|
|
193
|
+
},
|
|
316
194
|
};
|
|
317
195
|
}
|
|
@@ -51,6 +51,7 @@ export function listsCmd(wrap) {
|
|
|
51
51
|
})
|
|
52
52
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
53
53
|
handler: wrap(async (argv, app) => {
|
|
54
|
+
app.requireInstance();
|
|
54
55
|
const table = argv.table;
|
|
55
56
|
const limit = argv.limit || 50;
|
|
56
57
|
|
|
@@ -90,7 +91,7 @@ export function listsCmd(wrap) {
|
|
|
90
91
|
}, {
|
|
91
92
|
summary: `${views.length} list views for ${table}`,
|
|
92
93
|
breadcrumbs: [
|
|
93
|
-
{ action: 'show', cmd: `jsn
|
|
94
|
+
{ action: 'show', cmd: `jsn lists show ${table} --view "Default view"`, description: 'Show Default view columns' },
|
|
94
95
|
],
|
|
95
96
|
});
|
|
96
97
|
}),
|
|
@@ -102,6 +103,7 @@ export function listsCmd(wrap) {
|
|
|
102
103
|
builder: (y) => y
|
|
103
104
|
.option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
|
|
104
105
|
handler: wrap(async (argv, app) => {
|
|
106
|
+
app.requireInstance();
|
|
105
107
|
const table = argv.table;
|
|
106
108
|
const viewName = argv.view;
|
|
107
109
|
|
|
@@ -195,8 +197,8 @@ export function listsCmd(wrap) {
|
|
|
195
197
|
lines.push('─────');
|
|
196
198
|
lines.push('');
|
|
197
199
|
lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
|
|
198
|
-
lines.push(` ${`jsn
|
|
199
|
-
lines.push(` ${`jsn
|
|
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')}`);
|
|
200
202
|
lines.push('');
|
|
201
203
|
|
|
202
204
|
const formatted = lines.join('\n');
|
|
@@ -225,13 +227,21 @@ export function listsCmd(wrap) {
|
|
|
225
227
|
}, {
|
|
226
228
|
summary: `List: ${table} (${viewName}) - ${elements.length} columns`,
|
|
227
229
|
breadcrumbs: [
|
|
228
|
-
{ action: 'list', cmd: `jsn
|
|
229
|
-
{ action: 'form', cmd: `jsn
|
|
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' },
|
|
230
232
|
],
|
|
231
233
|
});
|
|
232
234
|
}),
|
|
233
235
|
});
|
|
234
236
|
},
|
|
235
|
-
handler: () => {
|
|
237
|
+
handler: () => {
|
|
238
|
+
console.log('Manage list layouts from the sys_ui_list table.');
|
|
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');
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('Run "jsn lists <command> --help" for details.');
|
|
245
|
+
},
|
|
236
246
|
};
|
|
237
247
|
}
|
package/src/commands/dev/logs.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { formatRecordForDisplay, interactiveList, getStringField } from '../../helpers.js';
|
|
2
2
|
|
|
3
|
+
function logIcon(level) {
|
|
4
|
+
level = (level || '').toLowerCase();
|
|
5
|
+
if (level === 'error') return '❌';
|
|
6
|
+
if (level === 'warning') return '⚠️';
|
|
7
|
+
if (level === 'information' || level === 'info') return 'ℹ️';
|
|
8
|
+
return '📝';
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export function logsCmd(wrap) {
|
|
4
12
|
return {
|
|
5
13
|
command: 'logs [subcommand]',
|
|
@@ -12,21 +20,47 @@ export function logsCmd(wrap) {
|
|
|
12
20
|
aliases: ['ls'],
|
|
13
21
|
describe: 'List system logs',
|
|
14
22
|
builder: (y) => y
|
|
15
|
-
.option('query', { type: 'string', describe: 'Encoded query (e.g. "
|
|
16
|
-
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns
|
|
23
|
+
.option('query', { type: 'string', describe: 'Encoded query (e.g. "level=error" or "sourceLIKEscript")' })
|
|
24
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns' })
|
|
17
25
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
18
26
|
handler: wrap(async (argv, app) => {
|
|
19
|
-
|
|
27
|
+
app.requireInstance();
|
|
28
|
+
const columns = argv.columns ? argv.columns.split(',') : ['level', 'message', 'source', 'sys_created_on', 'sys_id', 'context_map'];
|
|
20
29
|
const query = argv.query || '';
|
|
21
30
|
|
|
22
|
-
// Try interactive picker first
|
|
23
31
|
const picked = await interactiveList({
|
|
24
32
|
app, table: 'syslog', singular: 'log entry', columns, limit: argv.limit, query, labelField: 'message',
|
|
25
|
-
formatLabel: r =>
|
|
33
|
+
formatLabel: r => {
|
|
34
|
+
const level = getStringField(r, 'level') || '';
|
|
35
|
+
const msg = (getStringField(r, 'message') || '').substring(0, 80);
|
|
36
|
+
const src = getStringField(r, 'source') || '';
|
|
37
|
+
return `${logIcon(level)} ${msg}${src ? ` [${src}]` : ''}`;
|
|
38
|
+
},
|
|
26
39
|
});
|
|
40
|
+
if (picked === undefined) return;
|
|
27
41
|
if (picked) {
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
const sysID = getStringField(picked, 'sys_id') || '';
|
|
43
|
+
// Fetch full record for rich display (like jsn logs show)
|
|
44
|
+
const full = await app.sdk.get('syslog', sysID) || picked;
|
|
45
|
+
full._context = { instance_url: app.getEffectiveInstance(), table: 'syslog' };
|
|
46
|
+
// Parse and pretty-print context_map JSON (SN API may return as string or object)
|
|
47
|
+
const cm = full.context_map;
|
|
48
|
+
if (cm) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = typeof cm === 'string' ? JSON.parse(cm) : cm;
|
|
51
|
+
full.context_map = Object.entries(parsed).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
52
|
+
} catch { /* leave as-is */ }
|
|
53
|
+
}
|
|
54
|
+
const level = getStringField(full, 'level') || '?';
|
|
55
|
+
const instance = app.getEffectiveInstance();
|
|
56
|
+
return app.ok(full, {
|
|
57
|
+
summary: `${logIcon(level)} ${level}: ${(getStringField(full, 'message') || '').substring(0, 80)}`,
|
|
58
|
+
breadcrumbs: [
|
|
59
|
+
{ action: 'list', cmd: 'jsn logs list', description: 'Back to all logs' },
|
|
60
|
+
{ action: 'view', cmd: `jsn logs show ${sysID}`, description: 'View full details' },
|
|
61
|
+
...(instance && sysID ? [{ action: 'open', label: `${instance}/syslog_list.do?sysparm_query=sys_id=${sysID}`, description: 'Open in ServiceNow' }] : []),
|
|
62
|
+
],
|
|
63
|
+
});
|
|
30
64
|
}
|
|
31
65
|
|
|
32
66
|
// Fall back to text/table
|
|
@@ -55,10 +89,19 @@ export function logsCmd(wrap) {
|
|
|
55
89
|
if (!record) {
|
|
56
90
|
throw new Error(`Log entry not found: ${argv.sys_id}`);
|
|
57
91
|
}
|
|
58
|
-
|
|
92
|
+
const level = getStringField(record, 'level') || '?';
|
|
93
|
+
app.ok(record, { summary: `${logIcon(level)} ${level}: ${(getStringField(record, 'message') || '').substring(0, 80)}` });
|
|
59
94
|
}),
|
|
60
95
|
});
|
|
61
96
|
},
|
|
62
|
-
handler: () => {
|
|
97
|
+
handler: () => {
|
|
98
|
+
console.log('View system logs from the syslog table.');
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log('Available subcommands:');
|
|
101
|
+
console.log(' list List log entries');
|
|
102
|
+
console.log(' show <sys_id> Show a log entry by sys_id');
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log('Run "jsn logs <command> --help" for details.');
|
|
105
|
+
},
|
|
63
106
|
};
|
|
64
107
|
}
|
|
@@ -14,7 +14,7 @@ export function scopesCmd(wrap) {
|
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
16
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
17
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
17
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'scope', 'short_description', 'active'];
|
|
20
20
|
const query = argv.query || '';
|
|
@@ -23,6 +23,7 @@ export function scopesCmd(wrap) {
|
|
|
23
23
|
app, table: 'sys_scope', singular: 'scope', columns, limit: argv.limit, query, labelField: 'name',
|
|
24
24
|
formatLabel: r => `${getStringField(r, 'name')} [${getStringField(r, 'scope') || '?'}]`,
|
|
25
25
|
});
|
|
26
|
+
if (picked === undefined) return; // user cancelled
|
|
26
27
|
if (picked) {
|
|
27
28
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'sys_scope' };
|
|
28
29
|
return app.ok(picked, { summary: `Scope: ${getStringField(picked, 'name')}` });
|
|
@@ -143,7 +144,7 @@ export function scopesCmd(wrap) {
|
|
|
143
144
|
summary: `Created scope: ${scope}`,
|
|
144
145
|
breadcrumbs: [{
|
|
145
146
|
action: 'show',
|
|
146
|
-
cmd: `jsn
|
|
147
|
+
cmd: `jsn scopes show ${scope}`,
|
|
147
148
|
description: 'View the new scope',
|
|
148
149
|
}],
|
|
149
150
|
});
|
|
@@ -159,7 +160,7 @@ export function scopesCmd(wrap) {
|
|
|
159
160
|
console.log(' show <scope> Show a scope');
|
|
160
161
|
console.log(' set <scope> Set the current application scope');
|
|
161
162
|
console.log(' create Create a new application scope');
|
|
162
|
-
console.log('\nRun "jsn
|
|
163
|
+
console.log('\nRun "jsn scopes <command> --help" for details.');
|
|
163
164
|
}
|
|
164
165
|
},
|
|
165
166
|
};
|