@javalabs/prisma-client 1.0.27 → 1.0.29
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/.github/CODEOWNERS +1 -1
- package/README.md +269 -269
- package/migration-config.json +63 -63
- package/migration-config.json.bk +95 -95
- package/migrations/add_reserved_amount.sql +7 -7
- package/package.json +44 -44
- package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
- package/prisma/schema.prisma +609 -601
- package/src/index.ts +23 -23
- package/src/prisma-factory.service.ts +40 -40
- package/src/prisma.module.ts +9 -9
- package/src/prisma.service.ts +16 -16
- package/src/scripts/add-uuid-to-table.ts +138 -138
- package/src/scripts/create-tenant-schemas.ts +145 -145
- package/src/scripts/data-migration/batch-migrator.ts +248 -248
- package/src/scripts/data-migration/data-transformer.ts +426 -426
- package/src/scripts/data-migration/db-connector.ts +120 -120
- package/src/scripts/data-migration/dependency-resolver.ts +174 -174
- package/src/scripts/data-migration/entity-discovery.ts +196 -196
- package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
- package/src/scripts/data-migration/migration-config.json +63 -63
- package/src/scripts/data-migration/migration-tool.ts +509 -509
- package/src/scripts/data-migration/schema-utils.ts +248 -248
- package/src/scripts/data-migration/tenant-migrator.ts +201 -201
- package/src/scripts/data-migration/typecast-manager.ts +193 -193
- package/src/scripts/data-migration/types.ts +113 -113
- package/src/scripts/database-initializer.ts +49 -49
- package/src/scripts/drop-database.ts +104 -104
- package/src/scripts/dump-source-db.sh +61 -61
- package/src/scripts/encrypt-user-passwords.ts +36 -36
- package/src/scripts/error-handler.ts +117 -117
- package/src/scripts/fix-data-types.ts +241 -241
- package/src/scripts/fix-enum-values.ts +357 -357
- package/src/scripts/fix-schema-discrepancies.ts +317 -317
- package/src/scripts/fix-table-indexes.ts +601 -601
- package/src/scripts/migrate-schema-structure.ts +90 -90
- package/src/scripts/migrate-uuid.ts +76 -76
- package/src/scripts/post-migration-validator.ts +526 -526
- package/src/scripts/pre-migration-validator.ts +610 -610
- package/src/scripts/reset-database.ts +263 -263
- package/src/scripts/retry-failed-migrations.ts +416 -416
- package/src/scripts/run-migration.ts +707 -707
- package/src/scripts/schema-sync.ts +128 -128
- package/src/scripts/sequence-sync-cli.ts +416 -416
- package/src/scripts/sequence-synchronizer.ts +127 -127
- package/src/scripts/sync-enum-types.ts +170 -170
- package/src/scripts/sync-enum-values.ts +563 -563
- package/src/scripts/truncate-database.ts +123 -123
- package/src/scripts/verify-migration-setup.ts +135 -135
- package/tsconfig.json +17 -17
|
@@ -1,707 +1,707 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import * as dotenv from "dotenv";
|
|
3
|
-
import { exec } from "child_process";
|
|
4
|
-
import { promisify } from "util";
|
|
5
|
-
import { SchemaDiscrepancyFixer } from "./fix-schema-discrepancies";
|
|
6
|
-
import { EnumSynchronizer } from "./sync-enum-types";
|
|
7
|
-
import { PrismaClient } from "@prisma/client";
|
|
8
|
-
import { MigrationConfig } from "./data-migration/types";
|
|
9
|
-
import * as fs from "fs";
|
|
10
|
-
import * as path from "path";
|
|
11
|
-
import { DatabaseInitializer } from "./database-initializer";
|
|
12
|
-
import { SequenceSynchronizer } from "./sequence-synchronizer";
|
|
13
|
-
|
|
14
|
-
dotenv.config();
|
|
15
|
-
|
|
16
|
-
const execAsync = promisify(exec);
|
|
17
|
-
const program = new Command();
|
|
18
|
-
|
|
19
|
-
// Función para cargar la configuración de migración
|
|
20
|
-
async function loadMigrationConfig(
|
|
21
|
-
configPath?: string
|
|
22
|
-
): Promise<MigrationConfig> {
|
|
23
|
-
const defaultPath = path.join(
|
|
24
|
-
__dirname,
|
|
25
|
-
"data-migration",
|
|
26
|
-
"migration-config.json"
|
|
27
|
-
);
|
|
28
|
-
const filePath = configPath || defaultPath;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const configContent = await fs.promises.readFile(filePath, "utf8");
|
|
32
|
-
return JSON.parse(configContent);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error(`Error loading migration config from ${filePath}:`, error);
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Añadir esta función después de las importaciones
|
|
40
|
-
async function testDatabaseConnection(
|
|
41
|
-
url: string,
|
|
42
|
-
name: string
|
|
43
|
-
): Promise<boolean> {
|
|
44
|
-
const { Pool } = await import("pg");
|
|
45
|
-
const pool = new Pool({ connectionString: url });
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
console.log(`Testing connection to ${name} database...`);
|
|
49
|
-
const client = await pool.connect();
|
|
50
|
-
const result = await client.query("SELECT NOW()");
|
|
51
|
-
client.release();
|
|
52
|
-
|
|
53
|
-
console.log(`Successfully connected to ${name} database`);
|
|
54
|
-
return true;
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error(`Failed to connect to ${name} database: ${error.message}`);
|
|
57
|
-
return false;
|
|
58
|
-
} finally {
|
|
59
|
-
await pool.end();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Modificar la acción del comando migrate para incluir la prueba de conexión
|
|
64
|
-
program
|
|
65
|
-
.command("migrate")
|
|
66
|
-
.description("Run the data migration process")
|
|
67
|
-
.option("-s, --source <url>", "Source database connection URL")
|
|
68
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
69
|
-
.option(
|
|
70
|
-
"-m, --mode <mode>",
|
|
71
|
-
"Migration mode: 'multi-tenant' or 'public-only'",
|
|
72
|
-
"multi-tenant"
|
|
73
|
-
)
|
|
74
|
-
.option("-d, --dry-run", "Perform a dry run without making changes", false)
|
|
75
|
-
.option("--skip-schema-creation", "Skip the schema creation step", false)
|
|
76
|
-
.option("--skip-schema-migration", "Skip the schema migration step", false)
|
|
77
|
-
.option("--skip-data-migration", "Skip the data migration step", false)
|
|
78
|
-
.option("--skip-validation", "Skip pre and post validation steps", false)
|
|
79
|
-
.option("--skip-enum-sync", "Skip enum synchronization step", false)
|
|
80
|
-
.option("--auto-fix-schema", "Automatically fix schema discrepancies", false)
|
|
81
|
-
.option("--force", "Force migration even if validation fails", false)
|
|
82
|
-
.option("-y, --yes", "Automatically answer yes to all prompts", false)
|
|
83
|
-
.option("--config-path <path>", "Path to migration config file")
|
|
84
|
-
.option("--skip-initialization", "Skip database initialization step", false)
|
|
85
|
-
.option("--skip-sequence-sync", "Skip sequence synchronization step", false)
|
|
86
|
-
.action(async (options) => {
|
|
87
|
-
// Set environment variables for the migration
|
|
88
|
-
if (options.source) {
|
|
89
|
-
process.env.SOURCE_DATABASE_URL = options.source;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (options.target) {
|
|
93
|
-
process.env.DATABASE_URL = options.target;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!process.env.SOURCE_DATABASE_URL) {
|
|
97
|
-
console.error(
|
|
98
|
-
"Source database URL is required. Use --source or set SOURCE_DATABASE_URL environment variable."
|
|
99
|
-
);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!process.env.DATABASE_URL) {
|
|
104
|
-
console.error(
|
|
105
|
-
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
106
|
-
);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (options.dryRun) {
|
|
111
|
-
console.log(
|
|
112
|
-
"Performing dry run - no changes will be made to the database"
|
|
113
|
-
);
|
|
114
|
-
process.env.DRY_RUN = "true";
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const sourceConnected = await testDatabaseConnection(
|
|
118
|
-
process.env.SOURCE_DATABASE_URL,
|
|
119
|
-
"source"
|
|
120
|
-
);
|
|
121
|
-
const targetConnected = await testDatabaseConnection(
|
|
122
|
-
process.env.DATABASE_URL,
|
|
123
|
-
"target"
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
if (!sourceConnected || !targetConnected) {
|
|
127
|
-
console.error(
|
|
128
|
-
"Database connection test failed. Please check your connection strings and try again."
|
|
129
|
-
);
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
// Step 0: Initialize database if needed
|
|
135
|
-
if (!options.skipInitialization) {
|
|
136
|
-
console.log("Step 0: Checking database initialization...");
|
|
137
|
-
const initializer = new DatabaseInitializer(process.env.DATABASE_URL);
|
|
138
|
-
await initializer.initialize();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Step 1: Pre-migration validation
|
|
142
|
-
if (!options.skipValidation) {
|
|
143
|
-
console.log("Step 1: Running pre-migration validation...");
|
|
144
|
-
const { PreMigrationValidator } = await import(
|
|
145
|
-
"./pre-migration-validator"
|
|
146
|
-
);
|
|
147
|
-
const validator = new PreMigrationValidator();
|
|
148
|
-
const validationResult = await validator.validate();
|
|
149
|
-
|
|
150
|
-
if (!validationResult.success && !options.force) {
|
|
151
|
-
console.log(
|
|
152
|
-
`Pre-migration validation found ${validationResult.issueCount} issues.`
|
|
153
|
-
);
|
|
154
|
-
console.log(JSON.stringify(validationResult, null, 2));
|
|
155
|
-
|
|
156
|
-
// Si la opción --yes o --force está activada, continuar automáticamente
|
|
157
|
-
if (options.yes) {
|
|
158
|
-
console.log(
|
|
159
|
-
"Continuing with migration despite issues (--yes flag provided)"
|
|
160
|
-
);
|
|
161
|
-
} else if (!options.force) {
|
|
162
|
-
// Preguntar al usuario si desea continuar a pesar de los problemas
|
|
163
|
-
const readline = require("readline").createInterface({
|
|
164
|
-
input: process.stdin,
|
|
165
|
-
output: process.stdout,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const answer = await new Promise<string>((resolve) => {
|
|
169
|
-
readline.question(
|
|
170
|
-
"Continue with migration despite issues? (y/N) ",
|
|
171
|
-
(ans: string) => {
|
|
172
|
-
resolve(ans);
|
|
173
|
-
readline.close();
|
|
174
|
-
}
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
if (answer.toLowerCase() !== "y") {
|
|
179
|
-
console.log("Migration aborted by user.");
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
console.log("Pre-migration validation successful!");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Corregir automáticamente las discrepancias del esquema si se solicita
|
|
188
|
-
if (options.autoFixSchema && validationResult.reportPath) {
|
|
189
|
-
console.log("Automatically fixing schema discrepancies...");
|
|
190
|
-
const fixer = new SchemaDiscrepancyFixer(
|
|
191
|
-
process.env.SOURCE_DATABASE_URL,
|
|
192
|
-
process.env.DATABASE_URL,
|
|
193
|
-
validationResult.reportPath
|
|
194
|
-
);
|
|
195
|
-
await fixer.fixDiscrepancies();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Step 2: Migrate schema structure
|
|
200
|
-
if (!options.skipSchemaMigration) {
|
|
201
|
-
console.log("Step 2: Migrating schema structure...");
|
|
202
|
-
if (options.mode === "multi-tenant") {
|
|
203
|
-
await execAsync("node dist/scripts/migrate-schema-structure.js");
|
|
204
|
-
} else {
|
|
205
|
-
const { Pool } = await import("pg");
|
|
206
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
207
|
-
|
|
208
|
-
// Helper function to check if tables exist
|
|
209
|
-
async function checkIfTablesExist(pool: any): Promise<boolean> {
|
|
210
|
-
try {
|
|
211
|
-
const result = await pool.query(`
|
|
212
|
-
SELECT COUNT(*)
|
|
213
|
-
FROM information_schema.tables
|
|
214
|
-
WHERE table_schema = 'public'
|
|
215
|
-
AND table_type = 'BASE TABLE'
|
|
216
|
-
`);
|
|
217
|
-
|
|
218
|
-
return parseInt(result.rows[0].count) > 0;
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error("Error checking tables:", error);
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const tablesExist = await checkIfTablesExist(pool);
|
|
226
|
-
if (!tablesExist) {
|
|
227
|
-
await execAsync("npx prisma migrate deploy");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Step 3: Synchronize and fix enum types
|
|
233
|
-
if (!options.skipEnumSync) {
|
|
234
|
-
console.log("Step 3: Synchronizing and fixing enum types...");
|
|
235
|
-
// Sync enum types
|
|
236
|
-
const { EnumSynchronizer } = await import("./sync-enum-types");
|
|
237
|
-
const synchronizer = new EnumSynchronizer();
|
|
238
|
-
await synchronizer.synchronizeEnums();
|
|
239
|
-
console.log("Enum types synchronized successfully!");
|
|
240
|
-
|
|
241
|
-
// Sync enum values
|
|
242
|
-
console.log("Step 3.1: Synchronizing enum values...");
|
|
243
|
-
const { EnumValueSynchronizer } = await import("./sync-enum-values");
|
|
244
|
-
const valueSynchronizer = new EnumValueSynchronizer();
|
|
245
|
-
await valueSynchronizer.synchronizeEnumValues();
|
|
246
|
-
console.log("Enum values synchronized successfully!");
|
|
247
|
-
|
|
248
|
-
// Fix enum values
|
|
249
|
-
console.log("Step 3.2: Fixing enum values...");
|
|
250
|
-
const { EnumFixer } = await import("./fix-enum-values");
|
|
251
|
-
const enumFixer = new EnumFixer(process.env.DATABASE_URL);
|
|
252
|
-
await enumFixer.fixEnumValues();
|
|
253
|
-
console.log("Enum values fixed successfully!");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Step 4: Fix data types
|
|
257
|
-
console.log("Step 4: Fixing data type issues...");
|
|
258
|
-
const { DataTypeFixer } = await import("./fix-data-types");
|
|
259
|
-
const fixer = new DataTypeFixer();
|
|
260
|
-
await fixer.fixDataTypes();
|
|
261
|
-
console.log("Data type fixing completed successfully!");
|
|
262
|
-
|
|
263
|
-
// Step 5: Migrate data with transformation
|
|
264
|
-
if (!options.skipDataMigration) {
|
|
265
|
-
console.log("Step 5: Migrating data with transformation...");
|
|
266
|
-
const { DataMigrationTool } = await import(
|
|
267
|
-
"./data-migration/migration-tool"
|
|
268
|
-
);
|
|
269
|
-
const { PrismaClient } = await import("@prisma/client");
|
|
270
|
-
const { Pool } = await import("pg");
|
|
271
|
-
|
|
272
|
-
// Cargar la configuración de migración
|
|
273
|
-
const migrationConfig = await loadMigrationConfig(options.configPath);
|
|
274
|
-
|
|
275
|
-
// Create connections with increased pool size and timeout
|
|
276
|
-
const connections = {
|
|
277
|
-
sourcePool: new Pool({
|
|
278
|
-
connectionString: process.env.SOURCE_DATABASE_URL,
|
|
279
|
-
max: 20,
|
|
280
|
-
idleTimeoutMillis: 30000,
|
|
281
|
-
}),
|
|
282
|
-
targetPool: new Pool({
|
|
283
|
-
connectionString: process.env.DATABASE_URL,
|
|
284
|
-
max: 20,
|
|
285
|
-
idleTimeoutMillis: 30000,
|
|
286
|
-
}),
|
|
287
|
-
sourcePrisma: new PrismaClient({
|
|
288
|
-
datasources: { db: { url: process.env.SOURCE_DATABASE_URL } },
|
|
289
|
-
log: ["error", "warn"],
|
|
290
|
-
}),
|
|
291
|
-
targetPrisma: new PrismaClient({
|
|
292
|
-
datasources: { db: { url: process.env.DATABASE_URL } },
|
|
293
|
-
log: ["error", "warn"],
|
|
294
|
-
}),
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
// Disable foreign key constraints before migration
|
|
299
|
-
await connections.targetPool.query(
|
|
300
|
-
"SET session_replication_role = 'replica';"
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
// Si es public-only, modificar la configuración para solo incluir tablas públicas
|
|
304
|
-
if (options.mode === "public-only") {
|
|
305
|
-
migrationConfig.tables = Object.entries(migrationConfig.tables)
|
|
306
|
-
.filter(([_, config]) => config.type === "public")
|
|
307
|
-
.reduce(
|
|
308
|
-
(acc, [key, value]) => ({
|
|
309
|
-
...acc,
|
|
310
|
-
[key]: value,
|
|
311
|
-
}),
|
|
312
|
-
{}
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const migrationTool = new DataMigrationTool(
|
|
317
|
-
connections,
|
|
318
|
-
{
|
|
319
|
-
...migrationConfig,
|
|
320
|
-
tables: Object.entries(migrationConfig.tables).reduce(
|
|
321
|
-
(acc, [key, value]) => ({
|
|
322
|
-
...acc,
|
|
323
|
-
[key]: {
|
|
324
|
-
...value,
|
|
325
|
-
sourceTable: value.sourceTable || key,
|
|
326
|
-
targetTable: value.targetTable || key,
|
|
327
|
-
},
|
|
328
|
-
}),
|
|
329
|
-
{}
|
|
330
|
-
),
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
publicOnly: options.mode === "public-only",
|
|
334
|
-
targetSchema: "public",
|
|
335
|
-
sourceSchema: "public",
|
|
336
|
-
multiTenant: options.mode !== "public-only",
|
|
337
|
-
}
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
await migrationTool.migrate();
|
|
341
|
-
} finally {
|
|
342
|
-
// Re-enable foreign key constraints
|
|
343
|
-
await connections.targetPool.query(
|
|
344
|
-
"SET session_replication_role = 'origin';"
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
// Close connections
|
|
348
|
-
await connections.sourcePool.end();
|
|
349
|
-
await connections.targetPool.end();
|
|
350
|
-
await connections.sourcePrisma.$disconnect();
|
|
351
|
-
await connections.targetPrisma.$disconnect();
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Step 6: Post-migration validation
|
|
356
|
-
if (!options.skipValidation) {
|
|
357
|
-
console.log("Step 6: Running post-migration validation...");
|
|
358
|
-
const { PostMigrationValidator } = await import(
|
|
359
|
-
"./post-migration-validator"
|
|
360
|
-
);
|
|
361
|
-
const validator = new PostMigrationValidator();
|
|
362
|
-
const result = await validator.validate({
|
|
363
|
-
publicOnly: options.mode === "public-only",
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
if (result.success) {
|
|
367
|
-
console.log("Post-migration validation successful!");
|
|
368
|
-
} else {
|
|
369
|
-
console.log(
|
|
370
|
-
`Post-migration validation found ${result.issueCount} issues.`
|
|
371
|
-
);
|
|
372
|
-
console.log(JSON.stringify(result, null, 2));
|
|
373
|
-
|
|
374
|
-
if (!options.force) {
|
|
375
|
-
console.error(
|
|
376
|
-
"Migration validation failed. Use --force to continue anyway."
|
|
377
|
-
);
|
|
378
|
-
process.exit(1);
|
|
379
|
-
}
|
|
380
|
-
console.log(
|
|
381
|
-
"Continuing with migration despite validation issues (--force flag provided)"
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Step 7: Synchronize sequences (final step)
|
|
387
|
-
if (!options.skipSequenceSync) {
|
|
388
|
-
console.log("Step 7: Synchronizing database sequences...");
|
|
389
|
-
const sequenceSynchronizer = new SequenceSynchronizer(
|
|
390
|
-
process.env.DATABASE_URL
|
|
391
|
-
);
|
|
392
|
-
await sequenceSynchronizer.synchronizeSequences();
|
|
393
|
-
console.log("Database sequences synchronized successfully!");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
console.log("Migration process completed successfully!");
|
|
397
|
-
} catch (error) {
|
|
398
|
-
console.error("Error running migration:", error);
|
|
399
|
-
process.exit(1);
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
program
|
|
404
|
-
.command("fix-data-types")
|
|
405
|
-
.description("Fix data type issues in the migrated data")
|
|
406
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
407
|
-
.action(async (options) => {
|
|
408
|
-
if (options.target) {
|
|
409
|
-
process.env.DATABASE_URL = options.target;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (!process.env.DATABASE_URL) {
|
|
413
|
-
console.error(
|
|
414
|
-
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
415
|
-
);
|
|
416
|
-
process.exit(1);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
console.log("Fixing data type issues...");
|
|
421
|
-
const { DataTypeFixer } = await import("./fix-data-types");
|
|
422
|
-
const fixer = new DataTypeFixer();
|
|
423
|
-
await fixer.fixDataTypes();
|
|
424
|
-
console.log("Data type fixing completed successfully!");
|
|
425
|
-
} catch (error) {
|
|
426
|
-
console.error("Error fixing data types:", error);
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
program
|
|
432
|
-
.command("truncate-database")
|
|
433
|
-
.description(
|
|
434
|
-
"Truncate (empty) all tables in the database without dropping the database"
|
|
435
|
-
)
|
|
436
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
437
|
-
.action(async (options) => {
|
|
438
|
-
if (options.target) {
|
|
439
|
-
process.env.DATABASE_URL = options.target;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (!process.env.DATABASE_URL) {
|
|
443
|
-
console.error(
|
|
444
|
-
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
445
|
-
);
|
|
446
|
-
process.exit(1);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
console.log("Truncating database...");
|
|
451
|
-
const { DatabaseTruncateTool } = await import("./truncate-database");
|
|
452
|
-
const truncateTool = new DatabaseTruncateTool(process.env.DATABASE_URL);
|
|
453
|
-
await truncateTool.truncateDatabase();
|
|
454
|
-
console.log("Database truncation completed successfully!");
|
|
455
|
-
} catch (error) {
|
|
456
|
-
console.error("Error truncating database:", error);
|
|
457
|
-
process.exit(1);
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
program
|
|
462
|
-
.command("retry-failed")
|
|
463
|
-
.description("Retry failed migrations from an error log file")
|
|
464
|
-
.argument("<error-log-path>", "Path to the error log file")
|
|
465
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
466
|
-
.action(async (errorLogPath, options) => {
|
|
467
|
-
if (options.target) {
|
|
468
|
-
process.env.DATABASE_URL = options.target;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (!process.env.DATABASE_URL) {
|
|
472
|
-
console.error(
|
|
473
|
-
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
474
|
-
);
|
|
475
|
-
process.exit(1);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
try {
|
|
479
|
-
console.log(`Retrying failed migrations from log: ${errorLogPath}`);
|
|
480
|
-
const { FailedMigrationRetry } = await import(
|
|
481
|
-
"./retry-failed-migrations"
|
|
482
|
-
);
|
|
483
|
-
const retryTool = new FailedMigrationRetry(errorLogPath);
|
|
484
|
-
await retryTool.retryFailedMigrations();
|
|
485
|
-
console.log("Retry process completed!");
|
|
486
|
-
} catch (error) {
|
|
487
|
-
console.error("Error retrying failed migrations:", error);
|
|
488
|
-
process.exit(1);
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
program
|
|
493
|
-
.command("fix-enum-values")
|
|
494
|
-
.description("Fix invalid enum values in the database")
|
|
495
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
496
|
-
.action(async (options) => {
|
|
497
|
-
if (options.target) {
|
|
498
|
-
process.env.DATABASE_URL = options.target;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (!process.env.DATABASE_URL) {
|
|
502
|
-
console.error(
|
|
503
|
-
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
504
|
-
);
|
|
505
|
-
process.exit(1);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
try {
|
|
509
|
-
console.log("Fixing enum values...");
|
|
510
|
-
const { EnumFixer } = await import("./fix-enum-values");
|
|
511
|
-
const enumFixer = new EnumFixer(process.env.DATABASE_URL);
|
|
512
|
-
await enumFixer.fixEnumValues();
|
|
513
|
-
console.log("Enum values fixed successfully!");
|
|
514
|
-
} catch (error) {
|
|
515
|
-
console.error("Error fixing enum values:", error);
|
|
516
|
-
process.exit(1);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
program
|
|
521
|
-
.command("pre-validate")
|
|
522
|
-
.description("Validate database before migration")
|
|
523
|
-
.option("-s, --source <url>", "Source database connection URL")
|
|
524
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
525
|
-
.option("-y, --yes", "Automatically answer yes to all prompts", false)
|
|
526
|
-
.action(async (options) => {
|
|
527
|
-
if (options.source) {
|
|
528
|
-
process.env.SOURCE_DATABASE_URL = options.source;
|
|
529
|
-
}
|
|
530
|
-
if (options.target) {
|
|
531
|
-
process.env.DATABASE_URL = options.target;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
535
|
-
console.error(
|
|
536
|
-
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
537
|
-
);
|
|
538
|
-
process.exit(1);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
try {
|
|
542
|
-
console.log("Validating database before migration...");
|
|
543
|
-
const { PreMigrationValidator } = await import(
|
|
544
|
-
"./pre-migration-validator"
|
|
545
|
-
);
|
|
546
|
-
const validator = new PreMigrationValidator();
|
|
547
|
-
const result = await validator.validate();
|
|
548
|
-
|
|
549
|
-
if (result.success) {
|
|
550
|
-
console.log("Pre-migration validation successful!");
|
|
551
|
-
process.exit(0);
|
|
552
|
-
} else {
|
|
553
|
-
console.log(
|
|
554
|
-
`Pre-migration validation found ${result.issueCount} issues.`
|
|
555
|
-
);
|
|
556
|
-
console.log(JSON.stringify(result, null, 2));
|
|
557
|
-
|
|
558
|
-
// Si la opción --yes está activada, continuar automáticamente
|
|
559
|
-
if (options.yes) {
|
|
560
|
-
console.log(
|
|
561
|
-
"Continuing with migration despite issues (--yes flag provided)"
|
|
562
|
-
);
|
|
563
|
-
process.exit(0);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Arreglar el problema con readline usando una promesa
|
|
567
|
-
const readline = require("readline").createInterface({
|
|
568
|
-
input: process.stdin,
|
|
569
|
-
output: process.stdout,
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
const answer = await new Promise<string>((resolve) => {
|
|
573
|
-
readline.question(
|
|
574
|
-
"Continue with migration despite issues? (y/N) ",
|
|
575
|
-
(ans: string) => {
|
|
576
|
-
resolve(ans);
|
|
577
|
-
readline.close();
|
|
578
|
-
}
|
|
579
|
-
);
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
if (answer.toLowerCase() === "y") {
|
|
583
|
-
console.log("Continuing with migration despite issues.");
|
|
584
|
-
process.exit(0);
|
|
585
|
-
} else {
|
|
586
|
-
console.log("Migration aborted by user.");
|
|
587
|
-
process.exit(1);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
} catch (error) {
|
|
591
|
-
console.error("Error during pre-migration validation:", error);
|
|
592
|
-
process.exit(1);
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
program
|
|
597
|
-
.command("sync-enums")
|
|
598
|
-
.description("Synchronize enum types between databases")
|
|
599
|
-
.option("-s, --source <url>", "Source database connection URL")
|
|
600
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
601
|
-
.action(async (options) => {
|
|
602
|
-
if (options.source) {
|
|
603
|
-
process.env.SOURCE_DATABASE_URL = options.source;
|
|
604
|
-
}
|
|
605
|
-
if (options.target) {
|
|
606
|
-
process.env.DATABASE_URL = options.target;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
610
|
-
console.error(
|
|
611
|
-
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
612
|
-
);
|
|
613
|
-
process.exit(1);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
console.log("Synchronizing enum types...");
|
|
618
|
-
const { EnumSynchronizer } = await import("./sync-enum-types");
|
|
619
|
-
const synchronizer = new EnumSynchronizer();
|
|
620
|
-
await synchronizer.synchronizeEnums();
|
|
621
|
-
console.log("Enum types synchronized successfully!");
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error("Error synchronizing enum types:", error);
|
|
624
|
-
process.exit(1);
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
program
|
|
629
|
-
.command("post-validate")
|
|
630
|
-
.description("Validate database after migration")
|
|
631
|
-
.option("-s, --source <url>", "Source database connection URL")
|
|
632
|
-
.option("-t, --target <url>", "Target database connection URL")
|
|
633
|
-
.action(async (options) => {
|
|
634
|
-
if (options.source) {
|
|
635
|
-
process.env.SOURCE_DATABASE_URL = options.source;
|
|
636
|
-
}
|
|
637
|
-
if (options.target) {
|
|
638
|
-
process.env.DATABASE_URL = options.target;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
642
|
-
console.error(
|
|
643
|
-
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
644
|
-
);
|
|
645
|
-
process.exit(1);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
try {
|
|
649
|
-
console.log("Validating database after migration...");
|
|
650
|
-
const { PostMigrationValidator } = await import(
|
|
651
|
-
"./post-migration-validator"
|
|
652
|
-
);
|
|
653
|
-
const validator = new PostMigrationValidator();
|
|
654
|
-
const result = await validator.validate();
|
|
655
|
-
|
|
656
|
-
if (result.success) {
|
|
657
|
-
console.log("Post-migration validation successful!");
|
|
658
|
-
} else {
|
|
659
|
-
console.log(
|
|
660
|
-
`Post-migration validation found ${result.issueCount} issues.`
|
|
661
|
-
);
|
|
662
|
-
console.log(JSON.stringify(result, null, 2));
|
|
663
|
-
}
|
|
664
|
-
} catch (error) {
|
|
665
|
-
console.error("Error during post-migration validation:", error);
|
|
666
|
-
process.exit(1);
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
program
|
|
671
|
-
.command("verify")
|
|
672
|
-
.description("Verify migration setup and connections")
|
|
673
|
-
.action(async () => {
|
|
674
|
-
try {
|
|
675
|
-
const { MigrationSetupVerifier } = await import(
|
|
676
|
-
"./verify-migration-setup"
|
|
677
|
-
);
|
|
678
|
-
const verifier = new MigrationSetupVerifier();
|
|
679
|
-
await verifier.verifySetup();
|
|
680
|
-
} catch (error) {
|
|
681
|
-
console.error("Verification failed:", error);
|
|
682
|
-
process.exit(1);
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
program
|
|
687
|
-
.command("fix-schema")
|
|
688
|
-
.description("Fix schema discrepancies between source and target databases")
|
|
689
|
-
.option("-r, --report <path>", "Path to pre-migration report file")
|
|
690
|
-
.action(async (options) => {
|
|
691
|
-
try {
|
|
692
|
-
const fixer = new SchemaDiscrepancyFixer(
|
|
693
|
-
process.env.SOURCE_DATABASE_URL,
|
|
694
|
-
process.env.DATABASE_URL,
|
|
695
|
-
options.report
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
await fixer.fixDiscrepancies();
|
|
699
|
-
console.log("Schema discrepancies fixed successfully");
|
|
700
|
-
} catch (error) {
|
|
701
|
-
console.error("Error fixing schema discrepancies:", error.message);
|
|
702
|
-
process.exit(1);
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
// Remove the unused run() function or call it if needed
|
|
707
|
-
program.parse();
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import * as dotenv from "dotenv";
|
|
3
|
+
import { exec } from "child_process";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { SchemaDiscrepancyFixer } from "./fix-schema-discrepancies";
|
|
6
|
+
import { EnumSynchronizer } from "./sync-enum-types";
|
|
7
|
+
import { PrismaClient } from "@prisma/client";
|
|
8
|
+
import { MigrationConfig } from "./data-migration/types";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { DatabaseInitializer } from "./database-initializer";
|
|
12
|
+
import { SequenceSynchronizer } from "./sequence-synchronizer";
|
|
13
|
+
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
const program = new Command();
|
|
18
|
+
|
|
19
|
+
// Función para cargar la configuración de migración
|
|
20
|
+
async function loadMigrationConfig(
|
|
21
|
+
configPath?: string
|
|
22
|
+
): Promise<MigrationConfig> {
|
|
23
|
+
const defaultPath = path.join(
|
|
24
|
+
__dirname,
|
|
25
|
+
"data-migration",
|
|
26
|
+
"migration-config.json"
|
|
27
|
+
);
|
|
28
|
+
const filePath = configPath || defaultPath;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const configContent = await fs.promises.readFile(filePath, "utf8");
|
|
32
|
+
return JSON.parse(configContent);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(`Error loading migration config from ${filePath}:`, error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Añadir esta función después de las importaciones
|
|
40
|
+
async function testDatabaseConnection(
|
|
41
|
+
url: string,
|
|
42
|
+
name: string
|
|
43
|
+
): Promise<boolean> {
|
|
44
|
+
const { Pool } = await import("pg");
|
|
45
|
+
const pool = new Pool({ connectionString: url });
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
console.log(`Testing connection to ${name} database...`);
|
|
49
|
+
const client = await pool.connect();
|
|
50
|
+
const result = await client.query("SELECT NOW()");
|
|
51
|
+
client.release();
|
|
52
|
+
|
|
53
|
+
console.log(`Successfully connected to ${name} database`);
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`Failed to connect to ${name} database: ${error.message}`);
|
|
57
|
+
return false;
|
|
58
|
+
} finally {
|
|
59
|
+
await pool.end();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Modificar la acción del comando migrate para incluir la prueba de conexión
|
|
64
|
+
program
|
|
65
|
+
.command("migrate")
|
|
66
|
+
.description("Run the data migration process")
|
|
67
|
+
.option("-s, --source <url>", "Source database connection URL")
|
|
68
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
69
|
+
.option(
|
|
70
|
+
"-m, --mode <mode>",
|
|
71
|
+
"Migration mode: 'multi-tenant' or 'public-only'",
|
|
72
|
+
"multi-tenant"
|
|
73
|
+
)
|
|
74
|
+
.option("-d, --dry-run", "Perform a dry run without making changes", false)
|
|
75
|
+
.option("--skip-schema-creation", "Skip the schema creation step", false)
|
|
76
|
+
.option("--skip-schema-migration", "Skip the schema migration step", false)
|
|
77
|
+
.option("--skip-data-migration", "Skip the data migration step", false)
|
|
78
|
+
.option("--skip-validation", "Skip pre and post validation steps", false)
|
|
79
|
+
.option("--skip-enum-sync", "Skip enum synchronization step", false)
|
|
80
|
+
.option("--auto-fix-schema", "Automatically fix schema discrepancies", false)
|
|
81
|
+
.option("--force", "Force migration even if validation fails", false)
|
|
82
|
+
.option("-y, --yes", "Automatically answer yes to all prompts", false)
|
|
83
|
+
.option("--config-path <path>", "Path to migration config file")
|
|
84
|
+
.option("--skip-initialization", "Skip database initialization step", false)
|
|
85
|
+
.option("--skip-sequence-sync", "Skip sequence synchronization step", false)
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
// Set environment variables for the migration
|
|
88
|
+
if (options.source) {
|
|
89
|
+
process.env.SOURCE_DATABASE_URL = options.source;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (options.target) {
|
|
93
|
+
process.env.DATABASE_URL = options.target;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!process.env.SOURCE_DATABASE_URL) {
|
|
97
|
+
console.error(
|
|
98
|
+
"Source database URL is required. Use --source or set SOURCE_DATABASE_URL environment variable."
|
|
99
|
+
);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!process.env.DATABASE_URL) {
|
|
104
|
+
console.error(
|
|
105
|
+
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
106
|
+
);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (options.dryRun) {
|
|
111
|
+
console.log(
|
|
112
|
+
"Performing dry run - no changes will be made to the database"
|
|
113
|
+
);
|
|
114
|
+
process.env.DRY_RUN = "true";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sourceConnected = await testDatabaseConnection(
|
|
118
|
+
process.env.SOURCE_DATABASE_URL,
|
|
119
|
+
"source"
|
|
120
|
+
);
|
|
121
|
+
const targetConnected = await testDatabaseConnection(
|
|
122
|
+
process.env.DATABASE_URL,
|
|
123
|
+
"target"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!sourceConnected || !targetConnected) {
|
|
127
|
+
console.error(
|
|
128
|
+
"Database connection test failed. Please check your connection strings and try again."
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Step 0: Initialize database if needed
|
|
135
|
+
if (!options.skipInitialization) {
|
|
136
|
+
console.log("Step 0: Checking database initialization...");
|
|
137
|
+
const initializer = new DatabaseInitializer(process.env.DATABASE_URL);
|
|
138
|
+
await initializer.initialize();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 1: Pre-migration validation
|
|
142
|
+
if (!options.skipValidation) {
|
|
143
|
+
console.log("Step 1: Running pre-migration validation...");
|
|
144
|
+
const { PreMigrationValidator } = await import(
|
|
145
|
+
"./pre-migration-validator"
|
|
146
|
+
);
|
|
147
|
+
const validator = new PreMigrationValidator();
|
|
148
|
+
const validationResult = await validator.validate();
|
|
149
|
+
|
|
150
|
+
if (!validationResult.success && !options.force) {
|
|
151
|
+
console.log(
|
|
152
|
+
`Pre-migration validation found ${validationResult.issueCount} issues.`
|
|
153
|
+
);
|
|
154
|
+
console.log(JSON.stringify(validationResult, null, 2));
|
|
155
|
+
|
|
156
|
+
// Si la opción --yes o --force está activada, continuar automáticamente
|
|
157
|
+
if (options.yes) {
|
|
158
|
+
console.log(
|
|
159
|
+
"Continuing with migration despite issues (--yes flag provided)"
|
|
160
|
+
);
|
|
161
|
+
} else if (!options.force) {
|
|
162
|
+
// Preguntar al usuario si desea continuar a pesar de los problemas
|
|
163
|
+
const readline = require("readline").createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const answer = await new Promise<string>((resolve) => {
|
|
169
|
+
readline.question(
|
|
170
|
+
"Continue with migration despite issues? (y/N) ",
|
|
171
|
+
(ans: string) => {
|
|
172
|
+
resolve(ans);
|
|
173
|
+
readline.close();
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (answer.toLowerCase() !== "y") {
|
|
179
|
+
console.log("Migration aborted by user.");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
console.log("Pre-migration validation successful!");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Corregir automáticamente las discrepancias del esquema si se solicita
|
|
188
|
+
if (options.autoFixSchema && validationResult.reportPath) {
|
|
189
|
+
console.log("Automatically fixing schema discrepancies...");
|
|
190
|
+
const fixer = new SchemaDiscrepancyFixer(
|
|
191
|
+
process.env.SOURCE_DATABASE_URL,
|
|
192
|
+
process.env.DATABASE_URL,
|
|
193
|
+
validationResult.reportPath
|
|
194
|
+
);
|
|
195
|
+
await fixer.fixDiscrepancies();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Step 2: Migrate schema structure
|
|
200
|
+
if (!options.skipSchemaMigration) {
|
|
201
|
+
console.log("Step 2: Migrating schema structure...");
|
|
202
|
+
if (options.mode === "multi-tenant") {
|
|
203
|
+
await execAsync("node dist/scripts/migrate-schema-structure.js");
|
|
204
|
+
} else {
|
|
205
|
+
const { Pool } = await import("pg");
|
|
206
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
207
|
+
|
|
208
|
+
// Helper function to check if tables exist
|
|
209
|
+
async function checkIfTablesExist(pool: any): Promise<boolean> {
|
|
210
|
+
try {
|
|
211
|
+
const result = await pool.query(`
|
|
212
|
+
SELECT COUNT(*)
|
|
213
|
+
FROM information_schema.tables
|
|
214
|
+
WHERE table_schema = 'public'
|
|
215
|
+
AND table_type = 'BASE TABLE'
|
|
216
|
+
`);
|
|
217
|
+
|
|
218
|
+
return parseInt(result.rows[0].count) > 0;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error("Error checking tables:", error);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const tablesExist = await checkIfTablesExist(pool);
|
|
226
|
+
if (!tablesExist) {
|
|
227
|
+
await execAsync("npx prisma migrate deploy");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 3: Synchronize and fix enum types
|
|
233
|
+
if (!options.skipEnumSync) {
|
|
234
|
+
console.log("Step 3: Synchronizing and fixing enum types...");
|
|
235
|
+
// Sync enum types
|
|
236
|
+
const { EnumSynchronizer } = await import("./sync-enum-types");
|
|
237
|
+
const synchronizer = new EnumSynchronizer();
|
|
238
|
+
await synchronizer.synchronizeEnums();
|
|
239
|
+
console.log("Enum types synchronized successfully!");
|
|
240
|
+
|
|
241
|
+
// Sync enum values
|
|
242
|
+
console.log("Step 3.1: Synchronizing enum values...");
|
|
243
|
+
const { EnumValueSynchronizer } = await import("./sync-enum-values");
|
|
244
|
+
const valueSynchronizer = new EnumValueSynchronizer();
|
|
245
|
+
await valueSynchronizer.synchronizeEnumValues();
|
|
246
|
+
console.log("Enum values synchronized successfully!");
|
|
247
|
+
|
|
248
|
+
// Fix enum values
|
|
249
|
+
console.log("Step 3.2: Fixing enum values...");
|
|
250
|
+
const { EnumFixer } = await import("./fix-enum-values");
|
|
251
|
+
const enumFixer = new EnumFixer(process.env.DATABASE_URL);
|
|
252
|
+
await enumFixer.fixEnumValues();
|
|
253
|
+
console.log("Enum values fixed successfully!");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Step 4: Fix data types
|
|
257
|
+
console.log("Step 4: Fixing data type issues...");
|
|
258
|
+
const { DataTypeFixer } = await import("./fix-data-types");
|
|
259
|
+
const fixer = new DataTypeFixer();
|
|
260
|
+
await fixer.fixDataTypes();
|
|
261
|
+
console.log("Data type fixing completed successfully!");
|
|
262
|
+
|
|
263
|
+
// Step 5: Migrate data with transformation
|
|
264
|
+
if (!options.skipDataMigration) {
|
|
265
|
+
console.log("Step 5: Migrating data with transformation...");
|
|
266
|
+
const { DataMigrationTool } = await import(
|
|
267
|
+
"./data-migration/migration-tool"
|
|
268
|
+
);
|
|
269
|
+
const { PrismaClient } = await import("@prisma/client");
|
|
270
|
+
const { Pool } = await import("pg");
|
|
271
|
+
|
|
272
|
+
// Cargar la configuración de migración
|
|
273
|
+
const migrationConfig = await loadMigrationConfig(options.configPath);
|
|
274
|
+
|
|
275
|
+
// Create connections with increased pool size and timeout
|
|
276
|
+
const connections = {
|
|
277
|
+
sourcePool: new Pool({
|
|
278
|
+
connectionString: process.env.SOURCE_DATABASE_URL,
|
|
279
|
+
max: 20,
|
|
280
|
+
idleTimeoutMillis: 30000,
|
|
281
|
+
}),
|
|
282
|
+
targetPool: new Pool({
|
|
283
|
+
connectionString: process.env.DATABASE_URL,
|
|
284
|
+
max: 20,
|
|
285
|
+
idleTimeoutMillis: 30000,
|
|
286
|
+
}),
|
|
287
|
+
sourcePrisma: new PrismaClient({
|
|
288
|
+
datasources: { db: { url: process.env.SOURCE_DATABASE_URL } },
|
|
289
|
+
log: ["error", "warn"],
|
|
290
|
+
}),
|
|
291
|
+
targetPrisma: new PrismaClient({
|
|
292
|
+
datasources: { db: { url: process.env.DATABASE_URL } },
|
|
293
|
+
log: ["error", "warn"],
|
|
294
|
+
}),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
// Disable foreign key constraints before migration
|
|
299
|
+
await connections.targetPool.query(
|
|
300
|
+
"SET session_replication_role = 'replica';"
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Si es public-only, modificar la configuración para solo incluir tablas públicas
|
|
304
|
+
if (options.mode === "public-only") {
|
|
305
|
+
migrationConfig.tables = Object.entries(migrationConfig.tables)
|
|
306
|
+
.filter(([_, config]) => config.type === "public")
|
|
307
|
+
.reduce(
|
|
308
|
+
(acc, [key, value]) => ({
|
|
309
|
+
...acc,
|
|
310
|
+
[key]: value,
|
|
311
|
+
}),
|
|
312
|
+
{}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const migrationTool = new DataMigrationTool(
|
|
317
|
+
connections,
|
|
318
|
+
{
|
|
319
|
+
...migrationConfig,
|
|
320
|
+
tables: Object.entries(migrationConfig.tables).reduce(
|
|
321
|
+
(acc, [key, value]) => ({
|
|
322
|
+
...acc,
|
|
323
|
+
[key]: {
|
|
324
|
+
...value,
|
|
325
|
+
sourceTable: value.sourceTable || key,
|
|
326
|
+
targetTable: value.targetTable || key,
|
|
327
|
+
},
|
|
328
|
+
}),
|
|
329
|
+
{}
|
|
330
|
+
),
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
publicOnly: options.mode === "public-only",
|
|
334
|
+
targetSchema: "public",
|
|
335
|
+
sourceSchema: "public",
|
|
336
|
+
multiTenant: options.mode !== "public-only",
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
await migrationTool.migrate();
|
|
341
|
+
} finally {
|
|
342
|
+
// Re-enable foreign key constraints
|
|
343
|
+
await connections.targetPool.query(
|
|
344
|
+
"SET session_replication_role = 'origin';"
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Close connections
|
|
348
|
+
await connections.sourcePool.end();
|
|
349
|
+
await connections.targetPool.end();
|
|
350
|
+
await connections.sourcePrisma.$disconnect();
|
|
351
|
+
await connections.targetPrisma.$disconnect();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Step 6: Post-migration validation
|
|
356
|
+
if (!options.skipValidation) {
|
|
357
|
+
console.log("Step 6: Running post-migration validation...");
|
|
358
|
+
const { PostMigrationValidator } = await import(
|
|
359
|
+
"./post-migration-validator"
|
|
360
|
+
);
|
|
361
|
+
const validator = new PostMigrationValidator();
|
|
362
|
+
const result = await validator.validate({
|
|
363
|
+
publicOnly: options.mode === "public-only",
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (result.success) {
|
|
367
|
+
console.log("Post-migration validation successful!");
|
|
368
|
+
} else {
|
|
369
|
+
console.log(
|
|
370
|
+
`Post-migration validation found ${result.issueCount} issues.`
|
|
371
|
+
);
|
|
372
|
+
console.log(JSON.stringify(result, null, 2));
|
|
373
|
+
|
|
374
|
+
if (!options.force) {
|
|
375
|
+
console.error(
|
|
376
|
+
"Migration validation failed. Use --force to continue anyway."
|
|
377
|
+
);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
console.log(
|
|
381
|
+
"Continuing with migration despite validation issues (--force flag provided)"
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Step 7: Synchronize sequences (final step)
|
|
387
|
+
if (!options.skipSequenceSync) {
|
|
388
|
+
console.log("Step 7: Synchronizing database sequences...");
|
|
389
|
+
const sequenceSynchronizer = new SequenceSynchronizer(
|
|
390
|
+
process.env.DATABASE_URL
|
|
391
|
+
);
|
|
392
|
+
await sequenceSynchronizer.synchronizeSequences();
|
|
393
|
+
console.log("Database sequences synchronized successfully!");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
console.log("Migration process completed successfully!");
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error("Error running migration:", error);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
program
|
|
404
|
+
.command("fix-data-types")
|
|
405
|
+
.description("Fix data type issues in the migrated data")
|
|
406
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
407
|
+
.action(async (options) => {
|
|
408
|
+
if (options.target) {
|
|
409
|
+
process.env.DATABASE_URL = options.target;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!process.env.DATABASE_URL) {
|
|
413
|
+
console.error(
|
|
414
|
+
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
415
|
+
);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
console.log("Fixing data type issues...");
|
|
421
|
+
const { DataTypeFixer } = await import("./fix-data-types");
|
|
422
|
+
const fixer = new DataTypeFixer();
|
|
423
|
+
await fixer.fixDataTypes();
|
|
424
|
+
console.log("Data type fixing completed successfully!");
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error("Error fixing data types:", error);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
program
|
|
432
|
+
.command("truncate-database")
|
|
433
|
+
.description(
|
|
434
|
+
"Truncate (empty) all tables in the database without dropping the database"
|
|
435
|
+
)
|
|
436
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
437
|
+
.action(async (options) => {
|
|
438
|
+
if (options.target) {
|
|
439
|
+
process.env.DATABASE_URL = options.target;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!process.env.DATABASE_URL) {
|
|
443
|
+
console.error(
|
|
444
|
+
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
445
|
+
);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
console.log("Truncating database...");
|
|
451
|
+
const { DatabaseTruncateTool } = await import("./truncate-database");
|
|
452
|
+
const truncateTool = new DatabaseTruncateTool(process.env.DATABASE_URL);
|
|
453
|
+
await truncateTool.truncateDatabase();
|
|
454
|
+
console.log("Database truncation completed successfully!");
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.error("Error truncating database:", error);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
program
|
|
462
|
+
.command("retry-failed")
|
|
463
|
+
.description("Retry failed migrations from an error log file")
|
|
464
|
+
.argument("<error-log-path>", "Path to the error log file")
|
|
465
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
466
|
+
.action(async (errorLogPath, options) => {
|
|
467
|
+
if (options.target) {
|
|
468
|
+
process.env.DATABASE_URL = options.target;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!process.env.DATABASE_URL) {
|
|
472
|
+
console.error(
|
|
473
|
+
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
474
|
+
);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
console.log(`Retrying failed migrations from log: ${errorLogPath}`);
|
|
480
|
+
const { FailedMigrationRetry } = await import(
|
|
481
|
+
"./retry-failed-migrations"
|
|
482
|
+
);
|
|
483
|
+
const retryTool = new FailedMigrationRetry(errorLogPath);
|
|
484
|
+
await retryTool.retryFailedMigrations();
|
|
485
|
+
console.log("Retry process completed!");
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error("Error retrying failed migrations:", error);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
program
|
|
493
|
+
.command("fix-enum-values")
|
|
494
|
+
.description("Fix invalid enum values in the database")
|
|
495
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
496
|
+
.action(async (options) => {
|
|
497
|
+
if (options.target) {
|
|
498
|
+
process.env.DATABASE_URL = options.target;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!process.env.DATABASE_URL) {
|
|
502
|
+
console.error(
|
|
503
|
+
"Target database URL is required. Use --target or set DATABASE_URL environment variable."
|
|
504
|
+
);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
console.log("Fixing enum values...");
|
|
510
|
+
const { EnumFixer } = await import("./fix-enum-values");
|
|
511
|
+
const enumFixer = new EnumFixer(process.env.DATABASE_URL);
|
|
512
|
+
await enumFixer.fixEnumValues();
|
|
513
|
+
console.log("Enum values fixed successfully!");
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error("Error fixing enum values:", error);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
program
|
|
521
|
+
.command("pre-validate")
|
|
522
|
+
.description("Validate database before migration")
|
|
523
|
+
.option("-s, --source <url>", "Source database connection URL")
|
|
524
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
525
|
+
.option("-y, --yes", "Automatically answer yes to all prompts", false)
|
|
526
|
+
.action(async (options) => {
|
|
527
|
+
if (options.source) {
|
|
528
|
+
process.env.SOURCE_DATABASE_URL = options.source;
|
|
529
|
+
}
|
|
530
|
+
if (options.target) {
|
|
531
|
+
process.env.DATABASE_URL = options.target;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
535
|
+
console.error(
|
|
536
|
+
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
537
|
+
);
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
console.log("Validating database before migration...");
|
|
543
|
+
const { PreMigrationValidator } = await import(
|
|
544
|
+
"./pre-migration-validator"
|
|
545
|
+
);
|
|
546
|
+
const validator = new PreMigrationValidator();
|
|
547
|
+
const result = await validator.validate();
|
|
548
|
+
|
|
549
|
+
if (result.success) {
|
|
550
|
+
console.log("Pre-migration validation successful!");
|
|
551
|
+
process.exit(0);
|
|
552
|
+
} else {
|
|
553
|
+
console.log(
|
|
554
|
+
`Pre-migration validation found ${result.issueCount} issues.`
|
|
555
|
+
);
|
|
556
|
+
console.log(JSON.stringify(result, null, 2));
|
|
557
|
+
|
|
558
|
+
// Si la opción --yes está activada, continuar automáticamente
|
|
559
|
+
if (options.yes) {
|
|
560
|
+
console.log(
|
|
561
|
+
"Continuing with migration despite issues (--yes flag provided)"
|
|
562
|
+
);
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Arreglar el problema con readline usando una promesa
|
|
567
|
+
const readline = require("readline").createInterface({
|
|
568
|
+
input: process.stdin,
|
|
569
|
+
output: process.stdout,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const answer = await new Promise<string>((resolve) => {
|
|
573
|
+
readline.question(
|
|
574
|
+
"Continue with migration despite issues? (y/N) ",
|
|
575
|
+
(ans: string) => {
|
|
576
|
+
resolve(ans);
|
|
577
|
+
readline.close();
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
if (answer.toLowerCase() === "y") {
|
|
583
|
+
console.log("Continuing with migration despite issues.");
|
|
584
|
+
process.exit(0);
|
|
585
|
+
} else {
|
|
586
|
+
console.log("Migration aborted by user.");
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error("Error during pre-migration validation:", error);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
program
|
|
597
|
+
.command("sync-enums")
|
|
598
|
+
.description("Synchronize enum types between databases")
|
|
599
|
+
.option("-s, --source <url>", "Source database connection URL")
|
|
600
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
601
|
+
.action(async (options) => {
|
|
602
|
+
if (options.source) {
|
|
603
|
+
process.env.SOURCE_DATABASE_URL = options.source;
|
|
604
|
+
}
|
|
605
|
+
if (options.target) {
|
|
606
|
+
process.env.DATABASE_URL = options.target;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
610
|
+
console.error(
|
|
611
|
+
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
612
|
+
);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
console.log("Synchronizing enum types...");
|
|
618
|
+
const { EnumSynchronizer } = await import("./sync-enum-types");
|
|
619
|
+
const synchronizer = new EnumSynchronizer();
|
|
620
|
+
await synchronizer.synchronizeEnums();
|
|
621
|
+
console.log("Enum types synchronized successfully!");
|
|
622
|
+
} catch (error) {
|
|
623
|
+
console.error("Error synchronizing enum types:", error);
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
program
|
|
629
|
+
.command("post-validate")
|
|
630
|
+
.description("Validate database after migration")
|
|
631
|
+
.option("-s, --source <url>", "Source database connection URL")
|
|
632
|
+
.option("-t, --target <url>", "Target database connection URL")
|
|
633
|
+
.action(async (options) => {
|
|
634
|
+
if (options.source) {
|
|
635
|
+
process.env.SOURCE_DATABASE_URL = options.source;
|
|
636
|
+
}
|
|
637
|
+
if (options.target) {
|
|
638
|
+
process.env.DATABASE_URL = options.target;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (!process.env.SOURCE_DATABASE_URL || !process.env.DATABASE_URL) {
|
|
642
|
+
console.error(
|
|
643
|
+
"Source and target database URLs are required. Use --source and --target or set SOURCE_DATABASE_URL and DATABASE_URL environment variables."
|
|
644
|
+
);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
console.log("Validating database after migration...");
|
|
650
|
+
const { PostMigrationValidator } = await import(
|
|
651
|
+
"./post-migration-validator"
|
|
652
|
+
);
|
|
653
|
+
const validator = new PostMigrationValidator();
|
|
654
|
+
const result = await validator.validate();
|
|
655
|
+
|
|
656
|
+
if (result.success) {
|
|
657
|
+
console.log("Post-migration validation successful!");
|
|
658
|
+
} else {
|
|
659
|
+
console.log(
|
|
660
|
+
`Post-migration validation found ${result.issueCount} issues.`
|
|
661
|
+
);
|
|
662
|
+
console.log(JSON.stringify(result, null, 2));
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
console.error("Error during post-migration validation:", error);
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
program
|
|
671
|
+
.command("verify")
|
|
672
|
+
.description("Verify migration setup and connections")
|
|
673
|
+
.action(async () => {
|
|
674
|
+
try {
|
|
675
|
+
const { MigrationSetupVerifier } = await import(
|
|
676
|
+
"./verify-migration-setup"
|
|
677
|
+
);
|
|
678
|
+
const verifier = new MigrationSetupVerifier();
|
|
679
|
+
await verifier.verifySetup();
|
|
680
|
+
} catch (error) {
|
|
681
|
+
console.error("Verification failed:", error);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
program
|
|
687
|
+
.command("fix-schema")
|
|
688
|
+
.description("Fix schema discrepancies between source and target databases")
|
|
689
|
+
.option("-r, --report <path>", "Path to pre-migration report file")
|
|
690
|
+
.action(async (options) => {
|
|
691
|
+
try {
|
|
692
|
+
const fixer = new SchemaDiscrepancyFixer(
|
|
693
|
+
process.env.SOURCE_DATABASE_URL,
|
|
694
|
+
process.env.DATABASE_URL,
|
|
695
|
+
options.report
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
await fixer.fixDiscrepancies();
|
|
699
|
+
console.log("Schema discrepancies fixed successfully");
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error("Error fixing schema discrepancies:", error.message);
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// Remove the unused run() function or call it if needed
|
|
707
|
+
program.parse();
|