Skip to content

Commit aca46f0

Browse files
Tomas WinklerTomas Winkler
authored andcommitted
Run a range of migrations
1 parent 8027aa4 commit aca46f0

File tree

7 files changed

+139
-10
lines changed

7 files changed

+139
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ The supported commands are divided into groups according to their target, at thi
112112
export default migration;
113113
```
114114

115-
* `migration run` - Runs a migration script specified by file name (option `--name <file name>`), or runs multiple migration scripts in the order specified in the migration files (option `--all`).
115+
* `migration run` - Runs a migration script specified by file name (option `--name <file name>`), or runs multiple migration scripts in the order specified in the migration files (options `--all` or `--range`).
116116
* You can execute a migration against a specific project (options `--project <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>`) or environment stored in the local configuration file (option `--environment <YOUR_ENVIRONMENT_NAME>`).
117117
* After each run of a migration script, the CLI logs the execution into a status file. This file holds data for the next run to prevent running the same migration script more than once. You can choose to override this behavior, for example for debugging purposes, by using the `--force` parameter.
118118
* You can choose whether you want to keep executing the migration scripts even if one migration script fails (option `--continue-on-error`) or whether you want to run in the debug mode (option `--debug`) and get additional information for certain issues logged into the console.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kentico/kontent-cli",
3-
"version": "0.3.0",
3+
"version": "0.5.0",
44
"description": "Command line interface tool that can be used for generating and running Kontent migration scripts",
55
"main": "./lib/index.js",
66
"types": "./lib/types/index.d.ts",

src/cmds/migration/run.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import yargs from 'yargs';
22
import chalk from 'chalk';
3-
import { getDuplicates, getSuccessfullyExecutedMigrations, getMigrationFilepath, loadMigrationFiles, loadModule, runMigration } from '../../utils/migrationUtils';
3+
import { getDuplicates, getSuccessfullyExecutedMigrations, getMigrationFilepath, loadMigrationFiles, loadModule, runMigration, getMigrationsWithInvalidOrder } from '../../utils/migrationUtils';
44
import { fileExists, getFileWithExtension, isAllowedExtension } from '../../utils/fileUtils';
55
import { environmentConfigExists, getEnvironmentsConfig } from '../../utils/environmentUtils';
66
import { createManagementClient } from '../../managementClientFactory';
@@ -38,6 +38,11 @@ const runMigrationCommand: yargs.CommandModule = {
3838
describe: 'Run all migration scripts in the specified order',
3939
type: 'boolean',
4040
},
41+
range: {
42+
alias: 'r',
43+
describe: 'Run all migration scripts in the specified range, eg.: 3:5 will run migrations with the "order" property set to 3, 4 and 5',
44+
type: 'string',
45+
},
4146
force: {
4247
alias: 'f',
4348
describe: 'Enforces run of already executed scripts.',
@@ -58,6 +63,8 @@ const runMigrationCommand: yargs.CommandModule = {
5863
},
5964
})
6065
.conflicts('all', 'name')
66+
.conflicts('range', 'name')
67+
.conflicts('all', 'range')
6168
.conflicts('environment', 'api-key')
6269
.conflicts('environment', 'project-id')
6370
.check((args: any) => {
@@ -66,7 +73,11 @@ const runMigrationCommand: yargs.CommandModule = {
6673
}
6774

6875
if (!args.all) {
69-
if (args.name) {
76+
if (args.range) {
77+
if (!getRange(args.range)) {
78+
throw new Error(chalk.red(`The range has to be a string of a format "number:number" where the first number is less or equal to the second, eg.: "2:5".`));
79+
}
80+
} else if (args.name) {
7081
if (!isAllowedExtension(args.name)) {
7182
throw new Error(chalk.red(`File ${args.name} has not supported extension.`));
7283
}
@@ -76,7 +87,7 @@ const runMigrationCommand: yargs.CommandModule = {
7687
throw new Error(chalk.red(`Cannot find the specified migration script: ${migrationFilePath}.`));
7788
}
7889
} else {
79-
throw new Error(chalk.red('Either the migration script name or all migration options needs to be specified.'));
90+
throw new Error(chalk.red('Either the migration script name, range or all migration options needs to be specified.'));
8091
}
8192
}
8293

@@ -99,6 +110,7 @@ const runMigrationCommand: yargs.CommandModule = {
99110
let apiKey = argv.apiKey;
100111
const migrationName = argv.name;
101112
const runAll = argv.all;
113+
const runRange = getRange(argv.range);
102114
const debugMode = argv.debug;
103115
const continueOnError = argv.continueOnError;
104116
let migrationsResults: number = 0;
@@ -119,10 +131,15 @@ const runMigrationCommand: yargs.CommandModule = {
119131

120132
loadMigrationsExecutionStatus();
121133

122-
if (runAll) {
134+
if (runAll || runRange) {
123135
let migrationsToRun = await loadMigrationFiles();
124136

125137
checkForDuplicates(migrationsToRun);
138+
checkForInvalidOrder(migrationsToRun);
139+
140+
if (runRange) {
141+
migrationsToRun = getMigrationsByRange(migrationsToRun, runRange);
142+
}
126143

127144
if (runForce) {
128145
console.log('Skipping to check already executed migrations');
@@ -165,6 +182,17 @@ const runMigrationCommand: yargs.CommandModule = {
165182
},
166183
};
167184

185+
export const getRange = (range: string): [number, number] | null => {
186+
const match = range.match(/^([0-9]+):([0-9]+)$/);
187+
if (!match) {
188+
return null;
189+
}
190+
const from = Number(match[1]);
191+
const to = Number(match[2]);
192+
193+
return from <= to ? [from, to] : null;
194+
};
195+
168196
const checkForDuplicates = (migrationsToRun: IMigration[]): void => {
169197
const duplicateMigrationsOrder = getDuplicates(migrationsToRun, (migration) => migration.module.order);
170198

@@ -176,6 +204,29 @@ const checkForDuplicates = (migrationsToRun: IMigration[]): void => {
176204
}
177205
};
178206

207+
const getMigrationsByRange = (migrationsToRun: IMigration[], range: [number, number]): IMigration[] => {
208+
const migrations: IMigration[] = [];
209+
210+
for (const migration of migrationsToRun) {
211+
if (migration.module.order >= range[0] && migration.module.order <= range[1]) {
212+
migrations.push(migration);
213+
}
214+
}
215+
216+
return migrations.filter(String);
217+
};
218+
219+
const checkForInvalidOrder = (migrationsToRun: IMigration[]): void => {
220+
const migrationsWithInvalidOrder: IMigration[] = getMigrationsWithInvalidOrder(migrationsToRun);
221+
222+
if (migrationsWithInvalidOrder.length > 0) {
223+
console.log('Migration order has to be positive integer or zero:');
224+
migrationsWithInvalidOrder.map((migration) => console.error(chalk.red(`Migration: ${migration.name} order: ${migration.module.order}`)));
225+
226+
process.exit(1);
227+
}
228+
};
229+
179230
const skipExecutedMigrations = (migrations: IMigration[], projectId: string): IMigration[] => {
180231
const executedMigrations = getSuccessfullyExecutedMigrations(migrations, projectId);
181232
const result: IMigration[] = [];

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const createMigrationTool = (): number => {
1616
.example('kontent', 'migration run --name migration01 --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
1717
.example('kontent', 'migration run --name migration01 --environment DEV --debug true')
1818
.example('kontent', 'migration run --all --environment DEV')
19+
.example('kontent', 'migration run --range 1:4 --environment DEV')
1920

2021
.example('kontent', 'backup --action backup --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
2122
.example('kontent', 'backup --action backup --environment <YOUR_ENVIRONMENT>')
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { getMigrationsWithInvalidOrder } from '../utils/migrationUtils';
2+
3+
describe('Detection of invalid orders', () => {
4+
it('Finds migrations with invalid order', () => {
5+
const migrations = [
6+
{
7+
module: {
8+
order: -1,
9+
},
10+
name: 'test',
11+
},
12+
{
13+
module: {
14+
order: 1.1,
15+
},
16+
name: 'test',
17+
},
18+
{
19+
module: {
20+
order: 2,
21+
},
22+
name: 'test',
23+
},
24+
{
25+
module: {
26+
order: 'aaa',
27+
},
28+
name: 'test',
29+
},
30+
];
31+
32+
const result = getMigrationsWithInvalidOrder(migrations);
33+
34+
expect(result.length).toBe(3);
35+
expect(result[0].module.order).toBe(-1);
36+
expect(result[1].module.order).toBe(1.1);
37+
expect(result[2].module.order).toBe('aaa');
38+
});
39+
40+
it('No invalid order is found', () => {
41+
const migrations = [
42+
{
43+
module: {
44+
order: 1,
45+
name: 'test',
46+
},
47+
},
48+
{
49+
module: {
50+
order: 2,
51+
name: 'test',
52+
},
53+
},
54+
{
55+
module: {
56+
order: 3,
57+
name: 'test',
58+
},
59+
},
60+
];
61+
62+
const result = getMigrationsWithInvalidOrder(migrations);
63+
64+
expect(result.length).toBe(0);
65+
});
66+
});

src/utils/migrationUtils.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,18 @@ export const getDuplicates = <T extends any>(array: T[], key: (obj: T) => number
139139
return duplicates;
140140
};
141141

142+
export const getMigrationsWithInvalidOrder = <T extends any>(array: T[]): T[] => {
143+
const migrationsWithInvalidOrder: T[] = [];
144+
145+
for (const migration of array) {
146+
if (!Number.isInteger(migration.module.order) || Number(migration.module.order) < 0) {
147+
migrationsWithInvalidOrder.push(migration);
148+
}
149+
}
150+
151+
return migrationsWithInvalidOrder;
152+
};
153+
142154
export const loadModule = async (migrationFile: string): Promise<MigrationModule> => {
143155
const migrationPath = getMigrationFilepath(migrationFile);
144156

@@ -158,9 +170,7 @@ export const loadMigrationFiles = async (): Promise<IMigration[]> => {
158170
const files = listFiles('.js');
159171

160172
for (const file of files) {
161-
const migrationModule = await loadModule(file.name);
162-
163-
migrations.push({ name: file.name, module: migrationModule });
173+
migrations.push({ name: file.name, module: await loadModule(file.name) });
164174
}
165175

166176
return migrations.filter(String);

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"target": "es6",
44
"module": "commonjs",
55
"lib": [
6-
"esnext"
6+
"esnext",
7+
"DOM"
78
],
89
"sourceMap": true,
910
"allowJs": true,

0 commit comments

Comments
 (0)