Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beforeMigration option #167

Merged
merged 7 commits into from
Jul 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
}
});
Ciberusps marked this conversation as resolved.
Show resolved Hide resolved

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`\
Expand Down
19 changes: 14 additions & 5 deletions source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = unknown>(): T => {
const createPlainObject = <T = Record<string, unknown>>(): T => {
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
return Object.create(null);
};

Expand Down Expand Up @@ -141,7 +141,7 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> implements I
this.path = path.resolve(options.cwd, `${options.configName ?? 'config'}${fileExtension}`);

const fileStore = this.store;
const store = Object.assign(createPlainObject<T>(), options.defaults, fileStore);
const store = Object.assign(createPlainObject(), options.defaults, fileStore);
this._validate(store);

try {
Expand All @@ -163,7 +163,7 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> 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);
}
}

Expand Down Expand Up @@ -499,7 +499,7 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> implements I
}
}

private _migrate(migrations: Migrations<T>, versionToMigrate: string): void {
private _migrate(migrations: Migrations<T>, versionToMigrate: string, beforeEachMigration?: BeforeEachMigrationCallback<T>): void {
let previousMigratedVersion = this._get(MIGRATION_KEY, '0.0.0');

const newerVersions = Object.keys(migrations)
Expand All @@ -509,6 +509,15 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> 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);

Expand Down
15 changes: 15 additions & 0 deletions source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export interface Options<T extends Record<string, any>> {
*/
migrations?: Migrations<T>;

/**
The given callback function will be called before each migration step.

This can be useful for logging purposes, preparing migration data, etc.
*/
beforeEachMigration?: BeforeEachMigrationCallback<T>;

/**
__You most likely don't need this. Please don't use it unless you really have to.__

Expand Down Expand Up @@ -227,6 +234,14 @@ export interface Options<T extends Record<string, any>> {

export type Migrations<T extends Record<string, any>> = Record<string, (store: Conf<T>) => void>;

export type BeforeEachMigrationContext = {
fromVersion: string;
toVersion: string;
finalVersion: string;
versions: string[];
};
export type BeforeEachMigrationCallback<T extends Record<string, any>> = (store: Conf<T>, context: BeforeEachMigrationContext) => void;

export type Schema<T> = {[Property in keyof T]: ValueSchema};
export type ValueSchema = TypedJSONSchema;

Expand Down
5 changes: 5 additions & 0 deletions test/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ expectError<number>(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);
Expand Down
19 changes: 19 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});