@jacebenson/jsn 4.3.1 → 4.3.2

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.1",
3
+ "version": "4.3.2",
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": {
@@ -56,6 +56,6 @@ else
56
56
  echo ""
57
57
  echo "⚠️ Architecture violations found!"
58
58
  echo " Remember: Commands should call app.SDK.List() directly with local types."
59
- echo " See internal/commands/dev/forms.go for the correct pattern."
59
+ echo " See internal/commands/forms.go for the correct pattern."
60
60
  exit 1
61
61
  fi
package/src/auth.js CHANGED
@@ -812,8 +812,27 @@ export class AuthManager {
812
812
  async loginWithGck(instanceURL, input, username = this._activeUsername()) {
813
813
  instanceURL = normalizeInstanceURL(instanceURL);
814
814
  const creds = parseBrowserSessionInput(input);
815
- this.saveCredentials(instanceURL, { ...creds, username: username || undefined }, username || undefined);
816
- return { ...creds, username: username || undefined };
815
+ let response;
816
+ try {
817
+ response = await fetch(`${instanceURL}/api/now/table/sys_user?sysparm_limit=1`, {
818
+ method: 'GET',
819
+ headers: {
820
+ Accept: 'application/json',
821
+ 'X-UserToken': creds.access_token,
822
+ Cookie: creds.cookies,
823
+ },
824
+ });
825
+ } catch (error) {
826
+ throw errAuth(`Could not test the browser session against ${instanceURL}. Nothing was saved. Retry with: jsn auth login ${instanceURL} --gck (${error.message})`);
827
+ }
828
+ if (!response.ok) {
829
+ throw errAuth(`Browser session rejected by ${instanceURL} (HTTP ${response.status}). Nothing was saved. Retry with: jsn auth login ${instanceURL} --gck`);
830
+ }
831
+ const body = await response.json();
832
+ const user = body?.result?.[0];
833
+ const verifiedUsername = typeof user?.user_name === 'string' ? user.user_name : username;
834
+ this.saveCredentials(instanceURL, { ...creds, username: verifiedUsername || undefined }, verifiedUsername || undefined);
835
+ return { ...creds, username: verifiedUsername || undefined };
817
836
  }
818
837
 
819
838
  async loginWithCode(instanceURL, code, username = this._activeUsername()) {
@@ -8,8 +8,6 @@
8
8
  // // Subcommand verbs that mutate instance state. The mutation guard
9
9
  // // (src/mutations.js) derives its command-path list from these.
10
10
  // mutationSubcommands: ['create', 'update', 'delete'],
11
- // // Also registered under `jsn dev <name>` — derive both spellings.
12
- // devAlias: true,
13
11
  // // Command runs without a configured instance (joins the middleware
14
12
  // // skip-list for the instance guard).
15
13
  // noInstance: true,
@@ -42,7 +40,7 @@ const REGISTRY = new Map();
42
40
  * @returns {object} the caps object (for convenient inline use)
43
41
  */
44
42
  export function declareCapabilities(name, caps) {
45
- REGISTRY.set(name, { devAlias: false, ...caps });
43
+ REGISTRY.set(name, { ...caps });
46
44
  return caps;
47
45
  }
48
46
 
@@ -99,9 +97,6 @@ export function dailyCheckSkipCommands(caps = collectCapabilities()) {
99
97
  * begins with the pattern IS that mutation (trailing positionals like a
100
98
  * sys_id don't change intent).
101
99
  *
102
- * Commands with `devAlias: true` are also registered under `jsn dev <name>`,
103
- * so both spellings are emitted.
104
- *
105
100
  * A whole-command mutation surface (e.g. `jsn eval`, `jsn rest` — the
106
101
  * command itself is the mutation, there is no mutating sub-verb) declares
107
102
  * `mutationSubcommands: ['']`.
@@ -116,7 +111,6 @@ export function mutationPaths(caps = collectCapabilities()) {
116
111
  for (const sub of c.mutationSubcommands) {
117
112
  const parts = sub === '' ? [] : sub.split(' ');
118
113
  paths.push([name, ...parts]);
119
- if (c.devAlias) paths.push(['dev', name, ...parts]);
120
114
  }
121
115
  }
122
116
  return paths;
package/src/cli.js CHANGED
@@ -34,12 +34,11 @@ import { transactionsCmd } from './commands/transactions.js';
34
34
  import { platformCmd } from './commands/platform.js';
35
35
  import { codesearchCmd } from './commands/codesearch.js';
36
36
  import { versionCmd } from './commands/version.js';
37
- import { devCmd } from './commands/dev.js';
38
37
  import { skillCmd, checkSkill } from './commands/skill.js';
39
38
  import { getVersion, checkLatest } from './commands/version.js';
40
39
  import { docsCmd } from './commands/docs/docs.js';
41
40
 
42
- // Dev subcommands promoted to root level for progressive disclosure
41
+ // Table and platform commands registered at root level for progressive disclosure
43
42
  import {
44
43
  actionsCmd, includesCmd, rulesCmd,
45
44
  clientScriptsCmd, uiActionsCmd,
@@ -55,19 +54,19 @@ import {
55
54
  emailCmd,
56
55
  restmessageCmd, soapmessagesCmd,
57
56
  uimacrosCmd, uxlistsCmd, uxapplicabilityCmd,
58
- } from './commands/dev/_simple.js';
57
+ } from './commands/_simple.js';
59
58
  import { decisiontablesCmd } from './commands/decisiontables.js';
60
- import { flowsCmd } from './commands/dev/flows.js';
61
- import { formsCmd } from './commands/dev/forms.js';
62
- import { listsCmd } from './commands/dev/lists.js';
63
- import { updateSetsCmd } from './commands/dev/updatesets.js';
64
- import { scopesCmd } from './commands/dev/scopes.js';
65
- import { domainsCmd } from './commands/dev/domains.js';
66
- import { evalCmd } from './commands/dev/eval.js';
67
- import { restCmd } from './commands/dev/rest.js';
68
- import { logsCmd } from './commands/dev/logs.js';
69
- import { scrapiCmd } from './commands/dev/scrapi.js';
70
- import { b4rulesCmd } from './commands/dev/b4rules.js';
59
+ import { flowsCmd } from './commands/flows.js';
60
+ import { formsCmd } from './commands/forms.js';
61
+ import { listsCmd } from './commands/lists.js';
62
+ import { updateSetsCmd } from './commands/updatesets.js';
63
+ import { scopesCmd } from './commands/scopes.js';
64
+ import { domainsCmd } from './commands/domains.js';
65
+ import { evalCmd } from './commands/eval.js';
66
+ import { restCmd } from './commands/rest.js';
67
+ import { logsCmd } from './commands/logs.js';
68
+ import { scrapiCmd } from './commands/scrapi.js';
69
+ import { b4rulesCmd } from './commands/b4rules.js';
71
70
  import { atfCmd } from './commands/atf.js';
72
71
  import { approvalsCmd } from './commands/approvals.js';
73
72
  import { perfCmd } from './commands/perf.js';
@@ -378,8 +377,6 @@ export function buildCLI() {
378
377
  .command(skillCmd(wrap))
379
378
  .command(docsCmd(wrap))
380
379
  .command(versionCmd(wrap))
381
- // Legacy
382
- .command(devCmd(wrap))
383
380
  .demandCommand(1, 'You must specify a command')
384
381
  // Completion plumbing under a hidden command name, so the explicit
385
382
  // 'completion' command below owns the UX. (#176)
@@ -1,10 +1,10 @@
1
- // Generic dev subcommand builder for table-based CRUD
1
+ // Generic table-based CRUD command builder
2
2
 
3
- import { formatRecordForDisplay, getStringField, parseDataArg, confirmDelete, interactiveList, canPrompt } from '../../helpers.js';
4
- import { resolveRecord, unwrapSysId } from '../../resolve-record.js';
5
- import { getCurrentUser, getCurrentApplication } from '../../context.js';
6
- import { FormatAuto } from '../../output.js';
7
- import { declareCapabilities } from '../../capabilities.js';
3
+ import { formatRecordForDisplay, getStringField, parseDataArg, confirmDelete, interactiveList, canPrompt } from '../helpers.js';
4
+ import { resolveRecord, unwrapSysId } from '../resolve-record.js';
5
+ import { getCurrentUser, getCurrentApplication } from '../context.js';
6
+ import { FormatAuto } from '../output.js';
7
+ import { declareCapabilities } from '../capabilities.js';
8
8
 
9
9
  function vowelArticle(word) {
10
10
  const first = word.charAt(0).toLowerCase();
@@ -95,11 +95,8 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
95
95
  const singular = toSingular(name, opts.singular);
96
96
  const readOnly = opts.readOnly || false;
97
97
  // Capability declaration: the readOnly flag drives the mutation registry.
98
- // buildDevCmd commands are registered both at root and under `jsn dev`
99
- // unless opts.devAlias says otherwise (root-only commands pass false).
100
98
  declareCapabilities(name, {
101
99
  mutationSubcommands: readOnly ? [] : ['create', 'update', 'delete'],
102
- devAlias: opts.devAlias !== false,
103
100
  });
104
101
  const scopeValidation = opts.scopeValidation || false;
105
102
  const extraQuery = opts.extraQuery || '';
@@ -1,6 +1,6 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
3
- // Simple table-based dev commands — singular names passed for grammar
2
+ import { getStringField } from '../helpers.js';
3
+ // Simple table-based commands — singular names passed for grammar
4
4
  // Default columns and aliases match the Go version exactly
5
5
 
6
6
  async function fetchExtensionChain(sdk, record) {
@@ -81,7 +81,7 @@ export { columnsCmd } from './columns.js';
81
81
 
82
82
  // Read-only commands (Go only has list/show)
83
83
  export { importCmd } from './import.js';
84
- export const spPagesCmd = (wrap) => buildDevCmd('sppages', 'sp_page', ['sp-pages', 'pages'], ['id', 'title', 'sys_scope'], wrap, { singular: 'Service Portal page', readOnly: true });
84
+ export const spPagesCmd = (wrap) => buildDevCmd('sppages', 'sp_page', ['sp-pages'], ['id', 'title', 'sys_scope'], wrap, { singular: 'Service Portal page', readOnly: true });
85
85
  export const spWidgetsCmd = (wrap) => buildDevCmd('spwidgets', 'sp_widget', ['sp-widget', 'widgets'], ['id', 'name', 'sys_scope'], wrap, { singular: 'Service Portal widget', readOnly: true });
86
86
  export const uiPagesCmd = (wrap) => buildDevCmd('uipages', 'sys_ui_page', ['ui-page', 'pages'], ['name', 'sys_scope'], wrap, { singular: 'UI page', scopeValidation: true });
87
87
  export const appMenuCmd = (wrap) => buildDevCmd('appmenu', 'sys_app_application', ['app-menu', 'menu'], ['name', 'active', 'sys_scope'], wrap, { singular: 'application menu', scopeValidation: true });
@@ -99,30 +99,30 @@ export const viewsCmd = (wrap) => buildDevCmd('views', 'sys_ui_view', ['view', '
99
99
  export const privilegesCmd = (wrap) => buildDevCmd('privileges', 'sys_scope_privilege', ['privilege', 'priv'], ['source_scope', 'target_scope', 'target_name', 'operation', 'status'], wrap, { singular: 'privilege', scopeValidation: true });
100
100
  export const uxscriptsCmd = (wrap) => buildDevCmd('uxscripts', 'sys_ux_lib_source_script', ['uxscript', 'ux'], ['name', 'active', 'sys_scope'], wrap, { singular: 'UX script', scopeValidation: true });
101
101
  export { aliasesCmd } from './aliases.js';
102
- export const catalogscriptsCmd = (wrap) => buildDevCmd('catalogscripts', 'catalog_script_client', ['catalogscript', 'cs'], ['name', 'table', 'active', 'sys_scope'], wrap, { singular: 'catalog script', scopeValidation: true });
102
+ export const catalogscriptsCmd = (wrap) => buildDevCmd('catalogscripts', 'catalog_script_client', ['catalogscript'], ['name', 'table', 'active', 'sys_scope'], wrap, { singular: 'catalog script', scopeValidation: true });
103
103
 
104
104
  // ── Automation: Async ──
105
- export const scriptactionsCmd = (wrap) => buildDevCmd('scriptactions', 'sysevent_script_action', ['scriptaction'], ['name', 'active', 'sys_scope'], wrap, { singular: 'script action', scopeValidation: true, devAlias: false });
106
- export const scheduledjobsCmd = (wrap) => buildDevCmd('scheduledjobs', 'sysauto_script', ['scheduledjob'], ['name', 'active', 'run_type', 'sys_scope'], wrap, { singular: 'scheduled job', scopeValidation: true, devAlias: false });
107
- export const asyncrulesCmd = (wrap) => buildDevCmd('asyncrules', 'sys_script', ['asyncrule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'async business rule', scopeValidation: true, extraQuery: 'when=async', devAlias: false });
108
- export const triggersCmd = (wrap) => buildDevCmd('triggers', 'sysevent_register', ['trigger'], ['name', 'event_name', 'active', 'sys_scope'], wrap, { singular: 'event trigger', scopeValidation: true, devAlias: false });
105
+ export const scriptactionsCmd = (wrap) => buildDevCmd('scriptactions', 'sysevent_script_action', ['scriptaction'], ['name', 'active', 'sys_scope'], wrap, { singular: 'script action', scopeValidation: true });
106
+ export const scheduledjobsCmd = (wrap) => buildDevCmd('scheduledjobs', 'sysauto_script', ['scheduledjob'], ['name', 'active', 'run_type', 'sys_scope'], wrap, { singular: 'scheduled job', scopeValidation: true });
107
+ export const asyncrulesCmd = (wrap) => buildDevCmd('asyncrules', 'sys_script', ['asyncrule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'async business rule', scopeValidation: true, extraQuery: 'when=async' });
108
+ export const triggersCmd = (wrap) => buildDevCmd('triggers', 'sysevent_register', ['trigger'], ['name', 'event_name', 'active', 'sys_scope'], wrap, { singular: 'event trigger', scopeValidation: true });
109
109
 
110
110
  // ── Automation: In Memory ──
111
111
 
112
- export const assignmentsCmd = (wrap) => buildDevCmd('assignments', 'sysrule_assignment', ['assignment'], ['name', 'active', 'sys_scope'], wrap, { singular: 'assignment rule', scopeValidation: true, readOnly: true, devAlias: false });
112
+ export const assignmentsCmd = (wrap) => buildDevCmd('assignments', 'sysrule_assignment', ['assignment'], ['name', 'active', 'sys_scope'], wrap, { singular: 'assignment rule', scopeValidation: true, readOnly: true });
113
113
 
114
114
  // ── Automation: Inbound ──
115
- export const emailCmd = (wrap) => buildDevCmd('email', 'sysevent_in_email_action', ['emails'], ['name', 'active', 'type', 'sys_scope'], wrap, { singular: 'inbound email action', scopeValidation: true, devAlias: false });
115
+ export const emailCmd = (wrap) => buildDevCmd('email', 'sysevent_in_email_action', ['emails'], ['name', 'active', 'type', 'sys_scope'], wrap, { singular: 'inbound email action', scopeValidation: true });
116
116
 
117
117
  // ── Automation: Outbound ──
118
118
  export { restmessageCmd } from './restmessage.js';
119
119
  export { soapmessagesCmd } from './soapmessages.js';
120
120
 
121
121
  // ── UX: Core UI ──
122
- export const uimacrosCmd = (wrap) => buildDevCmd('uimacros', 'sys_ui_macro', ['uimacro'], ['name', 'active', 'sys_scope'], wrap, { singular: 'UI macro', scopeValidation: true, devAlias: false });
122
+ export const uimacrosCmd = (wrap) => buildDevCmd('uimacros', 'sys_ui_macro', ['uimacro'], ['name', 'active', 'sys_scope'], wrap, { singular: 'UI macro', scopeValidation: true });
123
123
 
124
124
  // ── UX: Workspaces ──
125
- export const uxlistsCmd = (wrap) => buildDevCmd('uxlists', 'sys_ux_list', ['uxlist'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace list', scopeValidation: true, readOnly: true, devAlias: false });
126
- export const uxapplicabilityCmd = (wrap) => buildDevCmd('uxapplicability', 'sys_ux_applicability_m2m', ['uxapp'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace applicability', scopeValidation: true, readOnly: true, devAlias: false });
125
+ export const uxlistsCmd = (wrap) => buildDevCmd('uxlists', 'sys_ux_list', ['uxlist'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace list', scopeValidation: true, readOnly: true });
126
+ export const uxapplicabilityCmd = (wrap) => buildDevCmd('uxapplicability', 'sys_ux_applicability_m2m', ['uxapp'], ['name', 'active', 'sys_scope'], wrap, { singular: 'workspace applicability', scopeValidation: true, readOnly: true });
127
127
  export { uipoliciesCmd } from './uipolicies.js';
128
128
  export const cataloguipoliciesCmd = (wrap) => buildDevCmd('cataloguipolicies', 'catalog_ui_policy', ['cataloguipolicy', 'cup'], ['short_description', 'catalog_item', 'variable_set', 'active', 'sys_scope'], wrap, { singular: 'catalog UI policy', scopeValidation: true });
@@ -8,7 +8,7 @@ import { declareCapabilities } from '../capabilities.js';
8
8
 
9
9
  export function buildTicketCommands(table, displayName, alias, defaultColumns, stateMap, iconFn, wrap) {
10
10
  // Ticket-style CRUD: list/show are reads; create/update/delete mutate.
11
- // Root-level only (no `jsn dev` spelling).
11
+ // Root-level command only.
12
12
  declareCapabilities(displayName, { mutationSubcommands: ['create', 'update', 'delete'] });
13
13
  return {
14
14
  command: `${displayName} [subcommand]`,
@@ -1,5 +1,5 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
2
+ import { getStringField } from '../helpers.js';
3
3
 
4
4
  export const aliasesCmd = (wrap) => buildDevCmd('aliases', 'sys_alias', ['alias', 'als'],
5
5
  ['name', 'table', 'sys_scope'], wrap, {
@@ -326,7 +326,7 @@ export async function loginWizard(app, argv = {}) {
326
326
  // Probe domain separation capability once and stamp the profile, so
327
327
  // `jsn domains` (and its help entry) only appear on instances with it.
328
328
  try {
329
- const domainsMod = await import('./dev/domains.js');
329
+ const domainsMod = await import('./domains.js');
330
330
  const sdk = app.getSDKForProfile
331
331
  ? app.getSDKForProfile(instance, {
332
332
  profile,
@@ -436,14 +436,14 @@ export async function modifyProfile(app, argv = {}) {
436
436
  app.ok({ profile: name, domain: '' }, { summary: `Domain cleared for ${name}` });
437
437
  return;
438
438
  }
439
- const { isDomainSeparationInstalled } = await import('./dev/domains.js');
439
+ const { isDomainSeparationInstalled } = await import('./domains.js');
440
440
  const sdk = app.getSDKForProfile
441
441
  ? app.getSDKForProfile(profile.instance_url, { profile, authMethod: profile.auth_method, username: profile.username })
442
442
  : app.sdk;
443
443
  if (!(await isDomainSeparationInstalled({ sdk }))) {
444
444
  throw new Error(`Domain separation is not installed on ${profile.instance_url}`);
445
445
  }
446
- const { resolveDomainSysId } = await import('./dev/domains.js');
446
+ const { resolveDomainSysId } = await import('./domains.js');
447
447
  const sysID = await resolveDomainSysId({ sdk }, argv.domain);
448
448
  profile.domain = sysID;
449
449
  await saveConfig(app.config);
@@ -472,7 +472,7 @@ export async function modifyProfile(app, argv = {}) {
472
472
  choices,
473
473
  });
474
474
  if (flag === 'domain') {
475
- const domainsMod = await import('./dev/domains.js');
475
+ const domainsMod = await import('./domains.js');
476
476
  const installed = await domainsMod.isDomainSeparationInstalled({ sdk });
477
477
  if (!installed) {
478
478
  throw new Error(`Domain separation is not installed on ${profile.instance_url}`);
@@ -948,7 +948,7 @@ Examples:
948
948
  // Re-probe domain separation on refresh so the capability flag
949
949
  // stays in sync if the plugin was installed/removed since setup.
950
950
  try {
951
- const { isDomainSeparationInstalled } = await import('./dev/domains.js');
951
+ const { isDomainSeparationInstalled } = await import('./domains.js');
952
952
  const sdk = app.getSDKForProfile
953
953
  ? app.getSDKForProfile(instanceURL, {
954
954
  ...targetAuthOptions,
@@ -2,4 +2,4 @@ import { buildDevCmd } from './_generic.js';
2
2
 
3
3
  // Before-query business rules — applies filters to GlideRecord queries before they execute.
4
4
  // These are sys_script records with order < 100.
5
- export const b4rulesCmd = (wrap) => buildDevCmd('b4rules', 'sys_script', ['b4rule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'before-query business rule', scopeValidation: true, extraQuery: 'when=before', devAlias: false });
5
+ export const b4rulesCmd = (wrap) => buildDevCmd('b4rules', 'sys_script', ['b4rule'], ['name', 'collection', 'active', 'order', 'sys_scope'], wrap, { singular: 'before-query business rule', scopeValidation: true, extraQuery: 'when=before' });
@@ -8,7 +8,7 @@ import { declareCapabilities } from '../capabilities.js';
8
8
  export const decisiontablesCmd = (wrap) => {
9
9
  const name = 'decisiontables';
10
10
  const singular = 'decision table';
11
- declareCapabilities(name, { mutationSubcommands: ['create', 'update', 'delete'], devAlias: false });
11
+ declareCapabilities(name, { mutationSubcommands: ['create', 'update', 'delete'] });
12
12
 
13
13
  const readData = (argv) => {
14
14
  if (argv.dataFile) return fs.readFileSync(argv.dataFile, 'utf8');
@@ -10,11 +10,11 @@
10
10
  // via the `X-Now-Domain` header — the same scoping the platform applies
11
11
  // to its own picker sessions.
12
12
 
13
- import { getStringField, interactiveList, assertSafeExactMatch } from '../../helpers.js';
14
- import { declareCapabilities } from '../../capabilities.js';
13
+ import { getStringField, interactiveList, assertSafeExactMatch } from '../helpers.js';
14
+ import { declareCapabilities } from '../capabilities.js';
15
15
 
16
16
  // `domains set` writes the chosen domain into the profile config.
17
- declareCapabilities('domains', { mutationSubcommands: ['set'], devAlias: true });
17
+ declareCapabilities('domains', { mutationSubcommands: ['set'] });
18
18
 
19
19
  const DETAIL_FIELDS = 'sys_id,name,parent,sys_domain_path,description,active,sys_created_on,sys_updated_on';
20
20
 
@@ -1,10 +1,10 @@
1
1
  import fs from 'node:fs';
2
- import { getCurrentUser, getCurrentApplication } from '../../context.js';
3
- import { declareCapabilities } from '../../capabilities.js';
2
+ import { getCurrentUser, getCurrentApplication } from '../context.js';
3
+ import { declareCapabilities } from '../capabilities.js';
4
4
 
5
5
  // The whole command is a mutation surface (runs arbitrary server-side JS) —
6
- // declared as `['']` so both `jsn eval` and `jsn dev eval` are gated.
7
- declareCapabilities('eval', { mutationSubcommands: [''], devAlias: true });
6
+ // declared as `['']` so the root `jsn eval` command are gated.
7
+ declareCapabilities('eval', { mutationSubcommands: [''] });
8
8
 
9
9
  export function evalCmd(wrap) {
10
10
  return {
@@ -1,9 +1,9 @@
1
- import { formatRecordForDisplay, getStringField, interactiveList } from '../../helpers.js';
2
- import { discoverFlowContextFields, normalizeFlowContext, summarizeFlowContexts, formatFlowContextSummary, aggregateFlowContextMappings, mergeFlowContextStats, buildFlowContextQuery } from '../../flow-context.js';
3
- import { declareCapabilities } from '../../capabilities.js';
4
- import { inspectFlow } from '../../flow-inspection.js';
5
- import { publishFlows, publishDoctor, flowStatus } from '../../flow-publish.js';
6
- import { resolveRecord, unwrapSysId } from '../../resolve-record.js';
1
+ import { formatRecordForDisplay, getStringField, interactiveList } from '../helpers.js';
2
+ import { discoverFlowContextFields, normalizeFlowContext, summarizeFlowContexts, formatFlowContextSummary, aggregateFlowContextMappings, mergeFlowContextStats, buildFlowContextQuery } from '../flow-context.js';
3
+ import { declareCapabilities } from '../capabilities.js';
4
+ import { inspectFlow } from '../flow-inspection.js';
5
+ import { publishFlows, publishDoctor, flowStatus } from '../flow-publish.js';
6
+ import { resolveRecord, unwrapSysId } from '../resolve-record.js';
7
7
 
8
8
  function flowInspectionAdapter(app) {
9
9
  return {
@@ -12,7 +12,7 @@ function flowInspectionAdapter(app) {
12
12
  };
13
13
  }
14
14
 
15
- declareCapabilities('flows', { mutationSubcommands: ['create', 'update', 'delete', 'publish'], devAlias: true });
15
+ declareCapabilities('flows', { mutationSubcommands: ['create', 'update', 'delete', 'publish'] });
16
16
 
17
17
  function renderChecks(checks) {
18
18
  return checks.map((c) => {
@@ -1,4 +1,4 @@
1
- import { getStringField, interactiveList } from '../../helpers.js';
1
+ import { getStringField, interactiveList } from '../helpers.js';
2
2
 
3
3
  function getDisplayValue(record, key) {
4
4
  if (!record || typeof record !== 'object') return '';
@@ -1,5 +1,5 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
2
+ import { getStringField } from '../helpers.js';
3
3
 
4
4
  export const importCmd = (wrap) => buildDevCmd('import', 'sys_import_set', ['imports', 'imp'],
5
5
  ['name', 'sys_target_table', 'state', 'sys_created_on'], wrap, {
@@ -1,4 +1,4 @@
1
- import { getStringField, interactiveList } from '../../helpers.js';
1
+ import { getStringField, interactiveList } from '../helpers.js';
2
2
 
3
3
  export function listsCmd(wrap) {
4
4
  return {
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, interactiveList, getStringField } from '../../helpers.js';
1
+ import { formatRecordForDisplay, interactiveList, getStringField } from '../helpers.js';
2
2
 
3
3
  function logIcon(level) {
4
4
  level = (level || '').toLowerCase();
@@ -1,8 +1,8 @@
1
- import { declareCapabilities } from '../../capabilities.js';
1
+ import { declareCapabilities } from '../capabilities.js';
2
2
 
3
3
  // Raw REST passthrough — can hit any endpoint with any method, so the whole
4
- // command is a mutation surface on read-only profiles (both spellings).
5
- declareCapabilities('rest', { mutationSubcommands: [''], devAlias: true });
4
+ // command is a mutation surface on read-only profiles.
5
+ declareCapabilities('rest', { mutationSubcommands: [''] });
6
6
 
7
7
  export function restCmd(wrap) {
8
8
  return {
@@ -1,11 +1,10 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
2
+ import { getStringField } from '../helpers.js';
3
3
 
4
4
  export const restmessageCmd = (wrap) => buildDevCmd('restmessage', 'sys_rest_message', ['rm', 'restmsg'],
5
5
  ['name', 'endpoint', 'active', 'sys_scope'], wrap, {
6
6
  singular: 'REST message',
7
7
  scopeValidation: true,
8
- devAlias: false,
9
8
  onShow: async (record, app) => {
10
9
  const sysID = getStringField(record, 'sys_id') || '';
11
10
  if (!sysID) return;
@@ -1,9 +1,9 @@
1
- import { formatRecordForDisplay, getStringField, interactiveList, assertSafeExactMatch } from '../../helpers.js';
2
- import { requireCurrentUserSysId, setCurrentApplication } from '../../context.js';
3
- import { declareCapabilities } from '../../capabilities.js';
4
- import { unwrapSysId } from '../../resolve-record.js';
1
+ import { formatRecordForDisplay, getStringField, interactiveList, assertSafeExactMatch } from '../helpers.js';
2
+ import { requireCurrentUserSysId, setCurrentApplication } from '../context.js';
3
+ import { declareCapabilities } from '../capabilities.js';
4
+ import { unwrapSysId } from '../resolve-record.js';
5
5
 
6
- declareCapabilities('scopes', { mutationSubcommands: ['create', 'set'], devAlias: true });
6
+ declareCapabilities('scopes', { mutationSubcommands: ['create', 'set'] });
7
7
 
8
8
  /**
9
9
  * Resolve a scope identifier to a single sys_scope record.
@@ -8,13 +8,13 @@
8
8
 
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
- import { formatRecordForDisplay, getStringField, isHexString } from '../../helpers.js';
12
- import { errUsageHint } from '../../errors.js';
13
- import { declareCapabilities } from '../../capabilities.js';
11
+ import { formatRecordForDisplay, getStringField, isHexString } from '../helpers.js';
12
+ import { errUsageHint } from '../errors.js';
13
+ import { declareCapabilities } from '../capabilities.js';
14
14
 
15
15
  // `scrapi create` builds a full Scripted REST API in one call (list/show are
16
- // reads). Registered at root and under `jsn dev`.
17
- declareCapabilities('scrapi', { mutationSubcommands: ['create'], devAlias: true });
16
+ // reads). Registered at the root.
17
+ declareCapabilities('scrapi', { mutationSubcommands: ['create'] });
18
18
 
19
19
  /**
20
20
  * Parse a --resource flag value of the form:
@@ -1,11 +1,10 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
2
+ import { getStringField } from '../helpers.js';
3
3
 
4
4
  export const soapmessagesCmd = (wrap) => buildDevCmd('soapmessages', 'sys_soap_message', ['soapmsg'],
5
5
  ['name', 'active', 'sys_scope'], wrap, {
6
6
  singular: 'SOAP message',
7
7
  scopeValidation: true,
8
- devAlias: false,
9
8
  onShow: async (record, app) => {
10
9
  const sysID = getStringField(record, 'sys_id') || '';
11
10
  if (!sysID) return;
@@ -1,5 +1,5 @@
1
1
  import { buildDevCmd } from './_generic.js';
2
- import { getStringField } from '../../helpers.js';
2
+ import { getStringField } from '../helpers.js';
3
3
 
4
4
  export const uipoliciesCmd = (wrap) => buildDevCmd('uipolicies', 'sys_ui_policy', ['uipolicy', 'up'],
5
5
  ['short_description', 'table', 'sys_class_name', 'active'], wrap, {
@@ -1,12 +1,11 @@
1
1
  import fs from 'node:fs';
2
- import { formatRecordForDisplay, getStringField, interactiveList, assertSafeExactMatch } from '../../helpers.js';
3
- import { getCurrentApplication, getCurrentUpdateSet, requireCurrentUserSysId, setCurrentApplication, setCurrentUpdateSet } from '../../context.js';
4
- import { errAPI, errUsage } from '../../errors.js';
5
- import { declareCapabilities } from '../../capabilities.js';
2
+ import { formatRecordForDisplay, getStringField, interactiveList, assertSafeExactMatch } from '../helpers.js';
3
+ import { getCurrentApplication, getCurrentUpdateSet, requireCurrentUserSysId, setCurrentApplication, setCurrentUpdateSet } from '../context.js';
4
+ import { errAPI, errUsage } from '../errors.js';
5
+ import { declareCapabilities } from '../capabilities.js';
6
6
 
7
7
  declareCapabilities('updatesets', {
8
8
  mutationSubcommands: ['create', 'set', 'complete', 'ignore', 'parent'],
9
- devAlias: true,
10
9
  });
11
10
 
12
11
  /**
package/src/sdk.js CHANGED
@@ -29,9 +29,11 @@ export class SDKClient {
29
29
  this.authProvider = authProvider;
30
30
  this.timeout = opts.timeout || DEFAULT_TIMEOUT;
31
31
  this.domain = opts.domain || '';
32
+ this.oauthCookies = null;
33
+ this.sessionUserToken = '';
32
34
  }
33
35
 
34
- async _setAuth(req) {
36
+ async _setAuth(req, { skipOAuthSession = false } = {}) {
35
37
  if (!this.authProvider) {
36
38
  throw errAuth('No authentication configured');
37
39
  }
@@ -41,10 +43,22 @@ export class SDKClient {
41
43
  }
42
44
  switch (creds.auth_method) {
43
45
  case 'basic':
46
+ if (!skipOAuthSession) {
47
+ const cookies = await this._warmSession();
48
+ if (cookies) {
49
+ req.headers.set('Cookie', cookies);
50
+ if (this.sessionUserToken) req.headers.set('X-UserToken', this.sessionUserToken);
51
+ break;
52
+ }
53
+ }
44
54
  req.headers.set('Authorization', 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`).toString('base64'));
45
55
  break;
46
56
  case 'token':
47
57
  case 'oauth':
58
+ if (!skipOAuthSession) {
59
+ const cookies = await this._warmSession();
60
+ if (cookies) req.headers.set('Cookie', cookies);
61
+ }
48
62
  req.headers.set('Authorization', `Bearer ${creds.access_token}`);
49
63
  break;
50
64
  case 'gck':
@@ -113,7 +127,7 @@ export class SDKClient {
113
127
  const timer = setTimeout(() => controller.abort(), this.timeout);
114
128
  try {
115
129
  const req = new Request(endpoint, { ...opts, signal: controller.signal });
116
- await this._setAuth(req);
130
+ await this._setAuth(req, { skipOAuthSession: true });
117
131
  return await fetch(req);
118
132
  } finally {
119
133
  clearTimeout(timer);
@@ -780,15 +794,52 @@ export class SDKClient {
780
794
  }
781
795
 
782
796
  async _warmSession() {
797
+ if (this.oauthCookies) return this.oauthCookies;
783
798
  try {
784
- const endpoint = `${this.baseURL}/api/now/table/sys_user?sysparm_limit=1`;
785
- const resp = await this._fetchWithAuth(endpoint, { method: 'GET', headers: { Accept: 'application/json' } });
786
- // Extract cookies for subsequent UI page requests
787
- const setCookie = resp.headers.getSetCookie?.() || resp.headers.get('set-cookie');
788
- if (setCookie) {
789
- return Array.isArray(setCookie) ? setCookie.join('; ') : setCookie;
799
+ const creds = await this.authProvider.getCredentials();
800
+ if (creds.auth_method === 'oauth') {
801
+ let cookies = '';
802
+ const collectCookies = (resp) => {
803
+ const values = resp.headers.getSetCookie?.() || [];
804
+ const raw = Array.isArray(values) ? values : [resp.headers.get('set-cookie')].filter(Boolean);
805
+ const next = raw.map(value => value.split(';', 1)[0]).filter(Boolean);
806
+ if (next.length) cookies = [...new Set(`${cookies}; ${next.join('; ')}`.split(';').map(v => v.trim()).filter(Boolean))].join('; ');
807
+ };
808
+ const login = `${this.baseURL}/angular.do?sysparm_type=get_user`;
809
+ collectCookies(await fetch(new Request(login, { method: 'POST' })));
810
+ const scopeEndpoint = `${this.baseURL}/api/now/table/sys_scope?sysparm_fields=sys_id%2Csys_class_name&sysparm_limit=1`;
811
+ collectCookies(await this._fetchWithAuth(scopeEndpoint, {
812
+ method: 'GET',
813
+ headers: cookies ? { Cookie: cookies } : {},
814
+ }));
815
+ collectCookies(await this._fetchWithAuth(login, {
816
+ method: 'POST',
817
+ headers: cookies ? { Cookie: cookies } : {},
818
+ }));
819
+ this.oauthCookies = cookies;
820
+ return cookies;
790
821
  }
791
- return '';
822
+ const endpoint = `${this.baseURL}/angular.do`;
823
+ const loginResp = await this._fetchWithAuth(`${endpoint}?sysparm_type=view_form.login`, {
824
+ method: 'POST',
825
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
826
+ body: new URLSearchParams({
827
+ sysparm_type: 'login',
828
+ 'ni.nolog.user_password': 'true',
829
+ user_name: creds.username,
830
+ user_password: creds.password,
831
+ }).toString(),
832
+ });
833
+ const values = loginResp.headers.getSetCookie?.() || [];
834
+ const raw = Array.isArray(values) ? values : [loginResp.headers.get('set-cookie')].filter(Boolean);
835
+ const cookies = raw.map(value => value.split(';', 1)[0]).filter(Boolean).join('; ');
836
+ const refreshResp = await this._fetchWithAuth(`${endpoint}?sysparm_type=get_user`, {
837
+ method: 'POST',
838
+ headers: cookies ? { Cookie: cookies } : {},
839
+ });
840
+ this.sessionUserToken = refreshResp.headers.get('x-usertoken-response') || '';
841
+ this.oauthCookies = cookies;
842
+ return cookies;
792
843
  } catch {
793
844
  return '';
794
845
  }
@@ -1,82 +0,0 @@
1
- // Parent dev command
2
-
3
- import {
4
- actionsCmd, includesCmd, rulesCmd,
5
- clientScriptsCmd, uiActionsCmd,
6
- tablesCmd, columnsCmd, importCmd,
7
- spPagesCmd, spWidgetsCmd, uiPagesCmd, appMenuCmd,
8
- aclsCmd, rolesCmd, propertiesCmd,
9
- relationshipsCmd, appmodulesCmd, listcontrolsCmd, viewsCmd,
10
- privilegesCmd, uxscriptsCmd, aliasesCmd,
11
- catalogscriptsCmd, cataloguipoliciesCmd,
12
- uipoliciesCmd,
13
- } from './dev/_simple.js';
14
- import { flowsCmd } from './dev/flows.js';
15
- import { formsCmd } from './dev/forms.js';
16
- import { listsCmd } from './dev/lists.js';
17
- import { updateSetsCmd } from './dev/updatesets.js';
18
- import { scopesCmd } from './dev/scopes.js';
19
- import { domainsCmd } from './dev/domains.js';
20
- import { evalCmd } from './dev/eval.js';
21
- import { restCmd } from './dev/rest.js';
22
- import { logsCmd } from './dev/logs.js';
23
- import { scrapiCmd } from './dev/scrapi.js';
24
-
25
- export function devCmd(wrap) {
26
- return {
27
- command: 'dev [subcommand]',
28
- describe: 'Manage ServiceNow development artifacts',
29
- builder: (yargs) => {
30
- return yargs
31
- .command(flowsCmd(wrap))
32
- .command(actionsCmd(wrap))
33
- .command(includesCmd(wrap))
34
- .command(rulesCmd(wrap))
35
- .command(clientScriptsCmd(wrap))
36
- .command(uiActionsCmd(wrap))
37
- .command(uipoliciesCmd(wrap))
38
- .command(tablesCmd(wrap))
39
- .command(columnsCmd(wrap))
40
- .command(formsCmd(wrap))
41
- .command(listsCmd(wrap))
42
- .command(importCmd(wrap))
43
- .command(spPagesCmd(wrap))
44
- .command(spWidgetsCmd(wrap))
45
- .command(uiPagesCmd(wrap))
46
- .command(appMenuCmd(wrap))
47
- .command(scrapiCmd(wrap))
48
- .command(aclsCmd(wrap))
49
- .command(rolesCmd(wrap))
50
- .command(updateSetsCmd(wrap))
51
- .command(scopesCmd(wrap))
52
- .command(domainsCmd(wrap))
53
- .command(propertiesCmd(wrap))
54
- .command(relationshipsCmd(wrap))
55
- .command(appmodulesCmd(wrap))
56
- .command(listcontrolsCmd(wrap))
57
- .command(viewsCmd(wrap))
58
- .command(privilegesCmd(wrap))
59
- .command(uxscriptsCmd(wrap))
60
- .command(aliasesCmd(wrap))
61
- .command(catalogscriptsCmd(wrap))
62
- .command(cataloguipoliciesCmd(wrap))
63
- .command(logsCmd(wrap))
64
- .command(evalCmd(wrap))
65
- .command(restCmd(wrap));
66
- },
67
- handler: (argv) => {
68
- if (argv._[1]) return; // a subcommand ran — its own handler handled it
69
- console.log('Manage ServiceNow development artifacts.');
70
- console.log('');
71
- console.log('Most dev commands are also available at the top level:');
72
- console.log(' flows, actions, rules, clientscripts, uiactions, uipolicies,');
73
- console.log(' tables, columns, forms, lists, import, sppages, spwidgets,');
74
- console.log(' uipages, appmenu, scrapi, acls, roles, properties,');
75
- console.log(' relationships, appmodules, listcontrols, views, privileges,');
76
- console.log(' uxscripts, aliases, catalogscripts, cataloguipolicies, logs,');
77
- console.log(' updatesets, scopes, eval, rest');
78
- console.log('');
79
- console.log('Run "jsn dev <command> --help" or "jsn <command> --help" for details.');
80
- },
81
- };
82
- }
File without changes