feat(up): add --limit option to limit the number of migrations to run
This commit is contained in:
parent
bf4d596980
commit
02c142e39a
7 changed files with 176 additions and 5 deletions
|
|
@ -48,6 +48,10 @@ const up: Action = async (args) => {
|
|||
type: 'string',
|
||||
short: 's',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
short: 'l',
|
||||
},
|
||||
dry: {
|
||||
type: 'boolean',
|
||||
},
|
||||
|
|
@ -80,6 +84,7 @@ Options:
|
|||
-s, --storage <name> The storage to use for where to store the migration history (required)
|
||||
-p, --plugin <name> The plugin(s) to use (can be specified multiple times)
|
||||
-r, --reporter <name> The reporter to use for reporting the migration progress
|
||||
-l, --limit <count> Limit the number of migrations to run
|
||||
--dry List the pending migrations that would be run without actually running them
|
||||
--color Force color output (this option is passed to the reporter)
|
||||
--no-color Disable color output (this option is passed to the reporter)
|
||||
|
|
@ -104,15 +109,34 @@ Examples:
|
|||
storage = config.storage,
|
||||
reporter = config.reporter,
|
||||
dry,
|
||||
limit: limitString,
|
||||
import: imports = [],
|
||||
} = values;
|
||||
const plugins = [...(config.plugins ?? []), ...(values.plugin ?? [])];
|
||||
|
||||
const limit = limitString === undefined ? undefined : Number.parseInt(limitString, 10);
|
||||
|
||||
if (Number.isNaN(limit)) {
|
||||
console.error('Invalid limit value, expected an integer but was:', limitString);
|
||||
console.log(usage);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
await importAll(cwd, imports);
|
||||
|
||||
try {
|
||||
const { default: upCommand } = await import('./commands/up.js');
|
||||
process.exitCode = await upCommand({ storage, reporter, directory, plugins, cwd, dry, color: useColors(values) });
|
||||
process.exitCode = await upCommand({
|
||||
storage,
|
||||
reporter,
|
||||
directory,
|
||||
plugins,
|
||||
cwd,
|
||||
dry,
|
||||
limit,
|
||||
color: useColors(values),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof ShowUsageError) {
|
||||
console.error(error.message, '\n');
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ describe('up', () => {
|
|||
const failedEntry = toEntry('some_failed_migration.js', 'failed');
|
||||
const { reporter, run } = getUpCommand([failedEntry.name, 'some_file.js'], getStorage([failedEntry]));
|
||||
|
||||
const exitCode = await run(true);
|
||||
const exitCode = await run({ dry: true });
|
||||
|
||||
assert.strictEqual(exitCode, 1);
|
||||
assert.strictEqual(reporter.onInit.mock.calls.length, 1);
|
||||
|
|
@ -344,6 +344,88 @@ describe('up', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('returns 0 and finishes without an error when the given number of pending migrations are run successfully', async () => {
|
||||
const { reporter, run } = getUpCommand(
|
||||
['some_already_run_migration.js', 'some_migration.js', 'some_other_migration.js'],
|
||||
getStorage(['some_already_run_migration.js']),
|
||||
[
|
||||
{
|
||||
loadableExtensions: ['.js'],
|
||||
async loadMigration() {
|
||||
return async () => {
|
||||
// Success
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const exitCode = await run({ limit: 1 });
|
||||
|
||||
assert.strictEqual(exitCode, 0);
|
||||
assert.strictEqual(reporter.onInit.mock.calls.length, 1);
|
||||
assert.deepStrictEqual(reporter.onInit.mock.calls[0]?.arguments, [
|
||||
{
|
||||
command: 'up',
|
||||
cwd: '/emigrate',
|
||||
version,
|
||||
dry: false,
|
||||
color: undefined,
|
||||
directory: 'migrations',
|
||||
},
|
||||
]);
|
||||
assert.strictEqual(reporter.onCollectedMigrations.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onLockedMigrations.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onMigrationStart.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onMigrationSuccess.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 0);
|
||||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onFinished.mock.calls.length, 1);
|
||||
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
|
||||
assert.strictEqual(error, undefined);
|
||||
assert.strictEqual(entries?.length, 2);
|
||||
assert.deepStrictEqual(
|
||||
entries.map((entry) => `${entry.name} (${entry.status})`),
|
||||
['some_migration.js (done)', 'some_other_migration.js (skipped)'],
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 0 and finishes without an error with the given number of pending migrations are validated and listed successfully in dry-mode', async () => {
|
||||
const { reporter, run } = getUpCommand(
|
||||
['some_already_run_migration.js', 'some_migration.js', 'some_other_migration.js'],
|
||||
getStorage(['some_already_run_migration.js']),
|
||||
);
|
||||
|
||||
const exitCode = await run({ dry: true, limit: 1 });
|
||||
|
||||
assert.strictEqual(exitCode, 0);
|
||||
assert.strictEqual(reporter.onInit.mock.calls.length, 1);
|
||||
assert.deepStrictEqual(reporter.onInit.mock.calls[0]?.arguments, [
|
||||
{
|
||||
command: 'up',
|
||||
cwd: '/emigrate',
|
||||
version,
|
||||
dry: true,
|
||||
color: undefined,
|
||||
directory: 'migrations',
|
||||
},
|
||||
]);
|
||||
assert.strictEqual(reporter.onCollectedMigrations.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onLockedMigrations.mock.calls.length, 1);
|
||||
assert.strictEqual(reporter.onMigrationStart.mock.calls.length, 0);
|
||||
assert.strictEqual(reporter.onMigrationSuccess.mock.calls.length, 0);
|
||||
assert.strictEqual(reporter.onMigrationError.mock.calls.length, 0);
|
||||
assert.strictEqual(reporter.onMigrationSkip.mock.calls.length, 2);
|
||||
assert.strictEqual(reporter.onFinished.mock.calls.length, 1);
|
||||
const [entries, error] = reporter.onFinished.mock.calls[0]?.arguments ?? [];
|
||||
assert.strictEqual(error, undefined);
|
||||
assert.strictEqual(entries?.length, 2);
|
||||
assert.deepStrictEqual(
|
||||
entries.map((entry) => `${entry.name} (${entry.status})`),
|
||||
['some_migration.js (pending)', 'some_other_migration.js (skipped)'],
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 1 and finishes with an error when a pending migration throw when run', async () => {
|
||||
const { reporter, run } = getUpCommand(
|
||||
['some_already_run_migration.js', 'some_migration.js', 'fail.js', 'some_other_migration.js'],
|
||||
|
|
@ -495,7 +577,12 @@ function getUpCommand(migrationFiles: string[], storage?: Mocked<Storage>, plugi
|
|||
onMigrationSkip: mock.fn(noop),
|
||||
};
|
||||
|
||||
const run = async (dry = false) => {
|
||||
const run = async (
|
||||
options?: Omit<
|
||||
Parameters<typeof upCommand>[0],
|
||||
'cwd' | 'directory' | 'storage' | 'reporter' | 'plugins' | 'getMigrations'
|
||||
>,
|
||||
) => {
|
||||
return upCommand({
|
||||
cwd: '/emigrate',
|
||||
directory: 'migrations',
|
||||
|
|
@ -509,11 +596,11 @@ function getUpCommand(migrationFiles: string[], storage?: Mocked<Storage>, plugi
|
|||
},
|
||||
},
|
||||
reporter,
|
||||
dry,
|
||||
plugins: plugins ?? [],
|
||||
async getMigrations(cwd, directory) {
|
||||
return toMigrations(cwd, directory, migrationFiles);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { version } from '../get-package-info.js';
|
|||
type ExtraFlags = {
|
||||
cwd: string;
|
||||
dry?: boolean;
|
||||
limit?: number;
|
||||
getMigrations?: GetMigrationsFunction;
|
||||
};
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ export default async function upCommand({
|
|||
reporter: reporterConfig,
|
||||
directory,
|
||||
color,
|
||||
limit,
|
||||
dry = false,
|
||||
plugins = [],
|
||||
cwd,
|
||||
|
|
@ -83,6 +85,7 @@ export default async function upCommand({
|
|||
|
||||
const error = await migrationRunner({
|
||||
dry,
|
||||
limit,
|
||||
reporter,
|
||||
storage,
|
||||
migrations: await arrayFromAsync(collectedMigrations),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { getDuration } from './get-duration.js';
|
|||
|
||||
type MigrationRunnerParameters = {
|
||||
dry: boolean;
|
||||
limit?: number;
|
||||
reporter: EmigrateReporter;
|
||||
storage: Storage;
|
||||
migrations: Array<MigrationMetadata | MigrationMetadataFinished>;
|
||||
|
|
@ -24,6 +25,7 @@ type MigrationRunnerParameters = {
|
|||
|
||||
export const migrationRunner = async ({
|
||||
dry,
|
||||
limit,
|
||||
reporter,
|
||||
storage,
|
||||
migrations,
|
||||
|
|
@ -70,7 +72,12 @@ export const migrationRunner = async ({
|
|||
}
|
||||
}
|
||||
|
||||
const [lockedMigrations, lockError] = dry ? [migrationsToRun] : await exec(async () => storage.lock(migrationsToRun));
|
||||
const migrationsToLock = limit ? migrationsToRun.slice(0, limit) : migrationsToRun;
|
||||
const migrationsToSkip = limit ? migrationsToRun.slice(limit) : [];
|
||||
|
||||
const [lockedMigrations, lockError] = dry
|
||||
? [migrationsToLock]
|
||||
: await exec(async () => storage.lock(migrationsToLock));
|
||||
|
||||
if (lockError) {
|
||||
for await (const migration of migrationsToRun) {
|
||||
|
|
@ -152,6 +159,17 @@ export const migrationRunner = async ({
|
|||
}
|
||||
}
|
||||
|
||||
for await (const migration of migrationsToSkip) {
|
||||
const finishedMigration: MigrationMetadataFinished = {
|
||||
...migration,
|
||||
status: 'skipped',
|
||||
};
|
||||
|
||||
await reporter.onMigrationSkip?.(finishedMigration);
|
||||
|
||||
finishedMigrations.push(finishedMigration);
|
||||
}
|
||||
|
||||
const [, unlockError] = dry ? [] : await exec(async () => storage.unlock(lockedMigrations ?? []));
|
||||
|
||||
// eslint-disable-next-line unicorn/no-array-callback-reference
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue