snow-flow 3.3.6 → 3.3.7

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.3.6';
39
+ return '3.3.7';
40
40
  }
41
41
  // Export a constant that uses the dynamic version
42
42
  exports.VERSION = getDynamicVersion();
@@ -1316,7 +1316,12 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
1316
1316
  const { updateSetId, updateSetName } = await this.ensureUpdateSet('Portal Page', args.page_id);
1317
1317
  // Validate portal page structure
1318
1318
  if (!args.page_id || !args.title) {
1319
- throw new Error('Portal page must have page_id and title');
1319
+ this.logger.error('Portal page validation failed', {
1320
+ args,
1321
+ hasPageId: !!args.page_id,
1322
+ hasTitle: !!args.title
1323
+ });
1324
+ throw new Error(`Portal page must have page_id and title. Received: page_id=${args.page_id}, title=${args.title}`);
1320
1325
  }
1321
1326
  // Find widget sys_id if widget name is provided
1322
1327
  let widgetSysId = args.widget_sys_id;
@@ -1337,18 +1342,22 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
1337
1342
  }
1338
1343
  }
1339
1344
  // Determine portal sys_id
1340
- let portalSysId = '';
1341
- try {
1342
- // Default to Employee Service Portal if available, otherwise standard Service Portal
1343
- const portalQuery = args.portal === 'esc' ? 'url_suffix=esc' : 'url_suffix=sp';
1344
- const portalResult = await this.client.searchRecords('sp_portal', portalQuery, 1);
1345
- if (portalResult.success && portalResult.data?.length > 0) {
1346
- portalSysId = portalResult.data[0].sys_id;
1347
- this.logger.info('Found portal', { portal: args.portal, sys_id: portalSysId });
1345
+ let portalSysId = args.sp_portal || '';
1346
+ // If sp_portal is already a sys_id (32 char hex), use it directly
1347
+ if (!/^[a-f0-9]{32}$/.test(portalSysId)) {
1348
+ // Not a sys_id, try to look it up
1349
+ try {
1350
+ // Default to Employee Service Portal if available, otherwise standard Service Portal
1351
+ const portalQuery = args.portal === 'esc' ? 'url_suffix=esc' : 'url_suffix=sp';
1352
+ const portalResult = await this.client.searchRecords('sp_portal', portalQuery, 1);
1353
+ if (portalResult.success && portalResult.data?.result?.length > 0) {
1354
+ portalSysId = portalResult.data.result[0].sys_id;
1355
+ this.logger.info('Found portal', { portal: args.portal, sys_id: portalSysId });
1356
+ }
1357
+ }
1358
+ catch (error) {
1359
+ this.logger.warn('Failed to lookup portal, using provided value', { portal: args.portal, error });
1348
1360
  }
1349
- }
1350
- catch (error) {
1351
- this.logger.warn('Failed to lookup portal, using default', { portal: args.portal, error });
1352
1361
  }
1353
1362
  // Create portal page
1354
1363
  let pageResult;
@@ -1381,8 +1390,35 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
1381
1390
  const credentials = await this.oauth.loadCredentials();
1382
1391
  // Create widget instances on the page if widget is provided
1383
1392
  const widgetInstances = [];
1384
- if (widgetSysId && args.widgets && args.widgets.length > 0) {
1385
- for (const widgetConfig of args.widgets) {
1393
+ // Support both widgets array and single widget
1394
+ const widgetsToCreate = args.widgets || [];
1395
+ // If no widgets array but a single widget is specified, create one widget
1396
+ if (widgetsToCreate.length === 0 && widgetSysId) {
1397
+ widgetsToCreate.push({
1398
+ widget: args.widget_name || widgetSysId,
1399
+ width: 12,
1400
+ row: 1,
1401
+ column: 1,
1402
+ title: args.widget_title || args.title || '',
1403
+ options: args.widget_options || {}
1404
+ });
1405
+ }
1406
+ if (widgetsToCreate.length > 0) {
1407
+ for (const widgetConfig of widgetsToCreate) {
1408
+ // Determine widget sys_id for this widget
1409
+ let currentWidgetSysId = widgetSysId; // Default to the main widget
1410
+ // If widget config specifies a different widget, look it up
1411
+ if (widgetConfig.widget && widgetConfig.widget !== args.widget_name) {
1412
+ try {
1413
+ const widgetLookup = await this.client.searchRecords('sp_widget', `sys_id=${widgetConfig.widget}^ORname=${widgetConfig.widget}`, 1);
1414
+ if (widgetLookup.success && widgetLookup.data?.result?.length > 0) {
1415
+ currentWidgetSysId = widgetLookup.data.result[0].sys_id;
1416
+ }
1417
+ }
1418
+ catch (lookupError) {
1419
+ this.logger.warn('Could not find widget, using default', { widget: widgetConfig.widget });
1420
+ }
1421
+ }
1386
1422
  try {
1387
1423
  // Create container
1388
1424
  const containerResult = await this.client.createRecord('sp_container', {
@@ -1417,10 +1453,10 @@ ${is403Error ? '\n⚠️ **Possible False Negative**: Widget may have been crea
1417
1453
  // Create widget instance
1418
1454
  const instanceResult = await this.client.createRecord('sp_instance', {
1419
1455
  sp_column: columnResult.data.sys_id,
1420
- sp_widget: widgetSysId,
1456
+ sp_widget: currentWidgetSysId, // Use the current widget sys_id, not the main one
1421
1457
  order: widgetConfig.order || 100,
1422
1458
  title: widgetConfig.title || '',
1423
- options: JSON.stringify(widgetConfig.options || {}),
1459
+ options: JSON.stringify(widgetConfig.options || widgetConfig.widget_parameters || {}),
1424
1460
  class_name: widgetConfig.instance_class || '',
1425
1461
  color: widgetConfig.color || 'default',
1426
1462
  active: true,
@@ -7260,7 +7296,23 @@ Use individual deployment tools like \`snow_deploy_${args.type}\` with manual co
7260
7296
  case 'widget':
7261
7297
  return await this.deployWidget(scopedConfig);
7262
7298
  case 'portal_page':
7263
- return await this.deployPortalPage(scopedConfig);
7299
+ // Map config fields to expected parameter names for portal_page
7300
+ const portalPageArgs = {
7301
+ page_id: scopedConfig.id || scopedConfig.page_id,
7302
+ title: scopedConfig.title,
7303
+ widget_name: scopedConfig.widget_name,
7304
+ widget_sys_id: scopedConfig.widget_sys_id,
7305
+ description: scopedConfig.summary || scopedConfig.description,
7306
+ page_css: scopedConfig.css,
7307
+ portal: scopedConfig.portal || scopedConfig.sp_portal || 'sp',
7308
+ public: scopedConfig.public !== undefined ? scopedConfig.public : true,
7309
+ requires_authentication: scopedConfig.requires_authentication,
7310
+ draft: scopedConfig.draft || false,
7311
+ // Map containers to widgets format
7312
+ widgets: this.mapContainersToWidgets(scopedConfig.containers),
7313
+ ...scopedConfig // Include any other fields
7314
+ };
7315
+ return await this.deployPortalPage(portalPageArgs);
7264
7316
  case 'application':
7265
7317
  return await this.deployApplication(scopedConfig);
7266
7318
  case 'xml_update_set':
@@ -8851,6 +8903,36 @@ c.$onInit = function() {
8851
8903
  await this.server.connect(transport);
8852
8904
  this.logger.info('ServiceNow Deployment MCP Server started');
8853
8905
  }
8906
+ /**
8907
+ * Map container structure to widget structure for portal page deployment
8908
+ */
8909
+ mapContainersToWidgets(containers) {
8910
+ if (!containers || !Array.isArray(containers)) {
8911
+ return [];
8912
+ }
8913
+ const widgets = [];
8914
+ for (const container of containers) {
8915
+ if (container.widget_instance) {
8916
+ widgets.push({
8917
+ widget: container.widget_instance.widget,
8918
+ width: container.width || 12,
8919
+ row: container.row || 1,
8920
+ column: container.column || 1,
8921
+ title: container.widget_instance.title || '',
8922
+ options: container.widget_instance.widget_parameters || {},
8923
+ container_title: container.title || '',
8924
+ class_name: container.class_name || '',
8925
+ background_color: container.background_color || '',
8926
+ background_image: container.background_image || '',
8927
+ background_style: container.background_style || 'default',
8928
+ instance_class: container.widget_instance.class_name || '',
8929
+ color: container.widget_instance.color || 'default',
8930
+ order: container.widget_instance.order || 100
8931
+ });
8932
+ }
8933
+ }
8934
+ return widgets;
8935
+ }
8854
8936
  /**
8855
8937
  * Escape XML special characters
8856
8938
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.3.6",
4
- "description": "Snow-Flow v3.3.6: CRITICAL QUERY & FIELD FIX - snow_query_table now fully supports pagination, field validation, and proper error handling! Fixed: offset support for large datasets, ORDERBY syntax, field discovery with correct ServiceNow queries, display values, and intelligent error messages. Enhanced field grouping (required/reference/regular), better limit strategies for analytics vs display, and clear content inclusion logic. Query any table with confidence - all 9 critical issues resolved! 180+ MCP tools across 17 specialized servers.",
3
+ "version": "3.3.7",
4
+ "description": "Snow-Flow v3.3.7: PORTAL PAGE FIX - Fixed portal_page deployment field mapping! Now correctly maps id->page_id, handles sp_portal sys_id, and converts containers to widgets format. Fixed widget instance creation with proper sys_id handling. Portal pages with AI chatbots and complex widgets now deploy successfully. Improved validation messages and support for both single widgets and widget arrays. 180+ MCP tools across 17 specialized servers.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",
7
7
  "bin": {