@noego/proper 0.1.0 → 0.2.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/bin/cli.js +947 -73
- package/bin/cli.js.map +1 -1
- package/bin/cli.mjs +989 -73
- package/bin/cli.mjs.map +1 -1
- package/bin/index.d.mts +181 -3
- package/bin/index.d.ts +181 -3
- package/bin/index.js +925 -62
- package/bin/index.js.map +1 -1
- package/bin/index.mjs +915 -62
- package/bin/index.mjs.map +1 -1
- package/package.json +3 -2
- package/readme.md +92 -0
package/bin/index.js
CHANGED
|
@@ -66,23 +66,32 @@ var __async = (__this, __arguments, generator) => {
|
|
|
66
66
|
// index.ts
|
|
67
67
|
var index_exports = {};
|
|
68
68
|
__export(index_exports, {
|
|
69
|
+
MigrationError: () => MigrationError,
|
|
69
70
|
MigrationRunner: () => MySQLMigrationRunner,
|
|
70
71
|
MigrationRunnerFactory: () => MigrationRunnerFactory,
|
|
72
|
+
PatchConflictError: () => PatchConflictError,
|
|
73
|
+
PatchCreator: () => PatchCreator,
|
|
74
|
+
PatchError: () => PatchError,
|
|
75
|
+
PatchExecutionError: () => PatchExecutionError,
|
|
76
|
+
PatchIntegrityError: () => PatchIntegrityError,
|
|
77
|
+
PatchRunner: () => PatchRunner,
|
|
78
|
+
PatchValidationError: () => PatchValidationError,
|
|
71
79
|
PgRunner: () => PgRunner,
|
|
72
80
|
SQLRunner: () => SQLRunner,
|
|
73
81
|
SQLiteRunner: () => SQLiteRunner,
|
|
74
82
|
createSeedFactory: () => createSeedFactory,
|
|
75
83
|
loadMigrationConfig: () => loadMigrationConfig,
|
|
84
|
+
resolvePatchFolder: () => resolvePatchFolder,
|
|
85
|
+
resolvePatchTable: () => resolvePatchTable,
|
|
76
86
|
runSeedsWithRunner: () => runSeedsWithRunner
|
|
77
87
|
});
|
|
78
88
|
module.exports = __toCommonJS(index_exports);
|
|
79
89
|
|
|
80
90
|
// framework/MigrationRunner.ts
|
|
81
|
-
var
|
|
91
|
+
var import_fs6 = __toESM(require("fs"));
|
|
82
92
|
|
|
83
93
|
// framework/MigrationDirectoryReader.ts
|
|
84
|
-
var
|
|
85
|
-
var import_path = __toESM(require("path"));
|
|
94
|
+
var import_fs2 = __toESM(require("fs"));
|
|
86
95
|
|
|
87
96
|
// framework/errors.ts
|
|
88
97
|
var MigrationError = class _MigrationError extends Error {
|
|
@@ -197,6 +206,54 @@ ${originalError.message}`;
|
|
|
197
206
|
return new _MigrationExecutionError(message);
|
|
198
207
|
}
|
|
199
208
|
};
|
|
209
|
+
var PatchError = class _PatchError extends MigrationError {
|
|
210
|
+
constructor(message, patchFile, patchKey) {
|
|
211
|
+
super(`Patch Error: ${message}`);
|
|
212
|
+
this.patchFile = patchFile;
|
|
213
|
+
this.patchKey = patchKey;
|
|
214
|
+
this.name = "PatchError";
|
|
215
|
+
Object.setPrototypeOf(this, _PatchError.prototype);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var PatchValidationError = class _PatchValidationError extends PatchError {
|
|
219
|
+
constructor(message, patchFile, patchKey) {
|
|
220
|
+
super(message, patchFile, patchKey);
|
|
221
|
+
this.name = "PatchValidationError";
|
|
222
|
+
Object.setPrototypeOf(this, _PatchValidationError.prototype);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var PatchIntegrityError = class _PatchIntegrityError extends PatchError {
|
|
226
|
+
constructor(message, patchFile, patchKey, expectedChecksum, actualChecksum) {
|
|
227
|
+
super(message, patchFile, patchKey);
|
|
228
|
+
this.expectedChecksum = expectedChecksum;
|
|
229
|
+
this.actualChecksum = actualChecksum;
|
|
230
|
+
this.name = "PatchIntegrityError";
|
|
231
|
+
Object.setPrototypeOf(this, _PatchIntegrityError.prototype);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var PatchConflictError = class _PatchConflictError extends PatchError {
|
|
235
|
+
constructor(message, patchFile, patchKey, operationIndex, operationVerb, migrationKeys, observedRowCounts) {
|
|
236
|
+
super(message, patchFile, patchKey);
|
|
237
|
+
this.operationIndex = operationIndex;
|
|
238
|
+
this.operationVerb = operationVerb;
|
|
239
|
+
this.migrationKeys = migrationKeys;
|
|
240
|
+
this.observedRowCounts = observedRowCounts;
|
|
241
|
+
this.name = "PatchConflictError";
|
|
242
|
+
Object.setPrototypeOf(this, _PatchConflictError.prototype);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
var PatchExecutionError = class _PatchExecutionError extends PatchError {
|
|
246
|
+
constructor(message, patchFile, patchKey, operationIndex, operationVerb, originalError) {
|
|
247
|
+
super(originalError ? `${message}
|
|
248
|
+
Original Error:
|
|
249
|
+
${originalError.message}` : message, patchFile, patchKey);
|
|
250
|
+
this.operationIndex = operationIndex;
|
|
251
|
+
this.operationVerb = operationVerb;
|
|
252
|
+
this.originalError = originalError;
|
|
253
|
+
this.name = "PatchExecutionError";
|
|
254
|
+
Object.setPrototypeOf(this, _PatchExecutionError.prototype);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
200
257
|
var CLIError = class _CLIError extends MigrationError {
|
|
201
258
|
constructor(message) {
|
|
202
259
|
super(`CLI Error: ${message}`);
|
|
@@ -359,6 +416,36 @@ var SqlMigrationBuilder = class {
|
|
|
359
416
|
}
|
|
360
417
|
};
|
|
361
418
|
|
|
419
|
+
// framework/MigrationManifest.ts
|
|
420
|
+
var import_fs = __toESM(require("fs"));
|
|
421
|
+
var import_path = __toESM(require("path"));
|
|
422
|
+
function canonicalMigrationKey(value) {
|
|
423
|
+
return value.replace(/(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i, "").toLowerCase();
|
|
424
|
+
}
|
|
425
|
+
function resolveMigrationFile(directory, baseName, direction, dialect) {
|
|
426
|
+
const dialectExt = dialect === "sql" ? "mysql" : dialect;
|
|
427
|
+
const dialectFile = import_path.default.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
|
|
428
|
+
if (import_fs.default.existsSync(dialectFile)) return dialectFile;
|
|
429
|
+
const genericFile = import_path.default.join(directory, `${baseName}.${direction}.sql`);
|
|
430
|
+
if (import_fs.default.existsSync(genericFile)) return genericFile;
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function loadMigrationManifest(directory, dialect) {
|
|
434
|
+
const manifest = /* @__PURE__ */ new Map();
|
|
435
|
+
if (!import_fs.default.existsSync(directory)) return manifest;
|
|
436
|
+
const files = import_fs.default.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name);
|
|
437
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
438
|
+
files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
|
|
439
|
+
uniqueKeys.forEach((key) => {
|
|
440
|
+
manifest.set(key, {
|
|
441
|
+
key,
|
|
442
|
+
upFile: resolveMigrationFile(directory, key, "up", dialect),
|
|
443
|
+
downFile: resolveMigrationFile(directory, key, "down", dialect)
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
return manifest;
|
|
447
|
+
}
|
|
448
|
+
|
|
362
449
|
// framework/MigrationDirectoryReader.ts
|
|
363
450
|
var MigrationDirectoryReader = class {
|
|
364
451
|
constructor(directory, read_strategy, sqlrunner, dialect = "sql") {
|
|
@@ -373,12 +460,7 @@ var MigrationDirectoryReader = class {
|
|
|
373
460
|
* File extensions: `.mysql.up.sql`, `.sqlite.up.sql`, `.pg.up.sql`.
|
|
374
461
|
*/
|
|
375
462
|
resolveFile(baseName, direction) {
|
|
376
|
-
|
|
377
|
-
const dialectFile = import_path.default.join(this.directory, `${baseName}.${dialectExt}.${direction}.sql`);
|
|
378
|
-
if (import_fs.default.existsSync(dialectFile)) return dialectFile;
|
|
379
|
-
const genericFile = import_path.default.join(this.directory, `${baseName}.${direction}.sql`);
|
|
380
|
-
if (import_fs.default.existsSync(genericFile)) return genericFile;
|
|
381
|
-
return null;
|
|
463
|
+
return resolveMigrationFile(this.directory, baseName, direction, this.dialect);
|
|
382
464
|
}
|
|
383
465
|
/**
|
|
384
466
|
* Checks if a file path is dialect-specific (contains .mysql. or .sqlite. in the name)
|
|
@@ -387,11 +469,11 @@ var MigrationDirectoryReader = class {
|
|
|
387
469
|
return /\.(mysql|sqlite|pg)\.(up|down)\.sql$/i.test(filePath);
|
|
388
470
|
}
|
|
389
471
|
loadMigrations(table, connection) {
|
|
390
|
-
|
|
391
|
-
const dir_content =
|
|
472
|
+
import_fs2.default.existsSync(this.directory) || import_fs2.default.mkdirSync(this.directory);
|
|
473
|
+
const dir_content = import_fs2.default.readdirSync(this.directory, { withFileTypes: true }).filter((file) => file.isFile()).map((file) => file.name);
|
|
392
474
|
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
393
475
|
dir_content.forEach((file) => {
|
|
394
|
-
const key = file
|
|
476
|
+
const key = canonicalMigrationKey(file);
|
|
395
477
|
uniqueKeys.add(key);
|
|
396
478
|
});
|
|
397
479
|
const migration_sorter = {};
|
|
@@ -431,14 +513,14 @@ var MigrationDirectoryReader = class {
|
|
|
431
513
|
return builder;
|
|
432
514
|
}
|
|
433
515
|
sql_up(file) {
|
|
434
|
-
let content =
|
|
516
|
+
let content = import_fs2.default.readFileSync(file).toString();
|
|
435
517
|
if (!this.isDialectSpecific(file)) {
|
|
436
518
|
content = this.read_strategy(content);
|
|
437
519
|
}
|
|
438
520
|
return content.trim();
|
|
439
521
|
}
|
|
440
522
|
sql_down(file) {
|
|
441
|
-
let content =
|
|
523
|
+
let content = import_fs2.default.readFileSync(file).toString();
|
|
442
524
|
if (!this.isDialectSpecific(file)) {
|
|
443
525
|
content = this.read_strategy(content);
|
|
444
526
|
}
|
|
@@ -447,7 +529,23 @@ var MigrationDirectoryReader = class {
|
|
|
447
529
|
};
|
|
448
530
|
|
|
449
531
|
// framework/MigrationSetup.ts
|
|
450
|
-
var
|
|
532
|
+
var import_fs3 = __toESM(require("fs"));
|
|
533
|
+
|
|
534
|
+
// framework/PatchTypes.ts
|
|
535
|
+
var import_path2 = __toESM(require("path"));
|
|
536
|
+
var PATCH_FORMAT_VERSION = 1;
|
|
537
|
+
var DEFAULT_PATCH_TABLE = "proper_patches";
|
|
538
|
+
var PATCH_FILENAME_REGEX = new RegExp("^(?<stamp>[0-9]{13})_(?<name>[a-z0-9][a-z0-9_-]{0,119})\\.yaml$");
|
|
539
|
+
function resolvePatchFolder(config) {
|
|
540
|
+
if (config.patch_folder) return config.patch_folder;
|
|
541
|
+
const dir = import_path2.default.dirname(config.migration_folder);
|
|
542
|
+
return dir === "." && !config.migration_folder.includes(import_path2.default.sep) && !config.migration_folder.includes("/") ? "patches" : import_path2.default.join(dir, "patches");
|
|
543
|
+
}
|
|
544
|
+
function resolvePatchTable(config) {
|
|
545
|
+
return config.patch_table || DEFAULT_PATCH_TABLE;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// framework/MigrationSetup.ts
|
|
451
549
|
var MigrationSetup = class {
|
|
452
550
|
constructor(sqlrunner, config) {
|
|
453
551
|
this.sqlrunner = sqlrunner;
|
|
@@ -455,7 +553,7 @@ var MigrationSetup = class {
|
|
|
455
553
|
}
|
|
456
554
|
setup() {
|
|
457
555
|
return __async(this, null, function* () {
|
|
458
|
-
|
|
556
|
+
import_fs3.default.existsSync(this.config.migration_folder) || import_fs3.default.mkdirSync(this.config.migration_folder);
|
|
459
557
|
const tableName = this.config.migration_table;
|
|
460
558
|
const createTableSql = this.config.database === "sqlite" ? `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
461
559
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -477,11 +575,39 @@ var MigrationSetup = class {
|
|
|
477
575
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
478
576
|
)`;
|
|
479
577
|
yield this.sqlrunner.query(createTableSql);
|
|
578
|
+
const patchTable = resolvePatchTable(this.config);
|
|
579
|
+
const createPatchTableSql = this.config.database === "sqlite" ? `CREATE TABLE IF NOT EXISTS ${patchTable} (
|
|
580
|
+
migration_table TEXT NOT NULL,
|
|
581
|
+
patch_key TEXT NOT NULL,
|
|
582
|
+
checksum TEXT NOT NULL,
|
|
583
|
+
format_version INTEGER NOT NULL,
|
|
584
|
+
description TEXT NOT NULL,
|
|
585
|
+
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
586
|
+
PRIMARY KEY (migration_table, patch_key)
|
|
587
|
+
)` : this.config.database === "pg" ? `CREATE TABLE IF NOT EXISTS ${patchTable} (
|
|
588
|
+
migration_table TEXT NOT NULL,
|
|
589
|
+
patch_key TEXT NOT NULL,
|
|
590
|
+
checksum TEXT NOT NULL,
|
|
591
|
+
format_version INTEGER NOT NULL,
|
|
592
|
+
description TEXT NOT NULL,
|
|
593
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
594
|
+
PRIMARY KEY (migration_table, patch_key)
|
|
595
|
+
)` : `CREATE TABLE IF NOT EXISTS ${patchTable} (
|
|
596
|
+
migration_table VARCHAR(255) NOT NULL,
|
|
597
|
+
patch_key VARCHAR(255) NOT NULL,
|
|
598
|
+
checksum CHAR(64) NOT NULL,
|
|
599
|
+
format_version INT NOT NULL,
|
|
600
|
+
description TEXT NOT NULL,
|
|
601
|
+
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
602
|
+
PRIMARY KEY (migration_table, patch_key)
|
|
603
|
+
)`;
|
|
604
|
+
yield this.sqlrunner.query(createPatchTableSql);
|
|
480
605
|
});
|
|
481
606
|
}
|
|
482
607
|
teardown() {
|
|
483
608
|
return __async(this, null, function* () {
|
|
484
609
|
yield this.sqlrunner.execute(`DROP TABLE ${this.config.migration_table}`);
|
|
610
|
+
yield this.sqlrunner.execute(`DROP TABLE IF EXISTS ${resolvePatchTable(this.config)}`);
|
|
485
611
|
});
|
|
486
612
|
}
|
|
487
613
|
};
|
|
@@ -558,6 +684,673 @@ function markerDialectParser(names) {
|
|
|
558
684
|
var SqliteDialectParser = markerDialectParser(["sqlite"]);
|
|
559
685
|
var PgDialectParser = markerDialectParser(["pg", "postgres", "postgresql"]);
|
|
560
686
|
|
|
687
|
+
// framework/PatchDirectoryReader.ts
|
|
688
|
+
var import_crypto = __toESM(require("crypto"));
|
|
689
|
+
var import_fs4 = __toESM(require("fs"));
|
|
690
|
+
var import_path3 = __toESM(require("path"));
|
|
691
|
+
|
|
692
|
+
// framework/PatchValidator.ts
|
|
693
|
+
var import_ajv = __toESM(require("ajv"));
|
|
694
|
+
var import_yaml = require("yaml");
|
|
695
|
+
var MAX_DESCRIPTION_LENGTH = 500;
|
|
696
|
+
var MAX_MIGRATION_KEY_LENGTH = 255;
|
|
697
|
+
var migrationKeySchema = {
|
|
698
|
+
type: "string",
|
|
699
|
+
minLength: 1,
|
|
700
|
+
maxLength: MAX_MIGRATION_KEY_LENGTH
|
|
701
|
+
};
|
|
702
|
+
var patchSchema = {
|
|
703
|
+
type: "object",
|
|
704
|
+
additionalProperties: false,
|
|
705
|
+
required: ["version", "description", "operations"],
|
|
706
|
+
properties: {
|
|
707
|
+
version: { type: "integer" },
|
|
708
|
+
description: { type: "string" },
|
|
709
|
+
operations: {
|
|
710
|
+
type: "array",
|
|
711
|
+
minItems: 1,
|
|
712
|
+
items: {
|
|
713
|
+
type: "object",
|
|
714
|
+
additionalProperties: false,
|
|
715
|
+
minProperties: 1,
|
|
716
|
+
maxProperties: 1,
|
|
717
|
+
properties: {
|
|
718
|
+
rename_migration: {
|
|
719
|
+
type: "object",
|
|
720
|
+
additionalProperties: false,
|
|
721
|
+
required: ["from", "to"],
|
|
722
|
+
properties: { from: migrationKeySchema, to: migrationKeySchema }
|
|
723
|
+
},
|
|
724
|
+
mark_applied: {
|
|
725
|
+
type: "object",
|
|
726
|
+
additionalProperties: false,
|
|
727
|
+
required: ["key"],
|
|
728
|
+
properties: { key: migrationKeySchema }
|
|
729
|
+
},
|
|
730
|
+
unmark_applied: {
|
|
731
|
+
type: "object",
|
|
732
|
+
additionalProperties: false,
|
|
733
|
+
required: ["key"],
|
|
734
|
+
properties: { key: migrationKeySchema }
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
var ajv = new import_ajv.default({ allErrors: true, strict: true });
|
|
742
|
+
var validateSchema = ajv.compile(patchSchema);
|
|
743
|
+
function fail(message, file, patchKey) {
|
|
744
|
+
throw new PatchValidationError(message, file, patchKey);
|
|
745
|
+
}
|
|
746
|
+
function checkMigrationKey(value, context, file, patchKey) {
|
|
747
|
+
if (value !== value.trim()) fail(`${context}: migration key has leading/trailing whitespace`, file, patchKey);
|
|
748
|
+
if (/[/\\]/.test(value)) fail(`${context}: migration key contains a path separator`, file, patchKey);
|
|
749
|
+
if (/[\x00-\x1f\x7f]/.test(value)) fail(`${context}: migration key contains control characters`, file, patchKey);
|
|
750
|
+
if (value.length === 0 || value.length > MAX_MIGRATION_KEY_LENGTH) {
|
|
751
|
+
fail(`${context}: migration key length out of bounds`, file, patchKey);
|
|
752
|
+
}
|
|
753
|
+
return canonicalMigrationKey(value);
|
|
754
|
+
}
|
|
755
|
+
function assertStrictYaml(doc, file, patchKey) {
|
|
756
|
+
if (doc.errors.length > 0) {
|
|
757
|
+
fail(`YAML parse error: ${doc.errors[0].message}`, file, patchKey);
|
|
758
|
+
}
|
|
759
|
+
if (doc.warnings.length > 0) {
|
|
760
|
+
fail(`YAML warning treated as error: ${doc.warnings[0].message}`, file, patchKey);
|
|
761
|
+
}
|
|
762
|
+
const visit = (node) => {
|
|
763
|
+
var _a, _b;
|
|
764
|
+
if (node == null || typeof node !== "object") return;
|
|
765
|
+
if ("source" in node && ((_a = node.constructor) == null ? void 0 : _a.name) === "Alias") {
|
|
766
|
+
fail("YAML aliases are not permitted in patch files", file, patchKey);
|
|
767
|
+
}
|
|
768
|
+
if (node.anchor) {
|
|
769
|
+
fail("YAML anchors are not permitted in patch files", file, patchKey);
|
|
770
|
+
}
|
|
771
|
+
if (node.tag && ![
|
|
772
|
+
"tag:yaml.org,2002:str",
|
|
773
|
+
"tag:yaml.org,2002:int",
|
|
774
|
+
"tag:yaml.org,2002:bool",
|
|
775
|
+
"tag:yaml.org,2002:null",
|
|
776
|
+
"tag:yaml.org,2002:map",
|
|
777
|
+
"tag:yaml.org,2002:seq"
|
|
778
|
+
].includes(node.tag)) {
|
|
779
|
+
fail(`YAML tag '${node.tag}' is not permitted in patch files`, file, patchKey);
|
|
780
|
+
}
|
|
781
|
+
if (Array.isArray(node.items)) {
|
|
782
|
+
for (const item of node.items) {
|
|
783
|
+
if (item && typeof item === "object" && "key" in item) {
|
|
784
|
+
const keyValue = (_b = item.key) == null ? void 0 : _b.value;
|
|
785
|
+
if (keyValue === "<<") fail("YAML merge keys are not permitted in patch files", file, patchKey);
|
|
786
|
+
visit(item.key);
|
|
787
|
+
visit(item.value);
|
|
788
|
+
} else {
|
|
789
|
+
visit(item);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
visit(doc.contents);
|
|
795
|
+
}
|
|
796
|
+
function parsePatchContent(content, fileName, patchKey) {
|
|
797
|
+
var _a;
|
|
798
|
+
const doc = (0, import_yaml.parseDocument)(content, {
|
|
799
|
+
uniqueKeys: true,
|
|
800
|
+
// duplicate mapping keys become errors
|
|
801
|
+
merge: false,
|
|
802
|
+
schema: "core",
|
|
803
|
+
version: "1.2"
|
|
804
|
+
});
|
|
805
|
+
assertStrictYaml(doc, fileName, patchKey);
|
|
806
|
+
const raw = doc.toJS({ mapAsMap: false });
|
|
807
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
808
|
+
fail("Patch document must be a YAML mapping", fileName, patchKey);
|
|
809
|
+
}
|
|
810
|
+
if (!validateSchema(raw)) {
|
|
811
|
+
const detail = ((_a = validateSchema.errors) != null ? _a : []).map((e) => `${e.instancePath || "/"} ${e.message}`).join("; ");
|
|
812
|
+
const anyRaw = raw;
|
|
813
|
+
if (typeof anyRaw.version === "number" && anyRaw.version !== PATCH_FORMAT_VERSION) {
|
|
814
|
+
fail(`Unknown patch format version: ${anyRaw.version} (supported: ${PATCH_FORMAT_VERSION})`, fileName, patchKey);
|
|
815
|
+
}
|
|
816
|
+
fail(`Schema validation failed: ${detail}`, fileName, patchKey);
|
|
817
|
+
}
|
|
818
|
+
const parsed = raw;
|
|
819
|
+
if (parsed.version !== PATCH_FORMAT_VERSION) {
|
|
820
|
+
fail(`Unknown patch format version: ${parsed.version} (supported: ${PATCH_FORMAT_VERSION})`, fileName, patchKey);
|
|
821
|
+
}
|
|
822
|
+
const description = parsed.description.trim();
|
|
823
|
+
if (description.length === 0) fail("description must be non-empty", fileName, patchKey);
|
|
824
|
+
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
825
|
+
fail(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters`, fileName, patchKey);
|
|
826
|
+
}
|
|
827
|
+
const operations = parsed.operations.map((op, index) => {
|
|
828
|
+
const verbs = Object.keys(op);
|
|
829
|
+
const verb = verbs[0];
|
|
830
|
+
const context = `operation ${index} (${verb})`;
|
|
831
|
+
switch (verb) {
|
|
832
|
+
case "rename_migration": {
|
|
833
|
+
const from = checkMigrationKey(op.rename_migration.from, context, fileName, patchKey);
|
|
834
|
+
const to = checkMigrationKey(op.rename_migration.to, context, fileName, patchKey);
|
|
835
|
+
if (from === to) {
|
|
836
|
+
fail(`${context}: 'from' and 'to' are identical after canonicalization ('${from}')`, fileName, patchKey);
|
|
837
|
+
}
|
|
838
|
+
return { verb: "rename_migration", from, to };
|
|
839
|
+
}
|
|
840
|
+
case "mark_applied":
|
|
841
|
+
return { verb: "mark_applied", key: checkMigrationKey(op.mark_applied.key, context, fileName, patchKey) };
|
|
842
|
+
case "unmark_applied":
|
|
843
|
+
return { verb: "unmark_applied", key: checkMigrationKey(op.unmark_applied.key, context, fileName, patchKey) };
|
|
844
|
+
default:
|
|
845
|
+
fail(`operation ${index}: unknown verb '${verb}'`, fileName, patchKey);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return { version: parsed.version, description, operations };
|
|
849
|
+
}
|
|
850
|
+
function validatePatchPlan(patches, manifest) {
|
|
851
|
+
const renames = [];
|
|
852
|
+
for (const patch of patches) {
|
|
853
|
+
patch.operations.forEach((op, index) => {
|
|
854
|
+
if (op.verb === "rename_migration") {
|
|
855
|
+
renames.push({ from: op.from, to: op.to, file: patch.fileName });
|
|
856
|
+
} else if (op.verb === "mark_applied") {
|
|
857
|
+
if (!manifest.has(op.key)) {
|
|
858
|
+
throw new PatchValidationError(
|
|
859
|
+
`operation ${index} (mark_applied): key '${op.key}' is not present in the current migration manifest`,
|
|
860
|
+
patch.fileName,
|
|
861
|
+
patch.patchKey
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
if (renames.length === 0) return;
|
|
868
|
+
const mentioned = /* @__PURE__ */ new Set();
|
|
869
|
+
renames.forEach((r) => {
|
|
870
|
+
mentioned.add(r.from);
|
|
871
|
+
mentioned.add(r.to);
|
|
872
|
+
});
|
|
873
|
+
const finalKey = (start) => {
|
|
874
|
+
let current = start;
|
|
875
|
+
for (const r of renames) {
|
|
876
|
+
if (current === r.from) current = r.to;
|
|
877
|
+
}
|
|
878
|
+
return current;
|
|
879
|
+
};
|
|
880
|
+
for (const key of mentioned) {
|
|
881
|
+
const finish = finalKey(key);
|
|
882
|
+
if (manifest.has(key)) {
|
|
883
|
+
if (finish !== key) {
|
|
884
|
+
throw new PatchValidationError(
|
|
885
|
+
`rename plan moves current migration '${key}' to '${finish}', which would make its file incorrectly pending`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
} else {
|
|
889
|
+
if (!manifest.has(finish)) {
|
|
890
|
+
throw new PatchValidationError(
|
|
891
|
+
`rename plan leaves historical key '${key}' at '${finish}', which is not present in the current migration manifest`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// framework/PatchDirectoryReader.ts
|
|
899
|
+
var PatchDirectoryReader = class {
|
|
900
|
+
constructor(directory) {
|
|
901
|
+
this.directory = directory;
|
|
902
|
+
}
|
|
903
|
+
loadPatches() {
|
|
904
|
+
if (!import_fs4.default.existsSync(this.directory)) return [];
|
|
905
|
+
const entries = import_fs4.default.readdirSync(this.directory, { withFileTypes: true });
|
|
906
|
+
const patchFiles = [];
|
|
907
|
+
for (const entry of entries) {
|
|
908
|
+
if (!entry.name.endsWith(".yaml")) continue;
|
|
909
|
+
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
910
|
+
if (entry.isSymbolicLink()) {
|
|
911
|
+
throw new PatchValidationError(`patch file must be a regular file, not a symlink`, entry.name);
|
|
912
|
+
}
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
patchFiles.push(entry.name);
|
|
916
|
+
}
|
|
917
|
+
patchFiles.sort((a, b) => {
|
|
918
|
+
const stampA = parseInt(a.slice(0, 13), 10);
|
|
919
|
+
const stampB = parseInt(b.slice(0, 13), 10);
|
|
920
|
+
if (!Number.isNaN(stampA) && !Number.isNaN(stampB) && stampA !== stampB) {
|
|
921
|
+
return stampA - stampB;
|
|
922
|
+
}
|
|
923
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
924
|
+
});
|
|
925
|
+
return patchFiles.map((fileName) => {
|
|
926
|
+
const match = PATCH_FILENAME_REGEX.exec(fileName);
|
|
927
|
+
if (!match) {
|
|
928
|
+
throw new PatchValidationError(
|
|
929
|
+
`invalid patch filename (expected <13-digit-stamp>_<name>.yaml with name matching [a-z0-9][a-z0-9_-]{0,119})`,
|
|
930
|
+
fileName
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
const patchKey = fileName.slice(0, -".yaml".length);
|
|
934
|
+
const filePath = import_path3.default.join(this.directory, fileName);
|
|
935
|
+
const bytes = import_fs4.default.readFileSync(filePath);
|
|
936
|
+
const checksum = import_crypto.default.createHash("sha256").update(bytes).digest("hex");
|
|
937
|
+
const content = bytes.toString("utf8");
|
|
938
|
+
const { version, description, operations } = parsePatchContent(content, fileName, patchKey);
|
|
939
|
+
return { patchKey, fileName, filePath, checksum, version, description, operations };
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// framework/PatchRunner.ts
|
|
945
|
+
function toError(error) {
|
|
946
|
+
if (error instanceof Error) return error;
|
|
947
|
+
return new Error(String(error));
|
|
948
|
+
}
|
|
949
|
+
function extractRows(result) {
|
|
950
|
+
if (!Array.isArray(result)) return [];
|
|
951
|
+
if (Array.isArray(result[0])) return result[0];
|
|
952
|
+
if (result.length === 2 && result[0] && typeof result[0] === "object" && result[1] && typeof result[1] === "object" && !("rows" in result[1])) {
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
if (result[0] == null) return [];
|
|
956
|
+
return [result[0]];
|
|
957
|
+
}
|
|
958
|
+
var PatchRunner = class {
|
|
959
|
+
constructor(sqlrunner, config) {
|
|
960
|
+
this.sqlrunner = sqlrunner;
|
|
961
|
+
this.config = config;
|
|
962
|
+
this.patchTable = resolvePatchTable(config);
|
|
963
|
+
this.migrationTable = config.migration_table;
|
|
964
|
+
this.dialect = config.database;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Discovers, validates, and applies every unapplied patch in order.
|
|
968
|
+
* Each unapplied patch is its own transaction; earlier committed patches
|
|
969
|
+
* remain committed if a later patch fails.
|
|
970
|
+
*/
|
|
971
|
+
applyPending() {
|
|
972
|
+
return __async(this, null, function* () {
|
|
973
|
+
const reader = new PatchDirectoryReader(resolvePatchFolder(this.config));
|
|
974
|
+
const patches = reader.loadPatches();
|
|
975
|
+
const history = yield this.loadHistory();
|
|
976
|
+
if (patches.length === 0 && history.length === 0) {
|
|
977
|
+
return [];
|
|
978
|
+
}
|
|
979
|
+
const byKey = new Map(patches.map((p) => [p.patchKey, p]));
|
|
980
|
+
for (const row of history) {
|
|
981
|
+
const file = byKey.get(row.patch_key);
|
|
982
|
+
if (!file) {
|
|
983
|
+
throw new PatchIntegrityError(
|
|
984
|
+
`applied patch '${row.patch_key}' has no corresponding file in the patch folder; patch files are permanent and must never be renamed or deleted`,
|
|
985
|
+
void 0,
|
|
986
|
+
row.patch_key
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (file.checksum !== row.checksum) {
|
|
990
|
+
throw new PatchIntegrityError(
|
|
991
|
+
`applied patch '${row.patch_key}' content changed after application; patch files are immutable once recorded`,
|
|
992
|
+
file.fileName,
|
|
993
|
+
row.patch_key,
|
|
994
|
+
row.checksum,
|
|
995
|
+
file.checksum
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const manifest = loadMigrationManifest(this.config.migration_folder, this.dialect);
|
|
1000
|
+
validatePatchPlan(patches, manifest);
|
|
1001
|
+
const appliedKeys = new Set(history.map((r) => r.patch_key));
|
|
1002
|
+
const results = [];
|
|
1003
|
+
for (const patch of patches) {
|
|
1004
|
+
if (appliedKeys.has(patch.patchKey)) {
|
|
1005
|
+
results.push({
|
|
1006
|
+
patchKey: patch.patchKey,
|
|
1007
|
+
fileName: patch.fileName,
|
|
1008
|
+
status: "already_applied",
|
|
1009
|
+
operations: []
|
|
1010
|
+
});
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
results.push(yield this.applyOne(patch, manifest));
|
|
1014
|
+
}
|
|
1015
|
+
return results;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
loadHistory() {
|
|
1019
|
+
return __async(this, null, function* () {
|
|
1020
|
+
try {
|
|
1021
|
+
const result = yield this.sqlrunner.query(
|
|
1022
|
+
`SELECT patch_key, checksum FROM ${this.patchTable} WHERE migration_table = ?`,
|
|
1023
|
+
[this.migrationTable]
|
|
1024
|
+
);
|
|
1025
|
+
return extractRows(result);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
throw new PatchExecutionError(
|
|
1028
|
+
`failed to read patch history from '${this.patchTable}'`,
|
|
1029
|
+
void 0,
|
|
1030
|
+
void 0,
|
|
1031
|
+
void 0,
|
|
1032
|
+
void 0,
|
|
1033
|
+
toError(error)
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
beginSql() {
|
|
1039
|
+
switch (this.dialect) {
|
|
1040
|
+
case "sqlite":
|
|
1041
|
+
return "BEGIN IMMEDIATE";
|
|
1042
|
+
case "pg":
|
|
1043
|
+
return "BEGIN";
|
|
1044
|
+
default:
|
|
1045
|
+
return "START TRANSACTION";
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
begin(patch) {
|
|
1049
|
+
return __async(this, null, function* () {
|
|
1050
|
+
const deadline = Date.now() + 1e4;
|
|
1051
|
+
while (true) {
|
|
1052
|
+
try {
|
|
1053
|
+
yield this.sqlrunner.execute(this.beginSql());
|
|
1054
|
+
return;
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
const message = toError(error).message;
|
|
1057
|
+
if (/SQLITE_BUSY|database is locked/i.test(message) && Date.now() < deadline) {
|
|
1058
|
+
yield new Promise((resolve) => setTimeout(resolve, 50));
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
throw new PatchExecutionError(
|
|
1062
|
+
"failed to start patch transaction",
|
|
1063
|
+
patch.fileName,
|
|
1064
|
+
patch.patchKey,
|
|
1065
|
+
void 0,
|
|
1066
|
+
void 0,
|
|
1067
|
+
toError(error)
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
rollbackQuietly() {
|
|
1074
|
+
return __async(this, null, function* () {
|
|
1075
|
+
try {
|
|
1076
|
+
yield this.sqlrunner.execute("ROLLBACK");
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
applyOne(patch, manifest) {
|
|
1082
|
+
return __async(this, null, function* () {
|
|
1083
|
+
yield this.begin(patch);
|
|
1084
|
+
try {
|
|
1085
|
+
yield this.sqlrunner.execute(
|
|
1086
|
+
`INSERT INTO ${this.patchTable} (migration_table, patch_key, checksum, format_version, description)
|
|
1087
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
1088
|
+
[this.migrationTable, patch.patchKey, patch.checksum, patch.version, patch.description]
|
|
1089
|
+
);
|
|
1090
|
+
} catch (claimError) {
|
|
1091
|
+
yield this.rollbackQuietly();
|
|
1092
|
+
const committed = yield this.findCommittedRow(patch.patchKey);
|
|
1093
|
+
if (committed) {
|
|
1094
|
+
if (committed.checksum === patch.checksum) {
|
|
1095
|
+
return {
|
|
1096
|
+
patchKey: patch.patchKey,
|
|
1097
|
+
fileName: patch.fileName,
|
|
1098
|
+
status: "already_applied",
|
|
1099
|
+
operations: []
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
throw new PatchIntegrityError(
|
|
1103
|
+
`patch '${patch.patchKey}' was applied elsewhere with a different checksum`,
|
|
1104
|
+
patch.fileName,
|
|
1105
|
+
patch.patchKey,
|
|
1106
|
+
committed.checksum,
|
|
1107
|
+
patch.checksum
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
throw new PatchExecutionError(
|
|
1111
|
+
"failed to claim patch-history row",
|
|
1112
|
+
patch.fileName,
|
|
1113
|
+
patch.patchKey,
|
|
1114
|
+
void 0,
|
|
1115
|
+
void 0,
|
|
1116
|
+
toError(claimError)
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const operationResults = [];
|
|
1120
|
+
try {
|
|
1121
|
+
for (let index = 0; index < patch.operations.length; index++) {
|
|
1122
|
+
operationResults.push(
|
|
1123
|
+
yield this.applyOperation(patch, patch.operations[index], index, manifest)
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
yield this.sqlrunner.execute("COMMIT");
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
yield this.rollbackQuietly();
|
|
1129
|
+
if (error instanceof PatchConflictError || error instanceof PatchExecutionError || error instanceof PatchIntegrityError) {
|
|
1130
|
+
throw error;
|
|
1131
|
+
}
|
|
1132
|
+
throw new PatchExecutionError(
|
|
1133
|
+
"patch application failed",
|
|
1134
|
+
patch.fileName,
|
|
1135
|
+
patch.patchKey,
|
|
1136
|
+
void 0,
|
|
1137
|
+
void 0,
|
|
1138
|
+
toError(error)
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
patchKey: patch.patchKey,
|
|
1143
|
+
fileName: patch.fileName,
|
|
1144
|
+
status: "applied",
|
|
1145
|
+
operations: operationResults
|
|
1146
|
+
};
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
findCommittedRow(patchKey) {
|
|
1150
|
+
return __async(this, null, function* () {
|
|
1151
|
+
const result = yield this.sqlrunner.query(
|
|
1152
|
+
`SELECT patch_key, checksum FROM ${this.patchTable} WHERE migration_table = ? AND patch_key = ?`,
|
|
1153
|
+
[this.migrationTable, patchKey]
|
|
1154
|
+
);
|
|
1155
|
+
const list = extractRows(result);
|
|
1156
|
+
return list.length > 0 ? list[0] : null;
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
countRows(key) {
|
|
1160
|
+
return __async(this, null, function* () {
|
|
1161
|
+
var _a, _b, _c;
|
|
1162
|
+
const result = yield this.sqlrunner.query(
|
|
1163
|
+
`SELECT COUNT(*) AS row_count FROM ${this.migrationTable} WHERE migration_key = ?`,
|
|
1164
|
+
[key]
|
|
1165
|
+
);
|
|
1166
|
+
const rows = extractRows(result);
|
|
1167
|
+
const value = (_c = (_a = rows[0]) == null ? void 0 : _a.row_count) != null ? _c : Object.values((_b = rows[0]) != null ? _b : {})[0];
|
|
1168
|
+
return Number(value != null ? value : 0);
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
conflict(patch, index, verb, message, keys, counts) {
|
|
1172
|
+
throw new PatchConflictError(
|
|
1173
|
+
`operation ${index} (${verb}): ${message}`,
|
|
1174
|
+
patch.fileName,
|
|
1175
|
+
patch.patchKey,
|
|
1176
|
+
index,
|
|
1177
|
+
verb,
|
|
1178
|
+
keys,
|
|
1179
|
+
counts
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
applyOperation(patch, op, index, manifest) {
|
|
1183
|
+
return __async(this, null, function* () {
|
|
1184
|
+
var _a, _b, _c, _d;
|
|
1185
|
+
try {
|
|
1186
|
+
switch (op.verb) {
|
|
1187
|
+
case "rename_migration": {
|
|
1188
|
+
const fromCount = yield this.countRows(op.from);
|
|
1189
|
+
const toCount = yield this.countRows(op.to);
|
|
1190
|
+
const counts = { [op.from]: fromCount, [op.to]: toCount };
|
|
1191
|
+
if (fromCount > 1 || toCount > 1) {
|
|
1192
|
+
this.conflict(
|
|
1193
|
+
patch,
|
|
1194
|
+
index,
|
|
1195
|
+
op.verb,
|
|
1196
|
+
`ledger corruption: duplicate rows for a migration key`,
|
|
1197
|
+
[op.from, op.to],
|
|
1198
|
+
counts
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
if (fromCount === 1 && toCount === 1) {
|
|
1202
|
+
this.conflict(
|
|
1203
|
+
patch,
|
|
1204
|
+
index,
|
|
1205
|
+
op.verb,
|
|
1206
|
+
`both '${op.from}' and '${op.to}' exist in the ledger`,
|
|
1207
|
+
[op.from, op.to],
|
|
1208
|
+
counts
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
if (fromCount === 0) {
|
|
1212
|
+
return { verb: op.verb, changed: false };
|
|
1213
|
+
}
|
|
1214
|
+
const target = manifest.get(op.to);
|
|
1215
|
+
if (target) {
|
|
1216
|
+
yield this.sqlrunner.execute(
|
|
1217
|
+
`UPDATE ${this.migrationTable} SET migration_key = ?, up = ?, down = ? WHERE migration_key = ?`,
|
|
1218
|
+
[op.to, (_a = target.upFile) != null ? _a : "", (_b = target.downFile) != null ? _b : "", op.from]
|
|
1219
|
+
);
|
|
1220
|
+
} else {
|
|
1221
|
+
yield this.sqlrunner.execute(
|
|
1222
|
+
`UPDATE ${this.migrationTable} SET migration_key = ? WHERE migration_key = ?`,
|
|
1223
|
+
[op.to, op.from]
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
return { verb: op.verb, changed: true };
|
|
1227
|
+
}
|
|
1228
|
+
case "mark_applied": {
|
|
1229
|
+
const count = yield this.countRows(op.key);
|
|
1230
|
+
if (count > 1) {
|
|
1231
|
+
this.conflict(
|
|
1232
|
+
patch,
|
|
1233
|
+
index,
|
|
1234
|
+
op.verb,
|
|
1235
|
+
`ledger corruption: duplicate rows for '${op.key}'`,
|
|
1236
|
+
[op.key],
|
|
1237
|
+
{ [op.key]: count }
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
if (count === 1) {
|
|
1241
|
+
return { verb: op.verb, changed: false };
|
|
1242
|
+
}
|
|
1243
|
+
const entry = manifest.get(op.key);
|
|
1244
|
+
yield this.sqlrunner.execute(
|
|
1245
|
+
`INSERT INTO ${this.migrationTable} (migration_key, up, down) VALUES (?, ?, ?)`,
|
|
1246
|
+
[op.key, (_c = entry == null ? void 0 : entry.upFile) != null ? _c : "", (_d = entry == null ? void 0 : entry.downFile) != null ? _d : ""]
|
|
1247
|
+
);
|
|
1248
|
+
return { verb: op.verb, changed: true };
|
|
1249
|
+
}
|
|
1250
|
+
case "unmark_applied": {
|
|
1251
|
+
const count = yield this.countRows(op.key);
|
|
1252
|
+
if (count > 1) {
|
|
1253
|
+
this.conflict(
|
|
1254
|
+
patch,
|
|
1255
|
+
index,
|
|
1256
|
+
op.verb,
|
|
1257
|
+
`ledger corruption: duplicate rows for '${op.key}'`,
|
|
1258
|
+
[op.key],
|
|
1259
|
+
{ [op.key]: count }
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
if (count === 0) {
|
|
1263
|
+
return { verb: op.verb, changed: false };
|
|
1264
|
+
}
|
|
1265
|
+
yield this.sqlrunner.execute(
|
|
1266
|
+
`DELETE FROM ${this.migrationTable} WHERE migration_key = ?`,
|
|
1267
|
+
[op.key]
|
|
1268
|
+
);
|
|
1269
|
+
return { verb: op.verb, changed: true };
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
if (error instanceof PatchConflictError) throw error;
|
|
1274
|
+
throw new PatchExecutionError(
|
|
1275
|
+
`operation failed`,
|
|
1276
|
+
patch.fileName,
|
|
1277
|
+
patch.patchKey,
|
|
1278
|
+
index,
|
|
1279
|
+
op.verb,
|
|
1280
|
+
toError(error)
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
// framework/PatchCreator.ts
|
|
1288
|
+
var import_fs5 = __toESM(require("fs"));
|
|
1289
|
+
var import_path4 = __toESM(require("path"));
|
|
1290
|
+
var MAX_NAME_LENGTH = 120;
|
|
1291
|
+
var SCAFFOLD = `version: 1
|
|
1292
|
+
description: TODO
|
|
1293
|
+
operations: []
|
|
1294
|
+
`;
|
|
1295
|
+
var PatchCreator = class _PatchCreator {
|
|
1296
|
+
constructor(patchFolder) {
|
|
1297
|
+
this.patchFolder = patchFolder;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Normalizes a patch name: trim, whitespace runs -> `_`, lowercase.
|
|
1301
|
+
* Rejects empty results, path separators, `..`, control characters,
|
|
1302
|
+
* characters outside [a-z0-9_-], and names longer than 120 characters.
|
|
1303
|
+
*/
|
|
1304
|
+
static normalizeName(name) {
|
|
1305
|
+
const normalized = (name != null ? name : "").trim().replace(/\s+/g, "_").toLowerCase();
|
|
1306
|
+
if (normalized.length === 0) {
|
|
1307
|
+
throw new CLIError("Patch name is required");
|
|
1308
|
+
}
|
|
1309
|
+
if (normalized.includes("/") || normalized.includes("\\")) {
|
|
1310
|
+
throw new CLIError("Patch name must not contain path separators");
|
|
1311
|
+
}
|
|
1312
|
+
if (normalized.includes("..")) {
|
|
1313
|
+
throw new CLIError("Patch name must not contain '..'");
|
|
1314
|
+
}
|
|
1315
|
+
if (/[\x00-\x1f\x7f]/.test(normalized)) {
|
|
1316
|
+
throw new CLIError("Patch name must not contain control characters");
|
|
1317
|
+
}
|
|
1318
|
+
if (!/^[a-z0-9_-]+$/.test(normalized)) {
|
|
1319
|
+
throw new CLIError("Patch name may only contain characters [a-z0-9_-]");
|
|
1320
|
+
}
|
|
1321
|
+
if (normalized.length > MAX_NAME_LENGTH) {
|
|
1322
|
+
throw new CLIError(`Patch name exceeds ${MAX_NAME_LENGTH} characters after normalization`);
|
|
1323
|
+
}
|
|
1324
|
+
return normalized;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Creates `<patch_folder>/<stamp>_<normalized_name>.yaml` with exclusive
|
|
1328
|
+
* file creation. On a millisecond-stamp collision, mints a later stamp
|
|
1329
|
+
* and retries. Returns the created path.
|
|
1330
|
+
*/
|
|
1331
|
+
create(name) {
|
|
1332
|
+
const normalized = _PatchCreator.normalizeName(name);
|
|
1333
|
+
if (!import_fs5.default.existsSync(this.patchFolder)) {
|
|
1334
|
+
import_fs5.default.mkdirSync(this.patchFolder, { recursive: true });
|
|
1335
|
+
}
|
|
1336
|
+
let stamp = Date.now();
|
|
1337
|
+
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
1338
|
+
const filePath = import_path4.default.join(this.patchFolder, `${stamp}_${normalized}.yaml`);
|
|
1339
|
+
try {
|
|
1340
|
+
import_fs5.default.writeFileSync(filePath, SCAFFOLD, { flag: "wx" });
|
|
1341
|
+
return filePath;
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
if (error && error.code === "EEXIST") {
|
|
1344
|
+
stamp += 1;
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
throw error;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
throw new CLIError("Unable to create patch file: too many filename collisions");
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
561
1354
|
// framework/SQLRunner.ts
|
|
562
1355
|
var BaseSQLRunner = class {
|
|
563
1356
|
/**
|
|
@@ -872,7 +1665,7 @@ var PgRunner = class _PgRunner extends BaseSQLRunner {
|
|
|
872
1665
|
};
|
|
873
1666
|
|
|
874
1667
|
// framework/MigrationRunner.ts
|
|
875
|
-
function
|
|
1668
|
+
function toError2(error) {
|
|
876
1669
|
if (error instanceof Error) return error;
|
|
877
1670
|
return new Error(String(error));
|
|
878
1671
|
}
|
|
@@ -900,10 +1693,12 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
900
1693
|
return __async(this, null, function* () {
|
|
901
1694
|
const configReader = new FileMigrationConfigReader(configFile);
|
|
902
1695
|
const config = configReader.loadFile();
|
|
1696
|
+
let factoryOwnsConnection = false;
|
|
903
1697
|
if (!conn) {
|
|
904
1698
|
conn = yield this.createConnection(config);
|
|
1699
|
+
factoryOwnsConnection = true;
|
|
905
1700
|
}
|
|
906
|
-
return new _MigrationRunnerFactory().create(config, conn);
|
|
1701
|
+
return new _MigrationRunnerFactory().create(config, conn, factoryOwnsConnection);
|
|
907
1702
|
});
|
|
908
1703
|
}
|
|
909
1704
|
static createConnection(config) {
|
|
@@ -923,7 +1718,7 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
923
1718
|
conn = yield ((_b = (_a = mysql.default) == null ? void 0 : _a.createConnection) != null ? _b : mysql.createConnection)(settings);
|
|
924
1719
|
return conn;
|
|
925
1720
|
} catch (error) {
|
|
926
|
-
throw DatabaseConnectionError.connectionFailed("sql",
|
|
1721
|
+
throw DatabaseConnectionError.connectionFailed("sql", toError2(error).message);
|
|
927
1722
|
}
|
|
928
1723
|
case "sqlite":
|
|
929
1724
|
if (!config.sqlite) {
|
|
@@ -938,7 +1733,7 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
938
1733
|
});
|
|
939
1734
|
return conn;
|
|
940
1735
|
} catch (error) {
|
|
941
|
-
throw DatabaseConnectionError.connectionFailed("sqlite",
|
|
1736
|
+
throw DatabaseConnectionError.connectionFailed("sqlite", toError2(error).message);
|
|
942
1737
|
}
|
|
943
1738
|
case "pg":
|
|
944
1739
|
if (!config.pg && !process.env.DATABASE_URL) {
|
|
@@ -954,7 +1749,7 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
954
1749
|
yield conn.connect();
|
|
955
1750
|
return conn;
|
|
956
1751
|
} catch (error) {
|
|
957
|
-
throw DatabaseConnectionError.connectionFailed("pg",
|
|
1752
|
+
throw DatabaseConnectionError.connectionFailed("pg", toError2(error).message);
|
|
958
1753
|
}
|
|
959
1754
|
default:
|
|
960
1755
|
throw ConfigurationError.unknownDatabaseType(config.database);
|
|
@@ -968,7 +1763,7 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
968
1763
|
return new _MigrationRunnerFactory().createEmpty(config);
|
|
969
1764
|
});
|
|
970
1765
|
}
|
|
971
|
-
create(config, conn) {
|
|
1766
|
+
create(config, conn, factoryOwnsConnection = false) {
|
|
972
1767
|
return __async(this, null, function* () {
|
|
973
1768
|
let sqlrunner;
|
|
974
1769
|
let driverConnection = conn;
|
|
@@ -981,8 +1776,19 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
981
1776
|
const setup = new MigrationSetup(sqlrunner, config);
|
|
982
1777
|
const read_strategy = this.getReadStategy(config);
|
|
983
1778
|
const migration_files = new MigrationDirectoryReader(config.migration_folder, read_strategy, sqlrunner, config.database);
|
|
984
|
-
|
|
985
|
-
|
|
1779
|
+
const runner = new MySQLMigrationRunner(config, migration_files, setup, sqlrunner, driverConnection);
|
|
1780
|
+
try {
|
|
1781
|
+
yield runner.setup();
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
if (factoryOwnsConnection) {
|
|
1784
|
+
try {
|
|
1785
|
+
yield sqlrunner.end();
|
|
1786
|
+
} catch (e) {
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
throw error;
|
|
1790
|
+
}
|
|
1791
|
+
return runner;
|
|
986
1792
|
});
|
|
987
1793
|
}
|
|
988
1794
|
createEmpty(config) {
|
|
@@ -992,7 +1798,7 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
992
1798
|
const setup = new MigrationSetup(sqlrunner, config);
|
|
993
1799
|
const read_strategy = this.getReadStategy(config);
|
|
994
1800
|
const migration_files = new MigrationDirectoryReader(config.migration_folder, read_strategy, sqlrunner, config.database);
|
|
995
|
-
return new MySQLMigrationRunner(config, migration_files, setup, sqlrunner, conn);
|
|
1801
|
+
return new MySQLMigrationRunner(config, migration_files, setup, sqlrunner, conn, false);
|
|
996
1802
|
});
|
|
997
1803
|
}
|
|
998
1804
|
getReadStategy(config) {
|
|
@@ -1009,33 +1815,77 @@ var MigrationRunnerFactory = class _MigrationRunnerFactory {
|
|
|
1009
1815
|
}
|
|
1010
1816
|
};
|
|
1011
1817
|
var MySQLMigrationRunner = class {
|
|
1012
|
-
constructor(config, directory, setupRunner, sqlrunner, connection) {
|
|
1818
|
+
constructor(config, directory, setupRunner, sqlrunner, connection, preflightEnabled = true) {
|
|
1013
1819
|
this.config = config;
|
|
1014
1820
|
this.directory = directory;
|
|
1015
1821
|
this.setupRunner = setupRunner;
|
|
1016
1822
|
this.sqlrunner = sqlrunner;
|
|
1017
1823
|
this.connection = connection;
|
|
1824
|
+
this.preflightEnabled = preflightEnabled;
|
|
1825
|
+
/**
|
|
1826
|
+
* Memoized in-flight preflight promise. Simultaneous or repeated calls
|
|
1827
|
+
* to setup() on one runner execute the preflight (migration table setup
|
|
1828
|
+
* + patch application) exactly once. Cleared after rejection so a caller
|
|
1829
|
+
* may retry after fixing the cause.
|
|
1830
|
+
*/
|
|
1831
|
+
this.preflightPromise = null;
|
|
1832
|
+
this.lastPatchResults = [];
|
|
1018
1833
|
}
|
|
1019
1834
|
setup() {
|
|
1835
|
+
return __async(this, null, function* () {
|
|
1836
|
+
if (!this.preflightEnabled) {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (!this.preflightPromise) {
|
|
1840
|
+
this.preflightPromise = this.runPreflight();
|
|
1841
|
+
this.preflightPromise.catch(() => {
|
|
1842
|
+
this.preflightPromise = null;
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
return this.preflightPromise;
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
runPreflight() {
|
|
1020
1849
|
return __async(this, null, function* () {
|
|
1021
1850
|
try {
|
|
1022
1851
|
yield this.setupRunner.setup();
|
|
1023
1852
|
} catch (error) {
|
|
1024
|
-
throw new MigrationExecutionError("Failed to set up migration database", void 0, void 0,
|
|
1853
|
+
throw new MigrationExecutionError("Failed to set up migration database", void 0, void 0, toError2(error));
|
|
1025
1854
|
}
|
|
1855
|
+
const patchRunner = new PatchRunner(this.sqlrunner, this.config);
|
|
1856
|
+
this.lastPatchResults = yield patchRunner.applyPending();
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Delegates to the same idempotent preflight; returns the results of the
|
|
1861
|
+
* patch pass that ran (or is running) for this runner.
|
|
1862
|
+
*/
|
|
1863
|
+
applyPendingPatches() {
|
|
1864
|
+
return __async(this, null, function* () {
|
|
1865
|
+
yield this.setup();
|
|
1866
|
+
return this.lastPatchResults;
|
|
1026
1867
|
});
|
|
1027
1868
|
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Scaffolds a new ledger patch file and returns the created path.
|
|
1871
|
+
* Never connects to a database.
|
|
1872
|
+
*/
|
|
1873
|
+
createPatch(name) {
|
|
1874
|
+
const creator = new PatchCreator(resolvePatchFolder(this.config));
|
|
1875
|
+
return creator.create(name);
|
|
1876
|
+
}
|
|
1028
1877
|
terminate() {
|
|
1029
1878
|
return __async(this, null, function* () {
|
|
1030
1879
|
try {
|
|
1031
1880
|
yield this.setupRunner.teardown();
|
|
1032
1881
|
} catch (error) {
|
|
1033
|
-
throw new MigrationExecutionError("Failed to tear down migration database", void 0, void 0,
|
|
1882
|
+
throw new MigrationExecutionError("Failed to tear down migration database", void 0, void 0, toError2(error));
|
|
1034
1883
|
}
|
|
1035
1884
|
});
|
|
1036
1885
|
}
|
|
1037
1886
|
getMigrationsHistory() {
|
|
1038
1887
|
return __async(this, null, function* () {
|
|
1888
|
+
yield this.setup();
|
|
1039
1889
|
try {
|
|
1040
1890
|
const results = yield this.sqlrunner.query(`
|
|
1041
1891
|
select *
|
|
@@ -1043,16 +1893,17 @@ var MySQLMigrationRunner = class {
|
|
|
1043
1893
|
`);
|
|
1044
1894
|
return results[0];
|
|
1045
1895
|
} catch (error) {
|
|
1046
|
-
throw new MigrationExecutionError("Failed to get migration history", void 0, void 0,
|
|
1896
|
+
throw new MigrationExecutionError("Failed to get migration history", void 0, void 0, toError2(error));
|
|
1047
1897
|
}
|
|
1048
1898
|
});
|
|
1049
1899
|
}
|
|
1050
1900
|
getMigrations() {
|
|
1051
1901
|
return __async(this, null, function* () {
|
|
1902
|
+
yield this.setup();
|
|
1052
1903
|
try {
|
|
1053
1904
|
return this.directory.loadMigrations(this.config.migration_table, this.connection);
|
|
1054
1905
|
} catch (error) {
|
|
1055
|
-
throw new MigrationExecutionError("Failed to load migrations", void 0, void 0,
|
|
1906
|
+
throw new MigrationExecutionError("Failed to load migrations", void 0, void 0, toError2(error));
|
|
1056
1907
|
}
|
|
1057
1908
|
});
|
|
1058
1909
|
}
|
|
@@ -1065,7 +1916,7 @@ var MySQLMigrationRunner = class {
|
|
|
1065
1916
|
if (error instanceof MigrationExecutionError) {
|
|
1066
1917
|
throw error;
|
|
1067
1918
|
}
|
|
1068
|
-
throw new MigrationExecutionError("Failed to get pending migrations", void 0, void 0,
|
|
1919
|
+
throw new MigrationExecutionError("Failed to get pending migrations", void 0, void 0, toError2(error));
|
|
1069
1920
|
}
|
|
1070
1921
|
});
|
|
1071
1922
|
}
|
|
@@ -1078,12 +1929,13 @@ var MySQLMigrationRunner = class {
|
|
|
1078
1929
|
if (error instanceof MigrationExecutionError) {
|
|
1079
1930
|
throw error;
|
|
1080
1931
|
}
|
|
1081
|
-
throw new MigrationExecutionError("Failed to get completed migrations", void 0, void 0,
|
|
1932
|
+
throw new MigrationExecutionError("Failed to get completed migrations", void 0, void 0, toError2(error));
|
|
1082
1933
|
}
|
|
1083
1934
|
});
|
|
1084
1935
|
}
|
|
1085
1936
|
migrate(migrationNodes, forward) {
|
|
1086
1937
|
return __async(this, null, function* () {
|
|
1938
|
+
yield this.setup();
|
|
1087
1939
|
for (let node of migrationNodes) {
|
|
1088
1940
|
try {
|
|
1089
1941
|
if (forward) {
|
|
@@ -1096,7 +1948,7 @@ var MySQLMigrationRunner = class {
|
|
|
1096
1948
|
`Failed to ${forward ? "apply" : "rollback"} migration`,
|
|
1097
1949
|
node.name || String(node),
|
|
1098
1950
|
forward ? node.up_sql() : node.down_sql(),
|
|
1099
|
-
|
|
1951
|
+
toError2(error)
|
|
1100
1952
|
);
|
|
1101
1953
|
}
|
|
1102
1954
|
}
|
|
@@ -1104,6 +1956,7 @@ var MySQLMigrationRunner = class {
|
|
|
1104
1956
|
}
|
|
1105
1957
|
reset() {
|
|
1106
1958
|
return __async(this, null, function* () {
|
|
1959
|
+
yield this.setup();
|
|
1107
1960
|
try {
|
|
1108
1961
|
let migrations = yield this.getMigrations();
|
|
1109
1962
|
const rollback = yield migration_filter(migrations, true);
|
|
@@ -1115,7 +1968,7 @@ var MySQLMigrationRunner = class {
|
|
|
1115
1968
|
if (error instanceof MigrationExecutionError) {
|
|
1116
1969
|
throw error;
|
|
1117
1970
|
}
|
|
1118
|
-
throw new MigrationExecutionError("Failed to reset migrations", void 0, void 0,
|
|
1971
|
+
throw new MigrationExecutionError("Failed to reset migrations", void 0, void 0, toError2(error));
|
|
1119
1972
|
}
|
|
1120
1973
|
});
|
|
1121
1974
|
}
|
|
@@ -1124,7 +1977,7 @@ var MySQLMigrationRunner = class {
|
|
|
1124
1977
|
const creator = new MigrationCreator(this.config);
|
|
1125
1978
|
creator.create(name);
|
|
1126
1979
|
} catch (error) {
|
|
1127
|
-
throw new MigrationExecutionError(`Failed to create migration: ${name}`, void 0, void 0,
|
|
1980
|
+
throw new MigrationExecutionError(`Failed to create migration: ${name}`, void 0, void 0, toError2(error));
|
|
1128
1981
|
}
|
|
1129
1982
|
}
|
|
1130
1983
|
close() {
|
|
@@ -1133,7 +1986,7 @@ var MySQLMigrationRunner = class {
|
|
|
1133
1986
|
try {
|
|
1134
1987
|
yield this.sqlrunner.end();
|
|
1135
1988
|
} catch (error) {
|
|
1136
|
-
throw new DatabaseConnectionError(`Failed to close database connection: ${
|
|
1989
|
+
throw new DatabaseConnectionError(`Failed to close database connection: ${toError2(error).message}`);
|
|
1137
1990
|
}
|
|
1138
1991
|
}
|
|
1139
1992
|
});
|
|
@@ -1147,7 +2000,7 @@ var MySQLMigrationRunner = class {
|
|
|
1147
2000
|
return __async(this, null, function* () {
|
|
1148
2001
|
try {
|
|
1149
2002
|
console.log(`Checking for ${config_file}`);
|
|
1150
|
-
const config_exist =
|
|
2003
|
+
const config_exist = import_fs6.default.existsSync(config_file);
|
|
1151
2004
|
if (!config_exist) {
|
|
1152
2005
|
console.log(`Creating ${config_file}`);
|
|
1153
2006
|
const default_config = {
|
|
@@ -1161,11 +2014,11 @@ var MySQLMigrationRunner = class {
|
|
|
1161
2014
|
"password": ""
|
|
1162
2015
|
}
|
|
1163
2016
|
};
|
|
1164
|
-
|
|
2017
|
+
import_fs6.default.writeFileSync(config_file, JSON.stringify(default_config, null, 2));
|
|
1165
2018
|
console.log(`Created ${config_file}`);
|
|
1166
2019
|
}
|
|
1167
2020
|
} catch (error) {
|
|
1168
|
-
throw new ConfigurationError(`Failed to initialize config file: ${
|
|
2021
|
+
throw new ConfigurationError(`Failed to initialize config file: ${toError2(error).message}`);
|
|
1169
2022
|
}
|
|
1170
2023
|
});
|
|
1171
2024
|
}
|
|
@@ -1176,7 +2029,7 @@ var FileMigrationConfigReader = class {
|
|
|
1176
2029
|
}
|
|
1177
2030
|
loadFile() {
|
|
1178
2031
|
try {
|
|
1179
|
-
const fileContent =
|
|
2032
|
+
const fileContent = import_fs6.default.readFileSync(this.configFile);
|
|
1180
2033
|
const config = JSON.parse(fileContent.toString());
|
|
1181
2034
|
if (!config.migration_folder) {
|
|
1182
2035
|
throw ConfigurationError.missingRequiredProperty("migration_folder");
|
|
@@ -1200,7 +2053,7 @@ var FileMigrationConfigReader = class {
|
|
|
1200
2053
|
if (error instanceof ConfigurationError) {
|
|
1201
2054
|
throw error;
|
|
1202
2055
|
}
|
|
1203
|
-
const err =
|
|
2056
|
+
const err = toError2(error);
|
|
1204
2057
|
if (err.message.includes("ENOENT")) {
|
|
1205
2058
|
throw new ConfigurationError(`Config file not found: ${this.configFile}`);
|
|
1206
2059
|
}
|
|
@@ -1217,16 +2070,16 @@ var MigrationCreator = class {
|
|
|
1217
2070
|
throw new CLIError("Migration name is required");
|
|
1218
2071
|
}
|
|
1219
2072
|
try {
|
|
1220
|
-
if (!
|
|
1221
|
-
|
|
2073
|
+
if (!import_fs6.default.existsSync(this.config.migration_folder)) {
|
|
2074
|
+
import_fs6.default.mkdirSync(this.config.migration_folder, { recursive: true });
|
|
1222
2075
|
}
|
|
1223
2076
|
const now_timestamp = Date.now();
|
|
1224
2077
|
const filename_up = `${now_timestamp}_${name}.up.sql`;
|
|
1225
2078
|
const filename_down = `${now_timestamp}_${name}.down.sql`;
|
|
1226
|
-
|
|
2079
|
+
import_fs6.default.writeFileSync(`${this.config.migration_folder}/${filename_up}`, `
|
|
1227
2080
|
-- Write your up migration here
|
|
1228
2081
|
`.trim());
|
|
1229
|
-
|
|
2082
|
+
import_fs6.default.writeFileSync(`${this.config.migration_folder}/${filename_down}`, `
|
|
1230
2083
|
-- Write your down migration here
|
|
1231
2084
|
`.trim());
|
|
1232
2085
|
console.log(`Created migration files:`);
|
|
@@ -1236,16 +2089,16 @@ var MigrationCreator = class {
|
|
|
1236
2089
|
if (error instanceof CLIError) {
|
|
1237
2090
|
throw error;
|
|
1238
2091
|
}
|
|
1239
|
-
throw new MigrationExecutionError(`Failed to create migration files: ${
|
|
2092
|
+
throw new MigrationExecutionError(`Failed to create migration files: ${toError2(error).message}`);
|
|
1240
2093
|
}
|
|
1241
2094
|
}
|
|
1242
2095
|
};
|
|
1243
2096
|
|
|
1244
2097
|
// framework/SeedRunner.ts
|
|
1245
|
-
var
|
|
1246
|
-
var
|
|
2098
|
+
var import_fs7 = __toESM(require("fs"));
|
|
2099
|
+
var import_path5 = __toESM(require("path"));
|
|
1247
2100
|
var import_url = require("url");
|
|
1248
|
-
var
|
|
2101
|
+
var import_ajv2 = __toESM(require("ajv"));
|
|
1249
2102
|
var import_ajv_formats = __toESM(require("ajv-formats"));
|
|
1250
2103
|
var import_api = require("tsx/esm/api");
|
|
1251
2104
|
function resolveAlias(name, aliasMap) {
|
|
@@ -1254,10 +2107,10 @@ function resolveAlias(name, aliasMap) {
|
|
|
1254
2107
|
return (_a = aliasMap[name]) != null ? _a : name;
|
|
1255
2108
|
}
|
|
1256
2109
|
function walkForFile(rootDir, fileName) {
|
|
1257
|
-
if (!
|
|
1258
|
-
const entries =
|
|
2110
|
+
if (!import_fs7.default.existsSync(rootDir)) return null;
|
|
2111
|
+
const entries = import_fs7.default.readdirSync(rootDir, { withFileTypes: true });
|
|
1259
2112
|
for (const entry of entries) {
|
|
1260
|
-
const full =
|
|
2113
|
+
const full = import_path5.default.join(rootDir, entry.name);
|
|
1261
2114
|
if (entry.isDirectory()) {
|
|
1262
2115
|
const found = walkForFile(full, fileName);
|
|
1263
2116
|
if (found) return found;
|
|
@@ -1351,39 +2204,39 @@ function resolveSeed(name, migrationConfig, options) {
|
|
|
1351
2204
|
}
|
|
1352
2205
|
function loadJson(filePath) {
|
|
1353
2206
|
return __async(this, null, function* () {
|
|
1354
|
-
const content = yield
|
|
2207
|
+
const content = yield import_fs7.default.promises.readFile(filePath, "utf8");
|
|
1355
2208
|
return JSON.parse(content);
|
|
1356
2209
|
});
|
|
1357
2210
|
}
|
|
1358
2211
|
function createValidator() {
|
|
1359
|
-
const
|
|
1360
|
-
(0, import_ajv_formats.default)(
|
|
1361
|
-
return
|
|
2212
|
+
const ajv2 = new import_ajv2.default({ allErrors: true, strict: false });
|
|
2213
|
+
(0, import_ajv_formats.default)(ajv2);
|
|
2214
|
+
return ajv2;
|
|
1362
2215
|
}
|
|
1363
2216
|
function validateData(schemaPath, data, validate, log) {
|
|
1364
2217
|
return __async(this, null, function* () {
|
|
1365
2218
|
if (!validate || !schemaPath) return;
|
|
1366
|
-
const content = yield
|
|
2219
|
+
const content = yield import_fs7.default.promises.readFile(schemaPath, "utf8");
|
|
1367
2220
|
const schema = JSON.parse(content);
|
|
1368
|
-
const
|
|
1369
|
-
const validateFn =
|
|
2221
|
+
const ajv2 = createValidator();
|
|
2222
|
+
const validateFn = ajv2.compile(schema);
|
|
1370
2223
|
const ok = validateFn(data);
|
|
1371
2224
|
if (!ok) {
|
|
1372
2225
|
log == null ? void 0 : log(`Validation failed for seed data (${schemaPath})`);
|
|
1373
|
-
throw new Error(`Seed data validation failed: ${
|
|
2226
|
+
throw new Error(`Seed data validation failed: ${ajv2.errorsText(validateFn.errors || [])}`);
|
|
1374
2227
|
}
|
|
1375
2228
|
});
|
|
1376
2229
|
}
|
|
1377
2230
|
function runSqlSeed(runner, resolved, direction) {
|
|
1378
2231
|
return __async(this, null, function* () {
|
|
1379
2232
|
const sqlPath = direction === "up" ? resolved.upPath : resolved.downPath;
|
|
1380
|
-
const sql = yield
|
|
2233
|
+
const sql = yield import_fs7.default.promises.readFile(sqlPath, "utf8");
|
|
1381
2234
|
yield runner.query(sql);
|
|
1382
2235
|
});
|
|
1383
2236
|
}
|
|
1384
2237
|
function loadSeedModule(modulePath) {
|
|
1385
2238
|
return __async(this, null, function* () {
|
|
1386
|
-
const resolved =
|
|
2239
|
+
const resolved = import_path5.default.resolve(modulePath);
|
|
1387
2240
|
if (resolved.endsWith(".ts")) {
|
|
1388
2241
|
const fileUrl = (0, import_url.pathToFileURL)(resolved).href;
|
|
1389
2242
|
return (0, import_api.tsImport)(fileUrl, fileUrl);
|
|
@@ -1504,13 +2357,23 @@ function createSeedFactory(options) {
|
|
|
1504
2357
|
}
|
|
1505
2358
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1506
2359
|
0 && (module.exports = {
|
|
2360
|
+
MigrationError,
|
|
1507
2361
|
MigrationRunner,
|
|
1508
2362
|
MigrationRunnerFactory,
|
|
2363
|
+
PatchConflictError,
|
|
2364
|
+
PatchCreator,
|
|
2365
|
+
PatchError,
|
|
2366
|
+
PatchExecutionError,
|
|
2367
|
+
PatchIntegrityError,
|
|
2368
|
+
PatchRunner,
|
|
2369
|
+
PatchValidationError,
|
|
1509
2370
|
PgRunner,
|
|
1510
2371
|
SQLRunner,
|
|
1511
2372
|
SQLiteRunner,
|
|
1512
2373
|
createSeedFactory,
|
|
1513
2374
|
loadMigrationConfig,
|
|
2375
|
+
resolvePatchFolder,
|
|
2376
|
+
resolvePatchTable,
|
|
1514
2377
|
runSeedsWithRunner
|
|
1515
2378
|
});
|
|
1516
2379
|
//# sourceMappingURL=index.js.map
|