diff --git a/readme.md b/readme.md index 061bf76..10d0170 100644 --- a/readme.md +++ b/readme.md @@ -131,6 +131,52 @@ const store = new Conf({ > Note: The version the migrations use refers to the **project version** by default. If you want to change this behavior, specify the [`projectVersion`](#projectVersion) option. +### beforeEachMigration + +Type: `Function`\ +Default: `undefined` + +The given callback function will be called before each migration step. + +The function receives the store as the first argument and a context object as the second argument with the following properties: + +- `fromVersion` - The version the migration step is being migrated from. +- `toVersion` - The version the migration step is being migrated to. +- `finalVersion` - The final version after all the migrations are applied. +- `versions` - All the versions with a migration step. + +This can be useful for logging purposes, preparing migration data, etc. + +Example: + +```js +const Conf = require('conf'); + +console.log = someLogger.log; + +const mainConfig = new Conf({ + beforeEachMigration: (store, context) => { + console.log(`[main-config] migrate from ${context.fromVersion} → ${context.toVersion}`); + }, + migrations: { + '0.4.0': store => { + store.set('debugPhase', true); + }, + } +}); + +const secondConfig = new Conf({ + beforeEachMigration: (store, context) => { + console.log(`[second-config] migrate from ${context.fromVersion} → ${context.toVersion}`); + }, + migrations: { + '1.0.1': store => { + store.set('debugPhase', true); + }, + } +}); +``` + #### configName Type: `string`\ diff --git a/source/index.ts b/source/index.ts index d50646d..28813b4 100644 --- a/source/index.ts +++ b/source/index.ts @@ -14,11 +14,11 @@ import debounceFn = require('debounce-fn'); import semver = require('semver'); import onetime = require('onetime'); import {JSONSchema} from 'json-schema-typed'; -import {Deserialize, Migrations, OnDidChangeCallback, Options, Serialize, Unsubscribe, Schema, OnDidAnyChangeCallback} from './types'; +import {Deserialize, Migrations, OnDidChangeCallback, Options, Serialize, Unsubscribe, Schema, OnDidAnyChangeCallback, BeforeEachMigrationCallback} from './types'; const encryptionAlgorithm = 'aes-256-cbc'; -const createPlainObject = (): T => { +const createPlainObject = >(): T => { return Object.create(null); }; @@ -141,7 +141,7 @@ class Conf = Record> implements I this.path = path.resolve(options.cwd, `${options.configName ?? 'config'}${fileExtension}`); const fileStore = this.store; - const store = Object.assign(createPlainObject(), options.defaults, fileStore); + const store = Object.assign(createPlainObject(), options.defaults, fileStore); this._validate(store); try { @@ -163,7 +163,7 @@ class Conf = Record> implements I throw new Error('Project version could not be inferred. Please specify the `projectVersion` option.'); } - this._migrate(options.migrations, options.projectVersion); + this._migrate(options.migrations, options.projectVersion, options.beforeEachMigration); } } @@ -499,7 +499,7 @@ class Conf = Record> implements I } } - private _migrate(migrations: Migrations, versionToMigrate: string): void { + private _migrate(migrations: Migrations, versionToMigrate: string, beforeEachMigration?: BeforeEachMigrationCallback): void { let previousMigratedVersion = this._get(MIGRATION_KEY, '0.0.0'); const newerVersions = Object.keys(migrations) @@ -509,6 +509,15 @@ class Conf = Record> implements I for (const version of newerVersions) { try { + if (beforeEachMigration) { + beforeEachMigration(this, { + fromVersion: previousMigratedVersion, + toVersion: version, + finalVersion: versionToMigrate, + versions: newerVersions + }); + } + const migration = migrations[version]; migration(this); diff --git a/source/types.ts b/source/types.ts index 21e77c3..0d874e1 100644 --- a/source/types.ts +++ b/source/types.ts @@ -103,6 +103,13 @@ export interface Options> { */ migrations?: Migrations; + /** + The given callback function will be called before each migration step. + + This can be useful for logging purposes, preparing migration data, etc. + */ + beforeEachMigration?: BeforeEachMigrationCallback; + /** __You most likely don't need this. Please don't use it unless you really have to.__ @@ -227,6 +234,14 @@ export interface Options> { export type Migrations> = Record) => void>; +export type BeforeEachMigrationContext = { + fromVersion: string; + toVersion: string; + finalVersion: string; + versions: string[]; +}; +export type BeforeEachMigrationCallback> = (store: Conf, context: BeforeEachMigrationContext) => void; + export type Schema = {[Property in keyof T]: ValueSchema}; export type ValueSchema = TypedJSONSchema; diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 066947d..6a7e49e 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -135,6 +135,11 @@ expectError(config.get('unicorn', 1)); // -- Migrations -- new Conf({ + beforeEachMigration: (store, context) => { + console.log(`[main-config] migrate from ${context.fromVersion} → ${context.toVersion}`); + console.log(`[main-config] final migration version ${context.finalVersion}, all migrations that were run or will be ran: ${context.versions.toString()}`); + console.log(`[main-config] phase ${(store.get('phase') || 'none') as string}`); + }, migrations: { '0.0.1': store => { store.set('debug phase', true); diff --git a/test/index.ts b/test/index.ts index 9203055..1449615 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1203,3 +1203,22 @@ test('__internal__ keys - should only match specific "__internal__" entry', t => conf.set('__internal__foo.you-shall', 'not-pass'); }); }); + +test('beforeEachMigration - should be called before every migration', t => { + const conf = new Conf({ + cwd: tempy.directory(), + projectVersion: '2.0.0', + beforeEachMigration: (store, context) => { + store.set(`beforeEachMigration ${context.fromVersion} → ${context.toVersion}`, true); + }, + migrations: { + '1.0.0': () => {}, + '1.0.1': () => {}, + '2.0.1': () => {} + } + }); + + t.true(conf.get('beforeEachMigration 0.0.0 → 1.0.0')); + t.true(conf.get('beforeEachMigration 1.0.0 → 1.0.1')); + t.false(conf.has('beforeEachMigration 1.0.1 → 2.0.1')); +});