@friggframework/devtools 2.0.0-next.40 → 2.0.0-next.42

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 (34) hide show
  1. package/frigg-cli/__tests__/unit/commands/build.test.js +173 -405
  2. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
  3. package/frigg-cli/__tests__/unit/commands/install.test.js +359 -377
  4. package/frigg-cli/__tests__/unit/commands/ui.test.js +266 -512
  5. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
  6. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
  7. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +486 -0
  8. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +22 -21
  10. package/frigg-cli/db-setup-command/index.js +186 -0
  11. package/frigg-cli/generate-command/__tests__/generate-command.test.js +151 -162
  12. package/frigg-cli/generate-iam-command.js +7 -4
  13. package/frigg-cli/index.js +9 -1
  14. package/frigg-cli/install-command/index.js +1 -1
  15. package/frigg-cli/jest.config.js +124 -0
  16. package/frigg-cli/package.json +4 -1
  17. package/frigg-cli/start-command/index.js +101 -2
  18. package/frigg-cli/start-command/start-command.test.js +297 -0
  19. package/frigg-cli/utils/database-validator.js +158 -0
  20. package/frigg-cli/utils/error-messages.js +257 -0
  21. package/frigg-cli/utils/prisma-runner.js +280 -0
  22. package/infrastructure/CLAUDE.md +481 -0
  23. package/infrastructure/IAM-POLICY-TEMPLATES.md +30 -12
  24. package/infrastructure/create-frigg-infrastructure.js +0 -2
  25. package/infrastructure/iam-generator.js +18 -38
  26. package/infrastructure/iam-generator.test.js +40 -8
  27. package/infrastructure/serverless-template.js +25 -4
  28. package/infrastructure/serverless-template.test.js +45 -0
  29. package/package.json +6 -6
  30. package/test/index.js +2 -4
  31. package/test/mock-integration.js +4 -14
  32. package/frigg-cli/__tests__/jest.config.js +0 -102
  33. package/frigg-cli/__tests__/utils/command-tester.js +0 -170
  34. package/test/auther-definition-tester.js +0 -125
@@ -51,7 +51,11 @@ describe('IAM Generator', () => {
51
51
  websockets: { enable: false }
52
52
  };
53
53
 
54
- const yaml = generateIAMCloudFormation(appDefinition);
54
+ const summary = getFeatureSummary(appDefinition);
55
+ const yaml = generateIAMCloudFormation({
56
+ appName: summary.appName,
57
+ features: summary.features
58
+ });
55
59
 
56
60
  expect(yaml).toContain('AWSTemplateFormatVersion');
57
61
  expect(yaml).toContain('FriggDeploymentUser');
@@ -66,7 +70,11 @@ describe('IAM Generator', () => {
66
70
  vpc: { enable: true }
67
71
  };
68
72
 
69
- const yaml = generateIAMCloudFormation(appDefinition);
73
+ const summary = getFeatureSummary(appDefinition);
74
+ const yaml = generateIAMCloudFormation({
75
+ appName: summary.appName,
76
+ features: summary.features
77
+ });
70
78
 
71
79
  expect(yaml).toContain('FriggVPCPolicy');
72
80
  expect(yaml).toContain('CreateVPCPermissions');
@@ -81,7 +89,11 @@ describe('IAM Generator', () => {
81
89
  encryption: { fieldLevelEncryptionMethod: 'kms' }
82
90
  };
83
91
 
84
- const yaml = generateIAMCloudFormation(appDefinition);
92
+ const summary = getFeatureSummary(appDefinition);
93
+ const yaml = generateIAMCloudFormation({
94
+ appName: summary.appName,
95
+ features: summary.features
96
+ });
85
97
 
86
98
  expect(yaml).toContain('FriggKMSPolicy');
87
99
  expect(yaml).toContain('CreateKMSPermissions');
@@ -97,7 +109,11 @@ describe('IAM Generator', () => {
97
109
  ssm: { enable: true }
98
110
  };
99
111
 
100
- const yaml = generateIAMCloudFormation(appDefinition);
112
+ const summary = getFeatureSummary(appDefinition);
113
+ const yaml = generateIAMCloudFormation({
114
+ appName: summary.appName,
115
+ features: summary.features
116
+ });
101
117
 
102
118
  expect(yaml).toContain('FriggSSMPolicy');
103
119
  expect(yaml).toContain('CreateSSMPermissions');
@@ -113,7 +129,11 @@ describe('IAM Generator', () => {
113
129
  ssm: { enable: true }
114
130
  };
115
131
 
116
- const yaml = generateIAMCloudFormation(appDefinition);
132
+ const summary = getFeatureSummary(appDefinition);
133
+ const yaml = generateIAMCloudFormation({
134
+ appName: summary.appName,
135
+ features: summary.features
136
+ });
117
137
 
118
138
  // Check parameter defaults match the enabled features
119
139
  expect(yaml).toContain("Default: 'true'"); // VPC enabled
@@ -127,7 +147,11 @@ describe('IAM Generator', () => {
127
147
  integrations: []
128
148
  };
129
149
 
130
- const yaml = generateIAMCloudFormation(appDefinition);
150
+ const summary = getFeatureSummary(appDefinition);
151
+ const yaml = generateIAMCloudFormation({
152
+ appName: summary.appName,
153
+ features: summary.features
154
+ });
131
155
 
132
156
  // Check for core permissions
133
157
  expect(yaml).toContain('cloudformation:CreateStack');
@@ -149,7 +173,11 @@ describe('IAM Generator', () => {
149
173
  integrations: []
150
174
  };
151
175
 
152
- const yaml = generateIAMCloudFormation(appDefinition);
176
+ const summary = getFeatureSummary(appDefinition);
177
+ const yaml = generateIAMCloudFormation({
178
+ appName: summary.appName,
179
+ features: summary.features
180
+ });
153
181
 
154
182
  expect(yaml).toContain('internal-error-queue-*');
155
183
  });
@@ -160,7 +188,11 @@ describe('IAM Generator', () => {
160
188
  integrations: []
161
189
  };
162
190
 
163
- const yaml = generateIAMCloudFormation(appDefinition);
191
+ const summary = getFeatureSummary(appDefinition);
192
+ const yaml = generateIAMCloudFormation({
193
+ appName: summary.appName,
194
+ features: summary.features
195
+ });
164
196
 
165
197
  expect(yaml).toContain('Outputs:');
166
198
  expect(yaml).toContain('DeploymentUserArn:');
@@ -2,10 +2,19 @@ const path = require('path');
2
2
  const fs = require('fs');
3
3
  const { AWSDiscovery } = require('./aws-discovery');
4
4
 
5
- const shouldRunDiscovery = (AppDefinition) =>
6
- AppDefinition.vpc?.enable === true ||
7
- AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
8
- AppDefinition.ssm?.enable === true;
5
+ const shouldRunDiscovery = (AppDefinition) => {
6
+ console.log('⚙️ Checking FRIGG_SKIP_AWS_DISCOVERY:', process.env.FRIGG_SKIP_AWS_DISCOVERY);
7
+ if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
8
+ console.log('⚙️ Skipping AWS discovery because FRIGG_SKIP_AWS_DISCOVERY is set.');
9
+ return false;
10
+ }
11
+
12
+ return (
13
+ AppDefinition.vpc?.enable === true ||
14
+ AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
15
+ AppDefinition.ssm?.enable === true
16
+ );
17
+ };
9
18
 
10
19
  const getAppEnvironmentVars = (AppDefinition) => {
11
20
  const envVars = {};
@@ -649,6 +658,12 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
649
658
  return;
650
659
  }
651
660
 
661
+ // Skip KMS configuration for local development when AWS discovery is disabled
662
+ if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
663
+ console.log('⚙️ Skipping KMS configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)');
664
+ return;
665
+ }
666
+
652
667
  if (discoveredResources.defaultKmsKeyId) {
653
668
  console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
654
669
  definition.resources.Resources.FriggKMSKeyAlias = {
@@ -846,6 +861,12 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
846
861
  return;
847
862
  }
848
863
 
864
+ // Skip VPC configuration for local development when AWS discovery is disabled
865
+ if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
866
+ console.log('⚙️ Skipping VPC configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)');
867
+ return;
868
+ }
869
+
849
870
  definition.provider.iamRoleStatements.push({
850
871
  Effect: 'Allow',
851
872
  Action: [
@@ -55,6 +55,7 @@ describe('composeServerlessDefinition', () => {
55
55
  jest.restoreAllMocks();
56
56
  // Restore env
57
57
  delete process.env.AWS_REGION;
58
+ delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
58
59
  process.argv = ['node', 'test'];
59
60
  });
60
61
 
@@ -86,6 +87,50 @@ describe('composeServerlessDefinition', () => {
86
87
 
87
88
  expect(AWSDiscovery).toHaveBeenCalledTimes(1);
88
89
  });
90
+
91
+ it('should skip AWS discovery when FRIGG_SKIP_AWS_DISCOVERY is set to true', async () => {
92
+ AWSDiscovery.mockClear();
93
+ process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
94
+
95
+ const appDefinition = {
96
+ integrations: [],
97
+ vpc: { enable: true },
98
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
99
+ ssm: { enable: true },
100
+ };
101
+
102
+ await composeServerlessDefinition(appDefinition);
103
+
104
+ expect(AWSDiscovery).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it('should run AWS discovery when FRIGG_SKIP_AWS_DISCOVERY is not set', async () => {
108
+ AWSDiscovery.mockClear();
109
+ delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
110
+
111
+ const appDefinition = {
112
+ integrations: [],
113
+ vpc: { enable: true },
114
+ };
115
+
116
+ await composeServerlessDefinition(appDefinition);
117
+
118
+ expect(AWSDiscovery).toHaveBeenCalledTimes(1);
119
+ });
120
+
121
+ it('should skip VPC configuration when FRIGG_SKIP_AWS_DISCOVERY is true', async () => {
122
+ process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
123
+
124
+ const appDefinition = {
125
+ integrations: [],
126
+ vpc: { enable: true, management: 'discover' },
127
+ };
128
+
129
+ const result = await composeServerlessDefinition(appDefinition);
130
+
131
+ // VPC configuration should not be present when skipped
132
+ expect(result.provider.vpc).toBeUndefined();
133
+ });
89
134
  });
90
135
 
91
136
  describe('Basic Configuration', () => {
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-next.40",
4
+ "version": "2.0.0-next.42",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -9,8 +9,8 @@
9
9
  "@babel/eslint-parser": "^7.18.9",
10
10
  "@babel/parser": "^7.25.3",
11
11
  "@babel/traverse": "^7.25.3",
12
- "@friggframework/schemas": "2.0.0-next.40",
13
- "@friggframework/test": "2.0.0-next.40",
12
+ "@friggframework/schemas": "2.0.0-next.42",
13
+ "@friggframework/test": "2.0.0-next.42",
14
14
  "@hapi/boom": "^10.0.1",
15
15
  "@inquirer/prompts": "^5.3.8",
16
16
  "axios": "^1.7.2",
@@ -32,8 +32,8 @@
32
32
  "serverless-http": "^2.7.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@friggframework/eslint-config": "2.0.0-next.40",
36
- "@friggframework/prettier-config": "2.0.0-next.40",
35
+ "@friggframework/eslint-config": "2.0.0-next.42",
36
+ "@friggframework/prettier-config": "2.0.0-next.42",
37
37
  "aws-sdk-client-mock": "^4.1.0",
38
38
  "aws-sdk-client-mock-jest": "^4.1.0",
39
39
  "jest": "^30.1.3",
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "access": "public"
70
70
  },
71
- "gitHead": "088c50c6e1e37a6d42be05af49349b70ae94ee31"
71
+ "gitHead": "9b956c72ec74f70bb5efd336782cf269c79c3d9b"
72
72
  }
package/test/index.js CHANGED
@@ -1,11 +1,9 @@
1
- const {testDefinitionRequiredAuthMethods} = require('./auther-definition-method-tester');
2
- const {createMockIntegration, createMockApiObject} = require('./mock-integration');
3
- const { testAutherDefinition } = require('./auther-definition-tester');
1
+ const { testDefinitionRequiredAuthMethods } = require('./auther-definition-method-tester');
2
+ const { createMockIntegration, createMockApiObject } = require('./mock-integration');
4
3
 
5
4
 
6
5
  module.exports = {
7
6
  createMockIntegration,
8
7
  createMockApiObject,
9
8
  testDefinitionRequiredAuthMethods,
10
- testAutherDefinition,
11
9
  };
@@ -1,8 +1,4 @@
1
1
  const {
2
- Auther,
3
- Credential,
4
- Entity,
5
- IntegrationFactory,
6
2
  createObjectId,
7
3
  } = require('@friggframework/core');
8
4
 
@@ -11,7 +7,6 @@ async function createMockIntegration(
11
7
  userId = null,
12
8
  config = { type: IntegrationClass.Definition.name }
13
9
  ) {
14
- const integrationFactory = new IntegrationFactory([IntegrationClass]);
15
10
  userId = userId || createObjectId();
16
11
 
17
12
  const insertOptions = {
@@ -24,10 +19,8 @@ async function createMockIntegration(
24
19
  const entities = [];
25
20
  for (const moduleName in IntegrationClass.modules) {
26
21
  const ModuleDef = IntegrationClass.Definition.modules[moduleName];
27
- const module = await Auther.getInstance({
28
- definition: ModuleDef,
29
- userId: userId,
30
- });
22
+ // todo: create module using the new architecture
23
+ const module = {}
31
24
  const credential = await module.CredentialModel.findOneAndUpdate(
32
25
  user,
33
26
  { $set: user },
@@ -51,11 +44,8 @@ async function createMockIntegration(
51
44
  );
52
45
  }
53
46
 
54
- const integration = await integrationFactory.createIntegration(
55
- entities,
56
- userId,
57
- config
58
- );
47
+ // todo: create integration using the new architecture
48
+ const integration = {}
59
49
 
60
50
  integration.id = integration.record._id;
61
51
 
@@ -1,102 +0,0 @@
1
- module.exports = {
2
- displayName: 'Frigg CLI Tests',
3
- testMatch: [
4
- '<rootDir>/__tests__/**/*.test.js',
5
- '<rootDir>/__tests__/**/*.spec.js'
6
- ],
7
- testEnvironment: 'node',
8
- collectCoverageFrom: [
9
- '../**/*.js',
10
- '!../**/*.test.js',
11
- '!../**/*.spec.js',
12
- '!../node_modules/**',
13
- '!../__tests__/**',
14
- '!../coverage/**'
15
- ],
16
- coverageDirectory: 'coverage',
17
- coverageReporters: [
18
- 'text',
19
- 'text-summary',
20
- 'html',
21
- 'lcov',
22
- 'json'
23
- ],
24
- coverageThreshold: {
25
- global: {
26
- branches: 85,
27
- functions: 85,
28
- lines: 85,
29
- statements: 85
30
- },
31
- '../install-command/index.js': {
32
- branches: 90,
33
- functions: 90,
34
- lines: 90,
35
- statements: 90
36
- },
37
- '../build-command/index.js': {
38
- branches: 90,
39
- functions: 90,
40
- lines: 90,
41
- statements: 90
42
- },
43
- '../deploy-command/index.js': {
44
- branches: 90,
45
- functions: 90,
46
- lines: 90,
47
- statements: 90
48
- },
49
- '../ui-command/index.js': {
50
- branches: 90,
51
- functions: 90,
52
- lines: 90,
53
- statements: 90
54
- },
55
- '../generate-command/index.js': {
56
- branches: 90,
57
- functions: 90,
58
- lines: 90,
59
- statements: 90
60
- }
61
- },
62
- setupFilesAfterEnv: [
63
- '<rootDir>/utils/test-setup.js'
64
- ],
65
- testTimeout: 10000,
66
- maxWorkers: '50%',
67
- verbose: true,
68
- collectCoverage: true,
69
- coveragePathIgnorePatterns: [
70
- '/node_modules/',
71
- '/__tests__/',
72
- '/coverage/',
73
- '.test.js',
74
- '.spec.js'
75
- ],
76
- moduleFileExtensions: [
77
- 'js',
78
- 'json',
79
- 'node'
80
- ],
81
- transform: {},
82
- testResultsProcessor: 'jest-sonar-reporter',
83
- reporters: [
84
- 'default',
85
- [
86
- 'jest-junit',
87
- {
88
- outputDirectory: 'coverage',
89
- outputName: 'junit.xml',
90
- ancestorSeparator: ' › ',
91
- uniqueOutputName: 'false',
92
- suiteNameTemplate: '{filepath}',
93
- classNameTemplate: '{classname}',
94
- titleTemplate: '{title}'
95
- }
96
- ]
97
- ],
98
- watchman: false,
99
- forceExit: true,
100
- detectOpenHandles: true,
101
- errorOnDeprecated: true
102
- };
@@ -1,170 +0,0 @@
1
- const { Command } = require('commander');
2
-
3
- /**
4
- * CommandTester - Utility class for testing CLI commands
5
- * Provides a fluent interface for setting up mocks and executing commands
6
- */
7
- class CommandTester {
8
- constructor(commandDefinition) {
9
- this.commandDefinition = commandDefinition;
10
- this.mocks = new Map();
11
- this.originalEnv = process.env;
12
- this.capturedLogs = {
13
- info: [],
14
- error: [],
15
- debug: [],
16
- warn: []
17
- };
18
- }
19
-
20
- /**
21
- * Set up a mock for a module
22
- * @param {string} modulePath - Path to the module to mock
23
- * @param {object} implementation - Mock implementation
24
- * @returns {CommandTester} - Fluent interface
25
- */
26
- mock(modulePath, implementation) {
27
- this.mocks.set(modulePath, implementation);
28
- return this;
29
- }
30
-
31
- /**
32
- * Set environment variables for the test
33
- * @param {object} env - Environment variables to set
34
- * @returns {CommandTester} - Fluent interface
35
- */
36
- withEnv(env) {
37
- process.env = { ...process.env, ...env };
38
- return this;
39
- }
40
-
41
- /**
42
- * Capture console output during test execution
43
- * @returns {CommandTester} - Fluent interface
44
- */
45
- captureOutput() {
46
- const originalConsole = { ...console };
47
-
48
- console.log = (...args) => {
49
- this.capturedLogs.info.push(args.join(' '));
50
- originalConsole.log(...args);
51
- };
52
-
53
- console.error = (...args) => {
54
- this.capturedLogs.error.push(args.join(' '));
55
- originalConsole.error(...args);
56
- };
57
-
58
- console.warn = (...args) => {
59
- this.capturedLogs.warn.push(args.join(' '));
60
- originalConsole.warn(...args);
61
- };
62
-
63
- console.debug = (...args) => {
64
- this.capturedLogs.debug.push(args.join(' '));
65
- originalConsole.debug(...args);
66
- };
67
-
68
- return this;
69
- }
70
-
71
- /**
72
- * Execute the command with given arguments
73
- * @param {string[]} args - Command arguments
74
- * @param {object} options - Command options
75
- * @returns {Promise<object>} - Execution result
76
- */
77
- async execute(args = [], options = {}) {
78
- // Set up mocks
79
- for (const [path, impl] of this.mocks) {
80
- jest.mock(path, () => impl, { virtual: true });
81
- }
82
-
83
- try {
84
- const program = new Command();
85
-
86
- // Set up the command
87
- const cmd = program
88
- .command(this.commandDefinition.name)
89
- .description(this.commandDefinition.description);
90
-
91
- // Add options if defined
92
- if (this.commandDefinition.options) {
93
- this.commandDefinition.options.forEach(option => {
94
- cmd.option(option.flags, option.description, option.defaultValue);
95
- });
96
- }
97
-
98
- // Add action
99
- cmd.action(this.commandDefinition.action);
100
-
101
- // Mock process.exit to prevent actual exit
102
- const originalExit = process.exit;
103
- let exitCode = 0;
104
- process.exit = (code) => {
105
- exitCode = code;
106
- throw new Error(`Process exited with code ${code}`);
107
- };
108
-
109
- try {
110
- await program.parseAsync(['node', 'cli', ...args]);
111
-
112
- return {
113
- success: true,
114
- exitCode: 0,
115
- logs: this.capturedLogs,
116
- args,
117
- options
118
- };
119
- } catch (error) {
120
- if (error.message.includes('Process exited with code')) {
121
- return {
122
- success: false,
123
- exitCode,
124
- error: error.message,
125
- logs: this.capturedLogs,
126
- args,
127
- options
128
- };
129
- }
130
- throw error;
131
- } finally {
132
- process.exit = originalExit;
133
- }
134
- } finally {
135
- // Clean up mocks
136
- for (const [path] of this.mocks) {
137
- jest.unmock(path);
138
- }
139
-
140
- // Restore environment
141
- process.env = this.originalEnv;
142
- }
143
- }
144
-
145
- /**
146
- * Get captured logs
147
- * @returns {object} - Captured logs by type
148
- */
149
- getLogs() {
150
- return this.capturedLogs;
151
- }
152
-
153
- /**
154
- * Reset the tester state
155
- * @returns {CommandTester} - Fluent interface
156
- */
157
- reset() {
158
- this.mocks.clear();
159
- this.capturedLogs = {
160
- info: [],
161
- error: [],
162
- debug: [],
163
- warn: []
164
- };
165
- process.env = this.originalEnv;
166
- return this;
167
- }
168
- }
169
-
170
- module.exports = { CommandTester };