@friggframework/devtools 2.0.0--canary.461.d2e26d0.0 → 2.0.0--canary.461.9667b72.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.
- package/infrastructure/domains/database/aurora-builder.js +16 -3
- package/infrastructure/domains/database/aurora-builder.test.js +53 -2
- package/infrastructure/domains/integration/integration-builder.js +30 -27
- package/infrastructure/domains/integration/integration-builder.test.js +231 -0
- package/infrastructure/domains/networking/vpc-builder.js +17 -5
- package/infrastructure/domains/networking/vpc-builder.test.js +52 -4
- package/package.json +6 -6
|
@@ -99,10 +99,23 @@ class AuroraBuilder extends InfrastructureBuilder {
|
|
|
99
99
|
// Clear granular option to prevent conflicts
|
|
100
100
|
delete appDefinition.database.postgres.management;
|
|
101
101
|
|
|
102
|
-
// Set management based on isolation strategy
|
|
102
|
+
// Set management based on isolation strategy AND existing stack resources
|
|
103
103
|
if (vpcIsolation === 'isolated') {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
// Check if CloudFormation stack already has Aurora (stage-specific)
|
|
105
|
+
// CloudFormation discovery sets 'auroraClusterId' (string) when found in stack
|
|
106
|
+
const hasStackAurora = discoveredResources?.auroraClusterId &&
|
|
107
|
+
typeof discoveredResources.auroraClusterId === 'string';
|
|
108
|
+
|
|
109
|
+
if (hasStackAurora) {
|
|
110
|
+
// Stack has Aurora - reuse it (standard flow: stack → orphaned → create)
|
|
111
|
+
management = 'discover';
|
|
112
|
+
appDefinition.database.postgres.autoCreateCredentials = true;
|
|
113
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has Aurora, reusing`);
|
|
114
|
+
} else {
|
|
115
|
+
// No stack Aurora - create new isolated Aurora for this stage
|
|
116
|
+
management = 'managed';
|
|
117
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack Aurora, creating new`);
|
|
118
|
+
}
|
|
106
119
|
} else {
|
|
107
120
|
management = 'discover'; // Shared VPC = reuse Aurora
|
|
108
121
|
appDefinition.database.postgres.autoCreateCredentials = true;
|
|
@@ -750,7 +750,52 @@ describe('AuroraBuilder', () => {
|
|
|
750
750
|
});
|
|
751
751
|
|
|
752
752
|
describe('Top-Level Management Mode', () => {
|
|
753
|
-
it('should
|
|
753
|
+
it('should reuse stack Aurora when managementMode=managed + vpcIsolation=isolated AND stack has Aurora', async () => {
|
|
754
|
+
const appDefinition = {
|
|
755
|
+
managementMode: 'managed',
|
|
756
|
+
vpcIsolation: 'isolated',
|
|
757
|
+
database: {
|
|
758
|
+
postgres: {
|
|
759
|
+
enable: true,
|
|
760
|
+
management: 'managed', // Should be IGNORED
|
|
761
|
+
minCapacity: 0.5,
|
|
762
|
+
maxCapacity: 1,
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// CloudFormation stack has Aurora (from previous deployment of this stage)
|
|
768
|
+
const discoveredResources = {
|
|
769
|
+
auroraClusterId: 'stack-cluster-dev', // CloudFormation discovery sets this
|
|
770
|
+
auroraClusterEndpoint: 'stack-cluster-dev.us-east-1.rds.amazonaws.com', // For discover mode
|
|
771
|
+
auroraClusterPort: 5432,
|
|
772
|
+
auroraClusterIdentifier: 'stack-cluster-dev',
|
|
773
|
+
privateSubnetId1: 'subnet-1',
|
|
774
|
+
privateSubnetId2: 'subnet-2',
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
778
|
+
|
|
779
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
780
|
+
|
|
781
|
+
// Should warn about ignored options
|
|
782
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
783
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// Should log reusing stack Aurora
|
|
787
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
788
|
+
expect.stringContaining("stack has Aurora, reusing")
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
// Should REUSE stack Aurora (not create new)
|
|
792
|
+
expect(result.resources.FriggAuroraCluster).toBeUndefined();
|
|
793
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
794
|
+
|
|
795
|
+
consoleLogSpy.mockRestore();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it('should create new Aurora when managementMode=managed + vpcIsolation=isolated AND stack has NO Aurora', async () => {
|
|
754
799
|
const appDefinition = {
|
|
755
800
|
managementMode: 'managed',
|
|
756
801
|
vpcIsolation: 'isolated',
|
|
@@ -764,10 +809,11 @@ describe('AuroraBuilder', () => {
|
|
|
764
809
|
},
|
|
765
810
|
};
|
|
766
811
|
|
|
812
|
+
// No Aurora in CloudFormation stack (fresh deployment)
|
|
767
813
|
const discoveredResources = {
|
|
768
|
-
auroraClusterEndpoint: 'existing-cluster.us-east-1.rds.amazonaws.com',
|
|
769
814
|
privateSubnetId1: 'subnet-1',
|
|
770
815
|
privateSubnetId2: 'subnet-2',
|
|
816
|
+
// No auroraEndpoint
|
|
771
817
|
};
|
|
772
818
|
|
|
773
819
|
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
@@ -779,6 +825,11 @@ describe('AuroraBuilder', () => {
|
|
|
779
825
|
expect.stringContaining("managementMode='managed' ignoring")
|
|
780
826
|
);
|
|
781
827
|
|
|
828
|
+
// Should log creating new Aurora
|
|
829
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
830
|
+
expect.stringContaining("no stack Aurora, creating new")
|
|
831
|
+
);
|
|
832
|
+
|
|
782
833
|
// Should create new Aurora cluster (isolated mode)
|
|
783
834
|
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
784
835
|
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
@@ -126,7 +126,36 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
126
126
|
|
|
127
127
|
console.log(` Adding integration: ${integrationName}`);
|
|
128
128
|
|
|
129
|
-
//
|
|
129
|
+
// Add webhook handler if enabled (BEFORE catch-all proxy route)
|
|
130
|
+
// CRITICAL: Webhook routes must be defined before the catch-all {proxy+} route
|
|
131
|
+
// to ensure proper route matching in AWS API Gateway/HTTP API
|
|
132
|
+
const webhookConfig = integration.Definition.webhooks;
|
|
133
|
+
if (webhookConfig && (webhookConfig === true || webhookConfig.enabled === true)) {
|
|
134
|
+
const webhookFunctionName = `${integrationName}Webhook`;
|
|
135
|
+
|
|
136
|
+
result.functions[webhookFunctionName] = {
|
|
137
|
+
handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
|
|
138
|
+
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
139
|
+
package: functionPackageConfig,
|
|
140
|
+
events: [
|
|
141
|
+
{
|
|
142
|
+
httpApi: {
|
|
143
|
+
path: `/api/${integrationName}-integration/webhooks`,
|
|
144
|
+
method: 'POST',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
httpApi: {
|
|
149
|
+
path: `/api/${integrationName}-integration/webhooks/{integrationId}`,
|
|
150
|
+
method: 'POST',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
console.log(` + Webhook handler enabled`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Create HTTP API handler for integration (catch-all route AFTER webhooks)
|
|
130
159
|
result.functions[integrationName] = {
|
|
131
160
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
132
161
|
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
@@ -181,32 +210,6 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
181
210
|
};
|
|
182
211
|
|
|
183
212
|
result.custom[queueReference] = queueName;
|
|
184
|
-
|
|
185
|
-
// Add webhook handler if enabled
|
|
186
|
-
const webhookConfig = integration.Definition.webhooks;
|
|
187
|
-
if (webhookConfig && (webhookConfig === true || webhookConfig.enabled === true)) {
|
|
188
|
-
const webhookFunctionName = `${integrationName}Webhook`;
|
|
189
|
-
|
|
190
|
-
result.functions[webhookFunctionName] = {
|
|
191
|
-
handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
|
|
192
|
-
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
193
|
-
events: [
|
|
194
|
-
{
|
|
195
|
-
httpApi: {
|
|
196
|
-
path: `/api/${integrationName}-integration/webhooks`,
|
|
197
|
-
method: 'POST',
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
httpApi: {
|
|
202
|
-
path: `/api/${integrationName}-integration/webhooks/{integrationId}`,
|
|
203
|
-
method: 'POST',
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
console.log(` + Webhook handler enabled`);
|
|
209
|
-
}
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
console.log(` ✅ Configured ${appDefinition.integrations.length} integrations`);
|
|
@@ -358,5 +358,236 @@ describe('IntegrationBuilder', () => {
|
|
|
358
358
|
expect(integrationBuilder.getName()).toBe('IntegrationBuilder');
|
|
359
359
|
});
|
|
360
360
|
});
|
|
361
|
+
|
|
362
|
+
describe('Webhook Handler Configuration', () => {
|
|
363
|
+
it('should create webhook handler when webhooks enabled with boolean true', async () => {
|
|
364
|
+
const appDefinition = {
|
|
365
|
+
integrations: [
|
|
366
|
+
{
|
|
367
|
+
Definition: {
|
|
368
|
+
name: 'hubspot',
|
|
369
|
+
webhooks: true,
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
376
|
+
|
|
377
|
+
expect(result.functions.hubspotWebhook).toBeDefined();
|
|
378
|
+
expect(result.functions.hubspotWebhook.handler).toBe(
|
|
379
|
+
'node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.hubspotWebhook.handler'
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should create webhook handler when webhooks enabled with object', async () => {
|
|
384
|
+
const appDefinition = {
|
|
385
|
+
integrations: [
|
|
386
|
+
{
|
|
387
|
+
Definition: {
|
|
388
|
+
name: 'salesforce',
|
|
389
|
+
webhooks: { enabled: true },
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
396
|
+
|
|
397
|
+
expect(result.functions.salesforceWebhook).toBeDefined();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should NOT create webhook handler when webhooks disabled', async () => {
|
|
401
|
+
const appDefinition = {
|
|
402
|
+
integrations: [
|
|
403
|
+
{
|
|
404
|
+
Definition: {
|
|
405
|
+
name: 'slack',
|
|
406
|
+
webhooks: false,
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
413
|
+
|
|
414
|
+
expect(result.functions.slackWebhook).toBeUndefined();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should NOT create webhook handler when webhooks explicitly disabled in object', async () => {
|
|
418
|
+
const appDefinition = {
|
|
419
|
+
integrations: [
|
|
420
|
+
{
|
|
421
|
+
Definition: {
|
|
422
|
+
name: 'test',
|
|
423
|
+
webhooks: { enabled: false },
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
430
|
+
|
|
431
|
+
expect(result.functions.testWebhook).toBeUndefined();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should configure webhook with both base and ID-specific routes', async () => {
|
|
435
|
+
const appDefinition = {
|
|
436
|
+
integrations: [
|
|
437
|
+
{
|
|
438
|
+
Definition: {
|
|
439
|
+
name: 'stripe',
|
|
440
|
+
webhooks: true,
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
447
|
+
|
|
448
|
+
expect(result.functions.stripeWebhook.events).toEqual([
|
|
449
|
+
{
|
|
450
|
+
httpApi: {
|
|
451
|
+
path: '/api/stripe-integration/webhooks',
|
|
452
|
+
method: 'POST',
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
httpApi: {
|
|
457
|
+
path: '/api/stripe-integration/webhooks/{integrationId}',
|
|
458
|
+
method: 'POST',
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
]);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should define webhook handler BEFORE catch-all proxy route (ordering bug fix)', async () => {
|
|
465
|
+
const appDefinition = {
|
|
466
|
+
integrations: [
|
|
467
|
+
{
|
|
468
|
+
Definition: {
|
|
469
|
+
name: 'asana',
|
|
470
|
+
webhooks: true,
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
477
|
+
|
|
478
|
+
// Get the keys (function names) in the order they were added
|
|
479
|
+
const functionKeys = Object.keys(result.functions);
|
|
480
|
+
|
|
481
|
+
// Webhook handler should be defined before the main integration handler
|
|
482
|
+
const webhookIndex = functionKeys.indexOf('asanaWebhook');
|
|
483
|
+
const integrationIndex = functionKeys.indexOf('asana');
|
|
484
|
+
|
|
485
|
+
expect(webhookIndex).toBeGreaterThanOrEqual(0);
|
|
486
|
+
expect(integrationIndex).toBeGreaterThan(0);
|
|
487
|
+
expect(webhookIndex).toBeLessThan(integrationIndex);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should maintain correct function order: webhook, integration, queue worker', async () => {
|
|
491
|
+
const appDefinition = {
|
|
492
|
+
integrations: [
|
|
493
|
+
{
|
|
494
|
+
Definition: {
|
|
495
|
+
name: 'test',
|
|
496
|
+
webhooks: true,
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
503
|
+
|
|
504
|
+
const functionKeys = Object.keys(result.functions);
|
|
505
|
+
|
|
506
|
+
// Expected order: webhook, integration, queueWorker
|
|
507
|
+
expect(functionKeys).toEqual([
|
|
508
|
+
'testWebhook',
|
|
509
|
+
'test',
|
|
510
|
+
'testQueueWorker',
|
|
511
|
+
]);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should handle multiple integrations with mixed webhook configurations', async () => {
|
|
515
|
+
const appDefinition = {
|
|
516
|
+
integrations: [
|
|
517
|
+
{
|
|
518
|
+
Definition: {
|
|
519
|
+
name: 'hubspot',
|
|
520
|
+
webhooks: true,
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
Definition: {
|
|
525
|
+
name: 'salesforce',
|
|
526
|
+
webhooks: false,
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
Definition: {
|
|
531
|
+
name: 'slack',
|
|
532
|
+
webhooks: { enabled: true },
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
539
|
+
|
|
540
|
+
// Hubspot: webhook enabled
|
|
541
|
+
expect(result.functions.hubspotWebhook).toBeDefined();
|
|
542
|
+
expect(result.functions.hubspot).toBeDefined();
|
|
543
|
+
expect(result.functions.hubspotQueueWorker).toBeDefined();
|
|
544
|
+
|
|
545
|
+
// Salesforce: webhook disabled
|
|
546
|
+
expect(result.functions.salesforceWebhook).toBeUndefined();
|
|
547
|
+
expect(result.functions.salesforce).toBeDefined();
|
|
548
|
+
expect(result.functions.salesforceQueueWorker).toBeDefined();
|
|
549
|
+
|
|
550
|
+
// Slack: webhook enabled via object
|
|
551
|
+
expect(result.functions.slackWebhook).toBeDefined();
|
|
552
|
+
expect(result.functions.slack).toBeDefined();
|
|
553
|
+
expect(result.functions.slackQueueWorker).toBeDefined();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('should use skipEsbuild for webhook handlers', async () => {
|
|
557
|
+
const appDefinition = {
|
|
558
|
+
integrations: [
|
|
559
|
+
{
|
|
560
|
+
Definition: {
|
|
561
|
+
name: 'test',
|
|
562
|
+
webhooks: true,
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
569
|
+
|
|
570
|
+
expect(result.functions.testWebhook.skipEsbuild).toBe(true);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should apply package configuration to webhook handlers', async () => {
|
|
574
|
+
const appDefinition = {
|
|
575
|
+
integrations: [
|
|
576
|
+
{
|
|
577
|
+
Definition: {
|
|
578
|
+
name: 'test',
|
|
579
|
+
webhooks: true,
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const result = await integrationBuilder.build(appDefinition, {});
|
|
586
|
+
|
|
587
|
+
expect(result.functions.testWebhook.package).toBeDefined();
|
|
588
|
+
expect(result.functions.testWebhook.package.exclude).toContain('node_modules/aws-sdk/**');
|
|
589
|
+
expect(result.functions.testWebhook.package.exclude).toContain('node_modules/@prisma/**');
|
|
590
|
+
});
|
|
591
|
+
});
|
|
361
592
|
});
|
|
362
593
|
|
|
@@ -141,12 +141,24 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
141
141
|
if (appDefinition.vpc.natGateway) delete appDefinition.vpc.natGateway.management;
|
|
142
142
|
delete appDefinition.vpc.shareAcrossStages;
|
|
143
143
|
|
|
144
|
-
// Set management based on isolation strategy
|
|
144
|
+
// Set management based on isolation strategy AND existing stack resources
|
|
145
145
|
if (vpcIsolation === 'isolated') {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
// Check if CloudFormation stack already has a VPC (stage-specific)
|
|
147
|
+
// CloudFormation discovery sets 'defaultVpcId' (string) when found in stack
|
|
148
|
+
const hasStackVpc = discoveredResources?.defaultVpcId && typeof discoveredResources.defaultVpcId === 'string';
|
|
149
|
+
|
|
150
|
+
if (hasStackVpc) {
|
|
151
|
+
// Stack has VPC - reuse it (standard flow: stack → orphaned → create)
|
|
152
|
+
management = 'discover';
|
|
153
|
+
appDefinition.vpc.selfHeal = true;
|
|
154
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has VPC, reusing`);
|
|
155
|
+
} else {
|
|
156
|
+
// No stack VPC - create new isolated VPC for this stage
|
|
157
|
+
management = 'create-new';
|
|
158
|
+
appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
|
|
159
|
+
appDefinition.vpc.natGateway.management = 'createAndManage';
|
|
160
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack VPC, creating new`);
|
|
161
|
+
}
|
|
150
162
|
} else {
|
|
151
163
|
management = 'discover';
|
|
152
164
|
appDefinition.vpc.selfHeal = true;
|
|
@@ -837,7 +837,53 @@ describe('VpcBuilder', () => {
|
|
|
837
837
|
});
|
|
838
838
|
|
|
839
839
|
describe('Management Mode (Simplified API)', () => {
|
|
840
|
-
it('should
|
|
840
|
+
it('should reuse stack VPC when managementMode=managed + vpcIsolation=isolated AND stack has VPC', async () => {
|
|
841
|
+
const appDefinition = {
|
|
842
|
+
managementMode: 'managed',
|
|
843
|
+
vpcIsolation: 'isolated',
|
|
844
|
+
vpc: {
|
|
845
|
+
enable: true,
|
|
846
|
+
management: 'create-new', // Should be IGNORED
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// CloudFormation stack has VPC (from previous deployment of this stage)
|
|
851
|
+
const discoveredResources = {
|
|
852
|
+
defaultVpcId: 'vpc-stack-dev', // CloudFormation discovery sets this
|
|
853
|
+
privateSubnetId1: 'subnet-private-1',
|
|
854
|
+
privateSubnetId2: 'subnet-private-2',
|
|
855
|
+
publicSubnetId1: 'subnet-public-1',
|
|
856
|
+
publicSubnetId2: 'subnet-public-2',
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
860
|
+
|
|
861
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
862
|
+
|
|
863
|
+
// Should warn about ignored options
|
|
864
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
865
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
// Should log reusing stack VPC
|
|
869
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
870
|
+
expect.stringContaining("stack has VPC, reusing")
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
// Should REUSE stack VPC (not create new)
|
|
874
|
+
expect(result.vpcId).toBe('vpc-stack-dev');
|
|
875
|
+
expect(result.resources.FriggVPC).toBeUndefined();
|
|
876
|
+
|
|
877
|
+
// Should REUSE stack subnets
|
|
878
|
+
expect(result.vpcConfig.subnetIds).toEqual([
|
|
879
|
+
'subnet-private-1',
|
|
880
|
+
'subnet-private-2'
|
|
881
|
+
]);
|
|
882
|
+
|
|
883
|
+
consoleLogSpy.mockRestore();
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it('should create new VPC when managementMode=managed + vpcIsolation=isolated AND stack has NO VPC', async () => {
|
|
841
887
|
const appDefinition = {
|
|
842
888
|
managementMode: 'managed',
|
|
843
889
|
vpcIsolation: 'isolated',
|
|
@@ -847,9 +893,9 @@ describe('VpcBuilder', () => {
|
|
|
847
893
|
},
|
|
848
894
|
};
|
|
849
895
|
|
|
896
|
+
// No VPC in CloudFormation stack (fresh deployment)
|
|
850
897
|
const discoveredResources = {
|
|
851
|
-
defaultVpcId: 'vpc-
|
|
852
|
-
natGatewayId: 'nat-existing',
|
|
898
|
+
defaultVpcId: 'vpc-default', // Only default VPC exists (not from stack)
|
|
853
899
|
};
|
|
854
900
|
|
|
855
901
|
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
@@ -860,8 +906,10 @@ describe('VpcBuilder', () => {
|
|
|
860
906
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
861
907
|
expect.stringContaining("managementMode='managed' ignoring")
|
|
862
908
|
);
|
|
909
|
+
|
|
910
|
+
// Should log creating new VPC
|
|
863
911
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
864
|
-
expect.stringContaining("
|
|
912
|
+
expect.stringContaining("no stack VPC, creating new")
|
|
865
913
|
);
|
|
866
914
|
|
|
867
915
|
// Should create new isolated VPC
|
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.461.
|
|
4
|
+
"version": "2.0.0--canary.461.9667b72.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@babel/eslint-parser": "^7.18.9",
|
|
12
12
|
"@babel/parser": "^7.25.3",
|
|
13
13
|
"@babel/traverse": "^7.25.3",
|
|
14
|
-
"@friggframework/schemas": "2.0.0--canary.461.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.461.9667b72.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.9667b72.0",
|
|
16
16
|
"@hapi/boom": "^10.0.1",
|
|
17
17
|
"@inquirer/prompts": "^5.3.8",
|
|
18
18
|
"axios": "^1.7.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"serverless-http": "^2.7.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@friggframework/eslint-config": "2.0.0--canary.461.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.9667b72.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.9667b72.0",
|
|
39
39
|
"aws-sdk-client-mock": "^4.1.0",
|
|
40
40
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
41
41
|
"jest": "^30.1.3",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "9667b7204b9faaeb6caf8d1a94841ab365f9b7a3"
|
|
74
74
|
}
|