@friggframework/core 2.0.0--canary.454.e2a280d.0 → 2.0.0--canary.458.c150d9a.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/README.md +0 -28
- package/database/config.js +1 -29
- package/database/prisma.js +2 -2
- package/database/use-cases/test-encryption-use-case.js +5 -6
- package/handlers/routers/user.js +3 -1
- package/modules/module.js +1 -1
- package/modules/use-cases/process-authorization-callback.js +2 -1
- package/package.json +66 -79
- package/prisma-mongodb/schema.prisma +20 -25
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/schema.prisma +14 -19
- package/database/use-cases/run-database-migration-use-case.js +0 -137
- package/database/use-cases/run-database-migration-use-case.test.js +0 -310
- package/database/utils/prisma-runner.js +0 -313
- package/database/utils/prisma-runner.test.js +0 -486
- package/handlers/workers/db-migration.js +0 -208
- package/handlers/workers/db-migration.test.js +0 -437
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Run Database Migration Use Case
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
RunDatabaseMigrationUseCase,
|
|
7
|
-
MigrationError,
|
|
8
|
-
ValidationError,
|
|
9
|
-
} = require('./run-database-migration-use-case');
|
|
10
|
-
|
|
11
|
-
describe('RunDatabaseMigrationUseCase', () => {
|
|
12
|
-
let useCase;
|
|
13
|
-
let mockPrismaRunner;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
// Mock prisma runner with all required methods
|
|
17
|
-
mockPrismaRunner = {
|
|
18
|
-
runPrismaGenerate: jest.fn(),
|
|
19
|
-
runPrismaMigrate: jest.fn(),
|
|
20
|
-
runPrismaDbPush: jest.fn(),
|
|
21
|
-
getMigrationCommand: jest.fn(),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
useCase = new RunDatabaseMigrationUseCase({ prismaRunner: mockPrismaRunner });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('Constructor', () => {
|
|
28
|
-
it('should throw error if prismaRunner is not provided', () => {
|
|
29
|
-
expect(() => new RunDatabaseMigrationUseCase({})).toThrow('prismaRunner dependency is required');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should create instance with valid dependencies', () => {
|
|
33
|
-
expect(useCase).toBeInstanceOf(RunDatabaseMigrationUseCase);
|
|
34
|
-
expect(useCase.prismaRunner).toBe(mockPrismaRunner);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('Parameter Validation', () => {
|
|
39
|
-
it('should throw ValidationError if dbType is missing', async () => {
|
|
40
|
-
await expect(useCase.execute({ stage: 'production' })).rejects.toThrow(ValidationError);
|
|
41
|
-
await expect(useCase.execute({ stage: 'production' })).rejects.toThrow('dbType is required');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should throw ValidationError if dbType is not a string', async () => {
|
|
45
|
-
await expect(useCase.execute({ dbType: 123, stage: 'production' })).rejects.toThrow(ValidationError);
|
|
46
|
-
await expect(useCase.execute({ dbType: 123, stage: 'production' })).rejects.toThrow(
|
|
47
|
-
'dbType must be a string'
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should throw ValidationError if stage is missing', async () => {
|
|
52
|
-
await expect(useCase.execute({ dbType: 'postgresql' })).rejects.toThrow(ValidationError);
|
|
53
|
-
await expect(useCase.execute({ dbType: 'postgresql' })).rejects.toThrow('stage is required');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should throw ValidationError if stage is not a string', async () => {
|
|
57
|
-
await expect(useCase.execute({ dbType: 'postgresql', stage: 123 })).rejects.toThrow(ValidationError);
|
|
58
|
-
await expect(useCase.execute({ dbType: 'postgresql', stage: 123 })).rejects.toThrow(
|
|
59
|
-
'stage must be a string'
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('PostgreSQL Migrations', () => {
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
67
|
-
mockPrismaRunner.runPrismaMigrate.mockResolvedValue({ success: true });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should successfully run PostgreSQL production migration', async () => {
|
|
71
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('deploy');
|
|
72
|
-
|
|
73
|
-
const result = await useCase.execute({
|
|
74
|
-
dbType: 'postgresql',
|
|
75
|
-
stage: 'production',
|
|
76
|
-
verbose: true,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(result).toEqual({
|
|
80
|
-
success: true,
|
|
81
|
-
dbType: 'postgresql',
|
|
82
|
-
stage: 'production',
|
|
83
|
-
command: 'deploy',
|
|
84
|
-
message: 'Database migration completed successfully',
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
expect(mockPrismaRunner.runPrismaGenerate).toHaveBeenCalledWith('postgresql', true);
|
|
88
|
-
expect(mockPrismaRunner.getMigrationCommand).toHaveBeenCalledWith('production');
|
|
89
|
-
expect(mockPrismaRunner.runPrismaMigrate).toHaveBeenCalledWith('deploy', true);
|
|
90
|
-
expect(mockPrismaRunner.runPrismaDbPush).not.toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should successfully run PostgreSQL development migration', async () => {
|
|
94
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('dev');
|
|
95
|
-
|
|
96
|
-
const result = await useCase.execute({
|
|
97
|
-
dbType: 'postgresql',
|
|
98
|
-
stage: 'dev',
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
expect(result.success).toBe(true);
|
|
102
|
-
expect(result.command).toBe('dev');
|
|
103
|
-
expect(mockPrismaRunner.getMigrationCommand).toHaveBeenCalledWith('dev');
|
|
104
|
-
expect(mockPrismaRunner.runPrismaMigrate).toHaveBeenCalledWith('dev', false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should throw MigrationError if Prisma generate fails', async () => {
|
|
108
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({
|
|
109
|
-
success: false,
|
|
110
|
-
error: 'Schema file not found',
|
|
111
|
-
output: 'Error output',
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await expect(
|
|
115
|
-
useCase.execute({ dbType: 'postgresql', stage: 'production' })
|
|
116
|
-
).rejects.toThrow(MigrationError);
|
|
117
|
-
|
|
118
|
-
await expect(
|
|
119
|
-
useCase.execute({ dbType: 'postgresql', stage: 'production' })
|
|
120
|
-
).rejects.toThrow('Failed to generate Prisma client: Schema file not found');
|
|
121
|
-
|
|
122
|
-
expect(mockPrismaRunner.runPrismaMigrate).not.toHaveBeenCalled();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should throw MigrationError if PostgreSQL migration fails', async () => {
|
|
126
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('deploy');
|
|
127
|
-
mockPrismaRunner.runPrismaMigrate.mockResolvedValue({
|
|
128
|
-
success: false,
|
|
129
|
-
error: 'Migration conflict detected',
|
|
130
|
-
output: 'Conflict output',
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
await expect(
|
|
134
|
-
useCase.execute({ dbType: 'postgresql', stage: 'production' })
|
|
135
|
-
).rejects.toThrow(MigrationError);
|
|
136
|
-
|
|
137
|
-
await expect(
|
|
138
|
-
useCase.execute({ dbType: 'postgresql', stage: 'production' })
|
|
139
|
-
).rejects.toThrow('PostgreSQL migration failed: Migration conflict detected');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should include context in MigrationError', async () => {
|
|
143
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('deploy');
|
|
144
|
-
mockPrismaRunner.runPrismaMigrate.mockResolvedValue({
|
|
145
|
-
success: false,
|
|
146
|
-
error: 'Migration failed',
|
|
147
|
-
output: 'Error output',
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
await useCase.execute({ dbType: 'postgresql', stage: 'production' });
|
|
152
|
-
fail('Should have thrown MigrationError');
|
|
153
|
-
} catch (error) {
|
|
154
|
-
expect(error).toBeInstanceOf(MigrationError);
|
|
155
|
-
expect(error.context).toEqual({
|
|
156
|
-
dbType: 'postgresql',
|
|
157
|
-
stage: 'production',
|
|
158
|
-
command: 'deploy',
|
|
159
|
-
step: 'migrate',
|
|
160
|
-
output: 'Error output',
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe('MongoDB Migrations', () => {
|
|
167
|
-
beforeEach(() => {
|
|
168
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
169
|
-
mockPrismaRunner.runPrismaDbPush.mockResolvedValue({ success: true });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should successfully run MongoDB migration', async () => {
|
|
173
|
-
const result = await useCase.execute({
|
|
174
|
-
dbType: 'mongodb',
|
|
175
|
-
stage: 'production',
|
|
176
|
-
verbose: true,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(result).toEqual({
|
|
180
|
-
success: true,
|
|
181
|
-
dbType: 'mongodb',
|
|
182
|
-
stage: 'production',
|
|
183
|
-
command: 'db push',
|
|
184
|
-
message: 'Database migration completed successfully',
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
expect(mockPrismaRunner.runPrismaGenerate).toHaveBeenCalledWith('mongodb', true);
|
|
188
|
-
expect(mockPrismaRunner.runPrismaDbPush).toHaveBeenCalledWith(true, true); // verbose=true, nonInteractive=true
|
|
189
|
-
expect(mockPrismaRunner.runPrismaMigrate).not.toHaveBeenCalled();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should use non-interactive mode for MongoDB', async () => {
|
|
193
|
-
await useCase.execute({
|
|
194
|
-
dbType: 'mongodb',
|
|
195
|
-
stage: 'production',
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Second parameter should be true for non-interactive
|
|
199
|
-
expect(mockPrismaRunner.runPrismaDbPush).toHaveBeenCalledWith(false, true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should throw MigrationError if MongoDB push fails', async () => {
|
|
203
|
-
mockPrismaRunner.runPrismaDbPush.mockResolvedValue({
|
|
204
|
-
success: false,
|
|
205
|
-
error: 'Connection timeout',
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
await expect(useCase.execute({ dbType: 'mongodb', stage: 'production' })).rejects.toThrow(
|
|
209
|
-
MigrationError
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
await expect(useCase.execute({ dbType: 'mongodb', stage: 'production' })).rejects.toThrow(
|
|
213
|
-
'MongoDB push failed: Connection timeout'
|
|
214
|
-
);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe('Unsupported Database Types', () => {
|
|
219
|
-
beforeEach(() => {
|
|
220
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should throw ValidationError for unsupported database type', async () => {
|
|
224
|
-
await expect(useCase.execute({ dbType: 'mysql', stage: 'production' })).rejects.toThrow(
|
|
225
|
-
ValidationError
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
await expect(useCase.execute({ dbType: 'mysql', stage: 'production' })).rejects.toThrow(
|
|
229
|
-
"Unsupported database type: mysql. Must be 'postgresql' or 'mongodb'."
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should run Prisma generate before checking database type', async () => {
|
|
234
|
-
try {
|
|
235
|
-
await useCase.execute({ dbType: 'mysql', stage: 'production' });
|
|
236
|
-
} catch (error) {
|
|
237
|
-
// Expected error
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
expect(mockPrismaRunner.runPrismaGenerate).toHaveBeenCalledWith('mysql', false);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('Error Handling', () => {
|
|
245
|
-
it('should handle undefined error from Prisma generate', async () => {
|
|
246
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({
|
|
247
|
-
success: false,
|
|
248
|
-
error: undefined,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
await expect(useCase.execute({ dbType: 'postgresql', stage: 'production' })).rejects.toThrow(
|
|
252
|
-
'Failed to generate Prisma client: Unknown error'
|
|
253
|
-
);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should handle undefined error from PostgreSQL migration', async () => {
|
|
257
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
258
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('deploy');
|
|
259
|
-
mockPrismaRunner.runPrismaMigrate.mockResolvedValue({
|
|
260
|
-
success: false,
|
|
261
|
-
error: undefined,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await expect(useCase.execute({ dbType: 'postgresql', stage: 'production' })).rejects.toThrow(
|
|
265
|
-
'PostgreSQL migration failed: Unknown error'
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should handle undefined error from MongoDB push', async () => {
|
|
270
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
271
|
-
mockPrismaRunner.runPrismaDbPush.mockResolvedValue({
|
|
272
|
-
success: false,
|
|
273
|
-
error: undefined,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
await expect(useCase.execute({ dbType: 'mongodb', stage: 'production' })).rejects.toThrow(
|
|
277
|
-
'MongoDB push failed: Unknown error'
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
describe('Verbose Mode', () => {
|
|
283
|
-
beforeEach(() => {
|
|
284
|
-
mockPrismaRunner.runPrismaGenerate.mockResolvedValue({ success: true });
|
|
285
|
-
mockPrismaRunner.runPrismaMigrate.mockResolvedValue({ success: true });
|
|
286
|
-
mockPrismaRunner.getMigrationCommand.mockReturnValue('deploy');
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('should pass verbose flag to all Prisma operations', async () => {
|
|
290
|
-
await useCase.execute({
|
|
291
|
-
dbType: 'postgresql',
|
|
292
|
-
stage: 'production',
|
|
293
|
-
verbose: true,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
expect(mockPrismaRunner.runPrismaGenerate).toHaveBeenCalledWith('postgresql', true);
|
|
297
|
-
expect(mockPrismaRunner.runPrismaMigrate).toHaveBeenCalledWith('deploy', true);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('should default verbose to false', async () => {
|
|
301
|
-
await useCase.execute({
|
|
302
|
-
dbType: 'postgresql',
|
|
303
|
-
stage: 'production',
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
expect(mockPrismaRunner.runPrismaGenerate).toHaveBeenCalledWith('postgresql', false);
|
|
307
|
-
expect(mockPrismaRunner.runPrismaMigrate).toHaveBeenCalledWith('deploy', false);
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
|
@@ -1,313 +0,0 @@
|
|
|
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
|
-
if (verbose) {
|
|
53
|
-
console.log(chalk.gray(`Running: npx prisma generate --schema=${schemaPath}`));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const output = execSync(
|
|
57
|
-
`npx prisma generate --schema=${schemaPath}`,
|
|
58
|
-
{
|
|
59
|
-
encoding: 'utf8',
|
|
60
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
61
|
-
env: {
|
|
62
|
-
...process.env,
|
|
63
|
-
// Suppress Prisma telemetry prompts
|
|
64
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1'
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
success: true,
|
|
71
|
-
output: verbose ? 'Generated successfully' : output
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return {
|
|
76
|
-
success: false,
|
|
77
|
-
error: error.message,
|
|
78
|
-
output: error.stdout?.toString() || error.stderr?.toString()
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Checks database migration status
|
|
85
|
-
* @param {'mongodb'|'postgresql'} dbType - Database type
|
|
86
|
-
* @returns {Promise<Object>} { upToDate: boolean, pendingMigrations?: number, error?: string }
|
|
87
|
-
*/
|
|
88
|
-
async function checkDatabaseState(dbType) {
|
|
89
|
-
try {
|
|
90
|
-
// Only applicable for PostgreSQL (MongoDB uses db push)
|
|
91
|
-
if (dbType !== 'postgresql') {
|
|
92
|
-
return { upToDate: true };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const schemaPath = getPrismaSchemaPath(dbType);
|
|
96
|
-
|
|
97
|
-
const output = execSync(
|
|
98
|
-
`npx prisma migrate status --schema=${schemaPath}`,
|
|
99
|
-
{
|
|
100
|
-
encoding: 'utf8',
|
|
101
|
-
stdio: 'pipe',
|
|
102
|
-
env: {
|
|
103
|
-
...process.env,
|
|
104
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1'
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
if (output.includes('Database schema is up to date')) {
|
|
110
|
-
return { upToDate: true };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Parse pending migrations count
|
|
114
|
-
const pendingMatch = output.match(/(\d+) migration/);
|
|
115
|
-
const pendingMigrations = pendingMatch ? parseInt(pendingMatch[1]) : 0;
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
upToDate: false,
|
|
119
|
-
pendingMigrations
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
} catch (error) {
|
|
123
|
-
// If migrate status fails, database might not be initialized
|
|
124
|
-
return {
|
|
125
|
-
upToDate: false,
|
|
126
|
-
error: error.message
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Runs Prisma migrate for PostgreSQL
|
|
133
|
-
* @param {'dev'|'deploy'} command - Migration command (dev or deploy)
|
|
134
|
-
* @param {boolean} verbose - Enable verbose output
|
|
135
|
-
* @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
|
|
136
|
-
*/
|
|
137
|
-
async function runPrismaMigrate(command = 'dev', verbose = false) {
|
|
138
|
-
return new Promise((resolve) => {
|
|
139
|
-
try {
|
|
140
|
-
const schemaPath = getPrismaSchemaPath('postgresql');
|
|
141
|
-
|
|
142
|
-
const args = [
|
|
143
|
-
'prisma',
|
|
144
|
-
'migrate',
|
|
145
|
-
command,
|
|
146
|
-
'--schema',
|
|
147
|
-
schemaPath
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
if (verbose) {
|
|
151
|
-
console.log(chalk.gray(`Running: npx ${args.join(' ')}`));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const proc = spawn('npx', args, {
|
|
155
|
-
stdio: 'inherit',
|
|
156
|
-
env: {
|
|
157
|
-
...process.env,
|
|
158
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1'
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
proc.on('error', (error) => {
|
|
163
|
-
resolve({
|
|
164
|
-
success: false,
|
|
165
|
-
error: error.message
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
proc.on('close', (code) => {
|
|
170
|
-
if (code === 0) {
|
|
171
|
-
resolve({
|
|
172
|
-
success: true,
|
|
173
|
-
output: 'Migration completed successfully'
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
resolve({
|
|
177
|
-
success: false,
|
|
178
|
-
error: `Migration process exited with code ${code}`
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
} catch (error) {
|
|
184
|
-
resolve({
|
|
185
|
-
success: false,
|
|
186
|
-
error: error.message
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Runs Prisma db push for MongoDB
|
|
194
|
-
* @param {boolean} verbose - Enable verbose output
|
|
195
|
-
* @param {boolean} nonInteractive - Run in non-interactive mode (accepts data loss, for Lambda/CI)
|
|
196
|
-
* @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
|
|
197
|
-
*/
|
|
198
|
-
async function runPrismaDbPush(verbose = false, nonInteractive = false) {
|
|
199
|
-
return new Promise((resolve) => {
|
|
200
|
-
try {
|
|
201
|
-
const schemaPath = getPrismaSchemaPath('mongodb');
|
|
202
|
-
|
|
203
|
-
const args = [
|
|
204
|
-
'prisma',
|
|
205
|
-
'db',
|
|
206
|
-
'push',
|
|
207
|
-
'--schema',
|
|
208
|
-
schemaPath,
|
|
209
|
-
'--skip-generate' // We generate separately
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
// Add non-interactive flag for Lambda/CI environments
|
|
213
|
-
if (nonInteractive) {
|
|
214
|
-
args.push('--accept-data-loss');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (verbose) {
|
|
218
|
-
console.log(chalk.gray(`Running: npx ${args.join(' ')}`));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (nonInteractive) {
|
|
222
|
-
console.log(chalk.yellow('⚠️ Non-interactive mode: Data loss will be automatically accepted'));
|
|
223
|
-
} else {
|
|
224
|
-
console.log(chalk.yellow('⚠️ Interactive mode: You may be prompted if schema changes cause data loss'));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const proc = spawn('npx', args, {
|
|
228
|
-
stdio: nonInteractive ? 'pipe' : 'inherit', // Use pipe for non-interactive to capture output
|
|
229
|
-
env: {
|
|
230
|
-
...process.env,
|
|
231
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1'
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
let stdout = '';
|
|
236
|
-
let stderr = '';
|
|
237
|
-
|
|
238
|
-
// Capture output in non-interactive mode
|
|
239
|
-
if (nonInteractive) {
|
|
240
|
-
if (proc.stdout) {
|
|
241
|
-
proc.stdout.on('data', (data) => {
|
|
242
|
-
stdout += data.toString();
|
|
243
|
-
if (verbose) {
|
|
244
|
-
process.stdout.write(data);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (proc.stderr) {
|
|
249
|
-
proc.stderr.on('data', (data) => {
|
|
250
|
-
stderr += data.toString();
|
|
251
|
-
if (verbose) {
|
|
252
|
-
process.stderr.write(data);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
proc.on('error', (error) => {
|
|
259
|
-
resolve({
|
|
260
|
-
success: false,
|
|
261
|
-
error: error.message
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
proc.on('close', (code) => {
|
|
266
|
-
if (code === 0) {
|
|
267
|
-
resolve({
|
|
268
|
-
success: true,
|
|
269
|
-
output: nonInteractive ? stdout || 'Database push completed successfully' : 'Database push completed successfully'
|
|
270
|
-
});
|
|
271
|
-
} else {
|
|
272
|
-
resolve({
|
|
273
|
-
success: false,
|
|
274
|
-
error: `Database push process exited with code ${code}`,
|
|
275
|
-
output: stderr || stdout
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
} catch (error) {
|
|
281
|
-
resolve({
|
|
282
|
-
success: false,
|
|
283
|
-
error: error.message
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Determines migration command based on STAGE environment variable
|
|
291
|
-
* @param {string} stage - Stage from CLI option or environment
|
|
292
|
-
* @returns {'dev'|'deploy'}
|
|
293
|
-
*/
|
|
294
|
-
function getMigrationCommand(stage) {
|
|
295
|
-
const normalizedStage = (stage || process.env.STAGE || 'development').toLowerCase();
|
|
296
|
-
|
|
297
|
-
const developmentStages = ['dev', 'local', 'test', 'development'];
|
|
298
|
-
|
|
299
|
-
if (developmentStages.includes(normalizedStage)) {
|
|
300
|
-
return 'dev';
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return 'deploy';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
module.exports = {
|
|
307
|
-
getPrismaSchemaPath,
|
|
308
|
-
runPrismaGenerate,
|
|
309
|
-
checkDatabaseState,
|
|
310
|
-
runPrismaMigrate,
|
|
311
|
-
runPrismaDbPush,
|
|
312
|
-
getMigrationCommand
|
|
313
|
-
};
|