@jacebenson/jsn 4.3.13 → 4.4.0-dev.113.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 +137 -0
- package/package.json +4 -3
- 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 +12 -0
- package/src/cli.js +45 -9
- package/src/command-audience.js +38 -0
- package/src/commands/_generic.js +45 -28
- package/src/commands/_simple.js +30 -33
- package/src/commands/_ticket.js +24 -20
- package/src/commands/attachments.js +88 -0
- package/src/commands/auth.js +94 -19
- package/src/commands/b4rules.js +8 -3
- package/src/commands/catalog-order.js +93 -0
- package/src/commands/catalog.js +8 -10
- package/src/commands/changes.js +2 -4
- package/src/commands/cmdb-search.js +38 -0
- package/src/commands/cmdb.js +2 -0
- 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/history.js +57 -0
- package/src/commands/incidents.js +2 -4
- package/src/commands/knowledge.js +75 -0
- package/src/commands/midservers.js +117 -10
- package/src/commands/records.js +57 -104
- package/src/commands/requests.js +9 -15
- 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 +73 -76
- package/src/commands/tasks.js +2 -4
- package/src/commands/tickets.js +20 -19
- package/src/commands/uipolicies.js +2 -27
- package/src/commands/updatesets.js +66 -13
- package/src/commands/upgrade.js +69 -0
- 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 +12 -2
- 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,18 +8,21 @@ 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';
|
|
25
|
+
import { knowledgeCmd } from './commands/knowledge.js';
|
|
22
26
|
import { snippetsCmd } from './commands/snippets.js';
|
|
23
27
|
import { catalogCmd } from './commands/catalog.js';
|
|
24
28
|
import { incidentsCmd } from './commands/incidents.js';
|
|
@@ -35,8 +39,9 @@ import { transactionsCmd } from './commands/transactions.js';
|
|
|
35
39
|
import { platformCmd } from './commands/platform.js';
|
|
36
40
|
import { codesearchCmd } from './commands/codesearch.js';
|
|
37
41
|
import { versionCmd } from './commands/version.js';
|
|
42
|
+
import { upgradeCmd } from './commands/upgrade.js';
|
|
38
43
|
import { skillCmd, checkSkill } from './commands/skill.js';
|
|
39
|
-
import { getVersion, checkLatest } from './commands/version.js';
|
|
44
|
+
import { getVersion, checkLatest, getReleaseChannel, getInstallCommand } from './commands/version.js';
|
|
40
45
|
import { docsCmd } from './commands/docs/docs.js';
|
|
41
46
|
|
|
42
47
|
// Table and platform commands registered at root level for progressive disclosure
|
|
@@ -69,7 +74,9 @@ import { logsCmd } from './commands/logs.js';
|
|
|
69
74
|
import { scrapiCmd } from './commands/scrapi.js';
|
|
70
75
|
import { b4rulesCmd } from './commands/b4rules.js';
|
|
71
76
|
import { atfCmd } from './commands/atf.js';
|
|
77
|
+
import { scanCmd } from './commands/scan.js';
|
|
72
78
|
import { approvalsCmd } from './commands/approvals.js';
|
|
79
|
+
import { historyCmd } from './commands/history.js';
|
|
73
80
|
import { perfCmd } from './commands/perf.js';
|
|
74
81
|
import { customizationsCmd } from './commands/customizations.js';
|
|
75
82
|
import { midserversCmd } from './commands/midservers.js';
|
|
@@ -116,6 +123,7 @@ function wrap(handler) {
|
|
|
116
123
|
process.stderr.write('Error: App context not initialized.\n');
|
|
117
124
|
process.exit(1);
|
|
118
125
|
}
|
|
126
|
+
await app.guardMutationContext(argv);
|
|
119
127
|
await handler(argv, app);
|
|
120
128
|
} catch (err) {
|
|
121
129
|
// All error rendering goes through the unified renderer in
|
|
@@ -144,6 +152,13 @@ export function buildCLI() {
|
|
|
144
152
|
},
|
|
145
153
|
])
|
|
146
154
|
.options({
|
|
155
|
+
'all-commands': { type: 'boolean', describe: 'Show the full command list, including admin and developer tools' },
|
|
156
|
+
reason: { type: 'string', describe: 'Optional non-sensitive reason saved in local mutation history' },
|
|
157
|
+
|
|
158
|
+
force: {
|
|
159
|
+
type: 'boolean',
|
|
160
|
+
description: 'Approve Global + Default mutation context only after user approval; delete commands also use this for their separate confirmation',
|
|
161
|
+
},
|
|
147
162
|
instance: {
|
|
148
163
|
type: 'string',
|
|
149
164
|
description: 'ServiceNow instance URL',
|
|
@@ -213,6 +228,7 @@ export function buildCLI() {
|
|
|
213
228
|
hint: 'Run "jsn auth status" to list profiles.',
|
|
214
229
|
});
|
|
215
230
|
}
|
|
231
|
+
app._authArgv = argv;
|
|
216
232
|
applySession(app, session);
|
|
217
233
|
} catch (err) {
|
|
218
234
|
guardExit(argv, { code: err.code || 'usage', message: err.message, hint: err.hint });
|
|
@@ -248,6 +264,7 @@ export function buildCLI() {
|
|
|
248
264
|
.middleware(async (argv) => {
|
|
249
265
|
// Session (incl. --instance/--profile overrides) was already applied
|
|
250
266
|
// by the middleware above — nothing to re-apply here.
|
|
267
|
+
if (isLocalPreview(argv)) return;
|
|
251
268
|
const cmd = (argv._[0] || '').toString();
|
|
252
269
|
// Daily-check + context-header skip-list is derived from command
|
|
253
270
|
// capability declarations (skipDailyChecks: true).
|
|
@@ -290,7 +307,7 @@ export function buildCLI() {
|
|
|
290
307
|
}
|
|
291
308
|
}
|
|
292
309
|
if (!skipNpmCheck) {
|
|
293
|
-
const cacheFile = path.join(globalConfigDir(),
|
|
310
|
+
const cacheFile = path.join(globalConfigDir(), `.last-npm-check-${getReleaseChannel()}`);
|
|
294
311
|
let shouldCheck = true;
|
|
295
312
|
try {
|
|
296
313
|
const mtime = fs.statSync(cacheFile).mtimeMs;
|
|
@@ -303,9 +320,9 @@ export function buildCLI() {
|
|
|
303
320
|
|
|
304
321
|
if (shouldCheck) {
|
|
305
322
|
const version = getVersion();
|
|
306
|
-
checkLatest({ background: true }).then(latest => {
|
|
323
|
+
checkLatest({ background: true, version }).then(latest => {
|
|
307
324
|
if (latest && version !== latest) {
|
|
308
|
-
process.stderr.write(`\n⚠ jsn ${latest} available (you have ${version}) — run "
|
|
325
|
+
process.stderr.write(`\n⚠ jsn ${latest} available (you have ${version}) — run "${getInstallCommand(version)}" to update\n\n`);
|
|
309
326
|
}
|
|
310
327
|
}).catch(() => {});
|
|
311
328
|
|
|
@@ -397,7 +414,9 @@ export function buildCLI() {
|
|
|
397
414
|
.command(propertiesCmd(wrap))
|
|
398
415
|
// DATA
|
|
399
416
|
.command(cmdbCmd(wrap))
|
|
417
|
+
.command(knowledgeCmd(wrap))
|
|
400
418
|
.command(recordsCmd(wrap))
|
|
419
|
+
.command(attachmentsCmd(wrap))
|
|
401
420
|
.command(transactionsCmd(wrap))
|
|
402
421
|
|
|
403
422
|
.command(platformCmd(wrap))
|
|
@@ -406,9 +425,11 @@ export function buildCLI() {
|
|
|
406
425
|
.command(codesearchCmd(wrap))
|
|
407
426
|
// DEVELOPER
|
|
408
427
|
.command(atfCmd(wrap))
|
|
428
|
+
.command(scanCmd(wrap))
|
|
409
429
|
.command(approvalsCmd(wrap))
|
|
410
430
|
.command(skippedUpdatesCmd(wrap))
|
|
411
431
|
.command(perfCmd(wrap))
|
|
432
|
+
.command(historyCmd(wrap))
|
|
412
433
|
.command(customizationsCmd(wrap))
|
|
413
434
|
.command(midserversCmd(wrap))
|
|
414
435
|
.command(diffCmd(wrap))
|
|
@@ -418,6 +439,8 @@ export function buildCLI() {
|
|
|
418
439
|
.command(skillCmd(wrap))
|
|
419
440
|
.command(docsCmd(wrap))
|
|
420
441
|
.command(versionCmd(wrap))
|
|
442
|
+
.command(upgradeCmd(wrap))
|
|
443
|
+
.command(discoverCmd(wrap, (argv) => visibleCommandRegistry(cliInstance.commandRegistry(), app.config, argv)))
|
|
421
444
|
.demandCommand(1, 'You must specify a command')
|
|
422
445
|
// Completion plumbing under a hidden command name, so the explicit
|
|
423
446
|
// 'completion' command below owns the UX. (#176)
|
|
@@ -437,6 +460,12 @@ export function buildCLI() {
|
|
|
437
460
|
if (current && !current.startsWith('-') && depth === 0) {
|
|
438
461
|
out = [...new Set([...out, ...rootCommands])];
|
|
439
462
|
}
|
|
463
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
464
|
+
const visible = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(options), options);
|
|
465
|
+
if (!depth) {
|
|
466
|
+
const names = new Set(visible.commands.flatMap(command => [command.name, ...command.aliases]));
|
|
467
|
+
out = out.filter(value => value.startsWith('-') || names.has(value));
|
|
468
|
+
}
|
|
440
469
|
done([...new Set(out)]);
|
|
441
470
|
});
|
|
442
471
|
})
|
|
@@ -473,7 +502,7 @@ export function buildCLI() {
|
|
|
473
502
|
})
|
|
474
503
|
.onParentDiscovery((argv, { path, subcommands }) => {
|
|
475
504
|
// Reached only for a parent command that declares no default handler of
|
|
476
|
-
// its own (
|
|
505
|
+
// its own (attachments, docs community, and any future parent) —
|
|
477
506
|
// the adapter used to fall through and exit 0 having printed nothing.
|
|
478
507
|
// Children come from the registry, description included, so this list
|
|
479
508
|
// cannot drift from the registrations the way a hand-written one did.
|
|
@@ -498,6 +527,7 @@ export function buildCLI() {
|
|
|
498
527
|
}, { summary });
|
|
499
528
|
})(argv);
|
|
500
529
|
})
|
|
530
|
+
.epilogue(renderTips())
|
|
501
531
|
.help('help', 'Show help')
|
|
502
532
|
.version(false)
|
|
503
533
|
.strictCommands()
|
|
@@ -541,7 +571,7 @@ export function buildCLI() {
|
|
|
541
571
|
if (typeof formatArgv?.get === 'string' && formatArgv.get) {
|
|
542
572
|
app.output.setJqFilter(formatArgv.get);
|
|
543
573
|
}
|
|
544
|
-
const { commands } = cliInstance.commandRegistry();
|
|
574
|
+
const { commands } = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv);
|
|
545
575
|
try {
|
|
546
576
|
app.ok({ commands }, {
|
|
547
577
|
summary: `${commands.length} registered commands — run "jsn <command> --json" for a command's subcommands.`,
|
|
@@ -551,7 +581,7 @@ export function buildCLI() {
|
|
|
551
581
|
}
|
|
552
582
|
process.exit(0);
|
|
553
583
|
}
|
|
554
|
-
process.stdout.write(renderHelp(loadConfig(), cliInstance.commandRegistry()) + '\n');
|
|
584
|
+
process.stdout.write(renderHelp(loadConfig(formatArgv), visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv)) + '\n');
|
|
555
585
|
process.exit(0);
|
|
556
586
|
}
|
|
557
587
|
// Every other failure here (unknown command, missing required argument,
|
|
@@ -571,6 +601,12 @@ export function buildCLI() {
|
|
|
571
601
|
exitWithError(new AppError(CodeUsage, msg, hint), { app });
|
|
572
602
|
});
|
|
573
603
|
|
|
604
|
+
cliInstance.rootHelpRenderer = () => {
|
|
605
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
606
|
+
const config = loadConfig(options);
|
|
607
|
+
return renderHelp(config, visibleCommandRegistry(cliInstance.commandRegistry(), config, options));
|
|
608
|
+
};
|
|
609
|
+
|
|
574
610
|
// Snapshot the root command names (plus aliases) for the completion
|
|
575
611
|
// filter. Done before any parse — command builders mutate the registry.
|
|
576
612
|
rootCommands = cliInstance.getInternalMethods().getCommandInstance().getCommands();
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
'knowledge',
|
|
6
|
+
'groups', 'groupmembers', 'grouproles', 'users', 'setup', 'auth', 'skill',
|
|
7
|
+
'cmdb',
|
|
8
|
+
'version', 'upgrade', 'docs', 'history', 'discover', 'completion',
|
|
9
|
+
'catalogitems',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export async function detectCommandAudience(sdk, username, instanceURL = sdk?.baseURL) {
|
|
13
|
+
if (!username || typeof sdk?.listStrict !== 'function') return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const roles = await sdk.listStrict('sys_user_has_role', {
|
|
16
|
+
sysparm_query: 'user=javascript:gs.getUserID()^role.name=admin',
|
|
17
|
+
sysparm_fields: 'sys_id', sysparm_limit: '1',
|
|
18
|
+
}, { touchLastSeen: false });
|
|
19
|
+
return { username, instance_url: instanceURL, mode: roles.length ? 'admin' : 'basic', role_check: 'verified' };
|
|
20
|
+
} catch {
|
|
21
|
+
// Missing role access must not expand help. This is a presentation fallback,
|
|
22
|
+
// not a claim that the role query succeeded or an authorization boundary.
|
|
23
|
+
return { username, instance_url: instanceURL, mode: 'basic', role_check: 'unavailable' };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function commandAudience(config, { instance, allCommands = false } = {}) {
|
|
28
|
+
if (allCommands || instance) return 'all';
|
|
29
|
+
const name = config?.activeProfile || config?.defaultProfile;
|
|
30
|
+
const profile = config?.profiles?.[name];
|
|
31
|
+
const cached = profile?.command_audience;
|
|
32
|
+
return cached?.username === profile?.username && cached?.instance_url === profile?.instance_url && cached?.mode === 'basic' ? 'basic' : 'all';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function visibleCommandRegistry(registry, config, options = {}) {
|
|
36
|
+
if (commandAudience(config, options) !== 'basic') return registry;
|
|
37
|
+
return { ...registry, commands: registry.commands.filter(command => BASIC_COMMANDS.has(command.name)) };
|
|
38
|
+
}
|
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
5
|
import { formatRecordForDisplay, getStringField, buildRecordPayload, confirmDelete, interactiveList, canPrompt } from '../helpers.js';
|
|
4
|
-
import {
|
|
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';
|
|
@@ -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,7 +155,7 @@ 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}`,
|
|
@@ -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,14 +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
|
|
226
|
+
builder: (y) => recordOptions(y, false)
|
|
218
227
|
.option('data', { type: 'string', describe: 'JSON fields' })
|
|
219
228
|
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
220
229
|
.option('data-stdin', { type: 'boolean', describe: 'Read JSON payload from stdin' })
|
|
221
230
|
.option('field', { alias: 'f', type: 'string', array: true, describe: 'Set a field as name=value; repeatable' }),
|
|
222
231
|
handler: wrap(async (argv, app) => {
|
|
223
232
|
const recordData = buildRecordPayload(argv);
|
|
224
|
-
const record = await app.sdk
|
|
233
|
+
const record = await createRecord(app.sdk, { table, data: recordData });
|
|
225
234
|
app.ok(record, {
|
|
226
235
|
summary: `Created ${singular}`,
|
|
227
236
|
breadcrumbs: [
|
|
@@ -233,31 +242,31 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
233
242
|
.command({
|
|
234
243
|
command: 'update <identifier>',
|
|
235
244
|
describe: `Update ${vowelArticle(singular)} ${singular}`,
|
|
236
|
-
builder: (y) => y
|
|
245
|
+
builder: (y) => recordOptions(y, false)
|
|
237
246
|
.option('data', { type: 'string', describe: 'JSON fields' })
|
|
238
247
|
.option('data-file', { type: 'string', describe: 'Read JSON payload from file' })
|
|
239
248
|
.option('data-stdin', { type: 'boolean', describe: 'Read JSON payload from stdin' })
|
|
240
249
|
.option('field', { alias: 'f', type: 'string', array: true, describe: 'Set a field as name=value; repeatable' }),
|
|
241
250
|
handler: wrap(async (argv, app) => {
|
|
242
251
|
const id = argv.identifier;
|
|
243
|
-
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 });
|
|
244
253
|
if (scopeValidation) {
|
|
245
254
|
await guardScope(app.sdk, record);
|
|
246
255
|
}
|
|
247
256
|
|
|
248
257
|
const sysID = unwrapSysId(record);
|
|
249
258
|
const recordData = buildRecordPayload(argv);
|
|
250
|
-
const updated = await app.sdk
|
|
259
|
+
const updated = await updateRecord(app.sdk, { table, record: { sys_id: sysID }, data: recordData });
|
|
251
260
|
app.ok(updated, { summary: `Updated ${singular} ${id}` });
|
|
252
261
|
}),
|
|
253
262
|
})
|
|
254
263
|
.command({
|
|
255
264
|
command: 'delete <identifier>',
|
|
256
265
|
describe: `Delete ${vowelArticle(singular)} ${singular}`,
|
|
257
|
-
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' }),
|
|
258
267
|
handler: wrap(async (argv, app) => {
|
|
259
268
|
const id = argv.identifier;
|
|
260
|
-
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 });
|
|
261
270
|
if (scopeValidation) {
|
|
262
271
|
await guardScope(app.sdk, record);
|
|
263
272
|
}
|
|
@@ -267,19 +276,27 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
267
276
|
|
|
268
277
|
await confirmDelete(app, argv, `Delete ${singular} '${recordName}'`);
|
|
269
278
|
|
|
270
|
-
await app.sdk
|
|
279
|
+
await deleteRecord(app.sdk, { table, record: { sys_id: sysID } });
|
|
271
280
|
app.ok({ name: recordName, sys_id: sysID, deleted: true }, { summary: `Deleted ${singular} '${recordName}'` });
|
|
272
281
|
}),
|
|
273
282
|
});
|
|
274
283
|
}
|
|
275
284
|
|
|
276
|
-
|
|
285
|
+
const help = [opts.help, extraQuery ? constrainedQueryHelp(extraQuery) : ''].filter(Boolean).join('\n');
|
|
286
|
+
return help ? y.epilogue(help) : y;
|
|
277
287
|
};
|
|
278
288
|
|
|
279
289
|
return {
|
|
280
290
|
command: `${name} [subcommand]`,
|
|
281
291
|
aliases: aliases || [],
|
|
282
|
-
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
|
+
},
|
|
283
300
|
builder,
|
|
284
301
|
handler: (_argv) => {
|
|
285
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);
|