@friggframework/devtools 2.0.0--canary.517.bd5ddfc.0 → 2.0.0--canary.517.9750290.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.
@@ -178,6 +178,16 @@ class AdminScriptBuilder extends InfrastructureBuilder {
178
178
  }
179
179
 
180
180
  createSchedulerResources(appDefinition, result) {
181
+ // Reference the executor by a constructed ARN (Fn::Sub) rather than
182
+ // Fn::GetAtt. GetAtt creates a CloudFormation dependency edge, and an
183
+ // edge from the scheduler role to the executor closed a cycle through the
184
+ // shared Lambda execution role (role → executor → IamRoleLambdaExecution
185
+ // → PassRole → role). The name is deterministic, so a Sub is safe.
186
+ const executorArn = {
187
+ 'Fn::Sub':
188
+ 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
189
+ };
190
+
181
191
  // Create IAM role for EventBridge Scheduler
182
192
  result.resources.AdminScriptSchedulerRole = {
183
193
  Type: 'AWS::IAM::Role',
@@ -198,7 +208,7 @@ class AdminScriptBuilder extends InfrastructureBuilder {
198
208
  Statement: [{
199
209
  Effect: 'Allow',
200
210
  Action: 'lambda:InvokeFunction',
201
- Resource: { 'Fn::GetAtt': ['AdminScriptExecutorLambdaFunction', 'Arn'] },
211
+ Resource: executorArn,
202
212
  }],
203
213
  },
204
214
  }],
@@ -213,13 +223,25 @@ class AdminScriptBuilder extends InfrastructureBuilder {
213
223
  },
214
224
  };
215
225
 
216
- // Env vars consumed by the admin-script router when building the AWS
217
- // scheduler adapter. Names must match admin-script-router.js exactly.
226
+ // SCHEDULER_PROVIDER is a constant, so it's safe on the shared provider
227
+ // environment inherited by every function.
218
228
  result.environment.SCHEDULER_PROVIDER = 'aws';
219
- result.environment.SCHEDULER_ROLE_ARN = { 'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'] };
220
- result.environment.ADMIN_SCRIPT_SCHEDULE_GROUP = { Ref: 'AdminScriptScheduleGroup' };
221
- result.environment.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN = {
222
- 'Fn::GetAtt': ['AdminScriptExecutorLambdaFunction', 'Arn'],
229
+
230
+ // The remaining vars carry resource references and are consumed ONLY by
231
+ // the router (it builds the AWS scheduler adapter in the schedule
232
+ // handlers). Scope them to the router function rather than the shared
233
+ // provider environment: broadcasting the executor's own ARN into every
234
+ // function's env made the executor depend on itself, and the role/group
235
+ // refs made every function depend on the scheduler role — both closed
236
+ // CloudFormation circular dependencies on deploy. Names must match
237
+ // admin-script-router.js exactly.
238
+ result.functions.adminScriptRouter.environment = {
239
+ ...(result.functions.adminScriptRouter.environment || {}),
240
+ SCHEDULER_ROLE_ARN: {
241
+ 'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
242
+ },
243
+ ADMIN_SCRIPT_SCHEDULE_GROUP: { Ref: 'AdminScriptScheduleGroup' },
244
+ ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN: executorArn,
223
245
  };
224
246
 
225
247
  // The router manages schedules through the AWS scheduler adapter, so it
@@ -366,17 +366,33 @@ describe('AdminScriptBuilder', () => {
366
366
  expect(result.resources.AdminScriptScheduleGroup).toBeDefined();
367
367
  expect(result.resources.AdminScriptScheduleGroup.Type).toBe('AWS::Scheduler::ScheduleGroup');
368
368
 
369
- // Check for environment variables consumed by the router's scheduler adapter
369
+ // SCHEDULER_PROVIDER is a constant and stays on the shared env.
370
370
  expect(result.environment.SCHEDULER_PROVIDER).toBe('aws');
371
- expect(result.environment.SCHEDULER_ROLE_ARN).toEqual({
371
+
372
+ // Resource-reference env vars are scoped to the router function (not
373
+ // the shared provider env) to avoid CloudFormation circular deps.
374
+ const routerEnv = result.functions.adminScriptRouter.environment;
375
+ expect(routerEnv.SCHEDULER_ROLE_ARN).toEqual({
372
376
  'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
373
377
  });
374
- expect(result.environment.ADMIN_SCRIPT_SCHEDULE_GROUP).toEqual({
378
+ expect(routerEnv.ADMIN_SCRIPT_SCHEDULE_GROUP).toEqual({
375
379
  Ref: 'AdminScriptScheduleGroup',
376
380
  });
377
- expect(result.environment.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN).toEqual({
378
- 'Fn::GetAtt': ['AdminScriptExecutorLambdaFunction', 'Arn'],
381
+ // Executor referenced by constructed ARN (Fn::Sub), not Fn::GetAtt,
382
+ // so it creates no dependency edge.
383
+ expect(routerEnv.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN).toEqual({
384
+ 'Fn::Sub':
385
+ 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
379
386
  });
387
+
388
+ // Regression guard: these must NOT leak onto the shared provider env
389
+ // (that is what produced the circular dependency, incl. the executor
390
+ // self-reference).
391
+ expect(result.environment.SCHEDULER_ROLE_ARN).toBeUndefined();
392
+ expect(result.environment.ADMIN_SCRIPT_SCHEDULE_GROUP).toBeUndefined();
393
+ expect(
394
+ result.environment.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN
395
+ ).toBeUndefined();
380
396
  });
381
397
 
382
398
  it('should not create scheduler resources when enableScheduling is false', async () => {
@@ -494,10 +510,15 @@ describe('AdminScriptBuilder', () => {
494
510
  const policies = result.resources.AdminScriptSchedulerRole.Properties.Policies;
495
511
 
496
512
  expect(policies[0].PolicyName).toBe('InvokeLambda');
513
+ // Constructed ARN (Fn::Sub), not Fn::GetAtt — a GetAtt here closes a
514
+ // circular dependency through the shared Lambda execution role.
497
515
  expect(policies[0].PolicyDocument.Statement[0]).toEqual({
498
516
  Effect: 'Allow',
499
517
  Action: 'lambda:InvokeFunction',
500
- Resource: { 'Fn::GetAtt': ['AdminScriptExecutorLambdaFunction', 'Arn'] },
518
+ Resource: {
519
+ 'Fn::Sub':
520
+ 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
521
+ },
501
522
  });
502
523
  });
503
524
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.bd5ddfc.0",
4
+ "version": "2.0.0--canary.517.9750290.0",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
@@ -25,9 +25,9 @@
25
25
  "@babel/eslint-parser": "^7.18.9",
26
26
  "@babel/parser": "^7.25.3",
27
27
  "@babel/traverse": "^7.25.3",
28
- "@friggframework/core": "2.0.0--canary.517.bd5ddfc.0",
29
- "@friggframework/schemas": "2.0.0--canary.517.bd5ddfc.0",
30
- "@friggframework/test": "2.0.0--canary.517.bd5ddfc.0",
28
+ "@friggframework/core": "2.0.0--canary.517.9750290.0",
29
+ "@friggframework/schemas": "2.0.0--canary.517.9750290.0",
30
+ "@friggframework/test": "2.0.0--canary.517.9750290.0",
31
31
  "@hapi/boom": "^10.0.1",
32
32
  "@inquirer/prompts": "^5.3.8",
33
33
  "axios": "^1.18.0",
@@ -55,8 +55,8 @@
55
55
  "validate-npm-package-name": "^5.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@friggframework/eslint-config": "2.0.0--canary.517.bd5ddfc.0",
59
- "@friggframework/prettier-config": "2.0.0--canary.517.bd5ddfc.0",
58
+ "@friggframework/eslint-config": "2.0.0--canary.517.9750290.0",
59
+ "@friggframework/prettier-config": "2.0.0--canary.517.9750290.0",
60
60
  "aws-sdk-client-mock": "^4.1.0",
61
61
  "aws-sdk-client-mock-jest": "^4.1.0",
62
62
  "jest": "^30.1.3",
@@ -88,5 +88,5 @@
88
88
  "publishConfig": {
89
89
  "access": "public"
90
90
  },
91
- "gitHead": "bd5ddfc493049d1c233bce2f2b8203d50a1f129c"
91
+ "gitHead": "9750290bdb331b5f2d1304d7e64adc3c480f1fb7"
92
92
  }