snow-flow 3.5.7 ā 3.5.9
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/utils/artifact-local-sync.d.ts +1 -1
- package/dist/utils/artifact-local-sync.js +60 -131
- package/dist/utils/servicenow-client-with-tracking.d.ts +4 -0
- package/dist/utils/servicenow-client-with-tracking.js +21 -0
- package/dist/utils/servicenow-client.d.ts +4 -0
- package/dist/utils/servicenow-client.js +27 -0
- package/dist/utils/smart-field-fetcher.js +84 -15
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@ export declare class ArtifactLocalSync {
|
|
|
37
37
|
private artifacts;
|
|
38
38
|
private client;
|
|
39
39
|
private smartFetcher;
|
|
40
|
-
constructor(client: ServiceNowClient);
|
|
40
|
+
constructor(client: ServiceNowClient, customBaseDir?: string);
|
|
41
41
|
/**
|
|
42
42
|
* DYNAMIC pull artifact from ServiceNow using artifact registry
|
|
43
43
|
* Works with ANY artifact type defined in the registry!
|
|
@@ -45,20 +45,71 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.ArtifactLocalSync = void 0;
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
|
-
const os = __importStar(require("os"));
|
|
49
48
|
const smart_field_fetcher_js_1 = require("./smart-field-fetcher.js");
|
|
50
49
|
const artifact_registry_1 = require("./artifact-sync/artifact-registry");
|
|
51
50
|
class ArtifactLocalSync {
|
|
52
|
-
constructor(client) {
|
|
51
|
+
constructor(client, customBaseDir) {
|
|
53
52
|
this.artifacts = new Map();
|
|
54
53
|
this.client = client;
|
|
55
54
|
this.smartFetcher = new smart_field_fetcher_js_1.SmartFieldFetcher(client);
|
|
56
|
-
//
|
|
57
|
-
|
|
55
|
+
// Use custom directory, environment variable, or default to current project's servicenow folder
|
|
56
|
+
if (customBaseDir) {
|
|
57
|
+
this.baseDir = customBaseDir;
|
|
58
|
+
}
|
|
59
|
+
else if (process.env.SNOW_FLOW_ARTIFACTS_DIR) {
|
|
60
|
+
this.baseDir = process.env.SNOW_FLOW_ARTIFACTS_DIR;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Default to 'servicenow' folder in current working directory
|
|
64
|
+
this.baseDir = path.join(process.cwd(), 'servicenow');
|
|
65
|
+
}
|
|
66
|
+
// Create base directory if it doesn't exist
|
|
58
67
|
if (!fs.existsSync(this.baseDir)) {
|
|
59
68
|
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
69
|
+
console.log(`š Created ServiceNow artifacts directory: ${this.baseDir}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(`š Using ServiceNow artifacts directory: ${this.baseDir}`);
|
|
73
|
+
}
|
|
74
|
+
// Create .gitignore if it doesn't exist to optionally exclude from version control
|
|
75
|
+
const gitignorePath = path.join(this.baseDir, '.gitignore');
|
|
76
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
77
|
+
fs.writeFileSync(gitignorePath, '# ServiceNow Artifacts\n' +
|
|
78
|
+
'# Uncomment the following lines to exclude from version control:\n' +
|
|
79
|
+
'# *\n' +
|
|
80
|
+
'# !.gitignore\n' +
|
|
81
|
+
'# !README.md\n');
|
|
82
|
+
}
|
|
83
|
+
// Create README if it doesn't exist
|
|
84
|
+
const readmePath = path.join(this.baseDir, 'README.md');
|
|
85
|
+
if (!fs.existsSync(readmePath)) {
|
|
86
|
+
fs.writeFileSync(readmePath, '# ServiceNow Artifacts Directory\n\n' +
|
|
87
|
+
'This directory contains ServiceNow artifacts synchronized from your instance for local development.\n\n' +
|
|
88
|
+
'## Structure\n\n' +
|
|
89
|
+
'```\n' +
|
|
90
|
+
'servicenow/\n' +
|
|
91
|
+
'āāā widgets/ # Service Portal widgets\n' +
|
|
92
|
+
'āāā script_includes/ # Script Includes\n' +
|
|
93
|
+
'āāā business_rules/ # Business Rules\n' +
|
|
94
|
+
'āāā flows/ # Flow Designer flows\n' +
|
|
95
|
+
'āāā ui_pages/ # UI Pages\n' +
|
|
96
|
+
'āāā ... # Other artifact types\n' +
|
|
97
|
+
'```\n\n' +
|
|
98
|
+
'## Workflow\n\n' +
|
|
99
|
+
'1. **Pull artifacts**: `snow_pull_artifact` downloads artifacts here\n' +
|
|
100
|
+
'2. **Edit locally**: Use your IDE/editor to modify files\n' +
|
|
101
|
+
'3. **Push changes**: `snow_push_artifact` syncs changes back to ServiceNow\n' +
|
|
102
|
+
'4. **Clean up**: `snow_sync_cleanup` removes local files after sync\n\n' +
|
|
103
|
+
'## Version Control\n\n' +
|
|
104
|
+
'You can choose to:\n' +
|
|
105
|
+
'- **Track changes**: Keep artifacts in git for version history\n' +
|
|
106
|
+
'- **Ignore artifacts**: Edit `.gitignore` to exclude from git\n\n' +
|
|
107
|
+
'## Configuration\n\n' +
|
|
108
|
+
'Set custom location with environment variable:\n' +
|
|
109
|
+
'```bash\n' +
|
|
110
|
+
'export SNOW_FLOW_ARTIFACTS_DIR=/path/to/artifacts\n' +
|
|
111
|
+
'```\n');
|
|
60
112
|
}
|
|
61
|
-
console.log(`š Snow-Flow local sync directory: ${this.baseDir}`);
|
|
62
113
|
}
|
|
63
114
|
/**
|
|
64
115
|
* DYNAMIC pull artifact from ServiceNow using artifact registry
|
|
@@ -143,8 +194,11 @@ class ArtifactLocalSync {
|
|
|
143
194
|
artifactConfig: config
|
|
144
195
|
};
|
|
145
196
|
this.artifacts.set(sys_id, artifact);
|
|
197
|
+
// Show relative path if in project directory
|
|
198
|
+
const relativePath = path.relative(process.cwd(), artifactPath);
|
|
199
|
+
const displayPath = relativePath.startsWith('..') ? artifactPath : relativePath;
|
|
146
200
|
console.log(`ā
${config.displayName} synced to local files:`);
|
|
147
|
-
console.log(`š Location: ${
|
|
201
|
+
console.log(`š Location: ${displayPath}`);
|
|
148
202
|
console.log(`š Files created:`);
|
|
149
203
|
files.forEach(f => console.log(` - ${f.filename} (${f.type})`));
|
|
150
204
|
console.log(`\nš” Claude Code can now use its native tools on these files!`);
|
|
@@ -158,77 +212,6 @@ class ArtifactLocalSync {
|
|
|
158
212
|
*/
|
|
159
213
|
async pullWidget(sys_id) {
|
|
160
214
|
return this.pullArtifact('sp_widget', sys_id);
|
|
161
|
-
console.log(`\nš Pulling widget ${sys_id} to local files...`);
|
|
162
|
-
// Fetch widget with smart chunking
|
|
163
|
-
const widget = await this.smartFetcher.fetchWidget(sys_id);
|
|
164
|
-
// Create local directory structure
|
|
165
|
-
const widgetName = this.sanitizeFilename(widget.name || `widget_${sys_id}`);
|
|
166
|
-
const widgetPath = path.join(this.baseDir, 'widgets', widgetName);
|
|
167
|
-
// Clean up any existing files
|
|
168
|
-
if (fs.existsSync(widgetPath)) {
|
|
169
|
-
fs.rmSync(widgetPath, { recursive: true });
|
|
170
|
-
}
|
|
171
|
-
fs.mkdirSync(widgetPath, { recursive: true });
|
|
172
|
-
// Create local files for each widget component
|
|
173
|
-
const files = [];
|
|
174
|
-
// 1. HTML Template
|
|
175
|
-
if (widget.template) {
|
|
176
|
-
const htmlFile = this.createLocalFile(widgetPath, `${widgetName}.html`, widget.template, 'template', 'html', '<!-- ServiceNow Widget Template -->\n<!-- Widget: ' + widget.name + ' -->\n<!-- Bindings: {{data.x}} from server, ng-click calls client methods -->\n\n');
|
|
177
|
-
files.push(htmlFile);
|
|
178
|
-
}
|
|
179
|
-
// 2. Server Script (ES5)
|
|
180
|
-
if (widget.script) {
|
|
181
|
-
const serverFile = this.createLocalFile(widgetPath, `${widgetName}.server.js`, widget.script, 'script', 'js', '/**\n * ServiceNow Widget Server Script (ES5 ONLY!)\n * Widget: ' + widget.name + '\n * \n * Available objects:\n * - data: Object to send to client\n * - input: Data from client\n * - options: Widget instance options\n * - gs: GlideSystem\n * - $sp: Service Portal API\n */\n\n(function() {\n', '\n})();');
|
|
182
|
-
files.push(serverFile);
|
|
183
|
-
}
|
|
184
|
-
// 3. Client Script (AngularJS)
|
|
185
|
-
if (widget.client_script) {
|
|
186
|
-
const clientFile = this.createLocalFile(widgetPath, `${widgetName}.client.js`, widget.client_script, 'client_script', 'js', '/**\n * ServiceNow Widget Client Controller (AngularJS)\n * Widget: ' + widget.name + '\n * \n * Available objects:\n * - c: Widget controller (this)\n * - c.data: Data from server\n * - c.server: Server communication\n * - $scope: Angular scope\n */\n\nfunction(');
|
|
187
|
-
files.push(clientFile);
|
|
188
|
-
}
|
|
189
|
-
// 4. CSS
|
|
190
|
-
if (widget.css) {
|
|
191
|
-
const cssFile = this.createLocalFile(widgetPath, `${widgetName}.css`, widget.css, 'css', 'css', '/* ServiceNow Widget Styles */\n/* Widget: ' + widget.name + ' */\n/* Prefix classes to avoid conflicts */\n\n');
|
|
192
|
-
files.push(cssFile);
|
|
193
|
-
}
|
|
194
|
-
// 5. Widget Configuration (JSON)
|
|
195
|
-
const config = {
|
|
196
|
-
sys_id: widget.sys_id,
|
|
197
|
-
name: widget.name,
|
|
198
|
-
title: widget.title,
|
|
199
|
-
option_schema: widget.option_schema,
|
|
200
|
-
data_table: widget.data_table,
|
|
201
|
-
roles: widget.roles,
|
|
202
|
-
public: widget.public,
|
|
203
|
-
_coherence_hints: widget._coherence_hints || []
|
|
204
|
-
};
|
|
205
|
-
const configFile = this.createLocalFile(widgetPath, `${widgetName}.config.json`, JSON.stringify(config, null, 2), 'metadata', 'json');
|
|
206
|
-
files.push(configFile);
|
|
207
|
-
// 6. README with context
|
|
208
|
-
const readmeContent = this.generateWidgetReadme(widget, files);
|
|
209
|
-
const readmeFile = this.createLocalFile(widgetPath, 'README.md', readmeContent, 'documentation', 'md');
|
|
210
|
-
files.push(readmeFile);
|
|
211
|
-
// Create artifact record
|
|
212
|
-
const artifact = {
|
|
213
|
-
sys_id: widget.sys_id,
|
|
214
|
-
name: widget.name,
|
|
215
|
-
type: 'widget',
|
|
216
|
-
tableName: 'sp_widget', // Added missing tableName
|
|
217
|
-
localPath: widgetPath,
|
|
218
|
-
files: files,
|
|
219
|
-
metadata: widget,
|
|
220
|
-
syncStatus: 'synced',
|
|
221
|
-
createdAt: new Date(),
|
|
222
|
-
lastSyncedAt: new Date()
|
|
223
|
-
};
|
|
224
|
-
this.artifacts.set(sys_id, artifact);
|
|
225
|
-
console.log(`ā
Widget synced to local files:`);
|
|
226
|
-
console.log(`š Location: ${widgetPath}`);
|
|
227
|
-
console.log(`š Files created:`);
|
|
228
|
-
files.forEach(f => console.log(` - ${f.filename} (${f.type})`));
|
|
229
|
-
console.log(`\nš” Claude Code can now use its native tools on these files!`);
|
|
230
|
-
console.log(` Edit, search, refactor - then run 'pushWidget' to sync back.`);
|
|
231
|
-
return artifact;
|
|
232
215
|
}
|
|
233
216
|
/**
|
|
234
217
|
* DYNAMIC push local changes back to ServiceNow using artifact registry
|
|
@@ -336,60 +319,6 @@ class ArtifactLocalSync {
|
|
|
336
319
|
*/
|
|
337
320
|
async pushWidget(sys_id) {
|
|
338
321
|
return this.pushArtifact(sys_id);
|
|
339
|
-
const artifact = this.artifacts.get(sys_id);
|
|
340
|
-
if (!artifact) {
|
|
341
|
-
throw new Error(`No local artifact found for ${sys_id}. Run pullWidget first.`);
|
|
342
|
-
}
|
|
343
|
-
console.log(`\nš Pushing local changes back to ServiceNow...`);
|
|
344
|
-
// Read current content from all files
|
|
345
|
-
const updates = {};
|
|
346
|
-
let hasChanges = false;
|
|
347
|
-
for (const file of artifact.files) {
|
|
348
|
-
if (fs.existsSync(file.path)) {
|
|
349
|
-
const currentContent = fs.readFileSync(file.path, 'utf8');
|
|
350
|
-
// Strip our added headers/footers for comparison
|
|
351
|
-
const cleanContent = this.stripAddedWrappers(currentContent, file.type);
|
|
352
|
-
if (cleanContent !== file.originalContent) {
|
|
353
|
-
hasChanges = true;
|
|
354
|
-
file.currentContent = cleanContent;
|
|
355
|
-
file.isModified = true;
|
|
356
|
-
// Map back to ServiceNow field
|
|
357
|
-
if (file.field && file.field !== 'documentation' && file.field !== 'metadata') {
|
|
358
|
-
updates[file.field] = cleanContent;
|
|
359
|
-
console.log(` š Changed: ${file.filename} (${file.field})`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (!hasChanges) {
|
|
365
|
-
console.log(`ā
No changes detected. Widget is up to date.`);
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
// Validate ES5 compliance for server script
|
|
369
|
-
if (updates.script) {
|
|
370
|
-
const es5Issues = this.validateES5(updates.script);
|
|
371
|
-
if (es5Issues.length > 0) {
|
|
372
|
-
console.log(`\nā ļø ES5 Validation Issues in server script:`);
|
|
373
|
-
es5Issues.forEach(issue => console.log(` - ${issue}`));
|
|
374
|
-
console.log(`\nā Continue with deployment anyway? (ServiceNow might fail)`);
|
|
375
|
-
// In real implementation, prompt for confirmation
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
// Update in ServiceNow
|
|
379
|
-
try {
|
|
380
|
-
console.log(`\nš¤ Updating widget in ServiceNow...`);
|
|
381
|
-
await this.client.updateRecord('sp_widget', sys_id, updates);
|
|
382
|
-
artifact.syncStatus = 'synced';
|
|
383
|
-
artifact.lastSyncedAt = new Date();
|
|
384
|
-
console.log(`ā
Widget successfully updated in ServiceNow!`);
|
|
385
|
-
console.log(`š sys_id: ${sys_id}`);
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
|
-
catch (error) {
|
|
389
|
-
console.error(`ā Failed to update widget:`, error);
|
|
390
|
-
artifact.syncStatus = 'pending_upload';
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
322
|
}
|
|
394
323
|
/**
|
|
395
324
|
* Clean up local files after successful sync
|
|
@@ -10,6 +10,10 @@ export declare class ServiceNowClientWithTracking extends ServiceNowClient {
|
|
|
10
10
|
* Override makeRequest to add tracking
|
|
11
11
|
*/
|
|
12
12
|
makeRequest(config: any): Promise<any>;
|
|
13
|
+
/**
|
|
14
|
+
* Override searchRecordsWithFields to add tracking
|
|
15
|
+
*/
|
|
16
|
+
searchRecordsWithFields(table: string, query: string, fields: string[], limit?: number): Promise<any>;
|
|
13
17
|
/**
|
|
14
18
|
* Override searchRecords to add tracking
|
|
15
19
|
*/
|
|
@@ -36,6 +36,27 @@ class ServiceNowClientWithTracking extends servicenow_client_js_1.ServiceNowClie
|
|
|
36
36
|
throw error;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Override searchRecordsWithFields to add tracking
|
|
41
|
+
*/
|
|
42
|
+
async searchRecordsWithFields(table, query, fields, limit = 10) {
|
|
43
|
+
this.mcpLogger.progress(`Searching ${table} with specific fields: ${fields.join(', ')}`);
|
|
44
|
+
try {
|
|
45
|
+
const result = await super.searchRecordsWithFields(table, query, fields, limit);
|
|
46
|
+
// Estimate tokens based on fields requested
|
|
47
|
+
const fieldCount = fields.length;
|
|
48
|
+
const estimatedTokens = Math.min(fieldCount * 100, 1000); // Rough estimate
|
|
49
|
+
this.mcpLogger.trackTokens(estimatedTokens, 0);
|
|
50
|
+
if (result?.data?.result?.length > 0) {
|
|
51
|
+
this.mcpLogger.info(`Found ${result.data.result.length} records with ${fieldCount} fields`);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.mcpLogger.error('Search with fields failed', error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
39
60
|
/**
|
|
40
61
|
* Override searchRecords to add tracking
|
|
41
62
|
*/
|
|
@@ -156,6 +156,10 @@ export declare class ServiceNowClient {
|
|
|
156
156
|
* Get multiple records from a table
|
|
157
157
|
*/
|
|
158
158
|
getRecords(table: string, params?: any): Promise<ServiceNowAPIResponse<any[]>>;
|
|
159
|
+
/**
|
|
160
|
+
* Search records with specific fields
|
|
161
|
+
*/
|
|
162
|
+
searchRecordsWithFields(table: string, query: string, fields: string[], limit?: number): Promise<ServiceNowAPIResponse<any>>;
|
|
159
163
|
/**
|
|
160
164
|
* Search records in a table using encoded query
|
|
161
165
|
*/
|
|
@@ -1039,6 +1039,33 @@ class ServiceNowClient {
|
|
|
1039
1039
|
};
|
|
1040
1040
|
}
|
|
1041
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Search records with specific fields
|
|
1044
|
+
*/
|
|
1045
|
+
async searchRecordsWithFields(table, query, fields, limit = 10) {
|
|
1046
|
+
try {
|
|
1047
|
+
await this.ensureAuthenticated();
|
|
1048
|
+
const url = `/api/now/table/${table}`;
|
|
1049
|
+
const params = {
|
|
1050
|
+
sysparm_query: query,
|
|
1051
|
+
sysparm_limit: limit.toString(),
|
|
1052
|
+
sysparm_fields: fields.join(',')
|
|
1053
|
+
};
|
|
1054
|
+
const response = await this.client.get(url, { params });
|
|
1055
|
+
return {
|
|
1056
|
+
success: true,
|
|
1057
|
+
data: response.data
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
catch (error) {
|
|
1061
|
+
console.error('API Error:', error);
|
|
1062
|
+
return {
|
|
1063
|
+
success: false,
|
|
1064
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1065
|
+
data: null
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1042
1069
|
/**
|
|
1043
1070
|
* Search records in a table using encoded query
|
|
1044
1071
|
*/
|
|
@@ -152,34 +152,103 @@ class SmartFieldFetcher {
|
|
|
152
152
|
* (Wrapper for backward compatibility)
|
|
153
153
|
*/
|
|
154
154
|
async fetchWidget(sys_id) {
|
|
155
|
-
return this.fetchArtifact('sp_widget', sys_id);
|
|
156
155
|
console.log(`\nš Smart fetching widget: ${sys_id}`);
|
|
157
156
|
const results = {
|
|
158
157
|
_fetch_strategy: 'smart_chunked',
|
|
159
158
|
_context_hint: 'Widget fields fetched separately but are interconnected: template references {{data.x}} from server script, calls methods from client script, and uses CSS classes',
|
|
160
159
|
_field_groups: {}
|
|
161
160
|
};
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
// Try to fetch all fields first, then fall back to individual fields if too large
|
|
162
|
+
console.log(`š¦ Attempting to fetch complete widget data...`);
|
|
163
|
+
try {
|
|
164
|
+
// Try to get all fields at once
|
|
165
|
+
const response = await this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1);
|
|
166
|
+
if (response && response.result && response.result.length > 0) {
|
|
167
|
+
const widgetData = response.result[0];
|
|
168
|
+
console.log(`ā
Successfully fetched complete widget`);
|
|
169
|
+
// Organize into field groups for better context
|
|
170
|
+
for (const group of WIDGET_FIELD_GROUPS) {
|
|
171
|
+
const groupData = {};
|
|
172
|
+
for (const fieldName of group.fields) {
|
|
173
|
+
if (widgetData[fieldName] !== undefined) {
|
|
174
|
+
groupData[fieldName] = widgetData[fieldName];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
168
177
|
results._field_groups[group.groupName] = {
|
|
169
|
-
data:
|
|
178
|
+
data: groupData,
|
|
170
179
|
description: group.description,
|
|
171
180
|
fields: group.fields
|
|
172
181
|
};
|
|
173
|
-
// Add to flat structure for easy access
|
|
174
|
-
Object.assign(results, response.result[0]);
|
|
175
182
|
}
|
|
183
|
+
// Add complete widget data to flat structure
|
|
184
|
+
Object.assign(results, widgetData);
|
|
176
185
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
else {
|
|
187
|
+
throw new Error('Widget not found');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.log(`ā ļø Complete fetch failed: ${error.message}`);
|
|
192
|
+
console.log(`š Switching to field-by-field fetching...`);
|
|
193
|
+
// Fall back to fetching fields per group or individually
|
|
194
|
+
for (const group of WIDGET_FIELD_GROUPS) {
|
|
195
|
+
console.log(`š¦ Fetching ${group.groupName}: ${group.description}`);
|
|
196
|
+
try {
|
|
197
|
+
// Try to fetch all fields in this group at once
|
|
198
|
+
const groupResponse = await this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, group.fields, 1);
|
|
199
|
+
if (groupResponse && groupResponse.success && groupResponse.data && groupResponse.data.result && groupResponse.data.result.length > 0) {
|
|
200
|
+
const groupData = groupResponse.data.result[0];
|
|
201
|
+
console.log(` ā
Successfully fetched ${group.groupName}`);
|
|
202
|
+
results._field_groups[group.groupName] = {
|
|
203
|
+
data: groupData,
|
|
204
|
+
description: group.description,
|
|
205
|
+
fields: group.fields
|
|
206
|
+
};
|
|
207
|
+
// Add to flat structure
|
|
208
|
+
Object.assign(results, groupData);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw new Error('No data returned for group');
|
|
212
|
+
}
|
|
182
213
|
}
|
|
214
|
+
catch (groupError) {
|
|
215
|
+
console.log(` ā ļø Group ${group.groupName} failed, fetching fields individually...`);
|
|
216
|
+
// If group fails, fetch fields one by one
|
|
217
|
+
const groupData = {};
|
|
218
|
+
for (const fieldName of group.fields) {
|
|
219
|
+
console.log(` š Fetching field: ${fieldName}`);
|
|
220
|
+
try {
|
|
221
|
+
// Fetch just this single field
|
|
222
|
+
const fieldResponse = await this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, [fieldName], 1);
|
|
223
|
+
if (fieldResponse && fieldResponse.success && fieldResponse.data && fieldResponse.data.result && fieldResponse.data.result.length > 0) {
|
|
224
|
+
const fieldValue = fieldResponse.data.result[0][fieldName];
|
|
225
|
+
if (fieldValue !== undefined && fieldValue !== null) {
|
|
226
|
+
groupData[fieldName] = fieldValue;
|
|
227
|
+
results[fieldName] = fieldValue; // Also add to flat structure
|
|
228
|
+
console.log(` ā
Got ${fieldName} (${typeof fieldValue === 'string' ? fieldValue.length : 0} chars)`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
groupData[fieldName] = '';
|
|
232
|
+
console.log(` ā ļø ${fieldName} is empty`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (fieldError) {
|
|
237
|
+
console.log(` ā Failed to fetch ${fieldName}: ${fieldError.message}`);
|
|
238
|
+
groupData[fieldName] = ''; // Empty string for failed fields
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
results._field_groups[group.groupName] = {
|
|
242
|
+
data: groupData,
|
|
243
|
+
description: group.description,
|
|
244
|
+
fields: group.fields,
|
|
245
|
+
_fetched_individually: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Make sure we have at least the sys_id
|
|
250
|
+
if (!results.sys_id) {
|
|
251
|
+
results.sys_id = sys_id;
|
|
183
252
|
}
|
|
184
253
|
}
|
|
185
254
|
// Add coherence validation hints
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.9",
|
|
4
4
|
"description": "ServiceNow development framework with MCP server integration. Executes background scripts using ES5 JavaScript only (ServiceNow Rhino engine requirement). Provides 18 MCP servers including local development sync for editing ServiceNow artifacts with Claude Code native tools, widget deployment with coherence validation, table operations, script execution, machine learning, advanced features, and comprehensive platform management.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|