@bdkinc/knex-ibmi 0.3.21 → 0.4.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/LICENSE +0 -0
- package/README.md +250 -127
- package/dist/cli.cjs +531 -0
- package/dist/index.d.mts +137 -0
- package/dist/index.d.ts +77 -53
- package/dist/index.js +654 -199
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +971 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +29 -20
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
27
|
+
|
|
28
|
+
// src/cli.ts
|
|
29
|
+
var import_knex = __toESM(require("knex"));
|
|
30
|
+
var import_path2 = require("path");
|
|
31
|
+
var import_url = require("url");
|
|
32
|
+
var import_fs2 = require("fs");
|
|
33
|
+
|
|
34
|
+
// src/migrations/ibmi-migration-runner.ts
|
|
35
|
+
var import_fs = __toESM(require("fs"));
|
|
36
|
+
var import_path = __toESM(require("path"));
|
|
37
|
+
var IBMiMigrationRunner = class {
|
|
38
|
+
constructor(knex2, config) {
|
|
39
|
+
__publicField(this, "knex");
|
|
40
|
+
__publicField(this, "config");
|
|
41
|
+
this.knex = knex2;
|
|
42
|
+
this.config = {
|
|
43
|
+
directory: "./migrations",
|
|
44
|
+
tableName: "KNEX_MIGRATIONS",
|
|
45
|
+
schemaName: void 0,
|
|
46
|
+
extension: "js",
|
|
47
|
+
...config
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
getFullTableName() {
|
|
51
|
+
return this.config.schemaName ? `${this.config.schemaName}.${this.config.tableName}` : this.config.tableName;
|
|
52
|
+
}
|
|
53
|
+
async latest() {
|
|
54
|
+
try {
|
|
55
|
+
console.log(
|
|
56
|
+
"\u{1F680} IBM i DB2 Migration Runner - bypassing Knex locking system"
|
|
57
|
+
);
|
|
58
|
+
const tableName = this.getFullTableName();
|
|
59
|
+
const migrationTableExists = await this.knex.schema.hasTable(
|
|
60
|
+
tableName
|
|
61
|
+
);
|
|
62
|
+
if (!migrationTableExists) {
|
|
63
|
+
console.log(`\u{1F4DD} Creating migration table: ${tableName}`);
|
|
64
|
+
await this.knex.schema.createTable(tableName, (table) => {
|
|
65
|
+
table.increments("id").primary();
|
|
66
|
+
table.string("name");
|
|
67
|
+
table.integer("batch");
|
|
68
|
+
table.timestamp("migration_time");
|
|
69
|
+
});
|
|
70
|
+
console.log("\u2705 Migration table created");
|
|
71
|
+
}
|
|
72
|
+
const completed = await this.knex(tableName).select("NAME").orderBy("ID");
|
|
73
|
+
const completedNames = completed.map((c) => c.NAME);
|
|
74
|
+
console.log(`\u{1F4CB} Found ${completedNames.length} completed migrations`);
|
|
75
|
+
const migrationFiles = this.getMigrationFiles();
|
|
76
|
+
console.log(`\u{1F4C1} Found ${migrationFiles.length} migration files`);
|
|
77
|
+
const newMigrations = migrationFiles.filter(
|
|
78
|
+
(file) => !completedNames.includes(file)
|
|
79
|
+
);
|
|
80
|
+
if (newMigrations.length === 0) {
|
|
81
|
+
console.log("\u2705 No new migrations to run");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(`\u{1F3AF} Running ${newMigrations.length} new migrations:`);
|
|
85
|
+
newMigrations.forEach((file) => console.log(` - ${file}`));
|
|
86
|
+
const batchResult = await this.knex(tableName).max("BATCH as max_batch");
|
|
87
|
+
const nextBatch = (batchResult[0]?.max_batch || 0) + 1;
|
|
88
|
+
console.log(`\u{1F4CA} Using batch number: ${nextBatch}`);
|
|
89
|
+
for (const migrationFile of newMigrations) {
|
|
90
|
+
console.log(`
|
|
91
|
+
\u{1F504} Running migration: ${migrationFile}`);
|
|
92
|
+
try {
|
|
93
|
+
const migrationPath = this.getMigrationPath(migrationFile);
|
|
94
|
+
const migration = await import(migrationPath);
|
|
95
|
+
if (!migration.up || typeof migration.up !== "function") {
|
|
96
|
+
throw new Error(`Migration ${migrationFile} has no 'up' function`);
|
|
97
|
+
}
|
|
98
|
+
console.log(` \u26A1 Executing migration...`);
|
|
99
|
+
await migration.up(this.knex);
|
|
100
|
+
await this.knex(tableName).insert({
|
|
101
|
+
name: migrationFile,
|
|
102
|
+
batch: nextBatch,
|
|
103
|
+
migration_time: /* @__PURE__ */ new Date()
|
|
104
|
+
});
|
|
105
|
+
console.log(` \u2705 Migration ${migrationFile} completed successfully`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(
|
|
108
|
+
` \u274C Migration ${migrationFile} failed:`,
|
|
109
|
+
error.message
|
|
110
|
+
);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log(`
|
|
115
|
+
\u{1F389} All migrations completed successfully!`);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("\u274C Migration runner failed:", error.message);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async rollback(steps = 1) {
|
|
122
|
+
try {
|
|
123
|
+
console.log(`\u{1F504} Rolling back ${steps} migration batch(es)...`);
|
|
124
|
+
const tableName = this.getFullTableName();
|
|
125
|
+
const batchesToRollback = await this.knex(tableName).distinct("BATCH").orderBy("BATCH", "desc").limit(steps);
|
|
126
|
+
if (batchesToRollback.length === 0) {
|
|
127
|
+
console.log("\u2705 No migrations to rollback");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const batchNumbers = batchesToRollback.map((b) => b.BATCH);
|
|
131
|
+
console.log(`\u{1F4CA} Rolling back batches: ${batchNumbers.join(", ")}`);
|
|
132
|
+
const migrationsToRollback = await this.knex(tableName).select("NAME").whereIn("BATCH", batchNumbers).orderBy("ID", "desc");
|
|
133
|
+
console.log(`\u{1F3AF} Rolling back ${migrationsToRollback.length} migrations:`);
|
|
134
|
+
migrationsToRollback.forEach((m) => console.log(` - ${m.NAME}`));
|
|
135
|
+
for (const migrationRecord of migrationsToRollback) {
|
|
136
|
+
const migrationFile = migrationRecord.NAME;
|
|
137
|
+
console.log(`
|
|
138
|
+
\u{1F504} Rolling back migration: ${migrationFile}`);
|
|
139
|
+
try {
|
|
140
|
+
const migrationPath = this.getMigrationPath(migrationFile);
|
|
141
|
+
const migration = await import(migrationPath);
|
|
142
|
+
if (migration.down && typeof migration.down === "function") {
|
|
143
|
+
console.log(` \u26A1 Executing rollback...`);
|
|
144
|
+
await migration.down(this.knex);
|
|
145
|
+
} else {
|
|
146
|
+
console.log(
|
|
147
|
+
` \u26A0\uFE0F Migration ${migrationFile} has no 'down' function, skipping rollback`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
await this.knex(tableName).where("NAME", migrationFile).del();
|
|
151
|
+
console.log(
|
|
152
|
+
` \u2705 Migration ${migrationFile} rolled back successfully`
|
|
153
|
+
);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(
|
|
156
|
+
` \u274C Migration ${migrationFile} rollback failed:`,
|
|
157
|
+
error.message
|
|
158
|
+
);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
console.log(`
|
|
163
|
+
\u{1F389} Rollback completed successfully!`);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error("\u274C Rollback failed:", error.message);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async currentVersion() {
|
|
170
|
+
try {
|
|
171
|
+
const tableName = this.getFullTableName();
|
|
172
|
+
const migrationTableExists = await this.knex.schema.hasTable(
|
|
173
|
+
tableName
|
|
174
|
+
);
|
|
175
|
+
if (!migrationTableExists) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const result = await this.knex(tableName).select("NAME").orderBy("ID", "desc").first();
|
|
179
|
+
return result?.NAME || null;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error("\u274C Error getting current version:", error.message);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async listExecuted() {
|
|
186
|
+
try {
|
|
187
|
+
const tableName = this.getFullTableName();
|
|
188
|
+
const migrationTableExists = await this.knex.schema.hasTable(
|
|
189
|
+
tableName
|
|
190
|
+
);
|
|
191
|
+
if (!migrationTableExists) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
const completed = await this.knex(tableName).select("NAME").orderBy("ID");
|
|
195
|
+
return completed.map((c) => c.NAME);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error("\u274C Error listing executed migrations:", error.message);
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async listPending() {
|
|
202
|
+
try {
|
|
203
|
+
const allFiles = this.getMigrationFiles();
|
|
204
|
+
const executed = await this.listExecuted();
|
|
205
|
+
return allFiles.filter((file) => !executed.includes(file));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error("\u274C Error listing pending migrations:", error.message);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
getMigrationFiles() {
|
|
212
|
+
const { directory, extension } = this.config;
|
|
213
|
+
if (!import_fs.default.existsSync(directory)) {
|
|
214
|
+
throw new Error(`Migration directory does not exist: ${directory}`);
|
|
215
|
+
}
|
|
216
|
+
const validExtensions = ["js", "ts", "mjs", "cjs"];
|
|
217
|
+
const extensionToCheck = extension || "js";
|
|
218
|
+
return import_fs.default.readdirSync(directory).filter((file) => {
|
|
219
|
+
if (extension && extension !== "js") {
|
|
220
|
+
return file.endsWith(`.${extension}`);
|
|
221
|
+
}
|
|
222
|
+
return validExtensions.some((ext) => file.endsWith(`.${ext}`));
|
|
223
|
+
}).sort();
|
|
224
|
+
}
|
|
225
|
+
getMigrationPath(filename) {
|
|
226
|
+
return import_path.default.resolve(this.config.directory, filename);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function createIBMiMigrationRunner(knex2, config) {
|
|
230
|
+
return new IBMiMigrationRunner(knex2, config);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/cli.ts
|
|
234
|
+
function showHelp() {
|
|
235
|
+
console.log("IBM i DB2 Migration CLI");
|
|
236
|
+
console.log("=======================");
|
|
237
|
+
console.log("");
|
|
238
|
+
console.log("Commands:");
|
|
239
|
+
console.log(" migrate:latest - Run all pending migrations");
|
|
240
|
+
console.log(" migrate:rollback - Rollback the last migration batch");
|
|
241
|
+
console.log(" migrate:status - Show migration status");
|
|
242
|
+
console.log(" migrate:currentVersion - Show current migration version");
|
|
243
|
+
console.log(" migrate:list - List all migrations");
|
|
244
|
+
console.log(" migrate:make <name> - Create a new migration file");
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log("Legacy aliases:");
|
|
247
|
+
console.log(" latest - Same as migrate:latest");
|
|
248
|
+
console.log(" rollback - Same as migrate:rollback");
|
|
249
|
+
console.log(" status - Same as migrate:status");
|
|
250
|
+
console.log("");
|
|
251
|
+
console.log("Options:");
|
|
252
|
+
console.log(
|
|
253
|
+
" --env <environment> - Specify environment (default: development)"
|
|
254
|
+
);
|
|
255
|
+
console.log(
|
|
256
|
+
" --knexfile <file> - Specify knexfile path (default: ./knexfile.js)"
|
|
257
|
+
);
|
|
258
|
+
console.log(
|
|
259
|
+
" -x <extension> - File extension for new migrations (js|ts)"
|
|
260
|
+
);
|
|
261
|
+
console.log(" --help - Show this help message");
|
|
262
|
+
console.log("");
|
|
263
|
+
console.log("Examples:");
|
|
264
|
+
console.log(" ibmi-migrations migrate:latest");
|
|
265
|
+
console.log(" ibmi-migrations migrate:rollback");
|
|
266
|
+
console.log(" ibmi-migrations migrate:status --env production");
|
|
267
|
+
console.log(" ibmi-migrations migrate:make create_users_table");
|
|
268
|
+
console.log(" ibmi-migrations migrate:make add_email_to_users -x ts");
|
|
269
|
+
console.log(" ibmi-migrations latest --knexfile ./config/knexfile.js");
|
|
270
|
+
}
|
|
271
|
+
function parseArgs() {
|
|
272
|
+
const args = process.argv.slice(2);
|
|
273
|
+
const parsed = {
|
|
274
|
+
command: null,
|
|
275
|
+
env: process.env.NODE_ENV || "development",
|
|
276
|
+
knexfile: "./knexfile.js",
|
|
277
|
+
help: false,
|
|
278
|
+
extension: "js",
|
|
279
|
+
migrationName: null
|
|
280
|
+
};
|
|
281
|
+
for (let i = 0; i < args.length; i++) {
|
|
282
|
+
const arg = args[i];
|
|
283
|
+
if (arg === "--help" || arg === "-h") {
|
|
284
|
+
parsed.help = true;
|
|
285
|
+
} else if (arg === "--env" && args[i + 1]) {
|
|
286
|
+
parsed.env = args[i + 1];
|
|
287
|
+
i++;
|
|
288
|
+
} else if (arg === "--knexfile" && args[i + 1]) {
|
|
289
|
+
parsed.knexfile = args[i + 1];
|
|
290
|
+
i++;
|
|
291
|
+
} else if (arg === "-x" && args[i + 1]) {
|
|
292
|
+
parsed.extension = args[i + 1];
|
|
293
|
+
i++;
|
|
294
|
+
} else if (!parsed.command) {
|
|
295
|
+
parsed.command = arg;
|
|
296
|
+
} else if (parsed.command === "migrate:make" && !parsed.migrationName) {
|
|
297
|
+
parsed.migrationName = arg;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return parsed;
|
|
301
|
+
}
|
|
302
|
+
function formatDate() {
|
|
303
|
+
const now = /* @__PURE__ */ new Date();
|
|
304
|
+
const year = now.getFullYear();
|
|
305
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
306
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
307
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
308
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
309
|
+
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
310
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
311
|
+
}
|
|
312
|
+
function getJsMigrationTemplate(migrationName) {
|
|
313
|
+
return `/**
|
|
314
|
+
* @param { import("knex").Knex } knex
|
|
315
|
+
* @returns { Promise<void> }
|
|
316
|
+
*/
|
|
317
|
+
export const up = (knex) => {
|
|
318
|
+
// Add your migration logic here
|
|
319
|
+
// Example: return knex.schema.createTable("table_name", (table) => {
|
|
320
|
+
// table.increments("id").primary();
|
|
321
|
+
// table.string("name").notNullable();
|
|
322
|
+
// table.timestamps(true, true);
|
|
323
|
+
// });
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @param { import("knex").Knex } knex
|
|
328
|
+
* @returns { Promise<void> }
|
|
329
|
+
*/
|
|
330
|
+
export const down = (knex) => {
|
|
331
|
+
// Add your rollback logic here
|
|
332
|
+
// Example: return knex.schema.dropTable("table_name");
|
|
333
|
+
};
|
|
334
|
+
`;
|
|
335
|
+
}
|
|
336
|
+
function getTsMigrationTemplate(migrationName) {
|
|
337
|
+
return `import { Knex } from "knex";
|
|
338
|
+
|
|
339
|
+
export const up = async (knex: Knex): Promise<void> => {
|
|
340
|
+
// Add your migration logic here
|
|
341
|
+
// Example: return knex.schema.createTable("table_name", (table) => {
|
|
342
|
+
// table.increments("id").primary();
|
|
343
|
+
// table.string("name").notNullable();
|
|
344
|
+
// table.timestamps(true, true);
|
|
345
|
+
// });
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export const down = async (knex: Knex): Promise<void> => {
|
|
349
|
+
// Add your rollback logic here
|
|
350
|
+
// Example: return knex.schema.dropTable("table_name");
|
|
351
|
+
};
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
function createMigrationFile(migrationName, directory, extension) {
|
|
355
|
+
if (!(0, import_fs2.existsSync)(directory)) {
|
|
356
|
+
(0, import_fs2.mkdirSync)(directory, { recursive: true });
|
|
357
|
+
}
|
|
358
|
+
const timestamp = formatDate();
|
|
359
|
+
const fileName = `${timestamp}_${migrationName}.${extension}`;
|
|
360
|
+
const filePath = (0, import_path2.join)(directory, fileName);
|
|
361
|
+
const template = extension === "ts" ? getTsMigrationTemplate(migrationName) : getJsMigrationTemplate(migrationName);
|
|
362
|
+
(0, import_fs2.writeFileSync)(filePath, template);
|
|
363
|
+
return filePath;
|
|
364
|
+
}
|
|
365
|
+
async function loadKnexfile(knexfilePath, environment) {
|
|
366
|
+
try {
|
|
367
|
+
const fullPath = (0, import_path2.resolve)(process.cwd(), knexfilePath);
|
|
368
|
+
const fileUrl = (0, import_url.pathToFileURL)(fullPath).href;
|
|
369
|
+
const knexfile = await import(`${fileUrl}?t=${Date.now()}`);
|
|
370
|
+
const config = knexfile.default || knexfile;
|
|
371
|
+
if (!config || typeof config !== "object") {
|
|
372
|
+
throw new Error("Invalid knexfile format");
|
|
373
|
+
}
|
|
374
|
+
const envConfig = typeof config === "function" ? config() : config[environment];
|
|
375
|
+
if (!envConfig) {
|
|
376
|
+
throw new Error(`No configuration found for environment: ${environment}`);
|
|
377
|
+
}
|
|
378
|
+
return envConfig;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
381
|
+
console.error("\u274C Failed to load knexfile:", message);
|
|
382
|
+
console.error(`Make sure you have a valid knexfile at: ${knexfilePath}`);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function main() {
|
|
387
|
+
const args = parseArgs();
|
|
388
|
+
if (args.help) {
|
|
389
|
+
showHelp();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!args.command) {
|
|
393
|
+
console.error("\u274C No command specified");
|
|
394
|
+
showHelp();
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
let command = args.command;
|
|
398
|
+
const legacyCommands = {
|
|
399
|
+
latest: "migrate:latest",
|
|
400
|
+
rollback: "migrate:rollback",
|
|
401
|
+
status: "migrate:status"
|
|
402
|
+
};
|
|
403
|
+
if (legacyCommands[command]) {
|
|
404
|
+
command = legacyCommands[command];
|
|
405
|
+
}
|
|
406
|
+
const validCommands = [
|
|
407
|
+
"migrate:latest",
|
|
408
|
+
"migrate:rollback",
|
|
409
|
+
"migrate:status",
|
|
410
|
+
"migrate:currentVersion",
|
|
411
|
+
"migrate:list",
|
|
412
|
+
"migrate:make"
|
|
413
|
+
];
|
|
414
|
+
if (!validCommands.includes(command)) {
|
|
415
|
+
console.error(`\u274C Unknown command: ${args.command}`);
|
|
416
|
+
showHelp();
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
if (command === "migrate:make") {
|
|
420
|
+
if (!args.migrationName) {
|
|
421
|
+
console.error("\u274C Migration name is required for migrate:make command");
|
|
422
|
+
console.error("Usage: ibmi-migrations migrate:make <migration_name>");
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
if (!["js", "ts"].includes(args.extension)) {
|
|
426
|
+
console.error("\u274C Invalid extension. Use 'js' or 'ts'");
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const config2 = await loadKnexfile(args.knexfile, args.env);
|
|
431
|
+
const migrationDir = config2.migrations?.directory || "./migrations";
|
|
432
|
+
const filePath = createMigrationFile(
|
|
433
|
+
args.migrationName,
|
|
434
|
+
migrationDir,
|
|
435
|
+
args.extension
|
|
436
|
+
);
|
|
437
|
+
console.log(`\u2705 Created migration file: ${filePath}`);
|
|
438
|
+
return;
|
|
439
|
+
} catch (error) {
|
|
440
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
441
|
+
console.error("\u274C Failed to create migration:", message);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const config = await loadKnexfile(args.knexfile, args.env);
|
|
446
|
+
const db = (0, import_knex.default)(config);
|
|
447
|
+
try {
|
|
448
|
+
const migrationConfig = {
|
|
449
|
+
directory: config.migrations?.directory || "./migrations",
|
|
450
|
+
tableName: config.migrations?.tableName || "KNEX_MIGRATIONS",
|
|
451
|
+
schemaName: config.migrations?.schemaName,
|
|
452
|
+
extension: config.migrations?.extension || "js"
|
|
453
|
+
};
|
|
454
|
+
const migrationRunner = createIBMiMigrationRunner(db, migrationConfig);
|
|
455
|
+
switch (command) {
|
|
456
|
+
case "migrate:latest":
|
|
457
|
+
console.log("\u{1F680} Running pending migrations...");
|
|
458
|
+
await migrationRunner.latest();
|
|
459
|
+
break;
|
|
460
|
+
case "migrate:rollback":
|
|
461
|
+
const steps = parseInt(
|
|
462
|
+
process.argv.find((arg, i) => process.argv[i - 1] === command)?.split(" ")[1] || "1"
|
|
463
|
+
) || 1;
|
|
464
|
+
console.log(`\u{1F504} Rolling back ${steps} migration batch(es)...`);
|
|
465
|
+
await migrationRunner.rollback(steps);
|
|
466
|
+
break;
|
|
467
|
+
case "migrate:status":
|
|
468
|
+
console.log("\u{1F4CB} Migration Status Report");
|
|
469
|
+
console.log("========================");
|
|
470
|
+
const currentVersion = await migrationRunner.currentVersion();
|
|
471
|
+
console.log("\u{1F4CC} Current version:", currentVersion || "None");
|
|
472
|
+
const executed = await migrationRunner.listExecuted();
|
|
473
|
+
console.log(`\u2705 Executed migrations (${executed.length}):`);
|
|
474
|
+
if (executed.length > 0) {
|
|
475
|
+
executed.forEach((migration, index) => {
|
|
476
|
+
console.log(` ${index + 1}. ${migration}`);
|
|
477
|
+
});
|
|
478
|
+
} else {
|
|
479
|
+
console.log(" (none)");
|
|
480
|
+
}
|
|
481
|
+
const pending = await migrationRunner.listPending();
|
|
482
|
+
console.log(`\u23F3 Pending migrations (${pending.length}):`);
|
|
483
|
+
if (pending.length > 0) {
|
|
484
|
+
pending.forEach((migration, index) => {
|
|
485
|
+
console.log(` ${index + 1}. ${migration}`);
|
|
486
|
+
});
|
|
487
|
+
} else {
|
|
488
|
+
console.log(" (none)");
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
case "migrate:currentVersion":
|
|
492
|
+
const version = await migrationRunner.currentVersion();
|
|
493
|
+
console.log("\u{1F4CC} Current migration version:", version || "None");
|
|
494
|
+
break;
|
|
495
|
+
case "migrate:list":
|
|
496
|
+
console.log("\u{1F4CB} All Migrations");
|
|
497
|
+
console.log("=================");
|
|
498
|
+
const allExecuted = await migrationRunner.listExecuted();
|
|
499
|
+
const allPending = await migrationRunner.listPending();
|
|
500
|
+
if (allExecuted.length > 0) {
|
|
501
|
+
console.log("\u2705 Executed:");
|
|
502
|
+
allExecuted.forEach((migration, index) => {
|
|
503
|
+
console.log(` ${index + 1}. ${migration}`);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (allPending.length > 0) {
|
|
507
|
+
console.log("\u23F3 Pending:");
|
|
508
|
+
allPending.forEach((migration, index) => {
|
|
509
|
+
console.log(` ${index + 1}. ${migration}`);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (allExecuted.length === 0 && allPending.length === 0) {
|
|
513
|
+
console.log(" No migrations found");
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
} catch (error) {
|
|
518
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
519
|
+
console.error(`\u274C Command '${command}' failed:`, message);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
} finally {
|
|
522
|
+
await db.destroy();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
main().catch((error) => {
|
|
526
|
+
console.error(
|
|
527
|
+
"\u274C CLI failed:",
|
|
528
|
+
error instanceof Error ? error.message : String(error)
|
|
529
|
+
);
|
|
530
|
+
process.exit(1);
|
|
531
|
+
});
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import knex, { Knex } from 'knex';
|
|
2
|
+
import odbc, { Connection } from 'odbc';
|
|
3
|
+
|
|
4
|
+
interface IBMiMigrationConfig {
|
|
5
|
+
directory: string;
|
|
6
|
+
tableName: string;
|
|
7
|
+
schemaName?: string;
|
|
8
|
+
extension?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class IBMiMigrationRunner {
|
|
11
|
+
private knex;
|
|
12
|
+
private config;
|
|
13
|
+
constructor(knex: Knex, config?: Partial<IBMiMigrationConfig>);
|
|
14
|
+
private getFullTableName;
|
|
15
|
+
latest(): Promise<void>;
|
|
16
|
+
rollback(steps?: number): Promise<void>;
|
|
17
|
+
currentVersion(): Promise<string | null>;
|
|
18
|
+
listExecuted(): Promise<string[]>;
|
|
19
|
+
listPending(): Promise<string[]>;
|
|
20
|
+
private getMigrationFiles;
|
|
21
|
+
private getMigrationPath;
|
|
22
|
+
}
|
|
23
|
+
declare function createIBMiMigrationRunner(knex: Knex, config?: Partial<IBMiMigrationConfig>): IBMiMigrationRunner;
|
|
24
|
+
|
|
25
|
+
interface QueryObject {
|
|
26
|
+
response?: {
|
|
27
|
+
rows: any[];
|
|
28
|
+
rowCount: number;
|
|
29
|
+
};
|
|
30
|
+
sqlMethod: SqlMethod;
|
|
31
|
+
output?: (runner: any, response: any) => any;
|
|
32
|
+
pluck?: (row: any) => any;
|
|
33
|
+
select?: boolean;
|
|
34
|
+
}
|
|
35
|
+
declare enum SqlMethod {
|
|
36
|
+
SELECT = "select",
|
|
37
|
+
PLUCK = "pluck",
|
|
38
|
+
FIRST = "first",
|
|
39
|
+
INSERT = "insert",
|
|
40
|
+
DELETE = "del",
|
|
41
|
+
DELETE_ALT = "delete",
|
|
42
|
+
UPDATE = "update",
|
|
43
|
+
COUNTER = "counter"
|
|
44
|
+
}
|
|
45
|
+
declare class DB2Client extends knex.Client {
|
|
46
|
+
constructor(config: Knex.Config<DB2Config>);
|
|
47
|
+
private safeStringify;
|
|
48
|
+
_driver(): typeof odbc;
|
|
49
|
+
wrapIdentifierImpl(value: string): string;
|
|
50
|
+
printDebug(message: string): void;
|
|
51
|
+
printError(message: string): void;
|
|
52
|
+
printWarn(message: string): void;
|
|
53
|
+
acquireRawConnection(): Promise<void | odbc.Connection>;
|
|
54
|
+
destroyRawConnection(connection: any): Promise<any>;
|
|
55
|
+
_getConnectionString(connectionConfig: DB2ConnectionConfig): string;
|
|
56
|
+
_query(connection: Connection, obj: any): Promise<any>;
|
|
57
|
+
private normalizeQueryObject;
|
|
58
|
+
private determineQueryMethod;
|
|
59
|
+
private isSelectMethod;
|
|
60
|
+
private executeSelectQuery;
|
|
61
|
+
private executeStatementQuery;
|
|
62
|
+
private isNoDataError;
|
|
63
|
+
/**
|
|
64
|
+
* Format statement response from ODBC driver
|
|
65
|
+
* Handles special case for IDENTITY_VAL_LOCAL() function
|
|
66
|
+
*/
|
|
67
|
+
private formatStatementResponse;
|
|
68
|
+
_stream(connection: Connection, obj: {
|
|
69
|
+
sql: string;
|
|
70
|
+
bindings: any[];
|
|
71
|
+
}, stream: any, options: {
|
|
72
|
+
fetchSize?: number;
|
|
73
|
+
}): Promise<unknown>;
|
|
74
|
+
private _createCursorStream;
|
|
75
|
+
transaction(container: any, config: any, outerTx: any): Knex.Transaction;
|
|
76
|
+
schemaCompiler(tableBuilder: any): any;
|
|
77
|
+
tableCompiler(tableBuilder: any): any;
|
|
78
|
+
columnCompiler(tableCompiler: any, columnCompiler: any): any;
|
|
79
|
+
queryCompiler(builder: Knex.QueryBuilder, bindings?: any[]): any;
|
|
80
|
+
createMigrationRunner(config?: Partial<IBMiMigrationConfig>): IBMiMigrationRunner;
|
|
81
|
+
processResponse(obj: QueryObject | null, runner: any): any;
|
|
82
|
+
private validateResponse;
|
|
83
|
+
private isConnectionError;
|
|
84
|
+
private processSqlMethod;
|
|
85
|
+
}
|
|
86
|
+
interface DB2PoolConfig {
|
|
87
|
+
min?: number;
|
|
88
|
+
max?: number;
|
|
89
|
+
acquireConnectionTimeout?: number;
|
|
90
|
+
}
|
|
91
|
+
interface DB2ConnectionParams {
|
|
92
|
+
CMT?: number;
|
|
93
|
+
CONNTYPE?: number;
|
|
94
|
+
DBQ?: string;
|
|
95
|
+
MAXDECPREC?: 31 | 63;
|
|
96
|
+
MAXDECSCALE?: number;
|
|
97
|
+
MINDIVSCALE?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
98
|
+
NAM?: 0 | 1;
|
|
99
|
+
DFT?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
100
|
+
DSP?: 0 | 1 | 2 | 3 | 4;
|
|
101
|
+
DEC?: 0 | 1;
|
|
102
|
+
DECFLOATERROROPTION?: 0 | 1;
|
|
103
|
+
DECFLOATROUNDMODE?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
104
|
+
MAPDECIMALFLOATDESCRIBE?: 1 | 3;
|
|
105
|
+
TFT?: 0 | 1 | 2 | 3 | 4;
|
|
106
|
+
TSP?: 0 | 1 | 2 | 3;
|
|
107
|
+
TSFT?: 0 | 1;
|
|
108
|
+
XMLCURIMPPARSE?: 0 | 1;
|
|
109
|
+
XMLDECLARATION?: 1 | 2 | 3 | 4;
|
|
110
|
+
ALLOWPROCCALLS?: 0 | 1;
|
|
111
|
+
XDYNAMIC?: 0 | 1;
|
|
112
|
+
DFTPKGLIB?: string;
|
|
113
|
+
PKG?: 0 | 1 | 2;
|
|
114
|
+
BLOCKFETCH?: 0 | 1;
|
|
115
|
+
COMPRESSION?: 0 | 1;
|
|
116
|
+
CONCURRENCY?: 0 | 1;
|
|
117
|
+
CURSORSENSITIVITY?: 0 | 1 | 2;
|
|
118
|
+
EXTCOLINFO?: "SQL_DESC_AUTO_UNIQUE_VALUE" | "SQL_DESC_BASE_COLUMN_NAME" | "SQL_DESC_BASE_TABLE_NAME and SQL_DESC_TABLE_NAME" | "SQL_DESC_LABEL" | "SQL_DESC_SCHEMA_NAME" | "SQL_DESC_SEARCHABLE" | "SQL_DESC_UNNAMED" | "SQL_DESC_UPDATABLE";
|
|
119
|
+
TRUEAUTOCOMMIT?: 0 | 1;
|
|
120
|
+
}
|
|
121
|
+
interface DB2ConnectionConfig {
|
|
122
|
+
database: string;
|
|
123
|
+
host: string;
|
|
124
|
+
port: 8471 | 9471 | number;
|
|
125
|
+
user: string;
|
|
126
|
+
password: string;
|
|
127
|
+
driver: "IBM i Access ODBC Driver" | string;
|
|
128
|
+
connectionStringParams?: DB2ConnectionParams;
|
|
129
|
+
}
|
|
130
|
+
interface DB2Config extends Knex.Config {
|
|
131
|
+
client: any;
|
|
132
|
+
connection: DB2ConnectionConfig;
|
|
133
|
+
pool?: DB2PoolConfig;
|
|
134
|
+
}
|
|
135
|
+
declare const DB2Dialect: typeof DB2Client;
|
|
136
|
+
|
|
137
|
+
export { type DB2Config, DB2Dialect, IBMiMigrationRunner, createIBMiMigrationRunner, DB2Client as default };
|