snow-flow 10.0.1-dev.397 → 10.0.1-dev.398
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
|
@@ -343,39 +343,53 @@ var FLOW_FACTORY_SCRIPT = [
|
|
|
343
343
|
].join('\n');
|
|
344
344
|
|
|
345
345
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
346
|
+
* Resolve the REST API namespace for a sys_ws_definition record.
|
|
347
|
+
* Uses dot-walking to read the scope string from the related sys_scope record,
|
|
348
|
+
* which is deterministic and doesn't depend on endpoint registration timing.
|
|
349
|
+
* Falls back to HTTP probing if dot-walking fails.
|
|
350
350
|
*/
|
|
351
|
-
async function
|
|
351
|
+
async function resolveFactoryNamespace(
|
|
352
352
|
client: any,
|
|
353
353
|
apiSysId: string,
|
|
354
354
|
instanceUrl: string
|
|
355
355
|
): Promise<string | null> {
|
|
356
|
-
//
|
|
357
|
-
|
|
356
|
+
// ── Strategy 1: Dot-walk to sys_scope.scope (deterministic) ──
|
|
357
|
+
try {
|
|
358
|
+
var dotWalkResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
359
|
+
params: {
|
|
360
|
+
sysparm_fields: 'namespace,namespace.scope',
|
|
361
|
+
sysparm_display_value: 'false'
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
var scopeStr = dotWalkResp.data.result?.['namespace.scope'];
|
|
365
|
+
if (scopeStr && typeof scopeStr === 'string' && scopeStr.length > 0) {
|
|
366
|
+
return scopeStr;
|
|
367
|
+
}
|
|
368
|
+
} catch (_) {}
|
|
358
369
|
|
|
359
|
-
//
|
|
370
|
+
// ── Strategy 2: Read namespace field with display_value=all ──
|
|
360
371
|
try {
|
|
361
372
|
var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
362
|
-
params: { sysparm_fields: '
|
|
373
|
+
params: { sysparm_fields: 'namespace', sysparm_display_value: 'all' }
|
|
363
374
|
});
|
|
364
|
-
var
|
|
365
|
-
var ns = record.namespace;
|
|
375
|
+
var ns = nsResp.data.result?.namespace;
|
|
366
376
|
if (ns) {
|
|
377
|
+
// If it's a reference object, extract useful values
|
|
367
378
|
if (typeof ns === 'object') {
|
|
368
379
|
var dv = ns.display_value || '';
|
|
369
380
|
var val = ns.value || '';
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
381
|
+
// display_value might be the scope string itself (e.g. "global", "x_snflw")
|
|
382
|
+
if (dv && dv !== 'Global') return dv;
|
|
383
|
+
// If display_value is "Global", the scope string is typically "global"
|
|
384
|
+
if (dv === 'Global') return 'global';
|
|
385
|
+
if (val && val.length > 0) return val;
|
|
386
|
+
} else if (typeof ns === 'string' && ns.length > 0) {
|
|
387
|
+
return ns === 'Global' ? 'global' : ns;
|
|
374
388
|
}
|
|
375
389
|
}
|
|
376
390
|
} catch (_) {}
|
|
377
391
|
|
|
378
|
-
//
|
|
392
|
+
// ── Strategy 3: Company code from sys_properties ──
|
|
379
393
|
try {
|
|
380
394
|
var compResp = await client.get('/api/now/table/sys_properties', {
|
|
381
395
|
params: {
|
|
@@ -385,64 +399,41 @@ async function probeFlowFactoryNamespace(
|
|
|
385
399
|
}
|
|
386
400
|
});
|
|
387
401
|
var companyCode = compResp.data.result?.[0]?.value;
|
|
388
|
-
if (companyCode)
|
|
402
|
+
if (companyCode) return companyCode;
|
|
389
403
|
} catch (_) {}
|
|
390
404
|
|
|
391
|
-
//
|
|
405
|
+
// ── Strategy 4: HTTP probing as last resort ──
|
|
406
|
+
var probeCandidates = ['global', 'now'];
|
|
392
407
|
try {
|
|
393
408
|
var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
|
|
394
|
-
if (match && match[1])
|
|
409
|
+
if (match && match[1]) probeCandidates.push(match[1]);
|
|
395
410
|
} catch (_) {}
|
|
396
411
|
|
|
397
|
-
|
|
398
|
-
candidates.push('now', 'global');
|
|
399
|
-
|
|
400
|
-
// Deduplicate
|
|
401
|
-
var seen: Record<string, boolean> = {};
|
|
402
|
-
var unique: string[] = [];
|
|
403
|
-
for (var i = 0; i < candidates.length; i++) {
|
|
404
|
-
if (!seen[candidates[i]]) {
|
|
405
|
-
seen[candidates[i]] = true;
|
|
406
|
-
unique.push(candidates[i]);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// 5. Probe each candidate — GET the /discover endpoint (a real GET handler)
|
|
411
|
-
// If /discover doesn't exist yet, fall back to /create (expects 405)
|
|
412
|
-
for (var j = 0; j < unique.length; j++) {
|
|
413
|
-
// Try /discover first (GET endpoint, returns 200 when namespace is correct)
|
|
412
|
+
for (var j = 0; j < probeCandidates.length; j++) {
|
|
414
413
|
try {
|
|
415
|
-
var
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
var ds = discoverErr.response?.status;
|
|
421
|
-
if (ds === 401 || ds === 403) {
|
|
422
|
-
return unique[j]; // Namespace correct but auth issue
|
|
423
|
-
}
|
|
424
|
-
// 404 = wrong namespace OR /discover not deployed yet, try /create
|
|
425
|
-
}
|
|
426
|
-
// Fallback: try /create (POST-only, expect 405 for correct namespace)
|
|
427
|
-
try {
|
|
428
|
-
await client.get('/api/' + unique[j] + '/' + FLOW_FACTORY_API_ID + '/create');
|
|
429
|
-
return unique[j]; // 200 = unexpected but valid
|
|
430
|
-
} catch (createErr: any) {
|
|
431
|
-
var cs = createErr.response?.status;
|
|
432
|
-
if (cs === 405 || cs === 401 || cs === 403) {
|
|
433
|
-
return unique[j]; // Namespace correct — method or auth rejected
|
|
434
|
-
}
|
|
435
|
-
// 404 = wrong namespace, try next candidate
|
|
414
|
+
var probeResp = await client.get('/api/' + probeCandidates[j] + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
415
|
+
if (probeResp.status === 200 || probeResp.data) return probeCandidates[j];
|
|
416
|
+
} catch (probeErr: any) {
|
|
417
|
+
var ps = probeErr.response?.status;
|
|
418
|
+
if (ps === 401 || ps === 403 || ps === 405) return probeCandidates[j];
|
|
436
419
|
}
|
|
437
420
|
}
|
|
438
421
|
|
|
439
|
-
return null;
|
|
422
|
+
return null;
|
|
440
423
|
}
|
|
441
424
|
|
|
442
425
|
/**
|
|
443
426
|
* Ensure the Flow Factory Scripted REST API exists on the ServiceNow instance.
|
|
444
427
|
* Idempotent — checks cache first, then instance, deploys only if missing.
|
|
445
|
-
*
|
|
428
|
+
*
|
|
429
|
+
* Namespace resolution strategy:
|
|
430
|
+
* 1. Dot-walk to sys_scope.scope from the API record (deterministic, no HTTP probing)
|
|
431
|
+
* 2. Read namespace field with display_value=all (fallback)
|
|
432
|
+
* 3. Company code from sys_properties (fallback)
|
|
433
|
+
* 4. HTTP probing to /discover and /create endpoints (last resort)
|
|
434
|
+
*
|
|
435
|
+
* Stale API detection: if the API exists but has no /discover endpoint
|
|
436
|
+
* (created by an older tool version), it is deleted and redeployed.
|
|
446
437
|
*/
|
|
447
438
|
async function ensureFlowFactoryAPI(
|
|
448
439
|
client: any,
|
|
@@ -460,7 +451,7 @@ async function ensureFlowFactoryAPI(
|
|
|
460
451
|
|
|
461
452
|
_bootstrapPromise = (async () => {
|
|
462
453
|
try {
|
|
463
|
-
// 3. Check if API already exists on instance
|
|
454
|
+
// 3. Check if API already exists on instance
|
|
464
455
|
var checkResp = await client.get('/api/now/table/sys_ws_definition', {
|
|
465
456
|
params: {
|
|
466
457
|
sysparm_query: 'api_id=' + FLOW_FACTORY_API_ID,
|
|
@@ -471,21 +462,36 @@ async function ensureFlowFactoryAPI(
|
|
|
471
462
|
|
|
472
463
|
if (checkResp.data.result && checkResp.data.result.length > 0) {
|
|
473
464
|
var existing = checkResp.data.result[0];
|
|
474
|
-
var ns = await
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
|
|
465
|
+
var ns = await resolveFactoryNamespace(client, existing.sys_id, instanceUrl);
|
|
466
|
+
|
|
467
|
+
if (ns) {
|
|
468
|
+
// Verify the API has v5 endpoints (check /discover exists)
|
|
469
|
+
var hasDiscover = false;
|
|
479
470
|
try {
|
|
480
|
-
await client.
|
|
481
|
-
|
|
482
|
-
|
|
471
|
+
var verifyResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
472
|
+
hasDiscover = verifyResp.status === 200 || !!verifyResp.data;
|
|
473
|
+
} catch (verifyErr: any) {
|
|
474
|
+
var vs = verifyErr.response?.status;
|
|
475
|
+
// 401/403 = endpoint exists but auth issue; 405 = exists but wrong method
|
|
476
|
+
hasDiscover = vs === 401 || vs === 403 || vs === 405;
|
|
483
477
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
478
|
+
|
|
479
|
+
if (hasDiscover) {
|
|
480
|
+
_flowFactoryCache = { apiSysId: existing.sys_id, namespace: ns, timestamp: Date.now() };
|
|
481
|
+
return { namespace: ns, apiSysId: existing.sys_id };
|
|
482
|
+
}
|
|
483
|
+
// /discover missing → stale API from older version, fall through to delete
|
|
488
484
|
}
|
|
485
|
+
|
|
486
|
+
// Delete stale API and redeploy with current scripts
|
|
487
|
+
invalidateFlowFactoryCache();
|
|
488
|
+
try {
|
|
489
|
+
await client.delete('/api/now/table/sys_ws_definition/' + existing.sys_id);
|
|
490
|
+
} catch (_) {
|
|
491
|
+
// If delete fails, try deployment anyway — will error on duplicate api_id
|
|
492
|
+
}
|
|
493
|
+
// Brief pause to let ServiceNow finalize the delete
|
|
494
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
489
495
|
}
|
|
490
496
|
|
|
491
497
|
// 4. Deploy the Scripted REST API (do NOT set namespace — let ServiceNow assign it)
|
|
@@ -504,7 +510,7 @@ async function ensureFlowFactoryAPI(
|
|
|
504
510
|
throw new Error('Failed to create Scripted REST API definition — no sys_id returned');
|
|
505
511
|
}
|
|
506
512
|
|
|
507
|
-
//
|
|
513
|
+
// 5a. Deploy the POST /create resource
|
|
508
514
|
try {
|
|
509
515
|
await client.post('/api/now/table/sys_ws_operation', {
|
|
510
516
|
web_service_definition: apiSysId,
|
|
@@ -540,18 +546,25 @@ async function ensureFlowFactoryAPI(
|
|
|
540
546
|
// Non-fatal: create endpoint is more important than discover
|
|
541
547
|
}
|
|
542
548
|
|
|
543
|
-
// 6.
|
|
544
|
-
|
|
549
|
+
// 6. Resolve namespace — read directly from the record (deterministic)
|
|
550
|
+
var resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
545
551
|
|
|
546
|
-
// 7.
|
|
547
|
-
var resolvedNs = await probeFlowFactoryNamespace(client, apiSysId, instanceUrl);
|
|
552
|
+
// 7. If direct resolution failed, wait for REST framework and retry
|
|
548
553
|
if (!resolvedNs) {
|
|
549
|
-
// Retry once after extra delay — some instances are slow to register
|
|
550
554
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
551
|
-
resolvedNs = await
|
|
555
|
+
resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
556
|
+
}
|
|
557
|
+
if (!resolvedNs) {
|
|
558
|
+
// Last resort: try 'global' (most common for PDI instances)
|
|
559
|
+
try {
|
|
560
|
+
var globalTest = await client.get('/api/global/' + FLOW_FACTORY_API_ID + '/discover');
|
|
561
|
+
if (globalTest.status === 200 || globalTest.data) resolvedNs = 'global';
|
|
562
|
+
} catch (gtErr: any) {
|
|
563
|
+
if (gtErr.response?.status === 401 || gtErr.response?.status === 403) resolvedNs = 'global';
|
|
564
|
+
}
|
|
552
565
|
}
|
|
553
566
|
if (!resolvedNs) {
|
|
554
|
-
throw new Error('Flow Factory
|
|
567
|
+
throw new Error('Flow Factory deployed (sys_id=' + apiSysId + ') but namespace could not be resolved. This may indicate a ServiceNow scope/permissions issue.');
|
|
555
568
|
}
|
|
556
569
|
|
|
557
570
|
_flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
|