@jacebenson/jsn 4.3.8-dev.54 → 4.3.8-dev.56
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 +1 -1
- package/src/commands/actions.js +20 -2
- package/src/commands/flows.js +22 -3
- package/src/commands/updatesets.js +14 -20
- package/src/context.js +16 -25
package/package.json
CHANGED
package/src/commands/actions.js
CHANGED
|
@@ -12,6 +12,7 @@ const ACTION_COMMANDS = [
|
|
|
12
12
|
{ name: 'help [identifier]', description: 'Show action input requirements and live usage examples' },
|
|
13
13
|
{ name: 'definition [identifier]', description: 'Read the full JSON definition including executable step_instances' },
|
|
14
14
|
{ name: 'step-types', description: 'Read step schema templates for a target scope' },
|
|
15
|
+
{ name: 'action-type-schema', description: 'Read provider metadata for an exact action type ID and scope' },
|
|
15
16
|
{ name: 'create', description: 'Create an action: guided in a terminal, full JSON definition in scripts' },
|
|
16
17
|
{ name: 'update [identifier]', description: 'Save a full definition through Process Flow (alias: edit)' },
|
|
17
18
|
{ name: 'test [identifier]', description: 'Execute an action and optionally wait for actual outputs' },
|
|
@@ -103,7 +104,7 @@ export const actionsCmd = wrap => {
|
|
|
103
104
|
describe: 'Flow Designer actions: create, edit full definitions, and test with outputs',
|
|
104
105
|
builder: y => base.builder(y)
|
|
105
106
|
.command({
|
|
106
|
-
command: 'create', describe:
|
|
107
|
+
command: 'create', describe: 'Create a Process Flow action. Use `jsn actions step-types --scope <sys_id>` for live provider schemas. The required definition structure is code-owned; step-specific fields are provider-defined.',
|
|
107
108
|
builder: yy => dataOptions(yy).option('scope', { type: 'string', describe: 'Exact target scope sys_id, or global' }),
|
|
108
109
|
handler: run(async (argv, app) => {
|
|
109
110
|
const guided = !hasDefinitionData(argv) ? await guidedActionCreate(argv, app) : undefined;
|
|
@@ -185,13 +186,30 @@ export const actionsCmd = wrap => {
|
|
|
185
186
|
}),
|
|
186
187
|
})
|
|
187
188
|
.command({
|
|
188
|
-
command: 'step-types', describe: 'Read step
|
|
189
|
+
command: 'step-types', describe: 'Read full live step schemas for a target scope',
|
|
189
190
|
builder: yy => yy.option('scope', { type: 'string', describe: 'Exact transaction scope sys_id, or global' }),
|
|
190
191
|
handler: run(async (argv, app) => {
|
|
191
192
|
const scope = await designerScope(argv, app);
|
|
192
193
|
app.ok(await app.sdk.getProcessFlowStepTypes(scope), { summary: 'Process Flow step schemas' });
|
|
193
194
|
}),
|
|
194
195
|
})
|
|
196
|
+
.command({
|
|
197
|
+
command: 'action-type-schema', describe: 'Read live provider input/output metadata for an exact action type ID and scope',
|
|
198
|
+
builder: yy => yy.option('id', { type: 'string', demandOption: true, describe: 'Exact 32-character action type sys_id' }).option('scope', { type: 'string', demandOption: true, describe: 'Exact transaction scope sys_id, or global' }),
|
|
199
|
+
handler: run(async (argv, app) => {
|
|
200
|
+
if (!/^[a-f\d]{32}$/i.test(argv.id)) throw errUsage('--id must be an exact 32-character action type sys_id');
|
|
201
|
+
if (!/^(?:[a-fA-F0-9]{32}|global)$/.test(argv.scope)) throw errUsage('--scope must be an exact scope sys_id or global');
|
|
202
|
+
app.requireInstance();
|
|
203
|
+
const url = `${app.sdk.baseURL}/api/now/processflow/action/action_types/${encodeURIComponent(argv.id)}?${new URLSearchParams({ sysparm_transaction_scope: argv.scope })}`;
|
|
204
|
+
const response = await app.sdk.request(url, { method: 'GET' });
|
|
205
|
+
const result = response?.result;
|
|
206
|
+
if (response?.error || result?.error || (result?.errorCode !== undefined && result.errorCode !== 0 && result.errorCode !== '0')) {
|
|
207
|
+
throw new Error(result?.errorMessage || response?.error?.message || 'Provider rejected the action type schema request');
|
|
208
|
+
}
|
|
209
|
+
if (!result || typeof result !== 'object') throw new Error('Provider returned no action type metadata');
|
|
210
|
+
app.ok(result, { summary: `Provider action type schema ${argv.id}` });
|
|
211
|
+
}),
|
|
212
|
+
})
|
|
195
213
|
.command({
|
|
196
214
|
command: 'delete [identifier]', describe: 'Delete action parent explicitly',
|
|
197
215
|
builder: yy => scopeOption(yy).option('force', { type: 'boolean', default: false, describe: 'Skip confirmation' }),
|
package/src/commands/flows.js
CHANGED
|
@@ -6,7 +6,7 @@ import { inspectFlow } from '../flow-inspection.js';
|
|
|
6
6
|
import { publishFlows, publishFlowLifecycle, publishDoctor, flowStatus } from '../flow-publish.js';
|
|
7
7
|
import { resolveRecord, unwrapSysId } from '../resolve-record.js';
|
|
8
8
|
import { createFlowFromManifest, updateFlowFromManifest, readCurrentUpdateSet } from '../flow-create.js';
|
|
9
|
-
import { EXCLUDED_TRIGGER_TYPES, SUPPORTED_TRIGGER_TYPES } from '../flow-triggers.js';
|
|
9
|
+
import { EXCLUDED_TRIGGER_TYPES, SUPPORTED_TRIGGER_TYPES, readTriggerCatalog, readTriggerInputDefinitions } from '../flow-triggers.js';
|
|
10
10
|
import { testFlow } from '../flow-test.js';
|
|
11
11
|
import { errUsage } from '../errors.js';
|
|
12
12
|
import { cancelled, pickDesignerRecord, guidedFlowCreate, guidedFlowEdit, guidedFlowTestRecord, hasDefinitionData, confirmDesignerMutation } from '../flow-designer-interactive.js';
|
|
@@ -63,6 +63,25 @@ export function flowsCmd(wrap) {
|
|
|
63
63
|
describe: 'Manage Flow Designer flows',
|
|
64
64
|
builder: (yargs) => {
|
|
65
65
|
return yargs
|
|
66
|
+
.command({
|
|
67
|
+
command: 'schemas',
|
|
68
|
+
describe: 'Read live trigger catalog and declared inputs; supported marks verified CLI creation families',
|
|
69
|
+
handler: wrap(async (_argv, app) => {
|
|
70
|
+
app.requireInstance();
|
|
71
|
+
const catalog = await readTriggerCatalog(app.sdk);
|
|
72
|
+
for (const category of catalog.categories) {
|
|
73
|
+
for (const trigger of category.triggers) {
|
|
74
|
+
if (trigger.supported) trigger.inputs = await readTriggerInputDefinitions(app.sdk, trigger.sysId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const trigger of catalog.ungrouped) {
|
|
78
|
+
if (trigger.supported) trigger.inputs = await readTriggerInputDefinitions(app.sdk, trigger.sysId);
|
|
79
|
+
}
|
|
80
|
+
const data = { ...catalog, structural_contract: 'Manifest keys and persistence shape are code-owned; trigger definitions and input metadata are instance-derived.' };
|
|
81
|
+
data._formatted = [...catalog.categories.flatMap(c => c.triggers.map(t => `${c.name}: ${t.name} [${t.type}] ${t.supported ? 'supported' : 'not supported'}; inputs: ${(t.inputs || []).map(i => i.name + ':' + i.type).join(', ')}`)), ...catalog.ungrouped.map(t => `${t.name} [${t.type}] ungrouped${t.supported ? '; supported' : '; not supported'}; inputs: ${(t.inputs || []).map(i => i.name + ':' + i.type).join(', ')}`)].join('\n');
|
|
82
|
+
app.ok(data, { summary: 'Live Flow trigger schemas' });
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
66
85
|
.command({
|
|
67
86
|
command: 'list',
|
|
68
87
|
aliases: ['ls'],
|
|
@@ -294,7 +313,7 @@ export function flowsCmd(wrap) {
|
|
|
294
313
|
})
|
|
295
314
|
.command({
|
|
296
315
|
command: 'create',
|
|
297
|
-
describe:
|
|
316
|
+
describe: 'Create a draft flow from a JSON manifest or guided prompts. Use `jsn flows schemas` for instance-derived trigger and input schemas, and `jsn actions action-type-schema --id <actionTypeId> --scope <scopeId>` for provider action inputs. Manifest structure remains code-owned.',
|
|
298
317
|
builder: (y) => y
|
|
299
318
|
.option('data', { type: 'string', describe: 'Flow manifest as inline JSON' })
|
|
300
319
|
.option('data-file', { type: 'string', describe: 'Read the flow manifest from a JSON file' })
|
|
@@ -322,7 +341,7 @@ export function flowsCmd(wrap) {
|
|
|
322
341
|
.command({
|
|
323
342
|
command: 'update [identifier]',
|
|
324
343
|
aliases: ['edit'],
|
|
325
|
-
describe:
|
|
344
|
+
describe: 'Safely update an inactive Draft flow from a partial JSON manifest. Use `jsn flows schemas` for live trigger inputs and `jsn actions action-type-schema --id <actionTypeId> --scope <scopeId>` for provider action inputs. Manifest structure remains code-owned; omitted values are preserved.',
|
|
326
345
|
builder: (y) => y
|
|
327
346
|
.option('data', { type: 'string', describe: 'Update manifest as inline JSON' })
|
|
328
347
|
.option('data-file', { type: 'string', describe: 'Read the update manifest from a JSON file' })
|
|
@@ -560,25 +560,16 @@ export function updateSetsCmd(wrap) {
|
|
|
560
560
|
throw errUsage(`Cannot set "${name}" as current — update set is ${stateRaw === 'ignore' ? 'ignored' : 'complete'}.\nView it instead: jsn updatesets show "${name}"`);
|
|
561
561
|
}
|
|
562
562
|
const userSysID = await requireCurrentUserSysId(app.sdk);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
// switches the application scope into the set's scope. The
|
|
567
|
-
// application field is a sys_scope reference, so its raw value
|
|
568
|
-
// IS the scope sys_id the apps.current_app preference stores.
|
|
563
|
+
// Scope selection can reset the session's capture set. Select the
|
|
564
|
+
// scope first, then the requested set, and verify both picker writes.
|
|
565
|
+
const scopeSysId = updateSetApp?.value || (typeof updateSetApp === 'string' ? updateSetApp : '');
|
|
569
566
|
let scopeNote = '';
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const scopeName = updateSetApp?.display_value || updateSetApp;
|
|
575
|
-
scopeNote = ` — scope switched to ${scopeName}`;
|
|
576
|
-
}
|
|
577
|
-
} catch {
|
|
578
|
-
// scope switch failed — fall back to a warning
|
|
579
|
-
const currentApp = await getCurrentApplication(app.sdk, userSysID).catch(() => null);
|
|
580
|
-
scopeNote = scopeMismatchWarning(updateSetApp, currentApp);
|
|
567
|
+
if (scopeSysId) {
|
|
568
|
+
await setCurrentApplication(app.sdk, userSysID, scopeSysId);
|
|
569
|
+
const scopeName = updateSetApp?.display_value || updateSetApp;
|
|
570
|
+
scopeNote = ` — scope switched to ${scopeName}`;
|
|
581
571
|
}
|
|
572
|
+
await setCurrentUpdateSet(app.sdk, userSysID, sysID);
|
|
582
573
|
|
|
583
574
|
app.ok({ update_set: name, sys_id: sysID }, { summary: `Current update set: ${name}${scopeNote}` });
|
|
584
575
|
}),
|
|
@@ -631,15 +622,18 @@ export function updateSetsCmd(wrap) {
|
|
|
631
622
|
|
|
632
623
|
// Auto-set as current update set
|
|
633
624
|
const sysID = record?.sys_id?.value || record?.sys_id;
|
|
625
|
+
let selectionNote = '';
|
|
634
626
|
try {
|
|
635
627
|
const userSysID = await requireCurrentUserSysId(app.sdk);
|
|
636
628
|
await setCurrentUpdateSet(app.sdk, userSysID, sysID);
|
|
637
|
-
} catch {
|
|
638
|
-
//
|
|
629
|
+
} catch (error) {
|
|
630
|
+
// Creation succeeded. Report partial success without hiding a
|
|
631
|
+
// failed selection or inviting a duplicate create on retry.
|
|
632
|
+
selectionNote = `; not selected as current: ${error.message}. Run jsn updatesets set "${argv.name}"`;
|
|
639
633
|
}
|
|
640
634
|
|
|
641
635
|
app.ok(record, {
|
|
642
|
-
summary: `Created update set: ${argv.name}`,
|
|
636
|
+
summary: `Created update set: ${argv.name}${selectionNote}`,
|
|
643
637
|
breadcrumbs: [
|
|
644
638
|
{
|
|
645
639
|
action: 'set',
|
package/src/context.js
CHANGED
|
@@ -63,25 +63,16 @@ export async function getCurrentUpdateSet(sdk, userSysID) {
|
|
|
63
63
|
return { name: val, sys_id: val };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (prefs.length > 0) {
|
|
77
|
-
await sdk.update('sys_user_preference', getStringField(prefs[0], 'sys_id'), { value });
|
|
78
|
-
} else {
|
|
79
|
-
await sdk.create('sys_user_preference', {
|
|
80
|
-
user: userSysID,
|
|
81
|
-
name,
|
|
82
|
-
value,
|
|
83
|
-
type: 'string',
|
|
84
|
-
});
|
|
66
|
+
// Use the same selection contract as the platform picker. Table API writes to
|
|
67
|
+
// sys_user_preference do not update an already-warmed session's capture context.
|
|
68
|
+
async function selectContext(sdk, picker, data, expected) {
|
|
69
|
+
const endpoint = `${sdk.baseURL}/api/now/ui/concoursepicker/${picker}`;
|
|
70
|
+
await sdk.request(endpoint, { method: 'PUT', body: JSON.stringify(data) });
|
|
71
|
+
const response = await sdk.request(endpoint, { method: 'GET' });
|
|
72
|
+
const current = response?.result?.current;
|
|
73
|
+
const actual = picker === 'application' ? current : current?.sysId;
|
|
74
|
+
if (actual !== expected) {
|
|
75
|
+
throw new Error(`${picker === 'application' ? 'Application' : 'Update set'} selection was not applied. Expected ${expected}, received ${typeof actual === 'string' ? actual : 'an unreadable selection'}.`);
|
|
85
76
|
}
|
|
86
77
|
}
|
|
87
78
|
|
|
@@ -98,12 +89,12 @@ export async function requireCurrentUserSysId(sdk) {
|
|
|
98
89
|
return getStringField(user[0], 'sys_id');
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
/**
|
|
102
|
-
export async function setCurrentApplication(sdk,
|
|
103
|
-
await
|
|
92
|
+
/** Select application scope in the authenticated session and verify it. */
|
|
93
|
+
export async function setCurrentApplication(sdk, _userSysID, scopeSysID) {
|
|
94
|
+
await selectContext(sdk, 'application', { app_id: scopeSysID }, scopeSysID);
|
|
104
95
|
}
|
|
105
96
|
|
|
106
|
-
/**
|
|
107
|
-
export async function setCurrentUpdateSet(sdk,
|
|
108
|
-
await
|
|
97
|
+
/** Select the capture update set in the authenticated session and verify it. */
|
|
98
|
+
export async function setCurrentUpdateSet(sdk, _userSysID, updateSetSysID) {
|
|
99
|
+
await selectContext(sdk, 'updateset', { sysId: updateSetSysID }, updateSetSysID);
|
|
109
100
|
}
|