@rglabs/butterfly 2.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.
Files changed (117) hide show
  1. package/CLAUDE.md +201 -0
  2. package/README.md +371 -0
  3. package/dist/commands/add.d.ts +23 -0
  4. package/dist/commands/add.js +303 -0
  5. package/dist/commands/code.d.ts +11 -0
  6. package/dist/commands/code.js +72 -0
  7. package/dist/commands/create-object.d.ts +6 -0
  8. package/dist/commands/create-object.js +293 -0
  9. package/dist/commands/create-report.d.ts +6 -0
  10. package/dist/commands/create-report.js +154 -0
  11. package/dist/commands/diff.d.ts +4 -0
  12. package/dist/commands/diff.js +238 -0
  13. package/dist/commands/download.d.ts +4 -0
  14. package/dist/commands/download.js +374 -0
  15. package/dist/commands/layout.d.ts +12 -0
  16. package/dist/commands/layout.js +83 -0
  17. package/dist/commands/record.d.ts +21 -0
  18. package/dist/commands/record.js +483 -0
  19. package/dist/commands/run-poc.d.ts +3 -0
  20. package/dist/commands/run-poc.js +18 -0
  21. package/dist/commands/setup.d.ts +3 -0
  22. package/dist/commands/setup.js +66 -0
  23. package/dist/commands/start-poc.d.ts +3 -0
  24. package/dist/commands/start-poc.js +55 -0
  25. package/dist/commands/sync-docs.d.ts +3 -0
  26. package/dist/commands/sync-docs.js +27 -0
  27. package/dist/commands/translate.d.ts +13 -0
  28. package/dist/commands/translate.js +401 -0
  29. package/dist/commands/upload.d.ts +3 -0
  30. package/dist/commands/upload.js +150 -0
  31. package/dist/commands/workflow-info.d.ts +13 -0
  32. package/dist/commands/workflow-info.js +161 -0
  33. package/dist/components/ConflictResolver.d.ts +12 -0
  34. package/dist/components/ConflictResolver.js +77 -0
  35. package/dist/components/DiffView.d.ts +11 -0
  36. package/dist/components/DiffView.js +101 -0
  37. package/dist/components/DownloadProgress.d.ts +11 -0
  38. package/dist/components/DownloadProgress.js +29 -0
  39. package/dist/components/RecordPreview.d.ts +11 -0
  40. package/dist/components/RecordPreview.js +91 -0
  41. package/dist/components/SetupForm.d.ts +8 -0
  42. package/dist/components/SetupForm.js +56 -0
  43. package/dist/components/UploadProgress.d.ts +13 -0
  44. package/dist/components/UploadProgress.js +42 -0
  45. package/dist/diff/adapters/index.d.ts +8 -0
  46. package/dist/diff/adapters/index.js +18 -0
  47. package/dist/diff/adapters/objectsAdapter.d.ts +13 -0
  48. package/dist/diff/adapters/objectsAdapter.js +177 -0
  49. package/dist/diff/adapters/reportsAdapter.d.ts +14 -0
  50. package/dist/diff/adapters/reportsAdapter.js +212 -0
  51. package/dist/diff/adapters/types.d.ts +19 -0
  52. package/dist/diff/adapters/types.js +2 -0
  53. package/dist/diff/engine.d.ts +19 -0
  54. package/dist/diff/engine.js +57 -0
  55. package/dist/diff/types.d.ts +34 -0
  56. package/dist/diff/types.js +110 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +117 -0
  59. package/dist/types/index.d.ts +18 -0
  60. package/dist/types/index.js +2 -0
  61. package/dist/utils/api.d.ts +85 -0
  62. package/dist/utils/api.js +1031 -0
  63. package/dist/utils/auth.d.ts +4 -0
  64. package/dist/utils/auth.js +22 -0
  65. package/dist/utils/bfySplitter.d.ts +12 -0
  66. package/dist/utils/bfySplitter.js +151 -0
  67. package/dist/utils/docs.d.ts +16 -0
  68. package/dist/utils/docs.js +186 -0
  69. package/dist/utils/errorLogger.d.ts +6 -0
  70. package/dist/utils/errorLogger.js +29 -0
  71. package/dist/utils/files.d.ts +14 -0
  72. package/dist/utils/files.js +772 -0
  73. package/dist/utils/lockManager.d.ts +15 -0
  74. package/dist/utils/lockManager.js +126 -0
  75. package/dist/utils/resourceHandlers.d.ts +50 -0
  76. package/dist/utils/resourceHandlers.js +684 -0
  77. package/dist/utils/resourceMapping.d.ts +32 -0
  78. package/dist/utils/resourceMapping.js +210 -0
  79. package/dist/utils/singleResourceDownload.d.ts +14 -0
  80. package/dist/utils/singleResourceDownload.js +261 -0
  81. package/dist/utils/summaryGenerator.d.ts +2 -0
  82. package/dist/utils/summaryGenerator.js +183 -0
  83. package/dist/utils/uploadHandler.d.ts +31 -0
  84. package/dist/utils/uploadHandler.js +263 -0
  85. package/docs/AI_API.md +93 -0
  86. package/docs/CLAUDE.md +216 -0
  87. package/docs/PROJECT_SPECIFIC.md +1 -0
  88. package/docs/RECORD_COMMAND.md +262 -0
  89. package/docs/WORKFLOW_API.md +480 -0
  90. package/docs/bfy-splitting.md +126 -0
  91. package/docs/cli-commands.md +333 -0
  92. package/docs/examples/README.md +95 -0
  93. package/docs/examples/order-system.md +147 -0
  94. package/docs/examples/product-catalog.md +195 -0
  95. package/docs/examples/reports.md +187 -0
  96. package/docs/excel-export.md +216 -0
  97. package/docs/field-types/README.md +29 -0
  98. package/docs/field-types/calculated.md +147 -0
  99. package/docs/field-types/code-mappings.md +84 -0
  100. package/docs/field-types/custom.md +340 -0
  101. package/docs/object-specs/README.md +136 -0
  102. package/docs/object-specs/code-parameters.md +151 -0
  103. package/docs/object-specs/creating.md +203 -0
  104. package/docs/object-specs/js-code-examples.md +208 -0
  105. package/docs/object-specs/js-field-updates.md +168 -0
  106. package/docs/objects/README.md +89 -0
  107. package/docs/objects/creating.md +127 -0
  108. package/docs/page-layout.md +361 -0
  109. package/docs/permissions.md +260 -0
  110. package/docs/reports.md +197 -0
  111. package/docs/state-machines.md +544 -0
  112. package/docs/tasks/create-object.md +81 -0
  113. package/docs/translations.md +346 -0
  114. package/docs/twig-helpers.md +283 -0
  115. package/docs/webservices.md +159 -0
  116. package/docs/workspaces.md +176 -0
  117. package/package.json +59 -0
@@ -0,0 +1,1031 @@
1
+ import axios from 'axios';
2
+ import { wrapper } from 'axios-cookiejar-support';
3
+ import { CookieJar } from 'tough-cookie';
4
+ import { join } from 'path';
5
+ import { promises as fs } from 'fs';
6
+ import { createInterface } from 'node:readline';
7
+ import { appendFileSync, mkdirSync } from 'fs';
8
+ const CONFIG_DIR = '.butterfly';
9
+ const COOKIES_FILE = 'cookies.json';
10
+ const LOG_FILE = 'requests.log';
11
+ export class ButterflyAPI {
12
+ config;
13
+ client;
14
+ cookieJar;
15
+ csrfToken;
16
+ lastValidated;
17
+ debug = process.env.BUTTERFLY_DEBUG === 'true';
18
+ pending2FAData = null;
19
+ static VALIDATION_CACHE_TTL = 5 * 60 * 1000;
20
+ constructor(config) {
21
+ this.config = config;
22
+ this.cookieJar = new CookieJar(undefined, {
23
+ rejectPublicSuffixes: false,
24
+ allowSpecialUseDomain: true,
25
+ looseMode: true
26
+ });
27
+ this.client = wrapper(axios.create({
28
+ baseURL: config.endpoint,
29
+ timeout: 30000,
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ },
33
+ jar: this.cookieJar,
34
+ withCredentials: true,
35
+ }));
36
+ this.client.interceptors.request.use(async (config) => {
37
+ const url = new URL(config.url, this.config.endpoint);
38
+ const cookies = await this.cookieJar.getCookies(url.href);
39
+ if (cookies && cookies.length > 0) {
40
+ const cookieString = cookies.map(c => `${c.key}=${c.value}`).join('; ');
41
+ config.headers['Cookie'] = cookieString;
42
+ }
43
+ if (this.debug) {
44
+ console.log('\n--- REQUEST ---');
45
+ console.log('URL:', config.url);
46
+ console.log('Method:', config.method?.toUpperCase());
47
+ console.log('Headers:', JSON.stringify(config.headers, null, 2));
48
+ console.log('Data:', JSON.stringify(config.data, null, 2));
49
+ console.log('Existing Cookies:', cookies);
50
+ }
51
+ return config;
52
+ }, (error) => {
53
+ if (this.debug) {
54
+ console.error('Request Error:', error.message);
55
+ }
56
+ return Promise.reject(error);
57
+ });
58
+ this.client.interceptors.response.use((response) => {
59
+ this.logRequest(response.config.method || 'GET', response.config.url || '', response.status);
60
+ if (this.debug) {
61
+ console.log('\n--- RESPONSE ---');
62
+ console.log('Status:', response.status, response.statusText);
63
+ console.log('Headers:', JSON.stringify(response.headers, null, 2));
64
+ console.log('Data:', JSON.stringify(response.data, null, 2));
65
+ console.log('Cookies:', this.cookieJar.getCookiesSync(this.config.endpoint));
66
+ }
67
+ return response;
68
+ }, (error) => {
69
+ this.logRequest(error.config?.method || 'GET', error.config?.url || '', error.response?.status, error.message);
70
+ if (this.debug) {
71
+ console.error('\n--- ERROR RESPONSE ---');
72
+ if (error.response) {
73
+ console.error('Status:', error.response.status, error.response.statusText);
74
+ console.error('Headers:', JSON.stringify(error.response.headers, null, 2));
75
+ console.error('Data:', JSON.stringify(error.response.data, null, 2));
76
+ }
77
+ else if (error.request) {
78
+ console.error('No response received');
79
+ console.error('Request:', error.request);
80
+ }
81
+ else {
82
+ console.error('Error:', error.message);
83
+ }
84
+ }
85
+ return Promise.reject(error);
86
+ });
87
+ }
88
+ getCookiesPath() {
89
+ return join(process.cwd(), CONFIG_DIR, COOKIES_FILE);
90
+ }
91
+ getLogPath() {
92
+ return join(process.cwd(), CONFIG_DIR, LOG_FILE);
93
+ }
94
+ logRequest(method, url, status, error) {
95
+ try {
96
+ const configDir = join(process.cwd(), CONFIG_DIR);
97
+ mkdirSync(configDir, { recursive: true });
98
+ const timestamp = new Date().toISOString();
99
+ const statusStr = status ? `${status}` : 'PENDING';
100
+ const errorStr = error ? ` | ERROR: ${error}` : '';
101
+ const logLine = `[${timestamp}] ${method.toUpperCase().padEnd(6)} ${statusStr.padEnd(3)} ${url}${errorStr}\n`;
102
+ appendFileSync(this.getLogPath(), logLine);
103
+ }
104
+ catch (e) {
105
+ }
106
+ }
107
+ async saveCookies() {
108
+ try {
109
+ const configDir = join(process.cwd(), CONFIG_DIR);
110
+ await fs.mkdir(configDir, { recursive: true });
111
+ const rawCookies = this.cookieJar.getCookiesSync(this.config.endpoint);
112
+ const cookies = rawCookies.map(c => ({
113
+ key: c.key,
114
+ value: c.value,
115
+ domain: c.domain || new URL(this.config.endpoint).hostname,
116
+ path: c.path || '/',
117
+ expires: c.expires instanceof Date ? c.expires.toISOString() : undefined
118
+ }));
119
+ const session = {
120
+ endpoint: this.config.endpoint,
121
+ cookies,
122
+ csrfToken: this.csrfToken,
123
+ timestamp: Date.now(),
124
+ lastValidated: this.lastValidated
125
+ };
126
+ await fs.writeFile(this.getCookiesPath(), JSON.stringify(session, null, 2));
127
+ if (this.debug) {
128
+ console.log('Cookies saved to disk:', cookies.map(c => c.key));
129
+ }
130
+ }
131
+ catch (error) {
132
+ if (this.debug) {
133
+ console.error('Failed to save cookies:', error.message);
134
+ }
135
+ }
136
+ }
137
+ async loadCookies() {
138
+ try {
139
+ const data = await fs.readFile(this.getCookiesPath(), 'utf-8');
140
+ const session = JSON.parse(data);
141
+ if (session.endpoint !== this.config.endpoint) {
142
+ if (this.debug) {
143
+ console.log('Stored cookies are for different endpoint, ignoring');
144
+ }
145
+ return false;
146
+ }
147
+ const maxAge = 24 * 60 * 60 * 1000;
148
+ if (Date.now() - session.timestamp > maxAge) {
149
+ if (this.debug) {
150
+ console.log('Stored cookies are too old, ignoring');
151
+ }
152
+ return false;
153
+ }
154
+ const url = this.config.endpoint;
155
+ for (const cookie of session.cookies) {
156
+ const cookieStr = `${cookie.key}=${cookie.value}; Domain=${cookie.domain}; Path=${cookie.path}`;
157
+ await this.cookieJar.setCookie(cookieStr, url);
158
+ }
159
+ this.csrfToken = session.csrfToken;
160
+ this.lastValidated = session.lastValidated;
161
+ if (this.debug) {
162
+ console.log('Cookies loaded from disk:', session.cookies.map(c => c.key));
163
+ }
164
+ return true;
165
+ }
166
+ catch (error) {
167
+ if (this.debug) {
168
+ console.log('No stored cookies found or failed to load:', error.message);
169
+ }
170
+ return false;
171
+ }
172
+ }
173
+ isRecentlyValidated() {
174
+ if (!this.lastValidated)
175
+ return false;
176
+ return (Date.now() - this.lastValidated) < ButterflyAPI.VALIDATION_CACHE_TTL;
177
+ }
178
+ async isSessionValid() {
179
+ if (this.isRecentlyValidated() && this.csrfToken) {
180
+ if (this.debug) {
181
+ console.log('Session recently validated, skipping validation request');
182
+ }
183
+ return true;
184
+ }
185
+ try {
186
+ const response = await this.client.get('/admin/ajax/cms_object/get', {
187
+ params: { table_name: 'users', column: 'id', value: '1', columns: 'id' }
188
+ });
189
+ if (response.data && (response.data.success === true || response.data.data)) {
190
+ const csrfResponse = await this.client.get('/admin/ajax/user/csrf');
191
+ if (csrfResponse.data?.token) {
192
+ this.csrfToken = csrfResponse.data.token;
193
+ }
194
+ this.lastValidated = Date.now();
195
+ if (this.debug) {
196
+ console.log('Existing session is valid');
197
+ }
198
+ return true;
199
+ }
200
+ return false;
201
+ }
202
+ catch (error) {
203
+ if (this.debug) {
204
+ console.log('Session validation failed:', error.message);
205
+ }
206
+ return false;
207
+ }
208
+ }
209
+ async fetchCSRFToken() {
210
+ try {
211
+ if (this.debug) {
212
+ console.log('\nFetching CSRF token...');
213
+ }
214
+ const response = await this.client.get('/admin/ajax/user/csrf');
215
+ if (response.data && response.data.token && typeof response.data.token === 'string') {
216
+ const token = response.data.token;
217
+ this.csrfToken = token;
218
+ if (this.debug) {
219
+ console.log('CSRF token fetched successfully');
220
+ }
221
+ return token;
222
+ }
223
+ else {
224
+ throw new Error('CSRF token not found in response');
225
+ }
226
+ }
227
+ catch (error) {
228
+ console.error('Failed to fetch CSRF token:', error.message);
229
+ throw new Error('Failed to fetch CSRF token');
230
+ }
231
+ }
232
+ async promptForInput(question) {
233
+ if (typeof process.stdin.setRawMode === 'function') {
234
+ process.stdin.setRawMode(false);
235
+ }
236
+ process.stdin.resume();
237
+ process.stdin.ref();
238
+ const rl = createInterface({
239
+ input: process.stdin,
240
+ output: process.stdout,
241
+ });
242
+ return new Promise((resolve) => {
243
+ rl.question(question, (answer) => {
244
+ rl.close();
245
+ resolve(answer.trim());
246
+ });
247
+ });
248
+ }
249
+ async handle2FAVerification(data) {
250
+ const method = data.method;
251
+ console.log('\nTwo-factor authentication required.');
252
+ if (method === 'totp') {
253
+ console.log('Method: Authenticator App');
254
+ }
255
+ else if (method === 'sms') {
256
+ console.log(`Method: SMS${data.recipient ? ` (sent to ${data.recipient})` : ''}`);
257
+ }
258
+ else if (method === 'email') {
259
+ console.log(`Method: Email${data.recipient ? ` (sent to ${data.recipient})` : ''}`);
260
+ }
261
+ if (data.message) {
262
+ console.log(`Server: ${data.message}`);
263
+ }
264
+ const maxAttempts = 3;
265
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
266
+ const code = await this.promptForInput('\nEnter verification code: ');
267
+ if (!code) {
268
+ console.log('No code entered.');
269
+ if (attempt < maxAttempts) {
270
+ console.log(`${maxAttempts - attempt} attempt(s) remaining.`);
271
+ }
272
+ continue;
273
+ }
274
+ try {
275
+ await this.fetchCSRFToken();
276
+ const formData = new URLSearchParams();
277
+ formData.append('code', code);
278
+ formData.append('trust_device', '1');
279
+ formData.append('csrf_token', this.csrfToken);
280
+ const verifyResponse = await this.client.post('/admin/ajax/twofactor/verify_code', formData, {
281
+ headers: {
282
+ 'Content-Type': 'application/x-www-form-urlencoded',
283
+ }
284
+ });
285
+ if (verifyResponse.data?.success) {
286
+ console.log('\n2FA verification successful!');
287
+ await this.saveCookies();
288
+ return;
289
+ }
290
+ console.log(`\nVerification failed: ${verifyResponse.data?.message || 'Invalid code'}`);
291
+ if (attempt < maxAttempts) {
292
+ console.log(`${maxAttempts - attempt} attempt(s) remaining.`);
293
+ }
294
+ }
295
+ catch (error) {
296
+ const message = error.response?.data?.message || error.message;
297
+ console.error(`\nVerification error: ${message}`);
298
+ if (attempt < maxAttempts) {
299
+ console.log(`${maxAttempts - attempt} attempt(s) remaining.`);
300
+ }
301
+ }
302
+ }
303
+ throw new Error('2FA verification failed after maximum attempts.');
304
+ }
305
+ get needs2FA() {
306
+ return this.pending2FAData !== null;
307
+ }
308
+ async complete2FA() {
309
+ if (!this.pending2FAData)
310
+ return;
311
+ await this.handle2FAVerification(this.pending2FAData);
312
+ this.pending2FAData = null;
313
+ }
314
+ async authenticate() {
315
+ const cookiesLoaded = await this.loadCookies();
316
+ if (cookiesLoaded) {
317
+ const isValid = await this.isSessionValid();
318
+ if (isValid) {
319
+ console.log('Reusing existing session...');
320
+ await this.saveCookies();
321
+ return;
322
+ }
323
+ if (this.debug) {
324
+ console.log('Stored session is invalid, performing fresh login');
325
+ }
326
+ }
327
+ console.log('\nAttempting to authenticate...');
328
+ console.log('Endpoint:', this.config.endpoint);
329
+ console.log('Email:', this.config.email);
330
+ console.log('Password:', '***' + this.config.password.slice(-3));
331
+ try {
332
+ await this.fetchCSRFToken();
333
+ const formData = new URLSearchParams();
334
+ formData.append('email', this.config.email);
335
+ formData.append('password', this.config.password);
336
+ formData.append('csrf_token', this.csrfToken);
337
+ const response = await this.client.post('/admin/ajax/user/login', formData, {
338
+ headers: {
339
+ 'Content-Type': 'application/x-www-form-urlencoded',
340
+ }
341
+ });
342
+ if (response.data?.require_2fa_setup) {
343
+ throw new Error('Two-factor authentication setup is required.\n' +
344
+ 'Please set up 2FA via your browser at:\n' +
345
+ `${this.config.endpoint}/admin/user/twofa_setup`);
346
+ }
347
+ if (response.data?.require_2fa) {
348
+ this.pending2FAData = response.data;
349
+ await this.saveCookies();
350
+ return;
351
+ }
352
+ if (response.data?.require_otp) {
353
+ this.pending2FAData = {
354
+ method: 'sms',
355
+ recipient: response.data.recipient || '***',
356
+ };
357
+ await this.saveCookies();
358
+ return;
359
+ }
360
+ if (!response.data || response.data.success !== true) {
361
+ throw new Error(`Authentication failed: ${response.data?.error || response.data?.message || 'Invalid credentials'}`);
362
+ }
363
+ console.log('\nAuthentication successful!');
364
+ await this.saveCookies();
365
+ if (this.debug) {
366
+ console.log('Response status:', response.status);
367
+ console.log('Response data:', JSON.stringify(response.data, null, 2));
368
+ }
369
+ }
370
+ catch (error) {
371
+ console.error('\nAuthentication failed!');
372
+ if (error.response) {
373
+ console.error('Server responded with error:');
374
+ console.error('Status:', error.response.status, error.response.statusText);
375
+ console.error('Response data:', JSON.stringify(error.response.data, null, 2));
376
+ if (error.response.status === 401) {
377
+ throw new Error('Authentication failed: Invalid credentials');
378
+ }
379
+ else if (error.response.status === 404) {
380
+ throw new Error('Authentication failed: Login endpoint not found');
381
+ }
382
+ else if (error.response.status >= 500) {
383
+ throw new Error(`Authentication failed: Server error (${error.response.status})`);
384
+ }
385
+ else {
386
+ throw new Error(`Authentication failed: ${error.response.status} ${error.response.statusText}`);
387
+ }
388
+ }
389
+ else if (error.request) {
390
+ console.error('No response received from server');
391
+ console.error('This could be due to:');
392
+ console.error('- Network connectivity issues');
393
+ console.error('- Invalid endpoint URL');
394
+ console.error('- Server is down');
395
+ throw new Error('Authentication failed: No response from server');
396
+ }
397
+ else {
398
+ console.error('Request setup error:', error.message);
399
+ throw new Error(`Authentication failed: ${error.message}`);
400
+ }
401
+ }
402
+ }
403
+ get httpClient() {
404
+ return this.client;
405
+ }
406
+ async downloadResource(type, name) {
407
+ try {
408
+ const url = name ? `/${type}/${name}` : `/${type}`;
409
+ const response = await this.client.get(url);
410
+ return Array.isArray(response.data) ? response.data : [response.data];
411
+ }
412
+ catch (error) {
413
+ throw new Error(`Failed to download ${type}`);
414
+ }
415
+ }
416
+ async fetchTable(tableName, column, value) {
417
+ try {
418
+ if (this.debug) {
419
+ console.log(`Cookies before fetch${tableName}:`, this.cookieJar.getCookiesSync(this.config.endpoint));
420
+ }
421
+ const params = {
422
+ table_name: tableName,
423
+ db_alias: 'default'
424
+ };
425
+ if (column && value !== undefined) {
426
+ params.column = column;
427
+ params.value = value;
428
+ }
429
+ const response = await this.client.get('/admin/ajax/cms_object/get', { params });
430
+ if (response.data && response.data.success === false &&
431
+ (response.data.message === "Records Not Found" || response.data.message === "Object Not Found")) {
432
+ return [];
433
+ }
434
+ if (!response.data || !response.data.data) {
435
+ throw new Error(`Invalid response format from ${tableName} endpoint: ` + JSON.stringify(response.data));
436
+ }
437
+ return response.data.data;
438
+ }
439
+ catch (error) {
440
+ console.error(`Error fetching ${tableName}:`, error.message);
441
+ throw new Error(`Failed to fetch ${tableName} from Butterfly`);
442
+ }
443
+ }
444
+ async fetchTableAdvanced(tableName, options = {}) {
445
+ try {
446
+ if (this.debug) {
447
+ console.log(`Cookies before fetch${tableName}:`, this.cookieJar.getCookiesSync(this.config.endpoint));
448
+ }
449
+ const params = {
450
+ table_name: tableName,
451
+ database_alias: options.dbAlias || 'default'
452
+ };
453
+ if (options.column && options.value !== undefined) {
454
+ params.column = options.column;
455
+ params.value = options.value;
456
+ }
457
+ if (options.columns) {
458
+ params.columns = options.columns;
459
+ }
460
+ const response = await this.client.get('/admin/ajax/cms_object/get', { params });
461
+ if (response.data && response.data.success === false &&
462
+ (response.data.message === "Records Not Found" || response.data.message === "Object Not Found")) {
463
+ return [];
464
+ }
465
+ if (!response.data || !response.data.data) {
466
+ throw new Error(`Invalid response format from ${tableName} endpoint: ` + JSON.stringify(response.data));
467
+ }
468
+ return response.data.data;
469
+ }
470
+ catch (error) {
471
+ console.error(`Error fetching ${tableName}:`, error.message);
472
+ throw new Error(`Failed to fetch ${tableName} from Butterfly`);
473
+ }
474
+ }
475
+ async fetchObjects() {
476
+ return this.fetchTable('objects', 'is_cms_object', '0');
477
+ }
478
+ async fetchCMSObjects() {
479
+ return this.fetchTable('objects', 'is_cms_object', '1');
480
+ }
481
+ async fetchObjectSpecs(objectId) {
482
+ try {
483
+ return await this.fetchTable('object_specs', 'object_id', objectId.toString());
484
+ }
485
+ catch (error) {
486
+ console.error(`Error fetching specs for object ${objectId}:`, error.message);
487
+ return [];
488
+ }
489
+ }
490
+ async fetchAllObjectSpecs() {
491
+ try {
492
+ return await this.fetchTable('object_specs');
493
+ }
494
+ catch (error) {
495
+ console.error('Error fetching all object specs:', error.message);
496
+ return [];
497
+ }
498
+ }
499
+ async fetchReports() {
500
+ return this.fetchTable('cms_reports');
501
+ }
502
+ async fetchReportCategories() {
503
+ try {
504
+ return await this.fetchTable('cms_report_categories');
505
+ }
506
+ catch (error) {
507
+ console.error('Error fetching report categories:', error.message);
508
+ return [];
509
+ }
510
+ }
511
+ async fetchReportSpecs(reportId) {
512
+ try {
513
+ return await this.fetchTable('cms_report_specs', 'cms_report_id', reportId.toString());
514
+ }
515
+ catch (error) {
516
+ console.error(`Error fetching specs for report ${reportId}:`, error.message);
517
+ return [];
518
+ }
519
+ }
520
+ async fetchReportQueries(reportId) {
521
+ try {
522
+ return await this.fetchTable('cms_report_queries', 'cms_report_id', reportId.toString());
523
+ }
524
+ catch (error) {
525
+ console.error(`Error fetching queries for report ${reportId}:`, error.message);
526
+ return [];
527
+ }
528
+ }
529
+ async fetchAllReportSpecs() {
530
+ try {
531
+ return await this.fetchTable('cms_report_specs');
532
+ }
533
+ catch (error) {
534
+ console.error('Error fetching all report specs:', error.message);
535
+ return [];
536
+ }
537
+ }
538
+ async fetchAllReportQueries() {
539
+ try {
540
+ return await this.fetchTable('cms_report_queries');
541
+ }
542
+ catch (error) {
543
+ console.error('Error fetching all report queries:', error.message);
544
+ return [];
545
+ }
546
+ }
547
+ async updateReport(reportId, updateData) {
548
+ try {
549
+ const formData = new URLSearchParams();
550
+ formData.append('id', reportId.toString());
551
+ Object.entries(updateData).forEach(([key, value]) => {
552
+ if (value !== null && value !== undefined) {
553
+ formData.append(key, String(value));
554
+ }
555
+ });
556
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_reports__edit', formData, {
557
+ headers: {
558
+ 'Content-Type': 'application/x-www-form-urlencoded',
559
+ }
560
+ });
561
+ return response.data;
562
+ }
563
+ catch (error) {
564
+ console.error(`Error updating report ${reportId}:`, error.message);
565
+ throw new Error(`Failed to update report: ${error.message}`);
566
+ }
567
+ }
568
+ async createReport(reportData) {
569
+ try {
570
+ const formData = new URLSearchParams();
571
+ if (reportData.hide_in_list === undefined || reportData.hide_in_list === null) {
572
+ formData.append('hide_in_list', '0');
573
+ }
574
+ Object.entries(reportData).forEach(([key, value]) => {
575
+ if (value !== null && value !== undefined) {
576
+ formData.append(key, String(value));
577
+ }
578
+ });
579
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_reports__add', formData, {
580
+ headers: {
581
+ 'Content-Type': 'application/x-www-form-urlencoded',
582
+ }
583
+ });
584
+ return response.data;
585
+ }
586
+ catch (error) {
587
+ console.error('Error creating report:', error.message);
588
+ throw new Error(`Failed to create report: ${error.message}`);
589
+ }
590
+ }
591
+ async updateReportSpec(specId, updateData) {
592
+ try {
593
+ const formData = new URLSearchParams();
594
+ formData.append('id', specId.toString());
595
+ Object.entries(updateData).forEach(([key, value]) => {
596
+ if (value !== null && value !== undefined) {
597
+ formData.append(key, String(value));
598
+ }
599
+ });
600
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_report_specs__edit', formData, {
601
+ headers: {
602
+ 'Content-Type': 'application/x-www-form-urlencoded',
603
+ }
604
+ });
605
+ return response.data;
606
+ }
607
+ catch (error) {
608
+ console.error(`Error updating report spec ${specId}:`, error.message);
609
+ throw new Error(`Failed to update report spec: ${error.message}`);
610
+ }
611
+ }
612
+ async createReportSpec(specData) {
613
+ try {
614
+ const formData = new URLSearchParams();
615
+ Object.entries(specData).forEach(([key, value]) => {
616
+ if (value !== null && value !== undefined) {
617
+ formData.append(key, String(value));
618
+ }
619
+ });
620
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_report_specs__add', formData, {
621
+ headers: {
622
+ 'Content-Type': 'application/x-www-form-urlencoded',
623
+ }
624
+ });
625
+ return response.data;
626
+ }
627
+ catch (error) {
628
+ console.error('Error creating report spec:', error.message);
629
+ throw new Error(`Failed to create report spec: ${error.message}`);
630
+ }
631
+ }
632
+ async updateReportQuery(queryId, updateData) {
633
+ try {
634
+ const formData = new URLSearchParams();
635
+ formData.append('id', queryId.toString());
636
+ Object.entries(updateData).forEach(([key, value]) => {
637
+ if (value !== null && value !== undefined) {
638
+ formData.append(key, String(value));
639
+ }
640
+ });
641
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_report_queries__edit', formData, {
642
+ headers: {
643
+ 'Content-Type': 'application/x-www-form-urlencoded',
644
+ }
645
+ });
646
+ return response.data;
647
+ }
648
+ catch (error) {
649
+ console.error(`Error updating report query ${queryId}:`, error.message);
650
+ throw new Error(`Failed to update report query: ${error.message}`);
651
+ }
652
+ }
653
+ async createReportQuery(queryData) {
654
+ try {
655
+ const formData = new URLSearchParams();
656
+ Object.entries(queryData).forEach(([key, value]) => {
657
+ if (value !== null && value !== undefined) {
658
+ formData.append(key, String(value));
659
+ }
660
+ });
661
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=cms_report_queries__add', formData, {
662
+ headers: {
663
+ 'Content-Type': 'application/x-www-form-urlencoded',
664
+ }
665
+ });
666
+ return response.data;
667
+ }
668
+ catch (error) {
669
+ console.error('Error creating report query:', error.message);
670
+ throw new Error(`Failed to create report query: ${error.message}`);
671
+ }
672
+ }
673
+ async uploadResource(filePath, data) {
674
+ try {
675
+ const pathParts = filePath.split('/');
676
+ const fileName = pathParts[pathParts.length - 1];
677
+ if (pathParts.includes('objects')) {
678
+ if (fileName === 'object.json') {
679
+ return await this.updateObject(data);
680
+ }
681
+ else if (fileName === 'spec.json') {
682
+ return await this.updateObjectSpec(data);
683
+ }
684
+ }
685
+ else if (pathParts.includes('reports')) {
686
+ if (fileName === 'report.json') {
687
+ return await this.updateReportFromData(data);
688
+ }
689
+ else if (fileName === 'spec.json') {
690
+ return await this.updateReportSpecFromData(data);
691
+ }
692
+ }
693
+ throw new Error(`Unsupported file type: ${fileName} in path: ${filePath}`);
694
+ }
695
+ catch (error) {
696
+ console.error(`Error uploading resource ${filePath}:`, error.message);
697
+ throw error;
698
+ }
699
+ }
700
+ async updateObject(objectData) {
701
+ const formData = new URLSearchParams();
702
+ if (this.csrfToken) {
703
+ formData.append('csrf_token', this.csrfToken);
704
+ }
705
+ Object.keys(objectData).forEach(key => {
706
+ if (objectData[key] !== null && objectData[key] !== undefined) {
707
+ formData.append(key, objectData[key].toString());
708
+ }
709
+ });
710
+ const url = `/admin/ajax/cms_object/operation?do=objects__edit`;
711
+ const response = await this.client.post(url, formData, {
712
+ headers: {
713
+ 'Content-Type': 'application/x-www-form-urlencoded',
714
+ }
715
+ });
716
+ if (!response.data || !response.data.success) {
717
+ throw new Error(`Failed to update object: ${response.data?.error || 'Unknown error'}`);
718
+ }
719
+ return response.data;
720
+ }
721
+ async updateObjectSpec(specData) {
722
+ const formData = new URLSearchParams();
723
+ if (this.csrfToken) {
724
+ formData.append('csrf_token', this.csrfToken);
725
+ }
726
+ Object.keys(specData).forEach(key => {
727
+ if (specData[key] !== null && specData[key] !== undefined) {
728
+ formData.append(key, specData[key].toString());
729
+ }
730
+ });
731
+ const url = `/admin/ajax/cms_object/operation?do=object_specs__edit`;
732
+ const response = await this.client.post(url, formData, {
733
+ headers: {
734
+ 'Content-Type': 'application/x-www-form-urlencoded',
735
+ }
736
+ });
737
+ if (!response.data || !response.data.success) {
738
+ throw new Error(`Failed to update object spec: ${response.data?.error || 'Unknown error'}`);
739
+ }
740
+ return response.data;
741
+ }
742
+ async updateReportFromData(reportData) {
743
+ const formData = new URLSearchParams();
744
+ if (this.csrfToken) {
745
+ formData.append('csrf_token', this.csrfToken);
746
+ }
747
+ Object.keys(reportData).forEach(key => {
748
+ if (reportData[key] !== null && reportData[key] !== undefined) {
749
+ formData.append(key, reportData[key].toString());
750
+ }
751
+ });
752
+ const url = `/admin/ajax/cms_object/operation?do=cms_reports__edit`;
753
+ const response = await this.client.post(url, formData, {
754
+ headers: {
755
+ 'Content-Type': 'application/x-www-form-urlencoded',
756
+ }
757
+ });
758
+ if (!response.data || !response.data.success) {
759
+ throw new Error(`Failed to update report: ${response.data?.error || 'Unknown error'}`);
760
+ }
761
+ return response.data;
762
+ }
763
+ async updateReportSpecFromData(specData) {
764
+ const formData = new URLSearchParams();
765
+ if (this.csrfToken) {
766
+ formData.append('csrf_token', this.csrfToken);
767
+ }
768
+ Object.keys(specData).forEach(key => {
769
+ if (specData[key] !== null && specData[key] !== undefined) {
770
+ formData.append(key, specData[key].toString());
771
+ }
772
+ });
773
+ const url = `/admin/ajax/cms_object/operation?do=cms_report_specs__edit`;
774
+ const response = await this.client.post(url, formData, {
775
+ headers: {
776
+ 'Content-Type': 'application/x-www-form-urlencoded',
777
+ }
778
+ });
779
+ if (!response.data || !response.data.success) {
780
+ throw new Error(`Failed to update report spec: ${response.data?.error || 'Unknown error'}`);
781
+ }
782
+ return response.data;
783
+ }
784
+ async createWorkflowConnection(connectionData) {
785
+ try {
786
+ const formData = new URLSearchParams();
787
+ Object.entries(connectionData).forEach(([key, value]) => {
788
+ if (value !== null && value !== undefined) {
789
+ formData.append(key, String(value));
790
+ }
791
+ });
792
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_connections__add', formData, {
793
+ headers: {
794
+ 'Content-Type': 'application/x-www-form-urlencoded',
795
+ }
796
+ });
797
+ return response.data;
798
+ }
799
+ catch (error) {
800
+ console.error('Error creating workflow connection:', error.message);
801
+ throw new Error(`Failed to create workflow connection: ${error.message}`);
802
+ }
803
+ }
804
+ async updateWorkflowConnection(connectionId, updateData) {
805
+ try {
806
+ const formData = new URLSearchParams();
807
+ formData.append('id', connectionId.toString());
808
+ Object.entries(updateData).forEach(([key, value]) => {
809
+ if (key !== 'id' && value !== null && value !== undefined) {
810
+ formData.append(key, String(value));
811
+ }
812
+ });
813
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_connections__edit', formData, {
814
+ headers: {
815
+ 'Content-Type': 'application/x-www-form-urlencoded',
816
+ }
817
+ });
818
+ return response.data;
819
+ }
820
+ catch (error) {
821
+ console.error(`Error updating workflow connection ${connectionId}:`, error.message);
822
+ throw new Error(`Failed to update workflow connection: ${error.message}`);
823
+ }
824
+ }
825
+ async deleteWorkflowConnection(connectionId) {
826
+ try {
827
+ const formData = new URLSearchParams();
828
+ formData.append('id', connectionId.toString());
829
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_connections__delete', formData, {
830
+ headers: {
831
+ 'Content-Type': 'application/x-www-form-urlencoded',
832
+ }
833
+ });
834
+ return response.data;
835
+ }
836
+ catch (error) {
837
+ console.error(`Error deleting workflow connection ${connectionId}:`, error.message);
838
+ throw new Error(`Failed to delete workflow connection: ${error.message}`);
839
+ }
840
+ }
841
+ async createWorkflowNode(nodeData) {
842
+ try {
843
+ const formData = new URLSearchParams();
844
+ Object.entries(nodeData).forEach(([key, value]) => {
845
+ if (value !== null && value !== undefined) {
846
+ if (typeof value === 'object') {
847
+ formData.append(key, JSON.stringify(value));
848
+ }
849
+ else {
850
+ formData.append(key, String(value));
851
+ }
852
+ }
853
+ });
854
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_nodes__add', formData, {
855
+ headers: {
856
+ 'Content-Type': 'application/x-www-form-urlencoded',
857
+ }
858
+ });
859
+ return response.data;
860
+ }
861
+ catch (error) {
862
+ console.error('Error creating workflow node:', error.message);
863
+ throw new Error(`Failed to create workflow node: ${error.message}`);
864
+ }
865
+ }
866
+ async updateWorkflowNode(nodeId, updateData) {
867
+ try {
868
+ const formData = new URLSearchParams();
869
+ formData.append('id', nodeId.toString());
870
+ Object.entries(updateData).forEach(([key, value]) => {
871
+ if (key !== 'id' && value !== null && value !== undefined) {
872
+ if (typeof value === 'object') {
873
+ formData.append(key, JSON.stringify(value));
874
+ }
875
+ else {
876
+ formData.append(key, String(value));
877
+ }
878
+ }
879
+ });
880
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_nodes__edit', formData, {
881
+ headers: {
882
+ 'Content-Type': 'application/x-www-form-urlencoded',
883
+ }
884
+ });
885
+ return response.data;
886
+ }
887
+ catch (error) {
888
+ console.error(`Error updating workflow node ${nodeId}:`, error.message);
889
+ throw new Error(`Failed to update workflow node: ${error.message}`);
890
+ }
891
+ }
892
+ async deleteWorkflowNode(nodeId) {
893
+ try {
894
+ const formData = new URLSearchParams();
895
+ formData.append('id', nodeId.toString());
896
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_nodes__delete', formData, {
897
+ headers: {
898
+ 'Content-Type': 'application/x-www-form-urlencoded',
899
+ }
900
+ });
901
+ return response.data;
902
+ }
903
+ catch (error) {
904
+ console.error(`Error deleting workflow node ${nodeId}:`, error.message);
905
+ throw new Error(`Failed to delete workflow node: ${error.message}`);
906
+ }
907
+ }
908
+ async createWorkflowVersion(versionData) {
909
+ try {
910
+ const formData = new URLSearchParams();
911
+ Object.entries(versionData).forEach(([key, value]) => {
912
+ if (value !== null && value !== undefined) {
913
+ formData.append(key, String(value));
914
+ }
915
+ });
916
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_versions__add', formData, {
917
+ headers: {
918
+ 'Content-Type': 'application/x-www-form-urlencoded',
919
+ }
920
+ });
921
+ return response.data;
922
+ }
923
+ catch (error) {
924
+ console.error('Error creating workflow version:', error.message);
925
+ throw new Error(`Failed to create workflow version: ${error.message}`);
926
+ }
927
+ }
928
+ async updateWorkflowVersion(versionId, updateData) {
929
+ try {
930
+ const formData = new URLSearchParams();
931
+ formData.append('id', versionId.toString());
932
+ Object.entries(updateData).forEach(([key, value]) => {
933
+ if (key !== 'id' && value !== null && value !== undefined) {
934
+ formData.append(key, String(value));
935
+ }
936
+ });
937
+ const response = await this.client.post('/admin/ajax/cms_object/operation?do=bfy_workflow_versions__edit', formData, {
938
+ headers: {
939
+ 'Content-Type': 'application/x-www-form-urlencoded',
940
+ }
941
+ });
942
+ return response.data;
943
+ }
944
+ catch (error) {
945
+ console.error(`Error updating workflow version ${versionId}:`, error.message);
946
+ throw new Error(`Failed to update workflow version: ${error.message}`);
947
+ }
948
+ }
949
+ async publishWorkflowVersion(versionId) {
950
+ return this.updateWorkflowVersion(versionId, { status: 2 });
951
+ }
952
+ async archiveWorkflowVersion(versionId) {
953
+ return this.updateWorkflowVersion(versionId, { status: 3 });
954
+ }
955
+ async executeCode(code) {
956
+ try {
957
+ const formData = new URLSearchParams();
958
+ formData.append('code', code);
959
+ const response = await this.client.post('/admin/api/code', formData, {
960
+ headers: {
961
+ 'Content-Type': 'application/x-www-form-urlencoded',
962
+ }
963
+ });
964
+ return {
965
+ success: response.data?.success === true,
966
+ vars: response.data?.vars,
967
+ output: response.data?.output,
968
+ error: response.data?.error
969
+ };
970
+ }
971
+ catch (error) {
972
+ console.error('Error executing code:', error.message);
973
+ return {
974
+ success: false,
975
+ error: error.message || 'Unknown error'
976
+ };
977
+ }
978
+ }
979
+ async fetchRecordById(tableName, id, _dbAlias = 'default') {
980
+ try {
981
+ const records = await this.fetchTable(tableName, 'id', id.toString());
982
+ return records.length > 0 ? records[0] : null;
983
+ }
984
+ catch (error) {
985
+ console.error(`Error fetching record ${id} from ${tableName}:`, error.message);
986
+ return null;
987
+ }
988
+ }
989
+ async performOperation(tableName, operation, data, dbAlias = 'default') {
990
+ try {
991
+ const formData = new URLSearchParams();
992
+ if (this.csrfToken) {
993
+ formData.append('csrf_token', this.csrfToken);
994
+ }
995
+ if (dbAlias !== 'default') {
996
+ formData.append('db_alias', dbAlias);
997
+ }
998
+ Object.entries(data).forEach(([key, value]) => {
999
+ if (value !== null && value !== undefined) {
1000
+ if (typeof value === 'object') {
1001
+ formData.append(key, JSON.stringify(value));
1002
+ }
1003
+ else {
1004
+ formData.append(key, String(value));
1005
+ }
1006
+ }
1007
+ });
1008
+ const endpoint = `/admin/ajax/cms_object/operation?do=${tableName}__${operation}`;
1009
+ const response = await this.client.post(endpoint, formData, {
1010
+ headers: {
1011
+ 'Content-Type': 'application/x-www-form-urlencoded',
1012
+ }
1013
+ });
1014
+ return {
1015
+ success: response.data?.success === true,
1016
+ id: response.data?.id,
1017
+ error: response.data?.error,
1018
+ message: response.data?.message,
1019
+ xtra: response.data?.xtra
1020
+ };
1021
+ }
1022
+ catch (error) {
1023
+ console.error(`Error performing ${operation} on ${tableName}:`, error.message);
1024
+ return {
1025
+ success: false,
1026
+ error: error.message || 'Unknown error'
1027
+ };
1028
+ }
1029
+ }
1030
+ }
1031
+ //# sourceMappingURL=api.js.map