@oliasoft-open-source/node-json-migrator 2.0.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.
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getPlanRevisionsFromGit = exports.getPlanFromGit = exports.getHistoricalPlansFromGit = exports.exec = void 0;
7
+
8
+ var _chalk = _interopRequireDefault(require("chalk"));
9
+
10
+ var _child_process = require("child_process");
11
+
12
+ var _hash = require("../hash/hash");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ const exec = command => new Promise((resolve, reject) => {
17
+ const args = ['-c', command];
18
+ const thread = (0, _child_process.spawn)('bash', args, {
19
+ stdio: ['inherit', 'pipe', 'pipe']
20
+ });
21
+ const stdOut = [];
22
+ const stdErr = [];
23
+ thread.stdout.on('data', data => {
24
+ stdOut.push(data.toString());
25
+ });
26
+ thread.stderr.on('data', data => {
27
+ stdErr.push(data.toString());
28
+ });
29
+ thread.on('close', () => {
30
+ if (stdErr.length) {
31
+ reject(stdErr.join(''));
32
+ return;
33
+ }
34
+
35
+ resolve(stdOut.join(''));
36
+ });
37
+ });
38
+
39
+ exports.exec = exec;
40
+
41
+ const getPlanRevisionsFromGit = async planFilePath => {
42
+ const stdOut = await exec(`git rev-list --pretty='format:%H|%ad' HEAD ${planFilePath}`);
43
+ const lines = stdOut.split(/\r\n|\r|\n/).filter(r => r === null || r === void 0 ? void 0 : r.includes('|'));
44
+ return lines.map(line => {
45
+ const [version, timeStamp] = line.split('|');
46
+ const utcTimeStamp = new Date(timeStamp).toISOString();
47
+ return {
48
+ version,
49
+ timeStamp: utcTimeStamp
50
+ };
51
+ });
52
+ };
53
+
54
+ exports.getPlanRevisionsFromGit = getPlanRevisionsFromGit;
55
+
56
+ const getPlanFromGit = async (planFilePath, revision) => {
57
+ return exec(`git show ${revision}:${planFilePath}`);
58
+ };
59
+
60
+ exports.getPlanFromGit = getPlanFromGit;
61
+
62
+ const getHistoricalPlansFromGit = async planFilePath => {
63
+ try {
64
+ const revisions = await getPlanRevisionsFromGit(planFilePath);
65
+ const historicalPlans = await Promise.all(revisions.map(async revision => {
66
+ const plan = await getPlanFromGit(planFilePath, revision.version);
67
+ const version = (0, _hash.hash)(plan);
68
+ return {
69
+ revision,
70
+ version,
71
+ plan
72
+ };
73
+ }));
74
+ return {
75
+ historicalPlans
76
+ };
77
+ } catch (error) {
78
+ console.error(_chalk.default.red(error));
79
+ throw new Error('Unable to fetch plan.json history from git');
80
+ }
81
+ };
82
+
83
+ exports.getHistoricalPlansFromGit = getHistoricalPlansFromGit;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sortFilePathsByFilename = exports.getUniqueSkippedFileNames = exports.getSkippedFilePaths = exports.getMigrationFilePaths = void 0;
7
+
8
+ var _path = _interopRequireDefault(require("path"));
9
+
10
+ var _globPromise = _interopRequireDefault(require("glob-promise"));
11
+
12
+ var _lodash = require("lodash");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ const matchSkip = /(.skip|.skip\d+).js$/g;
17
+ /**
18
+ * Sorts migration file paths
19
+ *
20
+ * @param {Array<String>} filePaths
21
+ * @returns {Array<String>} sorted files
22
+ */
23
+
24
+ const sortFilePathsByFilename = filePaths => (0, _lodash.sortBy)(filePaths, file => {
25
+ return _path.default.parse(file).base;
26
+ });
27
+ /**
28
+ * Gets paths to entity migrations in a directory
29
+ *
30
+ * @param {String} pathToMigrations
31
+ * @returns {Promise<Array<String>>} matching migration filePaths
32
+ */
33
+
34
+
35
+ exports.sortFilePathsByFilename = sortFilePathsByFilename;
36
+
37
+ const getMigrationFilePaths = async pathToMigrations => {
38
+ const filePath = pattern => `${pathToMigrations}${pattern}`;
39
+
40
+ const filePaths = await (0, _globPromise.default)(filePath('/**/*.js'), {
41
+ ignore: [filePath('/**/*.skip?([0-9]*).js'), filePath('/**/index.js'), filePath('/**/plan.json'), filePath('/**/*.bundle.js'), filePath('/**/*.test.js'), filePath('/**/*.spec.js'), filePath('/**/__tests__/*'), filePath('/**/__test__/*')]
42
+ });
43
+ return sortFilePathsByFilename(filePaths);
44
+ };
45
+ /**
46
+ * Gets paths to skipped migrations in a directory
47
+ *
48
+ * @param {String} pathToMigrations
49
+ * @returns {Promise<Array<String>>} skipped migration filePaths
50
+ */
51
+
52
+
53
+ exports.getMigrationFilePaths = getMigrationFilePaths;
54
+
55
+ const getSkippedFilePaths = async pathToMigrations => {
56
+ const filePath = pattern => `${pathToMigrations}${pattern}`;
57
+
58
+ const filePaths = await (0, _globPromise.default)(filePath('/**/*.skip?([0-9]*).js'), {
59
+ ignore: [filePath('/**/index.js'), filePath('/**/plan.json'), filePath('/**/*.bundle.js'), filePath('/**/*.test.js'), filePath('/**/*.spec.js'), filePath('/**/__tests__/*'), filePath('/**/__test__/*')]
60
+ });
61
+ return sortFilePathsByFilename(filePaths);
62
+ };
63
+
64
+ exports.getSkippedFilePaths = getSkippedFilePaths;
65
+
66
+ const getUniqueSkippedFileNames = skippedPaths => {
67
+ const fileNames = skippedPaths.map(p => _path.default.parse(p).base.replace(matchSkip, '.js'));
68
+ return Array.from(new Set(fileNames));
69
+ };
70
+
71
+ exports.getUniqueSkippedFileNames = getUniqueSkippedFileNames;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.hash = void 0;
7
+
8
+ var crypto = _interopRequireWildcard(require("crypto"));
9
+
10
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
11
+
12
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13
+
14
+ /**
15
+ * Generate SHA256 hash from a string
16
+ *
17
+ * @param {String} string
18
+ * @returns {String} SHA256 hash
19
+ */
20
+ const hash = string => crypto.createHash('sha256').update(string).digest('hex');
21
+
22
+ exports.hash = hash;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.printVersionHistory = void 0;
7
+
8
+ var _chalk = _interopRequireDefault(require("chalk"));
9
+
10
+ var _git = require("../git/git");
11
+
12
+ var _database = require("../database/database");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ /**
17
+ * Parse plan.json
18
+ *
19
+ * @param {string} plan
20
+ * @returns object|null parsed plan
21
+ */
22
+ const parsePlan = plan => {
23
+ return (() => {
24
+ try {
25
+ return JSON.parse(plan);
26
+ } catch (e) {
27
+ console.error('Unable to parse plan.json');
28
+ return null;
29
+ }
30
+ })();
31
+ };
32
+ /**
33
+ * @typedef {Object} Configuration
34
+ * @property {string} directory (path to migrations directory)
35
+ * @property {string} entity (entity name for payload, e.g. dataset)
36
+ * @property {Object} database (connected pg-promise database)
37
+ */
38
+
39
+ /**
40
+ * Prints the version history of plan.json (for debugging)
41
+ *
42
+ * @param {Object} args
43
+ * @param {Configuration} args.config
44
+ */
45
+
46
+
47
+ const printVersionHistory = async ({
48
+ config
49
+ }) => {
50
+ const {
51
+ database,
52
+ entity,
53
+ directory
54
+ } = config;
55
+
56
+ try {
57
+ const planFilePath = `${directory}/plan.json`; //read version history from database cache
58
+
59
+ const versionHistoryFromDatabase = await (0, _database.getVersions)(database, entity);
60
+ const fullDatabaseVersionHistory = versionHistoryFromDatabase.map(d => {
61
+ const plan = parsePlan(d === null || d === void 0 ? void 0 : d.plan);
62
+ const finalMigration = plan !== null && plan !== void 0 && plan.length ? plan[plan.length - 1] : null;
63
+ return {
64
+ planVersion: d === null || d === void 0 ? void 0 : d.version,
65
+ lastFileName: (finalMigration === null || finalMigration === void 0 ? void 0 : finalMigration.fileName) || null,
66
+ lastSequence: (finalMigration === null || finalMigration === void 0 ? void 0 : finalMigration.sequence) || null
67
+ };
68
+ }); //read full version history from git logs (back to beginning of time)
69
+
70
+ const {
71
+ historicalPlans: fullHistoricalPlans
72
+ } = await (0, _git.getHistoricalPlansFromGit)(planFilePath);
73
+ const fullGitVersionHistory = fullHistoricalPlans.map(p => {
74
+ var _p$revision, _p$revision2;
75
+
76
+ const plan = parsePlan(p === null || p === void 0 ? void 0 : p.plan);
77
+ const finalMigration = plan !== null && plan !== void 0 && plan.length ? plan[plan.length - 1] : null;
78
+ return {
79
+ gitCommitHash: p === null || p === void 0 ? void 0 : (_p$revision = p.revision) === null || _p$revision === void 0 ? void 0 : _p$revision.version,
80
+ planVersion: p === null || p === void 0 ? void 0 : p.version,
81
+ timeStamp: p === null || p === void 0 ? void 0 : (_p$revision2 = p.revision) === null || _p$revision2 === void 0 ? void 0 : _p$revision2.timeStamp,
82
+ lastFileName: (finalMigration === null || finalMigration === void 0 ? void 0 : finalMigration.fileName) || null,
83
+ lastSequence: (finalMigration === null || finalMigration === void 0 ? void 0 : finalMigration.sequence) || null
84
+ };
85
+ });
86
+ const output = {
87
+ fullDatabaseVersionHistory,
88
+ fullGitVersionHistory
89
+ };
90
+ console.log(JSON.stringify(output, null, 2));
91
+ } catch (error) {
92
+ console.error(_chalk.default.red(error));
93
+ throw new Error('Unable to print debug history');
94
+ }
95
+ };
96
+
97
+ exports.printVersionHistory = printVersionHistory;
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "createMigration", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _create.createMigration;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "getPlannedMigrations", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _plan.getPlannedMigrations;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "getPlannedVersion", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _plannedVersion.getPlannedVersion;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "getVersions", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _database.getVersions;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "migrate", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _migrate.migrate;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "pipe", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _pipe.pipe;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "printVersionHistory", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _history.printVersionHistory;
46
+ }
47
+ });
48
+
49
+ var _create = require("./create/create");
50
+
51
+ var _plan = require("./plan/plan");
52
+
53
+ var _plannedVersion = require("./plan/planned-version");
54
+
55
+ var _migrate = require("./migrate/migrate");
56
+
57
+ var _pipe = require("./pipe/pipe");
58
+
59
+ var _database = require("./database/database");
60
+
61
+ var _history = require("./history/history");
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.migrate = void 0;
7
+
8
+ var _pipe = require("../pipe/pipe");
9
+
10
+ var _pending = require("../pending/pending");
11
+
12
+ /**
13
+ * @typedef {Array|Object} Payload
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} PlannedMigrations
18
+ * @property {Array<function>} plannedMigrations (planned migrations)
19
+ * @property {string} nextVersion (next payload version)
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} Configuration
24
+ * @property {string} directory (path to migrations directory)
25
+ * @property {string} entity (entity name for payload, e.g. dataset)
26
+ * @property {string} [version] (current payload version)
27
+ * @property {boolean} [force] (override validation)
28
+ * @property {boolean} [dry] (don't alter database or files)
29
+ * @property {Object} [database] (connected pg-promise database)
30
+ * @property {Object} [pgpHelpers] (pg-promise helpers)
31
+ * @property {PlannedMigrations} [plan]
32
+ */
33
+
34
+ /**
35
+ * Migrates a payload
36
+ *
37
+ * There are two ways to use this function:
38
+ * i) function looks up the pending migrations for you (default)
39
+ * ii) pass in pre-fetched pending migrators via config.pending
40
+ *
41
+ * @param {Object} args
42
+ * @param {Payload} args.payload
43
+ * @param {Configuration} args.config
44
+ * @returns Promise<{nextPayload: Payload, nextVersion: string}>
45
+ */
46
+ const migrate = async ({
47
+ payload,
48
+ config
49
+ }) => {
50
+ const {
51
+ pendingMigrators,
52
+ nextVersion
53
+ } = await (0, _pending.getPendingMigrators)({
54
+ config
55
+ });
56
+ const nextPayload = (0, _pipe.pipe)(pendingMigrators, payload);
57
+ return {
58
+ nextPayload,
59
+ nextVersion
60
+ };
61
+ };
62
+
63
+ exports.migrate = migrate;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getPendingMigrators = exports.getPendingMigrations = void 0;
7
+
8
+ var _chalk = _interopRequireDefault(require("chalk"));
9
+
10
+ var _plan = require("../plan/plan");
11
+
12
+ var _executed = require("../executed/executed");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ const getPendingMigrations = ({
17
+ plannedMigrations,
18
+ executedMigrations,
19
+ printPendingFileNames = false
20
+ }) => {
21
+ const executedMigrationFileNames = executedMigrations.map(e => e.fileName);
22
+ const pendingMigrations = plannedMigrations.filter(m => !executedMigrationFileNames.includes(m.fileName));
23
+
24
+ if (printPendingFileNames) {
25
+ pendingMigrations.forEach(m => {
26
+ console.log(_chalk.default.gray(` ${m.fileName}`));
27
+ });
28
+ }
29
+
30
+ return pendingMigrations.map(m => ({
31
+ fileName: m.fileName,
32
+ migrator: m.migrator
33
+ }));
34
+ };
35
+
36
+ exports.getPendingMigrations = getPendingMigrations;
37
+
38
+ const getPendingMigrators = async ({
39
+ config
40
+ }) => {
41
+ const {
42
+ directory,
43
+ database,
44
+ pgpHelpers,
45
+ entity,
46
+ version,
47
+ plan,
48
+ printPendingFileNames
49
+ } = config;
50
+ const {
51
+ plannedMigrations,
52
+ nextVersion
53
+ } = plan || (await (0, _plan.getPlannedMigrations)({
54
+ config
55
+ }));
56
+ const executedMigrations = database ? await (0, _executed.getExecutedMigrationsFromVersion)(database, pgpHelpers, entity, directory, version, nextVersion, plannedMigrations) : [];
57
+ const pendingMigrators = getPendingMigrations({
58
+ plannedMigrations,
59
+ executedMigrations,
60
+ printPendingFileNames
61
+ });
62
+ return {
63
+ pendingMigrators,
64
+ nextVersion
65
+ };
66
+ };
67
+
68
+ exports.getPendingMigrators = getPendingMigrators;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.pipe = void 0;
7
+
8
+ const errorMessage = (fileName, message, stack) => `Unhandled exception in file ${fileName}: ${message}
9
+
10
+ ${stack}
11
+ `;
12
+ /**
13
+ * Executes the pending migrators on a payload, returns modified payload
14
+ *
15
+ * Loosely based on:
16
+ * - Eric Elliott's pipe implementation https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d
17
+ * - lodash flow https://lodash.com/docs/4.17.15#flow
18
+ *
19
+ * Implementation includes exception handling to extend errors with the filename
20
+ * until module-with-string fixes its stack trace handling (see OW-8879 and
21
+ * https://github.com/exuanbo/module-from-string/issues/18)
22
+ *
23
+ * @param {Array<Object>} migrations
24
+ * @param {Object} payload
25
+ * @returns {Object} migratedPayload
26
+ */
27
+
28
+
29
+ const pipe = (migrations, payload) => migrations.reduce((v, m) => {
30
+ if (typeof (m === null || m === void 0 ? void 0 : m.migrator) !== 'function') {
31
+ throw new TypeError('Expected a function');
32
+ }
33
+
34
+ try {
35
+ return m.migrator(v);
36
+ } catch (error) {
37
+ throw new Error(errorMessage(m === null || m === void 0 ? void 0 : m.fileName, error.message, error.stack));
38
+ }
39
+ }, payload);
40
+
41
+ exports.pipe = pipe;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.setCachedPlannedVersion = exports.getCachedPlannedVersion = void 0;
7
+ let cachedPlannedVersion = null;
8
+
9
+ const getCachedPlannedVersion = () => cachedPlannedVersion;
10
+
11
+ exports.getCachedPlannedVersion = getCachedPlannedVersion;
12
+
13
+ const setCachedPlannedVersion = version => {
14
+ cachedPlannedVersion = version;
15
+ };
16
+
17
+ exports.setCachedPlannedVersion = setCachedPlannedVersion;