@jacebenson/jsn 4.3.8-dev.52 → 4.3.8-dev.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacebenson/jsn",
3
- "version": "4.3.8-dev.52",
3
+ "version": "4.3.8-dev.54",
4
4
  "description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,53 @@ async function actionID(argv, app) {
29
29
  return unwrapSysId(await resolveRecord(app.sdk, { table: ACTION_TABLE, identifier: argv.identifier, matchField: 'name', resource: 'action' }));
30
30
  }
31
31
 
32
+ const refID = value => typeof value === 'string' ? value : value?.value ?? value?.sys_id ?? value?.sysId ?? value?.id;
33
+ const asBoolean = value => value === true || value === 1 || (typeof value === 'string' && value.toLowerCase() === 'true');
34
+ const EXAMPLE_TYPES = new Set(['approval_rules', 'conditions', 'schedule_date_time']);
35
+ function collectActionExamples(node, snapshotId, allowedInputs, examples, flowName) {
36
+ if (!node || typeof node !== 'object') return;
37
+ if (Array.isArray(node)) { for (const item of node) collectActionExamples(item, snapshotId, allowedInputs, examples, flowName); return; }
38
+ const typeId = refID(node.actionTypeSysId || node.action_type_sys_id || node.actionType || node.action_type);
39
+ if (typeId === snapshotId && Array.isArray(node.inputs)) {
40
+ const values = Object.fromEntries(node.inputs.filter(input => input?.name && allowedInputs.has(input.name) && input.value !== undefined && input.value !== null)
41
+ .map(input => [input.name, refID(input.value) ?? input.value]));
42
+ if (Object.keys(values).length) examples.push({ flow: flowName, inputs: values });
43
+ }
44
+ for (const [key, value] of Object.entries(node)) if (key !== 'inputs') collectActionExamples(value, snapshotId, allowedInputs, examples, flowName);
45
+ }
46
+ async function findActionExamples(sdk, definition, inputDefinitions) {
47
+ const snapshotId = refID(definition.latest_snapshot);
48
+ if (!/^[a-f0-9]{32}$/i.test(snapshotId || '')) return { examples: [], status: 'unavailable' };
49
+ const allowedInputs = new Set(inputDefinitions.filter(input => EXAMPLE_TYPES.has(input.type)
50
+ && !/(password|secret|token|credential|authorization|cookie|api[_-]?key)/i.test(input.name)).map(input => input.name));
51
+ if (!allowedInputs.size) return { examples: [], status: 'not_applicable' };
52
+ const query = new URLSearchParams({
53
+ sysparm_query: `action_type=${snapshotId}^ORDERBYsys_id`,
54
+ sysparm_fields: 'sys_id,flow,action_type',
55
+ sysparm_limit: '20',
56
+ sysparm_display_value: 'false',
57
+ });
58
+ const rows = await sdk.listStrict('sys_hub_action_instance', query);
59
+ if (!rows.length) return { examples: [], status: 'none' };
60
+ const flowIds = [...new Set(rows.map(row => refID(row.flow)).filter(value => /^[a-f0-9]{32}$/i.test(value || '')))];
61
+ if (!flowIds.length) return { examples: [], status: 'unavailable' };
62
+ const examples = [];
63
+ let unreadable = false;
64
+ for (const flowId of flowIds.slice(0, 10)) {
65
+ try {
66
+ const result = await sdk.inspectFlow(flowId);
67
+ const payloadActions = result?.payload?.actionInstances;
68
+ if (!Array.isArray(payloadActions) || !payloadActions.some(item => item && typeof item === 'object' && !Array.isArray(item))) {
69
+ unreadable = true;
70
+ continue;
71
+ }
72
+ collectActionExamples(result.payload, snapshotId, allowedInputs, examples, result?.flow?.name || flowId);
73
+ } catch { unreadable = true; }
74
+ if (examples.length >= 3) break;
75
+ }
76
+ return { examples: examples.slice(0, 3), status: examples.length ? 'found' : unreadable ? 'unavailable' : 'none' };
77
+ }
78
+
32
79
  export const actionsCmd = wrap => {
33
80
  // Reuse the existing list/show picker and output contract only.
34
81
  const base = buildDevCmd('actions', ACTION_TABLE, ['action'], ['name', 'active', 'sys_scope', 'sys_updated_on'], wrap, {
@@ -77,12 +124,13 @@ export const actionsCmd = wrap => {
77
124
  const inputs = (definition.inputs || []).map(input => ({
78
125
  name: input.name,
79
126
  type: input.type || input.parameter?.type || input.parameter?.inputType || input.valueType || 'unknown',
80
- mandatory: input.mandatory ?? input.required ?? input.parameter?.mandatory ?? input.parameter?.required ?? false,
127
+ mandatory: asBoolean(input.mandatory ?? input.required ?? input.parameter?.mandatory ?? input.parameter?.required),
81
128
  default: input.defaultValue ?? input.default ?? input.parameter?.defaultValue ?? '',
82
129
  description: input.description || input.label || input.parameter?.label || '',
83
130
  }));
84
- app.ok({ action: { id, name: definition.name, scope }, inputs, examples: [], examples_status: 'unverified',
85
- examples_note: 'Live examples are not shown yet because this instance’s action-instance to snapshot relationship has not been verified.' },
131
+ const exampleResult = await findActionExamples(app.sdk, definition, inputs);
132
+ app.ok({ action: { id, name: definition.name, scope }, inputs, examples: exampleResult.examples,
133
+ examples_status: exampleResult.status },
86
134
  { summary: `Input help for action ${definition.name || id}` });
87
135
  }),
88
136
  })
@@ -63,8 +63,8 @@ export function catalogCmd(wrap) {
63
63
  for (const v of argv.variable) {
64
64
  const parts = String(v).split(':');
65
65
  if (parts.length >= 2) {
66
- const typeName = parts.length >= 2 ? parts[parts.length - 1] : '';
67
- const label = parts.length >= 3 ? parts[parts.length - 1] : parts[0];
66
+ const typeName = parts[1];
67
+ const label = parts.length >= 3 ? parts.slice(2).join(':') : parts[0];
68
68
  const sysName = parts[0].replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
69
69
  try {
70
70
  await app.sdk.create('item_option_new', {