node-pg-migrate
Version:
PostgreSQL database migration management tool for node.js
256 lines (254 loc) • 10.1 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var migration_exports = {};
__export(migration_exports, {
FilenameFormat: () => FilenameFormat,
Migration: () => Migration,
getMigrationFilePaths: () => getMigrationFilePaths,
getNumericPrefix: () => getNumericPrefix
});
module.exports = __toCommonJS(migration_exports);
var import_glob = require("glob");
var import_node_fs = require("node:fs");
var import_promises = require("node:fs/promises");
var import_node_path = require("node:path");
var import_node_process = require("node:process");
var import_migrationBuilder = __toESM(require("./migrationBuilder"));
var import_utils = require("./utils");
var FilenameFormat = /* @__PURE__ */ ((FilenameFormat2) => {
FilenameFormat2["timestamp"] = "timestamp";
FilenameFormat2["utc"] = "utc";
return FilenameFormat2;
})(FilenameFormat || {});
const SEPARATOR = "_";
function localeCompareStringsNumerically(a, b) {
return a.localeCompare(b, void 0, {
usage: "sort",
numeric: true,
sensitivity: "variant",
ignorePunctuation: true
});
}
function compareFileNamesByTimestamp(a, b, logger) {
const aTimestamp = getNumericPrefix(a, logger);
const bTimestamp = getNumericPrefix(b, logger);
return aTimestamp - bTimestamp;
}
async function getMigrationFilePaths(dir, options = {}) {
const { ignorePattern, useGlob = false, logger } = options;
if (useGlob) {
const globMatches = await (0, import_glob.glob)(dir, {
ignore: ignorePattern,
nodir: true,
absolute: true
});
return globMatches.sort(localeCompareStringsNumerically);
}
if (Array.isArray(dir) || Array.isArray(ignorePattern)) {
throw new TypeError(
'Options "dir" and "ignorePattern" can only be arrays when "useGlob" is true'
);
}
const ignoreRegexp = new RegExp(
(ignorePattern == null ? void 0 : ignorePattern.length) ? `^${ignorePattern}$` : "^\\..*"
);
const dirContent = await (0, import_promises.readdir)(`${dir}/`, { withFileTypes: true });
return dirContent.filter(
(dirent) => (dirent.isFile() || dirent.isSymbolicLink()) && !ignoreRegexp.test(dirent.name)
).sort(
(a, b) => compareFileNamesByTimestamp(a.name, b.name, logger) || localeCompareStringsNumerically(a.name, b.name)
).map((dirent) => (0, import_node_path.resolve)(dir, dirent.name));
}
function getSuffixFromFileName(fileName) {
return (0, import_node_path.extname)(fileName).slice(1);
}
async function getLastSuffix(dir, ignorePattern) {
try {
const files = await getMigrationFilePaths(dir, { ignorePattern });
return files.length > 0 ? getSuffixFromFileName(files[files.length - 1]) : void 0;
} catch {
return void 0;
}
}
function getNumericPrefix(filename, logger = console) {
const prefix = filename.split(SEPARATOR)[0];
if (prefix && /^\d+$/.test(prefix)) {
if (prefix.length === 13) {
return Number(prefix);
}
if (prefix && prefix.length === 17) {
const year = prefix.slice(0, 4);
const month = prefix.slice(4, 6);
const date = prefix.slice(6, 8);
const hours = prefix.slice(8, 10);
const minutes = prefix.slice(10, 12);
const seconds = prefix.slice(12, 14);
const ms = prefix.slice(14, 17);
return (/* @__PURE__ */ new Date(
`${year}-${month}-${date}T${hours}:${minutes}:${seconds}.${ms}Z`
)).valueOf();
}
}
logger.error(`Can't determine timestamp for ${prefix}`);
return Number(prefix) || 0;
}
async function resolveSuffix(directory, options) {
const { language, ignorePattern } = options;
return language || await getLastSuffix(directory, ignorePattern) || "js";
}
class Migration {
constructor(db, migrationPath, { up, down }, options, typeShorthands, logger = console) {
__publicField(this, "db");
__publicField(this, "path");
__publicField(this, "name");
__publicField(this, "timestamp");
__publicField(this, "up");
__publicField(this, "down");
__publicField(this, "options");
__publicField(this, "typeShorthands");
__publicField(this, "logger");
this.db = db;
this.path = migrationPath;
this.name = (0, import_node_path.basename)(migrationPath, (0, import_node_path.extname)(migrationPath));
this.timestamp = getNumericPrefix(this.name, logger);
this.up = up;
this.down = down;
this.options = options;
this.typeShorthands = typeShorthands;
this.logger = logger;
}
// class method that creates a new migration file by cloning the migration template
static async create(name, directory, options = {}) {
const { filenameFormat = "timestamp" /* timestamp */ } = options;
await (0, import_promises.mkdir)(directory, { recursive: true });
const now = /* @__PURE__ */ new Date();
const time = filenameFormat === "utc" /* utc */ ? now.toISOString().replace(/\D/g, "") : now.valueOf();
const templateFileName = "templateFileName" in options ? (0, import_node_path.resolve)((0, import_node_process.cwd)(), options.templateFileName) : (0, import_node_path.resolve)(
(0, import_node_path.join)("node_modules", "node-pg-migrate", "templates"),
`migration-template.${await resolveSuffix(directory, options)}`
);
const suffix = getSuffixFromFileName(templateFileName);
const newFile = (0, import_node_path.join)(directory, `${time}${SEPARATOR}${name}.${suffix}`);
await new Promise((resolve2, reject) => {
(0, import_node_fs.createReadStream)(templateFileName).pipe((0, import_node_fs.createWriteStream)(newFile)).on("close", resolve2).on("error", reject);
});
return newFile;
}
_getMarkAsRun(action) {
const schema = (0, import_utils.getMigrationTableSchema)(this.options);
const { migrationsTable } = this.options;
const { name } = this;
switch (action) {
case this.down: {
this.logger.info(`### MIGRATION ${this.name} (DOWN) ###`);
return `DELETE FROM "${schema}"."${migrationsTable}" WHERE name='${name}';`;
}
case this.up: {
this.logger.info(`### MIGRATION ${this.name} (UP) ###`);
return `INSERT INTO "${schema}"."${migrationsTable}" (name, run_on) VALUES ('${name}', NOW());`;
}
default: {
throw new Error("Unknown direction");
}
}
}
async _apply(action, pgm) {
if (action.length === 2) {
await new Promise((resolve2) => {
action(pgm, resolve2);
});
} else {
await action(pgm);
}
const sqlSteps = pgm.getSqlSteps();
sqlSteps.push(this._getMarkAsRun(action));
if (!this.options.singleTransaction && pgm.isUsingTransaction()) {
sqlSteps.unshift("BEGIN;");
sqlSteps.push("COMMIT;");
} else if (this.options.singleTransaction && !pgm.isUsingTransaction()) {
this.logger.warn("#> WARNING: Need to break single transaction! <");
sqlSteps.unshift("COMMIT;");
sqlSteps.push("BEGIN;");
} else if (!this.options.singleTransaction || !pgm.isUsingTransaction()) {
this.logger.warn(
"#> WARNING: This migration is not wrapped in a transaction! <"
);
}
if (typeof this.logger.debug === "function") {
this.logger.debug(`${sqlSteps.join("\n")}
`);
}
return sqlSteps.reduce(
(promise, sql) => promise.then(() => this.options.dryRun || this.db.query(sql)),
Promise.resolve()
);
}
_getAction(direction) {
if (direction === "down" && this.down === void 0) {
this.down = this.up;
}
const action = this[direction];
if (action === false) {
throw new Error(
`User has disabled ${direction} migration on file: ${this.name}`
);
}
if (typeof action !== "function") {
throw new Error(
`Unknown value for direction: ${direction}. Is the migration ${this.name} exporting a '${direction}' function?`
);
}
return action;
}
apply(direction) {
const pgm = new import_migrationBuilder.default(
this.db,
this.typeShorthands,
Boolean(this.options.decamelize),
this.logger
);
const action = this._getAction(direction);
if (this.down === this.up) {
pgm.enableReverseMode();
}
return this._apply(action, pgm);
}
markAsRun(direction) {
return this.db.query(this._getMarkAsRun(this._getAction(direction)));
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FilenameFormat,
Migration,
getMigrationFilePaths,
getNumericPrefix
});