@friggframework/core 2.0.0--canary.463.62579dd.0 → 2.0.0--canary.461.84ff4f5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,377 @@
1
+ const { execSync, spawn } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const chalk = require('chalk');
5
+
6
+ /**
7
+ * Prisma Command Runner Utility
8
+ * Handles execution of Prisma CLI commands for database setup
9
+ */
10
+
11
+ /**
12
+ * Gets the path to the Prisma schema file for the database type
13
+ * @param {'mongodb'|'postgresql'} dbType - Database type
14
+ * @param {string} projectRoot - Project root directory
15
+ * @returns {string} Absolute path to schema file
16
+ * @throws {Error} If schema file doesn't exist
17
+ */
18
+ function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
19
+ // Try multiple locations for the schema file
20
+ // Priority order:
21
+ // 1. Local node_modules (where @friggframework/core is installed - production scenario)
22
+ // 2. Parent node_modules (workspace/monorepo setup)
23
+ const possiblePaths = [
24
+ // Check where Frigg is installed via npm (production scenario)
25
+ path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma'),
26
+ path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma')
27
+ ];
28
+
29
+ for (const schemaPath of possiblePaths) {
30
+ if (fs.existsSync(schemaPath)) {
31
+ return schemaPath;
32
+ }
33
+ }
34
+
35
+ // If not found in any location, throw error
36
+ throw new Error(
37
+ `Prisma schema not found at:\n${possiblePaths.join('\n')}\n\n` +
38
+ 'Ensure @friggframework/core is installed.'
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Runs prisma generate for the specified database type
44
+ * @param {'mongodb'|'postgresql'} dbType - Database type
45
+ * @param {boolean} verbose - Enable verbose output
46
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
47
+ */
48
+ async function runPrismaGenerate(dbType, verbose = false) {
49
+ try {
50
+ const schemaPath = getPrismaSchemaPath(dbType);
51
+
52
+ // Check if Prisma client already exists (e.g., in Lambda or pre-generated)
53
+ const generatedClientPath = path.join(path.dirname(path.dirname(schemaPath)), 'generated', `prisma-${dbType}`, 'client.js');
54
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
55
+
56
+ if (fs.existsSync(generatedClientPath)) {
57
+ if (verbose) {
58
+ console.log(chalk.gray(`✓ Prisma client already generated at: ${generatedClientPath}`));
59
+ }
60
+ if (isLambdaEnvironment) {
61
+ if (verbose) {
62
+ console.log(chalk.gray('Skipping generation in Lambda environment (using pre-generated client)'));
63
+ }
64
+ return {
65
+ success: true,
66
+ output: 'Using pre-generated Prisma client (Lambda environment)'
67
+ };
68
+ }
69
+ }
70
+
71
+ if (verbose) {
72
+ console.log(chalk.gray(`Running: npx prisma generate --schema=${schemaPath}`));
73
+ }
74
+
75
+ const output = execSync(
76
+ `npx prisma generate --schema=${schemaPath}`,
77
+ {
78
+ encoding: 'utf8',
79
+ stdio: verbose ? 'inherit' : 'pipe',
80
+ env: {
81
+ ...process.env,
82
+ // Suppress Prisma telemetry prompts
83
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
84
+ }
85
+ }
86
+ );
87
+
88
+ return {
89
+ success: true,
90
+ output: verbose ? 'Generated successfully' : output
91
+ };
92
+
93
+ } catch (error) {
94
+ return {
95
+ success: false,
96
+ error: error.message,
97
+ output: error.stdout?.toString() || error.stderr?.toString()
98
+ };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Checks database migration status
104
+ * @param {'mongodb'|'postgresql'} dbType - Database type
105
+ * @returns {Promise<Object>} { upToDate: boolean, pendingMigrations?: number, error?: string }
106
+ */
107
+ async function checkDatabaseState(dbType) {
108
+ try {
109
+ // Only applicable for PostgreSQL (MongoDB uses db push)
110
+ if (dbType !== 'postgresql') {
111
+ return { upToDate: true };
112
+ }
113
+
114
+ const schemaPath = getPrismaSchemaPath(dbType);
115
+
116
+ const output = execSync(
117
+ `npx prisma migrate status --schema=${schemaPath}`,
118
+ {
119
+ encoding: 'utf8',
120
+ stdio: 'pipe',
121
+ env: {
122
+ ...process.env,
123
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
124
+ }
125
+ }
126
+ );
127
+
128
+ if (output.includes('Database schema is up to date')) {
129
+ return { upToDate: true };
130
+ }
131
+
132
+ // Parse pending migrations count
133
+ const pendingMatch = output.match(/(\d+) migration/);
134
+ const pendingMigrations = pendingMatch ? parseInt(pendingMatch[1]) : 0;
135
+
136
+ return {
137
+ upToDate: false,
138
+ pendingMigrations
139
+ };
140
+
141
+ } catch (error) {
142
+ // If migrate status fails, database might not be initialized
143
+ return {
144
+ upToDate: false,
145
+ error: error.message
146
+ };
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Get Prisma binary path for Lambda environment
152
+ * Checks multiple locations in priority order:
153
+ * 1. Function's bundled Prisma (/var/task/node_modules/.bin/prisma) - for standalone functions
154
+ * 2. Layer's Prisma (/opt/nodejs/node_modules/.bin/prisma) - for functions using Prisma layer with CLI
155
+ * 3. Fallback to npx for local development
156
+ */
157
+ function getPrismaBinaryPath() {
158
+ const fs = require('fs');
159
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
160
+
161
+ if (!isLambdaEnvironment) {
162
+ return 'npx';
163
+ }
164
+
165
+ // Check function's own node_modules first (standalone dbMigrate)
166
+ const functionPrisma = '/var/task/node_modules/.bin/prisma';
167
+ if (fs.existsSync(functionPrisma)) {
168
+ return functionPrisma;
169
+ }
170
+
171
+ // Fall back to layer path (functions using Prisma layer with CLI)
172
+ const layerPrisma = '/opt/nodejs/node_modules/.bin/prisma';
173
+ if (fs.existsSync(layerPrisma)) {
174
+ return layerPrisma;
175
+ }
176
+
177
+ // Should not reach here in Lambda, but provide fallback
178
+ console.warn('⚠️ Prisma binary not found in expected Lambda paths, using npx');
179
+ return 'npx';
180
+ }
181
+
182
+ /**
183
+ * Runs Prisma migrate for PostgreSQL
184
+ * @param {'dev'|'deploy'} command - Migration command (dev or deploy)
185
+ * @param {boolean} verbose - Enable verbose output
186
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
187
+ */
188
+ async function runPrismaMigrate(command = 'dev', verbose = false) {
189
+ return new Promise((resolve) => {
190
+ try {
191
+ const schemaPath = getPrismaSchemaPath('postgresql');
192
+
193
+ // Get Prisma binary path (checks multiple locations)
194
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
195
+ const prismaBin = getPrismaBinaryPath();
196
+
197
+ // Determine args based on whether we're using direct binary or npx
198
+ // Direct binary (e.g., /var/task/node_modules/.bin/prisma): ['migrate', command, ...]
199
+ // npx (local dev or fallback): ['prisma', 'migrate', command, ...]
200
+ const isDirectBinary = prismaBin !== 'npx';
201
+ const args = isDirectBinary
202
+ ? ['migrate', command, '--schema', schemaPath]
203
+ : ['prisma', 'migrate', command, '--schema', schemaPath];
204
+
205
+ if (verbose) {
206
+ const displayCmd = isDirectBinary
207
+ ? `${prismaBin} ${args.join(' ')}`
208
+ : `npx ${args.join(' ')}`;
209
+ console.log(chalk.gray(`Running: ${displayCmd}`));
210
+ }
211
+
212
+ const proc = spawn(prismaBin, args, {
213
+ stdio: 'inherit',
214
+ env: {
215
+ ...process.env,
216
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
217
+ }
218
+ });
219
+
220
+ proc.on('error', (error) => {
221
+ resolve({
222
+ success: false,
223
+ error: error.message
224
+ });
225
+ });
226
+
227
+ proc.on('close', (code) => {
228
+ if (code === 0) {
229
+ resolve({
230
+ success: true,
231
+ output: 'Migration completed successfully'
232
+ });
233
+ } else {
234
+ resolve({
235
+ success: false,
236
+ error: `Migration process exited with code ${code}`
237
+ });
238
+ }
239
+ });
240
+
241
+ } catch (error) {
242
+ resolve({
243
+ success: false,
244
+ error: error.message
245
+ });
246
+ }
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Runs Prisma db push for MongoDB
252
+ * @param {boolean} verbose - Enable verbose output
253
+ * @param {boolean} nonInteractive - Run in non-interactive mode (accepts data loss, for Lambda/CI)
254
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
255
+ */
256
+ async function runPrismaDbPush(verbose = false, nonInteractive = false) {
257
+ return new Promise((resolve) => {
258
+ try {
259
+ const schemaPath = getPrismaSchemaPath('mongodb');
260
+
261
+ const args = [
262
+ 'prisma',
263
+ 'db',
264
+ 'push',
265
+ '--schema',
266
+ schemaPath,
267
+ '--skip-generate' // We generate separately
268
+ ];
269
+
270
+ // Add non-interactive flag for Lambda/CI environments
271
+ if (nonInteractive) {
272
+ args.push('--accept-data-loss');
273
+ }
274
+
275
+ if (verbose) {
276
+ console.log(chalk.gray(`Running: npx ${args.join(' ')}`));
277
+ }
278
+
279
+ if (nonInteractive) {
280
+ console.log(chalk.yellow('⚠️ Non-interactive mode: Data loss will be automatically accepted'));
281
+ } else {
282
+ console.log(chalk.yellow('⚠️ Interactive mode: You may be prompted if schema changes cause data loss'));
283
+ }
284
+
285
+ const proc = spawn('npx', args, {
286
+ stdio: nonInteractive ? 'pipe' : 'inherit', // Use pipe for non-interactive to capture output
287
+ env: {
288
+ ...process.env,
289
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
290
+ }
291
+ });
292
+
293
+ let stdout = '';
294
+ let stderr = '';
295
+
296
+ // Capture output in non-interactive mode
297
+ if (nonInteractive) {
298
+ if (proc.stdout) {
299
+ proc.stdout.on('data', (data) => {
300
+ stdout += data.toString();
301
+ if (verbose) {
302
+ process.stdout.write(data);
303
+ }
304
+ });
305
+ }
306
+ if (proc.stderr) {
307
+ proc.stderr.on('data', (data) => {
308
+ stderr += data.toString();
309
+ if (verbose) {
310
+ process.stderr.write(data);
311
+ }
312
+ });
313
+ }
314
+ }
315
+
316
+ proc.on('error', (error) => {
317
+ resolve({
318
+ success: false,
319
+ error: error.message
320
+ });
321
+ });
322
+
323
+ proc.on('close', (code) => {
324
+ if (code === 0) {
325
+ resolve({
326
+ success: true,
327
+ output: nonInteractive ? stdout || 'Database push completed successfully' : 'Database push completed successfully'
328
+ });
329
+ } else {
330
+ resolve({
331
+ success: false,
332
+ error: `Database push process exited with code ${code}`,
333
+ output: stderr || stdout
334
+ });
335
+ }
336
+ });
337
+
338
+ } catch (error) {
339
+ resolve({
340
+ success: false,
341
+ error: error.message
342
+ });
343
+ }
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Determines migration command based on STAGE environment variable
349
+ * @param {string} stage - Stage from CLI option or environment
350
+ * @returns {'dev'|'deploy'}
351
+ */
352
+ function getMigrationCommand(stage) {
353
+ // Always use 'deploy' in Lambda environment (it's non-interactive and doesn't create migrations)
354
+ const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
355
+ if (isLambdaEnvironment) {
356
+ return 'deploy';
357
+ }
358
+
359
+ const normalizedStage = (stage || process.env.STAGE || 'development').toLowerCase();
360
+
361
+ const developmentStages = ['dev', 'local', 'test', 'development'];
362
+
363
+ if (developmentStages.includes(normalizedStage)) {
364
+ return 'dev';
365
+ }
366
+
367
+ return 'deploy';
368
+ }
369
+
370
+ module.exports = {
371
+ getPrismaSchemaPath,
372
+ runPrismaGenerate,
373
+ checkDatabaseState,
374
+ runPrismaMigrate,
375
+ runPrismaDbPush,
376
+ getMigrationCommand
377
+ };