@friggframework/devtools 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.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.
Files changed (127) hide show
  1. package/frigg-cli/README.md +1 -1
  2. package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
  3. package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
  4. package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
  5. package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
  6. package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
  7. package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
  8. package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
  9. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
  10. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
  11. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
  12. package/frigg-cli/__tests__/unit/commands/build.test.js +1 -1
  13. package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
  14. package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
  15. package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
  16. package/frigg-cli/__tests__/unit/commands/provider-dispatch.test.js +383 -0
  17. package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
  18. package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
  19. package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
  20. package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
  21. package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
  22. package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
  23. package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
  24. package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
  25. package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
  26. package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
  27. package/frigg-cli/build-command/index.js +123 -11
  28. package/frigg-cli/container.js +172 -0
  29. package/frigg-cli/deploy-command/index.js +83 -1
  30. package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
  31. package/frigg-cli/doctor-command/index.js +37 -16
  32. package/frigg-cli/domain/entities/ApiModule.js +272 -0
  33. package/frigg-cli/domain/entities/AppDefinition.js +227 -0
  34. package/frigg-cli/domain/entities/Integration.js +198 -0
  35. package/frigg-cli/domain/exceptions/DomainException.js +24 -0
  36. package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
  37. package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
  38. package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
  39. package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
  40. package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
  41. package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
  42. package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
  43. package/frigg-cli/generate-iam-command.js +21 -1
  44. package/frigg-cli/index.js +21 -6
  45. package/frigg-cli/index.test.js +7 -2
  46. package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
  47. package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
  48. package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
  49. package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
  50. package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
  51. package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
  52. package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
  53. package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
  54. package/frigg-cli/init-command/backend-first-handler.js +124 -42
  55. package/frigg-cli/init-command/index.js +2 -1
  56. package/frigg-cli/init-command/template-handler.js +13 -3
  57. package/frigg-cli/install-command/backend-js.js +3 -3
  58. package/frigg-cli/install-command/environment-variables.js +16 -19
  59. package/frigg-cli/install-command/environment-variables.test.js +12 -13
  60. package/frigg-cli/install-command/index.js +14 -9
  61. package/frigg-cli/install-command/integration-file.js +3 -3
  62. package/frigg-cli/install-command/validate-package.js +5 -9
  63. package/frigg-cli/jest.config.js +4 -1
  64. package/frigg-cli/package-lock.json +16226 -0
  65. package/frigg-cli/repair-command/index.js +121 -128
  66. package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
  67. package/frigg-cli/start-command/index.js +324 -2
  68. package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
  69. package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
  70. package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
  71. package/frigg-cli/templates/backend/.env.example +62 -0
  72. package/frigg-cli/templates/backend/.eslintrc.json +12 -0
  73. package/frigg-cli/templates/backend/.prettierrc +6 -0
  74. package/frigg-cli/templates/backend/docker-compose.yml +22 -0
  75. package/frigg-cli/templates/backend/index.js +96 -0
  76. package/frigg-cli/templates/backend/infrastructure.js +12 -0
  77. package/frigg-cli/templates/backend/jest.config.js +17 -0
  78. package/frigg-cli/templates/backend/package.json +50 -0
  79. package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
  80. package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
  81. package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
  82. package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
  83. package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
  84. package/frigg-cli/templates/backend/test/setup.js +30 -0
  85. package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
  86. package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
  87. package/frigg-cli/ui-command/index.js +58 -36
  88. package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
  89. package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
  90. package/frigg-cli/utils/output.js +382 -0
  91. package/frigg-cli/utils/provider-helper.js +75 -0
  92. package/frigg-cli/utils/repo-detection.js +85 -37
  93. package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
  94. package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
  95. package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
  96. package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
  97. package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
  98. package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
  99. package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
  100. package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
  101. package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
  102. package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
  103. package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
  104. package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
  105. package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
  106. package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
  107. package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
  108. package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +128 -0
  109. package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
  110. package/infrastructure/create-frigg-infrastructure.js +93 -0
  111. package/infrastructure/docs/iam-policy-templates.md +1 -1
  112. package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
  113. package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
  114. package/infrastructure/domains/admin-scripts/index.js +5 -0
  115. package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
  116. package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
  117. package/infrastructure/domains/shared/resource-discovery.js +5 -5
  118. package/infrastructure/domains/shared/types/app-definition.js +21 -0
  119. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  120. package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
  121. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  122. package/infrastructure/infrastructure-composer.js +2 -0
  123. package/infrastructure/infrastructure-composer.test.js +2 -2
  124. package/infrastructure/jest.config.js +16 -0
  125. package/management-ui/README.md +245 -109
  126. package/package.json +8 -7
  127. package/frigg-cli/install-command/logger.js +0 -12
@@ -0,0 +1,591 @@
1
+ /**
2
+ * DatabaseAdapter - Infrastructure adapter for database connectivity checks
3
+ *
4
+ * Provides methods to parse connection strings and test database reachability
5
+ * Used by pre-flight checks to verify database infrastructure is ready
6
+ */
7
+
8
+ const net = require('net');
9
+ const { spawn } = require('child_process');
10
+ const path = require('path');
11
+
12
+ class DatabaseAdapter {
13
+ /**
14
+ * Parse a database connection string
15
+ * @param {string} connectionString - Database URL
16
+ * @returns {object} Parsed connection details or error
17
+ */
18
+ parseConnectionString(connectionString) {
19
+ if (!connectionString || typeof connectionString !== 'string') {
20
+ return { error: 'Invalid connection string: must be a non-empty string' };
21
+ }
22
+
23
+ try {
24
+ // Handle mongodb+srv:// protocol separately (URL doesn't parse it well)
25
+ if (connectionString.startsWith('mongodb+srv://')) {
26
+ return this._parseMongoSrv(connectionString);
27
+ }
28
+
29
+ // Handle MongoDB replica set connection strings with multiple hosts
30
+ // mongodb://host1:port1,host2:port2,host3:port3/database
31
+ if (connectionString.startsWith('mongodb://') && connectionString.includes(',')) {
32
+ return this._parseMongoReplicaSet(connectionString);
33
+ }
34
+
35
+ const url = new URL(connectionString);
36
+ const protocol = url.protocol.replace(':', '');
37
+
38
+ // Determine database type
39
+ let type;
40
+ let defaultPort;
41
+ if (protocol === 'mongodb') {
42
+ type = 'mongodb';
43
+ defaultPort = 27017;
44
+ } else if (protocol === 'postgresql' || protocol === 'postgres') {
45
+ type = 'postgresql';
46
+ defaultPort = 5432;
47
+ } else {
48
+ return { error: `Unsupported database type: ${protocol}` };
49
+ }
50
+
51
+ // Extract first host from potential replica set
52
+ const host = url.hostname.split(',')[0];
53
+ const port = url.port ? parseInt(url.port, 10) : defaultPort;
54
+
55
+ // Extract database name from pathname
56
+ const database = url.pathname.replace('/', '') || undefined;
57
+
58
+ // Extract user if present
59
+ const user = url.username || undefined;
60
+
61
+ return {
62
+ type,
63
+ host,
64
+ port,
65
+ database,
66
+ user
67
+ };
68
+ } catch (error) {
69
+ return { error: `Failed to parse connection string: ${error.message}` };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Parse MongoDB replica set connection string with multiple hosts
75
+ * @param {string} connectionString - MongoDB replica set connection string
76
+ * @returns {object} Parsed connection details
77
+ */
78
+ _parseMongoReplicaSet(connectionString) {
79
+ try {
80
+ // mongodb://user:pass@host1:port1,host2:port2/database?options
81
+ const withoutProtocol = connectionString.replace('mongodb://', '');
82
+
83
+ // Extract user:pass if present
84
+ let hostsPart = withoutProtocol;
85
+ let user;
86
+ if (withoutProtocol.includes('@')) {
87
+ const atIndex = withoutProtocol.indexOf('@');
88
+ const credentials = withoutProtocol.substring(0, atIndex);
89
+ hostsPart = withoutProtocol.substring(atIndex + 1);
90
+ user = credentials.split(':')[0];
91
+ }
92
+
93
+ // Extract database and options
94
+ let database;
95
+ const slashIndex = hostsPart.indexOf('/');
96
+ if (slashIndex !== -1) {
97
+ const pathPart = hostsPart.substring(slashIndex + 1);
98
+ hostsPart = hostsPart.substring(0, slashIndex);
99
+ database = pathPart.split('?')[0] || undefined;
100
+ }
101
+
102
+ // Get first host:port pair
103
+ const firstHost = hostsPart.split(',')[0];
104
+ const [host, portStr] = firstHost.split(':');
105
+ const port = portStr ? parseInt(portStr, 10) : 27017;
106
+
107
+ return {
108
+ type: 'mongodb',
109
+ host,
110
+ port,
111
+ database,
112
+ user
113
+ };
114
+ } catch (error) {
115
+ return { error: `Failed to parse MongoDB replica set connection string: ${error.message}` };
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Parse mongodb+srv:// connection string
121
+ * @param {string} connectionString - MongoDB SRV connection string
122
+ * @returns {object} Parsed connection details
123
+ */
124
+ _parseMongoSrv(connectionString) {
125
+ try {
126
+ // Replace mongodb+srv with https for URL parsing
127
+ const url = new URL(connectionString.replace('mongodb+srv://', 'https://'));
128
+
129
+ const host = url.hostname;
130
+ const database = url.pathname.replace('/', '') || undefined;
131
+ const user = url.username || undefined;
132
+
133
+ return {
134
+ type: 'mongodb',
135
+ host,
136
+ port: 27017, // SRV uses standard port
137
+ database,
138
+ user
139
+ };
140
+ } catch (error) {
141
+ return { error: `Failed to parse MongoDB SRV connection string: ${error.message}` };
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Get database type from connection string
147
+ * @param {string} connectionString - Database URL
148
+ * @returns {string|null} 'mongodb' | 'postgresql' | null
149
+ */
150
+ getDatabaseType(connectionString) {
151
+ if (!connectionString) return null;
152
+
153
+ if (connectionString.startsWith('mongodb://') || connectionString.startsWith('mongodb+srv://')) {
154
+ return 'mongodb';
155
+ }
156
+ if (connectionString.startsWith('postgresql://') || connectionString.startsWith('postgres://')) {
157
+ return 'postgresql';
158
+ }
159
+ return null;
160
+ }
161
+
162
+ /**
163
+ * Check if a TCP port is reachable
164
+ * @param {string} host - Host to connect to
165
+ * @param {number} port - Port to connect to
166
+ * @param {number} timeout - Timeout in milliseconds (default: 3000)
167
+ * @returns {Promise<boolean>} True if port is reachable
168
+ */
169
+ async isPortReachable(host, port, timeout = 3000) {
170
+ return new Promise((resolve) => {
171
+ const socket = net.createConnection(port, host);
172
+
173
+ socket.setTimeout(timeout);
174
+
175
+ socket.on('connect', () => {
176
+ socket.destroy();
177
+ resolve(true);
178
+ });
179
+
180
+ socket.on('timeout', () => {
181
+ socket.destroy();
182
+ resolve(false);
183
+ });
184
+
185
+ socket.on('error', () => {
186
+ socket.destroy();
187
+ resolve(false);
188
+ });
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Check if database is reachable at the connection string
194
+ * @param {string} connectionString - Database URL
195
+ * @param {number} timeout - Timeout in milliseconds
196
+ * @returns {Promise<{reachable: boolean, host?: string, port?: number, type?: string, error?: string}>}
197
+ */
198
+ async isDatabaseReachable(connectionString, timeout = 3000) {
199
+ const parsed = this.parseConnectionString(connectionString);
200
+
201
+ if (parsed.error) {
202
+ return {
203
+ reachable: false,
204
+ error: parsed.error
205
+ };
206
+ }
207
+
208
+ const { type, host, port } = parsed;
209
+
210
+ try {
211
+ const reachable = await this.isPortReachable(host, port, timeout);
212
+
213
+ if (reachable) {
214
+ return {
215
+ reachable: true,
216
+ type,
217
+ host,
218
+ port
219
+ };
220
+ } else {
221
+ return {
222
+ reachable: false,
223
+ type,
224
+ host,
225
+ port,
226
+ error: `ECONNREFUSED - Unable to connect to ${host}:${port}`
227
+ };
228
+ }
229
+ } catch (error) {
230
+ return {
231
+ reachable: false,
232
+ type,
233
+ host,
234
+ port,
235
+ error: error.message
236
+ };
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Get connection details summary
242
+ * @param {string} connectionString - Database URL
243
+ * @returns {object} Connection details or error
244
+ */
245
+ getConnectionDetails(connectionString) {
246
+ const parsed = this.parseConnectionString(connectionString);
247
+
248
+ if (parsed.error) {
249
+ return { error: parsed.error };
250
+ }
251
+
252
+ return {
253
+ type: parsed.type,
254
+ host: parsed.host,
255
+ port: parsed.port,
256
+ database: parsed.database,
257
+ user: parsed.user,
258
+ hasCredentials: !!parsed.user
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Suggest Docker service configuration for a database type
264
+ * @param {string} dbType - 'mongodb' | 'postgresql'
265
+ * @returns {object|null} Docker service suggestion or null
266
+ */
267
+ suggestDockerService(dbType) {
268
+ if (dbType === 'mongodb') {
269
+ return {
270
+ serviceName: 'mongodb',
271
+ image: 'mongo:7',
272
+ port: 27017,
273
+ envVars: {
274
+ MONGO_INITDB_DATABASE: 'frigg'
275
+ }
276
+ };
277
+ }
278
+
279
+ if (dbType === 'postgresql') {
280
+ return {
281
+ serviceName: 'postgres',
282
+ image: 'postgres:16',
283
+ port: 5432,
284
+ envVars: {
285
+ POSTGRES_DB: 'frigg',
286
+ POSTGRES_USER: 'postgres',
287
+ POSTGRES_PASSWORD: 'postgres'
288
+ }
289
+ };
290
+ }
291
+
292
+ return null;
293
+ }
294
+
295
+ /**
296
+ * Check PostgreSQL migration status using Prisma
297
+ * @param {string} projectPath - Path to the project
298
+ * @returns {Promise<{migrated: boolean, pendingMigrations?: string[], error?: string}>}
299
+ */
300
+ async checkMigrationStatus(projectPath) {
301
+ return new Promise((resolve) => {
302
+ // Look for prisma schema in common locations
303
+ // Frigg apps use the schema from @friggframework/core
304
+ const possibleSchemaPaths = [
305
+ // Project-local prisma schemas
306
+ path.join(projectPath, 'prisma', 'postgresql', 'schema.prisma'),
307
+ path.join(projectPath, 'prisma', 'schema.prisma'),
308
+ // Frigg core schemas (in node_modules)
309
+ path.join(projectPath, 'node_modules', '@friggframework', 'core', 'prisma-postgresql', 'schema.prisma'),
310
+ path.join(projectPath, 'node_modules', '@friggframework', 'core', 'generated', 'prisma-postgresql', 'schema.prisma')
311
+ ];
312
+
313
+ // Find the first existing schema path
314
+ const fs = require('fs');
315
+ let schemaPath = null;
316
+ for (const p of possibleSchemaPaths) {
317
+ if (fs.existsSync(p)) {
318
+ schemaPath = p;
319
+ break;
320
+ }
321
+ }
322
+
323
+ if (!schemaPath) {
324
+ // No schema found - might be MongoDB project
325
+ resolve({ migrated: true, note: 'No Prisma PostgreSQL schema found' });
326
+ return;
327
+ }
328
+
329
+ // Find prisma binary - check local node_modules first
330
+ const possiblePrismaPaths = [
331
+ path.join(projectPath, 'node_modules', '.bin', 'prisma'),
332
+ path.join(projectPath, 'node_modules', 'prisma', 'build', 'index.js')
333
+ ];
334
+
335
+ let prismaBin = 'npx';
336
+ let args = ['prisma', 'migrate', 'status', '--schema', schemaPath];
337
+
338
+ // Try to find local prisma binary for pnpm/yarn workspaces
339
+ for (const p of possiblePrismaPaths) {
340
+ if (fs.existsSync(p)) {
341
+ if (p.endsWith('.js')) {
342
+ prismaBin = 'node';
343
+ args = [p, 'migrate', 'status', '--schema', schemaPath];
344
+ } else {
345
+ prismaBin = p;
346
+ args = ['migrate', 'status', '--schema', schemaPath];
347
+ }
348
+ break;
349
+ }
350
+ }
351
+
352
+ const child = spawn(prismaBin, args, {
353
+ cwd: projectPath,
354
+ env: { ...process.env },
355
+ stdio: ['pipe', 'pipe', 'pipe'],
356
+ shell: prismaBin === 'npx' // Use shell for npx to help with path resolution
357
+ });
358
+
359
+ let stdout = '';
360
+ let stderr = '';
361
+
362
+ child.stdout.on('data', (data) => {
363
+ stdout += data.toString();
364
+ });
365
+
366
+ child.stderr.on('data', (data) => {
367
+ stderr += data.toString();
368
+ });
369
+
370
+ child.on('error', (error) => {
371
+ resolve({
372
+ migrated: false,
373
+ error: `Failed to run prisma migrate status: ${error.message}`
374
+ });
375
+ });
376
+
377
+ child.on('close', (code) => {
378
+ // Parse the output to determine migration status
379
+ const output = stdout + stderr;
380
+
381
+ // Check for Prisma not found / not installed error
382
+ if (output.includes('Cannot find module') && output.includes('prisma')) {
383
+ resolve({
384
+ migrated: false,
385
+ error: 'Prisma CLI not properly installed. Run "npm install" or "pnpm install" to fix.',
386
+ needsInstall: true
387
+ });
388
+ return;
389
+ }
390
+
391
+ // Check for common error patterns
392
+ if (output.includes('P1001') || output.includes('Can\'t reach database server')) {
393
+ resolve({
394
+ migrated: false,
395
+ error: 'Cannot connect to database to check migrations'
396
+ });
397
+ return;
398
+ }
399
+
400
+ if (output.includes('P1003') || output.includes('does not exist')) {
401
+ resolve({
402
+ migrated: false,
403
+ error: 'Database does not exist',
404
+ needsSetup: true
405
+ });
406
+ return;
407
+ }
408
+
409
+ // Check for "Database schema is not empty" - tables exist but no migrations
410
+ if (output.includes('Database schema is not empty')) {
411
+ resolve({
412
+ migrated: false,
413
+ error: 'Database has tables but no migration history. Run prisma migrate to baseline.',
414
+ needsBaseline: true
415
+ });
416
+ return;
417
+ }
418
+
419
+ // Check for pending migrations
420
+ if (output.includes('Following migration') && output.includes('have not yet been applied')) {
421
+ const pendingMatch = output.match(/Following migration[s]? have not yet been applied:\s*([\s\S]*?)(?:To apply|$)/);
422
+ const pendingMigrations = pendingMatch
423
+ ? pendingMatch[1].trim().split('\n').map(m => m.trim()).filter(Boolean)
424
+ : [];
425
+
426
+ resolve({
427
+ migrated: false,
428
+ pendingMigrations,
429
+ error: 'Database has pending migrations'
430
+ });
431
+ return;
432
+ }
433
+
434
+ // Check for "no migration found" - empty migrations folder
435
+ if (output.includes('No migration found') || output.includes('Database schema is up to date')) {
436
+ resolve({ migrated: true });
437
+ return;
438
+ }
439
+
440
+ // Check for table not found errors (P2021)
441
+ if (output.includes('P2021') || output.includes('does not exist in the current database')) {
442
+ resolve({
443
+ migrated: false,
444
+ error: 'Required database tables do not exist. Run migrations first.',
445
+ needsSetup: true
446
+ });
447
+ return;
448
+ }
449
+
450
+ // If exit code is 0 and no error patterns, assume migrated
451
+ if (code === 0) {
452
+ resolve({ migrated: true });
453
+ return;
454
+ }
455
+
456
+ // Unknown error
457
+ resolve({
458
+ migrated: false,
459
+ error: output.trim() || `prisma migrate status exited with code ${code}`
460
+ });
461
+ });
462
+
463
+ // Timeout after 30 seconds
464
+ setTimeout(() => {
465
+ child.kill();
466
+ resolve({
467
+ migrated: false,
468
+ error: 'Timeout checking migration status'
469
+ });
470
+ }, 30000);
471
+ });
472
+ }
473
+
474
+ /**
475
+ * Run Prisma migrations
476
+ * @param {string} projectPath - Path to the project
477
+ * @param {object} options - Options
478
+ * @param {boolean} options.dev - Use dev mode (interactive, can create migrations)
479
+ * @returns {Promise<{success: boolean, output?: string, error?: string}>}
480
+ */
481
+ async runMigrations(projectPath, options = {}) {
482
+ return new Promise((resolve) => {
483
+ const fs = require('fs');
484
+
485
+ // Look for prisma schema
486
+ // Frigg apps use the schema from @friggframework/core
487
+ const possibleSchemaPaths = [
488
+ // Project-local prisma schemas
489
+ path.join(projectPath, 'prisma', 'postgresql', 'schema.prisma'),
490
+ path.join(projectPath, 'prisma', 'schema.prisma'),
491
+ // Frigg core schemas (in node_modules)
492
+ path.join(projectPath, 'node_modules', '@friggframework', 'core', 'prisma-postgresql', 'schema.prisma'),
493
+ path.join(projectPath, 'node_modules', '@friggframework', 'core', 'generated', 'prisma-postgresql', 'schema.prisma')
494
+ ];
495
+
496
+ let schemaPath = null;
497
+ for (const p of possibleSchemaPaths) {
498
+ if (fs.existsSync(p)) {
499
+ schemaPath = p;
500
+ break;
501
+ }
502
+ }
503
+
504
+ if (!schemaPath) {
505
+ resolve({ success: false, error: 'No Prisma schema found' });
506
+ return;
507
+ }
508
+
509
+ // Find prisma binary - check local node_modules first
510
+ const possiblePrismaPaths = [
511
+ path.join(projectPath, 'node_modules', '.bin', 'prisma'),
512
+ path.join(projectPath, 'node_modules', 'prisma', 'build', 'index.js')
513
+ ];
514
+
515
+ let prismaBin = 'npx';
516
+ // Default to 'deploy' mode - non-interactive, just applies existing migrations
517
+ // Use 'dev' only if explicitly requested (which requires interactive terminal)
518
+ const migrateCommand = options.dev ? 'dev' : 'deploy';
519
+ let args = ['prisma', 'migrate', migrateCommand, '--schema', schemaPath];
520
+
521
+ // Try to find local prisma binary for pnpm/yarn workspaces
522
+ for (const p of possiblePrismaPaths) {
523
+ if (fs.existsSync(p)) {
524
+ if (p.endsWith('.js')) {
525
+ prismaBin = 'node';
526
+ args = [p, 'migrate', migrateCommand, '--schema', schemaPath];
527
+ } else {
528
+ prismaBin = p;
529
+ args = ['migrate', migrateCommand, '--schema', schemaPath];
530
+ }
531
+ break;
532
+ }
533
+ }
534
+
535
+ console.log(` Executing: ${prismaBin} ${args.join(' ')}`);
536
+
537
+ const child = spawn(prismaBin, args, {
538
+ cwd: projectPath,
539
+ env: { ...process.env },
540
+ stdio: ['pipe', 'pipe', 'pipe'],
541
+ shell: prismaBin === 'npx'
542
+ });
543
+
544
+ let stdout = '';
545
+ let stderr = '';
546
+
547
+ child.stdout.on('data', (data) => {
548
+ const text = data.toString();
549
+ stdout += text;
550
+ // Log progress in real-time
551
+ process.stdout.write(text);
552
+ });
553
+
554
+ child.stderr.on('data', (data) => {
555
+ const text = data.toString();
556
+ stderr += text;
557
+ // Log errors in real-time
558
+ process.stderr.write(text);
559
+ });
560
+
561
+ child.on('error', (error) => {
562
+ resolve({
563
+ success: false,
564
+ error: `Failed to run migrations: ${error.message}`
565
+ });
566
+ });
567
+
568
+ child.on('close', (code) => {
569
+ if (code === 0) {
570
+ resolve({ success: true, output: stdout });
571
+ } else {
572
+ resolve({
573
+ success: false,
574
+ error: stderr || stdout || `Migration failed with exit code ${code}`
575
+ });
576
+ }
577
+ });
578
+
579
+ // Timeout after 60 seconds for migrations (deploy mode is fast)
580
+ setTimeout(() => {
581
+ child.kill();
582
+ resolve({
583
+ success: false,
584
+ error: 'Timeout running migrations (60s)'
585
+ });
586
+ }, 60000);
587
+ });
588
+ }
589
+ }
590
+
591
+ module.exports = { DatabaseAdapter };