@jacebenson/jsn 4.3.7 → 4.3.8-dev.47
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 +266 -1
- package/package.json +1 -1
- package/skills/servicenow/SKILL.md +16 -0
- package/src/app.js +8 -1
- package/src/auth.js +14 -12
- package/src/cli-adapter.js +162 -11
- package/src/cli.js +121 -13
- package/src/commands/_generic.js +3 -2
- package/src/commands/_simple.js +1 -1
- package/src/commands/actions.js +156 -0
- package/src/commands/auth.js +21 -11
- package/src/commands/cmdb.js +18 -0
- package/src/commands/diff.js +205 -0
- package/src/commands/docs/docs.js +18 -16
- package/src/commands/flows.js +149 -47
- package/src/commands/graphql.js +95 -0
- package/src/commands/groupmembers.js +19 -4
- package/src/commands/grouproles.js +15 -4
- package/src/commands/midservers.js +708 -0
- package/src/commands/perf.js +36 -9
- package/src/commands/platform.js +202 -0
- package/src/commands/records.js +19 -15
- package/src/commands/skill.js +5 -2
- package/src/commands/transactions.js +78 -0
- package/src/commands/updatesets.js +77 -11
- package/src/errors.js +71 -6
- package/src/flow-create.js +884 -0
- package/src/flow-designer-interactive.js +417 -0
- package/src/flow-inspection.js +15 -5
- package/src/flow-publish.js +32 -0
- package/src/flow-test.js +122 -0
- package/src/flow-triggers.js +448 -0
- package/src/flow-versioning.js +53 -0
- package/src/help.js +93 -28
- package/src/helpers.js +124 -9
- package/src/midserver-direct.js +312 -0
- package/src/midserver-install.js +170 -0
- package/src/midserver-zip.js +107 -0
- package/src/output.js +7 -0
- package/src/processflow-actions.js +366 -0
- package/src/sdk.js +8 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { declareCapabilities } from '../capabilities.js';
|
|
3
|
+
|
|
4
|
+
// GraphQL documents may contain mutations. Gate the whole command so normal
|
|
5
|
+
// instance/auth and read-only protections apply without attempting AST parsing.
|
|
6
|
+
declareCapabilities('graphql', { mutationSubcommands: [''] });
|
|
7
|
+
|
|
8
|
+
function readStdin() {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
let data = '';
|
|
11
|
+
process.stdin.setEncoding('utf-8');
|
|
12
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
13
|
+
process.stdin.on('end', () => resolve(data));
|
|
14
|
+
process.stdin.on('error', reject);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function resolveGraphQLInput(argv) {
|
|
19
|
+
const sources = [
|
|
20
|
+
argv.document !== undefined,
|
|
21
|
+
argv.query !== undefined,
|
|
22
|
+
argv.queryFile !== undefined,
|
|
23
|
+
argv.stdin === true,
|
|
24
|
+
].filter(Boolean).length;
|
|
25
|
+
if (sources !== 1) {
|
|
26
|
+
throw new Error('Provide exactly one GraphQL document via positional document, --query, --query-file, or --stdin');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let query;
|
|
30
|
+
if (argv.document !== undefined) query = argv.document;
|
|
31
|
+
else if (argv.query !== undefined) query = argv.query;
|
|
32
|
+
else if (argv.queryFile !== undefined) query = fs.readFileSync(argv.queryFile, 'utf-8');
|
|
33
|
+
else query = await readStdin();
|
|
34
|
+
|
|
35
|
+
query = String(query || '').trim();
|
|
36
|
+
if (!query) throw new Error('GraphQL document must not be empty');
|
|
37
|
+
return query;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseGraphQLVariables(value) {
|
|
41
|
+
if (value === undefined) return {};
|
|
42
|
+
let variables;
|
|
43
|
+
try {
|
|
44
|
+
variables = JSON.parse(value);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error('--variables must be valid JSON');
|
|
47
|
+
}
|
|
48
|
+
if (!variables || typeof variables !== 'object' || Array.isArray(variables)) {
|
|
49
|
+
throw new Error('--variables must be a JSON object');
|
|
50
|
+
}
|
|
51
|
+
return variables;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function graphqlCmd(wrap) {
|
|
55
|
+
return {
|
|
56
|
+
command: 'graphql [document]',
|
|
57
|
+
describe: 'Execute a GraphQL document against ServiceNow Now GraphQL',
|
|
58
|
+
builder: (yargs) => yargs
|
|
59
|
+
.positional('document', {
|
|
60
|
+
describe: 'GraphQL document string',
|
|
61
|
+
type: 'string',
|
|
62
|
+
})
|
|
63
|
+
.option('query', {
|
|
64
|
+
type: 'string',
|
|
65
|
+
describe: 'GraphQL document string (alternative to positional)',
|
|
66
|
+
})
|
|
67
|
+
.option('query-file', {
|
|
68
|
+
type: 'string',
|
|
69
|
+
describe: 'Read the GraphQL document from a file',
|
|
70
|
+
})
|
|
71
|
+
.option('variables', {
|
|
72
|
+
type: 'string',
|
|
73
|
+
describe: 'GraphQL variables as a JSON object',
|
|
74
|
+
})
|
|
75
|
+
.option('stdin', {
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
describe: 'Read the GraphQL document from stdin',
|
|
78
|
+
}),
|
|
79
|
+
handler: wrap(async (argv, app) => {
|
|
80
|
+
const query = await resolveGraphQLInput(argv);
|
|
81
|
+
const variables = parseGraphQLVariables(argv.variables);
|
|
82
|
+
const endpoint = `${app.getEffectiveInstance()}/api/now/graphql`;
|
|
83
|
+
const result = await app.sdk.request(endpoint, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
body: JSON.stringify({ query, variables }),
|
|
86
|
+
});
|
|
87
|
+
const errorCount = Array.isArray(result?.errors) ? result.errors.length : 0;
|
|
88
|
+
app.ok(result, {
|
|
89
|
+
summary: errorCount
|
|
90
|
+
? `GraphQL returned ${errorCount} error(s)`
|
|
91
|
+
: 'GraphQL request succeeded',
|
|
92
|
+
});
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { formatRecordForDisplay, getStringField, resolveFieldsParam } from '../helpers.js';
|
|
1
|
+
import { formatRecordForDisplay, getStringField, resolveFieldsParam, assertSafeExactMatch, confirmDelete, quoteShellArg } from '../helpers.js';
|
|
2
|
+
import { declareCapabilities } from '../capabilities.js';
|
|
3
|
+
|
|
4
|
+
// add/remove write sys_user_grmember; the mutation guard (instance +
|
|
5
|
+
// read-only profile) and the delete confirmation both key off this.
|
|
6
|
+
declareCapabilities('groupmembers', { mutationSubcommands: ['add', 'remove'] });
|
|
2
7
|
|
|
3
8
|
export function groupMembersCmd(wrap) {
|
|
4
9
|
return {
|
|
@@ -42,6 +47,8 @@ export function groupMembersCmd(wrap) {
|
|
|
42
47
|
handler: wrap(async (argv, app) => {
|
|
43
48
|
const groupName = argv.group;
|
|
44
49
|
const userName = argv.user;
|
|
50
|
+
assertSafeExactMatch(groupName);
|
|
51
|
+
assertSafeExactMatch(userName);
|
|
45
52
|
|
|
46
53
|
// Resolve group sys_id
|
|
47
54
|
const isGroupSysID = groupName.length === 32 && /^[0-9a-fA-F]+$/.test(groupName);
|
|
@@ -86,7 +93,7 @@ export function groupMembersCmd(wrap) {
|
|
|
86
93
|
summary: `Added ${userName} to ${groupName}`,
|
|
87
94
|
breadcrumbs: [
|
|
88
95
|
{ action: 'list', cmd: `jsn groupmembers list --query "group.name=${groupName}"`, description: 'List group members' },
|
|
89
|
-
{ action: 'remove', cmd: `jsn groupmembers remove --group
|
|
96
|
+
{ action: 'remove', cmd: `jsn groupmembers remove --group ${quoteShellArg(groupName)} --user ${quoteShellArg(userName)}`, description: 'Remove this member' },
|
|
90
97
|
],
|
|
91
98
|
});
|
|
92
99
|
}),
|
|
@@ -96,10 +103,18 @@ export function groupMembersCmd(wrap) {
|
|
|
96
103
|
describe: 'Remove a user from a group',
|
|
97
104
|
builder: (y) => y
|
|
98
105
|
.option('group', { alias: 'g', type: 'string', demandOption: true, describe: 'Group name or sys_id (required)' })
|
|
99
|
-
.option('user', { alias: 'u', type: 'string', demandOption: true, describe: 'Username or sys_id (required)' })
|
|
106
|
+
.option('user', { alias: 'u', type: 'string', demandOption: true, describe: 'Username or sys_id (required)' })
|
|
107
|
+
.option('force', { type: 'boolean', describe: 'Skip the confirmation prompt (removal)' }),
|
|
100
108
|
handler: wrap(async (argv, app) => {
|
|
101
109
|
const groupName = argv.group;
|
|
102
110
|
const userName = argv.user;
|
|
111
|
+
assertSafeExactMatch(groupName);
|
|
112
|
+
assertSafeExactMatch(userName);
|
|
113
|
+
// Confirm before any lookup: a refused confirmation must not
|
|
114
|
+
// touch the instance at all.
|
|
115
|
+
await confirmDelete(app, argv, `Remove ${userName} from group ${groupName}`, {
|
|
116
|
+
rerun: ['groupmembers', 'remove', '--group', groupName, '--user', userName],
|
|
117
|
+
});
|
|
103
118
|
|
|
104
119
|
// Resolve group sys_id
|
|
105
120
|
const isGroupSysID = groupName.length === 32 && /^[0-9a-fA-F]+$/.test(groupName);
|
|
@@ -142,7 +157,7 @@ export function groupMembersCmd(wrap) {
|
|
|
142
157
|
summary: `Removed ${userName} from ${groupName}`,
|
|
143
158
|
breadcrumbs: [
|
|
144
159
|
{ action: 'list', cmd: `jsn groupmembers list --query "group.name=${groupName}"`, description: 'List group members' },
|
|
145
|
-
{ action: 'add', cmd: `jsn groupmembers add --group
|
|
160
|
+
{ action: 'add', cmd: `jsn groupmembers add --group ${quoteShellArg(groupName)} --user ${quoteShellArg(userName)}`, description: 'Add user back to group' },
|
|
146
161
|
],
|
|
147
162
|
});
|
|
148
163
|
}),
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { formatRecordForDisplay, getStringField, resolveFieldsParam, assertSafeExactMatch } from '../helpers.js';
|
|
1
|
+
import { formatRecordForDisplay, getStringField, resolveFieldsParam, assertSafeExactMatch, confirmDelete, quoteShellArg } from '../helpers.js';
|
|
2
|
+
import { declareCapabilities } from '../capabilities.js';
|
|
3
|
+
|
|
4
|
+
// add/remove write sys_group_has_role; the mutation guard (instance +
|
|
5
|
+
// read-only profile) and the delete confirmation both key off this.
|
|
6
|
+
declareCapabilities('grouproles', { mutationSubcommands: ['add', 'remove'] });
|
|
2
7
|
|
|
3
8
|
export function groupRolesCmd(wrap) {
|
|
4
9
|
return {
|
|
@@ -88,7 +93,7 @@ export function groupRolesCmd(wrap) {
|
|
|
88
93
|
summary: `Added role ${roleName} to ${groupName}`,
|
|
89
94
|
breadcrumbs: [
|
|
90
95
|
{ action: 'list', cmd: `jsn grouproles list --query "group.name=${groupName}"`, description: 'List group roles' },
|
|
91
|
-
{ action: 'remove', cmd: `jsn grouproles remove --group
|
|
96
|
+
{ action: 'remove', cmd: `jsn grouproles remove --group ${quoteShellArg(groupName)} --role ${quoteShellArg(roleName)}`, description: 'Remove this role' },
|
|
92
97
|
],
|
|
93
98
|
});
|
|
94
99
|
}),
|
|
@@ -98,12 +103,18 @@ export function groupRolesCmd(wrap) {
|
|
|
98
103
|
describe: 'Remove a role from a group',
|
|
99
104
|
builder: (y) => y
|
|
100
105
|
.option('group', { alias: 'g', type: 'string', demandOption: true, describe: 'Group name or sys_id (required)' })
|
|
101
|
-
.option('role', { alias: 'r', type: 'string', demandOption: true, describe: 'Role name or sys_id (required)' })
|
|
106
|
+
.option('role', { alias: 'r', type: 'string', demandOption: true, describe: 'Role name or sys_id (required)' })
|
|
107
|
+
.option('force', { type: 'boolean', describe: 'Skip the confirmation prompt (removal)' }),
|
|
102
108
|
handler: wrap(async (argv, app) => {
|
|
103
109
|
const groupName = argv.group;
|
|
104
110
|
const roleName = argv.role;
|
|
105
111
|
assertSafeExactMatch(groupName);
|
|
106
112
|
assertSafeExactMatch(roleName);
|
|
113
|
+
// Confirm before any lookup: a refused confirmation must not
|
|
114
|
+
// touch the instance at all.
|
|
115
|
+
await confirmDelete(app, argv, `Remove role ${roleName} from group ${groupName}`, {
|
|
116
|
+
rerun: ['grouproles', 'remove', '--group', groupName, '--role', roleName],
|
|
117
|
+
});
|
|
107
118
|
|
|
108
119
|
// Resolve group sys_id
|
|
109
120
|
const isGroupSysID = groupName.length === 32 && /^[0-9a-fA-F]+$/.test(groupName);
|
|
@@ -146,7 +157,7 @@ export function groupRolesCmd(wrap) {
|
|
|
146
157
|
summary: `Removed role ${roleName} from ${groupName}`,
|
|
147
158
|
breadcrumbs: [
|
|
148
159
|
{ action: 'list', cmd: `jsn grouproles list --query "group.name=${groupName}"`, description: 'List group roles' },
|
|
149
|
-
{ action: 'add', cmd: `jsn grouproles add --group
|
|
160
|
+
{ action: 'add', cmd: `jsn grouproles add --group ${quoteShellArg(groupName)} --role ${quoteShellArg(roleName)}`, description: 'Add role back to group' },
|
|
150
161
|
],
|
|
151
162
|
});
|
|
152
163
|
}),
|