@friggframework/devtools 2.0.0-next.47 → 2.0.0-next.48
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/frigg-cli/README.md +1290 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/build-command/index.js +66 -0
- package/frigg-cli/db-setup-command/index.js +193 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +302 -0
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +118 -0
- package/frigg-cli/index.js +173 -0
- package/frigg-cli/index.test.js +158 -0
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/backend-js.js +33 -0
- package/frigg-cli/install-command/commit-changes.js +16 -0
- package/frigg-cli/install-command/environment-variables.js +127 -0
- package/frigg-cli/install-command/environment-variables.test.js +136 -0
- package/frigg-cli/install-command/index.js +54 -0
- package/frigg-cli/install-command/install-package.js +13 -0
- package/frigg-cli/install-command/integration-file.js +30 -0
- package/frigg-cli/install-command/logger.js +12 -0
- package/frigg-cli/install-command/template.js +90 -0
- package/frigg-cli/install-command/validate-package.js +75 -0
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/start-command/index.js +149 -0
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/database-validator.js +154 -0
- package/frigg-cli/utils/error-messages.js +257 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/create-frigg-infrastructure.js +125 -12
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +31 -2
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/infrastructure-composer.js +22 -0
- package/layers/prisma/.build-complete +3 -0
- package/package.json +18 -7
- package/management-ui/package-lock.json +0 -16517
|
@@ -16,6 +16,33 @@ jest.mock('../../../scripts/build-prisma-layer', () => ({
|
|
|
16
16
|
|
|
17
17
|
const { buildPrismaLayer } = require('../../../scripts/build-prisma-layer');
|
|
18
18
|
|
|
19
|
+
// Helper to mock fs methods for different scenarios
|
|
20
|
+
const mockFs = {
|
|
21
|
+
completedBuild: () => {
|
|
22
|
+
fs.existsSync = jest.fn((path) => {
|
|
23
|
+
if (path.endsWith('.build-complete')) return true;
|
|
24
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
25
|
+
return false;
|
|
26
|
+
});
|
|
27
|
+
fs.writeFileSync = jest.fn();
|
|
28
|
+
fs.rmSync = jest.fn();
|
|
29
|
+
},
|
|
30
|
+
incompleteBuild: () => {
|
|
31
|
+
fs.existsSync = jest.fn((path) => {
|
|
32
|
+
if (path.endsWith('.build-complete')) return false;
|
|
33
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
34
|
+
return false;
|
|
35
|
+
});
|
|
36
|
+
fs.writeFileSync = jest.fn();
|
|
37
|
+
fs.rmSync = jest.fn();
|
|
38
|
+
},
|
|
39
|
+
noBuild: () => {
|
|
40
|
+
fs.existsSync = jest.fn().mockReturnValue(false);
|
|
41
|
+
fs.writeFileSync = jest.fn();
|
|
42
|
+
fs.rmSync = jest.fn();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
19
46
|
describe('Prisma Layer Manager', () => {
|
|
20
47
|
let originalCwd;
|
|
21
48
|
|
|
@@ -31,11 +58,15 @@ describe('Prisma Layer Manager', () => {
|
|
|
31
58
|
|
|
32
59
|
describe('ensurePrismaLayerExists()', () => {
|
|
33
60
|
it('should skip build if layer already exists', async () => {
|
|
34
|
-
fs.existsSync = jest.fn(
|
|
61
|
+
fs.existsSync = jest.fn((path) => {
|
|
62
|
+
// Completion marker exists
|
|
63
|
+
if (path.endsWith('.build-complete')) return true;
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
35
66
|
|
|
36
67
|
await ensurePrismaLayerExists();
|
|
37
68
|
|
|
38
|
-
expect(fs.existsSync).toHaveBeenCalledWith('/project/layers/prisma');
|
|
69
|
+
expect(fs.existsSync).toHaveBeenCalledWith('/project/layers/prisma/.build-complete');
|
|
39
70
|
expect(buildPrismaLayer).not.toHaveBeenCalled();
|
|
40
71
|
});
|
|
41
72
|
|
|
@@ -79,11 +110,15 @@ describe('Prisma Layer Manager', () => {
|
|
|
79
110
|
|
|
80
111
|
it('should use correct layer path relative to project root', async () => {
|
|
81
112
|
process.cwd = jest.fn().mockReturnValue('/custom/project/path');
|
|
82
|
-
fs.existsSync = jest.fn(
|
|
113
|
+
fs.existsSync = jest.fn((path) => {
|
|
114
|
+
// Completion marker exists
|
|
115
|
+
if (path.endsWith('.build-complete')) return true;
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
83
118
|
|
|
84
119
|
await ensurePrismaLayerExists();
|
|
85
120
|
|
|
86
|
-
expect(fs.existsSync).toHaveBeenCalledWith('/custom/project/path/layers/prisma');
|
|
121
|
+
expect(fs.existsSync).toHaveBeenCalledWith('/custom/project/path/layers/prisma/.build-complete');
|
|
87
122
|
});
|
|
88
123
|
|
|
89
124
|
it('should log success when layer already exists', async () => {
|
|
@@ -133,6 +168,277 @@ describe('Prisma Layer Manager', () => {
|
|
|
133
168
|
|
|
134
169
|
consoleErrorSpy.mockRestore();
|
|
135
170
|
});
|
|
171
|
+
|
|
172
|
+
describe('Concurrent Build Protection', () => {
|
|
173
|
+
it('should skip build if completion marker exists (TDD)', async () => {
|
|
174
|
+
mockFs.completedBuild();
|
|
175
|
+
|
|
176
|
+
await ensurePrismaLayerExists();
|
|
177
|
+
|
|
178
|
+
expect(fs.existsSync).toHaveBeenCalledWith('/project/layers/prisma/.build-complete');
|
|
179
|
+
expect(buildPrismaLayer).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should wait for active build process to complete (TDD)', async () => {
|
|
183
|
+
jest.useFakeTimers();
|
|
184
|
+
|
|
185
|
+
// Mock active lock file with running process
|
|
186
|
+
const activePid = 12345;
|
|
187
|
+
let completionMarkerExists = false;
|
|
188
|
+
fs.existsSync = jest.fn((path) => {
|
|
189
|
+
if (path.endsWith('.build-complete')) return completionMarkerExists;
|
|
190
|
+
if (path.endsWith('.build-lock')) return true;
|
|
191
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
192
|
+
return false;
|
|
193
|
+
});
|
|
194
|
+
fs.readFileSync = jest.fn().mockReturnValue(activePid.toString());
|
|
195
|
+
fs.writeFileSync = jest.fn();
|
|
196
|
+
fs.rmSync = jest.fn();
|
|
197
|
+
|
|
198
|
+
// Mock process.kill to simulate running process, then completion
|
|
199
|
+
let killCallCount = 0;
|
|
200
|
+
const originalKill = process.kill;
|
|
201
|
+
process.kill = jest.fn((pid, signal) => {
|
|
202
|
+
killCallCount++;
|
|
203
|
+
if (killCallCount >= 3) {
|
|
204
|
+
// After 3 seconds, build completes
|
|
205
|
+
completionMarkerExists = true;
|
|
206
|
+
}
|
|
207
|
+
return true; // Process is running
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const promise = ensurePrismaLayerExists();
|
|
211
|
+
|
|
212
|
+
// Fast-forward through the wait loop
|
|
213
|
+
for (let i = 0; i < 5; i++) {
|
|
214
|
+
jest.advanceTimersByTime(1000);
|
|
215
|
+
await Promise.resolve();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await promise;
|
|
219
|
+
|
|
220
|
+
// Should not rebuild (waited for concurrent build)
|
|
221
|
+
expect(buildPrismaLayer).not.toHaveBeenCalled();
|
|
222
|
+
|
|
223
|
+
process.kill = originalKill;
|
|
224
|
+
jest.useRealTimers();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should clean stale lock file if process not running (TDD)', async () => {
|
|
228
|
+
// Mock stale lock file (process not running)
|
|
229
|
+
fs.existsSync = jest.fn((path) => {
|
|
230
|
+
if (path.endsWith('.build-complete')) return false;
|
|
231
|
+
if (path.endsWith('.build-lock')) return true;
|
|
232
|
+
if (path.endsWith('layers/prisma')) return false;
|
|
233
|
+
return false;
|
|
234
|
+
});
|
|
235
|
+
fs.readFileSync = jest.fn().mockReturnValue('99999');
|
|
236
|
+
fs.writeFileSync = jest.fn();
|
|
237
|
+
fs.mkdirSync = jest.fn();
|
|
238
|
+
fs.rmSync = jest.fn();
|
|
239
|
+
|
|
240
|
+
// Mock process.kill to throw (process not running)
|
|
241
|
+
const originalKill = process.kill;
|
|
242
|
+
process.kill = jest.fn(() => {
|
|
243
|
+
throw new Error('ESRCH');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
buildPrismaLayer.mockResolvedValue();
|
|
247
|
+
|
|
248
|
+
await ensurePrismaLayerExists();
|
|
249
|
+
|
|
250
|
+
// Should remove stale lock
|
|
251
|
+
expect(fs.rmSync).toHaveBeenCalledWith('/project/layers/prisma/.build-lock', { force: true });
|
|
252
|
+
// Should proceed with build
|
|
253
|
+
expect(buildPrismaLayer).toHaveBeenCalled();
|
|
254
|
+
|
|
255
|
+
process.kill = originalKill;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should create and remove lock file during build (TDD)', async () => {
|
|
259
|
+
// Mock to simulate successful build flow
|
|
260
|
+
fs.existsSync = jest.fn((path) => {
|
|
261
|
+
// Completion marker doesn't exist initially
|
|
262
|
+
if (path.endsWith('.build-complete')) return false;
|
|
263
|
+
// Lock file exists in finally block (after writeFileSync)
|
|
264
|
+
if (path.endsWith('.build-lock')) return true;
|
|
265
|
+
// Directory doesn't exist initially
|
|
266
|
+
if (path.endsWith('layers/prisma')) return false;
|
|
267
|
+
return false;
|
|
268
|
+
});
|
|
269
|
+
fs.writeFileSync = jest.fn();
|
|
270
|
+
fs.mkdirSync = jest.fn();
|
|
271
|
+
fs.rmSync = jest.fn();
|
|
272
|
+
buildPrismaLayer.mockResolvedValue();
|
|
273
|
+
|
|
274
|
+
await ensurePrismaLayerExists();
|
|
275
|
+
|
|
276
|
+
// Should create directory for lock file
|
|
277
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith('/project/layers/prisma', { recursive: true });
|
|
278
|
+
|
|
279
|
+
// Should create lock file with PID
|
|
280
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
281
|
+
'/project/layers/prisma/.build-lock',
|
|
282
|
+
expect.any(String)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Should create completion marker
|
|
286
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
287
|
+
'/project/layers/prisma/.build-complete',
|
|
288
|
+
expect.any(String)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Should remove lock file in finally block
|
|
292
|
+
expect(fs.rmSync).toHaveBeenCalledWith(
|
|
293
|
+
'/project/layers/prisma/.build-lock',
|
|
294
|
+
{ force: true }
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should remove lock file even if build fails (TDD)', async () => {
|
|
299
|
+
mockFs.noBuild();
|
|
300
|
+
fs.mkdirSync = jest.fn();
|
|
301
|
+
buildPrismaLayer.mockRejectedValue(new Error('Build failed'));
|
|
302
|
+
|
|
303
|
+
// After failure, directory exists
|
|
304
|
+
fs.existsSync = jest.fn((path) => {
|
|
305
|
+
if (path.endsWith('.build-complete')) return false;
|
|
306
|
+
if (path.endsWith('.build-lock')) return true;
|
|
307
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
308
|
+
return false;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await expect(ensurePrismaLayerExists()).rejects.toThrow('Build failed');
|
|
312
|
+
|
|
313
|
+
// Should remove lock file in finally block
|
|
314
|
+
expect(fs.rmSync).toHaveBeenCalledWith(
|
|
315
|
+
'/project/layers/prisma/.build-lock',
|
|
316
|
+
{ force: true }
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should wait and rebuild if directory exists without completion marker (TDD)', async () => {
|
|
321
|
+
jest.useFakeTimers();
|
|
322
|
+
mockFs.incompleteBuild();
|
|
323
|
+
buildPrismaLayer.mockResolvedValue();
|
|
324
|
+
|
|
325
|
+
const promise = ensurePrismaLayerExists();
|
|
326
|
+
|
|
327
|
+
// Fast-forward through the wait
|
|
328
|
+
jest.advanceTimersByTime(1000);
|
|
329
|
+
|
|
330
|
+
await promise;
|
|
331
|
+
|
|
332
|
+
// Should NOT manually clean (buildPrismaLayer handles this)
|
|
333
|
+
expect(fs.rmSync).not.toHaveBeenCalled();
|
|
334
|
+
// Should rebuild
|
|
335
|
+
expect(buildPrismaLayer).toHaveBeenCalled();
|
|
336
|
+
// Should create completion marker
|
|
337
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
338
|
+
'/project/layers/prisma/.build-complete',
|
|
339
|
+
expect.any(String)
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
jest.useRealTimers();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should create completion marker after successful build (TDD)', async () => {
|
|
346
|
+
mockFs.noBuild();
|
|
347
|
+
buildPrismaLayer.mockResolvedValue();
|
|
348
|
+
|
|
349
|
+
await ensurePrismaLayerExists();
|
|
350
|
+
|
|
351
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
352
|
+
'/project/layers/prisma/.build-complete',
|
|
353
|
+
expect.any(String)
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should clean up partial build on failure (TDD)', async () => {
|
|
358
|
+
mockFs.noBuild();
|
|
359
|
+
buildPrismaLayer.mockRejectedValue(new Error('Build failed'));
|
|
360
|
+
|
|
361
|
+
// After build attempt fails, directory exists
|
|
362
|
+
fs.existsSync = jest.fn((path) => {
|
|
363
|
+
if (path.endsWith('.build-complete')) return false;
|
|
364
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
365
|
+
return false;
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await expect(ensurePrismaLayerExists()).rejects.toThrow('Build failed');
|
|
369
|
+
|
|
370
|
+
// Should clean up after failure
|
|
371
|
+
expect(fs.rmSync).toHaveBeenCalledWith(
|
|
372
|
+
'/project/layers/prisma',
|
|
373
|
+
{ recursive: true, force: true }
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should wait for potential concurrent build before rebuilding (TDD)', async () => {
|
|
378
|
+
jest.useFakeTimers();
|
|
379
|
+
mockFs.incompleteBuild();
|
|
380
|
+
buildPrismaLayer.mockResolvedValue();
|
|
381
|
+
|
|
382
|
+
const promise = ensurePrismaLayerExists();
|
|
383
|
+
|
|
384
|
+
// Fast-forward through the wait
|
|
385
|
+
jest.advanceTimersByTime(1000);
|
|
386
|
+
|
|
387
|
+
await promise;
|
|
388
|
+
|
|
389
|
+
// Should wait before rebuilding
|
|
390
|
+
// Should still attempt to build
|
|
391
|
+
expect(buildPrismaLayer).toHaveBeenCalled();
|
|
392
|
+
|
|
393
|
+
jest.useRealTimers();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should detect when concurrent process completes during wait (TDD)', async () => {
|
|
397
|
+
jest.useFakeTimers();
|
|
398
|
+
|
|
399
|
+
// Simulate lock file existing with completion happening during wait
|
|
400
|
+
let completionMarkerExists = false;
|
|
401
|
+
let lockFileExists = true;
|
|
402
|
+
fs.existsSync = jest.fn((path) => {
|
|
403
|
+
if (path.endsWith('.build-complete')) return completionMarkerExists;
|
|
404
|
+
if (path.endsWith('.build-lock')) return lockFileExists;
|
|
405
|
+
if (path.endsWith('layers/prisma')) return true;
|
|
406
|
+
return false;
|
|
407
|
+
});
|
|
408
|
+
fs.readFileSync = jest.fn().mockReturnValue('12345');
|
|
409
|
+
fs.writeFileSync = jest.fn();
|
|
410
|
+
fs.rmSync = jest.fn();
|
|
411
|
+
|
|
412
|
+
// Mock process.kill to simulate active process
|
|
413
|
+
let killCallCount = 0;
|
|
414
|
+
const originalKill = process.kill;
|
|
415
|
+
process.kill = jest.fn((pid, signal) => {
|
|
416
|
+
killCallCount++;
|
|
417
|
+
if (killCallCount >= 3) {
|
|
418
|
+
// After 3 checks, build completes
|
|
419
|
+
completionMarkerExists = true;
|
|
420
|
+
lockFileExists = false;
|
|
421
|
+
}
|
|
422
|
+
return true; // Process is running
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const promise = ensurePrismaLayerExists();
|
|
426
|
+
|
|
427
|
+
// Fast-forward through the wait loop
|
|
428
|
+
for (let i = 0; i < 5; i++) {
|
|
429
|
+
jest.advanceTimersByTime(1000);
|
|
430
|
+
await Promise.resolve();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
await promise;
|
|
434
|
+
|
|
435
|
+
// Should not rebuild (concurrent process completed)
|
|
436
|
+
expect(buildPrismaLayer).not.toHaveBeenCalled();
|
|
437
|
+
|
|
438
|
+
process.kill = originalKill;
|
|
439
|
+
jest.useRealTimers();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
136
442
|
});
|
|
137
443
|
});
|
|
138
444
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Validator
|
|
3
|
+
*
|
|
4
|
+
* Validation Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Validates serverless plugin configuration to detect conflicts and provide
|
|
7
|
+
* migration guidance for packaging plugins (serverless-esbuild vs serverless-jetpack).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect conflicting packaging plugins in serverless configuration
|
|
12
|
+
*
|
|
13
|
+
* @param {Array<string>} plugins - List of serverless plugins
|
|
14
|
+
* @returns {Object} Validation result with conflict detection
|
|
15
|
+
*/
|
|
16
|
+
function detectConflictingPlugins(plugins = []) {
|
|
17
|
+
const hasEsbuild = plugins.includes('serverless-esbuild');
|
|
18
|
+
const hasJetpack = plugins.includes('serverless-jetpack');
|
|
19
|
+
|
|
20
|
+
const result = {
|
|
21
|
+
hasConflict: false,
|
|
22
|
+
hasEsbuild,
|
|
23
|
+
hasJetpack,
|
|
24
|
+
warnings: [],
|
|
25
|
+
recommendations: [],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Check for explicit conflict - both plugins present
|
|
29
|
+
if (hasEsbuild && hasJetpack) {
|
|
30
|
+
result.hasConflict = true;
|
|
31
|
+
result.warnings.push(
|
|
32
|
+
'Both serverless-esbuild and serverless-jetpack are configured. ' +
|
|
33
|
+
'These plugins have overlapping functionality for Lambda packaging.'
|
|
34
|
+
);
|
|
35
|
+
result.recommendations.push(
|
|
36
|
+
'Remove serverless-jetpack from your serverless.yml plugins array.',
|
|
37
|
+
'The Frigg framework now uses serverless-esbuild as the standard bundling solution.',
|
|
38
|
+
'See docs/reference/aws-sdk-v3-osls-migration.md for migration guidance.'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check for legacy jetpack usage (jetpack without esbuild)
|
|
43
|
+
if (hasJetpack && !hasEsbuild) {
|
|
44
|
+
result.warnings.push(
|
|
45
|
+
'serverless-jetpack is a legacy packaging plugin. ' +
|
|
46
|
+
'Frigg framework now recommends serverless-esbuild for better performance and compatibility.'
|
|
47
|
+
);
|
|
48
|
+
result.recommendations.push(
|
|
49
|
+
'Consider migrating to serverless-esbuild for improved build times and tree-shaking.',
|
|
50
|
+
'Update your serverless.yml to use serverless-esbuild instead of serverless-jetpack.',
|
|
51
|
+
'See docs/reference/aws-sdk-v3-osls-migration.md for migration guidance.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate esbuild is present (standard for Frigg)
|
|
56
|
+
if (!hasEsbuild && !hasJetpack) {
|
|
57
|
+
result.warnings.push(
|
|
58
|
+
'No packaging plugin detected. Serverless will use default packaging which may be slow.'
|
|
59
|
+
);
|
|
60
|
+
result.recommendations.push(
|
|
61
|
+
'Add serverless-esbuild to your serverless.yml plugins array for optimized Lambda bundling.'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate and clean plugin list by removing conflicts
|
|
70
|
+
*
|
|
71
|
+
* Automatically removes serverless-jetpack if serverless-esbuild is present.
|
|
72
|
+
* This provides automatic migration for users with legacy configurations.
|
|
73
|
+
*
|
|
74
|
+
* @param {Array<string>} plugins - List of serverless plugins
|
|
75
|
+
* @param {Object} options - Validation options
|
|
76
|
+
* @param {boolean} options.autoFix - Automatically fix conflicts by removing jetpack (default: true)
|
|
77
|
+
* @param {boolean} options.silent - Suppress console warnings (default: false)
|
|
78
|
+
* @returns {Object} Result with cleaned plugins and validation info
|
|
79
|
+
*/
|
|
80
|
+
function validateAndCleanPlugins(plugins = [], options = {}) {
|
|
81
|
+
const { autoFix = true, silent = false } = options;
|
|
82
|
+
const validation = detectConflictingPlugins(plugins);
|
|
83
|
+
|
|
84
|
+
let cleanedPlugins = [...plugins];
|
|
85
|
+
let modified = false;
|
|
86
|
+
|
|
87
|
+
// Auto-fix: Remove jetpack if esbuild is present
|
|
88
|
+
if (validation.hasConflict && autoFix) {
|
|
89
|
+
cleanedPlugins = plugins.filter(p => p !== 'serverless-jetpack');
|
|
90
|
+
modified = true;
|
|
91
|
+
|
|
92
|
+
if (!silent) {
|
|
93
|
+
console.warn('\n⚠️ Plugin Conflict Detected and Auto-Fixed:');
|
|
94
|
+
console.warn(' Removed serverless-jetpack (using serverless-esbuild instead)');
|
|
95
|
+
console.warn(' The Frigg framework uses serverless-esbuild as the standard bundling solution.\n');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Show warnings for other cases
|
|
100
|
+
if (!silent && validation.warnings.length > 0 && !modified) {
|
|
101
|
+
console.warn('\n⚠️ Plugin Configuration Warning:');
|
|
102
|
+
validation.warnings.forEach(warning => {
|
|
103
|
+
console.warn(` ${warning}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (validation.recommendations.length > 0) {
|
|
107
|
+
console.warn('\n💡 Recommendations:');
|
|
108
|
+
validation.recommendations.forEach(rec => {
|
|
109
|
+
console.warn(` • ${rec}`);
|
|
110
|
+
});
|
|
111
|
+
console.warn('');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
plugins: cleanedPlugins,
|
|
117
|
+
modified,
|
|
118
|
+
validation,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a serverless definition has proper packaging configuration
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} serverlessDefinition - Serverless framework definition
|
|
126
|
+
* @returns {Object} Validation result
|
|
127
|
+
*/
|
|
128
|
+
function validatePackagingConfiguration(serverlessDefinition) {
|
|
129
|
+
const plugins = serverlessDefinition?.plugins || [];
|
|
130
|
+
const custom = serverlessDefinition?.custom || {};
|
|
131
|
+
|
|
132
|
+
const result = {
|
|
133
|
+
valid: true,
|
|
134
|
+
errors: [],
|
|
135
|
+
warnings: [],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const hasEsbuild = plugins.includes('serverless-esbuild');
|
|
139
|
+
const hasEsbuildConfig = custom.esbuild !== undefined;
|
|
140
|
+
|
|
141
|
+
// If esbuild plugin is present, ensure it's configured
|
|
142
|
+
if (hasEsbuild && !hasEsbuildConfig) {
|
|
143
|
+
result.warnings.push(
|
|
144
|
+
'serverless-esbuild plugin is present but custom.esbuild configuration is missing. ' +
|
|
145
|
+
'Using default esbuild settings.'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check for external dependencies in esbuild config
|
|
150
|
+
if (hasEsbuildConfig && hasEsbuild) {
|
|
151
|
+
const external = custom.esbuild.external || [];
|
|
152
|
+
|
|
153
|
+
// Validate that AWS SDK and Prisma are externalized
|
|
154
|
+
const hasAwsSdkExternal = external.some(e =>
|
|
155
|
+
e === '@aws-sdk/*' || e === 'aws-sdk' || e.startsWith('@aws-sdk/')
|
|
156
|
+
);
|
|
157
|
+
const hasPrismaExternal = external.some(e =>
|
|
158
|
+
e === '@prisma/client' || e === 'prisma' || e.startsWith('.prisma')
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (!hasAwsSdkExternal) {
|
|
162
|
+
result.warnings.push(
|
|
163
|
+
'AWS SDK is not externalized in esbuild config. ' +
|
|
164
|
+
'Consider adding "@aws-sdk/*" to external array to reduce bundle size.'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!hasPrismaExternal) {
|
|
169
|
+
result.warnings.push(
|
|
170
|
+
'Prisma is not externalized in esbuild config. ' +
|
|
171
|
+
'Consider adding "@prisma/client" to external array since it\'s provided via Lambda Layer.'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (result.errors.length > 0) {
|
|
177
|
+
result.valid = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
detectConflictingPlugins,
|
|
185
|
+
validateAndCleanPlugins,
|
|
186
|
+
validatePackagingConfiguration,
|
|
187
|
+
};
|