snow-flow 3.6.23 → 3.6.25

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.
@@ -36,7 +36,7 @@ function getDynamicVersion() {
36
36
  console.warn('Warning: Could not read version from package.json:', error);
37
37
  }
38
38
  // Fallback to hardcoded version
39
- return '3.6.23';
39
+ return '3.6.25';
40
40
  }
41
41
  // Export a constant that uses the dynamic version
42
42
  exports.VERSION = getDynamicVersion();
@@ -319,7 +319,7 @@ class ServiceNowDeploymentMCP {
319
319
  enum: [
320
320
  'widget', 'portal_page', 'application', 'script', 'business_rule', 'table',
321
321
  'script_include', 'ui_page', 'client_script', 'ui_action', 'ui_policy',
322
- 'acl', 'field', 'workflow', 'flow', 'notification', 'scheduled_job'
322
+ 'catalog_ui_policy', 'acl', 'field', 'workflow', 'flow', 'notification', 'scheduled_job'
323
323
  ],
324
324
  description: 'Type of artifact to deploy'
325
325
  },
@@ -7448,6 +7448,8 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
7448
7448
  return await this.deployApplication(scopedConfig);
7449
7449
  case 'xml_update_set':
7450
7450
  return await this.deployXMLUpdateSet(scopedConfig);
7451
+ case 'catalog_ui_policy':
7452
+ return await this.deployCatalogUIPolicy(scopedConfig);
7451
7453
  default:
7452
7454
  throw new Error(`Unsupported artifact type for unified deployment: ${type}`);
7453
7455
  }
@@ -7585,6 +7587,176 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
7585
7587
  throw new Error(`XML deployment failed: ${errorMsg}`);
7586
7588
  }
7587
7589
  }
7590
+ /**
7591
+ * Deploy Catalog UI Policy to ServiceNow
7592
+ */
7593
+ async deployCatalogUIPolicy(config) {
7594
+ const { catalog_item, cat_item, catalog_conditions, short_description, applies_to = 'all', active = true, on_load = true, reverse_if_false = true, conditions = [], actions = [] } = config;
7595
+ const catalogItemId = catalog_item || cat_item;
7596
+ if (!catalogItemId) {
7597
+ throw new Error('catalog_item or cat_item is required for catalog UI policy deployment');
7598
+ }
7599
+ if (!short_description) {
7600
+ throw new Error('short_description is required for catalog UI policy deployment');
7601
+ }
7602
+ this.logger.info('šŸš€ Deploying Catalog UI Policy', {
7603
+ catalogItem: catalogItemId,
7604
+ description: short_description,
7605
+ conditions: conditions.length,
7606
+ actions: actions.length
7607
+ });
7608
+ try {
7609
+ // If catalog_conditions is provided, convert it to conditions array
7610
+ let policyConditions = conditions;
7611
+ if (catalog_conditions && policyConditions.length === 0) {
7612
+ // Parse catalog_conditions string like "employee_type=contractor"
7613
+ const conditionParts = catalog_conditions.split('=');
7614
+ if (conditionParts.length === 2) {
7615
+ policyConditions = [{
7616
+ catalog_variable: conditionParts[0].trim(),
7617
+ operation: 'is',
7618
+ value: conditionParts[1].trim(),
7619
+ and_or: 'AND'
7620
+ }];
7621
+ this.logger.info('šŸ“ Converted catalog_conditions to conditions array:', policyConditions);
7622
+ }
7623
+ }
7624
+ // Build policy data for catalog_ui_policy table
7625
+ const policyData = {
7626
+ cat_item: catalogItemId,
7627
+ short_description: short_description,
7628
+ applies_to: applies_to,
7629
+ active: active,
7630
+ on_load: on_load,
7631
+ reverse_if_false: reverse_if_false,
7632
+ order: 100
7633
+ };
7634
+ this.logger.info('šŸŽÆ Creating main policy in catalog_ui_policy table with data:', policyData);
7635
+ const policyResponse = await this.client.createRecord('catalog_ui_policy', policyData);
7636
+ if (!policyResponse.success) {
7637
+ throw new Error(`Failed to create catalog UI policy: ${policyResponse.error}`);
7638
+ }
7639
+ const policyId = policyResponse.data.sys_id;
7640
+ this.logger.info(`āœ… Created policy with sys_id: ${policyId}`);
7641
+ // Create conditions if provided
7642
+ const createdConditions = [];
7643
+ for (let i = 0; i < policyConditions.length; i++) {
7644
+ const condition = policyConditions[i];
7645
+ // Resolve variable name to sys_id if needed
7646
+ let variableSysId = condition.catalog_variable;
7647
+ if (condition.catalog_variable && !condition.catalog_variable.match(/^[a-f0-9]{32}$/)) {
7648
+ // Search for variable by name
7649
+ const variableSearch = await this.client.searchRecords('item_option_new', `cat_item=${catalogItemId}^name=${condition.catalog_variable}`, 1);
7650
+ if (variableSearch.success && variableSearch.data.result.length > 0) {
7651
+ variableSysId = variableSearch.data.result[0].sys_id;
7652
+ this.logger.info(`āœ… Resolved variable '${condition.catalog_variable}' to sys_id: ${variableSysId}`);
7653
+ }
7654
+ else {
7655
+ this.logger.warn(`āš ļø Could not resolve variable '${condition.catalog_variable}', using as-is`);
7656
+ }
7657
+ }
7658
+ const conditionData = {
7659
+ ui_policy: policyId,
7660
+ catalog_variable: `IO:${variableSysId}`, // IO: prefix required
7661
+ operation: condition.operation || 'is',
7662
+ value: condition.value || '',
7663
+ and_or: condition.and_or || 'AND',
7664
+ order: (i + 1) * 100,
7665
+ active: true
7666
+ };
7667
+ this.logger.info(`šŸ”— Creating condition ${i + 1}:`, conditionData);
7668
+ const conditionResponse = await this.client.createRecord('catalog_ui_policy_condition', conditionData);
7669
+ if (conditionResponse.success) {
7670
+ createdConditions.push(conditionResponse.data.sys_id);
7671
+ this.logger.info(`āœ… Created condition ${i + 1} with sys_id: ${conditionResponse.data.sys_id}`);
7672
+ }
7673
+ else {
7674
+ this.logger.error(`āŒ Failed to create condition ${i + 1}:`, conditionResponse.error);
7675
+ }
7676
+ }
7677
+ // Create actions if provided
7678
+ const createdActions = [];
7679
+ for (let i = 0; i < actions.length; i++) {
7680
+ const action = actions[i];
7681
+ // Resolve variable name to sys_id if needed
7682
+ let actionVariableSysId = action.catalog_variable;
7683
+ if (action.catalog_variable && !action.catalog_variable.match(/^[a-f0-9]{32}$/)) {
7684
+ const actionVariableSearch = await this.client.searchRecords('item_option_new', `cat_item=${catalogItemId}^name=${action.catalog_variable}`, 1);
7685
+ if (actionVariableSearch.success && actionVariableSearch.data.result.length > 0) {
7686
+ actionVariableSysId = actionVariableSearch.data.result[0].sys_id;
7687
+ this.logger.info(`āœ… Resolved action variable '${action.catalog_variable}' to sys_id: ${actionVariableSysId}`);
7688
+ }
7689
+ else {
7690
+ this.logger.warn(`āš ļø Could not resolve action variable '${action.catalog_variable}', using as-is`);
7691
+ }
7692
+ }
7693
+ const actionData = {
7694
+ ui_policy: policyId,
7695
+ catalog_item: catalogItemId,
7696
+ catalog_variable: `IO:${actionVariableSysId}`, // IO: prefix required
7697
+ order: (i + 1) * 100,
7698
+ active: true
7699
+ };
7700
+ // Set action type fields based on action.type or individual properties
7701
+ if (action.type === 'set_mandatory' || action.mandatory !== undefined) {
7702
+ actionData.mandatory = action.mandatory === true ? 'true' :
7703
+ action.mandatory === false ? 'false' : 'ignore';
7704
+ }
7705
+ else {
7706
+ actionData.mandatory = 'ignore';
7707
+ }
7708
+ if (action.type === 'set_visible' || action.visible !== undefined) {
7709
+ actionData.visible = action.visible === true ? 'true' :
7710
+ action.visible === false ? 'false' : 'ignore';
7711
+ }
7712
+ else {
7713
+ actionData.visible = 'ignore';
7714
+ }
7715
+ if (action.type === 'set_readonly' || action.readonly !== undefined) {
7716
+ actionData.disabled = action.readonly === true ? 'true' :
7717
+ action.readonly === false ? 'false' : 'ignore';
7718
+ }
7719
+ else {
7720
+ actionData.disabled = 'ignore';
7721
+ }
7722
+ if (action.value !== undefined && action.value !== null && action.value !== '') {
7723
+ actionData.value = String(action.value);
7724
+ }
7725
+ this.logger.info(`šŸŽ¬ Creating action ${i + 1}:`, actionData);
7726
+ const actionResponse = await this.client.createRecord('catalog_ui_policy_action', actionData);
7727
+ if (actionResponse.success) {
7728
+ createdActions.push(actionResponse.data.sys_id);
7729
+ this.logger.info(`āœ… Created action ${i + 1} with sys_id: ${actionResponse.data.sys_id}`);
7730
+ }
7731
+ else {
7732
+ this.logger.error(`āŒ Failed to create action ${i + 1}:`, actionResponse.error);
7733
+ }
7734
+ }
7735
+ return {
7736
+ success: true,
7737
+ policy_id: policyId,
7738
+ conditions_created: createdConditions.length,
7739
+ actions_created: createdActions.length,
7740
+ message: `āœ… Catalog UI Policy '${short_description}' deployed successfully`,
7741
+ details: {
7742
+ policy_sys_id: policyId,
7743
+ catalog_item: catalogItemId,
7744
+ conditions: createdConditions,
7745
+ actions: createdActions,
7746
+ next_steps: [
7747
+ '1. Test the catalog item form to verify policy behavior',
7748
+ '2. Check that conditions trigger actions correctly',
7749
+ '3. Verify variable visibility/mandatory/readonly changes'
7750
+ ]
7751
+ }
7752
+ };
7753
+ }
7754
+ catch (error) {
7755
+ const errorMsg = error instanceof Error ? error.message : String(error);
7756
+ this.logger.error('Catalog UI Policy deployment failed', { error: errorMsg, config });
7757
+ throw new Error(`Catalog UI Policy deployment failed: ${errorMsg}`);
7758
+ }
7759
+ }
7588
7760
  /**
7589
7761
  * Check if error is permission-related
7590
7762
  */
@@ -370,11 +370,51 @@ class ServiceNowKnowledgeCatalogMCP {
370
370
  this.logger.info('Creating knowledge article...');
371
371
  // Find knowledge base if name provided
372
372
  let kbId = args.kb_knowledge_base;
373
- if (kbId && !kbId.match(/^[a-f0-9]{32}$/)) {
374
- const kbResponse = await this.client.searchRecords('kb_knowledge_base', `title=${kbId}`, 1);
375
- if (kbResponse.success && kbResponse.data.result.length) {
376
- kbId = kbResponse.data.result[0].sys_id;
373
+ let kbFound = false;
374
+ if (kbId) {
375
+ // If it's already a sys_id, verify it exists
376
+ if (kbId.match(/^[a-f0-9]{32}$/)) {
377
+ const kbVerifyResponse = await this.client.searchRecords('kb_knowledge_base', `sys_id=${kbId}`, 1);
378
+ if (kbVerifyResponse.success && kbVerifyResponse.data.result.length > 0) {
379
+ kbFound = true;
380
+ this.logger.info(`āœ… Knowledge base verified with sys_id: ${kbId}`);
381
+ }
382
+ }
383
+ else {
384
+ // It's a name/title, search for it
385
+ const kbResponse = await this.client.searchRecords('kb_knowledge_base', `title=${kbId}`, 1);
386
+ if (kbResponse.success && kbResponse.data.result.length > 0) {
387
+ kbId = kbResponse.data.result[0].sys_id;
388
+ kbFound = true;
389
+ this.logger.info(`āœ… Found knowledge base '${args.kb_knowledge_base}' with sys_id: ${kbId}`);
390
+ }
377
391
  }
392
+ // If knowledge base was specified but not found, this is an error
393
+ if (!kbFound) {
394
+ this.logger.error(`āŒ Knowledge base '${args.kb_knowledge_base}' not found!`);
395
+ // List available knowledge bases to help the user
396
+ const availableKBs = await this.client.searchRecords('kb_knowledge_base', 'active=true', 10);
397
+ let kbList = '';
398
+ if (availableKBs.success && availableKBs.data.result.length > 0) {
399
+ kbList = '\n\nšŸ“š **Available Knowledge Bases:**\n' +
400
+ availableKBs.data.result.map((kb) => ` - ${kb.title} (${kb.sys_id})`).join('\n');
401
+ }
402
+ throw new Error(`Knowledge base '${args.kb_knowledge_base}' does not exist! ` +
403
+ `Article cannot be created without a valid knowledge base.\n\n` +
404
+ `šŸ”§ **Solution Options:**\n` +
405
+ `1. Use snow_create_knowledge_base to create a new knowledge base first\n` +
406
+ `2. Use snow_discover_knowledge_bases to list existing knowledge bases\n` +
407
+ `3. Specify a valid knowledge base sys_id or title${kbList}`);
408
+ }
409
+ }
410
+ else {
411
+ // No knowledge base specified - this should also be an error for proper article management
412
+ this.logger.warn('āš ļø No knowledge base specified for article creation');
413
+ throw new Error('Knowledge base is required for article creation!\n\n' +
414
+ 'šŸ”§ **Solution:**\n' +
415
+ '1. Specify kb_knowledge_base with either a sys_id or title\n' +
416
+ '2. Use snow_discover_knowledge_bases to list available options\n' +
417
+ '3. Or create a new one with snow_create_knowledge_base');
378
418
  }
379
419
  // Find category if name provided
380
420
  let categoryId = args.kb_category;
@@ -387,7 +427,7 @@ class ServiceNowKnowledgeCatalogMCP {
387
427
  const articleData = {
388
428
  short_description: args.short_description,
389
429
  text: args.text,
390
- kb_knowledge_base: kbId || '',
430
+ kb_knowledge_base: kbId, // Now guaranteed to be valid or error thrown
391
431
  kb_category: categoryId || '',
392
432
  article_type: args.article_type || 'text',
393
433
  workflow_state: args.workflow_state || 'draft',
@@ -409,7 +449,7 @@ class ServiceNowKnowledgeCatalogMCP {
409
449
 
410
450
  šŸ“š **${args.short_description}**
411
451
  šŸ†” sys_id: ${response.data.sys_id}
412
- šŸ“‚ Knowledge Base: ${args.kb_knowledge_base || 'Default'}
452
+ šŸ“‚ Knowledge Base: ${args.kb_knowledge_base} (${kbId})
413
453
  šŸ·ļø Category: ${args.kb_category || 'Uncategorized'}
414
454
  šŸ“ Type: ${args.article_type || 'text'}
415
455
  šŸ“Š State: ${args.workflow_state || 'draft'}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.6.23",
4
- "description": "CATALOG VARIABLE TABLE FIX - v3.6.23 fixes critical issue: catalog variables now created in correct table 'item_option_new' instead of wrong table 'sc_cat_item_option'. This ensures variables are actually visible and functional in catalog items.",
3
+ "version": "3.6.25",
4
+ "description": "KNOWLEDGE BASE VERIFICATION - v3.6.25 adds critical validation to snow_create_knowledge_article. Now throws error when non-existent knowledge base is specified, preventing orphaned articles. Includes helpful suggestions to create or discover valid knowledge bases.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",
7
7
  "bin": {