@friggframework/devtools 2.0.0--canary.395.495dc7d.0 → 2.0.0--canary.395.04851d8.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/frigg-cli/__tests__/unit/commands/build.test.js +173 -405
- package/frigg-cli/__tests__/unit/commands/install.test.js +359 -377
- package/frigg-cli/__tests__/unit/commands/ui.test.js +266 -512
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +4 -4
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +2 -2
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +13 -7
- package/frigg-cli/__tests__/utils/test-setup.js +22 -21
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +151 -162
- package/frigg-cli/generate-iam-command.js +7 -4
- package/frigg-cli/{__tests__/jest.config.js → jest.config.js} +30 -32
- package/frigg-cli/start-command/index.js +17 -17
- package/frigg-cli/start-command/start-command.test.js +19 -13
- package/frigg-cli/utils/database-validator.js +11 -7
- package/frigg-cli/utils/error-messages.js +1 -1
- package/frigg-cli/utils/prisma-runner.js +17 -15
- package/infrastructure/IAM-POLICY-TEMPLATES.md +30 -12
- package/infrastructure/iam-generator.js +18 -38
- package/infrastructure/iam-generator.test.js +40 -8
- package/package.json +6 -6
- package/frigg-cli/__tests__/utils/command-tester.js +0 -170
|
@@ -2,16 +2,25 @@ module.exports = {
|
|
|
2
2
|
displayName: 'Frigg CLI Tests',
|
|
3
3
|
testMatch: [
|
|
4
4
|
'<rootDir>/__tests__/**/*.test.js',
|
|
5
|
-
'<rootDir>/__tests__/**/*.spec.js'
|
|
5
|
+
'<rootDir>/__tests__/**/*.spec.js',
|
|
6
|
+
'<rootDir>/**/start-command.test.js',
|
|
7
|
+
'<rootDir>/**/__tests__/**/*.test.js'
|
|
8
|
+
],
|
|
9
|
+
// Exclude utility files and config from being treated as tests
|
|
10
|
+
testPathIgnorePatterns: [
|
|
11
|
+
'/node_modules/',
|
|
12
|
+
'/__tests__/utils/',
|
|
13
|
+
'/__tests__/jest.config.js',
|
|
14
|
+
'/test-setup.js'
|
|
6
15
|
],
|
|
7
16
|
testEnvironment: 'node',
|
|
8
17
|
collectCoverageFrom: [
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
18
|
+
'**/*.js',
|
|
19
|
+
'!**/*.test.js',
|
|
20
|
+
'!**/*.spec.js',
|
|
21
|
+
'!**/node_modules/**',
|
|
22
|
+
'!**/__tests__/**',
|
|
23
|
+
'!**/coverage/**'
|
|
15
24
|
],
|
|
16
25
|
coverageDirectory: 'coverage',
|
|
17
26
|
coverageReporters: [
|
|
@@ -28,55 +37,55 @@ module.exports = {
|
|
|
28
37
|
lines: 85,
|
|
29
38
|
statements: 85
|
|
30
39
|
},
|
|
31
|
-
'
|
|
40
|
+
'./install-command/index.js': {
|
|
32
41
|
branches: 90,
|
|
33
42
|
functions: 90,
|
|
34
43
|
lines: 90,
|
|
35
44
|
statements: 90
|
|
36
45
|
},
|
|
37
|
-
'
|
|
46
|
+
'./build-command/index.js': {
|
|
38
47
|
branches: 90,
|
|
39
48
|
functions: 90,
|
|
40
49
|
lines: 90,
|
|
41
50
|
statements: 90
|
|
42
51
|
},
|
|
43
|
-
'
|
|
52
|
+
'./deploy-command/index.js': {
|
|
44
53
|
branches: 90,
|
|
45
54
|
functions: 90,
|
|
46
55
|
lines: 90,
|
|
47
56
|
statements: 90
|
|
48
57
|
},
|
|
49
|
-
'
|
|
58
|
+
'./ui-command/index.js': {
|
|
50
59
|
branches: 90,
|
|
51
60
|
functions: 90,
|
|
52
61
|
lines: 90,
|
|
53
62
|
statements: 90
|
|
54
63
|
},
|
|
55
|
-
'
|
|
64
|
+
'./generate-command/index.js': {
|
|
56
65
|
branches: 90,
|
|
57
66
|
functions: 90,
|
|
58
67
|
lines: 90,
|
|
59
68
|
statements: 90
|
|
60
69
|
},
|
|
61
|
-
'
|
|
70
|
+
'./db-setup-command/index.js': {
|
|
62
71
|
branches: 90,
|
|
63
72
|
functions: 90,
|
|
64
73
|
lines: 90,
|
|
65
74
|
statements: 90
|
|
66
75
|
},
|
|
67
|
-
'
|
|
76
|
+
'./utils/database-validator.js': {
|
|
68
77
|
branches: 85,
|
|
69
78
|
functions: 85,
|
|
70
79
|
lines: 85,
|
|
71
80
|
statements: 85
|
|
72
81
|
},
|
|
73
|
-
'
|
|
82
|
+
'./utils/prisma-runner.js': {
|
|
74
83
|
branches: 85,
|
|
75
84
|
functions: 85,
|
|
76
85
|
lines: 85,
|
|
77
86
|
statements: 85
|
|
78
87
|
},
|
|
79
|
-
'
|
|
88
|
+
'./utils/error-messages.js': {
|
|
80
89
|
branches: 85,
|
|
81
90
|
functions: 85,
|
|
82
91
|
lines: 85,
|
|
@@ -84,7 +93,7 @@ module.exports = {
|
|
|
84
93
|
}
|
|
85
94
|
},
|
|
86
95
|
setupFilesAfterEnv: [
|
|
87
|
-
'<rootDir>/utils/test-setup.js'
|
|
96
|
+
'<rootDir>/__tests__/utils/test-setup.js'
|
|
88
97
|
],
|
|
89
98
|
testTimeout: 10000,
|
|
90
99
|
maxWorkers: '50%',
|
|
@@ -103,24 +112,13 @@ module.exports = {
|
|
|
103
112
|
'node'
|
|
104
113
|
],
|
|
105
114
|
transform: {},
|
|
106
|
-
testResultsProcessor: 'jest-sonar-reporter',
|
|
115
|
+
// testResultsProcessor: 'jest-sonar-reporter', // Optional dependency
|
|
107
116
|
reporters: [
|
|
108
|
-
'default'
|
|
109
|
-
|
|
110
|
-
'jest-junit',
|
|
111
|
-
{
|
|
112
|
-
outputDirectory: 'coverage',
|
|
113
|
-
outputName: 'junit.xml',
|
|
114
|
-
ancestorSeparator: ' › ',
|
|
115
|
-
uniqueOutputName: 'false',
|
|
116
|
-
suiteNameTemplate: '{filepath}',
|
|
117
|
-
classNameTemplate: '{classname}',
|
|
118
|
-
titleTemplate: '{title}'
|
|
119
|
-
}
|
|
120
|
-
]
|
|
117
|
+
'default'
|
|
118
|
+
// jest-junit reporter removed - optional dependency
|
|
121
119
|
],
|
|
122
120
|
watchman: false,
|
|
123
121
|
forceExit: true,
|
|
124
122
|
detectOpenHandles: true,
|
|
125
123
|
errorOnDeprecated: true
|
|
126
|
-
};
|
|
124
|
+
};
|
|
@@ -40,7 +40,7 @@ async function startCommand(options) {
|
|
|
40
40
|
console.log('Starting backend and optional frontend...');
|
|
41
41
|
|
|
42
42
|
// Suppress AWS SDK warning message about maintenance mode
|
|
43
|
-
process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = 1;
|
|
43
|
+
process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = '1';
|
|
44
44
|
// Skip AWS discovery for local development
|
|
45
45
|
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
46
46
|
const backendPath = path.resolve(process.cwd());
|
|
@@ -124,38 +124,38 @@ async function performDatabaseChecks(verbose) {
|
|
|
124
124
|
console.log(chalk.green(`✓ Database type: ${dbType}`));
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
// Check 3:
|
|
127
|
+
// Check 3: Verify Prisma client is generated (BEFORE connection test to prevent auto-generation)
|
|
128
128
|
if (verbose) {
|
|
129
|
-
console.log(chalk.gray('
|
|
129
|
+
console.log(chalk.gray('Checking Prisma client...'));
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
const
|
|
132
|
+
const clientCheck = checkPrismaClientGenerated(dbType);
|
|
133
133
|
|
|
134
|
-
if (!
|
|
135
|
-
console.error(
|
|
136
|
-
|
|
134
|
+
if (!clientCheck.generated) {
|
|
135
|
+
console.error(getPrismaClientNotGeneratedError(dbType));
|
|
136
|
+
console.error(chalk.yellow('\nRun this command to generate the Prisma client:'));
|
|
137
|
+
console.error(chalk.cyan(' frigg db:setup\n'));
|
|
138
|
+
throw new Error('Prisma client not generated');
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
if (verbose) {
|
|
140
|
-
console.log(chalk.green('✓
|
|
142
|
+
console.log(chalk.green('✓ Prisma client generated'));
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
// Check 4:
|
|
145
|
+
// Check 4: Test database connection (only after confirming client exists)
|
|
144
146
|
if (verbose) {
|
|
145
|
-
console.log(chalk.gray('
|
|
147
|
+
console.log(chalk.gray('Testing database connection...'));
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
const
|
|
150
|
+
const connectionTest = await testDatabaseConnection(urlValidation.url, dbType, 5000);
|
|
149
151
|
|
|
150
|
-
if (!
|
|
151
|
-
console.error(
|
|
152
|
-
|
|
153
|
-
console.error(chalk.cyan(' frigg db:setup\n'));
|
|
154
|
-
throw new Error('Prisma client not generated');
|
|
152
|
+
if (!connectionTest.connected) {
|
|
153
|
+
console.error(getDatabaseConnectionError(connectionTest.error, dbType));
|
|
154
|
+
throw new Error('Database connection failed');
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (verbose) {
|
|
158
|
-
console.log(chalk.green('✓
|
|
158
|
+
console.log(chalk.green('✓ Database connection verified'));
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -36,21 +36,27 @@ describe('startCommand', () => {
|
|
|
36
36
|
let mockProcessExit;
|
|
37
37
|
|
|
38
38
|
beforeEach(() => {
|
|
39
|
-
// Mock process.exit
|
|
40
|
-
|
|
39
|
+
// Mock process.exit to throw error and stop execution (prevents actual exits)
|
|
40
|
+
const exitError = new Error('process.exit called');
|
|
41
|
+
exitError.code = 'PROCESS_EXIT';
|
|
42
|
+
mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
43
|
+
throw exitError;
|
|
44
|
+
});
|
|
41
45
|
|
|
42
46
|
// Reset mocks
|
|
43
47
|
jest.clearAllMocks();
|
|
44
48
|
|
|
45
49
|
// Re-apply process.exit mock after clearAllMocks
|
|
46
|
-
mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
50
|
+
mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
51
|
+
throw exitError;
|
|
52
|
+
});
|
|
47
53
|
|
|
48
54
|
// Set up default database validator mocks for all tests
|
|
49
55
|
const defaultValidator = createMockDatabaseValidator();
|
|
50
|
-
mockValidator.validateDatabaseUrl.
|
|
51
|
-
mockValidator.getDatabaseType.
|
|
52
|
-
mockValidator.testDatabaseConnection.
|
|
53
|
-
mockValidator.checkPrismaClientGenerated.
|
|
56
|
+
mockValidator.validateDatabaseUrl.mockReturnValue(defaultValidator.validateDatabaseUrl());
|
|
57
|
+
mockValidator.getDatabaseType.mockReturnValue(defaultValidator.getDatabaseType());
|
|
58
|
+
mockValidator.testDatabaseConnection.mockResolvedValue(defaultValidator.testDatabaseConnection());
|
|
59
|
+
mockValidator.checkPrismaClientGenerated.mockReturnValue(defaultValidator.checkPrismaClientGenerated());
|
|
54
60
|
|
|
55
61
|
// Mock dotenv
|
|
56
62
|
dotenv.config = jest.fn();
|
|
@@ -219,7 +225,7 @@ describe('startCommand', () => {
|
|
|
219
225
|
error: 'DATABASE_URL not found'
|
|
220
226
|
});
|
|
221
227
|
|
|
222
|
-
await startCommand({});
|
|
228
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
223
229
|
|
|
224
230
|
expect(mockConsoleError).toHaveBeenCalled();
|
|
225
231
|
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
|
@@ -231,7 +237,7 @@ describe('startCommand', () => {
|
|
|
231
237
|
error: 'Database not configured'
|
|
232
238
|
});
|
|
233
239
|
|
|
234
|
-
await startCommand({});
|
|
240
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
235
241
|
|
|
236
242
|
expect(mockConsoleError).toHaveBeenCalled();
|
|
237
243
|
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
|
@@ -244,7 +250,7 @@ describe('startCommand', () => {
|
|
|
244
250
|
error: 'Connection failed'
|
|
245
251
|
});
|
|
246
252
|
|
|
247
|
-
await startCommand({});
|
|
253
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
248
254
|
|
|
249
255
|
expect(mockConsoleError).toHaveBeenCalled();
|
|
250
256
|
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
|
@@ -257,7 +263,7 @@ describe('startCommand', () => {
|
|
|
257
263
|
error: 'Client not found'
|
|
258
264
|
});
|
|
259
265
|
|
|
260
|
-
await startCommand({});
|
|
266
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
261
267
|
|
|
262
268
|
expect(mockConsoleError).toHaveBeenCalled();
|
|
263
269
|
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('frigg db:setup'));
|
|
@@ -271,7 +277,7 @@ describe('startCommand', () => {
|
|
|
271
277
|
error: 'Client not generated'
|
|
272
278
|
});
|
|
273
279
|
|
|
274
|
-
await startCommand({});
|
|
280
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
275
281
|
|
|
276
282
|
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('frigg db:setup'));
|
|
277
283
|
});
|
|
@@ -281,7 +287,7 @@ describe('startCommand', () => {
|
|
|
281
287
|
valid: false
|
|
282
288
|
});
|
|
283
289
|
|
|
284
|
-
await startCommand({});
|
|
290
|
+
await expect(startCommand({})).rejects.toThrow('process.exit called');
|
|
285
291
|
|
|
286
292
|
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
|
287
293
|
});
|
|
@@ -111,17 +111,21 @@ async function testDatabaseConnection(databaseUrl, dbType, timeout = 5000) {
|
|
|
111
111
|
* @returns {Object} { generated: boolean, path?: string, error?: string }
|
|
112
112
|
*/
|
|
113
113
|
function checkPrismaClientGenerated(dbType, projectRoot = process.cwd()) {
|
|
114
|
-
const clientPackageName = dbType
|
|
114
|
+
const clientPackageName = `@prisma-${dbType}/client`;
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
|
-
//
|
|
118
|
-
// This
|
|
117
|
+
// First, resolve where @friggframework/core actually is
|
|
118
|
+
// This handles file: dependencies and symlinks correctly
|
|
119
|
+
const corePackagePath = require.resolve('@friggframework/core', {
|
|
120
|
+
paths: [projectRoot]
|
|
121
|
+
});
|
|
122
|
+
const corePackageDir = path.dirname(corePackagePath);
|
|
123
|
+
|
|
124
|
+
// Now look for the Prisma client within the resolved core package
|
|
119
125
|
const clientPath = require.resolve(clientPackageName, {
|
|
120
126
|
paths: [
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
// Fallback to project root (in case of different hoisting)
|
|
124
|
-
projectRoot
|
|
127
|
+
corePackageDir, // Look in the actual core package location
|
|
128
|
+
projectRoot // Fallback to project root
|
|
125
129
|
]
|
|
126
130
|
});
|
|
127
131
|
|
|
@@ -161,7 +161,7 @@ ${chalk.gray('5.')} Verify network/firewall settings
|
|
|
161
161
|
* @returns {string} Formatted error message
|
|
162
162
|
*/
|
|
163
163
|
function getPrismaClientNotGeneratedError(dbType) {
|
|
164
|
-
const clientName = dbType
|
|
164
|
+
const clientName = `@prisma-${dbType}/client`;
|
|
165
165
|
|
|
166
166
|
return `
|
|
167
167
|
${chalk.red(`❌ Prisma client not generated for ${dbType}`)}
|
|
@@ -16,23 +16,25 @@ const chalk = require('chalk');
|
|
|
16
16
|
* @throws {Error} If schema file doesn't exist
|
|
17
17
|
*/
|
|
18
18
|
function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'core',
|
|
24
|
-
`prisma-${dbType}`,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'Ensure @friggframework/core is installed.'
|
|
32
|
-
);
|
|
19
|
+
// Try multiple locations for the schema file
|
|
20
|
+
// 1. Local node_modules (standard install)
|
|
21
|
+
// 2. Parent node_modules (workspace/monorepo setup)
|
|
22
|
+
const possiblePaths = [
|
|
23
|
+
path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma'),
|
|
24
|
+
path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma')
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const schemaPath of possiblePaths) {
|
|
28
|
+
if (fs.existsSync(schemaPath)) {
|
|
29
|
+
return schemaPath;
|
|
30
|
+
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
// If not found in any location, throw error
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Prisma schema not found at:\n${possiblePaths.join('\n')}\n\n` +
|
|
36
|
+
'Ensure @friggframework/core is installed.'
|
|
37
|
+
);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -83,27 +83,45 @@ aws cloudformation update-stack \
|
|
|
83
83
|
For custom policy generation based on your app definition:
|
|
84
84
|
|
|
85
85
|
```javascript
|
|
86
|
-
const { generateIAMPolicy, generateIAMCloudFormation } = require('./iam-generator');
|
|
86
|
+
const { generateIAMPolicy, generateIAMCloudFormation, getFeatureSummary } = require('./iam-generator');
|
|
87
87
|
|
|
88
88
|
// Generate basic JSON policy
|
|
89
89
|
const basicPolicy = generateIAMPolicy('basic');
|
|
90
90
|
|
|
91
|
-
// Generate full JSON policy
|
|
91
|
+
// Generate full JSON policy
|
|
92
92
|
const fullPolicy = generateIAMPolicy('full');
|
|
93
93
|
|
|
94
|
-
// Generate CloudFormation template with auto-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
// Generate CloudFormation template with auto-detected features
|
|
95
|
+
const summary = getFeatureSummary(appDefinition);
|
|
96
|
+
const template = generateIAMCloudFormation({
|
|
97
|
+
appName: summary.appName,
|
|
98
|
+
features: summary.features,
|
|
99
|
+
userPrefix: 'frigg-deployment-user',
|
|
100
|
+
stackName: 'frigg-deployment-iam'
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Or manually specify features
|
|
104
|
+
const customTemplate = generateIAMCloudFormation({
|
|
105
|
+
appName: 'my-app',
|
|
106
|
+
features: {
|
|
107
|
+
vpc: true,
|
|
108
|
+
kms: true,
|
|
109
|
+
ssm: true,
|
|
110
|
+
websockets: false
|
|
111
|
+
},
|
|
112
|
+
userPrefix: 'my-deployment-user',
|
|
113
|
+
stackName: 'my-deployment-stack'
|
|
114
|
+
});
|
|
100
115
|
```
|
|
101
116
|
|
|
102
|
-
###
|
|
117
|
+
### Feature Detection
|
|
118
|
+
|
|
119
|
+
Use `getFeatureSummary(appDefinition)` to automatically detect features from your app definition:
|
|
103
120
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
```javascript
|
|
122
|
+
const summary = getFeatureSummary(appDefinition);
|
|
123
|
+
// Returns: { appName, features: { core, vpc, kms, ssm, websockets }, integrationCount }
|
|
124
|
+
```
|
|
107
125
|
|
|
108
126
|
## Security Best Practices
|
|
109
127
|
|
|
@@ -1,52 +1,31 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Generate IAM CloudFormation template
|
|
5
|
-
* @param {Object} appDefinition - Application definition object
|
|
4
|
+
* Generate IAM CloudFormation template
|
|
6
5
|
* @param {Object} options - Generation options
|
|
7
|
-
* @param {string} [options.
|
|
6
|
+
* @param {string} [options.appName='Frigg'] - Application name
|
|
7
|
+
* @param {Object} [options.features={}] - Enabled features { vpc, kms, ssm, websockets }
|
|
8
|
+
* @param {string} [options.userPrefix='frigg-deployment-user'] - IAM user name prefix
|
|
8
9
|
* @param {string} [options.stackName='frigg-deployment-iam'] - CloudFormation stack name
|
|
9
|
-
* @param {string} [options.mode='auto'] - Policy mode: 'basic', 'full', or 'auto' (auto-detect from appDefinition)
|
|
10
10
|
* @returns {string} CloudFormation YAML template
|
|
11
11
|
*/
|
|
12
|
-
function generateIAMCloudFormation(
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
} else if (mode === 'full') {
|
|
26
|
-
features = {
|
|
27
|
-
vpc: true,
|
|
28
|
-
kms: true,
|
|
29
|
-
ssm: true,
|
|
30
|
-
websockets: appDefinition.websockets?.enable === true,
|
|
31
|
-
};
|
|
32
|
-
} else {
|
|
33
|
-
// mode === 'auto'
|
|
34
|
-
features = {
|
|
35
|
-
vpc: appDefinition.vpc?.enable === true,
|
|
36
|
-
kms:
|
|
37
|
-
appDefinition.encryption
|
|
38
|
-
?.fieldLevelEncryptionMethod === 'kms',
|
|
39
|
-
ssm: appDefinition.ssm?.enable === true,
|
|
40
|
-
websockets: appDefinition.websockets?.enable === true,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
12
|
+
function generateIAMCloudFormation(options = {}) {
|
|
13
|
+
const {
|
|
14
|
+
appName = 'Frigg',
|
|
15
|
+
features = {},
|
|
16
|
+
userPrefix = 'frigg-deployment-user',
|
|
17
|
+
stackName = 'frigg-deployment-iam'
|
|
18
|
+
} = options;
|
|
19
|
+
|
|
20
|
+
const deploymentUserName = userPrefix;
|
|
21
|
+
|
|
22
|
+
// Features are already analyzed by caller (use getFeatureSummary to extract features from appDefinition)
|
|
23
|
+
// Expected features: { vpc, kms, ssm, websockets }
|
|
43
24
|
|
|
44
25
|
// Build the CloudFormation template
|
|
45
26
|
const template = {
|
|
46
27
|
AWSTemplateFormatVersion: '2010-09-09',
|
|
47
|
-
Description: `IAM roles and policies for ${
|
|
48
|
-
appDefinition.name || 'Frigg'
|
|
49
|
-
} application deployment pipeline`,
|
|
28
|
+
Description: `IAM roles and policies for ${appName} application deployment pipeline`,
|
|
50
29
|
|
|
51
30
|
Parameters: {
|
|
52
31
|
DeploymentUserName: {
|
|
@@ -829,6 +808,7 @@ function generateIAMPolicy(mode = 'basic') {
|
|
|
829
808
|
|
|
830
809
|
module.exports = {
|
|
831
810
|
generateIAMCloudFormation,
|
|
811
|
+
generateCloudFormationTemplate: generateIAMCloudFormation, // Alias for generate-command/index.js compatibility
|
|
832
812
|
getFeatureSummary,
|
|
833
813
|
generateBasicIAMPolicy,
|
|
834
814
|
generateFullIAMPolicy,
|
|
@@ -51,7 +51,11 @@ describe('IAM Generator', () => {
|
|
|
51
51
|
websockets: { enable: false }
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:');
|
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.395.
|
|
4
|
+
"version": "2.0.0--canary.395.04851d8.0",
|
|
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--canary.395.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.395.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.395.04851d8.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.395.04851d8.0",
|
|
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--canary.395.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.395.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.395.04851d8.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.395.04851d8.0",
|
|
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": "
|
|
71
|
+
"gitHead": "04851d8d253adc1abe6aeae2d8e26e9887d7334b"
|
|
72
72
|
}
|