snow-flow 3.5.8 → 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.
@@ -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
- // Create base directory for Snow-Flow artifacts
57
- this.baseDir = path.join(os.tmpdir(), 'snow-flow-artifacts');
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: ${artifactPath}`);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.5.8",
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",