@jacebenson/jsn 4.3.12 → 4.3.13-dev.101.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 +95 -0
- package/package.json +2 -1
- package/skills/servicenow/SKILL.md +20 -172
- package/src/app.js +29 -40
- package/src/auth-recovery.js +83 -0
- package/src/auth.js +2 -0
- package/src/capabilities.js +9 -0
- package/src/cli-adapter.js +24 -4
- package/src/cli.js +41 -9
- package/src/command-audience.js +35 -0
- package/src/commands/_generic.js +58 -37
- package/src/commands/_simple.js +30 -33
- package/src/commands/_ticket.js +38 -31
- package/src/commands/attachments.js +88 -0
- package/src/commands/auth.js +94 -19
- package/src/commands/b4rules.js +8 -3
- package/src/commands/changes.js +2 -4
- package/src/commands/columns.js +2 -1
- package/src/commands/customizations.js +13 -7
- package/src/commands/discover.js +54 -0
- package/src/commands/docs/db.js +9 -1
- package/src/commands/docs/docs.js +25 -2
- package/src/commands/docs/ingest.js +20 -3
- package/src/commands/docs/install.js +86 -0
- package/src/commands/docs/locks.js +34 -0
- package/src/commands/docs/refresh.js +22 -2
- package/src/commands/docs/replace.js +30 -0
- package/src/commands/docs/snapshot-check.js +11 -0
- package/src/commands/docs/snapshot.js +130 -0
- package/src/commands/docs/sync.js +16 -6
- package/src/commands/forms.js +22 -97
- package/src/commands/groups.js +11 -8
- package/src/commands/history.js +57 -0
- package/src/commands/incidents.js +2 -4
- package/src/commands/midservers.js +117 -10
- package/src/commands/records.js +64 -119
- package/src/commands/requests.js +10 -16
- package/src/commands/rest.js +15 -2
- package/src/commands/scan.js +234 -0
- package/src/commands/scopes.js +1 -1
- package/src/commands/setup.js +46 -9
- package/src/commands/skill.js +72 -76
- package/src/commands/tasks.js +2 -4
- package/src/commands/tickets.js +31 -22
- package/src/commands/uipolicies.js +2 -27
- package/src/commands/updatesets.js +66 -13
- package/src/commands/users.js +11 -8
- package/src/commands/version.js +14 -8
- package/src/constrained-query.js +20 -0
- package/src/context.js +6 -6
- package/src/customization-guidance.js +31 -0
- package/src/customizations-report.js +6 -0
- package/src/customizations.js +28 -13
- package/src/errors.js +23 -1
- package/src/essential-records.js +40 -0
- package/src/field-behavior.js +15 -0
- package/src/help.js +11 -2
- package/src/helpers.js +44 -0
- package/src/metadata/records.js +249 -0
- package/src/midserver-linux-remote.js +210 -0
- package/src/midserver-remote-recipe.js +49 -0
- package/src/midserver-remote.js +221 -0
- package/src/midserver-windows-remote.js +318 -0
- package/src/midserver-zip.js +2 -2
- package/src/mutation-context.js +74 -0
- package/src/mutation-history.js +75 -0
- package/src/mutations.js +3 -2
- package/src/output.js +34 -2
- package/src/record-command-profiles.js +70 -0
- package/src/record-profiles.js +93 -0
- package/src/records-engine.js +57 -0
- package/src/related-lists.js +153 -0
- package/src/resolve-record.js +50 -28
- package/src/scan.js +249 -0
- package/src/sdk-batch.js +130 -0
- package/src/sdk.js +157 -18
- package/src/update-set-xml.js +89 -0
package/src/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { visibleCommandRegistry } from './command-audience.js';
|
|
1
2
|
// Root CLI using the local yargs-compatible adapter
|
|
2
3
|
|
|
3
4
|
import yargs, { hideBin } from './cli-adapter.js';
|
|
@@ -7,17 +8,19 @@ import fs from 'node:fs';
|
|
|
7
8
|
import { loadConfig, globalConfigDir } from './config.js';
|
|
8
9
|
import { App } from './app.js';
|
|
9
10
|
import { resolveSession, applySession } from './session.js';
|
|
10
|
-
import { renderHelp } from './help.js';
|
|
11
|
+
import { renderHelp, renderTips } from './help.js';
|
|
11
12
|
import { isMutationCommand, refreshMutationCommands } from './mutations.js';
|
|
12
13
|
import { skippedUpdatesCmd } from './commands/skipped-updates.js';
|
|
13
|
-
import { noInstanceCommands, dailyCheckSkipCommands, contextHeaderSkipCommands } from './capabilities.js';
|
|
14
|
+
import { isLocalPreview, noInstanceCommands, dailyCheckSkipCommands, contextHeaderSkipCommands } from './capabilities.js';
|
|
14
15
|
import { guardExit, exitWithError, AppError, CodeUsage } from './errors.js';
|
|
15
16
|
import { formatCommandList } from './helpers.js';
|
|
16
17
|
|
|
17
18
|
// Command modules
|
|
19
|
+
import { discoverCmd } from './commands/discover.js';
|
|
18
20
|
import { setupCmd } from './commands/setup.js';
|
|
19
21
|
import { authCmd } from './commands/auth.js';
|
|
20
22
|
import { recordsCmd } from './commands/records.js';
|
|
23
|
+
import { attachmentsCmd } from './commands/attachments.js';
|
|
21
24
|
import { cmdbCmd } from './commands/cmdb.js';
|
|
22
25
|
import { snippetsCmd } from './commands/snippets.js';
|
|
23
26
|
import { catalogCmd } from './commands/catalog.js';
|
|
@@ -36,7 +39,7 @@ import { platformCmd } from './commands/platform.js';
|
|
|
36
39
|
import { codesearchCmd } from './commands/codesearch.js';
|
|
37
40
|
import { versionCmd } from './commands/version.js';
|
|
38
41
|
import { skillCmd, checkSkill } from './commands/skill.js';
|
|
39
|
-
import { getVersion, checkLatest } from './commands/version.js';
|
|
42
|
+
import { getVersion, checkLatest, getReleaseChannel, getInstallCommand } from './commands/version.js';
|
|
40
43
|
import { docsCmd } from './commands/docs/docs.js';
|
|
41
44
|
|
|
42
45
|
// Table and platform commands registered at root level for progressive disclosure
|
|
@@ -69,7 +72,9 @@ import { logsCmd } from './commands/logs.js';
|
|
|
69
72
|
import { scrapiCmd } from './commands/scrapi.js';
|
|
70
73
|
import { b4rulesCmd } from './commands/b4rules.js';
|
|
71
74
|
import { atfCmd } from './commands/atf.js';
|
|
75
|
+
import { scanCmd } from './commands/scan.js';
|
|
72
76
|
import { approvalsCmd } from './commands/approvals.js';
|
|
77
|
+
import { historyCmd } from './commands/history.js';
|
|
73
78
|
import { perfCmd } from './commands/perf.js';
|
|
74
79
|
import { customizationsCmd } from './commands/customizations.js';
|
|
75
80
|
import { midserversCmd } from './commands/midservers.js';
|
|
@@ -116,6 +121,7 @@ function wrap(handler) {
|
|
|
116
121
|
process.stderr.write('Error: App context not initialized.\n');
|
|
117
122
|
process.exit(1);
|
|
118
123
|
}
|
|
124
|
+
await app.guardMutationContext(argv);
|
|
119
125
|
await handler(argv, app);
|
|
120
126
|
} catch (err) {
|
|
121
127
|
// All error rendering goes through the unified renderer in
|
|
@@ -144,6 +150,13 @@ export function buildCLI() {
|
|
|
144
150
|
},
|
|
145
151
|
])
|
|
146
152
|
.options({
|
|
153
|
+
'all-commands': { type: 'boolean', describe: 'Show the full command list, including admin and developer tools' },
|
|
154
|
+
reason: { type: 'string', describe: 'Optional non-sensitive reason saved in local mutation history' },
|
|
155
|
+
|
|
156
|
+
force: {
|
|
157
|
+
type: 'boolean',
|
|
158
|
+
description: 'Approve Global + Default mutation context only after user approval; delete commands also use this for their separate confirmation',
|
|
159
|
+
},
|
|
147
160
|
instance: {
|
|
148
161
|
type: 'string',
|
|
149
162
|
description: 'ServiceNow instance URL',
|
|
@@ -213,6 +226,7 @@ export function buildCLI() {
|
|
|
213
226
|
hint: 'Run "jsn auth status" to list profiles.',
|
|
214
227
|
});
|
|
215
228
|
}
|
|
229
|
+
app._authArgv = argv;
|
|
216
230
|
applySession(app, session);
|
|
217
231
|
} catch (err) {
|
|
218
232
|
guardExit(argv, { code: err.code || 'usage', message: err.message, hint: err.hint });
|
|
@@ -248,6 +262,7 @@ export function buildCLI() {
|
|
|
248
262
|
.middleware(async (argv) => {
|
|
249
263
|
// Session (incl. --instance/--profile overrides) was already applied
|
|
250
264
|
// by the middleware above — nothing to re-apply here.
|
|
265
|
+
if (isLocalPreview(argv)) return;
|
|
251
266
|
const cmd = (argv._[0] || '').toString();
|
|
252
267
|
// Daily-check + context-header skip-list is derived from command
|
|
253
268
|
// capability declarations (skipDailyChecks: true).
|
|
@@ -290,7 +305,7 @@ export function buildCLI() {
|
|
|
290
305
|
}
|
|
291
306
|
}
|
|
292
307
|
if (!skipNpmCheck) {
|
|
293
|
-
const cacheFile = path.join(globalConfigDir(),
|
|
308
|
+
const cacheFile = path.join(globalConfigDir(), `.last-npm-check-${getReleaseChannel()}`);
|
|
294
309
|
let shouldCheck = true;
|
|
295
310
|
try {
|
|
296
311
|
const mtime = fs.statSync(cacheFile).mtimeMs;
|
|
@@ -303,9 +318,9 @@ export function buildCLI() {
|
|
|
303
318
|
|
|
304
319
|
if (shouldCheck) {
|
|
305
320
|
const version = getVersion();
|
|
306
|
-
checkLatest({ background: true }).then(latest => {
|
|
321
|
+
checkLatest({ background: true, version }).then(latest => {
|
|
307
322
|
if (latest && version !== latest) {
|
|
308
|
-
process.stderr.write(`\n⚠ jsn ${latest} available (you have ${version}) — run "
|
|
323
|
+
process.stderr.write(`\n⚠ jsn ${latest} available (you have ${version}) — run "${getInstallCommand(version)}" to update\n\n`);
|
|
309
324
|
}
|
|
310
325
|
}).catch(() => {});
|
|
311
326
|
|
|
@@ -398,6 +413,7 @@ export function buildCLI() {
|
|
|
398
413
|
// DATA
|
|
399
414
|
.command(cmdbCmd(wrap))
|
|
400
415
|
.command(recordsCmd(wrap))
|
|
416
|
+
.command(attachmentsCmd(wrap))
|
|
401
417
|
.command(transactionsCmd(wrap))
|
|
402
418
|
|
|
403
419
|
.command(platformCmd(wrap))
|
|
@@ -406,9 +422,11 @@ export function buildCLI() {
|
|
|
406
422
|
.command(codesearchCmd(wrap))
|
|
407
423
|
// DEVELOPER
|
|
408
424
|
.command(atfCmd(wrap))
|
|
425
|
+
.command(scanCmd(wrap))
|
|
409
426
|
.command(approvalsCmd(wrap))
|
|
410
427
|
.command(skippedUpdatesCmd(wrap))
|
|
411
428
|
.command(perfCmd(wrap))
|
|
429
|
+
.command(historyCmd(wrap))
|
|
412
430
|
.command(customizationsCmd(wrap))
|
|
413
431
|
.command(midserversCmd(wrap))
|
|
414
432
|
.command(diffCmd(wrap))
|
|
@@ -418,6 +436,7 @@ export function buildCLI() {
|
|
|
418
436
|
.command(skillCmd(wrap))
|
|
419
437
|
.command(docsCmd(wrap))
|
|
420
438
|
.command(versionCmd(wrap))
|
|
439
|
+
.command(discoverCmd(wrap, (argv) => visibleCommandRegistry(cliInstance.commandRegistry(), app.config, argv)))
|
|
421
440
|
.demandCommand(1, 'You must specify a command')
|
|
422
441
|
// Completion plumbing under a hidden command name, so the explicit
|
|
423
442
|
// 'completion' command below owns the UX. (#176)
|
|
@@ -437,6 +456,12 @@ export function buildCLI() {
|
|
|
437
456
|
if (current && !current.startsWith('-') && depth === 0) {
|
|
438
457
|
out = [...new Set([...out, ...rootCommands])];
|
|
439
458
|
}
|
|
459
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
460
|
+
const visible = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(options), options);
|
|
461
|
+
if (!depth) {
|
|
462
|
+
const names = new Set(visible.commands.flatMap(command => [command.name, ...command.aliases]));
|
|
463
|
+
out = out.filter(value => value.startsWith('-') || names.has(value));
|
|
464
|
+
}
|
|
440
465
|
done([...new Set(out)]);
|
|
441
466
|
});
|
|
442
467
|
})
|
|
@@ -473,7 +498,7 @@ export function buildCLI() {
|
|
|
473
498
|
})
|
|
474
499
|
.onParentDiscovery((argv, { path, subcommands }) => {
|
|
475
500
|
// Reached only for a parent command that declares no default handler of
|
|
476
|
-
// its own (
|
|
501
|
+
// its own (attachments, docs community, and any future parent) —
|
|
477
502
|
// the adapter used to fall through and exit 0 having printed nothing.
|
|
478
503
|
// Children come from the registry, description included, so this list
|
|
479
504
|
// cannot drift from the registrations the way a hand-written one did.
|
|
@@ -498,6 +523,7 @@ export function buildCLI() {
|
|
|
498
523
|
}, { summary });
|
|
499
524
|
})(argv);
|
|
500
525
|
})
|
|
526
|
+
.epilogue(renderTips())
|
|
501
527
|
.help('help', 'Show help')
|
|
502
528
|
.version(false)
|
|
503
529
|
.strictCommands()
|
|
@@ -541,7 +567,7 @@ export function buildCLI() {
|
|
|
541
567
|
if (typeof formatArgv?.get === 'string' && formatArgv.get) {
|
|
542
568
|
app.output.setJqFilter(formatArgv.get);
|
|
543
569
|
}
|
|
544
|
-
const { commands } = cliInstance.commandRegistry();
|
|
570
|
+
const { commands } = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv);
|
|
545
571
|
try {
|
|
546
572
|
app.ok({ commands }, {
|
|
547
573
|
summary: `${commands.length} registered commands — run "jsn <command> --json" for a command's subcommands.`,
|
|
@@ -551,7 +577,7 @@ export function buildCLI() {
|
|
|
551
577
|
}
|
|
552
578
|
process.exit(0);
|
|
553
579
|
}
|
|
554
|
-
process.stdout.write(renderHelp(loadConfig(), cliInstance.commandRegistry()) + '\n');
|
|
580
|
+
process.stdout.write(renderHelp(loadConfig(formatArgv), visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv)) + '\n');
|
|
555
581
|
process.exit(0);
|
|
556
582
|
}
|
|
557
583
|
// Every other failure here (unknown command, missing required argument,
|
|
@@ -571,6 +597,12 @@ export function buildCLI() {
|
|
|
571
597
|
exitWithError(new AppError(CodeUsage, msg, hint), { app });
|
|
572
598
|
});
|
|
573
599
|
|
|
600
|
+
cliInstance.rootHelpRenderer = () => {
|
|
601
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
602
|
+
const config = loadConfig(options);
|
|
603
|
+
return renderHelp(config, visibleCommandRegistry(cliInstance.commandRegistry(), config, options));
|
|
604
|
+
};
|
|
605
|
+
|
|
574
606
|
// Snapshot the root command names (plus aliases) for the completion
|
|
575
607
|
// filter. Done before any parse — command builders mutate the registry.
|
|
576
608
|
rootCommands = cliInstance.getInternalMethods().getCommandInstance().getCommands();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Presentation policy only. ServiceNow ACLs still authorize every request.
|
|
2
|
+
// Explicit command paths and --all-commands remain available to every account.
|
|
3
|
+
const BASIC_COMMANDS = new Set([
|
|
4
|
+
'incidents', 'changes', 'requests', 'tasks', 'tickets', 'approvals', 'records',
|
|
5
|
+
'groups', 'groupmembers', 'grouproles', 'users', 'setup', 'auth', 'skill',
|
|
6
|
+
'version', 'docs', 'history', 'discover', 'completion',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export async function detectCommandAudience(sdk, username, instanceURL = sdk?.baseURL) {
|
|
10
|
+
if (!username || typeof sdk?.listStrict !== 'function') return undefined;
|
|
11
|
+
try {
|
|
12
|
+
const roles = await sdk.listStrict('sys_user_has_role', {
|
|
13
|
+
sysparm_query: 'user=javascript:gs.getUserID()^role.name=admin',
|
|
14
|
+
sysparm_fields: 'sys_id', sysparm_limit: '1',
|
|
15
|
+
}, { touchLastSeen: false });
|
|
16
|
+
return { username, instance_url: instanceURL, mode: roles.length ? 'admin' : 'basic', role_check: 'verified' };
|
|
17
|
+
} catch {
|
|
18
|
+
// Missing role access must not expand help. This is a presentation fallback,
|
|
19
|
+
// not a claim that the role query succeeded or an authorization boundary.
|
|
20
|
+
return { username, instance_url: instanceURL, mode: 'basic', role_check: 'unavailable' };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function commandAudience(config, { instance, allCommands = false } = {}) {
|
|
25
|
+
if (allCommands || instance) return 'all';
|
|
26
|
+
const name = config?.activeProfile || config?.defaultProfile;
|
|
27
|
+
const profile = config?.profiles?.[name];
|
|
28
|
+
const cached = profile?.command_audience;
|
|
29
|
+
return cached?.username === profile?.username && cached?.instance_url === profile?.instance_url && cached?.mode === 'basic' ? 'basic' : 'all';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function visibleCommandRegistry(registry, config, options = {}) {
|
|
33
|
+
if (commandAudience(config, options) !== 'basic') return registry;
|
|
34
|
+
return { ...registry, commands: registry.commands.filter(command => BASIC_COMMANDS.has(command.name)) };
|
|
35
|
+
}
|
package/src/commands/_generic.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { constrainQuery, constrainedQueryHelp } from '../constrained-query.js';
|
|
2
|
+
import { enrichRelatedLists, relatedListOptions } from '../related-lists.js';
|
|
1
3
|
// Generic table-based CRUD command builder
|
|
2
4
|
|
|
3
|
-
import { formatRecordForDisplay, getStringField,
|
|
4
|
-
import {
|
|
5
|
+
import { formatRecordForDisplay, getStringField, buildRecordPayload, confirmDelete, interactiveList, canPrompt } from '../helpers.js';
|
|
6
|
+
import { getRecord, listRecords, recordListParams, createRecord, updateRecord, deleteRecord, recordOptions } from '../records-engine.js';
|
|
7
|
+
import { unwrapSysId } from '../resolve-record.js';
|
|
5
8
|
import { getCurrentUser, getCurrentApplication } from '../context.js';
|
|
6
9
|
import { FormatAuto } from '../output.js';
|
|
7
10
|
import { declareCapabilities } from '../capabilities.js';
|
|
@@ -108,7 +111,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
108
111
|
if (!readOnly) {
|
|
109
112
|
crumbs.unshift(
|
|
110
113
|
{ action: 'delete', cmd: `jsn ${name} delete ${id}`, description: `Delete this ${singular}` },
|
|
111
|
-
{ action: 'update', cmd: `jsn ${name} update ${id}
|
|
114
|
+
{ action: 'update', cmd: `jsn ${name} update ${id} -f name=value`, description: `Update this ${singular}` }
|
|
112
115
|
);
|
|
113
116
|
}
|
|
114
117
|
return crumbs;
|
|
@@ -120,19 +123,24 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
120
123
|
command: 'list',
|
|
121
124
|
aliases: ['ls'],
|
|
122
125
|
describe: `List ${name}`,
|
|
123
|
-
builder: (y) =>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
builder: (y) => {
|
|
127
|
+
const command = recordOptions(y)
|
|
128
|
+
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true^priority=1")' })
|
|
129
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "name,label,super_class")' })
|
|
130
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' });
|
|
131
|
+
const help = [opts.help, extraQuery ? constrainedQueryHelp(extraQuery) : ''].filter(Boolean).join('\n');
|
|
132
|
+
return help ? command.epilogue(help) : command;
|
|
133
|
+
},
|
|
127
134
|
handler: wrap(async (argv, app) => {
|
|
128
135
|
const query = argv.query || '';
|
|
136
|
+
const constrainedQuery = constrainQuery(query, extraQuery);
|
|
129
137
|
const columns = argv.columns ? argv.columns.split(',') : defaultColumns;
|
|
130
138
|
const limit = argv.limit;
|
|
131
139
|
|
|
132
140
|
// Interactive picker with pagination
|
|
133
141
|
if (canPrompt() && app.output.getFormat() === FormatAuto && !query) {
|
|
134
142
|
const picked = await interactiveList({
|
|
135
|
-
app, table, singular, columns: ['name', 'sys_scope'], limit, query:
|
|
143
|
+
app, table, singular, columns: ['name', 'sys_scope'], limit, query: constrainedQuery,
|
|
136
144
|
labelField: 'name',
|
|
137
145
|
message: `Select ${vowelArticle(singular)} ${singular}`,
|
|
138
146
|
formatLabel: (r) => {
|
|
@@ -147,12 +155,12 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
147
155
|
const recordName = getStringField(record, 'name') || getStringField(record, 'sys_id');
|
|
148
156
|
record._context = { instance_url: app.getEffectiveInstance(), table };
|
|
149
157
|
if (opts.onShow) {
|
|
150
|
-
await opts.onShow(record, app);
|
|
158
|
+
await opts.onShow(record, app, argv);
|
|
151
159
|
}
|
|
152
160
|
app.ok(record, {
|
|
153
161
|
summary: `${singular.charAt(0).toUpperCase() + singular.slice(1)}: ${recordName}`,
|
|
154
162
|
breadcrumbs: [
|
|
155
|
-
...(readOnly ? [] : [{ action: 'update', cmd: `jsn ${name} update ${recordName}
|
|
163
|
+
...(readOnly ? [] : [{ action: 'update', cmd: `jsn ${name} update ${recordName} -f name=value`, description: `Update this ${singular}` }]),
|
|
156
164
|
{ action: 'list', cmd: `jsn ${name} list`, description: `Back to all ${name}` },
|
|
157
165
|
],
|
|
158
166
|
});
|
|
@@ -160,19 +168,14 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
// Non-interactive: text/table output
|
|
163
|
-
const
|
|
164
|
-
params
|
|
165
|
-
|
|
166
|
-
params.set('sysparm_fields', ['sys_id', ...columns].join(','));
|
|
167
|
-
const baseQ = extraQuery ? extraQuery + '^' : '';
|
|
168
|
-
const q = query ? query + '^ORDERBYDESCsys_updated_on' : (baseQ + 'ORDERBYDESCsys_updated_on');
|
|
169
|
-
params.set('sysparm_query', q);
|
|
170
|
-
const records = await app.sdk.list(table, params);
|
|
171
|
+
const q = `${constrainedQuery}${constrainedQuery ? '^' : ''}ORDERBYDESCsys_updated_on`;
|
|
172
|
+
const params = recordListParams({ limit, fields: ['sys_id', ...columns], query: q });
|
|
173
|
+
const records = await listRecords(app.sdk, { table, params, displayValues: argv.displayValues ?? opts.referenceMode });
|
|
171
174
|
app.ok({
|
|
172
175
|
table,
|
|
173
176
|
count: records.length,
|
|
174
177
|
columns,
|
|
175
|
-
records: records.map(r => formatRecordForDisplay(r, columns)),
|
|
178
|
+
records: argv.displayValues || opts.listProjection === 'full' ? records : records.map(r => formatRecordForDisplay(r, columns)),
|
|
176
179
|
context: { instance_url: app.getEffectiveInstance() },
|
|
177
180
|
}, {
|
|
178
181
|
summary: `${records.length} ${name}(s)`,
|
|
@@ -184,6 +187,11 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
184
187
|
command: opts.resolveIdentifier ? 'show [identifier]' : 'show <identifier>',
|
|
185
188
|
aliases: ['get'],
|
|
186
189
|
describe: `Show ${vowelArticle(singular)} ${singular} by name or sys_id`,
|
|
190
|
+
builder: y => {
|
|
191
|
+
const command = relatedListOptions(recordOptions(y));
|
|
192
|
+
return opts.relationships?.essential?.length ? command.option('essential', { type: 'boolean', default: true,
|
|
193
|
+
describe: 'Fetch profile-declared essential children; --no-essential skips them. Supplementary lists remain lazy.' }) : command;
|
|
194
|
+
},
|
|
187
195
|
handler: wrap(async (argv, app) => {
|
|
188
196
|
const id = opts.resolveIdentifier ? await opts.resolveIdentifier(argv, app) : argv.identifier;
|
|
189
197
|
if (id === undefined) return;
|
|
@@ -192,16 +200,17 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
192
200
|
const fields = (showFields && showFields.length > 0)
|
|
193
201
|
? [...new Set(['sys_id', ...showFields])]
|
|
194
202
|
: undefined;
|
|
195
|
-
const record = await
|
|
203
|
+
const record = await getRecord(app.sdk, { table, identifier: id, identifierField: argv.identifierField, displayValues: argv.displayValues ?? opts.referenceMode, matchField: opts.identifierField || 'name', resource: singular, fields });
|
|
196
204
|
record._context = {
|
|
197
205
|
instance_url: app.getEffectiveInstance(),
|
|
198
206
|
table,
|
|
199
207
|
};
|
|
200
208
|
|
|
201
209
|
if (opts.onShow) {
|
|
202
|
-
await opts.onShow(record, app);
|
|
210
|
+
await opts.onShow(record, app, argv);
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
await enrichRelatedLists(record, { table, argv, app });
|
|
205
214
|
const summary = typeof showSummary === 'function' ? showSummary(record, id) : showSummary;
|
|
206
215
|
const breadcrumbs = typeof showBreadcrumbs === 'function' ? showBreadcrumbs(record, id) : showBreadcrumbs;
|
|
207
216
|
|
|
@@ -214,12 +223,14 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
214
223
|
.command({
|
|
215
224
|
command: 'create',
|
|
216
225
|
describe: `Create a new ${singular}`,
|
|
217
|
-
builder: (y) => y
|
|
218
|
-
.option('data', { type: 'string', describe: 'JSON fields
|
|
219
|
-
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
226
|
+
builder: (y) => recordOptions(y, false)
|
|
227
|
+
.option('data', { type: 'string', describe: 'JSON fields' })
|
|
228
|
+
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
229
|
+
.option('data-stdin', { type: 'boolean', describe: 'Read JSON payload from stdin' })
|
|
230
|
+
.option('field', { alias: 'f', type: 'string', array: true, describe: 'Set a field as name=value; repeatable' }),
|
|
220
231
|
handler: wrap(async (argv, app) => {
|
|
221
|
-
const recordData =
|
|
222
|
-
const record = await app.sdk
|
|
232
|
+
const recordData = buildRecordPayload(argv);
|
|
233
|
+
const record = await createRecord(app.sdk, { table, data: recordData });
|
|
223
234
|
app.ok(record, {
|
|
224
235
|
summary: `Created ${singular}`,
|
|
225
236
|
breadcrumbs: [
|
|
@@ -231,29 +242,31 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
231
242
|
.command({
|
|
232
243
|
command: 'update <identifier>',
|
|
233
244
|
describe: `Update ${vowelArticle(singular)} ${singular}`,
|
|
234
|
-
builder: (y) => y
|
|
235
|
-
.option('data', { type: 'string', describe: 'JSON fields
|
|
236
|
-
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
245
|
+
builder: (y) => recordOptions(y, false)
|
|
246
|
+
.option('data', { type: 'string', describe: 'JSON fields' })
|
|
247
|
+
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
248
|
+
.option('data-stdin', { type: 'boolean', describe: 'Read JSON payload from stdin' })
|
|
249
|
+
.option('field', { alias: 'f', type: 'string', array: true, describe: 'Set a field as name=value; repeatable' }),
|
|
237
250
|
handler: wrap(async (argv, app) => {
|
|
238
251
|
const id = argv.identifier;
|
|
239
|
-
const record = await
|
|
252
|
+
const record = await getRecord(app.sdk, { table, identifier: id, identifierField: argv.identifierField, displayValues: argv.displayValues ?? opts.referenceMode, matchField: opts.identifierField || 'name', resource: singular });
|
|
240
253
|
if (scopeValidation) {
|
|
241
254
|
await guardScope(app.sdk, record);
|
|
242
255
|
}
|
|
243
256
|
|
|
244
257
|
const sysID = unwrapSysId(record);
|
|
245
|
-
const recordData =
|
|
246
|
-
const updated = await app.sdk
|
|
258
|
+
const recordData = buildRecordPayload(argv);
|
|
259
|
+
const updated = await updateRecord(app.sdk, { table, record: { sys_id: sysID }, data: recordData });
|
|
247
260
|
app.ok(updated, { summary: `Updated ${singular} ${id}` });
|
|
248
261
|
}),
|
|
249
262
|
})
|
|
250
263
|
.command({
|
|
251
264
|
command: 'delete <identifier>',
|
|
252
265
|
describe: `Delete ${vowelArticle(singular)} ${singular}`,
|
|
253
|
-
builder: (y) => y.option('force', { type: 'boolean', default: false, describe: 'Skip confirmation' }),
|
|
266
|
+
builder: (y) => recordOptions(y, false).option('force', { type: 'boolean', default: false, describe: 'Skip confirmation' }),
|
|
254
267
|
handler: wrap(async (argv, app) => {
|
|
255
268
|
const id = argv.identifier;
|
|
256
|
-
const record = await
|
|
269
|
+
const record = await getRecord(app.sdk, { table, identifier: id, identifierField: argv.identifierField, displayValues: argv.displayValues ?? opts.referenceMode, matchField: opts.identifierField || 'name', resource: singular });
|
|
257
270
|
if (scopeValidation) {
|
|
258
271
|
await guardScope(app.sdk, record);
|
|
259
272
|
}
|
|
@@ -263,19 +276,27 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
263
276
|
|
|
264
277
|
await confirmDelete(app, argv, `Delete ${singular} '${recordName}'`);
|
|
265
278
|
|
|
266
|
-
await app.sdk
|
|
279
|
+
await deleteRecord(app.sdk, { table, record: { sys_id: sysID } });
|
|
267
280
|
app.ok({ name: recordName, sys_id: sysID, deleted: true }, { summary: `Deleted ${singular} '${recordName}'` });
|
|
268
281
|
}),
|
|
269
282
|
});
|
|
270
283
|
}
|
|
271
284
|
|
|
272
|
-
|
|
285
|
+
const help = [opts.help, extraQuery ? constrainedQueryHelp(extraQuery) : ''].filter(Boolean).join('\n');
|
|
286
|
+
return help ? y.epilogue(help) : y;
|
|
273
287
|
};
|
|
274
288
|
|
|
275
289
|
return {
|
|
276
290
|
command: `${name} [subcommand]`,
|
|
277
291
|
aliases: aliases || [],
|
|
278
|
-
describe: `Manage ${name} (e.g. "${name} list --query nameLIKEincident")`,
|
|
292
|
+
describe: opts.describe || `Manage ${name} (e.g. "${name} list --query nameLIKEincident")`,
|
|
293
|
+
discovery: {
|
|
294
|
+
table,
|
|
295
|
+
defaultColumns,
|
|
296
|
+
benefits: ['Configured list columns', 'Lookup by name or sys_id',
|
|
297
|
+
...(showFields ? ['Configured show fields'] : []),
|
|
298
|
+
...(opts.onShow ? ['Additional table-specific show processing'] : [])],
|
|
299
|
+
},
|
|
279
300
|
builder,
|
|
280
301
|
handler: (_argv) => {
|
|
281
302
|
// This handler only runs when no subcommand is matched (the default case).
|
package/src/commands/_simple.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildProfileCommand } from '../record-profiles.js';
|
|
2
2
|
import { getStringField } from '../helpers.js';
|
|
3
3
|
// Simple table-based commands — singular names passed for grammar
|
|
4
4
|
// Default columns and aliases match the Go version exactly
|
|
@@ -47,13 +47,13 @@ async function fetchExtensionChain(sdk, record) {
|
|
|
47
47
|
|
|
48
48
|
export { actionsCmd } from './actions.js';
|
|
49
49
|
|
|
50
|
-
export const includesCmd = (wrap) =>
|
|
50
|
+
export const includesCmd = (wrap) => buildProfileCommand('includes', wrap);
|
|
51
51
|
|
|
52
|
-
export const rulesCmd = (wrap) =>
|
|
52
|
+
export const rulesCmd = (wrap) => buildProfileCommand('rules', wrap);
|
|
53
53
|
|
|
54
|
-
export const clientScriptsCmd = (wrap) =>
|
|
54
|
+
export const clientScriptsCmd = (wrap) => buildProfileCommand('clientscripts', wrap);
|
|
55
55
|
|
|
56
|
-
export const uiActionsCmd = (wrap) =>
|
|
56
|
+
export const uiActionsCmd = (wrap) => buildProfileCommand('uiactions', wrap);
|
|
57
57
|
|
|
58
58
|
// NOTE: uipolicies has exactly ONE definition — the rich one in
|
|
59
59
|
// ./uipolicies.js (re-exported below at the bottom of this file). A plain
|
|
@@ -62,10 +62,7 @@ export const uiActionsCmd = (wrap) => buildDevCmd('uiactions', 'sys_ui_action',
|
|
|
62
62
|
// this module's uiPoliciesCmd before uipoliciesCmd — so the factory version
|
|
63
63
|
// was dead code shadowing nothing. Deleted; the re-export is the behavior.
|
|
64
64
|
|
|
65
|
-
export const tablesCmd = (wrap) =>
|
|
66
|
-
singular: 'table',
|
|
67
|
-
scopeValidation: true,
|
|
68
|
-
showFields: ['name', 'label', 'super_class', 'create_access_controls', 'sys_scope', 'sys_created_on', 'sys_updated_on', 'sys_created_by', 'sys_updated_by', 'is_extendable'],
|
|
65
|
+
export const tablesCmd = (wrap) => buildProfileCommand('tables', wrap, {
|
|
69
66
|
async onShow(record, app) {
|
|
70
67
|
const tableName = getStringField(record, 'name');
|
|
71
68
|
const [count, extChain] = await Promise.all([
|
|
@@ -81,48 +78,48 @@ export { columnsCmd } from './columns.js';
|
|
|
81
78
|
|
|
82
79
|
// Read-only commands (Go only has list/show)
|
|
83
80
|
export { importCmd } from './import.js';
|
|
84
|
-
export const spPagesCmd = (wrap) =>
|
|
85
|
-
export const spWidgetsCmd = (wrap) =>
|
|
86
|
-
export const uiPagesCmd = (wrap) =>
|
|
87
|
-
export const appMenuCmd = (wrap) =>
|
|
81
|
+
export const spPagesCmd = (wrap) => buildProfileCommand('sppages', wrap);
|
|
82
|
+
export const spWidgetsCmd = (wrap) => buildProfileCommand('spwidgets', wrap);
|
|
83
|
+
export const uiPagesCmd = (wrap) => buildProfileCommand('uipages', wrap);
|
|
84
|
+
export const appMenuCmd = (wrap) => buildProfileCommand('appmenu', wrap);
|
|
88
85
|
|
|
89
86
|
// Commands with full CRUD
|
|
90
|
-
export const aclsCmd = (wrap) =>
|
|
91
|
-
export const rolesCmd = (wrap) =>
|
|
92
|
-
export const propertiesCmd = (wrap) =>
|
|
87
|
+
export const aclsCmd = (wrap) => buildProfileCommand('acls', wrap);
|
|
88
|
+
export const rolesCmd = (wrap) => buildProfileCommand('roles', wrap);
|
|
89
|
+
export const propertiesCmd = (wrap) => buildProfileCommand('properties', wrap);
|
|
93
90
|
|
|
94
91
|
// New commands for scoped-app tables
|
|
95
|
-
export const relationshipsCmd = (wrap) =>
|
|
96
|
-
export const appmodulesCmd = (wrap) =>
|
|
97
|
-
export const listcontrolsCmd = (wrap) =>
|
|
98
|
-
export const viewsCmd = (wrap) =>
|
|
99
|
-
export const privilegesCmd = (wrap) =>
|
|
100
|
-
export const uxscriptsCmd = (wrap) =>
|
|
92
|
+
export const relationshipsCmd = (wrap) => buildProfileCommand('relationships', wrap);
|
|
93
|
+
export const appmodulesCmd = (wrap) => buildProfileCommand('appmodules', wrap);
|
|
94
|
+
export const listcontrolsCmd = (wrap) => buildProfileCommand('listcontrols', wrap);
|
|
95
|
+
export const viewsCmd = (wrap) => buildProfileCommand('views', wrap);
|
|
96
|
+
export const privilegesCmd = (wrap) => buildProfileCommand('privileges', wrap);
|
|
97
|
+
export const uxscriptsCmd = (wrap) => buildProfileCommand('uxscripts', wrap);
|
|
101
98
|
export { aliasesCmd } from './aliases.js';
|
|
102
|
-
export const catalogscriptsCmd = (wrap) =>
|
|
99
|
+
export const catalogscriptsCmd = (wrap) => buildProfileCommand('catalogscripts', wrap);
|
|
103
100
|
|
|
104
101
|
// ── Automation: Async ──
|
|
105
|
-
export const scriptactionsCmd = (wrap) =>
|
|
106
|
-
export const scheduledjobsCmd = (wrap) =>
|
|
107
|
-
export const asyncrulesCmd = (wrap) =>
|
|
108
|
-
export const triggersCmd = (wrap) =>
|
|
102
|
+
export const scriptactionsCmd = (wrap) => buildProfileCommand('scriptactions', wrap);
|
|
103
|
+
export const scheduledjobsCmd = (wrap) => buildProfileCommand('scheduledjobs', wrap);
|
|
104
|
+
export const asyncrulesCmd = (wrap) => buildProfileCommand('asyncrules', wrap);
|
|
105
|
+
export const triggersCmd = (wrap) => buildProfileCommand('triggers', wrap);
|
|
109
106
|
|
|
110
107
|
// ── Automation: In Memory ──
|
|
111
108
|
|
|
112
|
-
export const assignmentsCmd = (wrap) =>
|
|
109
|
+
export const assignmentsCmd = (wrap) => buildProfileCommand('assignments', wrap);
|
|
113
110
|
|
|
114
111
|
// ── Automation: Inbound ──
|
|
115
|
-
export const emailCmd = (wrap) =>
|
|
112
|
+
export const emailCmd = (wrap) => buildProfileCommand('email', wrap);
|
|
116
113
|
|
|
117
114
|
// ── Automation: Outbound ──
|
|
118
115
|
export { restmessageCmd } from './restmessage.js';
|
|
119
116
|
export { soapmessagesCmd } from './soapmessages.js';
|
|
120
117
|
|
|
121
118
|
// ── UX: Core UI ──
|
|
122
|
-
export const uimacrosCmd = (wrap) =>
|
|
119
|
+
export const uimacrosCmd = (wrap) => buildProfileCommand('uimacros', wrap);
|
|
123
120
|
|
|
124
121
|
// ── UX: Workspaces ──
|
|
125
|
-
export const uxlistsCmd = (wrap) =>
|
|
126
|
-
export const uxapplicabilityCmd = (wrap) =>
|
|
122
|
+
export const uxlistsCmd = (wrap) => buildProfileCommand('uxlists', wrap);
|
|
123
|
+
export const uxapplicabilityCmd = (wrap) => buildProfileCommand('uxapplicability', wrap);
|
|
127
124
|
export { uipoliciesCmd } from './uipolicies.js';
|
|
128
|
-
export const cataloguipoliciesCmd = (wrap) =>
|
|
125
|
+
export const cataloguipoliciesCmd = (wrap) => buildProfileCommand('cataloguipolicies', wrap);
|