@friggframework/serverless-plugin 2.0.0-next.45 → 2.0.0-next.47

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 (3) hide show
  1. package/index.js +48 -6
  2. package/index.test.js +235 -111
  3. package/package.json +2 -2
package/index.js CHANGED
@@ -28,6 +28,18 @@ class FriggServerlessPlugin {
28
28
  async asyncInit() {
29
29
  this.serverless.cli.log("Initializing Frigg Serverless Plugin...");
30
30
  console.log("Hello from Frigg Serverless Plugin!");
31
+
32
+ // CRITICAL: Create .esbuild/.serverless directory before serverless-esbuild needs it
33
+ // This prevents ENOENT errors during packaging
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const esbuildDir = path.join(this.serverless.config.servicePath || process.cwd(), '.esbuild', '.serverless');
37
+
38
+ if (!fs.existsSync(esbuildDir)) {
39
+ fs.mkdirSync(esbuildDir, { recursive: true });
40
+ console.log(`✓ Created ${esbuildDir} directory for serverless-esbuild`);
41
+ }
42
+
31
43
  if (this.serverless.processedInput.commands.includes("offline")) {
32
44
  console.log("Running in offline mode. Making queues!");
33
45
  const queues = Object.keys(this.serverless.service.custom)
@@ -42,16 +54,24 @@ class FriggServerlessPlugin {
42
54
 
43
55
  const AWS = require("aws-sdk");
44
56
 
45
- const endpointUrl = "localhost:4566"; // Assuming localstack is running on port 4
46
- const region = "us-east-1";
57
+ const endpointUrl = process.env.AWS_ENDPOINT || "http://localhost:4566"; // LocalStack SQS endpoint
58
+ const region = process.env.AWS_REGION || "us-east-1";
59
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID || "root"; // LocalStack default
60
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY || "root"; // LocalStack default
47
61
 
48
- // Configure AWS SDK
62
+ // Configure AWS SDK for LocalStack
49
63
  AWS.config.update({
50
64
  region: region,
51
65
  endpoint: endpointUrl,
66
+ accessKeyId: accessKeyId,
67
+ secretAccessKey: secretAccessKey,
68
+ s3ForcePathStyle: true, // Required for LocalStack
69
+ sslEnabled: false, // Disable SSL for LocalStack
52
70
  });
53
71
 
54
- const sqs = new AWS.SQS();
72
+ const sqs = new AWS.SQS({
73
+ sslEnabled: false, // Disable SSL validation for LocalStack
74
+ });
55
75
  // Find the environment variables that we need to override and create an easy map
56
76
  const environmentMap = {};
57
77
  const environment = this.serverless.service.provider.environment;
@@ -107,13 +127,35 @@ class FriggServerlessPlugin {
107
127
  // AWS discovery is now handled directly in serverless-template.js
108
128
  // This hook remains for potential future use or other pre-package tasks
109
129
  this.serverless.cli.log("Frigg Serverless Plugin: Pre-package hook");
130
+
131
+ // Ensure .esbuild/.serverless directory exists to prevent ENOENT errors
132
+ // serverless-esbuild may try to access this directory during packaging
133
+ const fs = require('fs');
134
+ const path = require('path');
135
+ const esbuildDir = path.join(this.serverless.config.servicePath || process.cwd(), '.esbuild', '.serverless');
136
+
137
+ if (!fs.existsSync(esbuildDir)) {
138
+ fs.mkdirSync(esbuildDir, { recursive: true });
139
+ }
110
140
  }
111
141
 
112
142
 
113
143
  /**
114
- * Initialization hook (currently empty)
144
+ * Initialization hook - runs very early, before packaging
145
+ * Create .esbuild/.serverless directory to prevent ENOENT errors
115
146
  */
116
- init() { }
147
+ init() {
148
+ // Ensure .esbuild/.serverless directory exists to prevent ENOENT errors
149
+ // serverless-esbuild may try to access this directory during packaging
150
+ const fs = require('fs');
151
+ const path = require('path');
152
+ const esbuildDir = path.join(this.serverless.config.servicePath || process.cwd(), '.esbuild', '.serverless');
153
+
154
+ if (!fs.existsSync(esbuildDir)) {
155
+ fs.mkdirSync(esbuildDir, { recursive: true });
156
+ console.log(`Created ${esbuildDir} directory for serverless-esbuild`);
157
+ }
158
+ }
117
159
  /**
118
160
  * Hook that runs after serverless package
119
161
  */
package/index.test.js CHANGED
@@ -1,183 +1,307 @@
1
1
  const FriggServerlessPlugin = require('./index');
2
+ const fs = require('fs');
3
+ const path = require('path');
2
4
 
3
- // Mock dependencies
4
- jest.mock('aws-sdk');
5
+ // Mock fs module
6
+ jest.mock('fs');
5
7
 
6
8
  describe('FriggServerlessPlugin', () => {
7
9
  let plugin;
8
10
  let mockServerless;
9
11
  let mockOptions;
10
- const originalEnv = process.env;
12
+ let mockServicePath;
11
13
 
12
14
  beforeEach(() => {
15
+ mockServicePath = '/test/service/path';
13
16
 
14
- // Mock serverless object
15
17
  mockServerless = {
18
+ config: {
19
+ servicePath: mockServicePath,
20
+ },
16
21
  cli: {
17
- log: jest.fn()
22
+ log: jest.fn(),
18
23
  },
19
24
  service: {
25
+ custom: {},
20
26
  provider: {
21
- name: 'aws',
22
- region: 'us-east-1'
27
+ environment: {},
23
28
  },
24
- plugins: [],
25
- custom: {},
26
- functions: {}
27
29
  },
28
30
  processedInput: {
29
- commands: []
31
+ commands: [],
30
32
  },
31
- getProvider: jest.fn(() => ({})),
32
- extendConfiguration: jest.fn()
33
+ getProvider: jest.fn().mockReturnValue({}),
34
+ extendConfiguration: jest.fn(),
33
35
  };
34
36
 
35
37
  mockOptions = {
36
- stage: 'test'
38
+ stage: 'test',
37
39
  };
38
40
 
39
- // Reset environment
40
- process.env = { ...originalEnv };
41
-
41
+ // Clear all mocks before each test
42
42
  jest.clearAllMocks();
43
-
44
- plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
45
43
  });
46
44
 
47
- afterEach(() => {
48
- process.env = originalEnv;
49
- });
45
+ describe('Constructor', () => {
46
+ it('should initialize with serverless instance and options', () => {
47
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
50
48
 
51
- describe('constructor', () => {
52
- it('should initialize plugin with correct hooks', () => {
53
49
  expect(plugin.serverless).toBe(mockServerless);
54
50
  expect(plugin.options).toBe(mockOptions);
55
- expect(plugin.hooks).toEqual({
56
- initialize: expect.any(Function),
57
- 'before:package:initialize': expect.any(Function),
58
- 'after:package:package': expect.any(Function),
59
- 'before:deploy:deploy': expect.any(Function)
60
- });
51
+ expect(plugin.hooks).toBeDefined();
61
52
  });
62
53
 
63
- it('should get AWS provider from serverless', () => {
64
- expect(mockServerless.getProvider).toHaveBeenCalledWith('aws');
54
+ it('should register required hooks', () => {
55
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
56
+
57
+ expect(plugin.hooks).toHaveProperty('initialize');
58
+ expect(plugin.hooks).toHaveProperty('before:package:initialize');
59
+ expect(plugin.hooks).toHaveProperty('after:package:package');
60
+ expect(plugin.hooks).toHaveProperty('before:deploy:deploy');
65
61
  });
66
62
  });
67
63
 
68
- describe('beforePackageInitialize', () => {
69
- it('should log pre-package hook message', async () => {
70
- await plugin.beforePackageInitialize();
64
+ describe('asyncInit - Directory Creation', () => {
65
+ it('should create .esbuild/.serverless directory if it does not exist', async () => {
66
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
71
67
 
72
- expect(mockServerless.cli.log).toHaveBeenCalledWith('Frigg Serverless Plugin: Pre-package hook');
73
- });
74
- });
68
+ // Mock fs.existsSync to return false (directory doesn't exist)
69
+ fs.existsSync.mockReturnValue(false);
70
+ fs.mkdirSync.mockImplementation(() => { });
75
71
 
72
+ // Spy on console.log
73
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
76
74
 
77
- describe('asyncInit (offline mode)', () => {
78
- beforeEach(() => {
79
- // Mock AWS SDK
80
- const mockSQS = {
81
- createQueue: jest.fn()
82
- };
83
-
84
- jest.doMock('aws-sdk', () => ({
85
- SQS: jest.fn(() => mockSQS),
86
- config: {
87
- update: jest.fn()
88
- }
89
- }));
75
+ await plugin.asyncInit();
90
76
 
91
- mockServerless.service.custom = {
92
- TestQueue: 'test-queue-name',
93
- AnotherQueue: 'another-queue-name'
94
- };
77
+ const expectedPath = path.join(mockServicePath, '.esbuild', '.serverless');
95
78
 
96
- mockServerless.service.provider.environment = {
97
- TEST_QUEUE_URL: { Ref: 'TestQueue' },
98
- ANOTHER_QUEUE_URL: { Ref: 'AnotherQueue' },
99
- NORMAL_VAR: 'normal-value'
100
- };
79
+ // Verify directory existence check
80
+ expect(fs.existsSync).toHaveBeenCalledWith(expectedPath);
81
+
82
+ // Verify directory creation
83
+ expect(fs.mkdirSync).toHaveBeenCalledWith(expectedPath, { recursive: true });
84
+
85
+ // Verify success message
86
+ expect(consoleLogSpy).toHaveBeenCalledWith(
87
+ expect.stringContaining('Created')
88
+ );
89
+ expect(consoleLogSpy).toHaveBeenCalledWith(
90
+ expect.stringContaining('.esbuild')
91
+ );
92
+
93
+ consoleLogSpy.mockRestore();
101
94
  });
102
95
 
103
- it('should create queues in offline mode', async () => {
104
- mockServerless.processedInput.commands = ['offline'];
105
-
106
- const AWS = require('aws-sdk');
107
- const mockSQS = new AWS.SQS();
108
- mockSQS.createQueue.mockImplementation((params, callback) => {
109
- callback(null, { QueueUrl: `http://localhost:4566/000000000000/${params.QueueName}` });
110
- });
96
+ it('should not create directory if it already exists', async () => {
97
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
98
+
99
+ // Mock fs.existsSync to return true (directory exists)
100
+ fs.existsSync.mockReturnValue(true);
101
+ fs.mkdirSync.mockImplementation(() => { });
102
+
103
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
111
104
 
112
105
  await plugin.asyncInit();
113
106
 
114
- expect(AWS.config.update).toHaveBeenCalledWith({
115
- region: 'us-east-1',
116
- endpoint: 'localhost:4566'
117
- });
107
+ const expectedPath = path.join(mockServicePath, '.esbuild', '.serverless');
118
108
 
119
- expect(mockSQS.createQueue).toHaveBeenCalledWith(
120
- { QueueName: 'test-queue-name' },
121
- expect.any(Function)
122
- );
109
+ // Verify directory existence check
110
+ expect(fs.existsSync).toHaveBeenCalledWith(expectedPath);
111
+
112
+ // Verify directory creation was NOT called
113
+ expect(fs.mkdirSync).not.toHaveBeenCalled();
123
114
 
124
- expect(mockSQS.createQueue).toHaveBeenCalledWith(
125
- { QueueName: 'another-queue-name' },
126
- expect.any(Function)
115
+ // Verify success message was NOT logged
116
+ expect(consoleLogSpy).not.toHaveBeenCalledWith(
117
+ expect.stringContaining('Created')
127
118
  );
128
119
 
129
- expect(mockServerless.extendConfiguration).toHaveBeenCalled();
120
+ consoleLogSpy.mockRestore();
121
+ });
122
+
123
+ it('should use process.cwd() if servicePath is not available', async () => {
124
+ // Remove servicePath from config
125
+ mockServerless.config.servicePath = undefined;
126
+
127
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
128
+
129
+ fs.existsSync.mockReturnValue(false);
130
+ fs.mkdirSync.mockImplementation(() => { });
131
+
132
+ await plugin.asyncInit();
133
+
134
+ const expectedPath = path.join(process.cwd(), '.esbuild', '.serverless');
135
+
136
+ expect(fs.existsSync).toHaveBeenCalledWith(expectedPath);
137
+ expect(fs.mkdirSync).toHaveBeenCalledWith(expectedPath, { recursive: true });
138
+ });
139
+
140
+ it('should log initialization messages', async () => {
141
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
142
+
143
+ fs.existsSync.mockReturnValue(true);
144
+
145
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
146
+
147
+ await plugin.asyncInit();
148
+
149
+ // Verify plugin initialization messages
150
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('Initializing Frigg Serverless Plugin...');
151
+ expect(consoleLogSpy).toHaveBeenCalledWith('Hello from Frigg Serverless Plugin!');
152
+
153
+ consoleLogSpy.mockRestore();
130
154
  });
155
+ });
156
+
157
+ describe('asyncInit - Offline Mode', () => {
158
+ it('should not create SQS queues when not in offline mode', async () => {
159
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
160
+
161
+ fs.existsSync.mockReturnValue(true);
131
162
 
132
- it('should skip queue creation in online mode', async () => {
163
+ // Not in offline mode
133
164
  mockServerless.processedInput.commands = ['deploy'];
134
165
 
166
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
167
+
135
168
  await plugin.asyncInit();
136
169
 
137
- const AWS = require('aws-sdk');
138
- expect(AWS.config.update).not.toHaveBeenCalled();
170
+ expect(consoleLogSpy).toHaveBeenCalledWith('Running in online mode, doing nothing');
171
+ expect(consoleLogSpy).not.toHaveBeenCalledWith(
172
+ expect.stringContaining('offline mode')
173
+ );
174
+
175
+ consoleLogSpy.mockRestore();
139
176
  });
140
177
 
141
- it('should handle queue creation errors', async () => {
178
+ it('should create SQS queues when in offline mode', async () => {
179
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
180
+
181
+ fs.existsSync.mockReturnValue(true);
182
+
183
+ // Set offline mode
142
184
  mockServerless.processedInput.commands = ['offline'];
143
-
144
- const AWS = require('aws-sdk');
145
- const mockSQS = new AWS.SQS();
146
- mockSQS.createQueue.mockImplementation((params, callback) => {
147
- callback(new Error('Queue creation failed'));
185
+ mockServerless.service.custom = {
186
+ testQueue: 'test-queue-name',
187
+ };
188
+
189
+ // Mock AWS SDK
190
+ const mockCreateQueue = jest.fn((params, callback) => {
191
+ callback(null, { QueueUrl: 'http://localhost:4566/queue/test-queue-name' });
148
192
  });
149
193
 
150
- await expect(plugin.asyncInit()).rejects.toThrow('Queue creation failed');
194
+ jest.mock('aws-sdk', () => ({
195
+ SQS: jest.fn(() => ({
196
+ createQueue: mockCreateQueue,
197
+ })),
198
+ config: {
199
+ update: jest.fn(),
200
+ },
201
+ }));
202
+
203
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
204
+
205
+ await plugin.asyncInit();
206
+
207
+ expect(consoleLogSpy).toHaveBeenCalledWith(
208
+ expect.stringContaining('offline mode')
209
+ );
210
+
211
+ consoleLogSpy.mockRestore();
151
212
  });
152
213
  });
153
214
 
154
- describe('hook methods', () => {
155
- it('should have init method', () => {
156
- expect(plugin.init).toBeDefined();
157
- expect(typeof plugin.init).toBe('function');
215
+ describe('beforePackageInitialize', () => {
216
+ it('should create .esbuild/.serverless directory', () => {
217
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
218
+
219
+ fs.existsSync.mockReturnValue(false);
220
+ fs.mkdirSync.mockImplementation(() => { });
221
+
222
+ plugin.beforePackageInitialize();
223
+
224
+ const expectedPath = path.join(mockServicePath, '.esbuild', '.serverless');
225
+
226
+ expect(fs.existsSync).toHaveBeenCalledWith(expectedPath);
227
+ expect(fs.mkdirSync).toHaveBeenCalledWith(expectedPath, { recursive: true });
158
228
  });
159
229
 
160
- it('should have afterPackage method', () => {
161
- expect(plugin.afterPackage).toBeDefined();
162
- expect(typeof plugin.afterPackage).toBe('function');
163
-
164
- // Test that it logs correctly
165
- const consoleSpy = jest.spyOn(console, 'log');
230
+ it('should log pre-package hook message', () => {
231
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
232
+
233
+ fs.existsSync.mockReturnValue(true);
234
+
235
+ plugin.beforePackageInitialize();
236
+
237
+ expect(mockServerless.cli.log).toHaveBeenCalledWith('Frigg Serverless Plugin: Pre-package hook');
238
+ });
239
+ });
240
+
241
+ describe('init', () => {
242
+ it('should create .esbuild/.serverless directory', () => {
243
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
244
+
245
+ fs.existsSync.mockReturnValue(false);
246
+ fs.mkdirSync.mockImplementation(() => { });
247
+
248
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
249
+
250
+ plugin.init();
251
+
252
+ const expectedPath = path.join(mockServicePath, '.esbuild', '.serverless');
253
+
254
+ expect(fs.existsSync).toHaveBeenCalledWith(expectedPath);
255
+ expect(fs.mkdirSync).toHaveBeenCalledWith(expectedPath, { recursive: true });
256
+ expect(consoleLogSpy).toHaveBeenCalledWith(
257
+ expect.stringContaining('Created')
258
+ );
259
+
260
+ consoleLogSpy.mockRestore();
261
+ });
262
+ });
263
+
264
+ describe('afterPackage', () => {
265
+ it('should log after package hook message', () => {
266
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
267
+
268
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
269
+
166
270
  plugin.afterPackage();
167
- expect(consoleSpy).toHaveBeenCalledWith('After package hook called');
168
- consoleSpy.mockRestore();
271
+
272
+ expect(consoleLogSpy).toHaveBeenCalledWith('After package hook called');
273
+
274
+ consoleLogSpy.mockRestore();
169
275
  });
276
+ });
277
+
278
+ describe('beforeDeploy', () => {
279
+ it('should log before deploy hook message', () => {
280
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
281
+
282
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
170
283
 
171
- it('should have beforeDeploy method', () => {
172
- expect(plugin.beforeDeploy).toBeDefined();
173
- expect(typeof plugin.beforeDeploy).toBe('function');
174
-
175
- // Test that it logs correctly
176
- const consoleSpy = jest.spyOn(console, 'log');
177
284
  plugin.beforeDeploy();
178
- expect(consoleSpy).toHaveBeenCalledWith('Before deploy hook called');
179
- consoleSpy.mockRestore();
285
+
286
+ expect(consoleLogSpy).toHaveBeenCalledWith('Before deploy hook called');
287
+
288
+ consoleLogSpy.mockRestore();
180
289
  });
181
290
  });
182
291
 
183
- });
292
+ describe('Error Handling', () => {
293
+ it('should handle fs.mkdirSync errors gracefully', async () => {
294
+ plugin = new FriggServerlessPlugin(mockServerless, mockOptions);
295
+
296
+ fs.existsSync.mockReturnValue(false);
297
+ fs.mkdirSync.mockImplementation(() => {
298
+ throw new Error('Permission denied');
299
+ });
300
+
301
+ // Should not throw - error should be caught or allowed to propagate
302
+ await expect(plugin.asyncInit()).rejects.toThrow('Permission denied');
303
+ });
304
+ });
305
+ });
306
+
307
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friggframework/serverless-plugin",
3
- "version": "2.0.0-next.45",
3
+ "version": "2.0.0-next.47",
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": "996a15bcdfaa4252b9891a33f1b1c84548d66bbc"
14
+ "gitHead": "7a7b05dc023f05a6b1c18075064e88f29156fee1"
15
15
  }