@friggframework/devtools 2.0.0--canary.398.e2147f7.0 → 2.0.0--canary.398.c9e5d61.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.
@@ -0,0 +1,169 @@
1
+ const { generateIAMCloudFormation, getFeatureSummary } = require('./iam-generator');
2
+
3
+ describe('IAM Generator', () => {
4
+ describe('getFeatureSummary', () => {
5
+ it('should detect all features when enabled', () => {
6
+ const appDefinition = {
7
+ name: 'test-app',
8
+ integrations: ['Integration1', 'Integration2'],
9
+ vpc: { enable: true },
10
+ encryption: { useDefaultKMSForFieldLevelEncryption: true },
11
+ ssm: { enable: true },
12
+ websockets: { enable: true }
13
+ };
14
+
15
+ const summary = getFeatureSummary(appDefinition);
16
+
17
+ expect(summary.appName).toBe('test-app');
18
+ expect(summary.integrationCount).toBe(2);
19
+ expect(summary.features.core).toBe(true);
20
+ expect(summary.features.vpc).toBe(true);
21
+ expect(summary.features.kms).toBe(true);
22
+ expect(summary.features.ssm).toBe(true);
23
+ expect(summary.features.websockets).toBe(true);
24
+ });
25
+
26
+ it('should detect minimal features when disabled', () => {
27
+ const appDefinition = {
28
+ integrations: []
29
+ };
30
+
31
+ const summary = getFeatureSummary(appDefinition);
32
+
33
+ expect(summary.appName).toBe('Unnamed Frigg App');
34
+ expect(summary.integrationCount).toBe(0);
35
+ expect(summary.features.core).toBe(true);
36
+ expect(summary.features.vpc).toBe(false);
37
+ expect(summary.features.kms).toBe(false);
38
+ expect(summary.features.ssm).toBe(false);
39
+ expect(summary.features.websockets).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe('generateIAMCloudFormation', () => {
44
+ it('should generate valid CloudFormation YAML', () => {
45
+ const appDefinition = {
46
+ name: 'test-app',
47
+ integrations: [],
48
+ vpc: { enable: false },
49
+ encryption: { useDefaultKMSForFieldLevelEncryption: false },
50
+ ssm: { enable: false },
51
+ websockets: { enable: false }
52
+ };
53
+
54
+ const yaml = generateIAMCloudFormation(appDefinition);
55
+
56
+ expect(yaml).toContain('AWSTemplateFormatVersion');
57
+ expect(yaml).toContain('FriggDeploymentUser');
58
+ expect(yaml).toContain('FriggCoreDeploymentPolicy');
59
+ expect(yaml).toContain('FriggDiscoveryPolicy');
60
+ });
61
+
62
+ it('should include VPC policy when VPC is enabled', () => {
63
+ const appDefinition = {
64
+ name: 'test-app',
65
+ integrations: [],
66
+ vpc: { enable: true }
67
+ };
68
+
69
+ const yaml = generateIAMCloudFormation(appDefinition);
70
+
71
+ expect(yaml).toContain('FriggVPCPolicy');
72
+ expect(yaml).toContain('CreateVPCPermissions');
73
+ expect(yaml).toContain('EnableVPCSupport');
74
+ });
75
+
76
+ it('should include KMS policy when encryption is enabled', () => {
77
+ const appDefinition = {
78
+ name: 'test-app',
79
+ integrations: [],
80
+ encryption: { useDefaultKMSForFieldLevelEncryption: true }
81
+ };
82
+
83
+ const yaml = generateIAMCloudFormation(appDefinition);
84
+
85
+ expect(yaml).toContain('FriggKMSPolicy');
86
+ expect(yaml).toContain('CreateKMSPermissions');
87
+ expect(yaml).toContain('EnableKMSSupport');
88
+ });
89
+
90
+ it('should include SSM policy when SSM is enabled', () => {
91
+ const appDefinition = {
92
+ name: 'test-app',
93
+ integrations: [],
94
+ ssm: { enable: true }
95
+ };
96
+
97
+ const yaml = generateIAMCloudFormation(appDefinition);
98
+
99
+ expect(yaml).toContain('FriggSSMPolicy');
100
+ expect(yaml).toContain('CreateSSMPermissions');
101
+ expect(yaml).toContain('EnableSSMSupport');
102
+ });
103
+
104
+ it('should set correct default parameter values based on features', () => {
105
+ const appDefinition = {
106
+ name: 'test-app',
107
+ integrations: [],
108
+ vpc: { enable: true },
109
+ encryption: { useDefaultKMSForFieldLevelEncryption: false },
110
+ ssm: { enable: true }
111
+ };
112
+
113
+ const yaml = generateIAMCloudFormation(appDefinition);
114
+
115
+ // Check parameter defaults match the enabled features
116
+ expect(yaml).toContain('Default: true'); // VPC enabled
117
+ expect(yaml).toContain('Default: false'); // KMS disabled
118
+ // SSM should be true
119
+ });
120
+
121
+ it('should include all core permissions', () => {
122
+ const appDefinition = {
123
+ name: 'test-app',
124
+ integrations: []
125
+ };
126
+
127
+ const yaml = generateIAMCloudFormation(appDefinition);
128
+
129
+ // Check for core permissions
130
+ expect(yaml).toContain('cloudformation:CreateStack');
131
+ expect(yaml).toContain('cloudformation:ListStackResources');
132
+ expect(yaml).toContain('lambda:CreateFunction');
133
+ expect(yaml).toContain('iam:CreateRole');
134
+ expect(yaml).toContain('s3:CreateBucket');
135
+ expect(yaml).toContain('sqs:CreateQueue');
136
+ expect(yaml).toContain('sns:CreateTopic');
137
+ expect(yaml).toContain('logs:CreateLogGroup');
138
+ expect(yaml).toContain('apigateway:POST');
139
+ expect(yaml).toContain('lambda:ListVersionsByFunction');
140
+ expect(yaml).toContain('iam:ListPolicyVersions');
141
+ });
142
+
143
+ it('should include internal-error-queue pattern in SQS resources', () => {
144
+ const appDefinition = {
145
+ name: 'test-app',
146
+ integrations: []
147
+ };
148
+
149
+ const yaml = generateIAMCloudFormation(appDefinition);
150
+
151
+ expect(yaml).toContain('internal-error-queue-*');
152
+ });
153
+
154
+ it('should generate outputs section', () => {
155
+ const appDefinition = {
156
+ name: 'test-app',
157
+ integrations: []
158
+ };
159
+
160
+ const yaml = generateIAMCloudFormation(appDefinition);
161
+
162
+ expect(yaml).toContain('Outputs:');
163
+ expect(yaml).toContain('DeploymentUserArn:');
164
+ expect(yaml).toContain('AccessKeyId:');
165
+ expect(yaml).toContain('SecretAccessKeyCommand:');
166
+ expect(yaml).toContain('CredentialsSecretArn:');
167
+ });
168
+ });
169
+ });
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-build script to run AWS discovery and set environment variables
5
+ * This should be run before serverless commands that need AWS resource discovery
6
+ */
7
+
8
+ const { BuildTimeDiscovery } = require('./build-time-discovery');
9
+ const { findNearestBackendPackageJson } = require('@friggframework/core');
10
+ const path = require('path');
11
+
12
+ async function runDiscovery() {
13
+ try {
14
+ console.log('🔍 Starting AWS resource discovery...');
15
+
16
+ // Find the backend package.json to get AppDefinition
17
+ const backendPath = findNearestBackendPackageJson();
18
+ if (!backendPath) {
19
+ console.log('⚠️ No backend package.json found, skipping discovery');
20
+ return;
21
+ }
22
+
23
+ const backendDir = path.dirname(backendPath);
24
+ const backendFilePath = path.join(backendDir, 'index.js');
25
+
26
+ if (!require('fs').existsSync(backendFilePath)) {
27
+ console.log('⚠️ No backend/index.js found, skipping discovery');
28
+ return;
29
+ }
30
+
31
+ // Load the app definition
32
+ const backend = require(backendFilePath);
33
+ const appDefinition = backend.Definition;
34
+
35
+ if (!appDefinition) {
36
+ console.log('⚠️ No Definition found in backend/index.js, skipping discovery');
37
+ return;
38
+ }
39
+
40
+ // Check if discovery is needed
41
+ const needsDiscovery = appDefinition.vpc?.enable ||
42
+ appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
43
+ appDefinition.ssm?.enable;
44
+
45
+ if (!needsDiscovery) {
46
+ console.log('ℹ️ No AWS discovery needed based on app definition');
47
+ return;
48
+ }
49
+
50
+ console.log('📋 App requires AWS discovery for:');
51
+ if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
52
+ if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.log(' ✅ KMS encryption');
53
+ if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
54
+
55
+ // Run discovery
56
+ const discovery = new BuildTimeDiscovery();
57
+ const resources = await discovery.preBuildHook(appDefinition, process.env.AWS_REGION || 'us-east-1');
58
+
59
+ if (resources) {
60
+ console.log('✅ AWS discovery completed successfully!');
61
+ console.log(` VPC: ${resources.defaultVpcId}`);
62
+ console.log(` Subnets: ${resources.privateSubnetId1}, ${resources.privateSubnetId2}`);
63
+ console.log(` Public Subnet: ${resources.publicSubnetId}`);
64
+ console.log(` Security Group: ${resources.defaultSecurityGroupId}`);
65
+ console.log(` Route Table: ${resources.privateRouteTableId}`);
66
+ console.log(` KMS Key: ${resources.defaultKmsKeyId}`);
67
+ }
68
+
69
+ } catch (error) {
70
+ console.error('❌ AWS discovery failed:', error.message);
71
+ console.error('');
72
+
73
+ // Check if this is an AWS SDK missing error
74
+ if (error.message.includes('Cannot find module') && error.message.includes('@aws-sdk')) {
75
+ console.error('🚨 AWS SDK not installed!');
76
+ console.error('');
77
+ console.error('💡 Install AWS SDK dependencies:');
78
+ console.error(' npm install @aws-sdk/client-ec2 @aws-sdk/client-kms @aws-sdk/client-sts');
79
+ console.error('');
80
+ } else {
81
+ console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
82
+ if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
83
+ if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
84
+ if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
85
+ console.error('');
86
+ console.error('💡 To fix this issue:');
87
+ console.error(' 1. Check AWS credentials: aws sts get-caller-identity');
88
+ console.error(' 2. Verify IAM permissions (see AWS-IAM-CREDENTIAL-NEEDS.md)');
89
+ console.error(' 3. Ensure default VPC exists: aws ec2 describe-vpcs');
90
+ console.error(' 4. Check AWS region: aws configure get region');
91
+ console.error('');
92
+ }
93
+
94
+ console.error('🔧 Or disable features in backend/index.js:');
95
+ console.error(' vpc: { enable: false }');
96
+ console.error(' encryption: { useDefaultKMSForFieldLevelEncryption: false }');
97
+ console.error(' ssm: { enable: false }');
98
+
99
+ process.exit(1);
100
+ }
101
+ }
102
+
103
+ // Run if called directly
104
+ if (require.main === module) {
105
+ runDiscovery();
106
+ }
107
+
108
+ module.exports = { runDiscovery };
@@ -434,6 +434,8 @@ const createVPCInfrastructure = (AppDefinition) => {
434
434
  * @param {Object} [AppDefinition.vpc] - VPC configuration
435
435
  * @param {Object} [AppDefinition.encryption] - KMS encryption configuration
436
436
  * @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
437
+ * @param {Object} [AppDefinition.websockets] - WebSocket configuration
438
+ * @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
437
439
  * @returns {Object} Complete serverless framework configuration
438
440
  */
439
441
  const composeServerlessDefinition = (AppDefinition) => {
@@ -514,26 +516,6 @@ const composeServerlessDefinition = (AppDefinition) => {
514
516
  },
515
517
  },
516
518
  functions: {
517
- defaultWebsocket: {
518
- handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
519
- events: [
520
- {
521
- websocket: {
522
- route: '$connect',
523
- },
524
- },
525
- {
526
- websocket: {
527
- route: '$default',
528
- },
529
- },
530
- {
531
- websocket: {
532
- route: '$disconnect',
533
- },
534
- },
535
- ],
536
- },
537
519
  auth: {
538
520
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
539
521
  events: [
@@ -923,6 +905,51 @@ const composeServerlessDefinition = (AppDefinition) => {
923
905
  definition.custom[queueReference] = queueName;
924
906
  }
925
907
 
908
+ // Check if AWS discovery environment variables are missing and error
909
+ const needsDiscovery = AppDefinition.vpc?.enable ||
910
+ AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
911
+ AppDefinition.ssm?.enable;
912
+
913
+ if (needsDiscovery && !process.env.AWS_DISCOVERY_VPC_ID) {
914
+ console.error('❌ AWS discovery environment variables not found');
915
+ console.error('');
916
+ console.error('🚨 Your AppDefinition requires AWS discovery but variables are missing:');
917
+ if (AppDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
918
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
919
+ if (AppDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
920
+ console.error('');
921
+ console.error('💡 Run AWS discovery before building:');
922
+ console.error(' node node_modules/@friggframework/devtools/infrastructure/run-discovery.js');
923
+ console.error('');
924
+ console.error('🔧 Or use the build command which runs discovery automatically:');
925
+ console.error(' npx frigg build');
926
+ throw new Error('AWS discovery required but environment variables not found');
927
+ }
928
+
929
+ // Add websocket function if enabled
930
+ if (AppDefinition.websockets?.enable === true) {
931
+ definition.functions.defaultWebsocket = {
932
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
933
+ events: [
934
+ {
935
+ websocket: {
936
+ route: '$connect',
937
+ },
938
+ },
939
+ {
940
+ websocket: {
941
+ route: '$default',
942
+ },
943
+ },
944
+ {
945
+ websocket: {
946
+ route: '$disconnect',
947
+ },
948
+ },
949
+ ],
950
+ };
951
+ }
952
+
926
953
  // Modify handler paths to point to the correct node_modules location
927
954
  definition.functions = modifyHandlerPaths(definition.functions);
928
955
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.398.e2147f7.0",
4
+ "version": "2.0.0--canary.398.c9e5d61.0",
5
5
  "dependencies": {
6
6
  "@babel/eslint-parser": "^7.18.9",
7
7
  "@babel/parser": "^7.25.3",
8
8
  "@babel/traverse": "^7.25.3",
9
- "@friggframework/test": "2.0.0--canary.398.e2147f7.0",
9
+ "@friggframework/test": "2.0.0--canary.398.c9e5d61.0",
10
10
  "@hapi/boom": "^10.0.1",
11
11
  "@inquirer/prompts": "^5.3.8",
12
12
  "axios": "^1.7.2",
@@ -23,12 +23,13 @@
23
23
  "express": "^4.19.2",
24
24
  "express-async-handler": "^1.2.0",
25
25
  "fs-extra": "^11.2.0",
26
+ "js-yaml": "^4.1.0",
26
27
  "lodash": "4.17.21",
27
28
  "serverless-http": "^2.7.0"
28
29
  },
29
30
  "devDependencies": {
30
- "@friggframework/eslint-config": "2.0.0--canary.398.e2147f7.0",
31
- "@friggframework/prettier-config": "2.0.0--canary.398.e2147f7.0",
31
+ "@friggframework/eslint-config": "2.0.0--canary.398.c9e5d61.0",
32
+ "@friggframework/prettier-config": "2.0.0--canary.398.c9e5d61.0",
32
33
  "prettier": "^2.7.1",
33
34
  "serverless": "3.39.0",
34
35
  "serverless-dotenv-plugin": "^6.0.0",
@@ -60,5 +61,5 @@
60
61
  "publishConfig": {
61
62
  "access": "public"
62
63
  },
63
- "gitHead": "e2147f7f03122a5daa917429ba610c1a7c6ef819"
64
+ "gitHead": "c9e5d614b0abcc3a1066ffa87da17ada71dc8f9d"
64
65
  }