snow-flow-test 10.0.1-test.109 → 10.0.1-test.111
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
|
@@ -35,7 +35,6 @@ function isSysId(value: string): boolean {
|
|
|
35
35
|
|
|
36
36
|
var FLOW_FACTORY_API_NAME = 'Snow-Flow Flow Factory';
|
|
37
37
|
var FLOW_FACTORY_API_ID = 'flow_factory';
|
|
38
|
-
var FLOW_FACTORY_NAMESPACE = 'x_snflw';
|
|
39
38
|
var FLOW_FACTORY_CACHE_TTL = 300000; // 5 minutes
|
|
40
39
|
|
|
41
40
|
var _flowFactoryCache: { apiSysId: string; namespace: string; timestamp: number } | null = null;
|
|
@@ -48,8 +47,29 @@ var _bootstrapPromise: Promise<{ namespace: string; apiSysId: string }> | null =
|
|
|
48
47
|
*/
|
|
49
48
|
var FLOW_FACTORY_SCRIPT = [
|
|
50
49
|
'(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {',
|
|
51
|
-
' var body =
|
|
52
|
-
' var
|
|
50
|
+
' var body = null;',
|
|
51
|
+
' var parseLog = [];',
|
|
52
|
+
' try {',
|
|
53
|
+
' var ds = request.body.dataString;',
|
|
54
|
+
' if (ds) { body = JSON.parse(ds + ""); parseLog.push("dataString:ok"); }',
|
|
55
|
+
' } catch(e1) { parseLog.push("dataString:" + e1); }',
|
|
56
|
+
' if (!body) {',
|
|
57
|
+
' try {',
|
|
58
|
+
' var d = request.body.data;',
|
|
59
|
+
' if (d && typeof d === "object") { body = d; parseLog.push("data:ok"); }',
|
|
60
|
+
' else if (d) { body = JSON.parse(d + ""); parseLog.push("data-parse:ok"); }',
|
|
61
|
+
' } catch(e2) { parseLog.push("data:" + e2); }',
|
|
62
|
+
' }',
|
|
63
|
+
' if (!body) {',
|
|
64
|
+
' try { body = JSON.parse(request.body + ""); parseLog.push("body-direct:ok"); }',
|
|
65
|
+
' catch(e3) { parseLog.push("body-direct:" + e3); }',
|
|
66
|
+
' }',
|
|
67
|
+
' if (!body || typeof body !== "object") {',
|
|
68
|
+
' response.setStatus(400);',
|
|
69
|
+
' response.setBody({ success: false, error: "No parseable body", parseLog: parseLog, bodyType: typeof request.body });',
|
|
70
|
+
' return;',
|
|
71
|
+
' }',
|
|
72
|
+
' var result = { success: false, steps: {}, parseLog: parseLog };',
|
|
53
73
|
'',
|
|
54
74
|
' try {',
|
|
55
75
|
' var flowName = body.name || "Unnamed Flow";',
|
|
@@ -101,6 +121,9 @@ var FLOW_FACTORY_SCRIPT = [
|
|
|
101
121
|
' ver.setValue("version", "1.0");',
|
|
102
122
|
' ver.setValue("state", shouldActivate ? "published" : "draft");',
|
|
103
123
|
' ver.setValue("active", true);',
|
|
124
|
+
' ver.setValue("compile_state", "compiled");',
|
|
125
|
+
' ver.setValue("is_current", true);',
|
|
126
|
+
' if (shouldActivate) ver.setValue("published_flow", flowSysId);',
|
|
104
127
|
' if (body.flow_definition) {',
|
|
105
128
|
' ver.setValue("flow_definition", typeof body.flow_definition === "string" ? body.flow_definition : JSON.stringify(body.flow_definition));',
|
|
106
129
|
' }',
|
|
@@ -229,12 +252,97 @@ var FLOW_FACTORY_SCRIPT = [
|
|
|
229
252
|
'})(request, response);'
|
|
230
253
|
].join('\n');
|
|
231
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Probe the ServiceNow instance to discover the correct URL namespace for
|
|
257
|
+
* the Flow Factory Scripted REST API. Sends GET requests to candidate
|
|
258
|
+
* namespaces — a 405 (Method Not Allowed) or 401/403 confirms the namespace
|
|
259
|
+
* exists, while 404 means wrong namespace.
|
|
260
|
+
*/
|
|
261
|
+
async function probeFlowFactoryNamespace(
|
|
262
|
+
client: any,
|
|
263
|
+
apiSysId: string,
|
|
264
|
+
instanceUrl: string
|
|
265
|
+
): Promise<string | null> {
|
|
266
|
+
// Build candidate list from multiple sources
|
|
267
|
+
var candidates: string[] = [];
|
|
268
|
+
|
|
269
|
+
// 1. Read back the record's namespace field
|
|
270
|
+
try {
|
|
271
|
+
var nsResp = await client.get('/api/now/table/sys_ws_definition/' + apiSysId, {
|
|
272
|
+
params: { sysparm_fields: 'sys_id,api_id,namespace', sysparm_display_value: 'all' }
|
|
273
|
+
});
|
|
274
|
+
var record = nsResp.data.result || {};
|
|
275
|
+
var ns = record.namespace;
|
|
276
|
+
if (ns) {
|
|
277
|
+
if (typeof ns === 'object') {
|
|
278
|
+
var dv = ns.display_value || '';
|
|
279
|
+
var val = ns.value || '';
|
|
280
|
+
if (dv && /^(x_|sn_)/.test(dv)) candidates.push(dv);
|
|
281
|
+
if (val && /^(x_|sn_)/.test(val)) candidates.push(val);
|
|
282
|
+
} else if (typeof ns === 'string' && ns.length > 0 && ns !== 'Global' && ns !== 'global') {
|
|
283
|
+
candidates.push(ns);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (_) {}
|
|
287
|
+
|
|
288
|
+
// 2. Company code from sys_properties
|
|
289
|
+
try {
|
|
290
|
+
var compResp = await client.get('/api/now/table/sys_properties', {
|
|
291
|
+
params: {
|
|
292
|
+
sysparm_query: 'name=glide.appcreator.company.code',
|
|
293
|
+
sysparm_fields: 'value',
|
|
294
|
+
sysparm_limit: 1
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
var companyCode = compResp.data.result?.[0]?.value;
|
|
298
|
+
if (companyCode) candidates.push(companyCode);
|
|
299
|
+
} catch (_) {}
|
|
300
|
+
|
|
301
|
+
// 3. Instance subdomain (e.g. "dev351277" from "https://dev351277.service-now.com")
|
|
302
|
+
try {
|
|
303
|
+
var match = instanceUrl.match(/https?:\/\/([^.]+)\./);
|
|
304
|
+
if (match && match[1]) candidates.push(match[1]);
|
|
305
|
+
} catch (_) {}
|
|
306
|
+
|
|
307
|
+
// 4. Fixed fallbacks
|
|
308
|
+
candidates.push('now', 'global');
|
|
309
|
+
|
|
310
|
+
// Deduplicate
|
|
311
|
+
var seen: Record<string, boolean> = {};
|
|
312
|
+
var unique: string[] = [];
|
|
313
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
314
|
+
if (!seen[candidates[i]]) {
|
|
315
|
+
seen[candidates[i]] = true;
|
|
316
|
+
unique.push(candidates[i]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 5. Probe each candidate — GET on a POST-only endpoint
|
|
321
|
+
for (var j = 0; j < unique.length; j++) {
|
|
322
|
+
try {
|
|
323
|
+
await client.get('/api/' + unique[j] + '/' + FLOW_FACTORY_API_ID + '/create');
|
|
324
|
+
return unique[j]; // 200 = endpoint exists (unlikely but valid)
|
|
325
|
+
} catch (probeError: any) {
|
|
326
|
+
var status = probeError.response?.status;
|
|
327
|
+
if (status === 405 || status === 401 || status === 403) {
|
|
328
|
+
return unique[j]; // Namespace correct — method or auth rejected
|
|
329
|
+
}
|
|
330
|
+
// 404 = wrong namespace, try next
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return null; // No namespace matched — factory unreachable
|
|
335
|
+
}
|
|
336
|
+
|
|
232
337
|
/**
|
|
233
338
|
* Ensure the Flow Factory Scripted REST API exists on the ServiceNow instance.
|
|
234
339
|
* Idempotent — checks cache first, then instance, deploys only if missing.
|
|
235
|
-
* Uses
|
|
340
|
+
* Uses namespace probing (HTTP GET) instead of record field parsing.
|
|
236
341
|
*/
|
|
237
|
-
async function ensureFlowFactoryAPI(
|
|
342
|
+
async function ensureFlowFactoryAPI(
|
|
343
|
+
client: any,
|
|
344
|
+
instanceUrl: string
|
|
345
|
+
): Promise<{ namespace: string; apiSysId: string }> {
|
|
238
346
|
// 1. Check in-memory cache
|
|
239
347
|
if (_flowFactoryCache && (Date.now() - _flowFactoryCache.timestamp) < FLOW_FACTORY_CACHE_TTL) {
|
|
240
348
|
return { namespace: _flowFactoryCache.namespace, apiSysId: _flowFactoryCache.apiSysId };
|
|
@@ -258,7 +366,10 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
|
|
|
258
366
|
|
|
259
367
|
if (checkResp.data.result && checkResp.data.result.length > 0) {
|
|
260
368
|
var existing = checkResp.data.result[0];
|
|
261
|
-
var ns =
|
|
369
|
+
var ns = await probeFlowFactoryNamespace(client, existing.sys_id, instanceUrl);
|
|
370
|
+
if (!ns) {
|
|
371
|
+
throw new Error('Flow Factory API exists (sys_id=' + existing.sys_id + ') but namespace could not be resolved via HTTP probing');
|
|
372
|
+
}
|
|
262
373
|
_flowFactoryCache = { apiSysId: existing.sys_id, namespace: ns, timestamp: Date.now() };
|
|
263
374
|
return { namespace: ns, apiSysId: existing.sys_id };
|
|
264
375
|
}
|
|
@@ -270,7 +381,7 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
|
|
|
270
381
|
active: true,
|
|
271
382
|
short_description: 'Bootstrapped by Snow-Flow MCP for reliable Flow Designer creation via GlideRecord',
|
|
272
383
|
is_versioned: false,
|
|
273
|
-
enforce_acl: '
|
|
384
|
+
enforce_acl: 'false',
|
|
274
385
|
requires_authentication: true
|
|
275
386
|
});
|
|
276
387
|
|
|
@@ -290,7 +401,7 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
|
|
|
290
401
|
short_description: 'Create a flow or subflow with GlideRecord (triggers all BRs + version record)',
|
|
291
402
|
operation_script: FLOW_FACTORY_SCRIPT,
|
|
292
403
|
requires_authentication: true,
|
|
293
|
-
enforce_acl: '
|
|
404
|
+
enforce_acl: 'false'
|
|
294
405
|
});
|
|
295
406
|
} catch (opError: any) {
|
|
296
407
|
// Cleanup the API definition if operation creation fails
|
|
@@ -298,11 +409,11 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
|
|
|
298
409
|
throw new Error('Failed to create Scripted REST operation: ' + (opError.message || opError));
|
|
299
410
|
}
|
|
300
411
|
|
|
301
|
-
// 6.
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
412
|
+
// 6. Probe to discover the namespace ServiceNow assigned
|
|
413
|
+
var resolvedNs = await probeFlowFactoryNamespace(client, apiSysId, instanceUrl);
|
|
414
|
+
if (!resolvedNs) {
|
|
415
|
+
throw new Error('Flow Factory API created (sys_id=' + apiSysId + ') but namespace could not be resolved via HTTP probing');
|
|
416
|
+
}
|
|
306
417
|
|
|
307
418
|
_flowFactoryCache = { apiSysId: apiSysId, namespace: resolvedNs, timestamp: Date.now() };
|
|
308
419
|
return { namespace: resolvedNs, apiSysId: apiSysId };
|
|
@@ -316,33 +427,92 @@ async function ensureFlowFactoryAPI(client: any): Promise<{ namespace: string; a
|
|
|
316
427
|
}
|
|
317
428
|
|
|
318
429
|
/**
|
|
319
|
-
*
|
|
320
|
-
* The namespace field can be a string, a reference object, or empty.
|
|
321
|
-
* For global scope APIs the namespace is typically the instance company prefix or 'now'.
|
|
430
|
+
* Invalidate the Flow Factory cache (e.g. on 404 when API was deleted externally).
|
|
322
431
|
*/
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
if (!ns) return FLOW_FACTORY_NAMESPACE;
|
|
326
|
-
|
|
327
|
-
// display_value / value pair (sysparm_display_value=all)
|
|
328
|
-
if (typeof ns === 'object') {
|
|
329
|
-
// display_value is the scope name like "Global" or "x_snflw" — use value for sys_id, display_value for name
|
|
330
|
-
var dv = ns.display_value || '';
|
|
331
|
-
// If display_value looks like a scope namespace (x_something, sn_something, now, global), use it
|
|
332
|
-
if (dv && dv !== 'Global' && dv !== 'global') return dv;
|
|
333
|
-
// For Global scope, fall through to default
|
|
334
|
-
}
|
|
335
|
-
if (typeof ns === 'string' && ns.length > 0 && ns.length < 100) {
|
|
336
|
-
return ns;
|
|
337
|
-
}
|
|
338
|
-
return FLOW_FACTORY_NAMESPACE;
|
|
432
|
+
function invalidateFlowFactoryCache(): void {
|
|
433
|
+
_flowFactoryCache = null;
|
|
339
434
|
}
|
|
340
435
|
|
|
341
436
|
/**
|
|
342
|
-
*
|
|
437
|
+
* After creating a flow (via any method), try to register it with the
|
|
438
|
+
* Flow Designer engine by calling its built-in compile / publish / activate
|
|
439
|
+
* REST endpoints. Without this step the flow record exists in the DB but
|
|
440
|
+
* Flow Designer cannot open it ("Your flow cannot be found").
|
|
441
|
+
*
|
|
442
|
+
* Tries multiple known endpoint patterns because the namespace/path changed
|
|
443
|
+
* across ServiceNow releases. Returns diagnostics on which attempts were
|
|
444
|
+
* made and what succeeded.
|
|
343
445
|
*/
|
|
344
|
-
function
|
|
345
|
-
|
|
446
|
+
async function registerFlowWithEngine(
|
|
447
|
+
client: any,
|
|
448
|
+
flowSysId: string,
|
|
449
|
+
shouldActivate: boolean
|
|
450
|
+
): Promise<{ success: boolean; method: string; attempts: string[] }> {
|
|
451
|
+
var attempts: string[] = [];
|
|
452
|
+
|
|
453
|
+
// Helper: try a POST and classify the result
|
|
454
|
+
async function tryPost(label: string, url: string, body?: any): Promise<boolean> {
|
|
455
|
+
try {
|
|
456
|
+
await client.post(url, body || {});
|
|
457
|
+
attempts.push(label + ': success');
|
|
458
|
+
return true;
|
|
459
|
+
} catch (e: any) {
|
|
460
|
+
var s = e.response?.status || 'err';
|
|
461
|
+
attempts.push(label + ': ' + s);
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── Strategy 1: Publish via Flow Designer REST API ───────────────
|
|
467
|
+
// This is the closest equivalent to clicking "Publish" in the UI.
|
|
468
|
+
var publishPaths = [
|
|
469
|
+
'/api/sn_fd/flow/' + flowSysId + '/publish',
|
|
470
|
+
'/api/sn_fd/designer/flow/' + flowSysId + '/publish',
|
|
471
|
+
'/api/sn_flow_designer/flow/' + flowSysId + '/publish',
|
|
472
|
+
];
|
|
473
|
+
for (var pi = 0; pi < publishPaths.length; pi++) {
|
|
474
|
+
if (await tryPost('publish[' + pi + ']', publishPaths[pi])) {
|
|
475
|
+
return { success: true, method: 'publish', attempts: attempts };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── Strategy 2: Activate (registers the flow with the engine) ────
|
|
480
|
+
if (shouldActivate) {
|
|
481
|
+
var activatePaths = [
|
|
482
|
+
'/api/sn_fd/flow/' + flowSysId + '/activate',
|
|
483
|
+
'/api/sn_fd/designer/flow/' + flowSysId + '/activate',
|
|
484
|
+
'/api/sn_flow_designer/flow/' + flowSysId + '/activate',
|
|
485
|
+
];
|
|
486
|
+
for (var ai = 0; ai < activatePaths.length; ai++) {
|
|
487
|
+
if (await tryPost('activate[' + ai + ']', activatePaths[ai])) {
|
|
488
|
+
return { success: true, method: 'activate', attempts: attempts };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ── Strategy 3: Checkout + checkin (triggers internal compilation) ─
|
|
494
|
+
var checkoutPaths = [
|
|
495
|
+
'/api/sn_fd/flow/' + flowSysId + '/checkout',
|
|
496
|
+
'/api/sn_fd/designer/flow/' + flowSysId + '/checkout',
|
|
497
|
+
];
|
|
498
|
+
for (var ci = 0; ci < checkoutPaths.length; ci++) {
|
|
499
|
+
if (await tryPost('checkout[' + ci + ']', checkoutPaths[ci])) {
|
|
500
|
+
var checkinPath = checkoutPaths[ci].replace('/checkout', '/checkin');
|
|
501
|
+
await tryPost('checkin[' + ci + ']', checkinPath);
|
|
502
|
+
return { success: true, method: 'checkout+checkin', attempts: attempts };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ── Strategy 4: Snapshot (already existed — triggers version snapshot) ─
|
|
507
|
+
if (await tryPost('snapshot', '/api/sn_flow_designer/flow/snapshot', { flow_id: flowSysId })) {
|
|
508
|
+
return { success: true, method: 'snapshot', attempts: attempts };
|
|
509
|
+
}
|
|
510
|
+
// Try alternate snapshot path
|
|
511
|
+
if (await tryPost('snapshot-alt', '/api/sn_fd/flow/' + flowSysId + '/snapshot')) {
|
|
512
|
+
return { success: true, method: 'snapshot-alt', attempts: attempts };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return { success: false, method: 'none', attempts: attempts };
|
|
346
516
|
}
|
|
347
517
|
|
|
348
518
|
/**
|
|
@@ -592,8 +762,23 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
592
762
|
var actionsCreated = 0;
|
|
593
763
|
var varsCreated = 0;
|
|
594
764
|
|
|
765
|
+
// Diagnostics: track every step for debugging "flow cannot be found" issues
|
|
766
|
+
var diagnostics: any = {
|
|
767
|
+
factory_bootstrap: null,
|
|
768
|
+
factory_namespace: null,
|
|
769
|
+
factory_call: null,
|
|
770
|
+
table_api_used: false,
|
|
771
|
+
version_created: false,
|
|
772
|
+
version_method: null,
|
|
773
|
+
version_fields_set: [] as string[],
|
|
774
|
+
engine_registration: null,
|
|
775
|
+
post_verify: null
|
|
776
|
+
};
|
|
777
|
+
|
|
595
778
|
try {
|
|
596
|
-
var factory = await ensureFlowFactoryAPI(client);
|
|
779
|
+
var factory = await ensureFlowFactoryAPI(client, context.instanceUrl);
|
|
780
|
+
diagnostics.factory_bootstrap = 'success';
|
|
781
|
+
diagnostics.factory_namespace = factory.namespace;
|
|
597
782
|
|
|
598
783
|
var factoryPayload = {
|
|
599
784
|
name: flowName,
|
|
@@ -622,7 +807,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
622
807
|
// 404 = API was deleted externally → invalidate cache, retry once
|
|
623
808
|
if (callError.response?.status === 404) {
|
|
624
809
|
invalidateFlowFactoryCache();
|
|
625
|
-
var retryFactory = await ensureFlowFactoryAPI(client);
|
|
810
|
+
var retryFactory = await ensureFlowFactoryAPI(client, context.instanceUrl);
|
|
811
|
+
diagnostics.factory_namespace = retryFactory.namespace;
|
|
626
812
|
var retryEndpoint = '/api/' + retryFactory.namespace + '/' + FLOW_FACTORY_API_ID + '/create';
|
|
627
813
|
factoryResp = await client.post(retryEndpoint, factoryPayload);
|
|
628
814
|
} else {
|
|
@@ -632,9 +818,13 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
632
818
|
|
|
633
819
|
var factoryResult = factoryResp.data?.result || factoryResp.data;
|
|
634
820
|
if (factoryResult && factoryResult.success && factoryResult.flow_sys_id) {
|
|
821
|
+
diagnostics.factory_call = 'success';
|
|
635
822
|
flowSysId = factoryResult.flow_sys_id;
|
|
636
823
|
usedMethod = 'scripted_rest_api';
|
|
637
824
|
versionCreated = !!factoryResult.version_created;
|
|
825
|
+
diagnostics.version_created = versionCreated;
|
|
826
|
+
diagnostics.version_method = 'factory';
|
|
827
|
+
diagnostics.version_fields_set = ['flow', 'name', 'version', 'state', 'active', 'compile_state', 'is_current', 'published_flow'];
|
|
638
828
|
|
|
639
829
|
// Extract step details
|
|
640
830
|
var steps = factoryResult.steps || {};
|
|
@@ -657,14 +847,19 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
657
847
|
} catch (factoryError: any) {
|
|
658
848
|
// Flow Factory unavailable — fall through to Table API
|
|
659
849
|
var statusCode = factoryError.response?.status;
|
|
850
|
+
var factoryErrMsg = statusCode
|
|
851
|
+
? 'HTTP ' + statusCode + ': ' + (factoryError.response?.data?.error?.message || factoryError.message || 'unknown')
|
|
852
|
+
: (factoryError.message || 'unknown');
|
|
853
|
+
diagnostics.factory_bootstrap = diagnostics.factory_bootstrap || ('error: ' + factoryErrMsg);
|
|
854
|
+
diagnostics.factory_call = diagnostics.factory_call || ('error: ' + factoryErrMsg);
|
|
660
855
|
if (statusCode !== 403) {
|
|
661
|
-
|
|
662
|
-
factoryWarnings.push('Flow Factory unavailable (' + (statusCode || factoryError.message || 'unknown') + '), using Table API fallback');
|
|
856
|
+
factoryWarnings.push('Flow Factory unavailable (' + factoryErrMsg + '), using Table API fallback');
|
|
663
857
|
}
|
|
664
858
|
}
|
|
665
859
|
|
|
666
860
|
// ── Table API fallback (existing logic) ─────────────────────
|
|
667
861
|
if (!flowSysId) {
|
|
862
|
+
diagnostics.table_api_used = true;
|
|
668
863
|
var flowData: any = {
|
|
669
864
|
name: flowName,
|
|
670
865
|
description: flowDescription,
|
|
@@ -691,12 +886,18 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
691
886
|
version: '1.0',
|
|
692
887
|
state: shouldActivate ? 'published' : 'draft',
|
|
693
888
|
active: true,
|
|
889
|
+
compile_state: 'compiled',
|
|
890
|
+
is_current: true,
|
|
694
891
|
flow_definition: JSON.stringify(flowDefinition)
|
|
695
892
|
};
|
|
893
|
+
if (shouldActivate) versionData.published_flow = flowSysId;
|
|
696
894
|
var versionResp = await client.post('/api/now/table/sys_hub_flow_version', versionData);
|
|
697
895
|
var versionSysId = versionResp.data.result?.sys_id;
|
|
698
896
|
if (versionSysId) {
|
|
699
897
|
versionCreated = true;
|
|
898
|
+
diagnostics.version_created = true;
|
|
899
|
+
diagnostics.version_method = 'table_api';
|
|
900
|
+
diagnostics.version_fields_set = ['flow', 'name', 'version', 'state', 'active', 'compile_state', 'is_current', 'published_flow'];
|
|
700
901
|
// Link flow to its version
|
|
701
902
|
try {
|
|
702
903
|
await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, {
|
|
@@ -814,10 +1015,88 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
814
1015
|
}
|
|
815
1016
|
}
|
|
816
1017
|
|
|
817
|
-
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// ── Register flow with Flow Designer engine ─────────────────
|
|
1021
|
+
// This is the KEY step: without it, records exist but Flow Designer
|
|
1022
|
+
// shows "Your flow cannot be found" because the engine hasn't compiled it.
|
|
1023
|
+
if (flowSysId) {
|
|
1024
|
+
var engineResult = await registerFlowWithEngine(client, flowSysId, shouldActivate);
|
|
1025
|
+
diagnostics.engine_registration = {
|
|
1026
|
+
success: engineResult.success,
|
|
1027
|
+
method: engineResult.method,
|
|
1028
|
+
attempts: engineResult.attempts
|
|
1029
|
+
};
|
|
1030
|
+
if (!engineResult.success) {
|
|
1031
|
+
factoryWarnings.push('Flow Designer engine registration failed — flow may show "cannot be found". Attempts: ' + engineResult.attempts.join(', '));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// ── Post-creation verification ─────────────────────────────
|
|
1036
|
+
if (flowSysId) {
|
|
818
1037
|
try {
|
|
819
|
-
await client.
|
|
820
|
-
|
|
1038
|
+
var verifyResp = await client.get('/api/now/table/sys_hub_flow/' + flowSysId, {
|
|
1039
|
+
params: { sysparm_fields: 'sys_id,name,latest_version' }
|
|
1040
|
+
});
|
|
1041
|
+
var verifyFlow = verifyResp.data.result;
|
|
1042
|
+
var hasLatestVersion = !!(verifyFlow && verifyFlow.latest_version);
|
|
1043
|
+
|
|
1044
|
+
// Check if version record exists
|
|
1045
|
+
var verCheckResp = await client.get('/api/now/table/sys_hub_flow_version', {
|
|
1046
|
+
params: {
|
|
1047
|
+
sysparm_query: 'flow=' + flowSysId,
|
|
1048
|
+
sysparm_fields: 'sys_id,name,state,compile_state,is_current',
|
|
1049
|
+
sysparm_limit: 1
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
var verRecords = verCheckResp.data.result || [];
|
|
1053
|
+
var hasVersionRecord = verRecords.length > 0;
|
|
1054
|
+
|
|
1055
|
+
diagnostics.post_verify = {
|
|
1056
|
+
flow_exists: true,
|
|
1057
|
+
has_latest_version_ref: hasLatestVersion,
|
|
1058
|
+
version_record_exists: hasVersionRecord,
|
|
1059
|
+
version_details: hasVersionRecord ? {
|
|
1060
|
+
sys_id: verRecords[0].sys_id,
|
|
1061
|
+
state: verRecords[0].state,
|
|
1062
|
+
compile_state: verRecords[0].compile_state,
|
|
1063
|
+
is_current: verRecords[0].is_current
|
|
1064
|
+
} : null
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// If version record is missing, retry creation
|
|
1068
|
+
if (!hasVersionRecord && !versionCreated) {
|
|
1069
|
+
try {
|
|
1070
|
+
var retryVersionData: any = {
|
|
1071
|
+
flow: flowSysId,
|
|
1072
|
+
name: '1.0',
|
|
1073
|
+
version: '1.0',
|
|
1074
|
+
state: shouldActivate ? 'published' : 'draft',
|
|
1075
|
+
active: true,
|
|
1076
|
+
compile_state: 'compiled',
|
|
1077
|
+
is_current: true,
|
|
1078
|
+
flow_definition: JSON.stringify(flowDefinition)
|
|
1079
|
+
};
|
|
1080
|
+
if (shouldActivate) retryVersionData.published_flow = flowSysId;
|
|
1081
|
+
|
|
1082
|
+
var retryVerResp = await client.post('/api/now/table/sys_hub_flow_version', retryVersionData);
|
|
1083
|
+
var retryVerSysId = retryVerResp.data.result?.sys_id;
|
|
1084
|
+
if (retryVerSysId) {
|
|
1085
|
+
versionCreated = true;
|
|
1086
|
+
diagnostics.version_created = true;
|
|
1087
|
+
diagnostics.version_method = (diagnostics.version_method || 'table_api') + ' (retry)';
|
|
1088
|
+
try {
|
|
1089
|
+
await client.patch('/api/now/table/sys_hub_flow/' + flowSysId, { latest_version: retryVerSysId });
|
|
1090
|
+
} catch (_) {}
|
|
1091
|
+
factoryWarnings.push('Version record was missing — created via post-verify retry');
|
|
1092
|
+
}
|
|
1093
|
+
} catch (retryErr: any) {
|
|
1094
|
+
factoryWarnings.push('Post-verify version retry failed: ' + (retryErr.message || retryErr));
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} catch (verifyErr: any) {
|
|
1098
|
+
diagnostics.post_verify = { error: verifyErr.message || 'verification failed' };
|
|
1099
|
+
}
|
|
821
1100
|
}
|
|
822
1101
|
|
|
823
1102
|
// ── Build summary ───────────────────────────────────────────
|
|
@@ -833,8 +1112,14 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
833
1112
|
.field('Status', shouldActivate ? 'Published (active)' : 'Draft')
|
|
834
1113
|
.field('Method', methodLabel);
|
|
835
1114
|
|
|
1115
|
+
if (diagnostics.factory_namespace) {
|
|
1116
|
+
createSummary.field('Namespace', diagnostics.factory_namespace);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
836
1119
|
if (versionCreated) {
|
|
837
|
-
createSummary.field('Version', 'v1.0 created');
|
|
1120
|
+
createSummary.field('Version', 'v1.0 created' + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
|
|
1121
|
+
} else {
|
|
1122
|
+
createSummary.warning('Version record NOT created — flow may show "cannot be found" in Flow Designer');
|
|
838
1123
|
}
|
|
839
1124
|
|
|
840
1125
|
if (!isSubflow && triggerType !== 'manual') {
|
|
@@ -851,6 +1136,31 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
851
1136
|
createSummary.warning(factoryWarnings[wi]);
|
|
852
1137
|
}
|
|
853
1138
|
|
|
1139
|
+
// Diagnostics section
|
|
1140
|
+
createSummary.blank().line('Diagnostics:');
|
|
1141
|
+
createSummary.indented('Factory bootstrap: ' + (diagnostics.factory_bootstrap || 'not attempted'));
|
|
1142
|
+
createSummary.indented('Factory namespace: ' + (diagnostics.factory_namespace || 'n/a'));
|
|
1143
|
+
createSummary.indented('Factory call: ' + (diagnostics.factory_call || 'not attempted'));
|
|
1144
|
+
createSummary.indented('Table API used: ' + diagnostics.table_api_used);
|
|
1145
|
+
createSummary.indented('Version created: ' + diagnostics.version_created + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
|
|
1146
|
+
if (diagnostics.engine_registration) {
|
|
1147
|
+
createSummary.indented('Engine registration: ' + (diagnostics.engine_registration.success ? diagnostics.engine_registration.method : 'FAILED'));
|
|
1148
|
+
if (diagnostics.engine_registration.attempts) {
|
|
1149
|
+
for (var ea = 0; ea < diagnostics.engine_registration.attempts.length; ea++) {
|
|
1150
|
+
createSummary.indented(' ' + diagnostics.engine_registration.attempts[ea]);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (diagnostics.post_verify) {
|
|
1155
|
+
if (diagnostics.post_verify.error) {
|
|
1156
|
+
createSummary.indented('Post-verify: error — ' + diagnostics.post_verify.error);
|
|
1157
|
+
} else {
|
|
1158
|
+
createSummary.indented('Post-verify: flow=' + diagnostics.post_verify.flow_exists +
|
|
1159
|
+
', version_record=' + diagnostics.post_verify.version_record_exists +
|
|
1160
|
+
', latest_version_ref=' + diagnostics.post_verify.has_latest_version_ref);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
854
1164
|
return createSuccessResult({
|
|
855
1165
|
created: true,
|
|
856
1166
|
method: usedMethod,
|
|
@@ -872,7 +1182,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
872
1182
|
activities_created: actionsCreated,
|
|
873
1183
|
activities_requested: activitiesArg.length,
|
|
874
1184
|
variables_created: varsCreated,
|
|
875
|
-
warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined
|
|
1185
|
+
warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined,
|
|
1186
|
+
diagnostics: diagnostics
|
|
876
1187
|
}, {}, createSummary.build());
|
|
877
1188
|
}
|
|
878
1189
|
|
|
@@ -1225,5 +1536,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
1225
1536
|
}
|
|
1226
1537
|
}
|
|
1227
1538
|
|
|
1228
|
-
export const version = '
|
|
1539
|
+
export const version = '4.0.0';
|
|
1229
1540
|
export const author = 'Snow-Flow Team';
|