@friggframework/serverless-plugin 2.0.0--canary.580.1003d8d.0 → 2.0.0--canary.580.4487187.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.
@@ -43,6 +43,19 @@ class LocalStackQueueService {
43
43
  * Serialize a CloudFormation `Properties` object into the
44
44
  * `Attributes` shape the SQS `CreateQueue` API accepts (string
45
45
  * values only; object values like `RedrivePolicy` get JSON-encoded).
46
+ *
47
+ * Attributes whose value still contains an unresolved CloudFormation
48
+ * intrinsic (`Fn::GetAtt`, `Ref`, `Fn::Sub`, …) are DROPPED rather
49
+ * than stringified. Example: integration-builder.js emits
50
+ * `RedrivePolicy.deadLetterTargetArn: {'Fn::GetAtt': [...]}`, which
51
+ * CloudFormation resolves to a real ARN in AWS but is still a raw
52
+ * intrinsic object at local plugin-time. Forwarding that JSON blob
53
+ * to SQS `CreateQueue` would fail (`deadLetterTargetArn` must be a
54
+ * valid ARN string) or produce malformed config. Dropping the
55
+ * attribute gives local parity on every other queue property
56
+ * (notably `VisibilityTimeout`, which is the main reason this code
57
+ * exists) while leaving the DLQ association intentionally un-wired
58
+ * locally — matching the pre-PR behavior for that one attribute.
46
59
  * @private
47
60
  */
48
61
  _propertiesToAttributes(properties = {}) {
@@ -50,12 +63,46 @@ class LocalStackQueueService {
50
63
  for (const key of LocalStackQueueService.PROPERTY_ATTRIBUTE_KEYS) {
51
64
  const value = properties[key];
52
65
  if (value === undefined || value === null) continue;
66
+ if (LocalStackQueueService._containsUnresolvedIntrinsic(value)) {
67
+ console.warn(
68
+ `[frigg-plugin] Skipping queue attribute "${key}" because it contains an unresolved CloudFormation intrinsic. ` +
69
+ `Deployed AWS will apply it via CloudFormation; local emulation will fall back to the AWS default for this attribute.`
70
+ );
71
+ continue;
72
+ }
53
73
  attributes[key] =
54
74
  typeof value === 'object' ? JSON.stringify(value) : String(value);
55
75
  }
56
76
  return attributes;
57
77
  }
58
78
 
79
+ /**
80
+ * Recursively checks whether a value still contains a CloudFormation
81
+ * intrinsic function key (`Fn::*` or `Ref`). Such values are unsafe
82
+ * to pass through to SQS `CreateQueue` — AWS's runtime API doesn't
83
+ * understand CloudFormation intrinsics; they're only valid inside
84
+ * serverless.yml / CloudFormation templates.
85
+ * @private
86
+ */
87
+ static _containsUnresolvedIntrinsic(value) {
88
+ if (value === null || value === undefined) return false;
89
+ if (typeof value !== 'object') return false;
90
+ if (Array.isArray(value)) {
91
+ return value.some((v) =>
92
+ LocalStackQueueService._containsUnresolvedIntrinsic(v)
93
+ );
94
+ }
95
+ for (const key of Object.keys(value)) {
96
+ if (key === 'Ref' || key.startsWith('Fn::')) return true;
97
+ if (
98
+ LocalStackQueueService._containsUnresolvedIntrinsic(value[key])
99
+ ) {
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
59
106
  /**
60
107
  * @param {string} queueName - Name of queue to create
61
108
  * @param {Object} [attributes] - SQS CreateQueue Attributes (already
@@ -109,6 +109,68 @@ describe('LocalStackQueueService', () => {
109
109
  it('returns an empty object when properties are missing', () => {
110
110
  expect(service._propertiesToAttributes()).toEqual({});
111
111
  });
112
+
113
+ it('drops attributes containing unresolved CloudFormation intrinsics', () => {
114
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
115
+ const attrs = service._propertiesToAttributes({
116
+ VisibilityTimeout: 1800,
117
+ RedrivePolicy: {
118
+ maxReceiveCount: 3,
119
+ deadLetterTargetArn: {
120
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
121
+ },
122
+ },
123
+ });
124
+
125
+ // VisibilityTimeout survives; RedrivePolicy is dropped because
126
+ // deadLetterTargetArn is still an unresolved Fn::GetAtt intrinsic
127
+ // (real AWS resolves it via CloudFormation; LocalStack cannot).
128
+ expect(attrs).toEqual({ VisibilityTimeout: '1800' });
129
+ expect(attrs).not.toHaveProperty('RedrivePolicy');
130
+ expect(warnSpy).toHaveBeenCalledWith(
131
+ expect.stringContaining('Skipping queue attribute "RedrivePolicy"')
132
+ );
133
+ warnSpy.mockRestore();
134
+ });
135
+
136
+ it('drops attributes with Ref intrinsics', () => {
137
+ jest.spyOn(console, 'warn').mockImplementation();
138
+ const attrs = service._propertiesToAttributes({
139
+ VisibilityTimeout: 60,
140
+ KmsMasterKeyId: { Ref: 'MyKmsKey' },
141
+ });
142
+ expect(attrs).toEqual({ VisibilityTimeout: '60' });
143
+ });
144
+
145
+ it('detects intrinsics nested deep inside objects and arrays', () => {
146
+ expect(
147
+ LocalStackQueueService._containsUnresolvedIntrinsic({
148
+ a: { b: [{ c: { 'Fn::Sub': '${AWS::Region}' } }] },
149
+ })
150
+ ).toBe(true);
151
+ expect(
152
+ LocalStackQueueService._containsUnresolvedIntrinsic({
153
+ a: { b: [{ c: 'hello' }] },
154
+ })
155
+ ).toBe(false);
156
+ expect(LocalStackQueueService._containsUnresolvedIntrinsic(null)).toBe(false);
157
+ expect(LocalStackQueueService._containsUnresolvedIntrinsic('str')).toBe(false);
158
+ });
159
+
160
+ it('retains resolved RedrivePolicy (ARN already a string)', () => {
161
+ const attrs = service._propertiesToAttributes({
162
+ RedrivePolicy: {
163
+ maxReceiveCount: 3,
164
+ deadLetterTargetArn: 'arn:aws:sqs:us-east-1:x:dlq',
165
+ },
166
+ });
167
+ expect(attrs.RedrivePolicy).toBe(
168
+ JSON.stringify({
169
+ maxReceiveCount: 3,
170
+ deadLetterTargetArn: 'arn:aws:sqs:us-east-1:x:dlq',
171
+ })
172
+ );
173
+ });
112
174
  });
113
175
 
114
176
  describe('createQueues', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friggframework/serverless-plugin",
3
- "version": "2.0.0--canary.580.1003d8d.0",
3
+ "version": "2.0.0--canary.580.4487187.0",
4
4
  "description": "Plugin to help dynamically load frigg resources",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,5 +11,5 @@
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
14
- "gitHead": "1003d8deff8f587be3cd9210f1c35c5dc92531be"
14
+ "gitHead": "44871876975b9e507ed635e9df4075b2215685d1"
15
15
  }