snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
340 lines (339 loc) • 13.2 kB
JavaScript
;
/**
* Update Set XML Packager
*
* Properly packages Flow Designer XML into ServiceNow Update Sets
* with all required metadata and escaping
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateSetXMLPackager = void 0;
exports.packageFlowForUpdateSet = packageFlowForUpdateSet;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class UpdateSetXMLPackager {
constructor() {
this.records = [];
this.updateSetId = this.generateSysId();
this.timestamp = new Date().toISOString();
}
/**
* Generate ServiceNow-compatible sys_id
*/
generateSysId() {
return crypto.randomBytes(16).toString('hex');
}
/**
* Escape XML special characters
*/
escapeXml(value) {
if (!value)
return '';
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Add a record to the update set
*/
addRecord(record) {
this.records.push(record);
}
/**
* Add flow components to update set
*/
addFlowComponents(components) {
// Add flow record
if (components.flow) {
this.addRecord({
table: 'sys_hub_flow',
sys_id: components.flow.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_flow', components.flow),
type: 'Flow Designer Flow',
name: `sys_hub_flow_${components.flow.sys_id}`
});
}
// Add snapshot
if (components.snapshot) {
this.addRecord({
table: 'sys_hub_flow_snapshot',
sys_id: components.snapshot.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_flow_snapshot', components.snapshot),
type: 'Flow Designer Snapshot',
name: `sys_hub_flow_snapshot_${components.snapshot.sys_id}`
});
}
// Add trigger
if (components.trigger) {
this.addRecord({
table: 'sys_hub_trigger_instance_v2',
sys_id: components.trigger.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_trigger_instance_v2', components.trigger),
type: 'Flow Designer Trigger',
name: `sys_hub_trigger_instance_v2_${components.trigger.sys_id}`
});
}
// Add actions
components.actions?.forEach(action => {
this.addRecord({
table: 'sys_hub_action_instance_v2',
sys_id: action.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_action_instance_v2', action),
type: 'Flow Designer Action',
name: `sys_hub_action_instance_v2_${action.sys_id}`
});
});
// Add logic
components.logic?.forEach(logic => {
this.addRecord({
table: 'sys_hub_flow_logic_instance_v2',
sys_id: logic.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_flow_logic_instance_v2', logic),
type: 'Flow Designer Logic',
name: `sys_hub_flow_logic_instance_v2_${logic.sys_id}`
});
});
// Add variables
components.variables?.forEach(variable => {
this.addRecord({
table: 'sys_hub_flow_variable',
sys_id: variable.sys_id,
action: 'INSERT_OR_UPDATE',
payload: this.createRecordPayload('sys_hub_flow_variable', variable),
type: 'Flow Designer Variable',
name: `sys_hub_flow_variable_${variable.sys_id}`
});
});
}
/**
* Create record payload XML
*/
createRecordPayload(table, record) {
const fields = Object.entries(record)
.filter(([key]) => !key.startsWith('_') && key !== 'sys_id')
.map(([key, value]) => {
if (value === null || value === undefined) {
return `<${key}/>`;
}
// Handle display values
const displayValue = record[`${key}_display_value`];
const displayAttr = displayValue ? ` display_value="${this.escapeXml(displayValue)}"` : '';
// Handle different value types
if (typeof value === 'boolean') {
return `<${key}${displayAttr}>${value}</${key}>`;
}
else if (typeof value === 'object') {
return `<${key}${displayAttr}>${this.escapeXml(JSON.stringify(value))}</${key}>`;
}
else {
return `<${key}${displayAttr}>${this.escapeXml(String(value))}</${key}>`;
}
})
.join('');
return `<?xml version="1.0" encoding="UTF-8"?><record_update table="${table}"><${table} action="INSERT_OR_UPDATE">${fields}<sys_id>${record.sys_id}</sys_id></${table}></record_update>`;
}
/**
* Generate complete Update Set XML
*/
generateXML(metadata) {
const updateSetSysId = this.generateSysId();
// Build update records
const updateRecords = this.records.map(record => {
const updateXmlSysId = this.generateSysId();
return `
<sys_update_xml action="INSERT_OR_UPDATE">
<sys_id>${updateXmlSysId}</sys_id>
<action>${record.action}</action>
<application display_value="${metadata.application || 'Global'}">global</application>
<category>${record.category || 'customer'}</category>
<comments/>
<name>${record.name || record.table + '_' + record.sys_id}</name>
<payload><![CDATA[${record.payload}]]></payload>
<remote_update_set display_value="${this.escapeXml(metadata.name)}">${updateSetSysId}</remote_update_set>
<replace_on_upgrade>false</replace_on_upgrade>
<source_table>${record.table}</source_table>
<sys_class_name>sys_update_xml</sys_class_name>
<sys_created_by>admin</sys_created_by>
<sys_created_on>${this.timestamp}</sys_created_on>
<sys_id>${updateXmlSysId}</sys_id>
<sys_mod_count>0</sys_mod_count>
<sys_recorded_at>${this.timestamp}</sys_recorded_at>
<sys_updated_by>admin</sys_updated_by>
<sys_updated_on>${this.timestamp}</sys_updated_on>
<type>${record.type}</type>
<update_domain>global</update_domain>
<update_guid>${this.generateSysId()}${this.generateSysId()}</update_guid>
<update_set display_value=""/>
<view display_value="Default view">Default view</view>
</sys_update_xml>`;
}).join('\n');
// Build complete XML
return `<?xml version="1.0" encoding="UTF-8"?>
<unload unload_date="${this.timestamp}">
<!-- Remote Update Set -->
<sys_remote_update_set action="INSERT_OR_UPDATE">
<sys_id>${updateSetSysId}</sys_id>
<name>${this.escapeXml(metadata.name)}</name>
<description>${this.escapeXml(metadata.description || '')}</description>
<origin_sys_id>${updateSetSysId}</origin_sys_id>
<parent/>
<release_date>${metadata.release_date || ''}</release_date>
<remote_parent_id/>
<remote_sys_id>${updateSetSysId}</remote_sys_id>
<state>${metadata.state || 'loaded'}</state>
<summary/>
<sys_class_name>sys_remote_update_set</sys_class_name>
<sys_created_by>admin</sys_created_by>
<sys_created_on>${this.timestamp}</sys_created_on>
<sys_domain>global</sys_domain>
<sys_domain_path>/</sys_domain_path>
<sys_id>${updateSetSysId}</sys_id>
<sys_mod_count>0</sys_mod_count>
<sys_updated_by>admin</sys_updated_by>
<sys_updated_on>${this.timestamp}</sys_updated_on>
<update_set/>
<update_source/>
<version/>
</sys_remote_update_set>
${updateRecords}
</unload>`;
}
/**
* Save XML to file
*/
saveToFile(xml, filename) {
const outputDir = path.join(process.cwd(), 'flow-update-sets');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const outputFile = path.join(outputDir, filename || `update_set_${this.updateSetId}.xml`);
fs.writeFileSync(outputFile, xml, 'utf8');
return outputFile;
}
/**
* Create Update Set from existing flow XML
*/
static packageFlowXML(flowXML, metadata) {
const packager = new UpdateSetXMLPackager();
// Parse existing XML to extract components
// This is a simplified version - real implementation would parse properly
const flowMatch = flowXML.match(/<sys_hub_flow[^>]*>[\s\S]*?<\/sys_hub_flow>/);
const snapshotMatch = flowXML.match(/<sys_hub_flow_snapshot[^>]*>[\s\S]*?<\/sys_hub_flow_snapshot>/);
if (flowMatch) {
packager.addRecord({
table: 'sys_hub_flow',
sys_id: packager.generateSysId(),
action: 'INSERT_OR_UPDATE',
payload: flowMatch[0],
type: 'Flow Designer Flow'
});
}
if (snapshotMatch) {
packager.addRecord({
table: 'sys_hub_flow_snapshot',
sys_id: packager.generateSysId(),
action: 'INSERT_OR_UPDATE',
payload: snapshotMatch[0],
type: 'Flow Designer Snapshot'
});
}
const xml = packager.generateXML(metadata);
const filePath = packager.saveToFile(xml);
return { xml, filePath };
}
/**
* Validate Update Set XML structure
*/
static validateXML(xml) {
const errors = [];
// Check for required elements
if (!xml.includes('<sys_remote_update_set')) {
errors.push('Missing sys_remote_update_set element');
}
if (!xml.includes('<sys_update_xml')) {
errors.push('Missing sys_update_xml records');
}
// Check for proper CDATA wrapping
const payloadMatches = xml.match(/<payload>/g);
const cdataMatches = xml.match(/<!\[CDATA\[/g);
if (payloadMatches && cdataMatches && payloadMatches.length !== cdataMatches.length) {
errors.push('Payload elements not properly wrapped in CDATA');
}
// Check XML declaration
if (!xml.startsWith('<?xml version="1.0" encoding="UTF-8"?>')) {
errors.push('Missing or incorrect XML declaration');
}
return {
valid: errors.length === 0,
errors
};
}
}
exports.UpdateSetXMLPackager = UpdateSetXMLPackager;
// Export helper function
function packageFlowForUpdateSet(flowDefinition, updateSetName, description) {
const packager = new UpdateSetXMLPackager();
// Add all flow components
packager.addFlowComponents({
flow: flowDefinition.flow,
snapshot: flowDefinition.snapshot,
trigger: flowDefinition.trigger,
actions: flowDefinition.actions || [],
logic: flowDefinition.logic || [],
variables: flowDefinition.variables || []
});
// Generate XML
const xml = packager.generateXML({
name: updateSetName,
description: description || `Flow deployment: ${flowDefinition.flow?.name || 'Unknown'}`
});
// Save to file
const filePath = packager.saveToFile(xml);
// Validate
const validation = UpdateSetXMLPackager.validateXML(xml);
return { xml, filePath, validation };
}
exports.default = UpdateSetXMLPackager;
//# sourceMappingURL=update-set-xml-packager.js.map