@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.
Files changed (69) hide show
  1. package/frigg-cli/README.md +1290 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  7. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  8. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  9. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
  10. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
  11. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  12. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  13. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  14. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  15. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  16. package/frigg-cli/build-command/index.js +66 -0
  17. package/frigg-cli/db-setup-command/index.js +193 -0
  18. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  19. package/frigg-cli/deploy-command/index.js +302 -0
  20. package/frigg-cli/doctor-command/index.js +335 -0
  21. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  22. package/frigg-cli/generate-command/azure-generator.js +43 -0
  23. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  24. package/frigg-cli/generate-command/index.js +332 -0
  25. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  26. package/frigg-cli/generate-iam-command.js +118 -0
  27. package/frigg-cli/index.js +173 -0
  28. package/frigg-cli/index.test.js +158 -0
  29. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  30. package/frigg-cli/init-command/index.js +93 -0
  31. package/frigg-cli/init-command/template-handler.js +143 -0
  32. package/frigg-cli/install-command/backend-js.js +33 -0
  33. package/frigg-cli/install-command/commit-changes.js +16 -0
  34. package/frigg-cli/install-command/environment-variables.js +127 -0
  35. package/frigg-cli/install-command/environment-variables.test.js +136 -0
  36. package/frigg-cli/install-command/index.js +54 -0
  37. package/frigg-cli/install-command/install-package.js +13 -0
  38. package/frigg-cli/install-command/integration-file.js +30 -0
  39. package/frigg-cli/install-command/logger.js +12 -0
  40. package/frigg-cli/install-command/template.js +90 -0
  41. package/frigg-cli/install-command/validate-package.js +75 -0
  42. package/frigg-cli/jest.config.js +124 -0
  43. package/frigg-cli/package.json +63 -0
  44. package/frigg-cli/repair-command/index.js +564 -0
  45. package/frigg-cli/start-command/index.js +149 -0
  46. package/frigg-cli/start-command/start-command.test.js +297 -0
  47. package/frigg-cli/test/init-command.test.js +180 -0
  48. package/frigg-cli/test/npm-registry.test.js +319 -0
  49. package/frigg-cli/ui-command/index.js +154 -0
  50. package/frigg-cli/utils/app-resolver.js +319 -0
  51. package/frigg-cli/utils/backend-path.js +25 -0
  52. package/frigg-cli/utils/database-validator.js +154 -0
  53. package/frigg-cli/utils/error-messages.js +257 -0
  54. package/frigg-cli/utils/npm-registry.js +167 -0
  55. package/frigg-cli/utils/process-manager.js +199 -0
  56. package/frigg-cli/utils/repo-detection.js +405 -0
  57. package/infrastructure/create-frigg-infrastructure.js +125 -12
  58. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  59. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  60. package/infrastructure/domains/shared/resource-discovery.js +31 -2
  61. package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
  62. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
  63. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
  64. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  65. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  66. package/infrastructure/infrastructure-composer.js +22 -0
  67. package/layers/prisma/.build-complete +3 -0
  68. package/package.json +18 -7
  69. 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().mockReturnValue(true);
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().mockReturnValue(true);
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
+ };