UNPKG

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

602 lines 27 kB
"use strict"; /** * Global Scope Strategy for ServiceNow Flow Creation * * This strategy implements a global scope approach for ServiceNow artifacts, * replacing the current application-scoped approach with a more flexible * global scope deployment model. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GlobalScopeStrategy = exports.ScopeType = void 0; const servicenow_client_js_1 = require("../utils/servicenow-client.js"); const snow_oauth_js_1 = require("../utils/snow-oauth.js"); const logger_js_1 = require("../utils/logger.js"); var ScopeType; (function (ScopeType) { ScopeType["GLOBAL"] = "global"; ScopeType["APPLICATION"] = "application"; ScopeType["AUTO"] = "auto"; })(ScopeType || (exports.ScopeType = ScopeType = {})); class GlobalScopeStrategy { constructor() { this.client = new servicenow_client_js_1.ServiceNowClient(); this.oauth = new snow_oauth_js_1.ServiceNowOAuth(); this.logger = new logger_js_1.Logger('GlobalScopeStrategy'); // Default configuration prioritizes global scope this.defaultConfiguration = { type: ScopeType.GLOBAL, globalDomain: 'global', fallbackToGlobal: true, permissions: ['sys_scope.write', 'sys_metadata.write'] }; } /** * Analyze and determine the optimal scope for deployment */ async analyzeScopeRequirements(artifactType, artifactData) { this.logger.info('Analyzing scope requirements', { artifactType, artifactData: artifactData.name }); const scopeConfig = { ...this.defaultConfiguration }; // Analyze artifact complexity and dependencies const complexity = this.assessArtifactComplexity(artifactType, artifactData); const dependencies = await this.analyzeDependencies(artifactType, artifactData); const permissions = await this.checkRequiredPermissions(artifactType); // Global scope is preferred for: // 1. System-wide flows and utilities // 2. Cross-application integrations // 3. Infrastructure components // 4. Shared libraries and common functions const globalScopeIndicators = [ artifactData.name?.toLowerCase().includes('global'), artifactData.name?.toLowerCase().includes('system'), artifactData.name?.toLowerCase().includes('util'), artifactData.description?.toLowerCase().includes('cross-application'), artifactData.description?.toLowerCase().includes('shared'), complexity.isInfrastructure, dependencies.crossApplication, artifactType === 'script_include' && artifactData.api_name?.includes('Global'), artifactType === 'business_rule' && artifactData.table === 'global' ]; const globalIndicatorCount = globalScopeIndicators.filter(Boolean).length; // Application scope is preferred for: // 1. Application-specific business logic // 2. Isolated functionality // 3. Customer-specific customizations const applicationScopeIndicators = [ artifactData.scope && artifactData.scope !== 'global', artifactData.application_specific === true, complexity.isApplicationSpecific, !dependencies.crossApplication, artifactData.name?.toLowerCase().includes('custom'), artifactData.description?.toLowerCase().includes('application-specific') ]; const applicationIndicatorCount = applicationScopeIndicators.filter(Boolean).length; // Decision logic if (globalIndicatorCount > applicationIndicatorCount) { scopeConfig.type = ScopeType.GLOBAL; scopeConfig.globalDomain = 'global'; this.logger.info('Global scope recommended', { globalIndicators: globalIndicatorCount, applicationIndicators: applicationIndicatorCount }); } else if (applicationIndicatorCount > globalIndicatorCount && artifactData.scope) { scopeConfig.type = ScopeType.APPLICATION; scopeConfig.applicationId = artifactData.scope; scopeConfig.applicationName = artifactData.application_name; this.logger.info('Application scope recommended', { scope: artifactData.scope, applicationIndicators: applicationIndicatorCount }); } else { // Auto mode - let the system decide based on permissions and context scopeConfig.type = ScopeType.AUTO; this.logger.info('Auto scope mode selected - will determine at deployment time'); } scopeConfig.permissions = permissions; return scopeConfig; } /** * Validate scope permissions and capabilities */ async validateScopePermissions(scopeConfig) { this.logger.info('Validating scope permissions', { scopeType: scopeConfig.type }); const result = { isValid: false, hasPermissions: false, canCreateGlobal: false, canCreateScoped: false, recommendedScope: ScopeType.GLOBAL, issues: [], warnings: [] }; try { // Check if user has global scope permissions const globalPermissions = await this.checkGlobalScopePermissions(); result.canCreateGlobal = globalPermissions.hasPermission; if (!globalPermissions.hasPermission) { result.issues.push(...globalPermissions.issues); result.warnings.push('Global scope permissions not available - consider using application scope'); } // Check if user has application scope permissions const applicationPermissions = await this.checkApplicationScopePermissions(scopeConfig.applicationId); result.canCreateScoped = applicationPermissions.hasPermission; if (!applicationPermissions.hasPermission) { result.issues.push(...applicationPermissions.issues); } // Determine validity and recommendation if (scopeConfig.type === ScopeType.GLOBAL) { result.isValid = result.canCreateGlobal; result.hasPermissions = result.canCreateGlobal; result.recommendedScope = result.canCreateGlobal ? ScopeType.GLOBAL : ScopeType.APPLICATION; } else if (scopeConfig.type === ScopeType.APPLICATION) { result.isValid = result.canCreateScoped; result.hasPermissions = result.canCreateScoped; result.recommendedScope = result.canCreateScoped ? ScopeType.APPLICATION : ScopeType.GLOBAL; } else { // Auto mode - prefer global if available, fallback to application result.isValid = result.canCreateGlobal || result.canCreateScoped; result.hasPermissions = result.canCreateGlobal || result.canCreateScoped; result.recommendedScope = result.canCreateGlobal ? ScopeType.GLOBAL : ScopeType.APPLICATION; } if (!result.isValid) { result.issues.push('Insufficient permissions for any scope type'); } this.logger.info('Scope validation completed', { isValid: result.isValid, canCreateGlobal: result.canCreateGlobal, canCreateScoped: result.canCreateScoped, recommendedScope: result.recommendedScope }); } catch (error) { this.logger.error('Scope validation failed', error); result.issues.push(`Validation error: ${error instanceof Error ? error.message : String(error)}`); } return result; } /** * Deploy artifact using global scope strategy */ async deployWithGlobalScope(artifactType, artifactData, scopeConfig) { this.logger.info('Starting global scope deployment', { artifactType, artifactName: artifactData.name, scopeType: scopeConfig?.type || 'default' }); const config = scopeConfig || await this.analyzeScopeRequirements(artifactType, artifactData); const validation = await this.validateScopePermissions(config); if (!validation.isValid) { return { success: false, scope: config.type, domain: config.globalDomain || 'unknown', artifactId: '', permissions: config.permissions || [], message: `Deployment failed: ${validation.issues.join(', ')}`, warnings: validation.warnings }; } try { // Apply the appropriate scope strategy let deploymentResult; let actualScope; if (config.type === ScopeType.GLOBAL || validation.recommendedScope === ScopeType.GLOBAL) { deploymentResult = await this.deployToGlobalScope(artifactType, artifactData, config); actualScope = ScopeType.GLOBAL; } else if (config.type === ScopeType.APPLICATION || validation.recommendedScope === ScopeType.APPLICATION) { deploymentResult = await this.deployToApplicationScope(artifactType, artifactData, config); actualScope = ScopeType.APPLICATION; } else { // Auto mode - try global first, fallback to application try { deploymentResult = await this.deployToGlobalScope(artifactType, artifactData, config); actualScope = ScopeType.GLOBAL; } catch (error) { if (config.fallbackToGlobal) { this.logger.warn('Global scope deployment failed, falling back to application scope', error); deploymentResult = await this.deployToApplicationScope(artifactType, artifactData, config); actualScope = ScopeType.APPLICATION; } else { throw error; } } } return { success: deploymentResult.success, scope: actualScope, domain: actualScope === ScopeType.GLOBAL ? 'global' : (config.applicationName || 'application'), artifactId: deploymentResult.sys_id || deploymentResult.id, permissions: config.permissions || [], message: `Successfully deployed to ${actualScope} scope`, warnings: validation.warnings, fallbackApplied: config.type !== actualScope }; } catch (error) { this.logger.error('Global scope deployment failed', error); return { success: false, scope: config.type, domain: config.globalDomain || 'global', artifactId: '', permissions: config.permissions || [], message: `Deployment failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Deploy artifact to global scope */ async deployToGlobalScope(artifactType, artifactData, config) { this.logger.info('Deploying to global scope', { artifactType, name: artifactData.name }); // Set global scope context const globalArtifactData = { ...artifactData, sys_scope: 'global', sys_domain: 'global', sys_domain_path: '/', application: null, // Remove application association scope: 'global' }; // Remove application-specific fields delete globalArtifactData.application_id; delete globalArtifactData.application_name; delete globalArtifactData.vendor; delete globalArtifactData.vendor_prefix; // Add global scope metadata globalArtifactData.sys_created_on = new Date().toISOString(); globalArtifactData.sys_updated_on = new Date().toISOString(); globalArtifactData.sys_class_name = this.getSystemClassName(artifactType); // Deploy based on artifact type switch (artifactType) { case 'flow': return await this.deployFlowToGlobalScope(globalArtifactData); case 'widget': return await this.deployWidgetToGlobalScope(globalArtifactData); case 'script_include': return await this.deployScriptIncludeToGlobalScope(globalArtifactData); case 'business_rule': return await this.deployBusinessRuleToGlobalScope(globalArtifactData); case 'table': return await this.deployTableToGlobalScope(globalArtifactData); default: throw new Error(`Unsupported artifact type for global scope: ${artifactType}`); } } /** * Deploy artifact to application scope (fallback) */ async deployToApplicationScope(artifactType, artifactData, config) { this.logger.info('Deploying to application scope', { artifactType, name: artifactData.name, applicationId: config.applicationId }); // If no application is specified, create one if (!config.applicationId) { const application = await this.createTemporaryApplication(artifactData.name); config.applicationId = application.sys_id; config.applicationName = application.name; } // Set application scope context const scopedArtifactData = { ...artifactData, sys_scope: config.applicationId, application: config.applicationId, scope: config.applicationId }; // Use existing application deployment logic return await this.client.createRecord(this.getTableName(artifactType), scopedArtifactData); } /** * Deploy flow to global scope */ async deployFlowToGlobalScope(flowData) { this.logger.info('Deploying flow to global scope', { name: flowData.name }); // Enhanced flow data for global scope const globalFlowData = { ...flowData, // Global scope specific fields sys_scope: 'global', run_as: 'user_who_triggers', // Global flows typically run as triggering user access: 'public', // Global access source_ui: 'flow_designer', // Remove application-specific constraints application: null, category: flowData.category || 'global_automation', // Enhanced metadata sys_class_name: 'sys_hub_flow', type: 'flow', status: 'published' }; // Ensure we're creating a flow, not a subflow globalFlowData.flow_type = 'flow'; globalFlowData.sys_class_name = 'sys_hub_flow'; this.logger.info('Creating flow with explicit type', { name: globalFlowData.name, type: globalFlowData.type, flow_type: globalFlowData.flow_type, sys_class_name: globalFlowData.sys_class_name }); return await this.client.createFlow(globalFlowData); } /** * Deploy widget to global scope */ async deployWidgetToGlobalScope(widgetData) { this.logger.info('Deploying widget to global scope', { name: widgetData.name }); const globalWidgetData = { ...widgetData, sys_scope: 'global', category: widgetData.category || 'global_widgets', has_preview: true, public: true // Global widgets are public }; return await this.client.createWidget(globalWidgetData); } /** * Deploy script include to global scope */ async deployScriptIncludeToGlobalScope(scriptData) { this.logger.info('Deploying script include to global scope', { name: scriptData.name }); const globalScriptData = { ...scriptData, sys_scope: 'global', access: 'public', // Global script includes are typically public active: true, api_name: scriptData.api_name || scriptData.name }; return await this.client.createScriptInclude(globalScriptData); } /** * Deploy business rule to global scope */ async deployBusinessRuleToGlobalScope(ruleData) { this.logger.info('Deploying business rule to global scope', { name: ruleData.name }); const globalRuleData = { ...ruleData, sys_scope: 'global', active: true, order: ruleData.order || 100 }; return await this.client.createBusinessRule(globalRuleData); } /** * Deploy table to global scope */ async deployTableToGlobalScope(tableData) { this.logger.info('Deploying table to global scope', { name: tableData.name }); const globalTableData = { ...tableData, sys_scope: 'global', access: 'public', is_extendable: true, create_access_controls: true }; return await this.client.createTable(globalTableData); } /** * Check global scope permissions */ async checkGlobalScopePermissions() { try { // Check if user can create global scope artifacts const userRoles = await this.client.getRecords('sys_user_has_role', { sysparm_query: `user=javascript:gs.getUserID()`, sysparm_fields: 'role.name' }); if (!userRoles.success) { return { hasPermission: false, issues: ['Cannot check user roles'] }; } const roles = userRoles.data.map((r) => r.role?.name).filter(Boolean); const globalPermissionRoles = ['admin', 'system_administrator', 'global_admin']; const hasGlobalPermission = globalPermissionRoles.some(role => roles.includes(role)); return { hasPermission: hasGlobalPermission, issues: hasGlobalPermission ? [] : [ '🔐 Permission Error: Global scope access required', `Missing roles: ${globalPermissionRoles.filter(r => !roles.includes(r)).join(', ')}`, 'To fix:', '1. Run: snow_escalate_permissions tool for detailed instructions', '2. Or contact your ServiceNow administrator', '3. Or use a personal developer instance from developer.servicenow.com' ] }; } catch (error) { return { hasPermission: false, issues: [`Permission check failed: ${error instanceof Error ? error.message : String(error)}`] }; } } /** * Check application scope permissions */ async checkApplicationScopePermissions(applicationId) { try { if (!applicationId) { // User can create applications if they have app_creator or admin role const userRoles = await this.client.getRecords('sys_user_has_role', { sysparm_query: `user=javascript:gs.getUserID()`, sysparm_fields: 'role.name' }); if (!userRoles.success) { return { hasPermission: false, issues: ['Cannot check user roles'] }; } const roles = userRoles.data.map((r) => r.role?.name).filter(Boolean); const appCreatorRoles = ['admin', 'system_administrator', 'app_creator']; const hasAppPermission = appCreatorRoles.some(role => roles.includes(role)); return { hasPermission: hasAppPermission, issues: hasAppPermission ? [] : ['User lacks application creation permissions (admin, system_administrator, or app_creator role required)'] }; } // Check if application exists and user has access const app = await this.client.getRecord('sys_app', applicationId); return { hasPermission: !!app, issues: app ? [] : [`Application ${applicationId} not found or not accessible`] }; } catch (error) { return { hasPermission: false, issues: [`Permission check failed: ${error instanceof Error ? error.message : String(error)}`] }; } } /** * Assess artifact complexity for scope decision */ assessArtifactComplexity(artifactType, artifactData) { const complexity = { isInfrastructure: false, isApplicationSpecific: false, crossApplication: false, sharedUtility: false }; // Infrastructure indicators const infraKeywords = ['system', 'global', 'util', 'lib', 'common', 'shared', 'infrastructure']; const artifactText = `${artifactData.name} ${artifactData.description || ''}`.toLowerCase(); complexity.isInfrastructure = infraKeywords.some(keyword => artifactText.includes(keyword)); // Application-specific indicators const appKeywords = ['custom', 'specific', 'application', 'business', 'process']; complexity.isApplicationSpecific = appKeywords.some(keyword => artifactText.includes(keyword)); // Cross-application indicators const crossAppKeywords = ['integration', 'cross', 'bridge', 'connector', 'api']; complexity.crossApplication = crossAppKeywords.some(keyword => artifactText.includes(keyword)); // Shared utility indicators const utilityKeywords = ['utility', 'helper', 'tool', 'function', 'method']; complexity.sharedUtility = utilityKeywords.some(keyword => artifactText.includes(keyword)); return complexity; } /** * Analyze artifact dependencies */ async analyzeDependencies(artifactType, artifactData) { const dependencies = { crossApplication: false, globalDependencies: [], applicationDependencies: [] }; // Simple dependency _analysis based on artifact content const content = JSON.stringify(artifactData); // Check for global API calls const globalApiPatterns = [ /gs\./g, // GlideSystem calls /GlideRecord\(/g, // Database access /gs\.getProperty\(/g, // System properties /gs\.log\(/g // System logging ]; const hasGlobalApis = globalApiPatterns.some(pattern => pattern.test(content)); if (hasGlobalApis) { dependencies.crossApplication = true; dependencies.globalDependencies.push('system_apis'); } return dependencies; } /** * Check required permissions for artifact type */ async checkRequiredPermissions(artifactType) { const permissionMap = { 'flow': ['sys_hub_flow.write', 'sys_metadata.write'], 'widget': ['sp_widget.write', 'sys_metadata.write'], 'script_include': ['sys_script_include.write', 'sys_metadata.write'], 'business_rule': ['sys_script.write', 'sys_metadata.write'], 'table': ['sys_db_object.write', 'sys_metadata.write'] }; return permissionMap[artifactType] || ['sys_metadata.write']; } /** * Create temporary application for scoped deployment */ async createTemporaryApplication(baseName) { const applicationData = { name: `Temp App for ${baseName}`, scope: `x_temp_${Date.now()}`, version: '1.0.0', short_description: `Temporary application for ${baseName}`, description: `Temporary application for scoped deployment of ${baseName}`, vendor: 'System', vendor_prefix: 'x', active: true }; const result = await this.client.createApplication(applicationData); if (!result.success) { throw new Error(`Failed to create temporary application: ${result.error}`); } return result.data; } /** * Get system class name for artifact type */ getSystemClassName(artifactType) { const classMap = { 'flow': 'sys_hub_flow', 'widget': 'sp_widget', 'script_include': 'sys_script_include', 'business_rule': 'sys_script', 'table': 'sys_db_object' }; return classMap[artifactType] || 'sys_metadata'; } /** * Get table name for artifact type */ getTableName(artifactType) { const tableMap = { 'flow': 'sys_hub_flow', 'widget': 'sp_widget', 'script_include': 'sys_script_include', 'business_rule': 'sys_script', 'table': 'sys_db_object' }; return tableMap[artifactType] || 'sys_metadata'; } /** * Get migration strategy for existing scoped artifacts */ async getMigrationStrategy(existingArtifacts) { this.logger.info('Generating migration strategy', { count: existingArtifacts.length }); const migrationPlan = { totalArtifacts: existingArtifacts.length, globalCandidates: [], keepScoped: [], requiresReview: [], estimatedTime: 0, risks: [], recommendations: [] }; for (const artifact of existingArtifacts) { const _analysis = await this.analyzeScopeRequirements(artifact.type, artifact); if (_analysis.type === ScopeType.GLOBAL) { migrationPlan.globalCandidates.push({ ...artifact, migrationComplexity: 'low', dependencies: await this.analyzeDependencies(artifact.type, artifact) }); } else { migrationPlan.keepScoped.push(artifact); } } // Calculate migration complexity and time migrationPlan.estimatedTime = migrationPlan.globalCandidates.length * 5; // 5 minutes per artifact if (migrationPlan.globalCandidates.length > 10) { migrationPlan.risks.push('Large number of artifacts to migrate - consider phased approach'); } migrationPlan.recommendations.push('Test migration in development environment first', 'Backup existing artifacts before migration', 'Monitor for breaking changes after migration'); return migrationPlan; } } exports.GlobalScopeStrategy = GlobalScopeStrategy; //# sourceMappingURL=global-scope-strategy.js.map