snow-flow 10.0.1-dev.397 → 10.0.1-dev.399
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,58 @@ 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
|
+
*
|
|
348
|
+
* Collects namespace candidates from multiple sources, then VERIFIES each
|
|
349
|
+
* one via HTTP before accepting it. This prevents returning invalid values
|
|
350
|
+
* like scope sys_ids or numeric identifiers that aren't valid URL namespaces.
|
|
351
|
+
*
|
|
352
|
+
* Verification: GET /api/{candidate}/{api_id}/discover
|
|
353
|
+
* - 200 = correct namespace, /discover endpoint works
|
|
354
|
+
* - 401/403 = correct namespace, auth issue
|
|
355
|
+
* - 405 = correct namespace, wrong HTTP method (endpoint exists)
|
|
356
|
+
* - 400/404 = wrong namespace or API not registered yet
|
|
350
357
|
*/
|
|
351
|
-
async function
|
|
358
|
+
async function resolveFactoryNamespace(
|
|
352
359
|
client: any,
|
|
353
360
|
apiSysId: string,
|
|
354
361
|
instanceUrl: string
|
|
355
362
|
): Promise<string | null> {
|
|
356
|
-
//
|
|
363
|
+
// ── Collect namespace candidates (ordered by likelihood) ──
|
|
357
364
|
var candidates: string[] = [];
|
|
358
365
|
|
|
359
|
-
//
|
|
366
|
+
// Most common for PDI / global scope custom APIs
|
|
367
|
+
candidates.push('global');
|
|
368
|
+
|
|
369
|
+
// Dot-walk to sys_scope.scope
|
|
370
|
+
try {
|
|
371
|
+
var dotWalkResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
372
|
+
params: {
|
|
373
|
+
sysparm_fields: 'namespace.scope',
|
|
374
|
+
sysparm_display_value: 'false'
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
var scopeStr = dotWalkResp.data.result?.['namespace.scope'];
|
|
378
|
+
if (scopeStr && typeof scopeStr === 'string' && scopeStr.length > 1) {
|
|
379
|
+
candidates.push(scopeStr);
|
|
380
|
+
}
|
|
381
|
+
} catch (_) {}
|
|
382
|
+
|
|
383
|
+
// Read namespace display_value (might be scope string itself)
|
|
360
384
|
try {
|
|
361
385
|
var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
362
|
-
params: { sysparm_fields: '
|
|
386
|
+
params: { sysparm_fields: 'namespace', sysparm_display_value: 'true' }
|
|
363
387
|
});
|
|
364
|
-
var
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (typeof ns === 'object') {
|
|
368
|
-
var dv = ns.display_value || '';
|
|
369
|
-
var val = ns.value || '';
|
|
370
|
-
if (dv && /^(x_|sn_)/.test(dv)) candidates.push(dv);
|
|
371
|
-
if (val && /^(x_|sn_)/.test(val)) candidates.push(val);
|
|
372
|
-
} else if (typeof ns === 'string' && ns.length > 0 && ns !== 'Global' && ns !== 'global') {
|
|
373
|
-
candidates.push(ns);
|
|
374
|
-
}
|
|
388
|
+
var nsDisplay = nsResp.data.result?.namespace;
|
|
389
|
+
if (typeof nsDisplay === 'string' && nsDisplay.length > 1) {
|
|
390
|
+
candidates.push(nsDisplay === 'Global' ? 'global' : nsDisplay);
|
|
375
391
|
}
|
|
376
392
|
} catch (_) {}
|
|
377
393
|
|
|
378
|
-
//
|
|
394
|
+
// OOB namespace
|
|
395
|
+
candidates.push('now');
|
|
396
|
+
|
|
397
|
+
// Company code from sys_properties
|
|
379
398
|
try {
|
|
380
399
|
var compResp = await client.get('/api/now/table/sys_properties', {
|
|
381
400
|
params: {
|
|
@@ -388,61 +407,76 @@ async function probeFlowFactoryNamespace(
|
|
|
388
407
|
if (companyCode) candidates.push(companyCode);
|
|
389
408
|
} catch (_) {}
|
|
390
409
|
|
|
391
|
-
//
|
|
410
|
+
// Instance subdomain (e.g. "dev354059")
|
|
392
411
|
try {
|
|
393
412
|
var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
|
|
394
413
|
if (match && match[1]) candidates.push(match[1]);
|
|
395
414
|
} catch (_) {}
|
|
396
415
|
|
|
397
|
-
//
|
|
398
|
-
candidates.push('now', 'global');
|
|
399
|
-
|
|
400
|
-
// Deduplicate
|
|
416
|
+
// ── Deduplicate ──
|
|
401
417
|
var seen: Record<string, boolean> = {};
|
|
402
418
|
var unique: string[] = [];
|
|
403
419
|
for (var i = 0; i < candidates.length; i++) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
420
|
+
var lower = candidates[i].toLowerCase();
|
|
421
|
+
if (!seen[lower]) {
|
|
422
|
+
seen[lower] = true;
|
|
423
|
+
unique.push(lower);
|
|
407
424
|
}
|
|
408
425
|
}
|
|
409
426
|
|
|
410
|
-
//
|
|
411
|
-
// If /discover doesn't exist yet, fall back to /create (expects 405)
|
|
427
|
+
// ── Verify each candidate via HTTP ──
|
|
412
428
|
for (var j = 0; j < unique.length; j++) {
|
|
413
|
-
|
|
429
|
+
var ns = unique[j];
|
|
430
|
+
// Check 1: GET /discover (v5 endpoint, returns 200 with discovery data)
|
|
414
431
|
try {
|
|
415
|
-
var discoverResp = await client.get('/api/' +
|
|
416
|
-
if (discoverResp.status === 200 || discoverResp.data)
|
|
417
|
-
return unique[j]; // Namespace confirmed via /discover
|
|
418
|
-
}
|
|
432
|
+
var discoverResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
433
|
+
if (discoverResp.status === 200 || discoverResp.data) return ns;
|
|
419
434
|
} catch (discoverErr: any) {
|
|
420
435
|
var ds = discoverErr.response?.status;
|
|
421
|
-
if (ds === 401 || ds === 403)
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
// 404 = wrong namespace OR /discover not deployed yet, try /create
|
|
436
|
+
if (ds === 401 || ds === 403 || ds === 405) return ns;
|
|
437
|
+
// 400/404 = wrong namespace or not registered yet
|
|
425
438
|
}
|
|
426
|
-
//
|
|
439
|
+
// Check 2: GET /create (POST-only, expect 405 for correct namespace)
|
|
427
440
|
try {
|
|
428
|
-
await client.get('/api/' +
|
|
429
|
-
return
|
|
441
|
+
await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create');
|
|
442
|
+
return ns; // 200 = unexpected but valid
|
|
430
443
|
} catch (createErr: any) {
|
|
431
444
|
var cs = createErr.response?.status;
|
|
432
|
-
if (cs === 405 || cs === 401 || cs === 403)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
445
|
+
if (cs === 405 || cs === 401 || cs === 403) return ns;
|
|
446
|
+
// 400/404 = wrong namespace (ServiceNow may return 400 instead of 405)
|
|
447
|
+
}
|
|
448
|
+
// Check 3: POST /create with empty body — distinguishes "wrong namespace" from
|
|
449
|
+
// "correct namespace but script validation error". Wrong namespace returns
|
|
450
|
+
// 400 with "Requested URI does not represent any resource". Our script returns
|
|
451
|
+
// a different error body (e.g. {success:false, error:"..."}).
|
|
452
|
+
try {
|
|
453
|
+
await client.post('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/create', {});
|
|
454
|
+
return ns; // Unexpected success, namespace confirmed
|
|
455
|
+
} catch (postErr: any) {
|
|
456
|
+
var pe = postErr.response;
|
|
457
|
+
if (!pe) continue;
|
|
458
|
+
if (pe.status === 401 || pe.status === 403) return ns;
|
|
459
|
+
// Check error body — "Requested URI" = wrong namespace, anything else = our script
|
|
460
|
+
var errStr = JSON.stringify(pe.data || '');
|
|
461
|
+
if (!errStr.includes('Requested URI')) return ns;
|
|
436
462
|
}
|
|
437
463
|
}
|
|
438
464
|
|
|
439
|
-
return null; // No
|
|
465
|
+
return null; // No candidate verified — API may not be registered yet
|
|
440
466
|
}
|
|
441
467
|
|
|
442
468
|
/**
|
|
443
469
|
* Ensure the Flow Factory Scripted REST API exists on the ServiceNow instance.
|
|
444
470
|
* Idempotent — checks cache first, then instance, deploys only if missing.
|
|
445
|
-
*
|
|
471
|
+
*
|
|
472
|
+
* Namespace resolution strategy:
|
|
473
|
+
* 1. Dot-walk to sys_scope.scope from the API record (deterministic, no HTTP probing)
|
|
474
|
+
* 2. Read namespace field with display_value=all (fallback)
|
|
475
|
+
* 3. Company code from sys_properties (fallback)
|
|
476
|
+
* 4. HTTP probing to /discover and /create endpoints (last resort)
|
|
477
|
+
*
|
|
478
|
+
* Stale API detection: if the API exists but has no /discover endpoint
|
|
479
|
+
* (created by an older tool version), it is deleted and redeployed.
|
|
446
480
|
*/
|
|
447
481
|
async function ensureFlowFactoryAPI(
|
|
448
482
|
client: any,
|
|
@@ -460,7 +494,7 @@ async function ensureFlowFactoryAPI(
|
|
|
460
494
|
|
|
461
495
|
_bootstrapPromise = (async () => {
|
|
462
496
|
try {
|
|
463
|
-
// 3. Check if API already exists on instance
|
|
497
|
+
// 3. Check if API already exists on instance
|
|
464
498
|
var checkResp = await client.get('/api/now/table/sys_ws_definition', {
|
|
465
499
|
params: {
|
|
466
500
|
sysparm_query: 'api_id=' + FLOW_FACTORY_API_ID,
|
|
@@ -471,21 +505,36 @@ async function ensureFlowFactoryAPI(
|
|
|
471
505
|
|
|
472
506
|
if (checkResp.data.result && checkResp.data.result.length > 0) {
|
|
473
507
|
var existing = checkResp.data.result[0];
|
|
474
|
-
var ns = await
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
|
|
508
|
+
var ns = await resolveFactoryNamespace(client, existing.sys_id, instanceUrl);
|
|
509
|
+
|
|
510
|
+
if (ns) {
|
|
511
|
+
// Verify the API has v5 endpoints (check /discover exists)
|
|
512
|
+
var hasDiscover = false;
|
|
479
513
|
try {
|
|
480
|
-
await client.
|
|
481
|
-
|
|
482
|
-
|
|
514
|
+
var verifyResp = await client.get('/api/' + ns + '/' + FLOW_FACTORY_API_ID + '/discover');
|
|
515
|
+
hasDiscover = verifyResp.status === 200 || !!verifyResp.data;
|
|
516
|
+
} catch (verifyErr: any) {
|
|
517
|
+
var vs = verifyErr.response?.status;
|
|
518
|
+
// 401/403 = endpoint exists but auth issue; 405 = exists but wrong method
|
|
519
|
+
hasDiscover = vs === 401 || vs === 403 || vs === 405;
|
|
483
520
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
521
|
+
|
|
522
|
+
if (hasDiscover) {
|
|
523
|
+
_flowFactoryCache = { apiSysId: existing.sys_id, namespace: ns, timestamp: Date.now() };
|
|
524
|
+
return { namespace: ns, apiSysId: existing.sys_id };
|
|
525
|
+
}
|
|
526
|
+
// /discover missing → stale API from older version, fall through to delete
|
|
488
527
|
}
|
|
528
|
+
|
|
529
|
+
// Delete stale API and redeploy with current scripts
|
|
530
|
+
invalidateFlowFactoryCache();
|
|
531
|
+
try {
|
|
532
|
+
await client.delete('/api/now/table/sys_ws_definition/' + existing.sys_id);
|
|
533
|
+
} catch (_) {
|
|
534
|
+
// If delete fails, try deployment anyway — will error on duplicate api_id
|
|
535
|
+
}
|
|
536
|
+
// Brief pause to let ServiceNow finalize the delete
|
|
537
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
489
538
|
}
|
|
490
539
|
|
|
491
540
|
// 4. Deploy the Scripted REST API (do NOT set namespace — let ServiceNow assign it)
|
|
@@ -504,7 +553,7 @@ async function ensureFlowFactoryAPI(
|
|
|
504
553
|
throw new Error('Failed to create Scripted REST API definition — no sys_id returned');
|
|
505
554
|
}
|
|
506
555
|
|
|
507
|
-
//
|
|
556
|
+
// 5a. Deploy the POST /create resource
|
|
508
557
|
try {
|
|
509
558
|
await client.post('/api/now/table/sys_ws_operation', {
|
|
510
559
|
web_service_definition: apiSysId,
|
|
@@ -540,18 +589,19 @@ async function ensureFlowFactoryAPI(
|
|
|
540
589
|
// Non-fatal: create endpoint is more important than discover
|
|
541
590
|
}
|
|
542
591
|
|
|
543
|
-
// 6. Wait for ServiceNow REST framework to register
|
|
544
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
592
|
+
// 6. Wait for ServiceNow REST framework to register endpoints
|
|
593
|
+
await new Promise(resolve => setTimeout(resolve, 4000));
|
|
594
|
+
|
|
595
|
+
// 7. Resolve namespace via HTTP-verified probing
|
|
596
|
+
var resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
545
597
|
|
|
546
|
-
//
|
|
547
|
-
var resolvedNs = await probeFlowFactoryNamespace(client, apiSysId, instanceUrl);
|
|
598
|
+
// 8. If resolution failed, wait longer and retry (some instances are slow)
|
|
548
599
|
if (!resolvedNs) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
resolvedNs = await probeFlowFactoryNamespace(client, apiSysId, instanceUrl);
|
|
600
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
601
|
+
resolvedNs = await resolveFactoryNamespace(client, apiSysId, instanceUrl);
|
|
552
602
|
}
|
|
553
603
|
if (!resolvedNs) {
|
|
554
|
-
throw new Error('Flow Factory
|
|
604
|
+
throw new Error('Flow Factory deployed (sys_id=' + apiSysId + ') but no namespace candidate could be verified via HTTP after 9s. Candidates tried: global, dot-walk scope, display_value, now, company code, subdomain.');
|
|
555
605
|
}
|
|
556
606
|
|
|
557
607
|
_flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
|