@friggframework/devtools 2.0.0-next.52 → 2.0.0-next.54
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/README.md +13 -14
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +267 -166
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +45 -14
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +44 -3
- package/frigg-cli/db-setup-command/index.js +75 -22
- package/frigg-cli/deploy-command/index.js +6 -3
- package/frigg-cli/utils/database-validator.js +18 -5
- package/frigg-cli/utils/error-messages.js +84 -12
- package/infrastructure/README.md +28 -0
- package/infrastructure/domains/database/migration-builder.js +26 -20
- package/infrastructure/domains/database/migration-builder.test.js +27 -0
- package/infrastructure/domains/integration/integration-builder.js +17 -10
- package/infrastructure/domains/integration/integration-builder.test.js +97 -0
- package/infrastructure/domains/networking/vpc-builder.js +240 -18
- package/infrastructure/domains/networking/vpc-builder.test.js +711 -13
- package/infrastructure/domains/networking/vpc-resolver.js +221 -40
- package/infrastructure/domains/networking/vpc-resolver.test.js +318 -18
- package/infrastructure/domains/security/kms-builder.js +55 -6
- package/infrastructure/domains/security/kms-builder.test.js +19 -1
- package/infrastructure/domains/shared/cloudformation-discovery.js +310 -13
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +395 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +41 -6
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +39 -0
- package/infrastructure/domains/shared/resource-discovery.js +17 -5
- package/infrastructure/domains/shared/resource-discovery.test.js +36 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +30 -20
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +43 -0
- package/infrastructure/infrastructure-composer.js +11 -3
- package/infrastructure/scripts/build-prisma-layer.js +153 -78
- package/infrastructure/scripts/build-prisma-layer.test.js +27 -11
- package/layers/prisma/.build-complete +2 -2
- package/package.json +7 -7
|
@@ -113,6 +113,15 @@ describe('Database Validator Utility', () => {
|
|
|
113
113
|
expect(result.error).toBeUndefined();
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
it('should return documentdb when core returns documentdb', () => {
|
|
117
|
+
getDatabaseTypeFromCore.mockReturnValue('documentdb');
|
|
118
|
+
|
|
119
|
+
const result = getDatabaseType();
|
|
120
|
+
|
|
121
|
+
expect(result.dbType).toBe('documentdb');
|
|
122
|
+
expect(result.error).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
116
125
|
it('should return error when core throws error', () => {
|
|
117
126
|
getDatabaseTypeFromCore.mockImplementation(() => {
|
|
118
127
|
throw new Error('[Frigg] Database not configured');
|
|
@@ -177,6 +186,17 @@ describe('Database Validator Utility', () => {
|
|
|
177
186
|
expect(disconnectPrisma).toHaveBeenCalled();
|
|
178
187
|
});
|
|
179
188
|
|
|
189
|
+
it('should connect successfully to DocumentDB and use $runCommandRaw', async () => {
|
|
190
|
+
const result = await testDatabaseConnection('mongodb://localhost', 'documentdb');
|
|
191
|
+
|
|
192
|
+
expect(result.connected).toBe(true);
|
|
193
|
+
expect(result.error).toBeUndefined();
|
|
194
|
+
expect(connectPrisma).toHaveBeenCalled();
|
|
195
|
+
expect(mockClient.$runCommandRaw).toHaveBeenCalledWith({ ping: 1 });
|
|
196
|
+
expect(mockClient.$queryRaw).not.toHaveBeenCalled(); // DocumentDB doesn't use SQL
|
|
197
|
+
expect(disconnectPrisma).toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
180
200
|
it('should connect successfully to PostgreSQL and use $queryRaw', async () => {
|
|
181
201
|
const result = await testDatabaseConnection('postgresql://localhost', 'postgresql');
|
|
182
202
|
|
|
@@ -303,20 +323,32 @@ describe('Database Validator Utility', () => {
|
|
|
303
323
|
// Note: Testing require.resolve behavior requires integration tests with real packages
|
|
304
324
|
// These unit tests focus on error handling and package name selection
|
|
305
325
|
|
|
306
|
-
it('should
|
|
307
|
-
// When
|
|
326
|
+
it('should check for MongoDB client directory', () => {
|
|
327
|
+
// When path doesn't exist, returns error about resolving module
|
|
308
328
|
const result = checkPrismaClientGenerated('mongodb', '/nonexistent/path');
|
|
309
329
|
|
|
310
330
|
expect(result.generated).toBe(false);
|
|
311
|
-
expect(result.error).
|
|
331
|
+
expect(result.error).toBeDefined();
|
|
332
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
312
333
|
});
|
|
313
334
|
|
|
314
|
-
it('should
|
|
315
|
-
// When
|
|
335
|
+
it('should check for PostgreSQL client directory', () => {
|
|
336
|
+
// When path doesn't exist, returns error about resolving module
|
|
316
337
|
const result = checkPrismaClientGenerated('postgresql', '/nonexistent/path');
|
|
317
338
|
|
|
318
339
|
expect(result.generated).toBe(false);
|
|
319
|
-
expect(result.error).
|
|
340
|
+
expect(result.error).toBeDefined();
|
|
341
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should normalize DocumentDB to check MongoDB client directory', () => {
|
|
345
|
+
// DocumentDB should use the same client as MongoDB (prisma-mongodb directory)
|
|
346
|
+
// When path doesn't exist, returns error about resolving module
|
|
347
|
+
const result = checkPrismaClientGenerated('documentdb', '/nonexistent/path');
|
|
348
|
+
|
|
349
|
+
expect(result.generated).toBe(false);
|
|
350
|
+
expect(result.error).toBeDefined();
|
|
351
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
320
352
|
});
|
|
321
353
|
|
|
322
354
|
it('should return error when MongoDB client not found', () => {
|
|
@@ -324,9 +356,8 @@ describe('Database Validator Utility', () => {
|
|
|
324
356
|
|
|
325
357
|
expect(result.generated).toBe(false);
|
|
326
358
|
expect(result.error).toBeDefined();
|
|
327
|
-
|
|
328
|
-
expect(result.error).toContain('
|
|
329
|
-
expect(result.error).toContain('frigg db:setup');
|
|
359
|
+
// Error will be about resolving @friggframework/core module
|
|
360
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
330
361
|
});
|
|
331
362
|
|
|
332
363
|
it('should return error when PostgreSQL client not found', () => {
|
|
@@ -334,15 +365,15 @@ describe('Database Validator Utility', () => {
|
|
|
334
365
|
|
|
335
366
|
expect(result.generated).toBe(false);
|
|
336
367
|
expect(result.error).toBeDefined();
|
|
337
|
-
|
|
338
|
-
expect(result.error).toContain('
|
|
339
|
-
expect(result.error).toContain('frigg db:setup');
|
|
368
|
+
// Error will be about resolving @friggframework/core module
|
|
369
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
340
370
|
});
|
|
341
371
|
|
|
342
|
-
it('should
|
|
372
|
+
it('should return error when core package cannot be resolved', () => {
|
|
343
373
|
const result = checkPrismaClientGenerated('mongodb', '/nonexistent/path');
|
|
344
374
|
|
|
345
|
-
expect(result.
|
|
375
|
+
expect(result.generated).toBe(false);
|
|
376
|
+
expect(result.error).toContain('Failed to check Prisma client');
|
|
346
377
|
});
|
|
347
378
|
|
|
348
379
|
it('should use process.cwd() by default when no project root specified', () => {
|
|
@@ -16,6 +16,12 @@ describe('Error Messages Utility', () => {
|
|
|
16
16
|
expect(DATABASE_URL_EXAMPLES.mongodb).toContain('replicaSet=rs0');
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
it('should include DocumentDB connection string example', () => {
|
|
20
|
+
expect(DATABASE_URL_EXAMPLES.documentdb).toBeDefined();
|
|
21
|
+
expect(DATABASE_URL_EXAMPLES.documentdb).toContain('docdb');
|
|
22
|
+
expect(DATABASE_URL_EXAMPLES.documentdb).toContain('retryWrites=false');
|
|
23
|
+
});
|
|
24
|
+
|
|
19
25
|
it('should include PostgreSQL connection string example', () => {
|
|
20
26
|
expect(DATABASE_URL_EXAMPLES.postgresql).toBeDefined();
|
|
21
27
|
expect(DATABASE_URL_EXAMPLES.postgresql).toContain('postgresql://');
|
|
@@ -31,10 +37,11 @@ describe('Error Messages Utility', () => {
|
|
|
31
37
|
expect(typeof message).toBe('string');
|
|
32
38
|
});
|
|
33
39
|
|
|
34
|
-
it('should include
|
|
40
|
+
it('should include all database type examples', () => {
|
|
35
41
|
const message = getDatabaseUrlMissingError();
|
|
36
42
|
|
|
37
43
|
expect(message).toContain('MongoDB');
|
|
44
|
+
expect(message).toContain('DocumentDB');
|
|
38
45
|
expect(message).toContain('PostgreSQL');
|
|
39
46
|
});
|
|
40
47
|
|
|
@@ -42,6 +49,7 @@ describe('Error Messages Utility', () => {
|
|
|
42
49
|
const message = getDatabaseUrlMissingError();
|
|
43
50
|
|
|
44
51
|
expect(message).toContain(DATABASE_URL_EXAMPLES.mongodb);
|
|
52
|
+
expect(message).toContain(DATABASE_URL_EXAMPLES.documentdb);
|
|
45
53
|
expect(message).toContain(DATABASE_URL_EXAMPLES.postgresql);
|
|
46
54
|
});
|
|
47
55
|
|
|
@@ -119,6 +127,14 @@ describe('Error Messages Utility', () => {
|
|
|
119
127
|
expect(message).toContain('27017');
|
|
120
128
|
});
|
|
121
129
|
|
|
130
|
+
it('should include DocumentDB-specific troubleshooting for DocumentDB', () => {
|
|
131
|
+
const message = getDatabaseConnectionError(mockError, 'documentdb');
|
|
132
|
+
|
|
133
|
+
expect(message).toContain('DocumentDB');
|
|
134
|
+
expect(message).toContain('retryWrites=false');
|
|
135
|
+
expect(message).toContain('global-bundle.pem');
|
|
136
|
+
});
|
|
137
|
+
|
|
122
138
|
it('should include PostgreSQL-specific troubleshooting for PostgreSQL', () => {
|
|
123
139
|
const message = getDatabaseConnectionError(mockError, 'postgresql');
|
|
124
140
|
|
|
@@ -150,6 +166,12 @@ describe('Error Messages Utility', () => {
|
|
|
150
166
|
expect(messagePostgres).toContain('Troubleshooting');
|
|
151
167
|
});
|
|
152
168
|
|
|
169
|
+
it('should display database name in the error output', () => {
|
|
170
|
+
const message = getDatabaseConnectionError(mockError, 'documentdb');
|
|
171
|
+
|
|
172
|
+
expect(message).toContain('AWS DocumentDB (MongoDB-compatible)');
|
|
173
|
+
});
|
|
174
|
+
|
|
153
175
|
it('should show DATABASE_URL when available', () => {
|
|
154
176
|
process.env.DATABASE_URL = 'mongodb://test';
|
|
155
177
|
const message = getDatabaseConnectionError(mockError, 'mongodb');
|
|
@@ -187,6 +209,13 @@ describe('Error Messages Utility', () => {
|
|
|
187
209
|
expect(message).toContain('@prisma-mongodb/client');
|
|
188
210
|
});
|
|
189
211
|
|
|
212
|
+
it('should mention Mongo client reuse for DocumentDB', () => {
|
|
213
|
+
const message = getPrismaClientNotGeneratedError('documentdb');
|
|
214
|
+
|
|
215
|
+
expect(message).toContain('@prisma-mongodb/client');
|
|
216
|
+
expect(message).toContain('DocumentDB reuses the MongoDB Prisma client');
|
|
217
|
+
});
|
|
218
|
+
|
|
190
219
|
it('should include correct client package name for PostgreSQL', () => {
|
|
191
220
|
const message = getPrismaClientNotGeneratedError('postgresql');
|
|
192
221
|
|
|
@@ -255,13 +284,19 @@ describe('Error Messages Utility', () => {
|
|
|
255
284
|
it('should include database type for MongoDB', () => {
|
|
256
285
|
const message = getDatabaseSetupSuccess('mongodb', 'development');
|
|
257
286
|
|
|
258
|
-
expect(message).toContain('
|
|
287
|
+
expect(message).toContain('MongoDB');
|
|
259
288
|
});
|
|
260
289
|
|
|
261
290
|
it('should include database type for PostgreSQL', () => {
|
|
262
291
|
const message = getDatabaseSetupSuccess('postgresql', 'production');
|
|
263
292
|
|
|
264
|
-
expect(message).toContain('
|
|
293
|
+
expect(message).toContain('PostgreSQL');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should include database type for DocumentDB', () => {
|
|
297
|
+
const message = getDatabaseSetupSuccess('documentdb', 'production');
|
|
298
|
+
|
|
299
|
+
expect(message).toContain('AWS DocumentDB (MongoDB-compatible)');
|
|
265
300
|
});
|
|
266
301
|
|
|
267
302
|
it('should include stage information', () => {
|
|
@@ -280,6 +315,12 @@ describe('Error Messages Utility', () => {
|
|
|
280
315
|
expect(postgresMessage).toContain('Migrations applied');
|
|
281
316
|
});
|
|
282
317
|
|
|
318
|
+
it('should mention DocumentDB-specific schema messaging', () => {
|
|
319
|
+
const message = getDatabaseSetupSuccess('documentdb', 'development');
|
|
320
|
+
|
|
321
|
+
expect(message).toContain('Schema pushed to DocumentDB');
|
|
322
|
+
});
|
|
323
|
+
|
|
283
324
|
it('should suggest next steps', () => {
|
|
284
325
|
const message = getDatabaseSetupSuccess('mongodb', 'development');
|
|
285
326
|
|
|
@@ -4,22 +4,39 @@ const dotenv = require('dotenv');
|
|
|
4
4
|
const {
|
|
5
5
|
validateDatabaseUrl,
|
|
6
6
|
getDatabaseType,
|
|
7
|
-
checkPrismaClientGenerated
|
|
7
|
+
checkPrismaClientGenerated,
|
|
8
8
|
} = require('../utils/database-validator');
|
|
9
9
|
const {
|
|
10
10
|
runPrismaGenerate,
|
|
11
11
|
checkDatabaseState,
|
|
12
12
|
runPrismaMigrate,
|
|
13
13
|
runPrismaDbPush,
|
|
14
|
-
getMigrationCommand
|
|
14
|
+
getMigrationCommand,
|
|
15
15
|
} = require('@friggframework/core/database/utils/prisma-runner');
|
|
16
16
|
const {
|
|
17
17
|
getDatabaseUrlMissingError,
|
|
18
18
|
getDatabaseTypeNotConfiguredError,
|
|
19
19
|
getPrismaCommandError,
|
|
20
|
-
getDatabaseSetupSuccess
|
|
20
|
+
getDatabaseSetupSuccess,
|
|
21
21
|
} = require('../utils/error-messages');
|
|
22
22
|
|
|
23
|
+
function getDatabaseDisplayName(dbType) {
|
|
24
|
+
switch (dbType) {
|
|
25
|
+
case 'postgresql':
|
|
26
|
+
return 'PostgreSQL';
|
|
27
|
+
case 'mongodb':
|
|
28
|
+
return 'MongoDB';
|
|
29
|
+
case 'documentdb':
|
|
30
|
+
return 'AWS DocumentDB (MongoDB-compatible)';
|
|
31
|
+
default:
|
|
32
|
+
return dbType;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPrismaRunnerDbType(dbType) {
|
|
37
|
+
return dbType === 'documentdb' ? 'mongodb' : dbType;
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
/**
|
|
24
41
|
* Database Setup Command
|
|
25
42
|
* Sets up the database for a Frigg application:
|
|
@@ -75,10 +92,16 @@ async function dbSetupCommand(options = {}) {
|
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
const dbType = dbTypeResult.dbType;
|
|
78
|
-
|
|
95
|
+
const dbDisplayName = getDatabaseDisplayName(dbType);
|
|
96
|
+
const isPostgres = dbType === 'postgresql';
|
|
97
|
+
const isDocumentDb = dbType === 'documentdb';
|
|
98
|
+
const isMongoFamily = dbType === 'mongodb' || isDocumentDb;
|
|
99
|
+
const runnerDbType = getPrismaRunnerDbType(dbType);
|
|
100
|
+
|
|
101
|
+
console.log(chalk.cyan(`Database type: ${dbDisplayName}`));
|
|
79
102
|
|
|
80
103
|
if (verbose) {
|
|
81
|
-
console.log(chalk.green(`✓ Using ${
|
|
104
|
+
console.log(chalk.green(`✓ Using ${dbDisplayName}\n`));
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
// Step 3: Check if Prisma client exists, generate if needed
|
|
@@ -91,22 +114,35 @@ async function dbSetupCommand(options = {}) {
|
|
|
91
114
|
|
|
92
115
|
if (clientCheck.generated && !forceRegenerate) {
|
|
93
116
|
// Client already exists and --force not specified
|
|
94
|
-
console.log(
|
|
117
|
+
console.log(
|
|
118
|
+
chalk.green(
|
|
119
|
+
'✓ Prisma client already exists (skipping generation)\n'
|
|
120
|
+
)
|
|
121
|
+
);
|
|
95
122
|
if (verbose) {
|
|
96
|
-
console.log(
|
|
123
|
+
console.log(
|
|
124
|
+
chalk.gray(` Client location: ${clientCheck.path}\n`)
|
|
125
|
+
);
|
|
97
126
|
}
|
|
98
127
|
} else {
|
|
99
128
|
// Client doesn't exist OR --force specified - generate it
|
|
100
129
|
if (forceRegenerate && clientCheck.generated) {
|
|
101
|
-
console.log(
|
|
130
|
+
console.log(
|
|
131
|
+
chalk.yellow('⚠️ Forcing Prisma client regeneration...')
|
|
132
|
+
);
|
|
102
133
|
} else {
|
|
103
134
|
console.log(chalk.cyan('Generating Prisma client...'));
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
const generateResult = await runPrismaGenerate(
|
|
137
|
+
const generateResult = await runPrismaGenerate(
|
|
138
|
+
runnerDbType,
|
|
139
|
+
verbose
|
|
140
|
+
);
|
|
107
141
|
|
|
108
142
|
if (!generateResult.success) {
|
|
109
|
-
console.error(
|
|
143
|
+
console.error(
|
|
144
|
+
getPrismaCommandError('generate', generateResult.error)
|
|
145
|
+
);
|
|
110
146
|
if (generateResult.output) {
|
|
111
147
|
console.error(chalk.gray(generateResult.output));
|
|
112
148
|
}
|
|
@@ -124,25 +160,32 @@ async function dbSetupCommand(options = {}) {
|
|
|
124
160
|
console.log(chalk.gray('Step 4: Checking database state...'));
|
|
125
161
|
}
|
|
126
162
|
|
|
127
|
-
const stateCheck = await checkDatabaseState(
|
|
163
|
+
const stateCheck = await checkDatabaseState(runnerDbType);
|
|
128
164
|
|
|
129
165
|
// Step 5: Run migrations or db push
|
|
130
|
-
if (
|
|
166
|
+
if (isPostgres) {
|
|
131
167
|
console.log(chalk.cyan('Running database migrations...'));
|
|
132
168
|
|
|
133
169
|
const migrationCommand = getMigrationCommand(stage);
|
|
134
170
|
|
|
135
171
|
if (verbose) {
|
|
136
|
-
console.log(
|
|
172
|
+
console.log(
|
|
173
|
+
chalk.gray(`Using migration command: ${migrationCommand}`)
|
|
174
|
+
);
|
|
137
175
|
}
|
|
138
176
|
|
|
139
177
|
if (stateCheck.upToDate && migrationCommand === 'deploy') {
|
|
140
178
|
console.log(chalk.yellow('Database is already up-to-date'));
|
|
141
179
|
} else {
|
|
142
|
-
const migrateResult = await runPrismaMigrate(
|
|
180
|
+
const migrateResult = await runPrismaMigrate(
|
|
181
|
+
migrationCommand,
|
|
182
|
+
verbose
|
|
183
|
+
);
|
|
143
184
|
|
|
144
185
|
if (!migrateResult.success) {
|
|
145
|
-
console.error(
|
|
186
|
+
console.error(
|
|
187
|
+
getPrismaCommandError('migrate', migrateResult.error)
|
|
188
|
+
);
|
|
146
189
|
if (migrateResult.output) {
|
|
147
190
|
console.error(chalk.gray(migrateResult.output));
|
|
148
191
|
}
|
|
@@ -151,14 +194,19 @@ async function dbSetupCommand(options = {}) {
|
|
|
151
194
|
|
|
152
195
|
console.log(chalk.green('✓ Migrations applied\n'));
|
|
153
196
|
}
|
|
197
|
+
} else if (isMongoFamily) {
|
|
198
|
+
const targetName = isDocumentDb
|
|
199
|
+
? 'AWS DocumentDB (MongoDB-compatible)'
|
|
200
|
+
: 'MongoDB';
|
|
154
201
|
|
|
155
|
-
|
|
156
|
-
console.log(chalk.cyan('Pushing schema to MongoDB...'));
|
|
202
|
+
console.log(chalk.cyan(`Pushing schema to ${targetName}...`));
|
|
157
203
|
|
|
158
204
|
const pushResult = await runPrismaDbPush(verbose);
|
|
159
205
|
|
|
160
206
|
if (!pushResult.success) {
|
|
161
|
-
console.error(
|
|
207
|
+
console.error(
|
|
208
|
+
getPrismaCommandError('db push', pushResult.error)
|
|
209
|
+
);
|
|
162
210
|
if (pushResult.output) {
|
|
163
211
|
console.error(chalk.gray(pushResult.output));
|
|
164
212
|
}
|
|
@@ -170,7 +218,6 @@ async function dbSetupCommand(options = {}) {
|
|
|
170
218
|
|
|
171
219
|
// Success!
|
|
172
220
|
console.log(getDatabaseSetupSuccess(dbType, stage));
|
|
173
|
-
|
|
174
221
|
} catch (error) {
|
|
175
222
|
console.error(chalk.red('\n❌ Database setup failed'));
|
|
176
223
|
console.error(chalk.gray(error.message));
|
|
@@ -182,9 +229,15 @@ async function dbSetupCommand(options = {}) {
|
|
|
182
229
|
|
|
183
230
|
console.error(chalk.yellow('\nTroubleshooting:'));
|
|
184
231
|
console.error(chalk.gray(' • Verify DATABASE_URL in your .env file'));
|
|
185
|
-
console.error(
|
|
186
|
-
|
|
187
|
-
|
|
232
|
+
console.error(
|
|
233
|
+
chalk.gray(' • Check database is running and accessible')
|
|
234
|
+
);
|
|
235
|
+
console.error(
|
|
236
|
+
chalk.gray(' • Ensure app definition has database configuration')
|
|
237
|
+
);
|
|
238
|
+
console.error(
|
|
239
|
+
chalk.gray(' • Run with --verbose flag for more details')
|
|
240
|
+
);
|
|
188
241
|
|
|
189
242
|
process.exit(1);
|
|
190
243
|
}
|
|
@@ -284,8 +284,10 @@ async function deployCommand(options) {
|
|
|
284
284
|
|
|
285
285
|
console.log('\n✓ Deployment completed successfully!');
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
const skipHealthCheck = options.skipDoctor || appDefinition?.deployment?.skipPostDeploymentHealthCheck;
|
|
288
|
+
|
|
289
|
+
// Run post-deployment health check (unless disabled)
|
|
290
|
+
if (!skipHealthCheck) {
|
|
289
291
|
const stackName = getStackName(appDefinition, options);
|
|
290
292
|
|
|
291
293
|
if (stackName) {
|
|
@@ -295,7 +297,8 @@ async function deployCommand(options) {
|
|
|
295
297
|
console.log(' Run "frigg doctor <stack-name>" manually to check stack health');
|
|
296
298
|
}
|
|
297
299
|
} else {
|
|
298
|
-
|
|
300
|
+
const reason = options.skipDoctor ? '--skip-doctor flag' : 'deployment.skipPostDeploymentHealthCheck: true';
|
|
301
|
+
console.log(`\n⏭️ Skipping post-deployment health check (${reason})`);
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
|
|
@@ -8,6 +8,16 @@ const { connectPrisma, disconnectPrisma } = require('@friggframework/core/databa
|
|
|
8
8
|
* Validates database configuration and connectivity for Frigg applications
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes MongoDB-compatible database types to 'mongodb'
|
|
13
|
+
* DocumentDB uses the same Prisma client as MongoDB
|
|
14
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
15
|
+
* @returns {'mongodb'|'postgresql'} Normalized database type
|
|
16
|
+
*/
|
|
17
|
+
function normalizeMongoCompatible(dbType) {
|
|
18
|
+
return dbType === 'documentdb' ? 'mongodb' : dbType;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
/**
|
|
12
22
|
* Validates that DATABASE_URL environment variable exists and has a value
|
|
13
23
|
* @returns {Object} { valid: boolean, url?: string, error?: string }
|
|
@@ -62,7 +72,7 @@ function getDatabaseType() {
|
|
|
62
72
|
* Uses the same Prisma client configuration as runtime
|
|
63
73
|
*
|
|
64
74
|
* @param {string} databaseUrl - Database connection URL (for validation purposes)
|
|
65
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type to determine appropriate health check
|
|
75
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type to determine appropriate health check
|
|
66
76
|
* @param {number} timeout - Connection timeout in milliseconds (default: 5000)
|
|
67
77
|
* @returns {Promise<Object>} { connected: boolean, error?: string }
|
|
68
78
|
*/
|
|
@@ -78,8 +88,8 @@ async function testDatabaseConnection(databaseUrl, dbType, timeout = 5000) {
|
|
|
78
88
|
const client = await Promise.race([connectPromise, timeoutPromise]);
|
|
79
89
|
|
|
80
90
|
// Test with database-appropriate health check
|
|
81
|
-
// MongoDB
|
|
82
|
-
if (dbType === 'mongodb') {
|
|
91
|
+
// MongoDB and DocumentDB don't support SQL, so we use the native ping command
|
|
92
|
+
if (dbType === 'mongodb' || dbType === 'documentdb') {
|
|
83
93
|
// Use MongoDB's native ping command via $runCommandRaw
|
|
84
94
|
await client.$runCommandRaw({ ping: 1 });
|
|
85
95
|
} else {
|
|
@@ -109,12 +119,15 @@ async function testDatabaseConnection(databaseUrl, dbType, timeout = 5000) {
|
|
|
109
119
|
* Checks if Prisma client is generated for the database type
|
|
110
120
|
* Checks for the generated client directory in @friggframework/core/generated
|
|
111
121
|
*
|
|
112
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type
|
|
122
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
113
123
|
* @param {string} projectRoot - Project root directory (used for require.resolve context)
|
|
114
124
|
* @returns {Object} { generated: boolean, path?: string, error?: string }
|
|
115
125
|
*/
|
|
116
126
|
function checkPrismaClientGenerated(dbType, projectRoot = process.cwd()) {
|
|
117
127
|
try {
|
|
128
|
+
// Normalize DocumentDB to MongoDB (they use the same Prisma client)
|
|
129
|
+
const normalizedType = normalizeMongoCompatible(dbType);
|
|
130
|
+
|
|
118
131
|
// Resolve where @friggframework/core actually is
|
|
119
132
|
// This handles file: dependencies and symlinks correctly
|
|
120
133
|
const corePackagePath = require.resolve('@friggframework/core', {
|
|
@@ -123,7 +136,7 @@ function checkPrismaClientGenerated(dbType, projectRoot = process.cwd()) {
|
|
|
123
136
|
const corePackageDir = path.dirname(corePackagePath);
|
|
124
137
|
|
|
125
138
|
// Check for the generated client directory (same path core uses)
|
|
126
|
-
const clientPath = path.join(corePackageDir, 'generated', `prisma-${
|
|
139
|
+
const clientPath = path.join(corePackageDir, 'generated', `prisma-${normalizedType}`);
|
|
127
140
|
const clientIndexPath = path.join(clientPath, 'index.js');
|
|
128
141
|
|
|
129
142
|
if (fs.existsSync(clientIndexPath)) {
|
|
@@ -6,13 +6,37 @@ const chalk = require('chalk');
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Normalizes MongoDB-compatible database types to 'mongodb'
|
|
10
|
+
* DocumentDB uses the same Prisma client as MongoDB
|
|
11
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
12
|
+
* @returns {'mongodb'|'postgresql'} Normalized database type
|
|
13
|
+
*/
|
|
14
|
+
function normalizeMongoCompatible(dbType) {
|
|
15
|
+
return dbType === 'documentdb' ? 'mongodb' : dbType;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Database URL examples for supported database types
|
|
10
20
|
*/
|
|
11
21
|
const DATABASE_URL_EXAMPLES = {
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
mongodb: 'mongodb://localhost:27017/frigg?replicaSet=rs0',
|
|
23
|
+
documentdb: 'mongodb://frigg-user:yourPassword@docdb-cluster.cluster-xyz123.us-east-1.docdb.amazonaws.com:27017/frigg?tls=true&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false',
|
|
24
|
+
postgresql: 'postgresql://postgres:postgres@localhost:5432/frigg?schema=public'
|
|
14
25
|
};
|
|
15
26
|
|
|
27
|
+
function getDatabaseDisplayName(dbType) {
|
|
28
|
+
switch (dbType) {
|
|
29
|
+
case 'mongodb':
|
|
30
|
+
return 'MongoDB';
|
|
31
|
+
case 'documentdb':
|
|
32
|
+
return 'AWS DocumentDB (MongoDB-compatible)';
|
|
33
|
+
case 'postgresql':
|
|
34
|
+
return 'PostgreSQL';
|
|
35
|
+
default:
|
|
36
|
+
return dbType;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
16
40
|
/**
|
|
17
41
|
* Gets helpful error message for missing DATABASE_URL
|
|
18
42
|
* @returns {string} Formatted error message
|
|
@@ -26,6 +50,9 @@ ${chalk.bold('Add DATABASE_URL to your .env file:')}
|
|
|
26
50
|
${chalk.cyan('For MongoDB:')}
|
|
27
51
|
${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.mongodb}"`)}
|
|
28
52
|
|
|
53
|
+
${chalk.cyan('For AWS DocumentDB (MongoDB-compatible):')}
|
|
54
|
+
${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.documentdb}"`)}
|
|
55
|
+
|
|
29
56
|
${chalk.cyan('For PostgreSQL:')}
|
|
30
57
|
${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.postgresql}"`)}
|
|
31
58
|
|
|
@@ -73,19 +100,26 @@ const appDefinition = {
|
|
|
73
100
|
}
|
|
74
101
|
};
|
|
75
102
|
`)}
|
|
103
|
+
|
|
104
|
+
${chalk.gray('DocumentDB uses the MongoDB Prisma client. Make sure TLS is enabled and replica set compatibility is configured on your cluster.')}
|
|
76
105
|
`;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
/**
|
|
80
109
|
* Gets helpful error message for database connection failure
|
|
81
110
|
* @param {string} error - Connection error message
|
|
82
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type
|
|
111
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
83
112
|
* @returns {string} Formatted error message
|
|
84
113
|
*/
|
|
85
114
|
function getDatabaseConnectionError(error, dbType) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
let troubleshootingSteps;
|
|
116
|
+
if (dbType === 'documentdb') {
|
|
117
|
+
troubleshootingSteps = getDocumentDbTroubleshooting();
|
|
118
|
+
} else if (dbType === 'mongodb') {
|
|
119
|
+
troubleshootingSteps = getMongoDatabaseTroubleshooting();
|
|
120
|
+
} else {
|
|
121
|
+
troubleshootingSteps = getPostgresTroubleshooting();
|
|
122
|
+
}
|
|
89
123
|
|
|
90
124
|
return `
|
|
91
125
|
${chalk.red('❌ Failed to connect to database')}
|
|
@@ -93,6 +127,8 @@ ${chalk.red('❌ Failed to connect to database')}
|
|
|
93
127
|
${chalk.bold('Connection error:')}
|
|
94
128
|
${chalk.gray(error)}
|
|
95
129
|
|
|
130
|
+
${chalk.bold('Database:')} ${chalk.cyan(getDatabaseDisplayName(dbType))}
|
|
131
|
+
|
|
96
132
|
${chalk.bold('Troubleshooting steps:')}
|
|
97
133
|
${troubleshootingSteps}
|
|
98
134
|
|
|
@@ -128,6 +164,26 @@ ${chalk.gray('5.')} Check network/firewall settings
|
|
|
128
164
|
`;
|
|
129
165
|
}
|
|
130
166
|
|
|
167
|
+
function getDocumentDbTroubleshooting() {
|
|
168
|
+
return `
|
|
169
|
+
${chalk.gray('1.')} Verify DocumentDB cluster is available
|
|
170
|
+
${chalk.cyan('aws docdb describe-db-clusters --db-cluster-identifier <name>')}
|
|
171
|
+
|
|
172
|
+
${chalk.gray('2.')} Use TLS with the AWS CA bundle
|
|
173
|
+
${chalk.gray('Download: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem')}
|
|
174
|
+
${chalk.gray('Add ?tls=true&tlsCAFile=/path/to/global-bundle.pem to DATABASE_URL')}
|
|
175
|
+
|
|
176
|
+
${chalk.gray('3.')} Disable retryable writes (DocumentDB limitation)
|
|
177
|
+
${chalk.gray('Add retryWrites=false to DATABASE_URL query string')}
|
|
178
|
+
|
|
179
|
+
${chalk.gray('4.')} Include replicaSet parameter
|
|
180
|
+
${chalk.gray('DocumentDB requires replicaSet=rs0 even for single-node clusters')}
|
|
181
|
+
|
|
182
|
+
${chalk.gray('5.')} Allow inbound access from your Lambda or local IP
|
|
183
|
+
${chalk.gray('Update security groups / VPC rules for port 27017')}
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
/**
|
|
132
188
|
* Gets PostgreSQL-specific troubleshooting steps
|
|
133
189
|
* @returns {string} Formatted troubleshooting steps
|
|
@@ -157,11 +213,19 @@ ${chalk.gray('5.')} Verify network/firewall settings
|
|
|
157
213
|
|
|
158
214
|
/**
|
|
159
215
|
* Gets helpful error message for missing Prisma client
|
|
160
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type
|
|
216
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
161
217
|
* @returns {string} Formatted error message
|
|
162
218
|
*/
|
|
163
219
|
function getPrismaClientNotGeneratedError(dbType) {
|
|
164
|
-
|
|
220
|
+
// Normalize DocumentDB to MongoDB (they use the same Prisma client)
|
|
221
|
+
const normalizedType = normalizeMongoCompatible(dbType);
|
|
222
|
+
const clientName = `@prisma-${normalizedType}/client`;
|
|
223
|
+
const documentDbNote = dbType === 'documentdb'
|
|
224
|
+
? [
|
|
225
|
+
chalk.gray(' • DocumentDB reuses the MongoDB Prisma client (prisma-mongodb)'),
|
|
226
|
+
chalk.gray(' • Confirm your connection string includes tls=true and retryWrites=false')
|
|
227
|
+
].join('\n')
|
|
228
|
+
: '';
|
|
165
229
|
|
|
166
230
|
return `
|
|
167
231
|
${chalk.red(`❌ Prisma client not generated for ${dbType}`)}
|
|
@@ -174,6 +238,7 @@ ${chalk.gray('This will:')}
|
|
|
174
238
|
${chalk.gray(' • Generate the Prisma client')} ${chalk.gray(`(${clientName})`)}
|
|
175
239
|
${chalk.gray(' • Set up database schema')}
|
|
176
240
|
${chalk.gray(' • Run migrations (PostgreSQL) or db push (MongoDB)')}
|
|
241
|
+
${documentDbNote ? `${documentDbNote}\n` : ''}
|
|
177
242
|
`;
|
|
178
243
|
}
|
|
179
244
|
|
|
@@ -205,23 +270,30 @@ ${chalk.cyan(' Review Prisma schema')} ${chalk.gray('(node_modules/@friggframew
|
|
|
205
270
|
|
|
206
271
|
/**
|
|
207
272
|
* Gets success message for database setup completion
|
|
208
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type
|
|
273
|
+
* @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
|
|
209
274
|
* @param {string} stage - Deployment stage
|
|
210
275
|
* @returns {string} Formatted success message
|
|
211
276
|
*/
|
|
212
277
|
function getDatabaseSetupSuccess(dbType, stage) {
|
|
278
|
+
const databaseDisplayName = getDatabaseDisplayName(dbType);
|
|
279
|
+
const schemaAction = dbType === 'postgresql'
|
|
280
|
+
? 'Migrations applied'
|
|
281
|
+
: dbType === 'documentdb'
|
|
282
|
+
? 'Schema pushed to DocumentDB'
|
|
283
|
+
: 'Schema pushed to database';
|
|
284
|
+
|
|
213
285
|
return `
|
|
214
286
|
${chalk.green('✅ Database setup completed successfully!')}
|
|
215
287
|
|
|
216
288
|
${chalk.bold('Configuration:')}
|
|
217
|
-
${chalk.gray(' Database type:')} ${chalk.cyan(
|
|
289
|
+
${chalk.gray(' Database type:')} ${chalk.cyan(databaseDisplayName)}
|
|
218
290
|
${chalk.gray(' Stage:')} ${chalk.cyan(stage)}
|
|
219
291
|
${chalk.gray(' Connection:')} ${chalk.green('verified')}
|
|
220
292
|
|
|
221
293
|
${chalk.bold('What happened:')}
|
|
222
294
|
${chalk.gray(' ✓')} Prisma client generated
|
|
223
295
|
${chalk.gray(' ✓')} Database connection verified
|
|
224
|
-
${chalk.gray(' ✓')} ${
|
|
296
|
+
${chalk.gray(' ✓')} ${schemaAction}
|
|
225
297
|
|
|
226
298
|
${chalk.yellow('Next steps:')}
|
|
227
299
|
${chalk.cyan(' frigg start')} ${chalk.gray('(start your application)')}
|