@friggframework/admin-scripts 2.0.0--canary.517.35ee143.0 → 2.0.0--canary.517.f738cdd.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/PR_517_REVIEW_TRACKER.md +56 -0
- package/index.js +10 -1
- package/package.json +6 -6
- package/src/application/__tests__/admin-script-base.test.js +98 -82
- package/src/application/admin-frigg-commands.js +12 -24
- package/src/application/admin-script-base.js +20 -97
- package/src/application/script-runner.js +7 -7
- package/src/builtins/__tests__/integration-health-check.test.js +66 -59
- package/src/builtins/__tests__/oauth-token-refresh.test.js +44 -36
- package/src/builtins/integration-health-check.js +22 -23
- package/src/builtins/oauth-token-refresh.js +18 -19
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# PR #517 Comment Tracker
|
|
2
|
+
|
|
3
|
+
## ✅ ADDRESSED - Ready to Reply
|
|
4
|
+
|
|
5
|
+
| # | File:Line | Original Comment | What We Did | Reply |
|
|
6
|
+
|---|-----------|------------------|-------------|-------|
|
|
7
|
+
| 1 | `admin-frigg-commands.js:15` | "I don't think this should even exist, we're just duplicating methods" | Renamed to `AdminScriptContext`, clarified as facade pattern | Renamed to `AdminScriptContext`. It's a facade - wraps repositories so scripts have one API instead of multiple imports. Open to discussing alternatives. |
|
|
8
|
+
| 2 | `admin-script-base.js:94` | "Commands should come via constructor" | Changed to constructor injection | Done. Context now passed via constructor, scripts access via `this.context`. |
|
|
9
|
+
| 3 | `admin-script-base.js:109` | "logging does not belong here" | Removed logging from base class | Removed. Scripts use `this.context.log()` which persists to admin process record. |
|
|
10
|
+
| 4 | `admin-script-base.js:54` | "I would rename to requireIntegrationInstance" | Already renamed | Done - already renamed in current code. |
|
|
11
|
+
| 5 | `admin-script-base.js:56` | "This is just a duplication of the static Definition" | Cleaned up display object | Cleaned up. `display` now only holds UI overrides (category, icon). Label/description fall back to top-level via static methods. |
|
|
12
|
+
| 6 | `schedule-management-use-case.js:1` | "A use case should have single entry point" | Already split in previous session | Already split into `UpsertScheduleUseCase`, `DeleteScheduleUseCase`, `GetEffectiveScheduleUseCase`. |
|
|
13
|
+
| 7 | `package.json:20` | "chai and sinon should slowly be pushed away" | Sinon removed in previous session | Sinon already removed. Will remove chai too. |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📝 NEEDS RESPONSE ONLY - No Code Change Required
|
|
18
|
+
|
|
19
|
+
| # | File:Line | Original Comment | Reply |
|
|
20
|
+
|---|-----------|------------------|-------|
|
|
21
|
+
| 8 | `admin-frigg-commands.js:160` | "this does not belong here" (queueScript) | queueScript enables self-queuing pattern (fan-out, pagination, retries). It's here so scripts don't need queue internals. Could move to separate utility if preferred. |
|
|
22
|
+
| 9 | `admin-script-base.js:39` | "why do we need the source?" | Distinguishes builtin vs user-defined scripts. UI can filter differently, builtins could have special handling. Could remove if not needed. |
|
|
23
|
+
| 10 | `admin-script-base.js:42` | "what's the idea with these schemas?" | Optional JSON Schema for validation/documentation. Could wire to OpenAPI or dynamic UI forms. Not critical for v1 - could remove and add later. |
|
|
24
|
+
| 11 | `admin-script-base.js:46` | "enabled property confusion" | Agreed the matrix is confusing. Intent: `schedule.enabled` controls auto-trigger independent of registration. Could simplify to just use presence in appDefinition. |
|
|
25
|
+
| 12 | `admin-script-base.js:52` | "Do we have retry logic in place already?" | Not yet - placeholder for Phase 2. Could remove until we build it. |
|
|
26
|
+
| 13 | `admin-script-base.js:81` | "What is the executionId?" | ID of AdminProcess record tracking this execution. Used to persist logs and update status. Created before script runs, passed to constructor. |
|
|
27
|
+
| 14 | `schedule-management-use-case.js:89` | "why save to database if EventBridge is source of truth?" | Database stores user's config override. EventBridge is execution engine. On deploy, we sync DB to EventBridge. Tracks user config vs code default. |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🔧 OUTSTANDING - Needs Code Changes
|
|
32
|
+
|
|
33
|
+
| # | File:Line | Original Comment | Task |
|
|
34
|
+
|---|-----------|------------------|------|
|
|
35
|
+
| 15 | `docs/architecture-decisions/005-admin-script-runner.md:71` | "What is 'frigg' in this parameter?" | Update ADR - rename `frigg` to `context` throughout |
|
|
36
|
+
| 16 | `package.json:22` | "We already use nock for http request mocking" | Remove msw if present, use nock consistently |
|
|
37
|
+
| 17 | `package.json:12` | "why mongoose?" | Check if mongoose needed or can be removed |
|
|
38
|
+
| 18 | `schedule-management-use-case.js:109` | "leaking AWS specifics" | Abstract behind SchedulerAdapter, remove EventBridge references from use case |
|
|
39
|
+
| 19 | `schedule-management-use-case.js:138` | "should not mention EventBridge here" | Same as above |
|
|
40
|
+
| 20 | `adapters/aws-scheduler-adapter.js:30` | "should not infer/guess/default any variable" | Remove defaults, require explicit config |
|
|
41
|
+
| 21 | `adapters/scheduler-adapter-factory.js:48` | "env var confusion, prefer appDefinition" | Move scheduler config to appDefinition |
|
|
42
|
+
| 22 | `script-runner.js:36` | "we should not assume default values" | Remove defaults, require explicit values |
|
|
43
|
+
| 23 | `dry-run-http-interceptor.js:1` | "I don't understand why this is needed" | Explain or remove - was for intercepting HTTP in dry-run mode |
|
|
44
|
+
| 24 | `dry-run-repository-wrapper.js:1` | "This is smelly" | Review/remove - was for wrapping repos in dry-run mode |
|
|
45
|
+
| 25 | `.github/workflows/release.yml:11` | "why do we need those?" | Check release workflow changes |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ❓ NEEDS DISCUSSION - Architectural Decisions
|
|
50
|
+
|
|
51
|
+
| # | File:Line | Original Comment | Decision Needed |
|
|
52
|
+
|---|-----------|------------------|-----------------|
|
|
53
|
+
| 26 | `admin-script-base.js:39` | source field | Keep BUILTIN/USER_DEFINED or remove? |
|
|
54
|
+
| 27 | `admin-script-base.js:42` | inputSchema/outputSchema | Keep for future or remove for now? |
|
|
55
|
+
| 28 | `admin-script-base.js:46` | schedule.enabled | Simplify to just appDefinition presence? |
|
|
56
|
+
| 29 | `admin-script-base.js:52` | maxRetries | Remove placeholder or keep? |
|
package/index.js
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
// Application Services
|
|
9
9
|
const { ScriptFactory, getScriptFactory, createScriptFactory } = require('./src/application/script-factory');
|
|
10
10
|
const { AdminScriptBase } = require('./src/application/admin-script-base');
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
AdminScriptContext,
|
|
13
|
+
createAdminScriptContext,
|
|
14
|
+
// Legacy aliases (deprecated)
|
|
15
|
+
AdminFriggCommands,
|
|
16
|
+
createAdminFriggCommands,
|
|
17
|
+
} = require('./src/application/admin-frigg-commands');
|
|
12
18
|
const { ScriptRunner, createScriptRunner } = require('./src/application/script-runner');
|
|
13
19
|
|
|
14
20
|
// Infrastructure
|
|
@@ -39,6 +45,9 @@ module.exports = {
|
|
|
39
45
|
ScriptFactory,
|
|
40
46
|
getScriptFactory,
|
|
41
47
|
createScriptFactory,
|
|
48
|
+
AdminScriptContext,
|
|
49
|
+
createAdminScriptContext,
|
|
50
|
+
// Legacy aliases (deprecated)
|
|
42
51
|
AdminFriggCommands,
|
|
43
52
|
createAdminFriggCommands,
|
|
44
53
|
ScriptRunner,
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/admin-scripts",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.517.
|
|
4
|
+
"version": "2.0.0--canary.517.f738cdd.0",
|
|
5
5
|
"description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@aws-sdk/client-scheduler": "^3.588.0",
|
|
8
|
-
"@friggframework/core": "2.0.0--canary.517.
|
|
8
|
+
"@friggframework/core": "2.0.0--canary.517.f738cdd.0",
|
|
9
9
|
"bcryptjs": "^2.4.3",
|
|
10
10
|
"express": "^4.18.2",
|
|
11
11
|
"lodash": "4.17.21",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"uuid": "^9.0.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@friggframework/eslint-config": "2.0.0--canary.517.
|
|
17
|
-
"@friggframework/prettier-config": "2.0.0--canary.517.
|
|
18
|
-
"@friggframework/test": "2.0.0--canary.517.
|
|
16
|
+
"@friggframework/eslint-config": "2.0.0--canary.517.f738cdd.0",
|
|
17
|
+
"@friggframework/prettier-config": "2.0.0--canary.517.f738cdd.0",
|
|
18
|
+
"@friggframework/test": "2.0.0--canary.517.f738cdd.0",
|
|
19
19
|
"eslint": "^8.22.0",
|
|
20
20
|
"jest": "^29.7.0",
|
|
21
21
|
"prettier": "^2.7.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"maintenance",
|
|
47
47
|
"operations"
|
|
48
48
|
],
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "f738cdd2e74529b3719a974c44e39e08cbf24313"
|
|
50
50
|
}
|
|
@@ -31,9 +31,8 @@ describe('AdminScriptBase', () => {
|
|
|
31
31
|
requireIntegrationInstance: true,
|
|
32
32
|
},
|
|
33
33
|
display: {
|
|
34
|
-
label: 'Test Script',
|
|
35
|
-
description: 'For testing',
|
|
36
34
|
category: 'testing',
|
|
35
|
+
icon: 'test-icon',
|
|
37
36
|
},
|
|
38
37
|
};
|
|
39
38
|
}
|
|
@@ -45,6 +44,15 @@ describe('AdminScriptBase', () => {
|
|
|
45
44
|
expect(TestScript.Definition.schedule.enabled).toBe(true);
|
|
46
45
|
expect(TestScript.Definition.config.timeout).toBe(600000);
|
|
47
46
|
});
|
|
47
|
+
|
|
48
|
+
it('should have clean display object without redundant fields', () => {
|
|
49
|
+
// Default display should only have UI-specific fields
|
|
50
|
+
expect(AdminScriptBase.Definition.display).toBeDefined();
|
|
51
|
+
expect(AdminScriptBase.Definition.display.category).toBe('maintenance');
|
|
52
|
+
// Should NOT have redundant label/description
|
|
53
|
+
expect(AdminScriptBase.Definition.display.label).toBeUndefined();
|
|
54
|
+
expect(AdminScriptBase.Definition.display.description).toBeUndefined();
|
|
55
|
+
});
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
describe('Static methods', () => {
|
|
@@ -90,18 +98,68 @@ describe('AdminScriptBase', () => {
|
|
|
90
98
|
source: 'USER_DEFINED',
|
|
91
99
|
});
|
|
92
100
|
});
|
|
101
|
+
|
|
102
|
+
it('getDisplayLabel() should return display.label or fall back to name', () => {
|
|
103
|
+
class ScriptWithLabel extends AdminScriptBase {
|
|
104
|
+
static Definition = {
|
|
105
|
+
name: 'my-script',
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
description: 'test',
|
|
108
|
+
display: { label: 'My Custom Label' },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class ScriptWithoutLabel extends AdminScriptBase {
|
|
113
|
+
static Definition = {
|
|
114
|
+
name: 'another-script',
|
|
115
|
+
version: '1.0.0',
|
|
116
|
+
description: 'test',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
expect(ScriptWithLabel.getDisplayLabel()).toBe('My Custom Label');
|
|
121
|
+
expect(ScriptWithoutLabel.getDisplayLabel()).toBe('another-script');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('getDisplayDescription() should return display.description or fall back to description', () => {
|
|
125
|
+
class ScriptWithDisplayDesc extends AdminScriptBase {
|
|
126
|
+
static Definition = {
|
|
127
|
+
name: 'my-script',
|
|
128
|
+
version: '1.0.0',
|
|
129
|
+
description: 'Technical description',
|
|
130
|
+
display: { description: 'User-friendly description' },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class ScriptWithoutDisplayDesc extends AdminScriptBase {
|
|
135
|
+
static Definition = {
|
|
136
|
+
name: 'another-script',
|
|
137
|
+
version: '1.0.0',
|
|
138
|
+
description: 'Technical description',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
expect(ScriptWithDisplayDesc.getDisplayDescription()).toBe('User-friendly description');
|
|
143
|
+
expect(ScriptWithoutDisplayDesc.getDisplayDescription()).toBe('Technical description');
|
|
144
|
+
});
|
|
93
145
|
});
|
|
94
146
|
|
|
95
147
|
describe('Constructor', () => {
|
|
96
148
|
it('should initialize with default values', () => {
|
|
97
149
|
const script = new AdminScriptBase();
|
|
98
150
|
|
|
151
|
+
expect(script.context).toBeNull();
|
|
99
152
|
expect(script.executionId).toBeNull();
|
|
100
|
-
expect(script.logs).toEqual([]);
|
|
101
|
-
expect(script._startTime).toBeNull();
|
|
102
153
|
expect(script.integrationFactory).toBeNull();
|
|
103
154
|
});
|
|
104
155
|
|
|
156
|
+
it('should accept context parameter', () => {
|
|
157
|
+
const mockContext = { log: jest.fn() };
|
|
158
|
+
const script = new AdminScriptBase({ context: mockContext });
|
|
159
|
+
|
|
160
|
+
expect(script.context).toBe(mockContext);
|
|
161
|
+
});
|
|
162
|
+
|
|
105
163
|
it('should accept executionId parameter', () => {
|
|
106
164
|
const script = new AdminScriptBase({ executionId: 'exec_123' });
|
|
107
165
|
|
|
@@ -117,13 +175,16 @@ describe('AdminScriptBase', () => {
|
|
|
117
175
|
expect(script.integrationFactory).toBe(mockFactory);
|
|
118
176
|
});
|
|
119
177
|
|
|
120
|
-
it('should accept
|
|
178
|
+
it('should accept all parameters together', () => {
|
|
179
|
+
const mockContext = { log: jest.fn() };
|
|
121
180
|
const mockFactory = { mock: true };
|
|
122
181
|
const script = new AdminScriptBase({
|
|
182
|
+
context: mockContext,
|
|
123
183
|
executionId: 'exec_456',
|
|
124
184
|
integrationFactory: mockFactory,
|
|
125
185
|
});
|
|
126
186
|
|
|
187
|
+
expect(script.context).toBe(mockContext);
|
|
127
188
|
expect(script.executionId).toBe('exec_456');
|
|
128
189
|
expect(script.integrationFactory).toBe(mockFactory);
|
|
129
190
|
});
|
|
@@ -133,12 +194,12 @@ describe('AdminScriptBase', () => {
|
|
|
133
194
|
it('should throw error when not implemented by subclass', async () => {
|
|
134
195
|
const script = new AdminScriptBase();
|
|
135
196
|
|
|
136
|
-
await expect(script.execute({}
|
|
197
|
+
await expect(script.execute({})).rejects.toThrow(
|
|
137
198
|
'AdminScriptBase.execute() must be implemented by subclass'
|
|
138
199
|
);
|
|
139
200
|
});
|
|
140
201
|
|
|
141
|
-
it('should allow child classes to implement execute()', async () => {
|
|
202
|
+
it('should allow child classes to implement execute() with params only', async () => {
|
|
142
203
|
class TestScript extends AdminScriptBase {
|
|
143
204
|
static Definition = {
|
|
144
205
|
name: 'test',
|
|
@@ -146,90 +207,45 @@ describe('AdminScriptBase', () => {
|
|
|
146
207
|
description: 'test',
|
|
147
208
|
};
|
|
148
209
|
|
|
149
|
-
async execute(
|
|
210
|
+
async execute(params) {
|
|
150
211
|
return { result: 'success', params };
|
|
151
212
|
}
|
|
152
213
|
}
|
|
153
214
|
|
|
154
215
|
const script = new TestScript();
|
|
155
|
-
const frigg = {};
|
|
156
216
|
const params = { foo: 'bar' };
|
|
157
217
|
|
|
158
|
-
const result = await script.execute(
|
|
218
|
+
const result = await script.execute(params);
|
|
159
219
|
|
|
160
220
|
expect(result.result).toBe('success');
|
|
161
221
|
expect(result.params).toEqual({ foo: 'bar' });
|
|
162
222
|
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('Logging methods', () => {
|
|
166
|
-
it('log() should create log entry with timestamp', () => {
|
|
167
|
-
const script = new AdminScriptBase();
|
|
168
|
-
const beforeTime = new Date().toISOString();
|
|
169
|
-
|
|
170
|
-
const entry = script.log('info', 'Test message', { key: 'value' });
|
|
171
|
-
|
|
172
|
-
const afterTime = new Date().toISOString();
|
|
173
|
-
|
|
174
|
-
expect(entry.level).toBe('info');
|
|
175
|
-
expect(entry.message).toBe('Test message');
|
|
176
|
-
expect(entry.data).toEqual({ key: 'value' });
|
|
177
|
-
expect(entry.timestamp).toBeDefined();
|
|
178
|
-
expect(entry.timestamp >= beforeTime).toBe(true);
|
|
179
|
-
expect(entry.timestamp <= afterTime).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('log() should add entry to logs array', () => {
|
|
183
|
-
const script = new AdminScriptBase();
|
|
184
|
-
|
|
185
|
-
script.log('info', 'First');
|
|
186
|
-
script.log('error', 'Second');
|
|
187
|
-
script.log('warn', 'Third');
|
|
188
|
-
|
|
189
|
-
const logs = script.getLogs();
|
|
190
|
-
|
|
191
|
-
expect(logs).toHaveLength(3);
|
|
192
|
-
expect(logs[0].message).toBe('First');
|
|
193
|
-
expect(logs[1].message).toBe('Second');
|
|
194
|
-
expect(logs[2].message).toBe('Third');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('log() should default data to empty object', () => {
|
|
198
|
-
const script = new AdminScriptBase();
|
|
199
|
-
|
|
200
|
-
const entry = script.log('info', 'No data');
|
|
201
223
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
script.log('error', 'Message 2');
|
|
210
|
-
|
|
211
|
-
const logs = script.getLogs();
|
|
212
|
-
|
|
213
|
-
expect(logs).toHaveLength(2);
|
|
214
|
-
expect(logs[0].level).toBe('info');
|
|
215
|
-
expect(logs[1].level).toBe('error');
|
|
216
|
-
});
|
|
224
|
+
it('should access context via this.context', async () => {
|
|
225
|
+
class TestScript extends AdminScriptBase {
|
|
226
|
+
static Definition = {
|
|
227
|
+
name: 'test',
|
|
228
|
+
version: '1.0.0',
|
|
229
|
+
description: 'test',
|
|
230
|
+
};
|
|
217
231
|
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
async execute(params) {
|
|
233
|
+
this.context.log('info', 'Starting');
|
|
234
|
+
return { success: true };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
220
237
|
|
|
221
|
-
|
|
222
|
-
script
|
|
223
|
-
expect(script.getLogs()).toHaveLength(2);
|
|
238
|
+
const mockContext = { log: jest.fn() };
|
|
239
|
+
const script = new TestScript({ context: mockContext });
|
|
224
240
|
|
|
225
|
-
script.
|
|
241
|
+
await script.execute({});
|
|
226
242
|
|
|
227
|
-
expect(
|
|
243
|
+
expect(mockContext.log).toHaveBeenCalledWith('info', 'Starting');
|
|
228
244
|
});
|
|
229
245
|
});
|
|
230
246
|
|
|
231
247
|
describe('Integration with child classes', () => {
|
|
232
|
-
it('should support full lifecycle', async () => {
|
|
248
|
+
it('should support full lifecycle with context injection', async () => {
|
|
233
249
|
class MyScript extends AdminScriptBase {
|
|
234
250
|
static Definition = {
|
|
235
251
|
name: 'my-script',
|
|
@@ -240,34 +256,34 @@ describe('AdminScriptBase', () => {
|
|
|
240
256
|
},
|
|
241
257
|
};
|
|
242
258
|
|
|
243
|
-
async execute(
|
|
244
|
-
this.log('info', 'Starting execution');
|
|
245
|
-
this.log('debug', 'Processing', params);
|
|
259
|
+
async execute(params) {
|
|
260
|
+
this.context.log('info', 'Starting execution');
|
|
261
|
+
this.context.log('debug', 'Processing', params);
|
|
246
262
|
|
|
247
263
|
if (this.integrationFactory) {
|
|
248
|
-
this.log('info', 'Integration factory available');
|
|
264
|
+
this.context.log('info', 'Integration factory available');
|
|
249
265
|
}
|
|
250
266
|
|
|
251
267
|
return { processed: true };
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
|
|
271
|
+
const mockContext = { log: jest.fn() };
|
|
255
272
|
const mockFactory = { getInstanceById: jest.fn() };
|
|
256
273
|
const script = new MyScript({
|
|
274
|
+
context: mockContext,
|
|
257
275
|
executionId: 'exec_789',
|
|
258
276
|
integrationFactory: mockFactory,
|
|
259
277
|
});
|
|
260
278
|
|
|
261
|
-
const
|
|
262
|
-
const result = await script.execute(frigg, { test: 'data' });
|
|
279
|
+
const result = await script.execute({ test: 'data' });
|
|
263
280
|
|
|
264
281
|
expect(result).toEqual({ processed: true });
|
|
265
282
|
|
|
266
|
-
|
|
267
|
-
expect(
|
|
268
|
-
expect(
|
|
269
|
-
expect(
|
|
270
|
-
expect(logs[2].message).toBe('Integration factory available');
|
|
283
|
+
expect(mockContext.log).toHaveBeenCalledTimes(3);
|
|
284
|
+
expect(mockContext.log).toHaveBeenCalledWith('info', 'Starting execution');
|
|
285
|
+
expect(mockContext.log).toHaveBeenCalledWith('debug', 'Processing', { test: 'data' });
|
|
286
|
+
expect(mockContext.log).toHaveBeenCalledWith('info', 'Integration factory available');
|
|
271
287
|
});
|
|
272
288
|
});
|
|
273
289
|
});
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
const { QueuerUtil } = require('@friggframework/core/queues');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* AdminFriggCommands
|
|
5
|
-
*
|
|
6
|
-
* Helper API for admin scripts. Provides:
|
|
7
|
-
* - Database access via repositories
|
|
8
|
-
* - Integration instantiation (optional)
|
|
9
|
-
* - Logging utilities
|
|
10
|
-
* - Queue operations for self-queuing pattern
|
|
11
|
-
*
|
|
12
|
-
* Follows lazy-loading pattern for repositories to avoid circular dependencies
|
|
13
|
-
* and unnecessary initialization.
|
|
14
|
-
*/
|
|
15
|
-
class AdminFriggCommands {
|
|
3
|
+
class AdminScriptContext {
|
|
16
4
|
constructor(params = {}) {
|
|
17
5
|
this.executionId = params.executionId || null;
|
|
18
6
|
this.logs = [];
|
|
@@ -151,12 +139,8 @@ class AdminFriggCommands {
|
|
|
151
139
|
});
|
|
152
140
|
}
|
|
153
141
|
|
|
154
|
-
// ==================== QUEUE OPERATIONS
|
|
142
|
+
// ==================== QUEUE OPERATIONS ====================
|
|
155
143
|
|
|
156
|
-
/**
|
|
157
|
-
* Queue a script for execution
|
|
158
|
-
* Used for self-queuing pattern with long-running scripts
|
|
159
|
-
*/
|
|
160
144
|
async queueScript(scriptName, params = {}) {
|
|
161
145
|
const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
|
|
162
146
|
if (!queueUrl) {
|
|
@@ -176,9 +160,6 @@ class AdminFriggCommands {
|
|
|
176
160
|
this.log('info', `Queued continuation for ${scriptName}`, { params });
|
|
177
161
|
}
|
|
178
162
|
|
|
179
|
-
/**
|
|
180
|
-
* Queue multiple scripts in a batch
|
|
181
|
-
*/
|
|
182
163
|
async queueScriptBatch(entries) {
|
|
183
164
|
const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
|
|
184
165
|
if (!queueUrl) {
|
|
@@ -230,13 +211,20 @@ class AdminFriggCommands {
|
|
|
230
211
|
}
|
|
231
212
|
|
|
232
213
|
/**
|
|
233
|
-
* Create
|
|
214
|
+
* Create AdminScriptContext instance
|
|
234
215
|
*/
|
|
235
|
-
function
|
|
236
|
-
return new
|
|
216
|
+
function createAdminScriptContext(params = {}) {
|
|
217
|
+
return new AdminScriptContext(params);
|
|
237
218
|
}
|
|
238
219
|
|
|
220
|
+
// Legacy aliases for backwards compatibility
|
|
221
|
+
const AdminFriggCommands = AdminScriptContext;
|
|
222
|
+
const createAdminFriggCommands = createAdminScriptContext;
|
|
223
|
+
|
|
239
224
|
module.exports = {
|
|
225
|
+
AdminScriptContext,
|
|
226
|
+
createAdminScriptContext,
|
|
227
|
+
// Legacy exports (deprecated)
|
|
240
228
|
AdminFriggCommands,
|
|
241
229
|
createAdminFriggCommands,
|
|
242
230
|
};
|
|
@@ -1,63 +1,27 @@
|
|
|
1
|
-
const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Admin Script Base Class
|
|
5
|
-
*
|
|
6
|
-
* Base class for all admin scripts. Provides:
|
|
7
|
-
* - Standard script definition pattern
|
|
8
|
-
* - Repository access
|
|
9
|
-
* - Logging helpers
|
|
10
|
-
* - Integration factory support (optional)
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* ```javascript
|
|
14
|
-
* class MyScript extends AdminScriptBase {
|
|
15
|
-
* static Definition = {
|
|
16
|
-
* name: 'my-script',
|
|
17
|
-
* version: '1.0.0',
|
|
18
|
-
* description: 'Does something useful',
|
|
19
|
-
* ...
|
|
20
|
-
* };
|
|
21
|
-
*
|
|
22
|
-
* async execute(frigg, params) {
|
|
23
|
-
* // Your script logic here
|
|
24
|
-
* }
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
1
|
class AdminScriptBase {
|
|
29
|
-
/**
|
|
30
|
-
* CHILDREN SHOULD SPECIFY A DEFINITION FOR THE SCRIPT
|
|
31
|
-
* Pattern matches IntegrationBase.Definition
|
|
32
|
-
*/
|
|
33
2
|
static Definition = {
|
|
34
|
-
name: 'Script Name',
|
|
35
|
-
version: '0.0.0',
|
|
36
|
-
description: 'What this script does',
|
|
37
|
-
|
|
38
|
-
// Script-specific properties
|
|
3
|
+
name: 'Script Name',
|
|
4
|
+
version: '0.0.0',
|
|
5
|
+
description: 'What this script does',
|
|
39
6
|
source: 'USER_DEFINED', // 'BUILTIN' | 'USER_DEFINED'
|
|
40
7
|
|
|
41
|
-
inputSchema: null,
|
|
42
|
-
outputSchema: null,
|
|
8
|
+
inputSchema: null,
|
|
9
|
+
outputSchema: null,
|
|
43
10
|
|
|
44
11
|
schedule: {
|
|
45
|
-
// Optional: Phase 2
|
|
46
12
|
enabled: false,
|
|
47
|
-
cronExpression: null,
|
|
13
|
+
cronExpression: null,
|
|
48
14
|
},
|
|
49
15
|
|
|
50
16
|
config: {
|
|
51
|
-
timeout: 300000,
|
|
17
|
+
timeout: 300000,
|
|
52
18
|
maxRetries: 0,
|
|
53
|
-
requireIntegrationInstance: false,
|
|
19
|
+
requireIntegrationInstance: false,
|
|
54
20
|
},
|
|
55
21
|
|
|
56
22
|
display: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
description: '',
|
|
60
|
-
category: 'maintenance', // 'maintenance' | 'healing' | 'sync' | 'custom'
|
|
23
|
+
category: 'maintenance',
|
|
24
|
+
icon: null,
|
|
61
25
|
},
|
|
62
26
|
};
|
|
63
27
|
|
|
@@ -73,63 +37,22 @@ class AdminScriptBase {
|
|
|
73
37
|
return this.Definition;
|
|
74
38
|
}
|
|
75
39
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* Pattern matches IntegrationBase constructor
|
|
79
|
-
*/
|
|
80
|
-
constructor(params = {}) {
|
|
81
|
-
this.executionId = params.executionId || null;
|
|
82
|
-
this.logs = [];
|
|
83
|
-
this._startTime = null;
|
|
84
|
-
|
|
85
|
-
// OPTIONAL: Integration factory for scripts that need it
|
|
86
|
-
this.integrationFactory = params.integrationFactory || null;
|
|
87
|
-
|
|
88
|
-
// OPTIONAL: Injected repositories (for testing or custom implementations)
|
|
89
|
-
this.adminProcessRepository = params.adminProcessRepository || null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* CHILDREN MUST IMPLEMENT THIS METHOD
|
|
94
|
-
* @param {AdminFriggCommands} frigg - Helper commands object
|
|
95
|
-
* @param {Object} params - Script parameters (validated against inputSchema)
|
|
96
|
-
* @returns {Promise<Object>} - Script results (validated against outputSchema)
|
|
97
|
-
*/
|
|
98
|
-
async execute(frigg, params) {
|
|
99
|
-
throw new Error('AdminScriptBase.execute() must be implemented by subclass');
|
|
40
|
+
static getDisplayLabel() {
|
|
41
|
+
return this.Definition.display?.label || this.Definition.name;
|
|
100
42
|
}
|
|
101
43
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
* @param {string} level - Log level (info, warn, error, debug)
|
|
105
|
-
* @param {string} message - Log message
|
|
106
|
-
* @param {Object} data - Additional data
|
|
107
|
-
* @returns {Object} Log entry
|
|
108
|
-
*/
|
|
109
|
-
log(level, message, data = {}) {
|
|
110
|
-
const entry = {
|
|
111
|
-
level,
|
|
112
|
-
message,
|
|
113
|
-
data,
|
|
114
|
-
timestamp: new Date().toISOString(),
|
|
115
|
-
};
|
|
116
|
-
this.logs.push(entry);
|
|
117
|
-
return entry;
|
|
44
|
+
static getDisplayDescription() {
|
|
45
|
+
return this.Definition.display?.description || this.Definition.description;
|
|
118
46
|
}
|
|
119
47
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
getLogs() {
|
|
125
|
-
return this.logs;
|
|
48
|
+
constructor(params = {}) {
|
|
49
|
+
this.context = params.context || null;
|
|
50
|
+
this.executionId = params.executionId || null;
|
|
51
|
+
this.integrationFactory = params.integrationFactory || null;
|
|
126
52
|
}
|
|
127
53
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*/
|
|
131
|
-
clearLogs() {
|
|
132
|
-
this.logs = [];
|
|
54
|
+
async execute(params) {
|
|
55
|
+
throw new Error('AdminScriptBase.execute() must be implemented by subclass');
|
|
133
56
|
}
|
|
134
57
|
}
|
|
135
58
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getScriptFactory } = require('./script-factory');
|
|
2
|
-
const {
|
|
2
|
+
const { createAdminScriptContext } = require('./admin-frigg-commands');
|
|
3
3
|
const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -7,8 +7,7 @@ const { createAdminScriptCommands } = require('@friggframework/core/application/
|
|
|
7
7
|
*
|
|
8
8
|
* Orchestrates script execution with:
|
|
9
9
|
* - Execution record creation
|
|
10
|
-
* - Script instantiation
|
|
11
|
-
* - AdminFriggCommands injection
|
|
10
|
+
* - Script instantiation with context injection
|
|
12
11
|
* - Error handling
|
|
13
12
|
* - Status updates
|
|
14
13
|
*/
|
|
@@ -71,20 +70,21 @@ class ScriptRunner {
|
|
|
71
70
|
try {
|
|
72
71
|
await this.commands.updateAdminProcessState(executionId, 'RUNNING');
|
|
73
72
|
|
|
74
|
-
// Create
|
|
75
|
-
const
|
|
73
|
+
// Create context for the script (facade over repositories, queue, logging)
|
|
74
|
+
const context = createAdminScriptContext({
|
|
76
75
|
executionId,
|
|
77
76
|
integrationFactory: this.integrationFactory,
|
|
78
77
|
});
|
|
79
78
|
|
|
80
|
-
// Create script instance
|
|
79
|
+
// Create script instance with context injected via constructor
|
|
81
80
|
const script = this.scriptFactory.createInstance(scriptName, {
|
|
81
|
+
context,
|
|
82
82
|
executionId,
|
|
83
83
|
integrationFactory: this.integrationFactory,
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
// Execute the script
|
|
87
|
-
const output = await script.execute(
|
|
87
|
+
const output = await script.execute(params);
|
|
88
88
|
|
|
89
89
|
// Calculate metrics
|
|
90
90
|
const endTime = new Date();
|