@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
package/src/commands/_ticket.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Used by incidents, changes, requests, tasks, and most dev subcommands
|
|
3
3
|
|
|
4
4
|
import { getStringField, formatRecordForDisplay, buildQuerySuffix, resolveFieldsParam } from '../helpers.js';
|
|
5
|
-
import search from '@inquirer/
|
|
5
|
+
import { search } from '@inquirer/prompts';
|
|
6
6
|
import { isTTY, FormatAuto } from '../output.js';
|
|
7
7
|
|
|
8
8
|
export function buildTicketCommands(table, displayName, alias, defaultColumns, stateMap, iconFn, wrap) {
|
|
@@ -255,6 +255,19 @@ export function buildTicketCommands(table, displayName, alias, defaultColumns, s
|
|
|
255
255
|
})
|
|
256
256
|
|
|
257
257
|
},
|
|
258
|
-
handler: () => {
|
|
258
|
+
handler: () => {
|
|
259
|
+
const singular = displayName.endsWith('s') ? displayName.slice(0, -1) : displayName;
|
|
260
|
+
const article = singular.startsWith('a') || singular.startsWith('e') || singular.startsWith('i') || singular.startsWith('o') || singular.startsWith('u') ? 'an' : 'a';
|
|
261
|
+
console.log(`Manage ${displayName} from the ${table} table.`);
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log('Available subcommands:');
|
|
264
|
+
console.log(` list List ${displayName}`);
|
|
265
|
+
console.log(` show <number or sys_id> Show ${article} ${singular} by number or sys_id`);
|
|
266
|
+
console.log(` create Create ${article} ${singular}`);
|
|
267
|
+
console.log(` update <identifier> Update ${article} ${singular}`);
|
|
268
|
+
console.log(` delete <identifier> Delete ${article} ${singular}`);
|
|
269
|
+
console.log('');
|
|
270
|
+
console.log(`Run "jsn ${displayName} <command> --help" for details.`);
|
|
271
|
+
}, // Default handler
|
|
259
272
|
};
|
|
260
273
|
}
|
package/src/commands/auth.js
CHANGED
|
@@ -302,6 +302,15 @@ Examples:
|
|
|
302
302
|
}),
|
|
303
303
|
});
|
|
304
304
|
},
|
|
305
|
-
handler: () => {
|
|
305
|
+
handler: () => {
|
|
306
|
+
console.log('Manage OAuth authentication for ServiceNow instances.');
|
|
307
|
+
console.log('');
|
|
308
|
+
console.log('Available subcommands:');
|
|
309
|
+
console.log(' login <url> Login with OAuth (supports --print-url, --code for CI/CD)');
|
|
310
|
+
console.log(' status Show auth status for the active profile');
|
|
311
|
+
console.log(' refresh Refresh the access token');
|
|
312
|
+
console.log('');
|
|
313
|
+
console.log('Run "jsn auth <command> --help" for details.');
|
|
314
|
+
},
|
|
306
315
|
};
|
|
307
316
|
}
|
package/src/commands/catalog.js
CHANGED
|
@@ -1,325 +1,212 @@
|
|
|
1
|
-
|
|
2
|
-
// NOTE: This is an AI-friendly high-level command that wraps sc_cat_item + item_option_new
|
|
3
|
-
|
|
4
|
-
import { resolveItemOptionType } from '../helpers.js';
|
|
1
|
+
import { getStringField, interactiveList } from '../helpers.js';
|
|
5
2
|
|
|
6
3
|
export function catalogCmd(wrap) {
|
|
7
4
|
return {
|
|
8
|
-
command: '
|
|
9
|
-
aliases: ['
|
|
10
|
-
describe: 'Manage Service Catalog items
|
|
5
|
+
command: 'catalogitems [subcommand]',
|
|
6
|
+
aliases: ['ci'],
|
|
7
|
+
describe: 'Manage Service Catalog items',
|
|
11
8
|
builder: (yargs) => {
|
|
12
9
|
return yargs
|
|
13
|
-
// ── create-item: Create a new catalog item definition ──
|
|
14
10
|
.command({
|
|
15
|
-
command: 'create
|
|
16
|
-
|
|
11
|
+
command: 'create',
|
|
12
|
+
aliases: ['new'],
|
|
13
|
+
describe: 'Create a catalog item',
|
|
17
14
|
builder: (y) => y
|
|
18
|
-
.option('name', { alias: 'n', type: 'string', demandOption: true
|
|
19
|
-
.option('short-description', { alias: 'd', type: 'string'
|
|
20
|
-
.option('
|
|
21
|
-
.option('category', { alias: 'c', type: 'string', describe: 'Category name (created if missing)' })
|
|
22
|
-
.option('variable', { alias: 'v', type: 'array', describe: 'Variable definition in format "name:type:label" or "name:type" (e.g. "reason:multilinetext:Reason" or "start_date:date")' })
|
|
23
|
-
.option('variables', { type: 'string', describe: 'JSON object of variable values for submit_produce API (e.g. \'{"quantity":"100","reason":"Need more"}\')' })
|
|
24
|
-
.option('update-set', { type: 'string', describe: 'Update set sys_id or name to capture changes' }),
|
|
15
|
+
.option('name', { alias: 'n', type: 'string', demandOption: true })
|
|
16
|
+
.option('short-description', { alias: 'd', type: 'string' })
|
|
17
|
+
.option('category', { alias: 'c', type: 'string' }),
|
|
25
18
|
handler: wrap(async (argv, app) => {
|
|
26
|
-
|
|
27
|
-
if (argv.variables) {
|
|
28
|
-
let varsObj;
|
|
29
|
-
try {
|
|
30
|
-
varsObj = JSON.parse(argv.variables);
|
|
31
|
-
} catch {
|
|
32
|
-
throw new Error('--variables must be a valid JSON object, e.g. {"reason":"Need this"}');
|
|
33
|
-
}
|
|
34
|
-
// Resolve the catalog item sys_id from name if needed
|
|
35
|
-
let itemSysID = argv.name;
|
|
36
|
-
if (!itemSysID.match(/^[a-f0-9]{32}$/i)) {
|
|
37
|
-
const params = new URLSearchParams();
|
|
38
|
-
params.set('sysparm_query', `name=${itemSysID}`);
|
|
39
|
-
params.set('sysparm_limit', '1');
|
|
40
|
-
params.set('sysparm_fields', 'sys_id');
|
|
41
|
-
const items = await app.sdk.list('sc_cat_item', params);
|
|
42
|
-
if (items.length === 0) throw new Error(`Catalog item not found: ${itemSysID}`);
|
|
43
|
-
const item = items[0];
|
|
44
|
-
itemSysID = item.sys_id?.value || item.sys_id;
|
|
45
|
-
}
|
|
46
|
-
// Submit via submit_produce API
|
|
47
|
-
const endpoint = `${app.sdk.baseURL}/api/sn_sc/servicecatalog/items/${itemSysID}/submit_produce`;
|
|
48
|
-
const body = { variables: varsObj };
|
|
49
|
-
const result = await app.sdk.request(endpoint, {
|
|
50
|
-
method: 'POST',
|
|
51
|
-
body: JSON.stringify(body),
|
|
52
|
-
});
|
|
53
|
-
const reqID = result?.result?.sys_id || result?.result?.number || '';
|
|
54
|
-
app.ok({
|
|
55
|
-
requested_item: reqID,
|
|
56
|
-
item_id: itemSysID,
|
|
57
|
-
variables: varsObj,
|
|
58
|
-
}, {
|
|
59
|
-
summary: `Request created: ${result?.result?.number || reqID}`,
|
|
60
|
-
breadcrumbs: [
|
|
61
|
-
{ action: 'view', cmd: `jsn requests show ${reqID}`, description: 'View the request' },
|
|
62
|
-
],
|
|
63
|
-
});
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Original create-item flow: create a new catalog item definition
|
|
68
|
-
const name = argv.name;
|
|
69
|
-
const shortDesc = argv['short-description'] || '';
|
|
70
|
-
const description = argv.description || shortDesc;
|
|
19
|
+
app.requireInstance();
|
|
71
20
|
let categoryID = '';
|
|
72
|
-
|
|
73
|
-
// Resolve or create category
|
|
74
21
|
if (argv.category) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const cats = await app.sdk.list('sc_category',
|
|
80
|
-
if (cats.length > 0)
|
|
81
|
-
|
|
82
|
-
} else {
|
|
83
|
-
const newCat = await app.sdk.create('sc_category', {
|
|
84
|
-
title: argv.category,
|
|
85
|
-
description: `Category for ${argv.category}`,
|
|
86
|
-
});
|
|
87
|
-
categoryID = newCat.sys_id?.value || newCat.sys_id;
|
|
88
|
-
}
|
|
22
|
+
const cp = new URLSearchParams();
|
|
23
|
+
cp.set('sysparm_query', `title=${argv.category}`);
|
|
24
|
+
cp.set('sysparm_limit', '1');
|
|
25
|
+
cp.set('sysparm_fields', 'sys_id');
|
|
26
|
+
const cats = await app.sdk.list('sc_category', cp);
|
|
27
|
+
if (cats.length > 0) categoryID = cats[0].sys_id?.value || cats[0].sys_id;
|
|
28
|
+
else { const nc = await app.sdk.create('sc_category', { title: argv.category }); categoryID = nc.sys_id?.value || nc.sys_id; }
|
|
89
29
|
}
|
|
90
|
-
|
|
91
|
-
// Create the catalog item
|
|
92
30
|
const item = await app.sdk.create('sc_cat_item', {
|
|
93
|
-
name,
|
|
94
|
-
short_description:
|
|
95
|
-
description,
|
|
31
|
+
name: argv.name,
|
|
32
|
+
short_description: argv['short-description'] || '',
|
|
96
33
|
category: categoryID || undefined,
|
|
97
34
|
active: true,
|
|
98
35
|
type: 'item',
|
|
99
|
-
stage: 'requested',
|
|
100
|
-
});
|
|
101
|
-
const itemID = item.sys_id?.value || item.sys_id;
|
|
102
|
-
|
|
103
|
-
// Parse and create variables
|
|
104
|
-
const varDefs = [];
|
|
105
|
-
if (argv.variable) {
|
|
106
|
-
for (const v of argv.variable) {
|
|
107
|
-
const parts = String(v).split(':');
|
|
108
|
-
if (parts.length >= 2) {
|
|
109
|
-
const typeName = parts.length >= 2 ? parts[parts.length - 2] : '';
|
|
110
|
-
const label = parts.length >= 3 ? parts[parts.length - 1] : parts[0];
|
|
111
|
-
const typeID = resolveItemOptionType(typeName);
|
|
112
|
-
const sysName = parts[0].replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
|
|
113
|
-
varDefs.push({
|
|
114
|
-
name: sysName,
|
|
115
|
-
question_text: label,
|
|
116
|
-
type: typeID,
|
|
117
|
-
order: varDefs.length * 100 + 100,
|
|
118
|
-
cat_item: itemID,
|
|
119
|
-
mandatory: true,
|
|
120
|
-
active: true,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
for (const vd of varDefs) {
|
|
126
|
-
try {
|
|
127
|
-
await app.sdk.create('item_option_new', vd);
|
|
128
|
-
} catch {
|
|
129
|
-
// variable creation failure is non-fatal
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
app.ok({
|
|
135
|
-
sys_id: itemID,
|
|
136
|
-
name,
|
|
137
|
-
category: argv.category || null,
|
|
138
|
-
variables_created: varDefs.length,
|
|
139
|
-
update_set: argv['update-set'] || null,
|
|
140
|
-
instance_url: app.getEffectiveInstance(),
|
|
141
|
-
}, {
|
|
142
|
-
summary: `Created catalog item: ${name} (${varDefs.length} variable(s))`,
|
|
143
|
-
breadcrumbs: [
|
|
144
|
-
{ action: 'view', cmd: `jsn records get --table sc_cat_item --sys-id ${itemID}`, description: 'View the catalog item' },
|
|
145
|
-
{ action: 'edit', cmd: `jsn catalog update-item ${itemID} --name "${name}"`, description: 'Edit the catalog item' },
|
|
146
|
-
],
|
|
147
36
|
});
|
|
37
|
+
app.ok(item, { summary: `Created: ${argv.name}` });
|
|
148
38
|
}),
|
|
149
39
|
})
|
|
150
|
-
// ── items: List and show catalog items ──
|
|
151
40
|
.command({
|
|
152
|
-
command: '
|
|
153
|
-
aliases: ['
|
|
154
|
-
describe: '
|
|
41
|
+
command: 'list',
|
|
42
|
+
aliases: ['ls'],
|
|
43
|
+
describe: 'List catalog items',
|
|
155
44
|
builder: (y) => y
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.option('limit', { alias: 'l', type: 'number', default: 20, describe: 'Max records' })
|
|
162
|
-
.option('category', { type: 'string', describe: 'Filter by category' }),
|
|
163
|
-
handler: wrap(async (argv, app) => {
|
|
164
|
-
let query = 'ORDERBYname';
|
|
165
|
-
if (argv.category) query = `category.titleLIKE${argv.category}^${query}`;
|
|
166
|
-
const params = new URLSearchParams();
|
|
167
|
-
params.set('sysparm_query', query);
|
|
168
|
-
params.set('sysparm_limit', String(argv.limit));
|
|
169
|
-
params.set('sysparm_display_value', 'all');
|
|
170
|
-
params.set('sysparm_fields', 'sys_id,name,short_description,category,active');
|
|
171
|
-
const items = await app.sdk.list('sc_cat_item', params);
|
|
172
|
-
app.ok({
|
|
173
|
-
table: 'sc_cat_item',
|
|
174
|
-
count: items.length,
|
|
175
|
-
columns: ['name', 'short_description', 'category', 'active'],
|
|
176
|
-
records: items.map(r => ({
|
|
177
|
-
sys_id: r.sys_id?.value || r.sys_id,
|
|
178
|
-
name: r.name?.display_value || r.name,
|
|
179
|
-
short_description: r.short_description?.display_value || r.short_description || '',
|
|
180
|
-
category: r.category?.display_value || '',
|
|
181
|
-
active: r.active?.display_value || r.active,
|
|
182
|
-
})),
|
|
183
|
-
context: { instance_url: app.getEffectiveInstance() },
|
|
184
|
-
}, { summary: `${items.length} catalog item(s)` });
|
|
185
|
-
}),
|
|
186
|
-
})
|
|
187
|
-
.command({
|
|
188
|
-
command: 'show <id>',
|
|
189
|
-
describe: 'Show a catalog item with variables and variable sets',
|
|
190
|
-
handler: wrap(async (argv, app) => {
|
|
191
|
-
let itemID = argv.id;
|
|
192
|
-
// Resolve name to sys_id
|
|
193
|
-
if (!itemID.match(/^[a-f0-9]{32}$/i)) {
|
|
194
|
-
const params = new URLSearchParams();
|
|
195
|
-
params.set('sysparm_query', `name=${itemID}`);
|
|
196
|
-
params.set('sysparm_limit', '1');
|
|
197
|
-
params.set('sysparm_fields', 'sys_id,name');
|
|
198
|
-
const items = await app.sdk.list('sc_cat_item', params);
|
|
199
|
-
if (items.length === 0) throw new Error(`Catalog item not found: ${itemID}`);
|
|
200
|
-
itemID = items[0].sys_id?.value || items[0].sys_id;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Get the catalog item
|
|
204
|
-
const item = await app.sdk.get('sc_cat_item', itemID);
|
|
205
|
-
|
|
206
|
-
// Get variables (item_option_new) for this catalog item
|
|
207
|
-
const varParams = new URLSearchParams();
|
|
208
|
-
varParams.set('sysparm_query', `cat_item=${itemID}^active=true`);
|
|
209
|
-
varParams.set('sysparm_limit', '100');
|
|
210
|
-
varParams.set('sysparm_display_value', 'all');
|
|
211
|
-
varParams.set('sysparm_fields', 'sys_id,name,question_text,type,order,mandatory,variable_set');
|
|
212
|
-
const variables = await app.sdk.list('item_option_new', varParams);
|
|
45
|
+
.option('limit', { alias: 'l', type: 'number', default: 50 })
|
|
46
|
+
.option('category', { type: 'string' }),
|
|
47
|
+
handler: wrap(async (argv, app) => {
|
|
48
|
+
app.requireInstance();
|
|
49
|
+
const query = argv.category ? `category.titleLIKE${argv.category}` : '';
|
|
213
50
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (!setVars.has(vs)) {
|
|
229
|
-
setVars.set(vs, { name: vs, label: vs, variables: [] });
|
|
230
|
-
}
|
|
231
|
-
setVars.get(vs).variables.push(entry);
|
|
232
|
-
} else {
|
|
233
|
-
standaloneVars.push(entry);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
51
|
+
const picked = await interactiveList({
|
|
52
|
+
app,
|
|
53
|
+
table: 'sc_cat_item',
|
|
54
|
+
singular: 'catalog item',
|
|
55
|
+
columns: ['name', 'short_description', 'category', 'active'],
|
|
56
|
+
limit: argv.limit,
|
|
57
|
+
query,
|
|
58
|
+
labelField: 'name',
|
|
59
|
+
formatLabel: (r) => {
|
|
60
|
+
const n = getStringField(r, 'name') || '';
|
|
61
|
+
const c = getStringField(r, 'category') || '';
|
|
62
|
+
return c ? `${n} [${c}]` : n;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
236
65
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
setData.label = vsRecord.label?.value || vsRecord.label || setData.name;
|
|
243
|
-
setData.order = vsRecord.order?.display_value || vsRecord.order;
|
|
244
|
-
} catch {
|
|
245
|
-
// Can't resolve, keep sys_id as name
|
|
246
|
-
}
|
|
247
|
-
}
|
|
66
|
+
if (picked === undefined) return;
|
|
67
|
+
if (picked) {
|
|
68
|
+
await showItem(app, getStringField(picked, 'sys_id') || '');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
248
71
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
variables: {
|
|
257
|
-
standalone: standaloneVars,
|
|
258
|
-
variable_sets: Array.from(setVars.values()),
|
|
259
|
-
},
|
|
260
|
-
instance_url: app.getEffectiveInstance(),
|
|
261
|
-
}, {
|
|
262
|
-
summary: `${item.name?.display_value || item.name} — ${standaloneVars.length + Array.from(setVars.values()).reduce((a, s) => a + s.variables.length, 0)} variable(s)`,
|
|
263
|
-
});
|
|
264
|
-
}),
|
|
265
|
-
})
|
|
266
|
-
.demandCommand(1, 'Specify a subcommand: list or show'),
|
|
267
|
-
handler: () => {},
|
|
268
|
-
})
|
|
269
|
-
// ── list-items: Legacy alias ──
|
|
270
|
-
.command({
|
|
271
|
-
command: 'list-items',
|
|
272
|
-
aliases: ['ls'],
|
|
273
|
-
describe: 'List catalog items (use "catalog items list" instead)',
|
|
274
|
-
builder: (y) => y
|
|
275
|
-
.option('limit', { alias: 'l', type: 'number', default: 20, describe: 'Max records' })
|
|
276
|
-
.option('category', { type: 'string', describe: 'Filter by category' }),
|
|
277
|
-
handler: wrap(async (argv, app) => {
|
|
278
|
-
let query = 'ORDERBYname';
|
|
279
|
-
if (argv.category) query = `category.titleLIKE${argv.category}^${query}`;
|
|
280
|
-
const params = new URLSearchParams();
|
|
281
|
-
params.set('sysparm_query', query);
|
|
282
|
-
params.set('sysparm_limit', String(argv.limit));
|
|
283
|
-
params.set('sysparm_display_value', 'all');
|
|
284
|
-
params.set('sysparm_fields', 'sys_id,name,short_description,category,active');
|
|
285
|
-
const items = await app.sdk.list('sc_cat_item', params);
|
|
72
|
+
// Fallback — non-TTY
|
|
73
|
+
const p = new URLSearchParams();
|
|
74
|
+
p.set('sysparm_query', query ? `${query}^ORDERBYname` : 'ORDERBYname');
|
|
75
|
+
p.set('sysparm_limit', String(argv.limit));
|
|
76
|
+
p.set('sysparm_display_value', 'all');
|
|
77
|
+
p.set('sysparm_fields', 'name,short_description,category,active');
|
|
78
|
+
const records = await app.sdk.list('sc_cat_item', p);
|
|
286
79
|
app.ok({
|
|
287
80
|
table: 'sc_cat_item',
|
|
288
|
-
count:
|
|
81
|
+
count: records.length,
|
|
289
82
|
columns: ['name', 'short_description', 'category', 'active'],
|
|
290
|
-
records
|
|
291
|
-
sys_id: r.sys_id?.value || r.sys_id,
|
|
292
|
-
name: r.name?.display_value || r.name,
|
|
293
|
-
short_description: r.short_description?.display_value || r.short_description || '',
|
|
294
|
-
category: r.category?.display_value || '',
|
|
295
|
-
active: r.active?.display_value || r.active,
|
|
296
|
-
})),
|
|
83
|
+
records,
|
|
297
84
|
context: { instance_url: app.getEffectiveInstance() },
|
|
298
|
-
}, { summary: `${
|
|
85
|
+
}, { summary: `${records.length} catalog item(s)` });
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
.command({
|
|
89
|
+
command: 'show <id>',
|
|
90
|
+
aliases: ['get'],
|
|
91
|
+
describe: 'Show a catalog item',
|
|
92
|
+
handler: wrap(async (argv, app) => {
|
|
93
|
+
app.requireInstance();
|
|
94
|
+
let id = argv.id;
|
|
95
|
+
if (!/^[a-f0-9]{32}$/i.test(id)) {
|
|
96
|
+
const p = new URLSearchParams();
|
|
97
|
+
p.set('sysparm_query', `name=${id}`);
|
|
98
|
+
p.set('sysparm_limit', '1');
|
|
99
|
+
p.set('sysparm_fields', 'sys_id');
|
|
100
|
+
const results = await app.sdk.list('sc_cat_item', p);
|
|
101
|
+
if (results.length === 0) throw new Error(`Not found: ${id}`);
|
|
102
|
+
id = results[0].sys_id?.value || results[0].sys_id;
|
|
103
|
+
}
|
|
104
|
+
const item = await app.sdk.get('sc_cat_item', id);
|
|
105
|
+
await showItem(app, id);
|
|
299
106
|
}),
|
|
300
107
|
});
|
|
301
108
|
},
|
|
302
|
-
handler: (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
console.log('\nExamples:');
|
|
310
|
-
console.log(' jsn catalog create-item "Request PTO" \\');
|
|
311
|
-
console.log(' --category "Time Off" \\');
|
|
312
|
-
console.log(' --short-description "Submit a PTO request" \\');
|
|
313
|
-
console.log(' --variable "start_date:date:Start Date" \\');
|
|
314
|
-
console.log(' --variable "type:select:Type" \\');
|
|
315
|
-
console.log(' --variable "reason:multilinetext:Reason/Notes"');
|
|
316
|
-
console.log('');
|
|
317
|
-
console.log(' # Submit a request via submit_produce:');
|
|
318
|
-
console.log(' jsn catalog create-item "Minecraft Coins" \\');
|
|
319
|
-
console.log(' --variables \'{"quantity":"100","reason":"Server upgrade"}\'');
|
|
320
|
-
console.log('\nVariable types: string, multilinetext, select, date, datetime, reference, checkbox, email');
|
|
321
|
-
console.log('\nRun "jsn catalog <command> --help" for details.');
|
|
322
|
-
}
|
|
109
|
+
handler: () => {
|
|
110
|
+
console.log('Service Catalog management.');
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log('Commands:');
|
|
113
|
+
console.log(' create Create a catalog item');
|
|
114
|
+
console.log(' list Browse catalog items');
|
|
115
|
+
console.log(' show <id> Show catalog item');
|
|
323
116
|
},
|
|
324
117
|
};
|
|
325
118
|
}
|
|
119
|
+
|
|
120
|
+
async function showItem(app, sysID) {
|
|
121
|
+
const item = await app.sdk.get('sc_cat_item', sysID);
|
|
122
|
+
|
|
123
|
+
// Fetch variables
|
|
124
|
+
const vp = new URLSearchParams();
|
|
125
|
+
vp.set('sysparm_query', `cat_item=${sysID}^active=true`);
|
|
126
|
+
vp.set('sysparm_limit', '100');
|
|
127
|
+
vp.set('sysparm_display_value', 'all');
|
|
128
|
+
vp.set('sysparm_fields', 'sys_id,name,question_text,type,order,mandatory,variable_set');
|
|
129
|
+
const variables = await app.sdk.list('item_option_new', vp);
|
|
130
|
+
|
|
131
|
+
const standalone = [];
|
|
132
|
+
const setVars = new Map();
|
|
133
|
+
for (const v of variables) {
|
|
134
|
+
const vs = getStringField(v, 'variable_set');
|
|
135
|
+
const entry = {
|
|
136
|
+
name: getStringField(v, 'name'),
|
|
137
|
+
question_text: getStringField(v, 'question_text'),
|
|
138
|
+
type: getStringField(v, 'type'),
|
|
139
|
+
mandatory: getStringField(v, 'mandatory'),
|
|
140
|
+
};
|
|
141
|
+
if (vs) {
|
|
142
|
+
if (!setVars.has(vs)) setVars.set(vs, { name: vs, variables: [] });
|
|
143
|
+
setVars.get(vs).variables.push(entry);
|
|
144
|
+
} else {
|
|
145
|
+
standalone.push(entry);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const totalVars = standalone.length + Array.from(setVars.values()).reduce((a, s) => a + s.variables.length, 0);
|
|
150
|
+
|
|
151
|
+
const lines = [];
|
|
152
|
+
lines.push(getStringField(item, 'name'));
|
|
153
|
+
if (getStringField(item, 'short_description')) lines.push(` ${getStringField(item, 'short_description')}`);
|
|
154
|
+
lines.push(` Category: ${getStringField(item, 'category') || '(none)'}`);
|
|
155
|
+
lines.push(` Active: ${getStringField(item, 'active') || 'false'}`);
|
|
156
|
+
lines.push(` URL: ${app.getEffectiveInstance()}/sc_cat_item.do?sys_id=${sysID}`);
|
|
157
|
+
|
|
158
|
+
// Flow / Workflow / Execution Plan
|
|
159
|
+
const flowName = getStringField(item, 'flow_designer_flow');
|
|
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
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(lines.join('\n'));
|
|
212
|
+
}
|