snow-flow 1.1.73 → 1.1.74
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/dist/mcp/servicenow-deployment-mcp.js +244 -25
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -1
- package/dist/mcp/servicenow-flow-composer-mcp.js +72 -6
- package/dist/mcp/servicenow-flow-composer-mcp.js.map +1 -1
- package/dist/orchestrator/flow-composer.d.ts.map +1 -1
- package/dist/orchestrator/flow-composer.js +201 -9
- package/dist/orchestrator/flow-composer.js.map +1 -1
- package/dist/utils/snow-oauth.d.ts +9 -0
- package/dist/utils/snow-oauth.d.ts.map +1 -1
- package/dist/utils/snow-oauth.js +68 -0
- package/dist/utils/snow-oauth.js.map +1 -1
- package/dist/version.d.ts +6 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +27 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -3665,6 +3665,11 @@ Use \`snow_preview_widget\` to see a detailed preview of the widget rendering.`,
|
|
|
3665
3665
|
async unifiedDeploy(args) {
|
|
3666
3666
|
try {
|
|
3667
3667
|
this.logger.info('Starting unified deployment', args);
|
|
3668
|
+
// CRITICAL: Check authentication FIRST before any deployment
|
|
3669
|
+
const isAuthenticated = await this.oauth.isAuthenticated();
|
|
3670
|
+
if (!isAuthenticated) {
|
|
3671
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Not authenticated. Run "snow-flow auth login" first.');
|
|
3672
|
+
}
|
|
3668
3673
|
const { type, instruction, config, auto_update_set = true, fallback_strategy = 'manual_steps', permission_escalation = 'auto_request', deployment_context } = args;
|
|
3669
3674
|
// Step 1: Ensure Update Set session if requested
|
|
3670
3675
|
let updateSetSession = null;
|
|
@@ -3722,10 +3727,16 @@ Use \`snow_preview_widget\` to see a detailed preview of the widget rendering.`,
|
|
|
3722
3727
|
try {
|
|
3723
3728
|
const escalationResult = await this.requestPermissionEscalation(error, strategy.scope);
|
|
3724
3729
|
if (escalationResult.granted) {
|
|
3725
|
-
//
|
|
3726
|
-
|
|
3730
|
+
// Use new scope if provided by escalation
|
|
3731
|
+
const effectiveScope = escalationResult.newScope || strategy.scope;
|
|
3732
|
+
this.logger.info('Retrying with escalated permissions', {
|
|
3733
|
+
originalScope: strategy.scope,
|
|
3734
|
+
newScope: effectiveScope
|
|
3735
|
+
});
|
|
3736
|
+
// Retry with escalated permissions and potentially new scope
|
|
3737
|
+
deploymentResult = await this.attemptDirectDeployment(type, deploymentConfig, effectiveScope);
|
|
3727
3738
|
if (deploymentResult.success) {
|
|
3728
|
-
return this.formatSuccessResponse(deploymentResult,
|
|
3739
|
+
return this.formatSuccessResponse(deploymentResult, effectiveScope, updateSetSession, 'escalated');
|
|
3729
3740
|
}
|
|
3730
3741
|
}
|
|
3731
3742
|
}
|
|
@@ -3838,13 +3849,128 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
|
|
|
3838
3849
|
* Request permission escalation
|
|
3839
3850
|
*/
|
|
3840
3851
|
async requestPermissionEscalation(error, scope) {
|
|
3841
|
-
|
|
3842
|
-
//
|
|
3852
|
+
this.logger.info('Attempting permission escalation', { error: error.message, scope });
|
|
3853
|
+
// Determine required roles based on error message and scope
|
|
3854
|
+
const requiredRoles = this.extractRequiredRoles(error, scope);
|
|
3855
|
+
// Try multiple escalation strategies
|
|
3856
|
+
const escalationStrategies = [
|
|
3857
|
+
{
|
|
3858
|
+
name: 'scoped_deployment',
|
|
3859
|
+
description: 'Switch to scoped application deployment',
|
|
3860
|
+
attempt: async () => {
|
|
3861
|
+
if (scope === 'global' && requiredRoles.includes('admin')) {
|
|
3862
|
+
// Fallback to scoped application
|
|
3863
|
+
this.logger.info('Attempting scoped deployment as fallback');
|
|
3864
|
+
return {
|
|
3865
|
+
granted: true,
|
|
3866
|
+
message: 'Switching to scoped application deployment (no global admin required)',
|
|
3867
|
+
newScope: 'x_custom_app'
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
return { granted: false };
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
name: 'personal_scope',
|
|
3875
|
+
description: 'Use personal developer scope',
|
|
3876
|
+
attempt: async () => {
|
|
3877
|
+
if (scope !== 'personal') {
|
|
3878
|
+
this.logger.info('Attempting personal scope deployment');
|
|
3879
|
+
return {
|
|
3880
|
+
granted: true,
|
|
3881
|
+
message: 'Using personal developer scope for deployment',
|
|
3882
|
+
newScope: 'x_personal_dev'
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
return { granted: false };
|
|
3886
|
+
}
|
|
3887
|
+
},
|
|
3888
|
+
{
|
|
3889
|
+
name: 'update_set_only',
|
|
3890
|
+
description: 'Create in Update Set without direct deployment',
|
|
3891
|
+
attempt: async () => {
|
|
3892
|
+
this.logger.info('Falling back to Update Set only creation');
|
|
3893
|
+
return {
|
|
3894
|
+
granted: true,
|
|
3895
|
+
message: 'Creating artifact definition in Update Set (manual import required)',
|
|
3896
|
+
newScope: 'update_set',
|
|
3897
|
+
requiresManualStep: true
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
];
|
|
3902
|
+
// Try each escalation strategy
|
|
3903
|
+
for (const strategy of escalationStrategies) {
|
|
3904
|
+
try {
|
|
3905
|
+
const result = await strategy.attempt();
|
|
3906
|
+
if (result.granted) {
|
|
3907
|
+
return {
|
|
3908
|
+
granted: true,
|
|
3909
|
+
message: `${strategy.description}: ${result.message}`,
|
|
3910
|
+
...result
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
catch (err) {
|
|
3915
|
+
this.logger.warn(`Escalation strategy ${strategy.name} failed`, err);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
// If all strategies fail, provide detailed manual guidance
|
|
3843
3919
|
return {
|
|
3844
3920
|
granted: false,
|
|
3845
|
-
message:
|
|
3921
|
+
message: this.generatePermissionGuidance(requiredRoles, scope, error)
|
|
3846
3922
|
};
|
|
3847
3923
|
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Extract required roles from error message
|
|
3926
|
+
*/
|
|
3927
|
+
extractRequiredRoles(error, scope) {
|
|
3928
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3929
|
+
const roles = [];
|
|
3930
|
+
// Common role patterns in ServiceNow errors
|
|
3931
|
+
if (message.includes('admin') || message.includes('administrator')) {
|
|
3932
|
+
roles.push('admin');
|
|
3933
|
+
}
|
|
3934
|
+
if (message.includes('global') || scope === 'global') {
|
|
3935
|
+
roles.push('global_admin');
|
|
3936
|
+
}
|
|
3937
|
+
if (message.includes('system_administrator')) {
|
|
3938
|
+
roles.push('system_administrator');
|
|
3939
|
+
}
|
|
3940
|
+
if (message.includes('app_creator')) {
|
|
3941
|
+
roles.push('app_creator');
|
|
3942
|
+
}
|
|
3943
|
+
return roles.length > 0 ? roles : ['admin']; // Default to admin if no specific role found
|
|
3944
|
+
}
|
|
3945
|
+
/**
|
|
3946
|
+
* Generate detailed permission guidance
|
|
3947
|
+
*/
|
|
3948
|
+
generatePermissionGuidance(roles, scope, error) {
|
|
3949
|
+
const credentials = this.oauth.loadCredentials();
|
|
3950
|
+
const instance = credentials?.then(c => c?.instance) || 'your-instance';
|
|
3951
|
+
return `
|
|
3952
|
+
🔐 **Permission Escalation Required**
|
|
3953
|
+
|
|
3954
|
+
**Required Roles**: ${roles.join(', ')}
|
|
3955
|
+
**Attempted Scope**: ${scope}
|
|
3956
|
+
|
|
3957
|
+
**Option 1: Request Role Assignment**
|
|
3958
|
+
1. Navigate to: https://${instance}/nav_to.do?uri=sys_user.do?sys_id=<your_user_sys_id>
|
|
3959
|
+
2. Go to "Roles" related list
|
|
3960
|
+
3. Add roles: ${roles.join(', ')}
|
|
3961
|
+
|
|
3962
|
+
**Option 2: Use Delegated Development**
|
|
3963
|
+
1. Create artifact in personal scope first
|
|
3964
|
+
2. Have admin promote to ${scope} scope
|
|
3965
|
+
3. Command: \`snow_deploy --scope personal\`
|
|
3966
|
+
|
|
3967
|
+
**Option 3: Manual Import via Update Set**
|
|
3968
|
+
1. Export the generated Update Set XML
|
|
3969
|
+
2. Import as admin user
|
|
3970
|
+
3. Preview and commit the Update Set
|
|
3971
|
+
|
|
3972
|
+
**Error Details**: ${error.message || error}`;
|
|
3973
|
+
}
|
|
3848
3974
|
/**
|
|
3849
3975
|
* Format successful deployment response
|
|
3850
3976
|
*/
|
|
@@ -3881,7 +4007,19 @@ Your artifact has been deployed and is ready for testing.` : ''}
|
|
|
3881
4007
|
* Generate manual deployment steps as fallback
|
|
3882
4008
|
*/
|
|
3883
4009
|
async generateManualDeploymentSteps(type, config, error, updateSetSession) {
|
|
3884
|
-
const
|
|
4010
|
+
const credentials = await this.oauth.loadCredentials();
|
|
4011
|
+
const instance = credentials?.instance || 'your-instance';
|
|
4012
|
+
const steps = await this.generateTypeSpecificManualSteps(type, config, instance);
|
|
4013
|
+
// Generate Update Set XML if available
|
|
4014
|
+
let updateSetXml = '';
|
|
4015
|
+
if (updateSetSession) {
|
|
4016
|
+
try {
|
|
4017
|
+
updateSetXml = await this.generateUpdateSetXML(type, config, updateSetSession);
|
|
4018
|
+
}
|
|
4019
|
+
catch (xmlError) {
|
|
4020
|
+
this.logger.warn('Failed to generate Update Set XML', xmlError);
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
3885
4023
|
return {
|
|
3886
4024
|
content: [{
|
|
3887
4025
|
type: 'text',
|
|
@@ -3890,42 +4028,123 @@ Your artifact has been deployed and is ready for testing.` : ''}
|
|
|
3890
4028
|
🚨 **Deployment Error**: ${error instanceof Error ? error.message : String(error)}
|
|
3891
4029
|
|
|
3892
4030
|
${updateSetSession ? `📋 **Update Set Ready**: ${updateSetSession.name} (${updateSetSession.update_set_id})
|
|
3893
|
-
✅ Manual changes will be automatically tracked in this Update Set
|
|
4031
|
+
✅ Manual changes will be automatically tracked in this Update Set.
|
|
4032
|
+
🔗 **Update Set URL**: https://${instance}/sys_update_set.do?sys_id=${updateSetSession.update_set_id}` : ''}
|
|
3894
4033
|
|
|
3895
|
-
🔧 **Manual Deployment Steps:**
|
|
4034
|
+
🔧 **Manual Deployment Steps with Direct URLs:**
|
|
3896
4035
|
|
|
3897
4036
|
${steps}
|
|
3898
4037
|
|
|
4038
|
+
${updateSetXml ? `📄 **Update Set XML Generated**
|
|
4039
|
+
Copy this XML to import the artifact:
|
|
4040
|
+
\`\`\`xml
|
|
4041
|
+
${updateSetXml.substring(0, 500)}...
|
|
4042
|
+
\`\`\`
|
|
4043
|
+
💡 Full XML saved to: update_set_${updateSetSession.update_set_id}.xml` : ''}
|
|
4044
|
+
|
|
3899
4045
|
📊 **After Manual Deployment:**
|
|
3900
4046
|
${updateSetSession ? '- Changes are automatically tracked in your active Update Set' : '- Consider creating an Update Set to track your changes'}
|
|
3901
4047
|
- Test thoroughly in your development environment
|
|
3902
4048
|
- Complete Update Set when ready for deployment
|
|
3903
4049
|
|
|
3904
|
-
💡 **
|
|
4050
|
+
💡 **Quick Actions:**
|
|
4051
|
+
- 🔧 Retry with different scope: \`snow_deploy --type ${type} --scope personal\`
|
|
4052
|
+
- 📋 Check permissions: \`snow_auth_diagnostics\`
|
|
4053
|
+
- 🚀 Use wizard mode: \`snow_${type}_wizard\``
|
|
3905
4054
|
}]
|
|
3906
4055
|
};
|
|
3907
4056
|
}
|
|
3908
4057
|
/**
|
|
3909
4058
|
* Generate type-specific manual steps
|
|
3910
4059
|
*/
|
|
3911
|
-
generateTypeSpecificManualSteps(type, config) {
|
|
4060
|
+
async generateTypeSpecificManualSteps(type, config, instance) {
|
|
3912
4061
|
switch (type) {
|
|
3913
4062
|
case 'widget':
|
|
3914
|
-
return `1.
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
4063
|
+
return `1. **Open Service Portal Widgets**
|
|
4064
|
+
🔗 URL: https://${instance}/nav_to.do?uri=%2F$sp_widget.do%3Fsys_id%3D-1%26sysparm_stack%3D$sp_widget_list.do
|
|
4065
|
+
|
|
4066
|
+
2. **Click "New" Button** (Top right of the page)
|
|
4067
|
+
📸 Look for: Blue "New" button in the header
|
|
4068
|
+
|
|
4069
|
+
3. **Fill in Widget Details:**
|
|
4070
|
+
- **Name**: ${config.name || 'your_widget_name'} (internal identifier)
|
|
4071
|
+
- **ID**: ${config.id || config.name?.toLowerCase().replace(/\s+/g, '_') || 'widget_id'}
|
|
4072
|
+
- **Title**: ${config.title || 'Your Widget Title'} (display name)
|
|
4073
|
+
- **Description**: ${config.description || 'Widget created via Snow-Flow'}
|
|
4074
|
+
|
|
4075
|
+
4. **Add Widget Code:**
|
|
4076
|
+
**HTML Template** tab:
|
|
4077
|
+
\`\`\`html
|
|
4078
|
+
${config.template || '<div>Your HTML here</div>'}
|
|
4079
|
+
\`\`\`
|
|
4080
|
+
|
|
4081
|
+
**CSS - SCSS** tab:
|
|
4082
|
+
\`\`\`css
|
|
4083
|
+
${config.css || '/* Your styles here */'}
|
|
4084
|
+
\`\`\`
|
|
4085
|
+
|
|
4086
|
+
**Client Script** tab:
|
|
4087
|
+
\`\`\`javascript
|
|
4088
|
+
${config.client_script || 'function() {\n var c = this;\n // Your client code\n}'}
|
|
4089
|
+
\`\`\`
|
|
4090
|
+
|
|
4091
|
+
**Server Script** tab:
|
|
4092
|
+
\`\`\`javascript
|
|
4093
|
+
${config.server_script || '(function() {\n // Your server code\n})();'}
|
|
4094
|
+
\`\`\`
|
|
4095
|
+
|
|
4096
|
+
5. **Save the Widget**
|
|
4097
|
+
- Click "Submit" or use Ctrl+S / Cmd+S
|
|
4098
|
+
- Note the sys_id from the URL for tracking
|
|
4099
|
+
|
|
4100
|
+
6. **Test Your Widget**
|
|
4101
|
+
🔗 Test Page URL: https://${instance}/$sp.do?id=widget_editor&sys_id=YOUR_WIDGET_SYS_ID
|
|
4102
|
+
|
|
4103
|
+
7. **Add to Portal Page**
|
|
4104
|
+
🔗 Page Designer: https://${instance}/nav_to.do?uri=%2F$sp_page.do`;
|
|
3921
4105
|
case 'flow':
|
|
3922
|
-
return `1.
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4106
|
+
return `1. **Open Flow Designer**
|
|
4107
|
+
🔗 URL: https://${instance}/nav_to.do?uri=%2Fflow_designer.do
|
|
4108
|
+
|
|
4109
|
+
2. **Create New Flow**
|
|
4110
|
+
- Click the "+" button or "New" in the top menu
|
|
4111
|
+
- Select "Flow" (not Subflow or Action)
|
|
4112
|
+
|
|
4113
|
+
3. **Configure Flow Properties:**
|
|
4114
|
+
- **Name**: ${config.name || 'approval_flow'}
|
|
4115
|
+
- **Description**: ${config.description || 'Flow created via Snow-Flow'}
|
|
4116
|
+
- **Application**: ${config.application || 'Global'}
|
|
4117
|
+
- **Protection**: None (for development)
|
|
4118
|
+
|
|
4119
|
+
4. **Set Up Trigger** (Step 1 in Flow Designer):
|
|
4120
|
+
- Click "Add a trigger"
|
|
4121
|
+
- Select: "${config.trigger_type || 'Record Created'}"
|
|
4122
|
+
- Table: ${config.table || 'Service Catalog Request [sc_request]'}
|
|
4123
|
+
${config.condition ? `- Condition: ${config.condition}` : ''}
|
|
4124
|
+
|
|
4125
|
+
5. **Add Flow Logic** (Example for approval flow):
|
|
4126
|
+
a. **Add Approval Action**:
|
|
4127
|
+
- Click "+" after trigger
|
|
4128
|
+
- Search "Approval"
|
|
4129
|
+
- Select "Ask for Approval"
|
|
4130
|
+
- Approver: ${config.approver || 'Manager of Requested for'}
|
|
4131
|
+
|
|
4132
|
+
b. **Add Condition**:
|
|
4133
|
+
- Click "+" → "Flow Logic" → "If"
|
|
4134
|
+
- Condition: Approval State = Approved
|
|
4135
|
+
|
|
4136
|
+
c. **Add Actions in "Then" branch**:
|
|
4137
|
+
- Update Request: State = Approved
|
|
4138
|
+
- Send Notification: To requester
|
|
4139
|
+
|
|
4140
|
+
6. **Save and Activate**
|
|
4141
|
+
- Click "Save" (top right)
|
|
4142
|
+
- Click "Activate" to make flow live
|
|
4143
|
+
|
|
4144
|
+
7. **Test Your Flow**
|
|
4145
|
+
🔗 Test Execution: https://${instance}/nav_to.do?uri=%2Fsys_flow_context_list.do
|
|
4146
|
+
- Create a test ${config.table || 'request'} record
|
|
4147
|
+
- Monitor execution in Flow Designer`;
|
|
3929
4148
|
case 'application':
|
|
3930
4149
|
return `1. Navigate to System Applications > Applications in ServiceNow
|
|
3931
4150
|
2. Click "New" to create a new application
|