@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,7 @@
|
|
|
1
1
|
// Custom scraPI command: list/show individual operations + one-shot API definition creation
|
|
2
2
|
//
|
|
3
3
|
// The 'create' subcommand lets you build a full Scripted REST API in one call:
|
|
4
|
-
// jsn
|
|
4
|
+
// jsn scrapi create \
|
|
5
5
|
// --name "SL Managed Services" --service-id managed_services --namespace slapi \
|
|
6
6
|
// --resource create=POST:/create:./create.js \
|
|
7
7
|
// --resource comment=POST:/comment:./comment.js
|
|
@@ -74,7 +74,7 @@ export function scrapiCmd(wrap) {
|
|
|
74
74
|
builder: (y) => y
|
|
75
75
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident")' })
|
|
76
76
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns' })
|
|
77
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
77
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
78
78
|
handler: wrap(async (argv, app) => {
|
|
79
79
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'sys_ws_definition', 'http_method', 'relative_path', 'sys_scope'];
|
|
80
80
|
const query = argv.query || '';
|
|
@@ -97,8 +97,8 @@ export function scrapiCmd(wrap) {
|
|
|
97
97
|
}, {
|
|
98
98
|
summary: `${records.length} operation(s)`,
|
|
99
99
|
breadcrumbs: [
|
|
100
|
-
{ action: 'create', cmd: 'jsn
|
|
101
|
-
{ action: 'show', cmd: 'jsn
|
|
100
|
+
{ action: 'create', cmd: 'jsn scrapi create --name "My API" --service-id my-api --namespace myapp --resource get=GET:/items:./script.js', description: 'Create a new REST API' },
|
|
101
|
+
{ action: 'show', cmd: 'jsn scrapi show <name>', description: 'Show operation details' },
|
|
102
102
|
],
|
|
103
103
|
});
|
|
104
104
|
}),
|
|
@@ -128,7 +128,7 @@ export function scrapiCmd(wrap) {
|
|
|
128
128
|
return app.ok(records[0], {
|
|
129
129
|
summary: `Operation: ${getStringField(records[0], 'name') || id}`,
|
|
130
130
|
breadcrumbs: [
|
|
131
|
-
{ action: 'list', cmd: 'jsn
|
|
131
|
+
{ action: 'list', cmd: 'jsn scrapi list', description: 'Back to all operations' },
|
|
132
132
|
],
|
|
133
133
|
});
|
|
134
134
|
}
|
|
@@ -182,7 +182,7 @@ export function scrapiCmd(wrap) {
|
|
|
182
182
|
formatted += ` ${(method + ' ').slice(0, 7)} ${relPath.padEnd(30)} ${opName}${active !== 'true' ? ' (inactive)' : ''}\n`;
|
|
183
183
|
}
|
|
184
184
|
} else {
|
|
185
|
-
formatted += `\n (no resources — run jsn
|
|
185
|
+
formatted += `\n (no resources — run jsn scrapi create --resource ... to add some)\n`;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
app.ok({
|
|
@@ -207,8 +207,8 @@ export function scrapiCmd(wrap) {
|
|
|
207
207
|
}, {
|
|
208
208
|
summary: `API: ${defName} — ${childOps.length} resource(s)`,
|
|
209
209
|
breadcrumbs: [
|
|
210
|
-
{ action: 'create', cmd: 'jsn
|
|
211
|
-
{ action: 'list', cmd: 'jsn
|
|
210
|
+
{ action: 'create', cmd: 'jsn scrapi create --name "..." --service-id ... --namespace ... --resource ...', description: 'Create a new API' },
|
|
211
|
+
{ action: 'list', cmd: 'jsn scrapi list', description: 'Back to all operations' },
|
|
212
212
|
],
|
|
213
213
|
});
|
|
214
214
|
}),
|
|
@@ -316,8 +316,8 @@ export function scrapiCmd(wrap) {
|
|
|
316
316
|
summary: `Created API "${apiName}" with ${operations.length} operation(s)`,
|
|
317
317
|
notice: `View API: ${apiURL}`,
|
|
318
318
|
breadcrumbs: [
|
|
319
|
-
{ action: 'show', cmd: `jsn
|
|
320
|
-
{ action: 'add', cmd: `jsn
|
|
319
|
+
{ action: 'show', cmd: `jsn scrapi list --query "web_service_definition=${defSysId}"`, description: 'List operations for this API' },
|
|
320
|
+
{ action: 'add', cmd: `jsn scrapi add-resource --api "${serviceId}" --name <name>=METHOD:/path:./script.js`, description: 'Add another resource' },
|
|
321
321
|
],
|
|
322
322
|
});
|
|
323
323
|
}),
|
|
@@ -333,9 +333,9 @@ export function scrapiCmd(wrap) {
|
|
|
333
333
|
console.log(' create Create a new API with operations (one-shot)');
|
|
334
334
|
console.log('');
|
|
335
335
|
console.log('Examples:');
|
|
336
|
-
console.log(' jsn
|
|
337
|
-
console.log(' jsn
|
|
338
|
-
console.log(' jsn
|
|
336
|
+
console.log(' jsn scrapi list');
|
|
337
|
+
console.log(' jsn scrapi show my_operation');
|
|
338
|
+
console.log(' jsn scrapi create \\');
|
|
339
339
|
console.log(' --name "My API" --service-id my-api --namespace myapp \\');
|
|
340
340
|
console.log(' --resource "create=POST:/create:./create.js" \\');
|
|
341
341
|
console.log(' --resource "list=GET:/list:./list.js"');
|
|
@@ -15,7 +15,7 @@ export function updateSetsCmd(wrap) {
|
|
|
15
15
|
builder: (y) => y
|
|
16
16
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
17
17
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
18
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
18
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
19
19
|
handler: wrap(async (argv, app) => {
|
|
20
20
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'state', 'application'];
|
|
21
21
|
const query = argv.query || '';
|
|
@@ -24,6 +24,7 @@ export function updateSetsCmd(wrap) {
|
|
|
24
24
|
app, table: 'sys_update_set', singular: 'update set', columns, limit: argv.limit, query, labelField: 'name',
|
|
25
25
|
formatLabel: r => `${getStringField(r, 'name')} [${getStringField(r, 'state') || '?'}]`,
|
|
26
26
|
});
|
|
27
|
+
if (picked === undefined) return; // user cancelled
|
|
27
28
|
if (picked) {
|
|
28
29
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'sys_update_set' };
|
|
29
30
|
return app.ok(picked, { summary: `Update set: ${getStringField(picked, 'name')}` });
|
|
@@ -151,12 +152,12 @@ export function updateSetsCmd(wrap) {
|
|
|
151
152
|
breadcrumbs: [
|
|
152
153
|
{
|
|
153
154
|
action: 'set',
|
|
154
|
-
cmd: `jsn
|
|
155
|
+
cmd: `jsn updatesets set "${argv.name}"`,
|
|
155
156
|
description: 'Switch to this update set',
|
|
156
157
|
},
|
|
157
158
|
{
|
|
158
159
|
action: 'complete',
|
|
159
|
-
cmd: `jsn
|
|
160
|
+
cmd: `jsn updatesets complete "${argv.name}"`,
|
|
160
161
|
description: 'Mark as complete when done',
|
|
161
162
|
},
|
|
162
163
|
],
|
|
@@ -225,9 +226,9 @@ export function updateSetsCmd(wrap) {
|
|
|
225
226
|
console.log(' export <name> Export an update set to XML');
|
|
226
227
|
console.log(' complete <name> Mark an update set as complete (coming soon)');
|
|
227
228
|
console.log(' yolo Silence the "Default update set" warning');
|
|
228
|
-
console.log('\nRun "jsn
|
|
229
|
+
console.log('\nRun "jsn updatesets <command> --help" for details.');
|
|
229
230
|
console.log('\nTip: Create an update set first:');
|
|
230
|
-
console.log(' jsn
|
|
231
|
+
console.log(' jsn updatesets create --name "My Feature"');
|
|
231
232
|
console.log(' # → Auto-set as current, then record changes are captured.');
|
|
232
233
|
}
|
|
233
234
|
},
|
|
@@ -148,6 +148,15 @@ export function groupMembersCmd(wrap) {
|
|
|
148
148
|
}),
|
|
149
149
|
});
|
|
150
150
|
},
|
|
151
|
-
handler: () => {
|
|
151
|
+
handler: () => {
|
|
152
|
+
console.log('Manage group memberships in sys_user_grmember.');
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('Available subcommands:');
|
|
155
|
+
console.log(' list List group members');
|
|
156
|
+
console.log(' add Add a user to a group');
|
|
157
|
+
console.log(' remove Remove a user from a group');
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log('Run "jsn groupmembers <command> --help" for details.');
|
|
160
|
+
},
|
|
152
161
|
};
|
|
153
162
|
}
|
|
@@ -148,6 +148,15 @@ export function groupRolesCmd(wrap) {
|
|
|
148
148
|
}),
|
|
149
149
|
});
|
|
150
150
|
},
|
|
151
|
-
handler: () => {
|
|
151
|
+
handler: () => {
|
|
152
|
+
console.log('Manage group roles in sys_user_has_role.');
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('Available subcommands:');
|
|
155
|
+
console.log(' list List group roles');
|
|
156
|
+
console.log(' add Add a role to a group');
|
|
157
|
+
console.log(' remove Remove a role from a group');
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log('Run "jsn grouproles <command> --help" for details.');
|
|
160
|
+
},
|
|
152
161
|
};
|
|
153
162
|
}
|
package/src/commands/groups.js
CHANGED
|
@@ -137,6 +137,17 @@ export function groupsCmd(wrap) {
|
|
|
137
137
|
}),
|
|
138
138
|
});
|
|
139
139
|
},
|
|
140
|
-
handler: () => {
|
|
140
|
+
handler: () => {
|
|
141
|
+
console.log('Manage ServiceNow groups from the sys_user_group table.');
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log('Available subcommands:');
|
|
144
|
+
console.log(' list List groups');
|
|
145
|
+
console.log(' show <name or sys_id> Show a group');
|
|
146
|
+
console.log(' create Create a new group');
|
|
147
|
+
console.log(' update <identifier> Update a group');
|
|
148
|
+
console.log(' delete <identifier> Delete a group');
|
|
149
|
+
console.log('');
|
|
150
|
+
console.log('Run "jsn groups <command> --help" for details.');
|
|
151
|
+
},
|
|
141
152
|
};
|
|
142
153
|
}
|
package/src/commands/records.js
CHANGED
|
@@ -74,7 +74,7 @@ export function recordsCmd(wrap) {
|
|
|
74
74
|
const breadcrumbs = [
|
|
75
75
|
{ action: 'create', cmd: `jsn records create --table ${table} --data '{...}'`, description: 'Create a new record' },
|
|
76
76
|
{ action: 'filter', cmd: `jsn records list --table ${table} --query "priority=1"`, description: 'Filter: priority 1 only' },
|
|
77
|
-
{ action: 'columns', cmd: `jsn
|
|
77
|
+
{ action: 'columns', cmd: `jsn columns --table ${table}`, description: 'View available columns' },
|
|
78
78
|
];
|
|
79
79
|
if (argv['sys-id']) {
|
|
80
80
|
breadcrumbs.unshift({
|
|
@@ -188,12 +188,12 @@ export function recordsCmd(wrap) {
|
|
|
188
188
|
const breadcrumbs = [
|
|
189
189
|
...result.flows.map(f => ({
|
|
190
190
|
action: 'show',
|
|
191
|
-
cmd: `jsn
|
|
191
|
+
cmd: `jsn flows show "${f.flow}"`,
|
|
192
192
|
description: `View flow details for ${f.flow}`,
|
|
193
193
|
})),
|
|
194
194
|
...result.businessRules.slice(0, 5).map(br => ({
|
|
195
195
|
action: 'show',
|
|
196
|
-
cmd: `jsn
|
|
196
|
+
cmd: `jsn rules show "${br.name}"`,
|
|
197
197
|
description: `View business rule: ${br.name}`,
|
|
198
198
|
})),
|
|
199
199
|
];
|
|
@@ -205,6 +205,18 @@ export function recordsCmd(wrap) {
|
|
|
205
205
|
})
|
|
206
206
|
|
|
207
207
|
},
|
|
208
|
-
handler: () => {
|
|
208
|
+
handler: () => {
|
|
209
|
+
console.log('Query and manage records in any ServiceNow table.');
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log('Available subcommands:');
|
|
212
|
+
console.log(' list List records from a table');
|
|
213
|
+
console.log(' get Get a single record by sys_id');
|
|
214
|
+
console.log(' create Create a record');
|
|
215
|
+
console.log(' update Update a record');
|
|
216
|
+
console.log(' delete Delete a record');
|
|
217
|
+
console.log(' inspect Inspect table schema and statistics');
|
|
218
|
+
console.log('');
|
|
219
|
+
console.log('Run "jsn records <command> --help" for details.');
|
|
220
|
+
},
|
|
209
221
|
};
|
|
210
222
|
}
|
package/src/commands/skill.js
CHANGED
|
@@ -230,6 +230,16 @@ export function skillCmd(wrap) {
|
|
|
230
230
|
}),
|
|
231
231
|
});
|
|
232
232
|
},
|
|
233
|
-
handler: () => {
|
|
233
|
+
handler: () => {
|
|
234
|
+
console.log('Manage the jsn AI agent skill file.');
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log('Available subcommands:');
|
|
237
|
+
console.log(' show Show the bundled skill file');
|
|
238
|
+
console.log(' fetch Download the latest skill from GitHub');
|
|
239
|
+
console.log(' path Show skill file locations');
|
|
240
|
+
console.log(' install Install skill file to agents directory');
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log('Run "jsn skill <command> --help" for details.');
|
|
243
|
+
},
|
|
234
244
|
};
|
|
235
245
|
}
|
package/src/commands/tickets.js
CHANGED
|
@@ -117,6 +117,17 @@ export function ticketsCmd(wrap) {
|
|
|
117
117
|
})
|
|
118
118
|
|
|
119
119
|
},
|
|
120
|
-
handler: () => {
|
|
120
|
+
handler: () => {
|
|
121
|
+
console.log('Query generic tickets from the ticket table.');
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log('Available subcommands:');
|
|
124
|
+
console.log(' list List tickets');
|
|
125
|
+
console.log(' show <number or sys_id> Show a ticket');
|
|
126
|
+
console.log(' create Create a new ticket');
|
|
127
|
+
console.log(' update <identifier> Update a ticket');
|
|
128
|
+
console.log(' delete <identifier> Delete a ticket');
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log('Run "jsn tickets <command> --help" for details.');
|
|
131
|
+
},
|
|
121
132
|
};
|
|
122
133
|
}
|
package/src/commands/users.js
CHANGED
|
@@ -140,6 +140,17 @@ export function usersCmd(wrap) {
|
|
|
140
140
|
}),
|
|
141
141
|
});
|
|
142
142
|
},
|
|
143
|
-
handler: () => {
|
|
143
|
+
handler: () => {
|
|
144
|
+
console.log('Manage ServiceNow users from the sys_user table.');
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log('Available subcommands:');
|
|
147
|
+
console.log(' list List users');
|
|
148
|
+
console.log(' show <name or sys_id> Show a user by user_name or sys_id');
|
|
149
|
+
console.log(' create Create a new user');
|
|
150
|
+
console.log(' update <identifier> Update a user');
|
|
151
|
+
console.log(' delete <identifier> Delete a user');
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log('Run "jsn users <command> --help" for details.');
|
|
154
|
+
},
|
|
144
155
|
};
|
|
145
156
|
}
|
package/src/help.js
CHANGED
|
@@ -1,47 +1,140 @@
|
|
|
1
1
|
// Custom grouped help renderer
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
const COMMAND_GROUPS = {
|
|
5
|
-
'CORE COMMANDS': [
|
|
6
|
-
{ name: 'incidents', alias: 'inc', desc: 'Manage incidents' },
|
|
7
|
-
{ name: 'changes', alias: 'chg', desc: 'Manage change requests' },
|
|
8
|
-
{ name: 'requests', alias: 'req', desc: 'Manage service catalog requests' },
|
|
9
|
-
{ name: 'tasks', alias: 'task', desc: 'Manage catalog tasks' },
|
|
10
|
-
{ name: 'tickets', alias: 'ticket', desc: 'Query generic tickets' },
|
|
11
|
-
],
|
|
12
|
-
'DATA & ADMIN': [
|
|
13
|
-
{ name: 'records', desc: 'Generic Table API for any table' },
|
|
14
|
-
{ name: 'users', alias: 'user', desc: 'Manage ServiceNow users' },
|
|
15
|
-
{ name: 'groups', alias: 'group', desc: 'Manage groups' },
|
|
16
|
-
{ name: 'groupmembers', alias: 'gm', desc: 'Manage group memberships' },
|
|
17
|
-
{ name: 'grouproles', alias: 'gr', desc: 'Manage group roles' },
|
|
18
|
-
],
|
|
19
|
-
'DEVELOPMENT': [
|
|
20
|
-
{ name: 'dev', desc: 'Manage development artifacts (flows, includes, rules, ACLs, etc.)' },
|
|
21
|
-
],
|
|
22
|
-
'CONFIGURATION': [
|
|
23
|
-
{ name: 'setup', desc: 'Interactive first-time setup' },
|
|
24
|
-
{ name: 'auth', desc: 'Manage OAuth authentication' },
|
|
25
|
-
{ name: 'profiles', alias: 'profile', desc: 'Manage instance profiles' },
|
|
26
|
-
],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const UTILITY_COMMANDS = [
|
|
30
|
-
{ name: 'skill', desc: 'Manage the jsn AI agent skill file (show, fetch, install)' },
|
|
31
|
-
{ name: 'version', desc: 'Show version information (--check for npm updates)' },
|
|
32
|
-
];
|
|
2
|
+
// Organized by ServiceNow domain model with subgroup dividers
|
|
33
3
|
|
|
34
4
|
function renderGroup(name, commands) {
|
|
35
5
|
const lines = [`\n${name}`];
|
|
36
6
|
lines.push('─'.repeat(50));
|
|
37
7
|
for (const cmd of commands) {
|
|
8
|
+
if (cmd.divider) {
|
|
9
|
+
lines.push('');
|
|
10
|
+
lines.push(` ${cmd.divider}`);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
38
13
|
const aliasPart = cmd.alias ? ` (${cmd.alias})` : '';
|
|
39
14
|
const padded = ` jsn ${cmd.name}`.padEnd(22);
|
|
40
|
-
|
|
15
|
+
const status = cmd.status ? ` ${cmd.status}` : '';
|
|
16
|
+
lines.push(`${padded}${cmd.desc}${aliasPart}${status}`);
|
|
41
17
|
}
|
|
42
18
|
return lines.join('\n');
|
|
43
19
|
}
|
|
44
20
|
|
|
21
|
+
const CORE_COMMANDS = [
|
|
22
|
+
{ name: 'incidents', alias: 'inc', desc: 'Manage incidents' },
|
|
23
|
+
{ name: 'changes', alias: 'chg', desc: 'Manage change requests' },
|
|
24
|
+
{ name: 'requests', alias: 'req', desc: 'Manage service catalog requests' },
|
|
25
|
+
{ name: 'tasks', alias: 'task', desc: 'Manage catalog tasks' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const AUTOMATION_COMMANDS = [
|
|
29
|
+
{ divider: '── Async ──' },
|
|
30
|
+
{ name: 'flows', alias: 'flow', desc: 'Flow Designer flows' },
|
|
31
|
+
{ name: 'actions', alias: 'action', desc: 'Flow action definitions' },
|
|
32
|
+
{ name: 'scriptactions', desc: 'Script actions' },
|
|
33
|
+
{ name: 'scheduledjobs', desc: 'Scheduled jobs' },
|
|
34
|
+
{ name: 'triggers', desc: 'Event triggers' },
|
|
35
|
+
{ name: 'asyncrules', desc: 'Asynchronous business rules' },
|
|
36
|
+
|
|
37
|
+
{ divider: '── In Memory ──' },
|
|
38
|
+
{ name: 'rules', alias: 'br', desc: 'Business rules (create, update, delete)' },
|
|
39
|
+
{ name: 'workflows', desc: 'Workflow definitions' },
|
|
40
|
+
{ name: 'decisiontables', desc: 'Decision tables' },
|
|
41
|
+
{ name: 'assignments', desc: 'Assignment rules' },
|
|
42
|
+
|
|
43
|
+
{ divider: '── Inbound ──' },
|
|
44
|
+
{ name: 'scrapi', desc: 'Scripted REST APIs' },
|
|
45
|
+
{ name: 'email', desc: 'Inbound email actions' },
|
|
46
|
+
|
|
47
|
+
{ divider: '── Outbound ──' },
|
|
48
|
+
{ name: 'restmessage', alias: 'rm', desc: 'REST Messages (sys_rest_message)' },
|
|
49
|
+
{ name: 'restmethods', desc: 'REST Message methods (sys_rest_message_fn)' },
|
|
50
|
+
{ name: 'soapmessages', desc: 'SOAP Messages' },
|
|
51
|
+
{ name: 'soapfunctions', desc: 'SOAP Message functions' },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const ACCESS_COMMANDS = [
|
|
55
|
+
{ name: 'acls', alias: 'acl', desc: 'Access controls' },
|
|
56
|
+
{ name: 'b4rules', alias: 'b4r', desc: 'Before-query business rules (see also Automation)' },
|
|
57
|
+
{ name: 'roles', alias: 'role', desc: 'Roles' },
|
|
58
|
+
{ name: 'privileges', alias: 'priv', desc: 'Scope privileges' },
|
|
59
|
+
{ name: 'securitytypes', alias: 'st', desc: 'Custom security types' },
|
|
60
|
+
{ name: 'aliases', alias: 'als', desc: 'Connection & credential aliases' },
|
|
61
|
+
{ name: 'groups', alias: 'group', desc: 'Manage groups' },
|
|
62
|
+
{ name: 'groupmembers', alias: 'gm', desc: 'Group memberships' },
|
|
63
|
+
{ name: 'grouproles', alias: 'gr', desc: 'Group roles' },
|
|
64
|
+
{ name: 'users', alias: 'user', desc: 'Manage users' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const UX_COMMANDS = [
|
|
68
|
+
{ divider: '── Shared (across all experiences) ──' },
|
|
69
|
+
{ name: 'forms', desc: 'Form layouts' },
|
|
70
|
+
{ name: 'lists', desc: 'List layouts' },
|
|
71
|
+
{ name: 'clientscripts', alias: 'cs', desc: 'Client scripts' },
|
|
72
|
+
{ name: 'uipolicies', alias: 'up', desc: 'UI policies' },
|
|
73
|
+
{ name: 'uiactions', alias: 'ua', desc: 'UI actions (buttons, links, context menus)' },
|
|
74
|
+
{ name: 'views', alias: 'vw', desc: 'Views' },
|
|
75
|
+
{ name: 'catalogitems', alias: 'ci', desc: 'Service Catalog items + variables' },
|
|
76
|
+
{ name: 'catalogscripts', desc: 'Catalog client scripts' },
|
|
77
|
+
{ name: 'cataloguipolicies', alias: 'cup', desc: 'Catalog UI policies' },
|
|
78
|
+
|
|
79
|
+
{ divider: '── Core UI ──' },
|
|
80
|
+
{ name: 'uipages', desc: 'UI pages' },
|
|
81
|
+
{ name: 'uimacros', desc: 'UI macros' },
|
|
82
|
+
{ name: 'listcontrols', alias: 'lc', desc: 'List controls' },
|
|
83
|
+
|
|
84
|
+
{ divider: '── Service Portal ──' },
|
|
85
|
+
{ name: 'sppages', desc: 'Service Portal pages' },
|
|
86
|
+
{ name: 'spwidgets', desc: 'Service Portal widgets' },
|
|
87
|
+
|
|
88
|
+
{ divider: '── Next Experience (Workspaces) ──' },
|
|
89
|
+
{ name: 'uxscripts', alias: 'ux', desc: 'UX source scripts' },
|
|
90
|
+
{ name: 'uxlists', desc: 'Workspace lists (sys_ux_list)' },
|
|
91
|
+
{ name: 'uxapplicability', desc: 'Workspace applicability (m2m)' },
|
|
92
|
+
|
|
93
|
+
{ divider: '── Navigation ──' },
|
|
94
|
+
{ name: 'appmenu', desc: 'Application menus' },
|
|
95
|
+
{ name: 'appmodules', alias: 'am', desc: 'Application modules' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const DATA_COMMANDS = [
|
|
99
|
+
{ divider: '── DB Schema ──' },
|
|
100
|
+
{ name: 'tables', alias: 't', desc: 'Table definitions' },
|
|
101
|
+
{ name: 'columns', alias: 'col', desc: 'Column definitions' },
|
|
102
|
+
{ name: 'relationships', alias: 'rel', desc: 'Table relationships' },
|
|
103
|
+
|
|
104
|
+
{ divider: '── Shared Code, Transforms, Logs ──' },
|
|
105
|
+
{ name: 'includes', alias: 'si', desc: 'Script includes' },
|
|
106
|
+
{ name: 'import', desc: 'Import sets' },
|
|
107
|
+
{ name: 'logs', desc: 'System logs' },
|
|
108
|
+
{ name: 'properties', alias: 'prop', desc: 'System properties' },
|
|
109
|
+
|
|
110
|
+
{ name: 'records', desc: 'Generic Table API (any table)' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const CONFIG_COMMANDS = [
|
|
114
|
+
{ name: 'setup', desc: 'Interactive first-time setup' },
|
|
115
|
+
{ name: 'auth', desc: 'OAuth authentication' },
|
|
116
|
+
{ name: 'profiles', alias: 'profile', desc: 'Instance profiles' },
|
|
117
|
+
{ name: 'updatesets', desc: 'Update sets (set, yolo, export, import)' },
|
|
118
|
+
{ name: 'scopes', desc: 'Application scopes' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const DEVELOPER_COMMANDS = [
|
|
122
|
+
{ name: 'eval', desc: 'Run server-side JavaScript' },
|
|
123
|
+
{ name: 'rest', desc: 'Raw REST API calls' },
|
|
124
|
+
{ name: 'skill', desc: 'AI agent skill file (show, fetch, install)' },
|
|
125
|
+
{ name: 'version', desc: 'Version info (--check for npm updates)' },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const COMMAND_GROUPS = {
|
|
129
|
+
'CORE': CORE_COMMANDS,
|
|
130
|
+
'AUTOMATION': AUTOMATION_COMMANDS,
|
|
131
|
+
'ACCESS': ACCESS_COMMANDS,
|
|
132
|
+
'USER EXPERIENCE': UX_COMMANDS,
|
|
133
|
+
'DATA': DATA_COMMANDS,
|
|
134
|
+
'CONFIGURATION': CONFIG_COMMANDS,
|
|
135
|
+
'DEVELOPER': DEVELOPER_COMMANDS,
|
|
136
|
+
};
|
|
137
|
+
|
|
45
138
|
function renderFlags() {
|
|
46
139
|
return `
|
|
47
140
|
FLAGS
|
|
@@ -67,7 +160,6 @@ LEARN MORE
|
|
|
67
160
|
|
|
68
161
|
export function renderHelp() {
|
|
69
162
|
const sections = Object.entries(COMMAND_GROUPS).map(([name, cmds]) => renderGroup(name, cmds));
|
|
70
|
-
const utilitySection = renderGroup('UTILITY', UTILITY_COMMANDS);
|
|
71
163
|
|
|
72
164
|
return [
|
|
73
165
|
'Usage: jsn <command> [options]',
|
|
@@ -76,8 +168,6 @@ export function renderHelp() {
|
|
|
76
168
|
'',
|
|
77
169
|
...sections,
|
|
78
170
|
'',
|
|
79
|
-
utilitySection,
|
|
80
|
-
'',
|
|
81
171
|
renderFlags(),
|
|
82
172
|
renderTips(),
|
|
83
173
|
'',
|
package/src/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Shared helper utilities
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import { paginatedSearch } from './paginated-search.js';
|
|
5
5
|
import { isTTY, FormatAuto } from './output.js';
|
|
6
6
|
|
|
7
7
|
export function getStringField(record, field) {
|
|
@@ -89,53 +89,69 @@ export function resolveFieldsParam(columns) {
|
|
|
89
89
|
* @param {string} opts.labelField — field used to match selection (default: 'name')
|
|
90
90
|
* @returns {Promise<void>|null} null if no selection made or non-interactive
|
|
91
91
|
*/
|
|
92
|
-
|
|
92
|
+
function vowelArticle(word) {
|
|
93
|
+
const first = word.charAt(0).toLowerCase();
|
|
94
|
+
return first === 'a' || first === 'e' || first === 'i' || first === 'o' || first === 'u' ? 'an' : 'a';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function interactiveList({ app, table, singular, columns, limit = 50, query = '', formatLabel, labelField = 'name' }) {
|
|
98
|
+
app.requireInstance();
|
|
93
99
|
const effectiveFormat = app.output.getFormat() === FormatAuto ? (isTTY(process.stdout) ? FormatAuto : FormatAuto) : app.output.getFormat();
|
|
94
|
-
if (effectiveFormat !== FormatAuto || !isTTY(process.stdout) || !isTTY(process.stdin)
|
|
100
|
+
if (effectiveFormat !== FormatAuto || !isTTY(process.stdout) || !isTTY(process.stdin)) {
|
|
95
101
|
return null; // not interactive — caller should fall back to text/table
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
const pickerColumns = ['sys_id', labelField, ...columns.filter(c => c !== labelField && c !== 'sys_id' && c !== '*')];
|
|
99
|
-
const params = new URLSearchParams();
|
|
100
|
-
params.set('sysparm_limit', String(limit));
|
|
101
|
-
params.set('sysparm_display_value', 'all');
|
|
102
105
|
const pickerFields = pickerColumns.join(',');
|
|
103
|
-
if (pickerFields) params.set('sysparm_fields', pickerFields);
|
|
104
|
-
params.set('sysparm_query', 'ORDERBYDESCsys_updated_on');
|
|
105
|
-
|
|
106
|
-
const records = await app.sdk.list(table, params);
|
|
107
|
-
if (records.length === 0) return null;
|
|
108
|
-
|
|
109
|
-
const choices = records.map(r => ({
|
|
110
|
-
name: formatLabel ? formatLabel(r) : (getStringField(r, labelField) || getStringField(r, 'sys_id')),
|
|
111
|
-
value: r,
|
|
112
|
-
}));
|
|
113
106
|
|
|
107
|
+
// Get total count
|
|
108
|
+
let totalCount = 0;
|
|
114
109
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
110
|
+
totalCount = await app.sdk.aggregateCount(table, '');
|
|
111
|
+
} catch {
|
|
112
|
+
totalCount = 0;
|
|
113
|
+
}
|
|
114
|
+
if (totalCount === 0) return null;
|
|
115
|
+
|
|
116
|
+
// Paginated source adapter: (term, offset, { signal }) => choices[]
|
|
117
|
+
async function serverSource(term, offset, { signal } = {}) {
|
|
118
|
+
try {
|
|
119
|
+
const params = new URLSearchParams();
|
|
120
|
+
params.set('sysparm_limit', String(limit));
|
|
121
|
+
params.set('sysparm_display_value', 'all');
|
|
122
|
+
if (pickerFields) params.set('sysparm_fields', pickerFields);
|
|
123
|
+
if (term) {
|
|
124
|
+
params.set('sysparm_query', `${labelField}LIKE${term}^ORDERBYDESCsys_updated_on`);
|
|
125
|
+
} else {
|
|
126
|
+
params.set('sysparm_query', 'ORDERBYDESCsys_updated_on');
|
|
127
|
+
params.set('sysparm_offset', String(offset));
|
|
128
|
+
}
|
|
129
|
+
const records = await app.sdk.list(table, params);
|
|
130
|
+
if (!Array.isArray(records)) {
|
|
131
|
+
console.error('SDK non-array:', typeof records);
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return records.map(r => ({
|
|
135
|
+
name: formatLabel ? formatLabel(r) : (getStringField(r, labelField) || getStringField(r, 'sys_id')),
|
|
136
|
+
value: r,
|
|
137
|
+
}));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.error('serverSource error:', err.message || err);
|
|
140
|
+
return [];
|
|
127
141
|
}
|
|
128
|
-
throw err;
|
|
129
142
|
}
|
|
130
|
-
}
|
|
131
143
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
const selected = await paginatedSearch({
|
|
145
|
+
message: `Select ${singular === 'flow' ? 'a flow' : `a ${singular}`}`,
|
|
146
|
+
pageSize: 10,
|
|
147
|
+
totalCount,
|
|
148
|
+
source: serverSource,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return selected?.value; // unwrap {name, value} → raw record, undefined on cancel
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
/**
|
|
138
|
-
* Known derived (read-only) ServiceNow fields that should not be set directly.
|
|
139
155
|
* Maps table name → array of field names that are computed by the platform.
|
|
140
156
|
* When a create/update payload contains these, a warning is emitted.
|
|
141
157
|
*/
|