ultravisor-beacon 0.0.1

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.
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Ultravisor Beacon Provider — FileSystem
3
+ *
4
+ * Built-in provider for local file operations on the Beacon worker.
5
+ *
6
+ * Capability: 'FileSystem'
7
+ * Actions: 'Read', 'Write', 'List', 'Copy'
8
+ *
9
+ * Provider config:
10
+ * AllowedPaths {string[]} — path prefixes the provider may access (empty = allow all)
11
+ * MaxFileSizeBytes {number} — max file size for read/write (default: 100MB)
12
+ */
13
+
14
+ const libFS = require('fs');
15
+ const libPath = require('path');
16
+
17
+ const libBeaconCapabilityProvider = require('../Ultravisor-Beacon-CapabilityProvider.cjs');
18
+
19
+ class UltravisorBeaconProviderFileSystem extends libBeaconCapabilityProvider
20
+ {
21
+ constructor(pProviderConfig)
22
+ {
23
+ super(pProviderConfig);
24
+
25
+ this.Name = 'FileSystem';
26
+ this.Capability = 'FileSystem';
27
+
28
+ this._AllowedPaths = this._ProviderConfig.AllowedPaths || [];
29
+ this._MaxFileSizeBytes = this._ProviderConfig.MaxFileSizeBytes || 104857600;
30
+ }
31
+
32
+ get actions()
33
+ {
34
+ return {
35
+ 'Read':
36
+ {
37
+ Description: 'Read a file from disk.',
38
+ SettingsSchema:
39
+ [
40
+ { Name: 'FilePath', DataType: 'String', Required: true, Description: 'Path to the file to read' },
41
+ { Name: 'Encoding', DataType: 'String', Required: false, Description: 'File encoding (default: utf8)' }
42
+ ]
43
+ },
44
+ 'Write':
45
+ {
46
+ Description: 'Write content to a file on disk.',
47
+ SettingsSchema:
48
+ [
49
+ { Name: 'FilePath', DataType: 'String', Required: true, Description: 'Path to the output file' },
50
+ { Name: 'Content', DataType: 'String', Required: true, Description: 'Content to write' },
51
+ { Name: 'Encoding', DataType: 'String', Required: false, Description: 'File encoding (default: utf8)' }
52
+ ]
53
+ },
54
+ 'List':
55
+ {
56
+ Description: 'List files in a directory.',
57
+ SettingsSchema:
58
+ [
59
+ { Name: 'Folder', DataType: 'String', Required: true, Description: 'Directory path to list' },
60
+ { Name: 'Pattern', DataType: 'String', Required: false, Description: 'Glob-style pattern filter (e.g. *.txt)' }
61
+ ]
62
+ },
63
+ 'Copy':
64
+ {
65
+ Description: 'Copy a file from source to target.',
66
+ SettingsSchema:
67
+ [
68
+ { Name: 'Source', DataType: 'String', Required: true, Description: 'Source file path' },
69
+ { Name: 'TargetFile', DataType: 'String', Required: true, Description: 'Target file path' }
70
+ ]
71
+ }
72
+ };
73
+ }
74
+
75
+ execute(pAction, pWorkItem, pContext, fCallback, fReportProgress)
76
+ {
77
+ switch (pAction)
78
+ {
79
+ case 'Read':
80
+ return this._executeRead(pWorkItem, pContext, fCallback);
81
+ case 'Write':
82
+ return this._executeWrite(pWorkItem, pContext, fCallback);
83
+ case 'List':
84
+ return this._executeList(pWorkItem, pContext, fCallback);
85
+ case 'Copy':
86
+ return this._executeCopy(pWorkItem, pContext, fCallback);
87
+ default:
88
+ return fCallback(null, {
89
+ Outputs: { StdOut: `Unknown FileSystem action: ${pAction}`, ExitCode: -1, Result: '' },
90
+ Log: [`FileSystem Provider: unsupported action [${pAction}].`]
91
+ });
92
+ }
93
+ }
94
+
95
+ // ================================================================
96
+ // Path helpers
97
+ // ================================================================
98
+
99
+ _resolvePath(pFilePath, pStagingPath)
100
+ {
101
+ if (!pFilePath) return '';
102
+ if (libPath.isAbsolute(pFilePath)) return pFilePath;
103
+ return libPath.resolve(pStagingPath || process.cwd(), pFilePath);
104
+ }
105
+
106
+ _isPathAllowed(pResolvedPath)
107
+ {
108
+ if (this._AllowedPaths.length === 0) return true;
109
+ for (let i = 0; i < this._AllowedPaths.length; i++)
110
+ {
111
+ if (pResolvedPath.startsWith(this._AllowedPaths[i])) return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ // ================================================================
117
+ // Actions
118
+ // ================================================================
119
+
120
+ _executeRead(pWorkItem, pContext, fCallback)
121
+ {
122
+ let tmpSettings = pWorkItem.Settings || {};
123
+ let tmpFilePath = tmpSettings.FilePath || '';
124
+ let tmpEncoding = tmpSettings.Encoding || 'utf8';
125
+
126
+ if (!tmpFilePath)
127
+ {
128
+ return fCallback(null, {
129
+ Outputs: { StdOut: 'No FilePath specified.', ExitCode: -1, Result: '' },
130
+ Log: ['FileSystem Read: no FilePath specified.']
131
+ });
132
+ }
133
+
134
+ tmpFilePath = this._resolvePath(tmpFilePath, pContext.StagingPath);
135
+
136
+ if (!this._isPathAllowed(tmpFilePath))
137
+ {
138
+ return fCallback(null, {
139
+ Outputs: { StdOut: `Path not allowed: ${tmpFilePath}`, ExitCode: -1, Result: '' },
140
+ Log: [`FileSystem Read: path not in AllowedPaths: ${tmpFilePath}`]
141
+ });
142
+ }
143
+
144
+ try
145
+ {
146
+ let tmpContent = libFS.readFileSync(tmpFilePath, tmpEncoding);
147
+ let tmpBytesRead = Buffer.byteLength(tmpContent, tmpEncoding);
148
+
149
+ return fCallback(null, {
150
+ Outputs: {
151
+ StdOut: `Read ${tmpBytesRead} bytes from ${tmpFilePath}`,
152
+ ExitCode: 0,
153
+ Result: tmpContent
154
+ },
155
+ Log: [`FileSystem Read: read ${tmpBytesRead} bytes from ${tmpFilePath}`]
156
+ });
157
+ }
158
+ catch (pError)
159
+ {
160
+ return fCallback(null, {
161
+ Outputs: { StdOut: `Read failed: ${pError.message}`, ExitCode: 1, Result: '' },
162
+ Log: [`FileSystem Read: failed to read ${tmpFilePath}: ${pError.message}`]
163
+ });
164
+ }
165
+ }
166
+
167
+ _executeWrite(pWorkItem, pContext, fCallback)
168
+ {
169
+ let tmpSettings = pWorkItem.Settings || {};
170
+ let tmpFilePath = tmpSettings.FilePath || '';
171
+ let tmpContent = tmpSettings.Content;
172
+ let tmpEncoding = tmpSettings.Encoding || 'utf8';
173
+
174
+ if (!tmpFilePath)
175
+ {
176
+ return fCallback(null, {
177
+ Outputs: { StdOut: 'No FilePath specified.', ExitCode: -1, Result: '' },
178
+ Log: ['FileSystem Write: no FilePath specified.']
179
+ });
180
+ }
181
+
182
+ if (tmpContent === undefined || tmpContent === null) { tmpContent = ''; }
183
+ if (typeof tmpContent !== 'string') { tmpContent = JSON.stringify(tmpContent, null, '\t'); }
184
+
185
+ tmpFilePath = this._resolvePath(tmpFilePath, pContext.StagingPath);
186
+
187
+ if (!this._isPathAllowed(tmpFilePath))
188
+ {
189
+ return fCallback(null, {
190
+ Outputs: { StdOut: `Path not allowed: ${tmpFilePath}`, ExitCode: -1, Result: '' },
191
+ Log: [`FileSystem Write: path not in AllowedPaths: ${tmpFilePath}`]
192
+ });
193
+ }
194
+
195
+ try
196
+ {
197
+ let tmpDir = libPath.dirname(tmpFilePath);
198
+ if (!libFS.existsSync(tmpDir)) { libFS.mkdirSync(tmpDir, { recursive: true }); }
199
+ libFS.writeFileSync(tmpFilePath, tmpContent, tmpEncoding);
200
+ let tmpBytesWritten = Buffer.byteLength(tmpContent, tmpEncoding);
201
+
202
+ return fCallback(null, {
203
+ Outputs: {
204
+ StdOut: `Wrote ${tmpBytesWritten} bytes to ${tmpFilePath}`,
205
+ ExitCode: 0,
206
+ Result: tmpFilePath
207
+ },
208
+ Log: [`FileSystem Write: wrote ${tmpBytesWritten} bytes to ${tmpFilePath}`]
209
+ });
210
+ }
211
+ catch (pError)
212
+ {
213
+ return fCallback(null, {
214
+ Outputs: { StdOut: `Write failed: ${pError.message}`, ExitCode: 1, Result: '' },
215
+ Log: [`FileSystem Write: failed to write ${tmpFilePath}: ${pError.message}`]
216
+ });
217
+ }
218
+ }
219
+
220
+ _executeList(pWorkItem, pContext, fCallback)
221
+ {
222
+ let tmpSettings = pWorkItem.Settings || {};
223
+ let tmpFolder = tmpSettings.Folder || '';
224
+ let tmpPattern = tmpSettings.Pattern || '*';
225
+
226
+ if (!tmpFolder)
227
+ {
228
+ return fCallback(null, {
229
+ Outputs: { StdOut: 'No Folder specified.', ExitCode: -1, Result: '' },
230
+ Log: ['FileSystem List: no Folder specified.']
231
+ });
232
+ }
233
+
234
+ tmpFolder = this._resolvePath(tmpFolder, pContext.StagingPath);
235
+
236
+ if (!this._isPathAllowed(tmpFolder))
237
+ {
238
+ return fCallback(null, {
239
+ Outputs: { StdOut: `Path not allowed: ${tmpFolder}`, ExitCode: -1, Result: '' },
240
+ Log: [`FileSystem List: path not in AllowedPaths: ${tmpFolder}`]
241
+ });
242
+ }
243
+
244
+ try
245
+ {
246
+ let tmpFiles = libFS.readdirSync(tmpFolder);
247
+
248
+ // Simple glob: convert * and ? to regex
249
+ if (tmpPattern && tmpPattern !== '*')
250
+ {
251
+ let tmpRegex = new RegExp('^' + tmpPattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
252
+ tmpFiles = tmpFiles.filter(function (pFile) { return tmpRegex.test(pFile); });
253
+ }
254
+
255
+ return fCallback(null, {
256
+ Outputs: {
257
+ StdOut: `Found ${tmpFiles.length} files in ${tmpFolder}`,
258
+ ExitCode: 0,
259
+ Result: JSON.stringify(tmpFiles)
260
+ },
261
+ Log: [`FileSystem List: found ${tmpFiles.length} files in ${tmpFolder}`]
262
+ });
263
+ }
264
+ catch (pError)
265
+ {
266
+ return fCallback(null, {
267
+ Outputs: { StdOut: `List failed: ${pError.message}`, ExitCode: 1, Result: '' },
268
+ Log: [`FileSystem List: failed: ${pError.message}`]
269
+ });
270
+ }
271
+ }
272
+
273
+ _executeCopy(pWorkItem, pContext, fCallback)
274
+ {
275
+ let tmpSettings = pWorkItem.Settings || {};
276
+ let tmpSource = tmpSettings.Source || '';
277
+ let tmpTarget = tmpSettings.TargetFile || '';
278
+
279
+ if (!tmpSource || !tmpTarget)
280
+ {
281
+ return fCallback(null, {
282
+ Outputs: { StdOut: 'Source and TargetFile are required.', ExitCode: -1, Result: '' },
283
+ Log: ['FileSystem Copy: Source and TargetFile are required.']
284
+ });
285
+ }
286
+
287
+ tmpSource = this._resolvePath(tmpSource, pContext.StagingPath);
288
+ tmpTarget = this._resolvePath(tmpTarget, pContext.StagingPath);
289
+
290
+ if (!this._isPathAllowed(tmpSource))
291
+ {
292
+ return fCallback(null, {
293
+ Outputs: { StdOut: `Source path not allowed: ${tmpSource}`, ExitCode: -1, Result: '' },
294
+ Log: [`FileSystem Copy: source not in AllowedPaths: ${tmpSource}`]
295
+ });
296
+ }
297
+
298
+ if (!this._isPathAllowed(tmpTarget))
299
+ {
300
+ return fCallback(null, {
301
+ Outputs: { StdOut: `Target path not allowed: ${tmpTarget}`, ExitCode: -1, Result: '' },
302
+ Log: [`FileSystem Copy: target not in AllowedPaths: ${tmpTarget}`]
303
+ });
304
+ }
305
+
306
+ try
307
+ {
308
+ let tmpDir = libPath.dirname(tmpTarget);
309
+ if (!libFS.existsSync(tmpDir)) { libFS.mkdirSync(tmpDir, { recursive: true }); }
310
+ libFS.copyFileSync(tmpSource, tmpTarget);
311
+
312
+ return fCallback(null, {
313
+ Outputs: {
314
+ StdOut: `Copied ${tmpSource} → ${tmpTarget}`,
315
+ ExitCode: 0,
316
+ Result: tmpTarget
317
+ },
318
+ Log: [`FileSystem Copy: copied ${tmpSource} → ${tmpTarget}`]
319
+ });
320
+ }
321
+ catch (pError)
322
+ {
323
+ return fCallback(null, {
324
+ Outputs: { StdOut: `Copy failed: ${pError.message}`, ExitCode: 1, Result: '' },
325
+ Log: [`FileSystem Copy: failed: ${pError.message}`]
326
+ });
327
+ }
328
+ }
329
+ }
330
+
331
+ module.exports = UltravisorBeaconProviderFileSystem;