@friggframework/admin-scripts 2.0.0--canary.517.41839c5.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.
- package/LICENSE.md +9 -0
- package/index.js +66 -0
- package/package.json +53 -0
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +322 -0
- package/src/adapters/__tests__/local-scheduler-adapter.test.js +325 -0
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +257 -0
- package/src/adapters/__tests__/scheduler-adapter.test.js +103 -0
- package/src/adapters/aws-scheduler-adapter.js +138 -0
- package/src/adapters/local-scheduler-adapter.js +103 -0
- package/src/adapters/scheduler-adapter-factory.js +69 -0
- package/src/adapters/scheduler-adapter.js +64 -0
- package/src/application/__tests__/admin-frigg-commands.test.js +643 -0
- package/src/application/__tests__/admin-script-base.test.js +273 -0
- package/src/application/__tests__/dry-run-http-interceptor.test.js +313 -0
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +257 -0
- package/src/application/__tests__/schedule-management-use-case.test.js +276 -0
- package/src/application/__tests__/script-factory.test.js +381 -0
- package/src/application/__tests__/script-runner.test.js +202 -0
- package/src/application/admin-frigg-commands.js +242 -0
- package/src/application/admin-script-base.js +138 -0
- package/src/application/dry-run-http-interceptor.js +296 -0
- package/src/application/dry-run-repository-wrapper.js +261 -0
- package/src/application/schedule-management-use-case.js +230 -0
- package/src/application/script-factory.js +161 -0
- package/src/application/script-runner.js +254 -0
- package/src/builtins/__tests__/integration-health-check.test.js +598 -0
- package/src/builtins/__tests__/oauth-token-refresh.test.js +344 -0
- package/src/builtins/index.js +28 -0
- package/src/builtins/integration-health-check.js +279 -0
- package/src/builtins/oauth-token-refresh.js +221 -0
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +148 -0
- package/src/infrastructure/__tests__/admin-script-router.test.js +701 -0
- package/src/infrastructure/admin-auth-middleware.js +49 -0
- package/src/infrastructure/admin-script-router.js +311 -0
- package/src/infrastructure/script-executor-handler.js +75 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
const { getScriptFactory } = require('./script-factory');
|
|
2
|
+
const { createAdminFriggCommands } = require('./admin-frigg-commands');
|
|
3
|
+
const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
|
|
4
|
+
const { wrapAdminFriggCommandsForDryRun } = require('./dry-run-repository-wrapper');
|
|
5
|
+
const { createDryRunHttpClient, injectDryRunHttpClient } = require('./dry-run-http-interceptor');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Script Runner
|
|
9
|
+
*
|
|
10
|
+
* Orchestrates script execution with:
|
|
11
|
+
* - Execution record creation
|
|
12
|
+
* - Script instantiation
|
|
13
|
+
* - AdminFriggCommands injection
|
|
14
|
+
* - Error handling
|
|
15
|
+
* - Status updates
|
|
16
|
+
*/
|
|
17
|
+
class ScriptRunner {
|
|
18
|
+
constructor(params = {}) {
|
|
19
|
+
this.scriptFactory = params.scriptFactory || getScriptFactory();
|
|
20
|
+
this.commands = params.commands || createAdminScriptCommands();
|
|
21
|
+
this.integrationFactory = params.integrationFactory || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute a script
|
|
26
|
+
* @param {string} scriptName - Name of the script to run
|
|
27
|
+
* @param {Object} params - Script parameters
|
|
28
|
+
* @param {Object} options - Execution options
|
|
29
|
+
* @param {string} options.trigger - 'MANUAL' | 'SCHEDULED' | 'QUEUE'
|
|
30
|
+
* @param {string} options.mode - 'sync' | 'async'
|
|
31
|
+
* @param {Object} options.audit - Audit info { apiKeyName, apiKeyLast4, ipAddress }
|
|
32
|
+
* @param {string} options.executionId - Reuse existing execution ID
|
|
33
|
+
* @param {boolean} options.dryRun - Execute in dry-run mode (no writes, log operations)
|
|
34
|
+
*/
|
|
35
|
+
async execute(scriptName, params = {}, options = {}) {
|
|
36
|
+
const { trigger = 'MANUAL', audit = {}, executionId: existingExecutionId, dryRun = false } = options;
|
|
37
|
+
|
|
38
|
+
// Get script class
|
|
39
|
+
const scriptClass = this.scriptFactory.get(scriptName);
|
|
40
|
+
const definition = scriptClass.Definition;
|
|
41
|
+
|
|
42
|
+
// Validate integrationFactory requirement
|
|
43
|
+
if (definition.config?.requiresIntegrationFactory && !this.integrationFactory) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Script "${scriptName}" requires integrationFactory but none was provided`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let executionId = existingExecutionId;
|
|
50
|
+
|
|
51
|
+
// Create execution record if not provided
|
|
52
|
+
if (!executionId) {
|
|
53
|
+
const execution = await this.commands.createScriptExecution({
|
|
54
|
+
scriptName,
|
|
55
|
+
scriptVersion: definition.version,
|
|
56
|
+
trigger,
|
|
57
|
+
mode: options.mode || 'async',
|
|
58
|
+
input: params,
|
|
59
|
+
audit,
|
|
60
|
+
});
|
|
61
|
+
executionId = execution.id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const startTime = new Date();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Update status to RUNNING (skip in dry-run)
|
|
68
|
+
if (!dryRun) {
|
|
69
|
+
await this.commands.updateScriptExecutionStatus(executionId, 'RUNNING');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create frigg commands for the script
|
|
73
|
+
let frigg;
|
|
74
|
+
let operationLog = [];
|
|
75
|
+
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
// Dry-run mode: wrap commands to intercept writes
|
|
78
|
+
frigg = this.createDryRunFriggCommands(operationLog);
|
|
79
|
+
} else {
|
|
80
|
+
// Normal mode: create real commands
|
|
81
|
+
frigg = createAdminFriggCommands({
|
|
82
|
+
executionId,
|
|
83
|
+
integrationFactory: this.integrationFactory,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Create script instance
|
|
88
|
+
const script = this.scriptFactory.createInstance(scriptName, {
|
|
89
|
+
executionId,
|
|
90
|
+
integrationFactory: this.integrationFactory,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Execute the script
|
|
94
|
+
const output = await script.execute(frigg, params);
|
|
95
|
+
|
|
96
|
+
// Calculate metrics
|
|
97
|
+
const endTime = new Date();
|
|
98
|
+
const durationMs = endTime - startTime;
|
|
99
|
+
|
|
100
|
+
// Complete execution (skip in dry-run)
|
|
101
|
+
if (!dryRun) {
|
|
102
|
+
await this.commands.completeScriptExecution(executionId, {
|
|
103
|
+
status: 'COMPLETED',
|
|
104
|
+
output,
|
|
105
|
+
metrics: {
|
|
106
|
+
startTime: startTime.toISOString(),
|
|
107
|
+
endTime: endTime.toISOString(),
|
|
108
|
+
durationMs,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Return dry-run preview if in dry-run mode
|
|
114
|
+
if (dryRun) {
|
|
115
|
+
return {
|
|
116
|
+
executionId,
|
|
117
|
+
dryRun: true,
|
|
118
|
+
status: 'DRY_RUN_COMPLETED',
|
|
119
|
+
scriptName,
|
|
120
|
+
preview: {
|
|
121
|
+
operations: operationLog,
|
|
122
|
+
summary: this.summarizeOperations(operationLog),
|
|
123
|
+
scriptOutput: output,
|
|
124
|
+
},
|
|
125
|
+
metrics: { durationMs },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
executionId,
|
|
131
|
+
status: 'COMPLETED',
|
|
132
|
+
scriptName,
|
|
133
|
+
output,
|
|
134
|
+
metrics: { durationMs },
|
|
135
|
+
};
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// Calculate metrics even on failure
|
|
138
|
+
const endTime = new Date();
|
|
139
|
+
const durationMs = endTime - startTime;
|
|
140
|
+
|
|
141
|
+
// Record failure (skip in dry-run)
|
|
142
|
+
if (!dryRun) {
|
|
143
|
+
await this.commands.completeScriptExecution(executionId, {
|
|
144
|
+
status: 'FAILED',
|
|
145
|
+
error: {
|
|
146
|
+
name: error.name,
|
|
147
|
+
message: error.message,
|
|
148
|
+
stack: error.stack,
|
|
149
|
+
},
|
|
150
|
+
metrics: {
|
|
151
|
+
startTime: startTime.toISOString(),
|
|
152
|
+
endTime: endTime.toISOString(),
|
|
153
|
+
durationMs,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
executionId,
|
|
160
|
+
dryRun,
|
|
161
|
+
status: dryRun ? 'DRY_RUN_FAILED' : 'FAILED',
|
|
162
|
+
scriptName,
|
|
163
|
+
error: {
|
|
164
|
+
name: error.name,
|
|
165
|
+
message: error.message,
|
|
166
|
+
},
|
|
167
|
+
metrics: { durationMs },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create dry-run version of AdminFriggCommands
|
|
174
|
+
* Intercepts all write operations and logs them
|
|
175
|
+
*
|
|
176
|
+
* @param {Array} operationLog - Array to collect logged operations
|
|
177
|
+
* @returns {Object} Wrapped AdminFriggCommands
|
|
178
|
+
*/
|
|
179
|
+
createDryRunFriggCommands(operationLog) {
|
|
180
|
+
// Create real commands (for read operations)
|
|
181
|
+
const realCommands = createAdminFriggCommands({
|
|
182
|
+
executionId: null, // Don't persist logs in dry-run
|
|
183
|
+
integrationFactory: this.integrationFactory,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Wrap commands to intercept writes
|
|
187
|
+
const wrappedCommands = wrapAdminFriggCommandsForDryRun(realCommands, operationLog);
|
|
188
|
+
|
|
189
|
+
// Create dry-run HTTP client
|
|
190
|
+
const dryRunHttpClient = createDryRunHttpClient(operationLog);
|
|
191
|
+
|
|
192
|
+
// Override instantiate to inject dry-run HTTP client
|
|
193
|
+
const originalInstantiate = wrappedCommands.instantiate.bind(wrappedCommands);
|
|
194
|
+
wrappedCommands.instantiate = async (integrationId) => {
|
|
195
|
+
const instance = await originalInstantiate(integrationId);
|
|
196
|
+
|
|
197
|
+
// Inject dry-run HTTP client into the integration instance
|
|
198
|
+
injectDryRunHttpClient(instance, dryRunHttpClient);
|
|
199
|
+
|
|
200
|
+
return instance;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return wrappedCommands;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Summarize operations from dry-run log
|
|
208
|
+
*
|
|
209
|
+
* @param {Array} log - Operation log
|
|
210
|
+
* @returns {Object} Summary statistics
|
|
211
|
+
*/
|
|
212
|
+
summarizeOperations(log) {
|
|
213
|
+
const summary = {
|
|
214
|
+
totalOperations: log.length,
|
|
215
|
+
databaseWrites: 0,
|
|
216
|
+
httpRequests: 0,
|
|
217
|
+
byOperation: {},
|
|
218
|
+
byModel: {},
|
|
219
|
+
byService: {},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
for (const op of log) {
|
|
223
|
+
// Count by operation type
|
|
224
|
+
const operation = op.operation || op.method || 'UNKNOWN';
|
|
225
|
+
summary.byOperation[operation] = (summary.byOperation[operation] || 0) + 1;
|
|
226
|
+
|
|
227
|
+
// Database operations
|
|
228
|
+
if (op.model) {
|
|
229
|
+
summary.databaseWrites++;
|
|
230
|
+
summary.byModel[op.model] = summary.byModel[op.model] || [];
|
|
231
|
+
summary.byModel[op.model].push({
|
|
232
|
+
operation: op.operation,
|
|
233
|
+
method: op.method,
|
|
234
|
+
timestamp: op.timestamp,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// HTTP requests
|
|
239
|
+
if (op.operation === 'HTTP_REQUEST') {
|
|
240
|
+
summary.httpRequests++;
|
|
241
|
+
const service = op.service || 'unknown';
|
|
242
|
+
summary.byService[service] = (summary.byService[service] || 0) + 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return summary;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createScriptRunner(params = {}) {
|
|
251
|
+
return new ScriptRunner(params);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { ScriptRunner, createScriptRunner };
|