@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.
Files changed (35) hide show
  1. package/LICENSE.md +9 -0
  2. package/index.js +66 -0
  3. package/package.json +53 -0
  4. package/src/adapters/__tests__/aws-scheduler-adapter.test.js +322 -0
  5. package/src/adapters/__tests__/local-scheduler-adapter.test.js +325 -0
  6. package/src/adapters/__tests__/scheduler-adapter-factory.test.js +257 -0
  7. package/src/adapters/__tests__/scheduler-adapter.test.js +103 -0
  8. package/src/adapters/aws-scheduler-adapter.js +138 -0
  9. package/src/adapters/local-scheduler-adapter.js +103 -0
  10. package/src/adapters/scheduler-adapter-factory.js +69 -0
  11. package/src/adapters/scheduler-adapter.js +64 -0
  12. package/src/application/__tests__/admin-frigg-commands.test.js +643 -0
  13. package/src/application/__tests__/admin-script-base.test.js +273 -0
  14. package/src/application/__tests__/dry-run-http-interceptor.test.js +313 -0
  15. package/src/application/__tests__/dry-run-repository-wrapper.test.js +257 -0
  16. package/src/application/__tests__/schedule-management-use-case.test.js +276 -0
  17. package/src/application/__tests__/script-factory.test.js +381 -0
  18. package/src/application/__tests__/script-runner.test.js +202 -0
  19. package/src/application/admin-frigg-commands.js +242 -0
  20. package/src/application/admin-script-base.js +138 -0
  21. package/src/application/dry-run-http-interceptor.js +296 -0
  22. package/src/application/dry-run-repository-wrapper.js +261 -0
  23. package/src/application/schedule-management-use-case.js +230 -0
  24. package/src/application/script-factory.js +161 -0
  25. package/src/application/script-runner.js +254 -0
  26. package/src/builtins/__tests__/integration-health-check.test.js +598 -0
  27. package/src/builtins/__tests__/oauth-token-refresh.test.js +344 -0
  28. package/src/builtins/index.js +28 -0
  29. package/src/builtins/integration-health-check.js +279 -0
  30. package/src/builtins/oauth-token-refresh.js +221 -0
  31. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +148 -0
  32. package/src/infrastructure/__tests__/admin-script-router.test.js +701 -0
  33. package/src/infrastructure/admin-auth-middleware.js +49 -0
  34. package/src/infrastructure/admin-script-router.js +311 -0
  35. 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 };