@jacebenson/jsn 4.3.7-dev.14 → 4.3.7-dev.15
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 +6 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/graphql.js +95 -0
package/README.md
CHANGED
|
@@ -81,6 +81,10 @@ jsn diff record --table incident --sys-id <sys_id> --profile-a dev --profile-b p
|
|
|
81
81
|
jsn eval "gs.info('Hello World')"
|
|
82
82
|
cat script.js | jsn eval --stdin # pipe a script in (no shell escaping)
|
|
83
83
|
|
|
84
|
+
# Now GraphQL (documents may contain mutations; read-only profiles are blocked)
|
|
85
|
+
jsn graphql --query '{ GlideRecord_Query { incident(pagination: {limit: 2}) { _results { number { value } } } } }'
|
|
86
|
+
cat incident.graphql | jsn graphql --stdin --variables '{"limit":2}'
|
|
87
|
+
|
|
84
88
|
# Saved query snippets (stored locally, run against the active profile)
|
|
85
89
|
jsn snippets save open-inc --table incident --query "active=true"
|
|
86
90
|
jsn snippets run open-inc
|
|
@@ -169,6 +173,8 @@ jsn incidents list
|
|
|
169
173
|
|
|
170
174
|
For the credential identity, auth-method, diagnostics, migration, and concurrency contract, see [`docs/AUTH_LIFECYCLE.md`](docs/AUTH_LIFECYCLE.md).
|
|
171
175
|
|
|
176
|
+
GraphQL uses ServiceNow's `/api/now/graphql` endpoint and sends `{query, variables}` through the authenticated SDK transport. The command preserves GraphQL `data` and `errors`; introspection and available namespaces depend on the target instance.
|
|
177
|
+
|
|
172
178
|
## Local data
|
|
173
179
|
|
|
174
180
|
JSN stores durable local data under `~/.jsn/`. Documentation lives in
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -70,6 +70,7 @@ import { atfCmd } from './commands/atf.js';
|
|
|
70
70
|
import { approvalsCmd } from './commands/approvals.js';
|
|
71
71
|
import { perfCmd } from './commands/perf.js';
|
|
72
72
|
import { diffCmd } from './commands/diff.js';
|
|
73
|
+
import { graphqlCmd } from './commands/graphql.js';
|
|
73
74
|
|
|
74
75
|
function wrap(handler) {
|
|
75
76
|
return async (argv) => {
|
|
@@ -373,6 +374,7 @@ export function buildCLI() {
|
|
|
373
374
|
.command(approvalsCmd(wrap))
|
|
374
375
|
.command(perfCmd(wrap))
|
|
375
376
|
.command(diffCmd(wrap))
|
|
377
|
+
.command(graphqlCmd(wrap))
|
|
376
378
|
.command(evalCmd(wrap))
|
|
377
379
|
.command(restCmd(wrap))
|
|
378
380
|
.command(skillCmd(wrap))
|
|
@@ -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
|
+
}
|