@zintrust/d1-migrator 1.8.1 → 1.8.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataMigrator.d.ts","sourceRoot":"","sources":["../../src/cli/DataMigrator.ts"],"names":[],"mappings":"AACA;;;GAGG;AAWH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxE,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"DataMigrator.d.ts","sourceRoot":"","sources":["../../src/cli/DataMigrator.ts"],"names":[],"mappings":"AACA;;;GAGG;AAWH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxE,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAkLF;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB;;OAEG;wBACuB,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAqFtE;;OAEG;4BAC2B,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4BzE;;OAEG;4BAC2B,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyCzE;;OAEG;0CAEiB,gBAAgB,oBAChB,gBAAgB,UAC1B,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;+BAC8B,gBAAgB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,EAAE,CAAA;KAAE,CAAC;IAiBpF;;OAEG;wBAEM,SAAS,oBACE,gBAAgB,oBAChB,gBAAgB,UAC1B,eAAe,GACtB,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA4EtD;;OAEG;oCAEiB,gBAAgB,aACvB,MAAM,UACT,MAAM,aACH,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAkBrC;;OAEG;yBAEM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,aACrB,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IA4CrC;;OAEG;iCAEiB,gBAAgB,aACvB,MAAM,QACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,OAAO,CAAC,MAAM,CAAC;IAkClB;;OAEG;gCACyB,eAAe,CAAC,cAAc,CAAC,aAAa,MAAM,GAAG,MAAM;IAavF;;OAEG;wCAEM,MAAM,UACL,MAAM,gBACA,MAAM,gBACN,MAAM,GACnB,0BAA0B;IAS7B;;OAEG;gCACyB,MAAM,GAAG,iBAAiB;IAetD;;OAEG;6BAES,iBAAiB,WAClB,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB;EAGpB,CAAC"}
|
package/dist/cli/DataMigrator.js
CHANGED
|
@@ -10,6 +10,47 @@ import { SQLiteAdapter } from '@zintrust/db-sqlite';
|
|
|
10
10
|
import { SQLServerAdapter } from '@zintrust/db-sqlserver';
|
|
11
11
|
import { SchemaBuilder } from '../schema/SchemaBuilder.js';
|
|
12
12
|
import { SchemaAnalyzer } from './SchemaAnalyzer.js';
|
|
13
|
+
const redactConnectionString = (connectionString) => {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = new URL(connectionString);
|
|
16
|
+
if (parsed.password.trim() !== '') {
|
|
17
|
+
parsed.password = '***';
|
|
18
|
+
}
|
|
19
|
+
return parsed.toString();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return connectionString;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const getErrorCause = (error) => {
|
|
26
|
+
if (error === null || typeof error !== 'object') {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return error.cause;
|
|
30
|
+
};
|
|
31
|
+
const getErrorMessage = (error) => {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
return error.message;
|
|
34
|
+
}
|
|
35
|
+
return String(error);
|
|
36
|
+
};
|
|
37
|
+
const getErrorChainMessages = (error) => {
|
|
38
|
+
const messages = [];
|
|
39
|
+
let current = error;
|
|
40
|
+
while (current !== undefined) {
|
|
41
|
+
const message = getErrorMessage(current);
|
|
42
|
+
if (message.trim() !== '') {
|
|
43
|
+
messages.push(message);
|
|
44
|
+
}
|
|
45
|
+
current = getErrorCause(current);
|
|
46
|
+
}
|
|
47
|
+
return [...new Set(messages)];
|
|
48
|
+
};
|
|
49
|
+
const describePasswordForLog = (password) => {
|
|
50
|
+
const hasSpecialCharacters = /[^a-zA-Z0-9]/.test(password);
|
|
51
|
+
const containsBang = password.includes('!');
|
|
52
|
+
return `len=${password.length}, special_chars=${hasSpecialCharacters}, contains_bang=${containsBang}`;
|
|
53
|
+
};
|
|
13
54
|
const normalizeNullLikeValue = (value) => {
|
|
14
55
|
if (typeof value !== 'string')
|
|
15
56
|
return value;
|
|
@@ -51,6 +92,47 @@ const parseSqliteDatabasePath = (connectionString) => {
|
|
|
51
92
|
return trimmed;
|
|
52
93
|
}
|
|
53
94
|
};
|
|
95
|
+
const createSourceAdapter = (config) => {
|
|
96
|
+
switch (config.sourceDriver) {
|
|
97
|
+
case 'mysql': {
|
|
98
|
+
const connectionDetails = parseConnectionDetails(config.sourceConnection, 3306, 'mysql', 'root');
|
|
99
|
+
Logger.info(`[DataMigrator] Source password diagnostics: ${describePasswordForLog(connectionDetails.password)}`);
|
|
100
|
+
return MySQLAdapter.create({
|
|
101
|
+
driver: 'mysql',
|
|
102
|
+
connectionString: config.sourceConnection,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
case 'postgresql': {
|
|
106
|
+
const connectionDetails = parseConnectionDetails(config.sourceConnection, 5432, 'postgres', 'postgres');
|
|
107
|
+
return PostgreSQLAdapter.create({
|
|
108
|
+
driver: 'postgresql',
|
|
109
|
+
host: connectionDetails.host,
|
|
110
|
+
port: connectionDetails.port,
|
|
111
|
+
database: connectionDetails.database,
|
|
112
|
+
username: connectionDetails.username,
|
|
113
|
+
password: connectionDetails.password,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
case 'sqlite':
|
|
117
|
+
return SQLiteAdapter.create({
|
|
118
|
+
driver: 'sqlite',
|
|
119
|
+
database: parseSqliteDatabasePath(config.sourceConnection),
|
|
120
|
+
});
|
|
121
|
+
case 'sqlserver': {
|
|
122
|
+
const connectionDetails = parseConnectionDetails(config.sourceConnection, 1433, 'master', 'sa');
|
|
123
|
+
return SQLServerAdapter.create({
|
|
124
|
+
driver: 'sqlserver',
|
|
125
|
+
host: connectionDetails.host,
|
|
126
|
+
port: connectionDetails.port,
|
|
127
|
+
database: connectionDetails.database,
|
|
128
|
+
username: connectionDetails.username,
|
|
129
|
+
password: connectionDetails.password,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
throw ErrorFactory.createValidationError(`Unsupported driver: ${config.sourceDriver}`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
54
136
|
const safelyDisconnect = async (label, connection) => {
|
|
55
137
|
try {
|
|
56
138
|
await connection?.adapter?.disconnect?.();
|
|
@@ -121,7 +203,9 @@ export const DataMigrator = Object.freeze({
|
|
|
121
203
|
return progress;
|
|
122
204
|
}
|
|
123
205
|
catch (error) {
|
|
124
|
-
|
|
206
|
+
const errorChain = getErrorChainMessages(error);
|
|
207
|
+
Logger.error(`Data migration failed: ${errorChain.join(' -> ')}`);
|
|
208
|
+
Logger.error('Data migration failure details:', error);
|
|
125
209
|
throw error;
|
|
126
210
|
}
|
|
127
211
|
finally {
|
|
@@ -133,49 +217,18 @@ export const DataMigrator = Object.freeze({
|
|
|
133
217
|
* Connect to source database
|
|
134
218
|
*/
|
|
135
219
|
async connectToSource(config) {
|
|
136
|
-
Logger.info(`Connecting to ${config.sourceDriver} database
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
adapter = PostgreSQLAdapter.create({
|
|
148
|
-
driver: 'postgresql',
|
|
149
|
-
host: connectionDetails.host,
|
|
150
|
-
port: connectionDetails.port,
|
|
151
|
-
database: connectionDetails.database,
|
|
152
|
-
username: connectionDetails.username,
|
|
153
|
-
password: connectionDetails.password,
|
|
154
|
-
});
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
case 'sqlite':
|
|
158
|
-
adapter = SQLiteAdapter.create({
|
|
159
|
-
driver: 'sqlite',
|
|
160
|
-
database: parseSqliteDatabasePath(config.sourceConnection),
|
|
161
|
-
});
|
|
162
|
-
break;
|
|
163
|
-
case 'sqlserver': {
|
|
164
|
-
const connectionDetails = parseConnectionDetails(config.sourceConnection, 1433, 'master', 'sa');
|
|
165
|
-
adapter = SQLServerAdapter.create({
|
|
166
|
-
driver: 'sqlserver',
|
|
167
|
-
host: connectionDetails.host,
|
|
168
|
-
port: connectionDetails.port,
|
|
169
|
-
database: connectionDetails.database,
|
|
170
|
-
username: connectionDetails.username,
|
|
171
|
-
password: connectionDetails.password,
|
|
172
|
-
});
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
default:
|
|
176
|
-
throw ErrorFactory.createValidationError(`Unsupported driver: ${config.sourceDriver}`);
|
|
220
|
+
Logger.info(`Connecting to ${config.sourceDriver} database...`);
|
|
221
|
+
Logger.info(`[DataMigrator] Source connection (redacted): ${redactConnectionString(config.sourceConnection)}`);
|
|
222
|
+
const adapter = createSourceAdapter(config);
|
|
223
|
+
try {
|
|
224
|
+
await adapter.connect();
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
const errorChain = getErrorChainMessages(error);
|
|
228
|
+
Logger.error(`Source database connection failed: ${errorChain.join(' -> ')}`);
|
|
229
|
+
Logger.error('Source database connection failure details:', error);
|
|
230
|
+
throw error;
|
|
177
231
|
}
|
|
178
|
-
await adapter.connect();
|
|
179
232
|
const connection = {
|
|
180
233
|
driver: config.sourceDriver,
|
|
181
234
|
connectionString: config.sourceConnection || '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MigrateToD1Command.d.ts","sourceRoot":"","sources":["../../src/cli/MigrateToD1Command.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAOhD,KAAK,iBAAiB,GAAG;IACvB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,IAAI,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B,CAAC;
|
|
1
|
+
{"version":3,"file":"MigrateToD1Command.d.ts","sourceRoot":"","sources":["../../src/cli/MigrateToD1Command.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAOhD,KAAK,iBAAiB,GAAG;IACvB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,IAAI,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B,CAAC;AA6rBF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBA4G/B,CAAC;AAEH;;GAEG;AACH,iBAAe,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CActE;AAED;;GAEG;AACH,iBAAe,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkGxE;AAED;;GAEG;AACH,iBAAe,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiGtE;AAED;;GAEG;AACH,iBAAS,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CA2BrF;AAGD,eAAO,MAAM,iBAAiB;;;;;EAK5B,CAAC"}
|
|
@@ -48,6 +48,18 @@ const TARGET_DATABASE_ENV_KEYS = Object.freeze([
|
|
|
48
48
|
'D1_DATABASE',
|
|
49
49
|
'D1_DATABASE_ID',
|
|
50
50
|
]);
|
|
51
|
+
const uniq = (items) => {
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const item of items) {
|
|
55
|
+
if (seen.has(item)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
seen.add(item);
|
|
59
|
+
out.push(item);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
51
63
|
const describeConfiguredD1Target = (config) => {
|
|
52
64
|
const parts = [];
|
|
53
65
|
if (typeof config.database_name === 'string' && config.database_name.trim() !== '') {
|
|
@@ -157,6 +169,112 @@ const encodeConnectionSegment = (value) => {
|
|
|
157
169
|
return `%${match.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
158
170
|
});
|
|
159
171
|
};
|
|
172
|
+
const decodeConnectionSegment = (value) => {
|
|
173
|
+
return value.trim() === '' ? '' : decodeURIComponent(value);
|
|
174
|
+
};
|
|
175
|
+
const getNetworkScheme = (sourceDriver) => {
|
|
176
|
+
if (sourceDriver === 'mysql')
|
|
177
|
+
return 'mysql';
|
|
178
|
+
if (sourceDriver === 'postgresql')
|
|
179
|
+
return 'postgresql';
|
|
180
|
+
if (sourceDriver === 'sqlserver')
|
|
181
|
+
return 'mssql';
|
|
182
|
+
return undefined;
|
|
183
|
+
};
|
|
184
|
+
const getDefaultNetworkPort = (sourceDriver) => {
|
|
185
|
+
if (sourceDriver === 'mysql')
|
|
186
|
+
return 3306;
|
|
187
|
+
if (sourceDriver === 'postgresql')
|
|
188
|
+
return 5432;
|
|
189
|
+
return 1433;
|
|
190
|
+
};
|
|
191
|
+
const parseNetworkConnectionDetails = (connectionString, sourceDriver) => {
|
|
192
|
+
const expectedScheme = getNetworkScheme(sourceDriver);
|
|
193
|
+
if (expectedScheme === undefined) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const parsed = new URL(connectionString);
|
|
198
|
+
const protocol = parsed.protocol.replace(/:$/, '').toLowerCase();
|
|
199
|
+
const allowedProtocols = sourceDriver === 'sqlserver' ? ['mssql', 'sqlserver'] : [expectedScheme];
|
|
200
|
+
if (!allowedProtocols.includes(protocol)) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
scheme: expectedScheme,
|
|
205
|
+
host: parsed.hostname || 'localhost',
|
|
206
|
+
port: parsed.port.trim() === ''
|
|
207
|
+
? getDefaultNetworkPort(sourceDriver)
|
|
208
|
+
: Number.parseInt(parsed.port, 10),
|
|
209
|
+
database: decodeConnectionSegment(parsed.pathname.replace(/^\/+/, '')),
|
|
210
|
+
username: decodeConnectionSegment(parsed.username),
|
|
211
|
+
password: decodeConnectionSegment(parsed.password),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const normalizeSourceConnectionString = (sourceConnection, sourceDriver) => {
|
|
219
|
+
const details = parseNetworkConnectionDetails(sourceConnection, sourceDriver);
|
|
220
|
+
if (details === undefined) {
|
|
221
|
+
return sourceConnection;
|
|
222
|
+
}
|
|
223
|
+
return buildNetworkConnectionString(details);
|
|
224
|
+
};
|
|
225
|
+
const redactConnectionString = (sourceConnection) => {
|
|
226
|
+
try {
|
|
227
|
+
const parsed = new URL(sourceConnection);
|
|
228
|
+
if (parsed.password.trim() !== '') {
|
|
229
|
+
parsed.password = '***';
|
|
230
|
+
}
|
|
231
|
+
return parsed.toString();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return sourceConnection;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const describePasswordForLog = (password) => {
|
|
238
|
+
const hasSpecialCharacters = /[^a-zA-Z0-9]/.test(password);
|
|
239
|
+
const containsBang = password.includes('!');
|
|
240
|
+
return `len=${password.length}, special_chars=${hasSpecialCharacters}, contains_bang=${containsBang}`;
|
|
241
|
+
};
|
|
242
|
+
const getErrorCause = (error) => {
|
|
243
|
+
if (error === null || typeof error !== 'object') {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
return error.cause;
|
|
247
|
+
};
|
|
248
|
+
const getErrorMessage = (error) => {
|
|
249
|
+
if (error instanceof Error) {
|
|
250
|
+
return error.message;
|
|
251
|
+
}
|
|
252
|
+
return String(error);
|
|
253
|
+
};
|
|
254
|
+
const getErrorChainMessages = (error) => {
|
|
255
|
+
const messages = [];
|
|
256
|
+
let current = error;
|
|
257
|
+
while (current !== undefined) {
|
|
258
|
+
const message = getErrorMessage(current);
|
|
259
|
+
if (message.trim() !== '') {
|
|
260
|
+
messages.push(message);
|
|
261
|
+
}
|
|
262
|
+
current = getErrorCause(current);
|
|
263
|
+
}
|
|
264
|
+
return uniq(messages);
|
|
265
|
+
};
|
|
266
|
+
const logSourceConnectionDiagnostics = (sourceDriver, sourceConnection, origin, originalValue) => {
|
|
267
|
+
Logger.info(`[d1-migrator] Source connection origin: ${origin}`);
|
|
268
|
+
Logger.info(`[d1-migrator] Source connection driver: ${sourceDriver}`);
|
|
269
|
+
Logger.info(`[d1-migrator] Source connection (redacted): ${redactConnectionString(sourceConnection)}`);
|
|
270
|
+
const originalDetails = parseNetworkConnectionDetails(originalValue, sourceDriver);
|
|
271
|
+
const finalDetails = parseNetworkConnectionDetails(sourceConnection, sourceDriver);
|
|
272
|
+
if (originalDetails === undefined || finalDetails === undefined) {
|
|
273
|
+
Logger.info('[d1-migrator] Source connection diagnostics: non-network source or unable to parse URL');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
Logger.info(`[d1-migrator] Source password diagnostics: provided(${describePasswordForLog(originalDetails.password)}), final(${describePasswordForLog(finalDetails.password)}), matches=${originalDetails.password === finalDetails.password}`);
|
|
277
|
+
};
|
|
160
278
|
const normalizeSourceDriver = (value) => {
|
|
161
279
|
if (value === undefined) {
|
|
162
280
|
return undefined;
|
|
@@ -272,15 +390,27 @@ const buildSourceConnectionFromDbEnv = (sourceDriver) => {
|
|
|
272
390
|
const resolveSourceConnection = (options, sourceDriver) => {
|
|
273
391
|
const fromOption = readOptionString(options, ['source-connection', 'sourceConnection']);
|
|
274
392
|
if (fromOption !== undefined) {
|
|
275
|
-
return
|
|
393
|
+
return {
|
|
394
|
+
value: normalizeSourceConnectionString(fromOption, sourceDriver),
|
|
395
|
+
origin: 'option',
|
|
396
|
+
originalValue: fromOption,
|
|
397
|
+
};
|
|
276
398
|
}
|
|
277
399
|
const fromEnv = readEnvString(SOURCE_CONNECTION_ENV_KEYS);
|
|
278
400
|
if (fromEnv !== undefined) {
|
|
279
|
-
return
|
|
401
|
+
return {
|
|
402
|
+
value: normalizeSourceConnectionString(fromEnv, sourceDriver),
|
|
403
|
+
origin: 'env',
|
|
404
|
+
originalValue: fromEnv,
|
|
405
|
+
};
|
|
280
406
|
}
|
|
281
407
|
const fromDbEnv = buildSourceConnectionFromDbEnv(sourceDriver);
|
|
282
408
|
if (fromDbEnv !== undefined && fromDbEnv.trim().length > 0) {
|
|
283
|
-
return
|
|
409
|
+
return {
|
|
410
|
+
value: normalizeSourceConnectionString(fromDbEnv, sourceDriver),
|
|
411
|
+
origin: 'db-env',
|
|
412
|
+
originalValue: fromDbEnv,
|
|
413
|
+
};
|
|
284
414
|
}
|
|
285
415
|
throw ErrorFactory.createValidationError('Source connection is required. Use --source-connection or set MIGRATE_TO_D1_SOURCE_CONNECTION (or DB_* variables)');
|
|
286
416
|
};
|
|
@@ -314,7 +444,7 @@ const resolveTargetDatabase = (options) => {
|
|
|
314
444
|
};
|
|
315
445
|
const resolveMigrationConfig = (options) => {
|
|
316
446
|
const sourceDriver = resolveSourceDriver(options);
|
|
317
|
-
const
|
|
447
|
+
const sourceConnectionResolution = resolveSourceConnection(options, sourceDriver);
|
|
318
448
|
const targetDatabase = resolveTargetDatabase(options);
|
|
319
449
|
const targetType = resolveTargetType(options);
|
|
320
450
|
const dryRun = resolveFlag(options, ['dry-run', 'dryRun'], ['MIGRATE_TO_D1_DRY_RUN', 'D1_MIGRATOR_DRY_RUN']);
|
|
@@ -328,7 +458,7 @@ const resolveMigrationConfig = (options) => {
|
|
|
328
458
|
}
|
|
329
459
|
return {
|
|
330
460
|
config: {
|
|
331
|
-
sourceConnection,
|
|
461
|
+
sourceConnection: sourceConnectionResolution.value,
|
|
332
462
|
sourceDriver,
|
|
333
463
|
targetDatabase,
|
|
334
464
|
targetType,
|
|
@@ -339,6 +469,8 @@ const resolveMigrationConfig = (options) => {
|
|
|
339
469
|
migrationId,
|
|
340
470
|
},
|
|
341
471
|
schemaOnly,
|
|
472
|
+
sourceConnectionOrigin: sourceConnectionResolution.origin,
|
|
473
|
+
originalSourceConnection: sourceConnectionResolution.originalValue,
|
|
342
474
|
};
|
|
343
475
|
};
|
|
344
476
|
/**
|
|
@@ -366,7 +498,8 @@ export const MigrateToD1Command = BaseCommand.create({
|
|
|
366
498
|
execute: async (options) => {
|
|
367
499
|
try {
|
|
368
500
|
Logger.info('Starting D1 migration process...');
|
|
369
|
-
const { config, schemaOnly } = resolveMigrationConfig(options);
|
|
501
|
+
const { config, schemaOnly, sourceConnectionOrigin, originalSourceConnection } = resolveMigrationConfig(options);
|
|
502
|
+
logSourceConnectionDiagnostics(config.sourceDriver, config.sourceConnection, sourceConnectionOrigin, originalSourceConnection);
|
|
370
503
|
const configValidation = validateConfig(config);
|
|
371
504
|
if (!configValidation.valid) {
|
|
372
505
|
throw ErrorFactory.createValidationError(`Invalid migration configuration: ${configValidation.errors.join(', ')}`);
|
|
@@ -419,8 +552,10 @@ export const MigrateToD1Command = BaseCommand.create({
|
|
|
419
552
|
Logger.info('D1 migration completed successfully');
|
|
420
553
|
}
|
|
421
554
|
catch (error) {
|
|
422
|
-
|
|
423
|
-
|
|
555
|
+
const errorChain = getErrorChainMessages(error);
|
|
556
|
+
Logger.error(`Migration failed: ${errorChain.join(' -> ')}`);
|
|
557
|
+
Logger.error('Migration failure details:', error);
|
|
558
|
+
throw ErrorFactory.createValidationError(`Migration failed: ${errorChain[0] ?? getErrorMessage(error)}`, error);
|
|
424
559
|
}
|
|
425
560
|
},
|
|
426
561
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaAnalyzer.d.ts","sourceRoot":"","sources":["../../src/cli/SchemaAnalyzer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"SchemaAnalyzer.d.ts","sourceRoot":"","sources":["../../src/cli/SchemaAnalyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,42 @@ import { MySQLAdapter } from '@zintrust/db-mysql';
|
|
|
7
7
|
import { PostgreSQLAdapter } from '@zintrust/db-postgres';
|
|
8
8
|
import { SQLiteAdapter } from '@zintrust/db-sqlite';
|
|
9
9
|
import { SQLServerAdapter } from '@zintrust/db-sqlserver';
|
|
10
|
+
const redactConnectionString = (connectionString) => {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(connectionString);
|
|
13
|
+
if (parsed.password.trim() !== '') {
|
|
14
|
+
parsed.password = '***';
|
|
15
|
+
}
|
|
16
|
+
return parsed.toString();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return connectionString;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const getErrorCause = (error) => {
|
|
23
|
+
if (error === null || typeof error !== 'object') {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return error.cause;
|
|
27
|
+
};
|
|
28
|
+
const getErrorMessage = (error) => {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
return error.message;
|
|
31
|
+
}
|
|
32
|
+
return String(error);
|
|
33
|
+
};
|
|
34
|
+
const getErrorChainMessages = (error) => {
|
|
35
|
+
const messages = [];
|
|
36
|
+
let current = error;
|
|
37
|
+
while (current !== undefined) {
|
|
38
|
+
const message = getErrorMessage(current);
|
|
39
|
+
if (message.trim() !== '') {
|
|
40
|
+
messages.push(message);
|
|
41
|
+
}
|
|
42
|
+
current = getErrorCause(current);
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(messages)];
|
|
45
|
+
};
|
|
10
46
|
/**
|
|
11
47
|
* SchemaAnalyzer - Sealed namespace for schema analysis
|
|
12
48
|
* Provides database schema analysis and compatibility checking
|
|
@@ -40,6 +76,7 @@ export const SchemaAnalyzer = Object.freeze({
|
|
|
40
76
|
*/
|
|
41
77
|
async extractTables(connection) {
|
|
42
78
|
Logger.info(`Extracting tables from ${connection.driver} database...`);
|
|
79
|
+
Logger.info(`[SchemaAnalyzer] Source connection (redacted): ${redactConnectionString(connection.connectionString)}`);
|
|
43
80
|
try {
|
|
44
81
|
// Create appropriate adapter based on driver
|
|
45
82
|
let adapter;
|
|
@@ -79,8 +116,10 @@ export const SchemaAnalyzer = Object.freeze({
|
|
|
79
116
|
return tableSchemas;
|
|
80
117
|
}
|
|
81
118
|
catch (error) {
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
const errorChain = getErrorChainMessages(error);
|
|
120
|
+
Logger.error(`Failed to extract database tables: ${errorChain.join(' -> ')}`);
|
|
121
|
+
Logger.error('Schema extraction failure details:', error);
|
|
122
|
+
throw ErrorFactory.createTryCatchError(`Schema extraction failed: ${errorChain[0] ?? getErrorMessage(error)}`, error);
|
|
84
123
|
}
|
|
85
124
|
},
|
|
86
125
|
/**
|