@friggframework/admin-scripts 2.0.0--canary.517.a37d697.0 → 2.0.0--canary.517.300ded3.0

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.
@@ -1,296 +0,0 @@
1
- /**
2
- * Dry-Run HTTP Interceptor
3
- *
4
- * Creates a mock HTTP client that logs requests instead of executing them.
5
- * Used to intercept API module calls during dry-run.
6
- */
7
-
8
- /**
9
- * Sanitize headers to remove authentication tokens
10
- * @param {Object} headers - HTTP headers
11
- * @returns {Object} Sanitized headers
12
- */
13
- function sanitizeHeaders(headers) {
14
- if (!headers || typeof headers !== 'object') {
15
- return {};
16
- }
17
-
18
- const safe = { ...headers };
19
-
20
- // Remove common auth headers
21
- const sensitiveHeaders = [
22
- 'authorization',
23
- 'Authorization',
24
- 'x-api-key',
25
- 'X-API-Key',
26
- 'x-auth-token',
27
- 'X-Auth-Token',
28
- 'api-key',
29
- 'API-Key',
30
- 'apikey',
31
- 'ApiKey',
32
- 'token',
33
- 'Token',
34
- ];
35
-
36
- for (const header of sensitiveHeaders) {
37
- if (safe[header]) {
38
- safe[header] = '[REDACTED]';
39
- }
40
- }
41
-
42
- return safe;
43
- }
44
-
45
- /**
46
- * Detect service name from base URL
47
- * @param {string} baseURL - Base URL of the API
48
- * @returns {string} Service name
49
- */
50
- function detectService(baseURL) {
51
- if (!baseURL) return 'unknown';
52
-
53
- const url = baseURL.toLowerCase();
54
-
55
- // CRM Systems
56
- if (url.includes('hubspot') || url.includes('hubapi')) return 'HubSpot';
57
- if (url.includes('salesforce')) return 'Salesforce';
58
- if (url.includes('pipedrive')) return 'Pipedrive';
59
- if (url.includes('zoho')) return 'Zoho CRM';
60
- if (url.includes('attio')) return 'Attio';
61
-
62
- // Communication
63
- if (url.includes('slack')) return 'Slack';
64
- if (url.includes('discord')) return 'Discord';
65
- if (url.includes('teams.microsoft')) return 'Microsoft Teams';
66
-
67
- // Project Management
68
- if (url.includes('asana')) return 'Asana';
69
- if (url.includes('monday')) return 'Monday.com';
70
- if (url.includes('trello')) return 'Trello';
71
- if (url.includes('clickup')) return 'ClickUp';
72
-
73
- // Storage
74
- if (url.includes('googleapis.com/drive')) return 'Google Drive';
75
- if (url.includes('dropbox')) return 'Dropbox';
76
- if (url.includes('box.com')) return 'Box';
77
-
78
- // Email & Marketing
79
- if (url.includes('sendgrid')) return 'SendGrid';
80
- if (url.includes('mailchimp')) return 'Mailchimp';
81
- if (url.includes('gmail')) return 'Gmail';
82
-
83
- // Accounting
84
- if (url.includes('quickbooks')) return 'QuickBooks';
85
- if (url.includes('xero')) return 'Xero';
86
-
87
- // Other
88
- if (url.includes('stripe')) return 'Stripe';
89
- if (url.includes('shopify')) return 'Shopify';
90
- if (url.includes('github')) return 'GitHub';
91
- if (url.includes('gitlab')) return 'GitLab';
92
-
93
- return 'unknown';
94
- }
95
-
96
- /**
97
- * Sanitize request data to remove sensitive information
98
- * @param {*} data - Request data
99
- * @returns {*} Sanitized data
100
- */
101
- function sanitizeData(data) {
102
- if (data === null || data === undefined) {
103
- return data;
104
- }
105
-
106
- if (typeof data !== 'object') {
107
- return data;
108
- }
109
-
110
- if (Array.isArray(data)) {
111
- return data.map(sanitizeData);
112
- }
113
-
114
- const sanitized = {};
115
- for (const [key, value] of Object.entries(data)) {
116
- const lowerKey = key.toLowerCase();
117
-
118
- // Check if this is a leaf node that should be redacted
119
- const isSensitiveField =
120
- lowerKey === 'password' ||
121
- lowerKey === 'token' ||
122
- lowerKey === 'secret' ||
123
- lowerKey === 'apikey' ||
124
- lowerKey.endsWith('password') ||
125
- lowerKey.endsWith('token') ||
126
- lowerKey.endsWith('secret') ||
127
- lowerKey.endsWith('key') && !lowerKey.endsWith('publickey');
128
-
129
- // Only redact if it's a primitive value (not an object/array)
130
- if (isSensitiveField && typeof value !== 'object') {
131
- sanitized[key] = '[REDACTED]';
132
- continue;
133
- }
134
-
135
- // Recursively sanitize nested objects
136
- if (typeof value === 'object' && value !== null) {
137
- sanitized[key] = sanitizeData(value);
138
- } else {
139
- sanitized[key] = value;
140
- }
141
- }
142
-
143
- return sanitized;
144
- }
145
-
146
- /**
147
- * Create a dry-run HTTP client
148
- *
149
- * @param {Array} operationLog - Array to append logged HTTP requests
150
- * @returns {Object} Mock HTTP client compatible with axios interface
151
- */
152
- function createDryRunHttpClient(operationLog) {
153
- /**
154
- * Mock HTTP request handler
155
- * @param {Object} config - Request configuration
156
- * @returns {Promise<Object>} Mock response
157
- */
158
- const mockRequest = async (config) => {
159
- // Build full URL
160
- let fullUrl = config.url;
161
- if (config.baseURL && !config.url.startsWith('http')) {
162
- fullUrl = `${config.baseURL}${config.url.startsWith('/') ? '' : '/'}${config.url}`;
163
- }
164
-
165
- // Log the request that WOULD have been made
166
- const logEntry = {
167
- operation: 'HTTP_REQUEST',
168
- method: (config.method || 'GET').toUpperCase(),
169
- url: fullUrl,
170
- baseURL: config.baseURL,
171
- path: config.url,
172
- service: detectService(config.baseURL || fullUrl),
173
- headers: sanitizeHeaders(config.headers),
174
- timestamp: new Date().toISOString(),
175
- };
176
-
177
- // Include request data for write operations
178
- if (config.data && ['POST', 'PUT', 'PATCH'].includes(logEntry.method)) {
179
- logEntry.data = sanitizeData(config.data);
180
- }
181
-
182
- // Include query params
183
- if (config.params) {
184
- logEntry.params = sanitizeData(config.params);
185
- }
186
-
187
- operationLog.push(logEntry);
188
-
189
- // Return mock response
190
- return {
191
- status: 200,
192
- statusText: 'OK (Dry-Run)',
193
- data: {
194
- _dryRun: true,
195
- _message: 'This is a dry-run mock response',
196
- _wouldHaveExecuted: `${logEntry.method} ${fullUrl}`,
197
- _service: logEntry.service,
198
- },
199
- headers: {
200
- 'content-type': 'application/json',
201
- 'x-dry-run': 'true',
202
- },
203
- config,
204
- };
205
- };
206
-
207
- // Return axios-compatible interface
208
- return {
209
- request: mockRequest,
210
- get: (url, config = {}) => mockRequest({ ...config, method: 'GET', url }),
211
- post: (url, data, config = {}) => mockRequest({ ...config, method: 'POST', url, data }),
212
- put: (url, data, config = {}) => mockRequest({ ...config, method: 'PUT', url, data }),
213
- patch: (url, data, config = {}) =>
214
- mockRequest({ ...config, method: 'PATCH', url, data }),
215
- delete: (url, config = {}) => mockRequest({ ...config, method: 'DELETE', url }),
216
- head: (url, config = {}) => mockRequest({ ...config, method: 'HEAD', url }),
217
- options: (url, config = {}) => mockRequest({ ...config, method: 'OPTIONS', url }),
218
-
219
- // Axios-specific properties
220
- defaults: {
221
- headers: {
222
- common: {},
223
- get: {},
224
- post: {},
225
- put: {},
226
- patch: {},
227
- delete: {},
228
- },
229
- },
230
-
231
- // Interceptors (no-op in dry-run)
232
- interceptors: {
233
- request: { use: () => {}, eject: () => {} },
234
- response: { use: () => {}, eject: () => {} },
235
- },
236
- };
237
- }
238
-
239
- /**
240
- * Inject dry-run HTTP client into an integration instance
241
- *
242
- * @param {Object} integrationInstance - Integration instance from integrationFactory
243
- * @param {Object} dryRunHttpClient - Dry-run HTTP client
244
- */
245
- function injectDryRunHttpClient(integrationInstance, dryRunHttpClient) {
246
- if (!integrationInstance) {
247
- return;
248
- }
249
-
250
- // Inject into primary API module
251
- if (integrationInstance.primary?.api) {
252
- injectIntoApiModule(integrationInstance.primary.api, dryRunHttpClient);
253
- }
254
-
255
- // Inject into target API module
256
- if (integrationInstance.target?.api) {
257
- injectIntoApiModule(integrationInstance.target.api, dryRunHttpClient);
258
- }
259
- }
260
-
261
- /**
262
- * Inject dry-run HTTP client into an API module
263
- * @param {Object} apiModule - API module instance
264
- * @param {Object} dryRunHttpClient - Dry-run HTTP client
265
- */
266
- function injectIntoApiModule(apiModule, dryRunHttpClient) {
267
- // Common property names for HTTP clients in API modules
268
- const httpClientProps = [
269
- '_httpClient',
270
- 'httpClient',
271
- 'client',
272
- 'axios',
273
- 'request',
274
- 'api',
275
- 'http',
276
- ];
277
-
278
- for (const prop of httpClientProps) {
279
- if (apiModule[prop] && typeof apiModule[prop] === 'object') {
280
- apiModule[prop] = dryRunHttpClient;
281
- }
282
- }
283
-
284
- // Also check if the API module itself has request methods
285
- if (typeof apiModule.request === 'function') {
286
- Object.assign(apiModule, dryRunHttpClient);
287
- }
288
- }
289
-
290
- module.exports = {
291
- createDryRunHttpClient,
292
- injectDryRunHttpClient,
293
- sanitizeHeaders,
294
- sanitizeData,
295
- detectService,
296
- };
@@ -1,261 +0,0 @@
1
- /**
2
- * Dry-Run Repository Wrapper
3
- *
4
- * Wraps any repository to intercept write operations.
5
- * - READ operations pass through unchanged
6
- * - WRITE operations are logged but not executed
7
- *
8
- * Uses Proxy pattern for dynamic method interception
9
- */
10
-
11
- /**
12
- * Create a dry-run wrapper for any repository
13
- *
14
- * @param {Object} repository - The real repository to wrap
15
- * @param {Array} operationLog - Array to append logged operations
16
- * @param {string} modelName - Name of the model (for logging)
17
- * @returns {Proxy} Wrapped repository that logs write operations
18
- */
19
- function createDryRunWrapper(repository, operationLog, modelName) {
20
- return new Proxy(repository, {
21
- get(target, prop) {
22
- const value = target[prop];
23
-
24
- // Return non-function properties as-is
25
- if (typeof value !== 'function') {
26
- return value;
27
- }
28
-
29
- // Identify write operations by name pattern
30
- const writePatterns = /^(create|update|delete|upsert|append|remove|insert|save)/i;
31
- const isWrite = writePatterns.test(prop);
32
-
33
- // Pass through read operations
34
- if (!isWrite) {
35
- return value.bind(target);
36
- }
37
-
38
- // Wrap write operation
39
- return async (...args) => {
40
- // Log the operation that WOULD have been performed
41
- operationLog.push({
42
- operation: prop.toUpperCase(),
43
- model: modelName,
44
- method: prop,
45
- args: sanitizeArgs(args),
46
- timestamp: new Date().toISOString(),
47
- wouldExecute: `${modelName}.${prop}()`,
48
- });
49
-
50
- // For write operations, try to return existing data or mock data
51
- // This helps scripts continue executing without errors
52
-
53
- // For updates, try to return existing data
54
- if (prop.includes('update') || prop.includes('upsert')) {
55
- // Try to extract ID from first argument
56
- const possibleId = args[0];
57
- let existing = null;
58
-
59
- if (possibleId && typeof possibleId === 'string') {
60
- // Try to find existing record
61
- const findMethod = getFindMethod(target, prop);
62
- if (findMethod) {
63
- try {
64
- existing = await findMethod.call(target, possibleId);
65
- } catch (err) {
66
- // Ignore errors, continue to mock
67
- }
68
- }
69
- }
70
-
71
- // Return merged data
72
- if (existing) {
73
- // Merge update data with existing
74
- return { ...existing, ...args[1], _dryRun: true };
75
- }
76
-
77
- // No existing data, return mock
78
- if (args[1]) {
79
- return { id: possibleId, ...args[1], _dryRun: true };
80
- }
81
-
82
- return { id: possibleId, _dryRun: true };
83
- }
84
-
85
- // For creates, return mock object with the data
86
- if (prop.includes('create') || prop.includes('insert')) {
87
- const data = args[0] || {};
88
- return {
89
- id: `dry-run-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
90
- ...data,
91
- _dryRun: true,
92
- createdAt: new Date().toISOString(),
93
- };
94
- }
95
-
96
- // For deletes, return success indication
97
- if (prop.includes('delete') || prop.includes('remove')) {
98
- return { deletedCount: 1, _dryRun: true };
99
- }
100
-
101
- // Default: return mock success
102
- return { success: true, _dryRun: true };
103
- };
104
- },
105
- });
106
- }
107
-
108
- /**
109
- * Try to find a corresponding find method for an update operation
110
- * @param {Object} target - Repository target
111
- * @param {string} updateMethod - Update method name
112
- * @returns {Function|null} Find method or null
113
- */
114
- function getFindMethod(target, updateMethod) {
115
- // Common patterns: updateIntegration -> findIntegrationById
116
- const patterns = [
117
- () => {
118
- const match = updateMethod.match(/update(\w+)/i);
119
- return match ? `find${match[1]}ById` : null;
120
- },
121
- () => {
122
- const match = updateMethod.match(/update(\w+)/i);
123
- return match ? `get${match[1]}ById` : null;
124
- },
125
- () => 'findById',
126
- () => 'getById',
127
- ];
128
-
129
- for (const pattern of patterns) {
130
- const methodName = pattern();
131
- if (methodName && typeof target[methodName] === 'function') {
132
- return target[methodName];
133
- }
134
- }
135
-
136
- return null;
137
- }
138
-
139
- /**
140
- * Sanitize arguments for logging (remove sensitive data)
141
- * @param {Array} args - Function arguments
142
- * @returns {Array} Sanitized arguments
143
- */
144
- function sanitizeArgs(args) {
145
- return args.map((arg) => {
146
- if (arg === null || arg === undefined) {
147
- return arg;
148
- }
149
-
150
- if (typeof arg !== 'object') {
151
- return arg;
152
- }
153
-
154
- if (Array.isArray(arg)) {
155
- return arg.map((item) => sanitizeArgs([item])[0]);
156
- }
157
-
158
- // Sanitize object - remove sensitive fields
159
- const sanitized = {};
160
- for (const [key, value] of Object.entries(arg)) {
161
- const lowerKey = key.toLowerCase();
162
-
163
- // Skip sensitive fields
164
- if (
165
- lowerKey.includes('password') ||
166
- lowerKey.includes('token') ||
167
- lowerKey.includes('secret') ||
168
- lowerKey.includes('key') ||
169
- lowerKey.includes('auth')
170
- ) {
171
- sanitized[key] = '[REDACTED]';
172
- continue;
173
- }
174
-
175
- // Recursively sanitize nested objects
176
- if (typeof value === 'object' && value !== null) {
177
- sanitized[key] = sanitizeArgs([value])[0];
178
- } else {
179
- sanitized[key] = value;
180
- }
181
- }
182
-
183
- return sanitized;
184
- });
185
- }
186
-
187
- /**
188
- * Wrap AdminFriggCommands for dry-run mode
189
- *
190
- * @param {Object} realCommands - Real AdminFriggCommands instance
191
- * @param {Array} operationLog - Array to append logged operations
192
- * @returns {Object} Wrapped commands with dry-run repository wrappers
193
- */
194
- function wrapAdminFriggCommandsForDryRun(realCommands, operationLog) {
195
- return new Proxy(realCommands, {
196
- get(target, prop) {
197
- const value = target[prop];
198
-
199
- // Pass through non-functions
200
- if (typeof value !== 'function') {
201
- // For lazy-loaded repositories, wrap them
202
- if (prop.endsWith('Repository') && value && typeof value === 'object') {
203
- const modelName = prop.replace('Repository', '');
204
- return createDryRunWrapper(
205
- value,
206
- operationLog,
207
- modelName.charAt(0).toUpperCase() + modelName.slice(1)
208
- );
209
- }
210
- return value;
211
- }
212
-
213
- // Identify write operations on the commands themselves
214
- const writePatterns = /^(update|create|delete|append)/i;
215
- const isWrite = writePatterns.test(prop);
216
-
217
- if (!isWrite) {
218
- // Read operations pass through
219
- return value.bind(target);
220
- }
221
-
222
- // Wrap write operations
223
- return async (...args) => {
224
- operationLog.push({
225
- operation: prop.toUpperCase(),
226
- source: 'AdminFriggCommands',
227
- method: prop,
228
- args: sanitizeArgs(args),
229
- timestamp: new Date().toISOString(),
230
- });
231
-
232
- // For specific known methods, try to return sensible mocks
233
- if (prop === 'updateIntegrationConfig') {
234
- const [integrationId] = args;
235
- const existing = await target.findIntegrationById(integrationId);
236
- return existing;
237
- }
238
-
239
- if (prop === 'updateIntegrationStatus') {
240
- const [integrationId] = args;
241
- const existing = await target.findIntegrationById(integrationId);
242
- return existing;
243
- }
244
-
245
- if (prop === 'updateCredential') {
246
- const [credentialId, updates] = args;
247
- return { id: credentialId, ...updates, _dryRun: true };
248
- }
249
-
250
- // Default mock
251
- return { success: true, _dryRun: true };
252
- };
253
- },
254
- });
255
- }
256
-
257
- module.exports = {
258
- createDryRunWrapper,
259
- wrapAdminFriggCommandsForDryRun,
260
- sanitizeArgs,
261
- };