@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,138 @@
|
|
|
1
|
+
const { SchedulerAdapter } = require('./scheduler-adapter');
|
|
2
|
+
|
|
3
|
+
// Lazy-loaded AWS SDK clients (following AWSProviderAdapter pattern)
|
|
4
|
+
let SchedulerClient, CreateScheduleCommand, DeleteScheduleCommand,
|
|
5
|
+
GetScheduleCommand, UpdateScheduleCommand, ListSchedulesCommand;
|
|
6
|
+
|
|
7
|
+
function loadSchedulerSDK() {
|
|
8
|
+
if (!SchedulerClient) {
|
|
9
|
+
const schedulerModule = require('@aws-sdk/client-scheduler');
|
|
10
|
+
SchedulerClient = schedulerModule.SchedulerClient;
|
|
11
|
+
CreateScheduleCommand = schedulerModule.CreateScheduleCommand;
|
|
12
|
+
DeleteScheduleCommand = schedulerModule.DeleteScheduleCommand;
|
|
13
|
+
GetScheduleCommand = schedulerModule.GetScheduleCommand;
|
|
14
|
+
UpdateScheduleCommand = schedulerModule.UpdateScheduleCommand;
|
|
15
|
+
ListSchedulesCommand = schedulerModule.ListSchedulesCommand;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* AWS EventBridge Scheduler Adapter
|
|
21
|
+
*
|
|
22
|
+
* Infrastructure Adapter - Hexagonal Architecture
|
|
23
|
+
*
|
|
24
|
+
* Implements scheduling using AWS EventBridge Scheduler.
|
|
25
|
+
* Supports cron expressions, timezone configuration, and Lambda invocation.
|
|
26
|
+
*/
|
|
27
|
+
class AWSSchedulerAdapter extends SchedulerAdapter {
|
|
28
|
+
constructor({ region, credentials, targetLambdaArn, scheduleGroupName } = {}) {
|
|
29
|
+
super();
|
|
30
|
+
this.region = region || process.env.AWS_REGION || 'us-east-1';
|
|
31
|
+
this.credentials = credentials;
|
|
32
|
+
this.targetLambdaArn = targetLambdaArn || process.env.ADMIN_SCRIPT_LAMBDA_ARN;
|
|
33
|
+
this.scheduleGroupName = scheduleGroupName || process.env.SCHEDULE_GROUP_NAME || 'frigg-admin-scripts';
|
|
34
|
+
this.scheduler = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getSchedulerClient() {
|
|
38
|
+
if (!this.scheduler) {
|
|
39
|
+
loadSchedulerSDK();
|
|
40
|
+
this.scheduler = new SchedulerClient({
|
|
41
|
+
region: this.region,
|
|
42
|
+
credentials: this.credentials,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this.scheduler;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getName() {
|
|
49
|
+
return 'aws-eventbridge-scheduler';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async createSchedule({ scriptName, cronExpression, timezone, input }) {
|
|
53
|
+
const client = this.getSchedulerClient();
|
|
54
|
+
const scheduleName = `frigg-script-${scriptName}`;
|
|
55
|
+
|
|
56
|
+
const command = new CreateScheduleCommand({
|
|
57
|
+
Name: scheduleName,
|
|
58
|
+
GroupName: this.scheduleGroupName,
|
|
59
|
+
ScheduleExpression: cronExpression,
|
|
60
|
+
ScheduleExpressionTimezone: timezone || 'UTC',
|
|
61
|
+
FlexibleTimeWindow: { Mode: 'OFF' },
|
|
62
|
+
Target: {
|
|
63
|
+
Arn: this.targetLambdaArn,
|
|
64
|
+
RoleArn: process.env.SCHEDULER_ROLE_ARN,
|
|
65
|
+
Input: JSON.stringify({
|
|
66
|
+
scriptName,
|
|
67
|
+
trigger: 'SCHEDULED',
|
|
68
|
+
params: input || {},
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
State: 'ENABLED',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const response = await client.send(command);
|
|
75
|
+
return {
|
|
76
|
+
scheduleArn: response.ScheduleArn,
|
|
77
|
+
scheduleName: scheduleName,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async deleteSchedule(scriptName) {
|
|
82
|
+
const client = this.getSchedulerClient();
|
|
83
|
+
const scheduleName = `frigg-script-${scriptName}`;
|
|
84
|
+
|
|
85
|
+
await client.send(new DeleteScheduleCommand({
|
|
86
|
+
Name: scheduleName,
|
|
87
|
+
GroupName: this.scheduleGroupName,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async setScheduleEnabled(scriptName, enabled) {
|
|
92
|
+
const client = this.getSchedulerClient();
|
|
93
|
+
const scheduleName = `frigg-script-${scriptName}`;
|
|
94
|
+
|
|
95
|
+
// Get the current schedule first to preserve all settings
|
|
96
|
+
const getCommand = new GetScheduleCommand({
|
|
97
|
+
Name: scheduleName,
|
|
98
|
+
GroupName: this.scheduleGroupName,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const currentSchedule = await client.send(getCommand);
|
|
102
|
+
|
|
103
|
+
// Update with the new state
|
|
104
|
+
await client.send(new UpdateScheduleCommand({
|
|
105
|
+
Name: scheduleName,
|
|
106
|
+
GroupName: this.scheduleGroupName,
|
|
107
|
+
ScheduleExpression: currentSchedule.ScheduleExpression,
|
|
108
|
+
ScheduleExpressionTimezone: currentSchedule.ScheduleExpressionTimezone,
|
|
109
|
+
FlexibleTimeWindow: currentSchedule.FlexibleTimeWindow,
|
|
110
|
+
Target: currentSchedule.Target,
|
|
111
|
+
State: enabled ? 'ENABLED' : 'DISABLED',
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async listSchedules() {
|
|
116
|
+
const client = this.getSchedulerClient();
|
|
117
|
+
|
|
118
|
+
const response = await client.send(new ListSchedulesCommand({
|
|
119
|
+
GroupName: this.scheduleGroupName,
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
return response.Schedules || [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getSchedule(scriptName) {
|
|
126
|
+
const client = this.getSchedulerClient();
|
|
127
|
+
const scheduleName = `frigg-script-${scriptName}`;
|
|
128
|
+
|
|
129
|
+
const response = await client.send(new GetScheduleCommand({
|
|
130
|
+
Name: scheduleName,
|
|
131
|
+
GroupName: this.scheduleGroupName,
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
return response;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { AWSSchedulerAdapter };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const { SchedulerAdapter } = require('./scheduler-adapter');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local Scheduler Adapter
|
|
5
|
+
*
|
|
6
|
+
* Infrastructure Adapter - Hexagonal Architecture
|
|
7
|
+
*
|
|
8
|
+
* In-memory implementation for local development and testing.
|
|
9
|
+
* Stores schedule configurations but does not execute them.
|
|
10
|
+
* For actual cron execution, use a library like node-cron.
|
|
11
|
+
*/
|
|
12
|
+
class LocalSchedulerAdapter extends SchedulerAdapter {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.schedules = new Map();
|
|
16
|
+
this.intervals = new Map();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getName() {
|
|
20
|
+
return 'local-cron';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async createSchedule({ scriptName, cronExpression, timezone, input }) {
|
|
24
|
+
// Store schedule (actual cron execution would use node-cron)
|
|
25
|
+
this.schedules.set(scriptName, {
|
|
26
|
+
scriptName,
|
|
27
|
+
cronExpression,
|
|
28
|
+
timezone: timezone || 'UTC',
|
|
29
|
+
input,
|
|
30
|
+
enabled: true,
|
|
31
|
+
createdAt: new Date().toISOString(),
|
|
32
|
+
updatedAt: new Date().toISOString(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
scheduleName: scriptName,
|
|
37
|
+
scheduleArn: `local:schedule:${scriptName}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async deleteSchedule(scriptName) {
|
|
42
|
+
this.schedules.delete(scriptName);
|
|
43
|
+
if (this.intervals.has(scriptName)) {
|
|
44
|
+
clearInterval(this.intervals.get(scriptName));
|
|
45
|
+
this.intervals.delete(scriptName);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async setScheduleEnabled(scriptName, enabled) {
|
|
50
|
+
const schedule = this.schedules.get(scriptName);
|
|
51
|
+
if (!schedule) {
|
|
52
|
+
throw new Error(`Schedule for script "${scriptName}" not found`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
schedule.enabled = enabled;
|
|
56
|
+
schedule.updatedAt = new Date().toISOString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async listSchedules() {
|
|
60
|
+
return Array.from(this.schedules.values());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async getSchedule(scriptName) {
|
|
64
|
+
const schedule = this.schedules.get(scriptName);
|
|
65
|
+
if (!schedule) {
|
|
66
|
+
throw new Error(`Schedule for script "${scriptName}" not found`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
Name: scriptName,
|
|
71
|
+
State: schedule.enabled ? 'ENABLED' : 'DISABLED',
|
|
72
|
+
ScheduleExpression: schedule.cronExpression,
|
|
73
|
+
ScheduleExpressionTimezone: schedule.timezone,
|
|
74
|
+
Target: {
|
|
75
|
+
Input: JSON.stringify({
|
|
76
|
+
scriptName,
|
|
77
|
+
trigger: 'SCHEDULED',
|
|
78
|
+
params: schedule.input || {},
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
CreationDate: new Date(schedule.createdAt),
|
|
82
|
+
LastModificationDate: new Date(schedule.updatedAt),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear all schedules (useful for testing)
|
|
88
|
+
*/
|
|
89
|
+
clear() {
|
|
90
|
+
this.schedules.clear();
|
|
91
|
+
this.intervals.forEach((interval) => clearInterval(interval));
|
|
92
|
+
this.intervals.clear();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get number of schedules (useful for testing)
|
|
97
|
+
*/
|
|
98
|
+
get size() {
|
|
99
|
+
return this.schedules.size;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { LocalSchedulerAdapter };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { AWSSchedulerAdapter } = require('./aws-scheduler-adapter');
|
|
2
|
+
const { LocalSchedulerAdapter } = require('./local-scheduler-adapter');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scheduler Adapter Factory
|
|
6
|
+
*
|
|
7
|
+
* Application Layer - Hexagonal Architecture
|
|
8
|
+
*
|
|
9
|
+
* Creates the appropriate scheduler adapter based on configuration.
|
|
10
|
+
* Supports environment-based auto-detection and explicit configuration.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a scheduler adapter instance
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} options - Configuration options
|
|
17
|
+
* @param {string} [options.type] - Adapter type ('aws', 'eventbridge', 'local')
|
|
18
|
+
* @param {string} [options.region] - AWS region (for AWS adapter)
|
|
19
|
+
* @param {Object} [options.credentials] - AWS credentials (for AWS adapter)
|
|
20
|
+
* @param {string} [options.targetLambdaArn] - Lambda ARN to invoke (for AWS adapter)
|
|
21
|
+
* @param {string} [options.scheduleGroupName] - EventBridge schedule group name (for AWS adapter)
|
|
22
|
+
* @returns {SchedulerAdapter} Configured scheduler adapter
|
|
23
|
+
*/
|
|
24
|
+
function createSchedulerAdapter(options = {}) {
|
|
25
|
+
const adapterType = options.type || detectSchedulerAdapterType();
|
|
26
|
+
|
|
27
|
+
switch (adapterType.toLowerCase()) {
|
|
28
|
+
case 'aws':
|
|
29
|
+
case 'eventbridge':
|
|
30
|
+
return new AWSSchedulerAdapter({
|
|
31
|
+
region: options.region,
|
|
32
|
+
credentials: options.credentials,
|
|
33
|
+
targetLambdaArn: options.targetLambdaArn,
|
|
34
|
+
scheduleGroupName: options.scheduleGroupName,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
case 'local':
|
|
38
|
+
default:
|
|
39
|
+
return new LocalSchedulerAdapter();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Determine the appropriate scheduler adapter type based on environment
|
|
45
|
+
*
|
|
46
|
+
* @returns {string} Adapter type ('aws' or 'local')
|
|
47
|
+
*/
|
|
48
|
+
function detectSchedulerAdapterType() {
|
|
49
|
+
// If explicitly set, use that
|
|
50
|
+
if (process.env.SCHEDULER_ADAPTER) {
|
|
51
|
+
return process.env.SCHEDULER_ADAPTER;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Auto-detect based on environment
|
|
55
|
+
const stage = process.env.STAGE || process.env.NODE_ENV || 'local';
|
|
56
|
+
|
|
57
|
+
// Use AWS adapter in production/staging environments
|
|
58
|
+
if (['production', 'prod', 'staging', 'stage'].includes(stage.toLowerCase())) {
|
|
59
|
+
return 'aws';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Use local adapter for dev/test/local
|
|
63
|
+
return 'local';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
createSchedulerAdapter,
|
|
68
|
+
detectSchedulerAdapterType,
|
|
69
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler Adapter (Abstract Base Class)
|
|
3
|
+
*
|
|
4
|
+
* Port - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Defines the contract for scheduler implementations.
|
|
7
|
+
* Supports AWS EventBridge, local cron, or other providers.
|
|
8
|
+
*/
|
|
9
|
+
class SchedulerAdapter {
|
|
10
|
+
getName() {
|
|
11
|
+
throw new Error('SchedulerAdapter.getName() must be implemented');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create or update a schedule for a script
|
|
16
|
+
* @param {Object} config
|
|
17
|
+
* @param {string} config.scriptName - Script identifier
|
|
18
|
+
* @param {string} config.cronExpression - Cron expression
|
|
19
|
+
* @param {string} [config.timezone] - Timezone (default UTC)
|
|
20
|
+
* @param {Object} [config.input] - Optional input params
|
|
21
|
+
* @returns {Promise<Object>} Created schedule { scheduleArn, scheduleName }
|
|
22
|
+
*/
|
|
23
|
+
async createSchedule(config) {
|
|
24
|
+
throw new Error('SchedulerAdapter.createSchedule() must be implemented');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Delete a schedule
|
|
29
|
+
* @param {string} scriptName - Script identifier
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
async deleteSchedule(scriptName) {
|
|
33
|
+
throw new Error('SchedulerAdapter.deleteSchedule() must be implemented');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Enable or disable a schedule
|
|
38
|
+
* @param {string} scriptName - Script identifier
|
|
39
|
+
* @param {boolean} enabled - Whether to enable
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
async setScheduleEnabled(scriptName, enabled) {
|
|
43
|
+
throw new Error('SchedulerAdapter.setScheduleEnabled() must be implemented');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* List all schedules
|
|
48
|
+
* @returns {Promise<Array>} List of schedules
|
|
49
|
+
*/
|
|
50
|
+
async listSchedules() {
|
|
51
|
+
throw new Error('SchedulerAdapter.listSchedules() must be implemented');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get a specific schedule
|
|
56
|
+
* @param {string} scriptName - Script identifier
|
|
57
|
+
* @returns {Promise<Object>} Schedule details
|
|
58
|
+
*/
|
|
59
|
+
async getSchedule(scriptName) {
|
|
60
|
+
throw new Error('SchedulerAdapter.getSchedule() must be implemented');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { SchedulerAdapter };
|