@friggframework/admin-scripts 2.0.0--canary.517.b384239.0 → 2.0.0--canary.517.bd5ddfc.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/README.md CHANGED
@@ -6,7 +6,7 @@ Typical use cases:
6
6
 
7
7
  - **Healing scripts** — repair broken integration state (e.g. corrupted config).
8
8
  - **Recurring maintenance** — refresh webhooks/subscriptions before they expire.
9
- - **Built-in utilities** — OAuth token refresh, integration health checks.
9
+ - **Operational tasks** — OAuth token refresh, integration health checks, one-off data backfills. (You write these — none ship built-in.)
10
10
 
11
11
  > Admin scripts are a **high-privilege** surface. Every endpoint is protected by an admin API key (`x-frigg-admin-api-key`), scripts run in your private VPC subnets, and every execution is tracked in the `AdminScriptExecution` table. Never expose the admin API key to browsers or end users.
12
12
 
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.b384239.0",
4
+ "version": "2.0.0--canary.517.bd5ddfc.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.b384239.0",
8
+ "@friggframework/core": "2.0.0--canary.517.bd5ddfc.0",
9
9
  "@hapi/boom": "^10.0.1",
10
10
  "express": "^4.18.2",
11
11
  "serverless-http": "^3.2.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@friggframework/eslint-config": "2.0.0--canary.517.b384239.0",
15
- "@friggframework/prettier-config": "2.0.0--canary.517.b384239.0",
16
- "@friggframework/test": "2.0.0--canary.517.b384239.0",
14
+ "@friggframework/eslint-config": "2.0.0--canary.517.bd5ddfc.0",
15
+ "@friggframework/prettier-config": "2.0.0--canary.517.bd5ddfc.0",
16
+ "@friggframework/test": "2.0.0--canary.517.bd5ddfc.0",
17
17
  "eslint": "^8.22.0",
18
18
  "jest": "^29.7.0",
19
19
  "prettier": "^2.7.1",
@@ -44,5 +44,5 @@
44
44
  "maintenance",
45
45
  "operations"
46
46
  ],
47
- "gitHead": "b384239f327baa975cb82c0f63888e67f40160c3"
47
+ "gitHead": "bd5ddfc493049d1c233bce2f2b8203d50a1f129c"
48
48
  }
@@ -111,27 +111,8 @@ describe('AdminScriptContext', () => {
111
111
  mockFactory.getInstanceFromIntegrationId
112
112
  ).toHaveBeenCalledWith({
113
113
  integrationId: 'int_123',
114
- _isAdminContext: true,
115
114
  });
116
115
  });
117
-
118
- it('passes _isAdminContext: true', async () => {
119
- const mockInstance = { primary: { api: {} } };
120
- const mockFactory = {
121
- getInstanceFromIntegrationId: jest
122
- .fn()
123
- .mockResolvedValue(mockInstance),
124
- };
125
- const ctx = new AdminScriptContext({
126
- integrationFactory: mockFactory,
127
- });
128
-
129
- await ctx.instantiate('int_123');
130
-
131
- const callArgs =
132
- mockFactory.getInstanceFromIntegrationId.mock.calls[0][0];
133
- expect(callArgs._isAdminContext).toBe(true);
134
- });
135
116
  });
136
117
 
137
118
  describe('queueScript()', () => {
@@ -11,8 +11,8 @@ const { QueuerUtil } = require('@friggframework/core/queues');
11
11
  * `commands.entities`, and `commands.integrations` expose the framework's
12
12
  * command layer. Each command returns data on success or an `{ error }`
13
13
  * object on failure — scripts check `.error` themselves.
14
- * - **Admin bypass**: `instantiate()` passes `_isAdminContext: true` to
15
- * skip user-ownership checks when loading integration instances
14
+ * - **Integration instantiation**: `instantiate(integrationId)` hydrates a live
15
+ * integration instance (system-scoped load) for calling external APIs
16
16
  * - **Script chaining**: `queueScript()` / `queueScriptBatch()` let scripts
17
17
  * enqueue follow-up work with parent execution tracking
18
18
  * - **Execution-scoped logging**: `log()` collects structured entries tied
@@ -52,7 +52,6 @@ class AdminScriptContext {
52
52
  }
53
53
  return this.integrationFactory.getInstanceFromIntegrationId({
54
54
  integrationId,
55
- _isAdminContext: true, // Bypass user ownership check
56
55
  });
57
56
  }
58
57
 
@@ -253,6 +253,70 @@ describe('Admin Script Router', () => {
253
253
  expect(response.status).toBe(404);
254
254
  expect(response.body.code).toBe('SCRIPT_NOT_FOUND');
255
255
  });
256
+
257
+ it('rejects a sync script whose timeout exceeds the API budget on AWS', async () => {
258
+ // TestScript.Definition.config.timeout is 300000 (> 25000)
259
+ process.env.AWS_LAMBDA_FUNCTION_NAME = 'admin-script-router';
260
+
261
+ const response = await request(app)
262
+ .post('/admin/scripts/test-script')
263
+ .send({ params: {}, mode: 'sync' });
264
+
265
+ expect(response.status).toBe(400);
266
+ expect(response.body.code).toBe('SYNC_TIMEOUT_TOO_LONG');
267
+ expect(mockRunner.execute).not.toHaveBeenCalled();
268
+ delete process.env.AWS_LAMBDA_FUNCTION_NAME;
269
+ });
270
+
271
+ it('allows a sync script within the budget on AWS (25000 boundary)', async () => {
272
+ process.env.AWS_LAMBDA_FUNCTION_NAME = 'admin-script-router';
273
+ class ShortScript extends AdminScriptBase {
274
+ static Definition = {
275
+ name: 'short-script',
276
+ version: '1.0.0',
277
+ description: 'within budget',
278
+ config: { timeout: 25000 },
279
+ };
280
+ async execute() {
281
+ return {};
282
+ }
283
+ }
284
+ mockFactory.get.mockReturnValue(ShortScript);
285
+ mockRunner.execute.mockResolvedValue({
286
+ executionId: 'exec-1',
287
+ status: 'COMPLETED',
288
+ scriptName: 'short-script',
289
+ output: {},
290
+ metrics: { durationMs: 1 },
291
+ });
292
+
293
+ const response = await request(app)
294
+ .post('/admin/scripts/short-script')
295
+ .send({ params: {}, mode: 'sync' });
296
+
297
+ expect(response.status).toBe(200);
298
+ expect(mockRunner.execute).toHaveBeenCalled();
299
+ delete process.env.AWS_LAMBDA_FUNCTION_NAME;
300
+ });
301
+
302
+ it('does not queue and surfaces the error when createExecution fails (async)', async () => {
303
+ process.env.ADMIN_SCRIPT_QUEUE_URL =
304
+ 'https://sqs.us-east-1.amazonaws.com/123/test-queue';
305
+ mockCommands.createExecution.mockResolvedValue({
306
+ error: 500,
307
+ reason: 'DB down',
308
+ code: 'DB_ERROR',
309
+ });
310
+
311
+ const response = await request(app)
312
+ .post('/admin/scripts/test-script')
313
+ .send({ params: { foo: 'bar' }, mode: 'async' });
314
+
315
+ expect(response.status).toBe(500);
316
+ expect(response.body.error).toBe('DB down');
317
+ expect(QueuerUtil.send).not.toHaveBeenCalled();
318
+ delete process.env.ADMIN_SCRIPT_QUEUE_URL;
319
+ });
256
320
  });
257
321
 
258
322
  describe('GET /admin/scripts/:scriptName/executions/:executionId', () => {
@@ -308,7 +308,7 @@ router.get('/scripts/:scriptName/executions', async (req, res) => {
308
308
 
309
309
  /**
310
310
  * GET /admin/scripts/:scriptName/schedule
311
- * Get effective schedule (DB override > Definition default > none)
311
+ * Get the effective schedule (the DB override, or none)
312
312
  */
313
313
  router.get('/scripts/:scriptName/schedule', async (req, res) => {
314
314
  try {
@@ -372,7 +372,7 @@ router.put('/scripts/:scriptName/schedule', async (req, res) => {
372
372
 
373
373
  /**
374
374
  * DELETE /admin/scripts/:scriptName/schedule
375
- * Remove schedule override (revert to Definition default)
375
+ * Remove the schedule override
376
376
  */
377
377
  router.delete('/scripts/:scriptName/schedule', async (req, res) => {
378
378
  try {